001/* 002 * (C) Copyright 2006-2014 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 * George Lefter 018 * Florent Guillaume 019 * Anahide Tchertchian 020 * Gagnavarslan ehf 021 */ 022package org.nuxeo.ecm.platform.usermanager; 023 024import java.io.Serializable; 025import java.security.Principal; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collections; 029import java.util.HashMap; 030import java.util.HashSet; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.Map; 034import java.util.Map.Entry; 035import java.util.Set; 036import java.util.regex.Matcher; 037import java.util.regex.Pattern; 038 039import org.apache.commons.codec.digest.DigestUtils; 040import org.apache.commons.lang.StringUtils; 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.nuxeo.ecm.core.api.DocumentModel; 044import org.nuxeo.ecm.core.api.DocumentModelComparator; 045import org.nuxeo.ecm.core.api.DocumentModelList; 046import org.nuxeo.ecm.core.api.NuxeoException; 047import org.nuxeo.ecm.core.api.NuxeoGroup; 048import org.nuxeo.ecm.core.api.NuxeoPrincipal; 049import org.nuxeo.ecm.core.api.PropertyException; 050import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 051import org.nuxeo.ecm.core.api.impl.NuxeoGroupImpl; 052import org.nuxeo.ecm.core.api.local.ClientLoginModule; 053import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 054import org.nuxeo.ecm.core.api.security.ACE; 055import org.nuxeo.ecm.core.api.security.ACL; 056import org.nuxeo.ecm.core.api.security.ACP; 057import org.nuxeo.ecm.core.api.security.AdministratorGroupsProvider; 058import org.nuxeo.ecm.core.api.security.PermissionProvider; 059import org.nuxeo.ecm.core.api.security.SecurityConstants; 060import org.nuxeo.ecm.core.cache.Cache; 061import org.nuxeo.ecm.core.cache.CacheService; 062import org.nuxeo.ecm.core.event.EventProducer; 063import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 064import org.nuxeo.ecm.core.event.impl.UnboundEventContext; 065import org.nuxeo.ecm.directory.BaseSession; 066import org.nuxeo.ecm.directory.DirectoryException; 067import org.nuxeo.ecm.directory.Session; 068import org.nuxeo.ecm.directory.api.DirectoryService; 069import org.nuxeo.ecm.platform.usermanager.exceptions.GroupAlreadyExistsException; 070import org.nuxeo.ecm.platform.usermanager.exceptions.UserAlreadyExistsException; 071import org.nuxeo.runtime.api.Framework; 072import org.nuxeo.runtime.services.event.Event; 073import org.nuxeo.runtime.services.event.EventService; 074 075/** 076 * Standard implementation of the Nuxeo UserManager. 077 */ 078public class UserManagerImpl implements UserManager, MultiTenantUserManager, AdministratorGroupsProvider { 079 080 private static final long serialVersionUID = 1L; 081 082 private static final Log log = LogFactory.getLog(UserManagerImpl.class); 083 084 public static final String USERMANAGER_TOPIC = "usermanager"; 085 086 /** Used by JaasCacheFlusher. */ 087 public static final String USERCHANGED_EVENT_ID = "user_changed"; 088 089 public static final String USERCREATED_EVENT_ID = "user_created"; 090 091 public static final String USERDELETED_EVENT_ID = "user_deleted"; 092 093 public static final String USERMODIFIED_EVENT_ID = "user_modified"; 094 095 /** Used by JaasCacheFlusher. */ 096 public static final String GROUPCHANGED_EVENT_ID = "group_changed"; 097 098 public static final String GROUPCREATED_EVENT_ID = "group_created"; 099 100 public static final String GROUPDELETED_EVENT_ID = "group_deleted"; 101 102 public static final String GROUPMODIFIED_EVENT_ID = "group_modified"; 103 104 public static final String DEFAULT_ANONYMOUS_USER_ID = "Anonymous"; 105 106 public static final String VIRTUAL_FIELD_FILTER_PREFIX = "__"; 107 108 public static final String INVALIDATE_PRINCIPAL_EVENT_ID = "invalidatePrincipal"; 109 110 public static final String INVALIDATE_ALL_PRINCIPALS_EVENT_ID = "invalidateAllPrincipals"; 111 112 private static final String USER_GROUP_CATEGORY = "userGroup"; 113 114 protected final DirectoryService dirService; 115 116 protected final CacheService cacheService; 117 118 protected Cache principalCache = null; 119 120 public UserMultiTenantManagement multiTenantManagement = new DefaultUserMultiTenantManagement(); 121 122 /** 123 * A structure used to inject field name configuration of users schema into a NuxeoPrincipalImpl instance. TODO not 124 * all fields inside are configurable for now - they will use default values 125 */ 126 protected UserConfig userConfig; 127 128 protected String userDirectoryName; 129 130 protected String userSchemaName; 131 132 protected String userIdField; 133 134 protected String userEmailField; 135 136 protected Map<String, MatchType> userSearchFields; 137 138 protected String groupDirectoryName; 139 140 protected String groupSchemaName; 141 142 protected String groupIdField; 143 144 protected String groupLabelField; 145 146 protected String groupMembersField; 147 148 protected String groupSubGroupsField; 149 150 protected String groupParentGroupsField; 151 152 protected String groupSortField; 153 154 protected Map<String, MatchType> groupSearchFields; 155 156 protected String defaultGroup; 157 158 protected List<String> administratorIds; 159 160 protected List<String> administratorGroups; 161 162 protected Boolean disableDefaultAdministratorsGroup; 163 164 protected String userSortField; 165 166 protected String userListingMode; 167 168 protected String groupListingMode; 169 170 protected Pattern userPasswordPattern; 171 172 protected VirtualUser anonymousUser; 173 174 protected String digestAuthDirectory; 175 176 protected String digestAuthRealm; 177 178 protected final Map<String, VirtualUserDescriptor> virtualUsers; 179 180 public UserManagerImpl() { 181 dirService = Framework.getLocalService(DirectoryService.class); 182 cacheService = Framework.getLocalService(CacheService.class); 183 virtualUsers = new HashMap<String, VirtualUserDescriptor>(); 184 userConfig = new UserConfig(); 185 } 186 187 @Override 188 public void setConfiguration(UserManagerDescriptor descriptor) { 189 defaultGroup = descriptor.defaultGroup; 190 administratorIds = descriptor.defaultAdministratorIds; 191 disableDefaultAdministratorsGroup = false; 192 if (descriptor.disableDefaultAdministratorsGroup != null) { 193 disableDefaultAdministratorsGroup = descriptor.disableDefaultAdministratorsGroup; 194 } 195 administratorGroups = new ArrayList<String>(); 196 if (!disableDefaultAdministratorsGroup) { 197 administratorGroups.add(SecurityConstants.ADMINISTRATORS); 198 } 199 if (descriptor.administratorsGroups != null) { 200 administratorGroups.addAll(descriptor.administratorsGroups); 201 } 202 if (administratorGroups.isEmpty()) { 203 log.warn("No administrators group has been defined: at least one should be set" 204 + " to avoid lockups when blocking rights for instance"); 205 } 206 userSortField = descriptor.userSortField; 207 groupSortField = descriptor.groupSortField; 208 userListingMode = descriptor.userListingMode; 209 groupListingMode = descriptor.groupListingMode; 210 userEmailField = descriptor.userEmailField; 211 userSearchFields = descriptor.userSearchFields; 212 userPasswordPattern = descriptor.userPasswordPattern; 213 groupLabelField = descriptor.groupLabelField; 214 groupMembersField = descriptor.groupMembersField; 215 groupSubGroupsField = descriptor.groupSubGroupsField; 216 groupParentGroupsField = descriptor.groupParentGroupsField; 217 groupSearchFields = descriptor.groupSearchFields; 218 anonymousUser = descriptor.anonymousUser; 219 220 setUserDirectoryName(descriptor.userDirectoryName); 221 setGroupDirectoryName(descriptor.groupDirectoryName); 222 setVirtualUsers(descriptor.virtualUsers); 223 224 digestAuthDirectory = descriptor.digestAuthDirectory; 225 digestAuthRealm = descriptor.digestAuthRealm; 226 227 userConfig = new UserConfig(); 228 userConfig.emailKey = userEmailField; 229 userConfig.schemaName = userSchemaName; 230 userConfig.nameKey = userIdField; 231 232 if (cacheService != null && descriptor.userCacheName != null) { 233 principalCache = cacheService.getCache(descriptor.userCacheName); 234 principalCache.invalidateAll(); 235 } 236 237 } 238 239 protected void setUserDirectoryName(String userDirectoryName) { 240 this.userDirectoryName = userDirectoryName; 241 userSchemaName = dirService.getDirectorySchema(userDirectoryName); 242 userIdField = dirService.getDirectoryIdField(userDirectoryName); 243 } 244 245 @Override 246 public String getUserDirectoryName() { 247 return userDirectoryName; 248 } 249 250 @Override 251 public String getUserIdField() { 252 return userIdField; 253 } 254 255 @Override 256 public String getUserSchemaName() { 257 return userSchemaName; 258 } 259 260 @Override 261 public String getUserEmailField() { 262 return userEmailField; 263 } 264 265 @Override 266 public Set<String> getUserSearchFields() { 267 return Collections.unmodifiableSet(userSearchFields.keySet()); 268 } 269 270 @Override 271 public Set<String> getGroupSearchFields() { 272 return Collections.unmodifiableSet(groupSearchFields.keySet()); 273 } 274 275 protected void setGroupDirectoryName(String groupDirectoryName) { 276 this.groupDirectoryName = groupDirectoryName; 277 groupSchemaName = dirService.getDirectorySchema(groupDirectoryName); 278 groupIdField = dirService.getDirectoryIdField(groupDirectoryName); 279 } 280 281 @Override 282 public String getGroupDirectoryName() { 283 return groupDirectoryName; 284 } 285 286 @Override 287 public String getGroupIdField() { 288 return groupIdField; 289 } 290 291 @Override 292 public String getGroupLabelField() { 293 return groupLabelField; 294 } 295 296 @Override 297 public String getGroupSchemaName() { 298 return groupSchemaName; 299 } 300 301 @Override 302 public String getGroupMembersField() { 303 return groupMembersField; 304 } 305 306 @Override 307 public String getGroupSubGroupsField() { 308 return groupSubGroupsField; 309 } 310 311 @Override 312 public String getGroupParentGroupsField() { 313 return groupParentGroupsField; 314 } 315 316 @Override 317 public String getUserListingMode() { 318 return userListingMode; 319 } 320 321 @Override 322 public String getGroupListingMode() { 323 return groupListingMode; 324 } 325 326 @Override 327 public String getDefaultGroup() { 328 return defaultGroup; 329 } 330 331 @Override 332 public Pattern getUserPasswordPattern() { 333 return userPasswordPattern; 334 } 335 336 @Override 337 public String getAnonymousUserId() { 338 if (anonymousUser == null) { 339 return null; 340 } 341 String anonymousUserId = anonymousUser.getId(); 342 if (anonymousUserId == null) { 343 return DEFAULT_ANONYMOUS_USER_ID; 344 } 345 return anonymousUserId; 346 } 347 348 protected void setVirtualUsers(Map<String, VirtualUserDescriptor> virtualUsers) { 349 this.virtualUsers.clear(); 350 if (virtualUsers != null) { 351 this.virtualUsers.putAll(virtualUsers); 352 } 353 } 354 355 @Override 356 public boolean checkUsernamePassword(String username, String password) { 357 358 if (username == null || password == null) { 359 log.warn("Trying to authenticate against null username or password"); 360 return false; 361 } 362 363 // deal with anonymous user 364 String anonymousUserId = getAnonymousUserId(); 365 if (username.equals(anonymousUserId)) { 366 log.warn(String.format("Trying to authenticate anonymous user (%s)", anonymousUserId)); 367 return false; 368 } 369 370 // deal with virtual users 371 if (virtualUsers.containsKey(username)) { 372 VirtualUser user = virtualUsers.get(username); 373 String expected = user.getPassword(); 374 if (expected == null) { 375 return false; 376 } 377 return expected.equals(password); 378 } 379 380 String userDirName; 381 // BBB backward compat for userDirectory + userAuthentication 382 if ("userDirectory".equals(userDirectoryName) && dirService.getDirectory("userAuthentication") != null) { 383 userDirName = "userAuthentication"; 384 } else { 385 userDirName = userDirectoryName; 386 } 387 try (Session userDir = dirService.open(userDirName)) { 388 if (!userDir.isAuthenticating()) { 389 log.error("Trying to authenticate against a non authenticating " + "directory: " + userDirName); 390 return false; 391 } 392 393 boolean authenticated = userDir.authenticate(username, password); 394 if (authenticated) { 395 syncDigestAuthPassword(username, password); 396 } 397 return authenticated; 398 } 399 } 400 401 protected void syncDigestAuthPassword(String username, String password) { 402 if (StringUtils.isEmpty(digestAuthDirectory) || StringUtils.isEmpty(digestAuthRealm) || username == null 403 || password == null) { 404 return; 405 } 406 407 String ha1 = encodeDigestAuthPassword(username, digestAuthRealm, password); 408 try (Session dir = dirService.open(digestAuthDirectory)) { 409 String schema = dirService.getDirectorySchema(digestAuthDirectory); 410 DocumentModel entry = dir.getEntry(username, true); 411 if (entry == null) { 412 entry = getDigestAuthModel(); 413 entry.setProperty(schema, dir.getIdField(), username); 414 entry.setProperty(schema, dir.getPasswordField(), ha1); 415 dir.createEntry(entry); 416 log.debug("Created digest auth password for user:" + username); 417 } else { 418 String storedHa1 = (String) entry.getProperty(schema, dir.getPasswordField()); 419 if (!ha1.equals(storedHa1)) { 420 entry.setProperty(schema, dir.getPasswordField(), ha1); 421 dir.updateEntry(entry); 422 log.debug("Updated digest auth password for user:" + username); 423 } 424 } 425 } catch (DirectoryException e) { 426 log.warn("Digest auth password not synchronized, check your configuration", e); 427 } 428 } 429 430 protected DocumentModel getDigestAuthModel() { 431 String schema = dirService.getDirectorySchema(digestAuthDirectory); 432 return BaseSession.createEntryModel(null, schema, null, null); 433 } 434 435 public static String encodeDigestAuthPassword(String username, String realm, String password) { 436 String a1 = username + ":" + realm + ":" + password; 437 return DigestUtils.md5Hex(a1); 438 } 439 440 @Override 441 public String getDigestAuthDirectory() { 442 return digestAuthDirectory; 443 } 444 445 @Override 446 public String getDigestAuthRealm() { 447 return digestAuthRealm; 448 } 449 450 @Override 451 public boolean validatePassword(String password) { 452 if (userPasswordPattern == null) { 453 return true; 454 } else { 455 Matcher userPasswordMatcher = userPasswordPattern.matcher(password); 456 return userPasswordMatcher.find(); 457 } 458 } 459 460 @Override 461 public void updatePassword(String username, String oldPassword, String newPassword) { 462 if (checkUsernamePassword(username, oldPassword)) { 463 DocumentModel userModel = getUserModel(username); 464 try (Session userDir = dirService.open(userDirectoryName, null)) { 465 userModel.setProperty(userSchemaName, userDir.getPasswordField(), newPassword); 466 } 467 updateUser(userModel); 468 return; 469 } 470 throw new NuxeoException("Invalid old password"); 471 } 472 473 protected NuxeoPrincipal makeAnonymousPrincipal() { 474 DocumentModel userEntry = makeVirtualUserEntry(getAnonymousUserId(), anonymousUser); 475 // XXX: pass anonymous user groups, but they will be ignored 476 return makePrincipal(userEntry, true, anonymousUser.getGroups()); 477 } 478 479 protected NuxeoPrincipal makeVirtualPrincipal(VirtualUser user) { 480 DocumentModel userEntry = makeVirtualUserEntry(user.getId(), user); 481 return makePrincipal(userEntry, false, user.getGroups()); 482 } 483 484 protected NuxeoPrincipal makeTransientPrincipal(String username) { 485 DocumentModel userEntry = BaseSession.createEntryModel(null, userSchemaName, username, null); 486 userEntry.setProperty(userSchemaName, userIdField, username); 487 NuxeoPrincipal principal = makePrincipal(userEntry, false, true, null); 488 String[] parts = username.split("/"); 489 String email = parts[1]; 490 principal.setFirstName(email); 491 principal.setEmail(email); 492 return principal; 493 } 494 495 protected DocumentModel makeVirtualUserEntry(String id, VirtualUser user) { 496 final DocumentModel userEntry = BaseSession.createEntryModel(null, userSchemaName, id, null); 497 // at least fill id field 498 userEntry.setProperty(userSchemaName, userIdField, id); 499 for (Entry<String, Serializable> prop : user.getProperties().entrySet()) { 500 try { 501 userEntry.setProperty(userSchemaName, prop.getKey(), prop.getValue()); 502 } catch (PropertyNotFoundException ce) { 503 log.error( 504 "Property: " + prop.getKey() + " does not exists. Check your " + "UserService configuration.", 505 ce); 506 } 507 } 508 return userEntry; 509 } 510 511 protected NuxeoPrincipal makePrincipal(DocumentModel userEntry) { 512 return makePrincipal(userEntry, false, null); 513 } 514 515 protected NuxeoPrincipal makePrincipal(DocumentModel userEntry, boolean anonymous, List<String> groups) { 516 return makePrincipal(userEntry, anonymous, false, groups); 517 } 518 519 protected NuxeoPrincipal makePrincipal(DocumentModel userEntry, boolean anonymous, boolean isTransient, 520 List<String> groups) { 521 boolean admin = false; 522 String username = userEntry.getId(); 523 524 List<String> virtualGroups = new LinkedList<String>(); 525 // Add preconfigured groups: useful for LDAP, not for anonymous users 526 if (defaultGroup != null && !anonymous && !isTransient) { 527 virtualGroups.add(defaultGroup); 528 } 529 // Add additional groups: useful for virtual users 530 if (groups != null && !isTransient) { 531 virtualGroups.addAll(groups); 532 } 533 // Create a default admin if needed 534 if (administratorIds != null && administratorIds.contains(username)) { 535 admin = true; 536 if (administratorGroups != null) { 537 virtualGroups.addAll(administratorGroups); 538 } 539 } 540 541 NuxeoPrincipalImpl principal = new NuxeoPrincipalImpl(username, anonymous, admin, false); 542 principal.setConfig(userConfig); 543 544 principal.setModel(userEntry, false); 545 principal.setVirtualGroups(virtualGroups, true); 546 547 // TODO: reenable roles initialization once we have a use case for 548 // a role directory. In the mean time we only set the JBOSS role 549 // that is required to login 550 List<String> roles = Arrays.asList("regular"); 551 principal.setRoles(roles); 552 553 return principal; 554 } 555 556 protected boolean useCache() { 557 return principalCache != null; 558 } 559 560 @Override 561 public NuxeoPrincipal getPrincipal(String username) { 562 if (useCache()) { 563 return getPrincipalUsingCache(username); 564 } 565 return getPrincipal(username, null); 566 } 567 568 protected NuxeoPrincipal getPrincipalUsingCache(String username) { 569 NuxeoPrincipal ret = (NuxeoPrincipal) principalCache.get(username); 570 if (ret == null) { 571 ret = getPrincipal(username, null); 572 if (ret == null) { 573 return ret; 574 } 575 principalCache.put(username, ret); 576 } 577 return ((NuxeoPrincipalImpl) ret).cloneTransferable(); // should not return cached principal 578 } 579 580 @Override 581 public DocumentModel getUserModel(String userName) { 582 return getUserModel(userName, null); 583 } 584 585 @Override 586 public DocumentModel getBareUserModel() { 587 String schema = dirService.getDirectorySchema(userDirectoryName); 588 return BaseSession.createEntryModel(null, schema, null, null); 589 } 590 591 @Override 592 public NuxeoGroup getGroup(String groupName) { 593 return getGroup(groupName, null); 594 } 595 596 protected NuxeoGroup getGroup(String groupName, DocumentModel context) { 597 DocumentModel groupEntry = getGroupModel(groupName, context); 598 if (groupEntry != null) { 599 return makeGroup(groupEntry); 600 } 601 return null; 602 603 } 604 605 @Override 606 public DocumentModel getGroupModel(String groupName) { 607 return getGroupModel(groupName, null); 608 } 609 610 @SuppressWarnings("unchecked") 611 protected NuxeoGroup makeGroup(DocumentModel groupEntry) { 612 NuxeoGroup group = new NuxeoGroupImpl(groupEntry.getId()); 613 List<String> list; 614 try { 615 list = (List<String>) groupEntry.getProperty(groupSchemaName, groupMembersField); 616 } catch (PropertyException e) { 617 list = null; 618 } 619 if (list != null) { 620 group.setMemberUsers(list); 621 } 622 try { 623 list = (List<String>) groupEntry.getProperty(groupSchemaName, groupSubGroupsField); 624 } catch (PropertyException e) { 625 list = null; 626 } 627 if (list != null) { 628 group.setMemberGroups(list); 629 } 630 try { 631 list = (List<String>) groupEntry.getProperty(groupSchemaName, groupParentGroupsField); 632 } catch (PropertyException e) { 633 list = null; 634 } 635 if (list != null) { 636 group.setParentGroups(list); 637 } 638 try { 639 String label = (String) groupEntry.getProperty(groupSchemaName, groupLabelField); 640 if (label != null) { 641 group.setLabel(label); 642 } 643 } catch (PropertyException e) { 644 // Nothing to do. 645 } 646 return group; 647 } 648 649 @Override 650 public List<String> getTopLevelGroups() { 651 return getTopLevelGroups(null); 652 } 653 654 @Override 655 public List<String> getGroupsInGroup(String parentId) { 656 NuxeoGroup group = getGroup(parentId, null); 657 if (group != null) { 658 return group.getMemberGroups(); 659 } else { 660 return Collections.emptyList(); 661 } 662 } 663 664 @Override 665 public List<String> getUsersInGroup(String groupId) { 666 return getGroup(groupId).getMemberUsers(); 667 } 668 669 @Override 670 public List<String> getUsersInGroupAndSubGroups(String groupId) { 671 return getUsersInGroupAndSubGroups(groupId, null); 672 } 673 674 protected void appendSubgroups(String groupId, Set<String> groups, DocumentModel context) { 675 List<String> groupsToAppend = getGroupsInGroup(groupId, context); 676 groups.addAll(groupsToAppend); 677 for (String subgroupId : groupsToAppend) { 678 groups.add(subgroupId); 679 // avoiding infinite loop 680 if (!groups.contains(subgroupId)) { 681 appendSubgroups(subgroupId, groups, context); 682 } 683 } 684 685 } 686 687 protected boolean isAnonymousMatching(Map<String, Serializable> filter, Set<String> fulltext) { 688 String anonymousUserId = getAnonymousUserId(); 689 if (anonymousUserId == null) { 690 return false; 691 } 692 if (filter == null || filter.isEmpty()) { 693 return true; 694 } 695 Map<String, Serializable> anonymousUserMap = anonymousUser.getProperties(); 696 anonymousUserMap.put(userIdField, anonymousUserId); 697 for (Entry<String, Serializable> e : filter.entrySet()) { 698 String fieldName = e.getKey(); 699 Object expected = e.getValue(); 700 Object value = anonymousUserMap.get(fieldName); 701 if (value == null) { 702 if (expected != null) { 703 return false; 704 } 705 } else { 706 if (fulltext != null && fulltext.contains(fieldName)) { 707 if (!value.toString().toLowerCase().startsWith(expected.toString().toLowerCase())) { 708 return false; 709 } 710 } else { 711 if (!value.equals(expected)) { 712 return false; 713 } 714 } 715 } 716 } 717 return true; 718 } 719 720 @Override 721 public List<NuxeoPrincipal> searchPrincipals(String pattern) { 722 DocumentModelList entries = searchUsers(pattern); 723 List<NuxeoPrincipal> principals = new ArrayList<NuxeoPrincipal>(entries.size()); 724 for (DocumentModel entry : entries) { 725 principals.add(makePrincipal(entry)); 726 } 727 return principals; 728 } 729 730 @Override 731 public DocumentModelList searchGroups(String pattern) { 732 return searchGroups(pattern, null); 733 } 734 735 @Override 736 public String getUserSortField() { 737 return userSortField; 738 } 739 740 protected Map<String, String> getUserSortMap() { 741 return getDirectorySortMap(userSortField, userIdField); 742 } 743 744 protected Map<String, String> getGroupSortMap() { 745 return getDirectorySortMap(groupSortField, groupIdField); 746 } 747 748 protected Map<String, String> getDirectorySortMap(String descriptorSortField, String fallBackField) { 749 String sortField = descriptorSortField != null ? descriptorSortField : fallBackField; 750 Map<String, String> orderBy = new HashMap<String, String>(); 751 orderBy.put(sortField, DocumentModelComparator.ORDER_ASC); 752 return orderBy; 753 } 754 755 /** 756 * @since 8.2 757 */ 758 private void notifyCore(String userOrGroupId, String eventId) { 759 Map<String, Serializable> eventProperties = new HashMap<>(); 760 eventProperties.put(DocumentEventContext.CATEGORY_PROPERTY_KEY, USER_GROUP_CATEGORY); 761 eventProperties.put("id", userOrGroupId); 762 NuxeoPrincipal principal = ClientLoginModule.getCurrentPrincipal(); 763 UnboundEventContext envContext = new UnboundEventContext(principal, eventProperties); 764 envContext.setProperties(eventProperties); 765 EventProducer eventProducer = Framework.getLocalService(EventProducer.class); 766 eventProducer.fireEvent(envContext.newEvent(eventId)); 767 } 768 769 protected void notifyRuntime(String userOrGroupName, String eventId) { 770 EventService eventService = Framework.getService(EventService.class); 771 eventService.sendEvent(new Event(USERMANAGER_TOPIC, eventId, this, userOrGroupName)); 772 } 773 774 /** 775 * Notifies user has changed so that the JaasCacheFlusher listener can make sure principals cache is reset. 776 */ 777 protected void notifyUserChanged(String userName, String eventId) { 778 invalidatePrincipal(userName); 779 notifyRuntime(userName, USERCHANGED_EVENT_ID); 780 if (eventId != null) { 781 notifyRuntime(userName, eventId); 782 notifyCore(userName, eventId); 783 } 784 } 785 786 protected void invalidatePrincipal(String userName) { 787 if (useCache()) { 788 principalCache.invalidate(userName); 789 } 790 } 791 792 /** 793 * Notifies group has changed so that the JaasCacheFlusher listener can make sure principals cache is reset. 794 */ 795 protected void notifyGroupChanged(String groupName, String eventId) { 796 invalidateAllPrincipals(); 797 notifyRuntime(groupName, GROUPCHANGED_EVENT_ID); 798 if (eventId != null) { 799 notifyRuntime(groupName, eventId); 800 notifyCore(groupName, eventId); 801 } 802 } 803 804 protected void invalidateAllPrincipals() { 805 if (useCache()) { 806 principalCache.invalidateAll(); 807 } 808 } 809 810 @Override 811 public Boolean areGroupsReadOnly() { 812 try (Session groupDir = dirService.open(groupDirectoryName)) { 813 return groupDir.isReadOnly(); 814 } catch (DirectoryException e) { 815 log.error(e); 816 return false; 817 } 818 } 819 820 @Override 821 public Boolean areUsersReadOnly() { 822 try (Session userDir = dirService.open(userDirectoryName)) { 823 return userDir.isReadOnly(); 824 } catch (DirectoryException e) { 825 log.error(e); 826 return false; 827 } 828 } 829 830 protected void checkGrouId(DocumentModel groupModel) { 831 // be sure the name does not contains trailing spaces 832 Object groupIdValue = groupModel.getProperty(groupSchemaName, groupIdField); 833 if (groupIdValue != null) { 834 groupModel.setProperty(groupSchemaName, groupIdField, groupIdValue.toString().trim()); 835 } 836 } 837 838 protected String getGroupId(DocumentModel groupModel) { 839 Object groupIdValue = groupModel.getProperty(groupSchemaName, groupIdField); 840 if (groupIdValue != null && !(groupIdValue instanceof String)) { 841 throw new NuxeoException("Invalid group id " + groupIdValue); 842 } 843 return (String) groupIdValue; 844 } 845 846 protected void checkUserId(DocumentModel userModel) { 847 Object userIdValue = userModel.getProperty(userSchemaName, userIdField); 848 if (userIdValue != null) { 849 userModel.setProperty(userSchemaName, userIdField, userIdValue.toString().trim()); 850 } 851 } 852 853 protected String getUserId(DocumentModel userModel) { 854 Object userIdValue = userModel.getProperty(userSchemaName, userIdField); 855 if (userIdValue != null && !(userIdValue instanceof String)) { 856 throw new NuxeoException("Invalid user id " + userIdValue); 857 } 858 return (String) userIdValue; 859 } 860 861 @Override 862 public DocumentModel createGroup(DocumentModel groupModel) { 863 return createGroup(groupModel, null); 864 } 865 866 @Override 867 public DocumentModel createUser(DocumentModel userModel) { 868 return createUser(userModel, null); 869 } 870 871 @Override 872 public void deleteGroup(String groupId) { 873 deleteGroup(groupId, null); 874 } 875 876 @Override 877 public void deleteGroup(DocumentModel groupModel) { 878 deleteGroup(groupModel, null); 879 } 880 881 @Override 882 public void deleteUser(String userId) { 883 deleteUser(userId, null); 884 } 885 886 @Override 887 public void deleteUser(DocumentModel userModel) { 888 String userId = getUserId(userModel); 889 deleteUser(userId); 890 } 891 892 @Override 893 public List<String> getGroupIds() { 894 try (Session groupDir = dirService.open(groupDirectoryName)) { 895 List<String> groupIds = groupDir.getProjection(Collections.<String, Serializable> emptyMap(), 896 groupDir.getIdField()); 897 Collections.sort(groupIds); 898 return groupIds; 899 } 900 } 901 902 @Override 903 public List<String> getUserIds() { 904 return getUserIds(null); 905 } 906 907 protected void removeVirtualFilters(Map<String, Serializable> filter) { 908 if (filter == null) { 909 return; 910 } 911 List<String> keys = new ArrayList<String>(filter.keySet()); 912 for (String key : keys) { 913 if (key.startsWith(VIRTUAL_FIELD_FILTER_PREFIX)) { 914 filter.remove(key); 915 } 916 } 917 } 918 919 @Override 920 public DocumentModelList searchGroups(Map<String, Serializable> filter, Set<String> fulltext) { 921 return searchGroups(filter, fulltext, null); 922 } 923 924 @Override 925 public DocumentModelList searchUsers(String pattern) { 926 return searchUsers(pattern, null); 927 } 928 929 @Override 930 public DocumentModelList searchUsers(Map<String, Serializable> filter, Set<String> fulltext) { 931 return searchUsers(filter, fulltext, getUserSortMap(), null); 932 } 933 934 @Override 935 public void updateGroup(DocumentModel groupModel) { 936 updateGroup(groupModel, null); 937 } 938 939 @Override 940 public void updateUser(DocumentModel userModel) { 941 updateUser(userModel, null); 942 } 943 944 @Override 945 public DocumentModel getBareGroupModel() { 946 String schema = dirService.getDirectorySchema(groupDirectoryName); 947 return BaseSession.createEntryModel(null, schema, null, null); 948 } 949 950 @Override 951 public void createGroup(NuxeoGroup group) { 952 DocumentModel newGroupModel = getBareGroupModel(); 953 newGroupModel.setProperty(groupSchemaName, groupIdField, group.getName()); 954 newGroupModel.setProperty(groupSchemaName, groupLabelField, group.getLabel()); 955 newGroupModel.setProperty(groupSchemaName, groupMembersField, group.getMemberUsers()); 956 newGroupModel.setProperty(groupSchemaName, groupSubGroupsField, group.getMemberGroups()); 957 createGroup(newGroupModel); 958 } 959 960 @Override 961 public void createPrincipal(NuxeoPrincipal principal) { 962 createUser(principal.getModel()); 963 } 964 965 @Override 966 public void deleteGroup(NuxeoGroup group) { 967 deleteGroup(group.getName()); 968 } 969 970 @Override 971 public void deletePrincipal(NuxeoPrincipal principal) { 972 deleteUser(principal.getName()); 973 } 974 975 @Override 976 public List<NuxeoGroup> getAvailableGroups() { 977 DocumentModelList groupModels = searchGroups(Collections.<String, Serializable> emptyMap(), null); 978 List<NuxeoGroup> groups = new ArrayList<NuxeoGroup>(groupModels.size()); 979 for (DocumentModel groupModel : groupModels) { 980 groups.add(makeGroup(groupModel)); 981 } 982 return groups; 983 } 984 985 @Override 986 public List<NuxeoPrincipal> getAvailablePrincipals() { 987 DocumentModelList userModels = searchUsers(Collections.<String, Serializable> emptyMap(), null); 988 List<NuxeoPrincipal> users = new ArrayList<NuxeoPrincipal>(userModels.size()); 989 for (DocumentModel userModel : userModels) { 990 users.add(makePrincipal(userModel)); 991 } 992 return users; 993 } 994 995 @Override 996 public DocumentModel getModelForUser(String name) { 997 return getUserModel(name); 998 } 999 1000 @Override 1001 public List<NuxeoPrincipal> searchByMap(Map<String, Serializable> filter, Set<String> pattern) { 1002 try (Session userDir = dirService.open(userDirectoryName)) { 1003 removeVirtualFilters(filter); 1004 1005 DocumentModelList entries = userDir.query(filter, pattern); 1006 List<NuxeoPrincipal> principals = new ArrayList<NuxeoPrincipal>(entries.size()); 1007 for (DocumentModel entry : entries) { 1008 principals.add(makePrincipal(entry)); 1009 } 1010 if (isAnonymousMatching(filter, pattern)) { 1011 principals.add(makeAnonymousPrincipal()); 1012 } 1013 return principals; 1014 } 1015 } 1016 1017 @Override 1018 public void updateGroup(NuxeoGroup group) { 1019 // XXX: need to refetch it for tests to pass, i don't get why (session 1020 // id is used maybe?) 1021 DocumentModel newGroupModel = getGroupModel(group.getName()); 1022 newGroupModel.setProperty(groupSchemaName, groupIdField, group.getName()); 1023 newGroupModel.setProperty(groupSchemaName, groupLabelField, group.getLabel()); 1024 newGroupModel.setProperty(groupSchemaName, groupMembersField, group.getMemberUsers()); 1025 newGroupModel.setProperty(groupSchemaName, groupSubGroupsField, group.getMemberGroups()); 1026 updateGroup(newGroupModel); 1027 } 1028 1029 @Override 1030 public void updatePrincipal(NuxeoPrincipal principal) { 1031 updateUser(principal.getModel()); 1032 } 1033 1034 @Override 1035 public List<String> getAdministratorsGroups() { 1036 return administratorGroups; 1037 } 1038 1039 protected List<String> getLeafPermissions(String perm) { 1040 ArrayList<String> permissions = new ArrayList<String>(); 1041 PermissionProvider permissionProvider = Framework.getService(PermissionProvider.class); 1042 String[] subpermissions = permissionProvider.getSubPermissions(perm); 1043 if (subpermissions == null || subpermissions.length <= 0) { 1044 // it's a leaf 1045 permissions.add(perm); 1046 return permissions; 1047 } 1048 for (String subperm : subpermissions) { 1049 permissions.addAll(getLeafPermissions(subperm)); 1050 } 1051 return permissions; 1052 } 1053 1054 @Override 1055 public String[] getUsersForPermission(String perm, ACP acp) { 1056 return getUsersForPermission(perm, acp, null); 1057 } 1058 1059 @Override 1060 public Principal authenticate(String name, String password) { 1061 return checkUsernamePassword(name, password) ? getPrincipal(name) : null; 1062 } 1063 1064 /*************** MULTI-TENANT-IMPLEMENTATION ************************/ 1065 1066 public DocumentModelList searchUsers(Map<String, Serializable> filter, Set<String> fulltext, 1067 Map<String, String> orderBy, DocumentModel context) { 1068 try (Session userDir = dirService.open(userDirectoryName, context)) { 1069 removeVirtualFilters(filter); 1070 1071 // XXX: do not fetch references, can be costly 1072 DocumentModelList entries = userDir.query(filter, fulltext, null, false); 1073 if (isAnonymousMatching(filter, fulltext)) { 1074 entries.add(makeVirtualUserEntry(getAnonymousUserId(), anonymousUser)); 1075 } 1076 1077 // TODO: match searchable virtual users 1078 1079 if (orderBy != null && !orderBy.isEmpty()) { 1080 // sort: cannot sort before virtual users are added 1081 Collections.sort(entries, new DocumentModelComparator(userSchemaName, orderBy)); 1082 } 1083 1084 return entries; 1085 } 1086 } 1087 1088 @Override 1089 public List<String> getUsersInGroup(String groupId, DocumentModel context) { 1090 String storeGroupId = multiTenantManagement.groupnameTranformer(this, groupId, context); 1091 return getGroup(storeGroupId).getMemberUsers(); 1092 } 1093 1094 @Override 1095 public DocumentModelList searchUsers(String pattern, DocumentModel context) { 1096 DocumentModelList entries = new DocumentModelListImpl(); 1097 if (pattern == null || pattern.length() == 0) { 1098 entries = searchUsers(Collections.<String, Serializable> emptyMap(), null); 1099 } else { 1100 pattern = pattern.trim(); 1101 Map<String, DocumentModel> uniqueEntries = new HashMap<String, DocumentModel>(); 1102 1103 for (Entry<String, MatchType> fieldEntry : userSearchFields.entrySet()) { 1104 Map<String, Serializable> filter = new HashMap<String, Serializable>(); 1105 filter.put(fieldEntry.getKey(), pattern); 1106 DocumentModelList fetchedEntries; 1107 if (fieldEntry.getValue() == MatchType.SUBSTRING) { 1108 fetchedEntries = searchUsers(filter, filter.keySet(), null, context); 1109 } else { 1110 fetchedEntries = searchUsers(filter, null, null, context); 1111 } 1112 for (DocumentModel entry : fetchedEntries) { 1113 uniqueEntries.put(entry.getId(), entry); 1114 } 1115 } 1116 log.debug(String.format("found %d unique entries", uniqueEntries.size())); 1117 entries.addAll(uniqueEntries.values()); 1118 } 1119 // sort 1120 Collections.sort(entries, new DocumentModelComparator(userSchemaName, getUserSortMap())); 1121 1122 return entries; 1123 } 1124 1125 @Override 1126 public DocumentModelList searchUsers(Map<String, Serializable> filter, Set<String> fulltext, DocumentModel context) { 1127 throw new UnsupportedOperationException(); 1128 } 1129 1130 @Override 1131 public List<String> getGroupIds(DocumentModel context) { 1132 throw new UnsupportedOperationException(); 1133 } 1134 1135 @Override 1136 public DocumentModelList searchGroups(Map<String, Serializable> filter, Set<String> fulltext, DocumentModel context) { 1137 filter = filter != null ? cloneMap(filter) : new HashMap<String, Serializable>(); 1138 HashSet<String> fulltextClone = fulltext != null ? cloneSet(fulltext) : new HashSet<String>(); 1139 multiTenantManagement.queryTransformer(this, filter, fulltextClone, context); 1140 1141 try (Session groupDir = dirService.open(groupDirectoryName, context)) { 1142 removeVirtualFilters(filter); 1143 return groupDir.query(filter, fulltextClone, getGroupSortMap(), false); 1144 } 1145 } 1146 1147 @Override 1148 public DocumentModel createGroup(DocumentModel groupModel, DocumentModel context) 1149 throws GroupAlreadyExistsException { 1150 groupModel = multiTenantManagement.groupTransformer(this, groupModel, context); 1151 1152 // be sure the name does not contains trailing spaces 1153 checkGrouId(groupModel); 1154 1155 try (Session groupDir = dirService.open(groupDirectoryName, context)) { 1156 String groupId = getGroupId(groupModel); 1157 1158 // check the group does not exist 1159 if (groupDir.hasEntry(groupId)) { 1160 throw new GroupAlreadyExistsException(); 1161 } 1162 groupModel = groupDir.createEntry(groupModel); 1163 notifyGroupChanged(groupId, GROUPCREATED_EVENT_ID); 1164 return groupModel; 1165 1166 } 1167 } 1168 1169 @Override 1170 public DocumentModel getGroupModel(String groupIdValue, DocumentModel context) { 1171 String groupName = multiTenantManagement.groupnameTranformer(this, groupIdValue, context); 1172 if (groupName != null) { 1173 groupName = groupName.trim(); 1174 } 1175 1176 try (Session groupDir = dirService.open(groupDirectoryName, context)) { 1177 return groupDir.getEntry(groupName); 1178 } 1179 } 1180 1181 @Override 1182 public DocumentModel getUserModel(String userName, DocumentModel context) { 1183 if (userName == null) { 1184 return null; 1185 } 1186 1187 userName = userName.trim(); 1188 // return anonymous model 1189 if (anonymousUser != null && userName.equals(anonymousUser.getId())) { 1190 return makeVirtualUserEntry(getAnonymousUserId(), anonymousUser); 1191 } 1192 1193 try (Session userDir = dirService.open(userDirectoryName, context)) { 1194 return userDir.getEntry(userName); 1195 } 1196 } 1197 1198 protected Map<String, Serializable> cloneMap(Map<String, Serializable> map) { 1199 Map<String, Serializable> result = new HashMap<String, Serializable>(); 1200 for (String key : map.keySet()) { 1201 result.put(key, map.get(key)); 1202 } 1203 return result; 1204 } 1205 1206 protected HashSet<String> cloneSet(Set<String> set) { 1207 HashSet<String> result = new HashSet<String>(); 1208 for (String key : set) { 1209 result.add(key); 1210 } 1211 return result; 1212 } 1213 1214 @Override 1215 public NuxeoPrincipal getPrincipal(String username, DocumentModel context) { 1216 if (username == null) { 1217 return null; 1218 } 1219 String anonymousUserId = getAnonymousUserId(); 1220 if (username.equals(anonymousUserId)) { 1221 return makeAnonymousPrincipal(); 1222 } 1223 if (virtualUsers.containsKey(username)) { 1224 return makeVirtualPrincipal(virtualUsers.get(username)); 1225 } 1226 if (NuxeoPrincipal.isTransientUsername(username)) { 1227 return makeTransientPrincipal(username); 1228 } 1229 DocumentModel userModel = getUserModel(username, context); 1230 if (userModel != null) { 1231 return makePrincipal(userModel); 1232 } 1233 return null; 1234 } 1235 1236 @Override 1237 public DocumentModelList searchGroups(String pattern, DocumentModel context) { 1238 DocumentModelList entries = new DocumentModelListImpl(); 1239 if (pattern == null || pattern.length() == 0) { 1240 entries = searchGroups(Collections.<String, Serializable> emptyMap(), null); 1241 } else { 1242 pattern = pattern.trim(); 1243 Map<String, DocumentModel> uniqueEntries = new HashMap<String, DocumentModel>(); 1244 1245 for (Entry<String, MatchType> fieldEntry : groupSearchFields.entrySet()) { 1246 Map<String, Serializable> filter = new HashMap<String, Serializable>(); 1247 filter.put(fieldEntry.getKey(), pattern); 1248 DocumentModelList fetchedEntries; 1249 if (fieldEntry.getValue() == MatchType.SUBSTRING) { 1250 fetchedEntries = searchGroups(filter, filter.keySet(), context); 1251 } else { 1252 fetchedEntries = searchGroups(filter, null, context); 1253 } 1254 for (DocumentModel entry : fetchedEntries) { 1255 uniqueEntries.put(entry.getId(), entry); 1256 } 1257 } 1258 log.debug(String.format("found %d unique group entries", uniqueEntries.size())); 1259 entries.addAll(uniqueEntries.values()); 1260 } 1261 // sort 1262 Collections.sort(entries, new DocumentModelComparator(groupSchemaName, getGroupSortMap())); 1263 1264 return entries; 1265 } 1266 1267 @Override 1268 public List<String> getUserIds(DocumentModel context) { 1269 try (Session userDir = dirService.open(userDirectoryName, context)) { 1270 List<String> userIds = userDir.getProjection(Collections.<String, Serializable> emptyMap(), 1271 userDir.getIdField()); 1272 Collections.sort(userIds); 1273 return userIds; 1274 } 1275 } 1276 1277 @Override 1278 public DocumentModel createUser(DocumentModel userModel, DocumentModel context) throws UserAlreadyExistsException { 1279 // be sure UserId does not contains any trailing spaces 1280 checkUserId(userModel); 1281 1282 try (Session userDir = dirService.open(userDirectoryName, context)) { 1283 String userId = getUserId(userModel); 1284 1285 // check the user does not exist 1286 if (userDir.hasEntry(userId)) { 1287 throw new UserAlreadyExistsException(); 1288 } 1289 1290 String schema = dirService.getDirectorySchema(userDirectoryName); 1291 String clearUsername = (String) userModel.getProperty(schema, userDir.getIdField()); 1292 String clearPassword = (String) userModel.getProperty(schema, userDir.getPasswordField()); 1293 1294 userModel = userDir.createEntry(userModel); 1295 1296 syncDigestAuthPassword(clearUsername, clearPassword); 1297 1298 notifyUserChanged(userId, USERCREATED_EVENT_ID); 1299 return userModel; 1300 1301 } 1302 } 1303 1304 @Override 1305 public void updateUser(DocumentModel userModel, DocumentModel context) { 1306 try (Session userDir = dirService.open(userDirectoryName, context)) { 1307 String userId = getUserId(userModel); 1308 1309 if (!userDir.hasEntry(userId)) { 1310 throw new DirectoryException("user does not exist: " + userId); 1311 } 1312 1313 String schema = dirService.getDirectorySchema(userDirectoryName); 1314 String clearUsername = (String) userModel.getProperty(schema, userDir.getIdField()); 1315 String clearPassword = (String) userModel.getProperty(schema, userDir.getPasswordField()); 1316 1317 userDir.updateEntry(userModel); 1318 1319 syncDigestAuthPassword(clearUsername, clearPassword); 1320 1321 notifyUserChanged(userId, USERMODIFIED_EVENT_ID); 1322 } 1323 } 1324 1325 @Override 1326 public void deleteUser(DocumentModel userModel, DocumentModel context) { 1327 String userId = getUserId(userModel); 1328 deleteUser(userId, context); 1329 } 1330 1331 @Override 1332 public void deleteUser(String userId, DocumentModel context) { 1333 try (Session userDir = dirService.open(userDirectoryName, context)) { 1334 if (!userDir.hasEntry(userId)) { 1335 throw new DirectoryException("User does not exist: " + userId); 1336 } 1337 userDir.deleteEntry(userId); 1338 notifyUserChanged(userId, USERDELETED_EVENT_ID); 1339 1340 } finally { 1341 notifyUserChanged(userId, null); 1342 } 1343 } 1344 1345 @Override 1346 public void updateGroup(DocumentModel groupModel, DocumentModel context) { 1347 try (Session groupDir = dirService.open(groupDirectoryName, context)) { 1348 String groupId = getGroupId(groupModel); 1349 1350 if (!groupDir.hasEntry(groupId)) { 1351 throw new DirectoryException("group does not exist: " + groupId); 1352 } 1353 groupDir.updateEntry(groupModel); 1354 notifyGroupChanged(groupId, GROUPMODIFIED_EVENT_ID); 1355 } 1356 } 1357 1358 @Override 1359 public void deleteGroup(DocumentModel groupModel, DocumentModel context) { 1360 String groupId = getGroupId(groupModel); 1361 deleteGroup(groupId, context); 1362 } 1363 1364 @Override 1365 public void deleteGroup(String groupId, DocumentModel context) { 1366 try (Session groupDir = dirService.open(groupDirectoryName, context)) { 1367 if (!groupDir.hasEntry(groupId)) { 1368 throw new DirectoryException("Group does not exist: " + groupId); 1369 } 1370 groupDir.deleteEntry(groupId); 1371 notifyGroupChanged(groupId, GROUPDELETED_EVENT_ID); 1372 } 1373 } 1374 1375 @Override 1376 public List<String> getGroupsInGroup(String parentId, DocumentModel context) { 1377 return getGroup(parentId, null).getMemberGroups(); 1378 } 1379 1380 @Override 1381 public List<String> getTopLevelGroups(DocumentModel context) { 1382 try (Session groupDir = dirService.open(groupDirectoryName, context)) { 1383 List<String> topLevelGroups = new LinkedList<String>(); 1384 // XXX retrieve all entries with references, can be costly. 1385 DocumentModelList groups = groupDir.query(Collections.<String, Serializable> emptyMap(), null, null, true); 1386 for (DocumentModel group : groups) { 1387 @SuppressWarnings("unchecked") 1388 List<String> parents = (List<String>) group.getProperty(groupSchemaName, groupParentGroupsField); 1389 1390 if (parents == null || parents.isEmpty()) { 1391 topLevelGroups.add(group.getId()); 1392 } 1393 } 1394 return topLevelGroups; 1395 } 1396 } 1397 1398 @Override 1399 public List<String> getUsersInGroupAndSubGroups(String groupId, DocumentModel context) { 1400 Set<String> groups = new HashSet<String>(); 1401 groups.add(groupId); 1402 appendSubgroups(groupId, groups, context); 1403 1404 Set<String> users = new HashSet<String>(); 1405 for (String groupid : groups) { 1406 users.addAll(getGroup(groupid, context).getMemberUsers()); 1407 } 1408 1409 return new ArrayList<String>(users); 1410 } 1411 1412 @Override 1413 public String[] getUsersForPermission(String perm, ACP acp, DocumentModel context) { 1414 PermissionProvider permissionProvider = Framework.getService(PermissionProvider.class); 1415 // using a hashset to avoid duplicates 1416 HashSet<String> usernames = new HashSet<String>(); 1417 1418 ACL merged = acp.getMergedACLs("merged"); 1419 // The list of permission that is has "perm" as its (compound) 1420 // permission 1421 ArrayList<ACE> filteredACEbyPerm = new ArrayList<ACE>(); 1422 1423 List<String> currentPermissions = getLeafPermissions(perm); 1424 1425 for (ACE ace : merged.getACEs()) { 1426 // Checking if the permission contains the permission we want to 1427 // check (we use the security service method for coumpound 1428 // permissions) 1429 List<String> acePermissions = getLeafPermissions(ace.getPermission()); 1430 1431 // Everything is a special permission (not compound) 1432 if (SecurityConstants.EVERYTHING.equals(ace.getPermission())) { 1433 acePermissions = Arrays.asList(permissionProvider.getPermissions()); 1434 } 1435 1436 if (acePermissions.containsAll(currentPermissions)) { 1437 // special case: everybody perm grant false, don't take in 1438 // account the previous ace 1439 if (SecurityConstants.EVERYONE.equals(ace.getUsername()) && !ace.isGranted()) { 1440 break; 1441 } 1442 filteredACEbyPerm.add(ace); 1443 } 1444 } 1445 1446 for (ACE ace : filteredACEbyPerm) { 1447 String aceUsername = ace.getUsername(); 1448 List<String> users = null; 1449 // If everyone, add/remove all the users 1450 if (SecurityConstants.EVERYONE.equals(aceUsername)) { 1451 users = getUserIds(); 1452 } 1453 // if a group, add/remove all the user from the group (and 1454 // subgroups) 1455 if (users == null) { 1456 NuxeoGroup group; 1457 group = getGroup(aceUsername, context); 1458 if (group != null) { 1459 users = getUsersInGroupAndSubGroups(aceUsername, context); 1460 } 1461 1462 } 1463 // otherwise, add the user 1464 if (users == null) { 1465 users = new ArrayList<String>(); 1466 users.add(aceUsername); 1467 } 1468 if (ace.isGranted()) { 1469 usernames.addAll(users); 1470 } else { 1471 usernames.removeAll(users); 1472 } 1473 } 1474 return usernames.toArray(new String[usernames.size()]); 1475 } 1476 1477 @Override 1478 public boolean aboutToHandleEvent(Event event) { 1479 return true; 1480 } 1481 1482 @Override 1483 public void handleEvent(Event event) { 1484 String id = event.getId(); 1485 if (INVALIDATE_PRINCIPAL_EVENT_ID.equals(id)) { 1486 invalidatePrincipal((String) event.getData()); 1487 } else if (INVALIDATE_ALL_PRINCIPALS_EVENT_ID.equals(id)) { 1488 invalidateAllPrincipals(); 1489 } 1490 } 1491 1492}