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