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