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