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