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