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