001/*
002 * (C) Copyright 2013-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     <a href="mailto:grenard@nuxeo.com">Guillaume</a>
018 */
019package org.nuxeo.ecm.automation.core.operations.users;
020
021import java.io.IOException;
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Locale;
028import java.util.Map;
029
030import org.apache.commons.lang3.StringUtils;
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.nuxeo.common.utils.i18n.I18NUtils;
034import org.nuxeo.ecm.automation.OperationContext;
035import org.nuxeo.ecm.automation.core.Constants;
036import org.nuxeo.ecm.automation.core.annotations.Context;
037import org.nuxeo.ecm.automation.core.annotations.Operation;
038import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
039import org.nuxeo.ecm.automation.core.annotations.Param;
040import org.nuxeo.ecm.automation.features.SuggestConstants;
041import org.nuxeo.ecm.core.api.Blob;
042import org.nuxeo.ecm.core.api.Blobs;
043import org.nuxeo.ecm.core.api.DocumentModel;
044import org.nuxeo.ecm.core.api.DocumentModelList;
045import org.nuxeo.ecm.core.api.NuxeoGroup;
046import org.nuxeo.ecm.core.api.NuxeoPrincipal;
047import org.nuxeo.ecm.core.schema.SchemaManager;
048import org.nuxeo.ecm.core.schema.types.Field;
049import org.nuxeo.ecm.core.schema.types.QName;
050import org.nuxeo.ecm.core.schema.types.Schema;
051import org.nuxeo.ecm.directory.Directory;
052import org.nuxeo.ecm.directory.SizeLimitExceededException;
053import org.nuxeo.ecm.directory.api.DirectoryService;
054import org.nuxeo.ecm.platform.usermanager.UserAdapter;
055import org.nuxeo.ecm.platform.usermanager.UserManager;
056
057/**
058 * SuggestUser Operation.
059 *
060 * @since 5.7.3
061 */
062@Operation(id = SuggestUserEntries.ID, category = Constants.CAT_SERVICES, label = "Get user/group suggestion", description = "Get the user/group list of the running instance. This is returning a blob containing a serialized JSON array..", addToStudio = false)
063public class SuggestUserEntries {
064
065    @SuppressWarnings("unused")
066    private static final Log log = LogFactory.getLog(SuggestUserEntries.class);
067
068    public static final String ID = "UserGroup.Suggestion";
069
070    public static final String POWERUSERS = "powerusers";
071
072    @Context
073    protected OperationContext ctx;
074
075    @Context
076    protected SchemaManager schemaManager;
077
078    @Param(name = "searchTerm", alias = "prefix", required = false)
079    protected String prefix;
080
081    @Param(name = "searchType", required = false)
082    protected String searchType;
083
084    @Param(name = "groupRestriction", required = false, description = "Enter the id of a group to suggest only user from this group.")
085    protected String groupRestriction;
086
087    /**
088     * @since 7.10
089     */
090    @Param(name = "hideAdminGroups", required = false, description = "If set, remove all administrator groups from the suggestions")
091    protected boolean hideAdminGroups;
092
093    /**
094     * @since 8.3
095     */
096    @Param(name = "hidePowerUsersGroup", required = false, description = "If set, remove power users group from the suggestions")
097    protected boolean hidePowerUsersGroup;
098
099    @Param(name = "userSuggestionMaxSearchResults", required = false)
100    protected Integer userSuggestionMaxSearchResults;
101
102    @Param(name = "firstLabelField", required = false)
103    protected String firstLabelField;
104
105    @Param(name = "secondLabelField", required = false)
106    protected String secondLabelField;
107
108    @Param(name = "thirdLabelField", required = false)
109    protected String thirdLabelField;
110
111    @Param(name = "hideFirstLabel", required = false)
112    protected boolean hideFirstLabel = false;
113
114    @Param(name = "hideSecondLabel", required = false)
115    protected boolean hideSecondLabel = false;
116
117    @Param(name = "hideThirdLabel", required = false)
118    protected boolean hideThirdLabel;
119
120    @Param(name = "displayEmailInSuggestion", required = false)
121    protected boolean displayEmailInSuggestion;
122
123    @Param(name = "hideIcon", required = false)
124    protected boolean hideIcon;
125
126    @Context
127    protected UserManager userManager;
128
129    @Context
130    protected DirectoryService directoryService;
131
132    @Param(name = "lang", required = false)
133    protected String lang;
134
135    @OperationMethod
136    public Blob run() throws IOException {
137        List<Map<String, Object>> result = new ArrayList<>();
138        boolean isGroupRestriction = !StringUtils.isBlank(groupRestriction);
139        boolean groupOnly = false;
140        boolean userOnly = isGroupRestriction;
141
142        if (!isGroupRestriction && searchType != null && !searchType.isEmpty()) {
143            if (searchType.equals(SuggestConstants.USER_TYPE)) {
144                userOnly = true;
145            } else if (searchType.equals(SuggestConstants.GROUP_TYPE)) {
146                groupOnly = true;
147            }
148        }
149        try {
150            DocumentModelList userList = null;
151            DocumentModelList groupList = null;
152            if (!groupOnly) {
153                Schema schema = schemaManager.getSchema(userManager.getUserSchemaName());
154                userList = userManager.searchUsers(prefix);
155                Directory userDir = directoryService.getDirectory(userManager.getUserDirectoryName());
156                for (DocumentModel user : userList) {
157                    Map<String, Object> obj = new LinkedHashMap<>();
158                    for (Field field : schema.getFields()) {
159                        QName fieldName = field.getName();
160                        String key = fieldName.getLocalName();
161                        Serializable value = user.getPropertyValue(fieldName.getPrefixedName());
162                        if (key.equals(userDir.getPasswordField())) {
163                            continue;
164                        }
165                        obj.put(key, value);
166                    }
167                    String userId = user.getId();
168                    obj.put(SuggestConstants.ID, userId);
169                    obj.put(SuggestConstants.TYPE_KEY_NAME, SuggestConstants.USER_TYPE);
170                    obj.put(SuggestConstants.PREFIXED_ID_KEY_NAME, NuxeoPrincipal.PREFIX + userId);
171                    SuggestConstants.computeUserLabel(obj, firstLabelField, secondLabelField, thirdLabelField,
172                            hideFirstLabel, hideSecondLabel, hideThirdLabel, displayEmailInSuggestion, userId);
173                    SuggestConstants.computeUserGroupIcon(obj, hideIcon);
174                    if (isGroupRestriction) {
175                        // We need to load all data about the user particularly
176                        // its
177                        // groups.
178                        user = userManager.getUserModel(userId);
179                        UserAdapter userAdapter = user.getAdapter(UserAdapter.class);
180                        List<String> groups = userAdapter.getGroups();
181                        if (groups != null && groups.contains(groupRestriction)) {
182                            result.add(obj);
183                        }
184                    } else {
185                        result.add(obj);
186                    }
187                }
188            }
189            if (!userOnly) {
190                Schema schema = schemaManager.getSchema(userManager.getGroupSchemaName());
191                groupList = userManager.searchGroups(prefix);
192                List<String> admins = new ArrayList<>();
193                if (hideAdminGroups) {
194                    admins = userManager.getAdministratorsGroups();
195                }
196                groupLoop: for (DocumentModel group : groupList) {
197                    if (hideAdminGroups) {
198                        for (String adminGroupName : admins) {
199                            if (adminGroupName.equals(group.getId())) {
200                                break groupLoop;
201                            }
202                        }
203                    }
204                    if (hidePowerUsersGroup) {
205                        if (POWERUSERS.equals(group.getId())) {
206                            break groupLoop;
207                        }
208                    }
209                    Map<String, Object> obj = new LinkedHashMap<>();
210                    for (Field field : schema.getFields()) {
211                        QName fieldName = field.getName();
212                        String key = fieldName.getLocalName();
213                        Serializable value = group.getPropertyValue(fieldName.getPrefixedName());
214                        obj.put(key, value);
215                    }
216                    String groupId = group.getId();
217                    obj.put(SuggestConstants.ID, groupId);
218                    // If the group hasn't an label, let's put the groupid
219                    SuggestConstants.computeGroupLabel(obj, groupId, userManager.getGroupLabelField(), hideFirstLabel);
220                    obj.put(SuggestConstants.TYPE_KEY_NAME, SuggestConstants.GROUP_TYPE);
221                    obj.put(SuggestConstants.PREFIXED_ID_KEY_NAME, NuxeoGroup.PREFIX + groupId);
222                    SuggestConstants.computeUserGroupIcon(obj, hideIcon);
223                    result.add(obj);
224                }
225            }
226
227            // Limit size results.
228            int userSize = userList != null ? userList.size() : 0;
229            int groupSize = groupList != null ? groupList.size() : 0;
230            int totalSize = userSize + groupSize;
231            if (userSuggestionMaxSearchResults != null && userSuggestionMaxSearchResults > 0) {
232                if (userSize > userSuggestionMaxSearchResults || groupSize > userSuggestionMaxSearchResults
233                        || totalSize > userSuggestionMaxSearchResults) {
234                    throw new SizeLimitExceededException();
235                }
236            }
237
238        } catch (SizeLimitExceededException e) {
239            return searchOverflowMessage();
240        }
241
242        return Blobs.createJSONBlobFromValue(result);
243    }
244
245    /**
246     * @return searchOverflowMessage
247     * @since 5.7.3
248     */
249    private Blob searchOverflowMessage() throws IOException {
250        String label = I18NUtils.getMessageString("messages", "label.security.searchOverFlow", new Object[0],
251                getLocale());
252        Map<String, Object> obj = Collections.singletonMap(SuggestConstants.LABEL, label);
253        return Blobs.createJSONBlobFromValue(Collections.singletonList(obj));
254    }
255
256    protected String getLang() {
257        if (lang == null) {
258            lang = (String) ctx.get("lang");
259            if (lang == null) {
260                lang = SuggestConstants.DEFAULT_LANG;
261            }
262        }
263        return lang;
264    }
265
266    protected Locale getLocale() {
267        return new Locale(getLang());
268    }
269
270}