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