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