001/* 002 * (C) Copyright 2006-2008 Nuxeo SA (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:[email protected]">Anahide Tchertchian</a> 018 * 019 * $Id: $ 020 */ 021 022package org.nuxeo.ecm.webapp.security; 023 024import static org.jboss.seam.ScopeType.PAGE; 025 026import java.io.Serializable; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032 033import javax.faces.convert.Converter; 034 035import org.apache.commons.lang3.StringUtils; 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038import org.jboss.seam.annotations.In; 039import org.jboss.seam.annotations.Name; 040import org.jboss.seam.annotations.Scope; 041import org.jboss.seam.annotations.web.RequestParameter; 042import org.jboss.seam.faces.FacesMessages; 043import org.jboss.seam.international.StatusMessage; 044import org.nuxeo.ecm.core.api.DocumentModel; 045import org.nuxeo.ecm.core.api.DocumentModelComparator; 046import org.nuxeo.ecm.core.api.DocumentModelList; 047import org.nuxeo.ecm.core.api.NuxeoGroup; 048import org.nuxeo.ecm.core.api.NuxeoPrincipal; 049import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 050import org.nuxeo.ecm.directory.SizeLimitExceededException; 051import org.nuxeo.ecm.platform.computedgroups.UserManagerWithComputedGroups; 052import org.nuxeo.ecm.platform.ui.web.component.list.UIEditableList; 053import org.nuxeo.ecm.platform.usermanager.UserManager; 054import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor; 055 056/** 057 * Methods to get user/groups suggestions from searches. 058 * 059 * @author Anahide Tchertchian 060 */ 061@Name("userSuggestionActions") 062@Scope(PAGE) 063public class UserSuggestionActionsBean implements Serializable { 064 065 private static final long serialVersionUID = 1L; 066 067 private static final Log log = LogFactory.getLog(UserSuggestionActionsBean.class); 068 069 public static final String USER_TYPE = "USER_TYPE"; 070 071 public static final String GROUP_TYPE = "GROUP_TYPE"; 072 073 public static final String TYPE_KEY_NAME = "type"; 074 075 public static final String PREFIXED_ID_KEY_NAME = "prefixed_id"; 076 077 public static final String ID_KEY_NAME = "id"; 078 079 public static final String ENTRY_KEY_NAME = "entry"; 080 081 @In(create = true) 082 protected transient UserManager userManager; 083 084 @In(create = true, required = false) 085 protected transient FacesMessages facesMessages; 086 087 @In(create = true) 088 protected transient ResourcesAccessor resourcesAccessor; 089 090 @RequestParameter 091 protected String userSuggestionSearchType; 092 093 protected String cachedUserSuggestionSearchType; 094 095 @RequestParameter 096 protected Integer userSuggestionMaxSearchResults; 097 098 @RequestParameter 099 protected Boolean hideVirtualGroups; 100 101 protected Integer cachedUserSuggestionMaxSearchResults; 102 103 protected Object cachedInput; 104 105 protected Object cachedSuggestions; 106 107 @RequestParameter 108 protected String userSuggestionMessageId; 109 110 /** 111 * Id of the editable list component where selection ids are put. 112 * <p> 113 * Component must be an instance of {@link UIEditableList} 114 */ 115 @RequestParameter 116 protected String suggestionSelectionListId; 117 118 protected void addSearchOverflowMessage() { 119 if (userSuggestionMessageId != null) { 120 facesMessages.addToControl(userSuggestionMessageId, StatusMessage.Severity.ERROR, 121 resourcesAccessor.getMessages().get("label.security.searchOverFlow")); 122 } else { 123 log.error("Search overflow"); 124 } 125 } 126 127 public List<DocumentModel> getGroupsSuggestions(Object input) { 128 try { 129 Map<String, DocumentModel> uniqueGroups = new HashMap<String, DocumentModel>(); 130 131 String pattern = (String) input; 132 for (String field : userManager.getGroupSearchFields()) { 133 // XXX: this doesn't fetch group members (references) 134 Map<String, Serializable> filter = new HashMap<String, Serializable>(); 135 136 if (StringUtils.isNotEmpty(pattern)) { 137 filter.put(field, pattern); 138 } 139 if (Boolean.TRUE.equals(hideVirtualGroups)) { 140 filter.put(UserManagerWithComputedGroups.VIRTUAL_GROUP_MARKER, false); 141 } 142 143 for (DocumentModel group : userManager.searchGroups(filter, filter.keySet())) { 144 uniqueGroups.put(group.getId(), group); 145 } 146 } 147 148 DocumentModelList groups = new DocumentModelListImpl(); 149 groups.addAll(uniqueGroups.values()); 150 Collections.sort(groups, new DocumentModelComparator(userManager.getGroupSchemaName(), getGroupsOrderBy())); 151 return groups; 152 } catch (SizeLimitExceededException e) { 153 addSearchOverflowMessage(); 154 return Collections.emptyList(); 155 } 156 } 157 158 protected Map<String, String> getGroupsOrderBy() { 159 Map<String, String> order = new HashMap<String, String>(); 160 order.put(userManager.getGroupLabelField(), DocumentModelComparator.ORDER_ASC); 161 return order; 162 } 163 164 public List<DocumentModel> getUserSuggestions(Object input) { 165 try { 166 String searchPattern = (String) input; 167 return userManager.searchUsers(searchPattern); 168 } catch (SizeLimitExceededException e) { 169 addSearchOverflowMessage(); 170 return Collections.emptyList(); 171 } 172 } 173 174 protected boolean equals(Object item1, Object item2) { 175 if (item1 == null && item2 == null) { 176 return true; 177 } else if (item1 == null) { 178 return false; 179 } else { 180 return item1.equals(item2); 181 } 182 } 183 184 public Object getSuggestions(Object input) { 185 if (equals(cachedUserSuggestionSearchType, userSuggestionSearchType) 186 && equals(cachedUserSuggestionMaxSearchResults, userSuggestionMaxSearchResults) 187 && equals(cachedInput, input)) { 188 return cachedSuggestions; 189 } 190 191 List<DocumentModel> users = Collections.emptyList(); 192 if (USER_TYPE.equals(userSuggestionSearchType) || StringUtils.isEmpty(userSuggestionSearchType)) { 193 users = getUserSuggestions(input); 194 } 195 196 List<DocumentModel> groups = Collections.emptyList(); 197 if (GROUP_TYPE.equals(userSuggestionSearchType) || StringUtils.isEmpty(userSuggestionSearchType)) { 198 groups = getGroupsSuggestions(input); 199 } 200 201 int userSize = users.size(); 202 int groupSize = groups.size(); 203 int totalSize = userSize + groupSize; 204 205 if (userSuggestionMaxSearchResults != null && userSuggestionMaxSearchResults > 0) { 206 if (userSize > userSuggestionMaxSearchResults || groupSize > userSuggestionMaxSearchResults 207 || totalSize > userSuggestionMaxSearchResults) { 208 addSearchOverflowMessage(); 209 return null; 210 } 211 } 212 213 List<Map<String, Object>> result = new ArrayList<Map<String, Object>>(totalSize); 214 215 for (DocumentModel user : users) { 216 Map<String, Object> entry = new HashMap<String, Object>(); 217 entry.put(TYPE_KEY_NAME, USER_TYPE); 218 entry.put(ENTRY_KEY_NAME, user); 219 String userId = user.getId(); 220 entry.put(ID_KEY_NAME, userId); 221 entry.put(PREFIXED_ID_KEY_NAME, NuxeoPrincipal.PREFIX + userId); 222 result.add(entry); 223 } 224 225 for (DocumentModel group : groups) { 226 Map<String, Object> entry = new HashMap<String, Object>(); 227 entry.put(TYPE_KEY_NAME, GROUP_TYPE); 228 entry.put(ENTRY_KEY_NAME, group); 229 String groupId = group.getId(); 230 entry.put(ID_KEY_NAME, groupId); 231 entry.put(PREFIXED_ID_KEY_NAME, NuxeoGroup.PREFIX + groupId); 232 result.add(entry); 233 } 234 235 cachedInput = input; 236 cachedUserSuggestionSearchType = userSuggestionSearchType; 237 cachedUserSuggestionMaxSearchResults = userSuggestionMaxSearchResults; 238 cachedSuggestions = result; 239 240 return result; 241 } 242 243 // XXX: needs optimisation 244 public Map<String, Object> getPrefixedUserInfo(String id) { 245 Map<String, Object> res = new HashMap<String, Object>(); 246 res.put(PREFIXED_ID_KEY_NAME, id); 247 if (!StringUtils.isBlank(id)) { 248 if (id.startsWith(NuxeoPrincipal.PREFIX)) { 249 res.put(TYPE_KEY_NAME, USER_TYPE); 250 String username = id.substring(NuxeoPrincipal.PREFIX.length()); 251 res.put(ID_KEY_NAME, username); 252 res.put(ENTRY_KEY_NAME, userManager.getUserModel(username)); 253 } else if (id.startsWith(NuxeoGroup.PREFIX)) { 254 res.put(TYPE_KEY_NAME, GROUP_TYPE); 255 String groupname = id.substring(NuxeoGroup.PREFIX.length()); 256 res.put(ID_KEY_NAME, groupname); 257 res.put(ENTRY_KEY_NAME, userManager.getGroupModel(groupname)); 258 } else { 259 res.putAll(getUserInfo(id)); 260 } 261 } 262 return res; 263 } 264 265 // XXX: needs optimisation 266 public Map<String, Object> getUserInfo(String id) { 267 Map<String, Object> res = new HashMap<String, Object>(); 268 res.put(ID_KEY_NAME, id); 269 if (!StringUtils.isBlank(id)) { 270 if (userManager.getGroup(id) != null) { 271 // group 272 res.put(PREFIXED_ID_KEY_NAME, NuxeoGroup.PREFIX + id); 273 res.put(TYPE_KEY_NAME, GROUP_TYPE); 274 res.put(ENTRY_KEY_NAME, userManager.getGroupModel(id)); 275 } else { 276 // user 277 res.put(PREFIXED_ID_KEY_NAME, NuxeoPrincipal.PREFIX + id); 278 res.put(TYPE_KEY_NAME, USER_TYPE); 279 res.put(ENTRY_KEY_NAME, userManager.getUserModel(id)); 280 } 281 } 282 return res; 283 } 284 285 public Converter getUserConverter() { 286 return new UserDisplayConverter(); 287 } 288 289}