1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.cms.security;
35
36 import static info.magnolia.cms.security.SecurityConstants.*;
37
38 import info.magnolia.cms.core.Content;
39 import info.magnolia.cms.core.HierarchyManager;
40 import info.magnolia.cms.security.auth.ACL;
41 import info.magnolia.cms.util.ContentUtil;
42 import info.magnolia.context.MgnlContext;
43 import info.magnolia.jcr.iterator.FilteringPropertyIterator;
44 import info.magnolia.jcr.predicate.JCRMgnlPropertyHidingPredicate;
45 import info.magnolia.jcr.util.NodeNameHelper;
46 import info.magnolia.jcr.util.NodeTypes;
47 import info.magnolia.jcr.util.NodeUtil;
48 import info.magnolia.jcr.util.PropertyUtil;
49 import info.magnolia.jcr.wrapper.MgnlPropertySettingNodeWrapper;
50 import info.magnolia.objectfactory.Components;
51 import info.magnolia.repository.RepositoryConstants;
52
53 import java.util.ArrayList;
54 import java.util.Collection;
55 import java.util.Collections;
56 import java.util.GregorianCalendar;
57 import java.util.HashMap;
58 import java.util.HashSet;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Map;
62 import java.util.Set;
63 import java.util.SortedSet;
64 import java.util.TreeSet;
65 import java.util.stream.Collectors;
66
67 import javax.inject.Inject;
68 import javax.jcr.ItemNotFoundException;
69 import javax.jcr.Node;
70 import javax.jcr.NodeIterator;
71 import javax.jcr.PathNotFoundException;
72 import javax.jcr.Property;
73 import javax.jcr.PropertyIterator;
74 import javax.jcr.RepositoryException;
75 import javax.jcr.Session;
76 import javax.jcr.Value;
77 import javax.jcr.ValueFormatException;
78 import javax.jcr.lock.LockException;
79 import javax.security.auth.Subject;
80
81 import org.apache.commons.lang3.StringUtils;
82 import org.apache.jackrabbit.JcrConstants;
83 import org.slf4j.Logger;
84 import org.slf4j.LoggerFactory;
85
86
87
88
89 public class MgnlUserManager extends RepositoryBackedSecurityManager implements UserManager {
90
91 private static final Logger log = LoggerFactory.getLogger(MgnlUserManager.class);
92
93 public static final String PROPERTY_EMAIL = "email";
94 public static final String PROPERTY_LANGUAGE = "language";
95 public static final String PROPERTY_LASTACCESS = "lastaccess";
96 public static final String PROPERTY_PASSWORD = "pswd";
97 public static final String PROPERTY_TITLE = "title";
98 public static final String PROPERTY_ENABLED = "enabled";
99 public static final String PROPERTY_TIMEZONE = "timezone";
100
101 public static final String NODE_ACLUSERS = "acl_users";
102
103 private String realmName;
104
105 private boolean allowCrossRealmDuplicateNames = false;
106
107 private int maxFailedLoginAttempts;
108
109 private int lockTimePeriod;
110
111
112
113
114 @Inject
115 public MgnlUserManager(NodeNameHelper nodeNameHelper) {
116 super(nodeNameHelper);
117 }
118
119
120
121
122 @Deprecated
123 public MgnlUserManager() {
124 this(Components.getComponent(NodeNameHelper.class));
125 }
126
127 @Override
128 public void setMaxFailedLoginAttempts(int maxFailedLoginAttempts) {
129 this.maxFailedLoginAttempts = maxFailedLoginAttempts;
130 }
131
132 @Override
133 public int getMaxFailedLoginAttempts() {
134 return maxFailedLoginAttempts;
135 }
136
137 @Override
138 public int getLockTimePeriod() {
139 return lockTimePeriod;
140 }
141
142 @Override
143 public void setLockTimePeriod(int lockTimePeriod) {
144 this.lockTimePeriod = lockTimePeriod;
145 }
146
147 @Override
148 public User setProperty(final User user, final String propertyName, final Value propertyValue) {
149 return MgnlContext.doInSystemContext(new SilentSessionOp<User>(getRepositoryName()) {
150
151 @Override
152 public User doExec(Session session) throws RepositoryException {
153 String path = ((MgnlUser) user).getPath();
154 Node userNode;
155 try {
156 userNode = session.getNode(path);
157
158
159 if (propertyValue != null || PropertyUtil.getPropertyOrNull(userNode, propertyName) != null) {
160 if (StringUtils.equals(propertyName, PROPERTY_PASSWORD)) {
161 setPasswordProperty(userNode, propertyValue.getString());
162 } else {
163 userNode.setProperty(propertyName, propertyValue);
164 session.save();
165 }
166 }
167 } catch (RepositoryException e) {
168 session.refresh(false);
169 log.error("Property {} can't be changed. {}", propertyName, e.getMessage());
170 return user;
171 }
172 return newUserInstance(userNode);
173 }
174
175 @Override
176 public String toString() {
177 return getClass().getName() + " setProperty(user, propertyName, Value propertyValue)";
178 }
179 });
180 }
181
182 @Override
183 public User setProperty(final User user, final String propertyName, final String propertyValue) {
184 return MgnlContext.doInSystemContext(new SilentSessionOp<User>(getRepositoryName()) {
185
186 @Override
187 public User doExec(Session session) throws RepositoryException {
188 String path = ((MgnlUser) user).getPath();
189 Node userNode;
190 try {
191 userNode = session.getNode(path);
192
193
194 if (propertyName != null) {
195 if (StringUtils.equals(propertyName, PROPERTY_PASSWORD)) {
196 setPasswordProperty(userNode, propertyValue);
197 } else {
198 userNode.setProperty(propertyName, propertyValue);
199 session.save();
200 }
201 }
202 } catch (RepositoryException e) {
203 session.refresh(false);
204 log.error("Property {} can't be changed. {}", propertyName, e.getMessage());
205 return user;
206 }
207 return newUserInstance(userNode);
208 }
209
210 @Override
211 public String toString() {
212 return getClass().getName() + " setProperty(user, propertyName, String propertyValue)";
213 }
214 });
215 }
216
217 public void setRealmName(String name) {
218 this.realmName = name;
219 }
220
221 public String getRealmName() {
222 return realmName;
223 }
224
225 public void setAllowCrossRealmDuplicateNames(boolean allowCrossRealmDuplicateNames) {
226 this.allowCrossRealmDuplicateNames = allowCrossRealmDuplicateNames;
227 }
228
229 public boolean isAllowCrossRealmDuplicateNames() {
230 return allowCrossRealmDuplicateNames;
231 }
232
233
234
235
236
237
238
239 @Override
240 public User getUser(final String name) {
241 try {
242
243 if (MgnlContext.isSystemInstance()) {
244 return getUser(name, MgnlContext.getJCRSession(getRepositoryName()));
245 } else {
246 return MgnlContext.doInSystemContext(new JCRSessionOp<User>(getRepositoryName()) {
247 @Override
248 public User exec(Session session) throws RepositoryException {
249 return getUser(name, session);
250 }
251
252 @Override
253 public String toString() {
254 return "retrieve user " + name;
255 }
256 });
257 }
258 } catch (RepositoryException e) {
259 log.error("Could not retrieve user with name: {}", name, e);
260 }
261 return null;
262 }
263
264 private User getUser(String name, Session session) throws RepositoryException {
265 Node priviledgedUserNode = findPrincipalNode(name, session);
266 return newUserInstance(priviledgedUserNode);
267 }
268
269
270
271
272
273
274
275 @Override
276 public User getUserById(final String id) {
277 try {
278 return MgnlContext.doInSystemContext(new JCRSessionOp<User>(getRepositoryName()) {
279 @Override
280 public User exec(Session session) throws RepositoryException {
281 Node priviledgedUserNode = session.getNodeByIdentifier(id);
282 return newUserInstance(priviledgedUserNode);
283 }
284
285 @Override
286 public String toString() {
287 return "retrieve user with id " + id;
288 }
289 });
290 } catch (RepositoryException e) {
291 log.error("Could not retrieve user with id: {}", id, e);
292 }
293 return null;
294 }
295
296 @Override
297 public User getUser(Subject subject) throws UnsupportedOperationException {
298
299 if (subject == null) {
300 log.debug("subject not set.");
301 return new DummyUser();
302 }
303
304 Set<User> principalSet = subject.getPrincipals(User.class);
305 Iterator<User> entityIterator = principalSet.iterator();
306 if (!entityIterator.hasNext()) {
307
308 log.debug("user name not contained in principal set.");
309 return new DummyUser();
310 }
311 return entityIterator.next();
312 }
313
314
315
316
317
318 @Override
319 protected Node findPrincipalNode(String name, Session session) throws RepositoryException {
320 final String realmName = getRealmName();
321
322 final Node startNode = (Realm.REALM_ALL.getName().equals(realmName)) ? session.getRootNode() : session.getNode("/" + realmName);
323
324 return findPrincipalNode(name, session, NodeTypes.User.NAME, startNode);
325 }
326
327 protected User getFromRepository(final String name) throws RepositoryException {
328 return MgnlContext.doInSystemContext(new SilentSessionOp<User>(getRepositoryName()) {
329
330 @Override
331 public User doExec(Session session) throws RepositoryException {
332 Node userNode = findPrincipalNode(name, session);
333 return newUserInstance(userNode);
334 }
335
336 @Override
337 public String toString() {
338 return "Retrieve user [" + name + "] from repository.";
339 }
340 });
341 }
342
343
344
345
346 @Override
347 public User getSystemUser() throws UnsupportedOperationException {
348 throw new UnsupportedOperationException();
349 }
350
351
352
353
354 @Override
355 public User getAnonymousUser() throws UnsupportedOperationException {
356 throw new UnsupportedOperationException();
357 }
358
359
360
361
362 @Override
363 public Collection<User> getAllUsers() {
364 return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<User>>(getRepositoryName()) {
365
366 @Override
367 public Collection<User> doExec(Session session) throws RepositoryException {
368 List<User> users = new ArrayList<User>();
369 Node node = session.getNode("/" + realmName);
370 findAllUsersInFolder(node, users);
371 return users;
372 }
373
374 @Override
375 public String toString() {
376 return "get all users";
377 }
378
379 });
380 }
381
382
383
384
385
386 public void findAllUsersInFolder(Node node, Collection<User> addTo) throws RepositoryException {
387 final NodeIterator nodesIter = findPrincipalNodes(node, NodeTypes.User.NAME);
388 while (nodesIter.hasNext()) {
389 addTo.add(newUserInstance(nodesIter.nextNode()));
390 }
391 }
392
393 @Override
394 public User createUser(final String name, final String pw) {
395 return this.createUser(null, name, pw);
396 }
397
398 @Override
399 public User createUser(final String path, final String name, final String pw) throws UnsupportedOperationException {
400 validateUsername(name);
401 return MgnlContext.doInSystemContext(new SilentSessionOp<MgnlUser>(getRepositoryName()) {
402
403 @Override
404 public MgnlUser doExec(Session session) throws RepositoryException {
405 String uPath = path == null ? "/" + getRealmName() : path;
406 Node userNode = createUserNode(uPath, name, session);
407 userNode.addMixin(JcrConstants.MIX_LOCKABLE);
408 userNode.setProperty("name", name);
409 setPasswordProperty(userNode, pw);
410 userNode.setProperty("language", "en");
411
412 final String handle = userNode.getPath();
413 final Node acls = userNode.addNode(NODE_ACLUSERS, NodeTypes.ContentNode.NAME);
414
415 Node acl = acls.addNode(nodeNameHelper.getUniqueName(session, acls.getPath(), "0"), NodeTypes.ContentNode.NAME);
416 acl.setProperty("path", handle);
417 acl.setProperty("permissions", Permission.READ);
418
419 addWrite(handle, PROPERTY_EMAIL, acls);
420 addWrite(handle, PROPERTY_LANGUAGE, acls);
421 addWrite(handle, PROPERTY_LASTACCESS, acls);
422 addWrite(handle, PROPERTY_PASSWORD, acls);
423 addWrite(handle, PROPERTY_TITLE, acls);
424 addWrite(handle, PROPERTY_TIMEZONE, acls);
425 session.save();
426 return new MgnlUser(userNode.getName(), getRealmName(), Collections.emptySet(), Collections.emptySet(), Collections.emptyMap(), userNode.getPath(), userNode.getIdentifier(), Collections.emptySet(), Collections.emptySet());
427 }
428
429 @Override
430 public String toString() {
431 return "create user " + name;
432 }
433 });
434 }
435
436 @Override
437 public User changePassword(final User user, final String newPassword) {
438 return MgnlContext.doInSystemContext(new SilentSessionOp<User>(getRepositoryName()) {
439
440 @Override
441 public User doExec(Session session) throws RepositoryException {
442 Node userNode = findPrincipalNode(user.getName(), session);
443 if (userNode != null) {
444 setPasswordProperty(userNode, newPassword);
445
446 return newUserInstance(userNode);
447 }
448 return null;
449 }
450
451 @Override
452 public String toString() {
453 return "change password of user " + user.getName();
454 }
455 });
456 }
457
458 protected void setPasswordProperty(Node userNode, String clearPassword) throws RepositoryException {
459 userNode.setProperty(PROPERTY_PASSWORD, encodePassword(clearPassword));
460 userNode.getSession().save();
461 }
462
463 protected String encodePassword(String clearPassword) {
464 return SecurityUtil.getBCrypt(clearPassword);
465 }
466
467 protected void validateUsername(String name) {
468 if (StringUtils.isBlank(name)) {
469 throw new IllegalArgumentException(name + " is not a valid username.");
470 }
471
472 User user;
473 if (isAllowCrossRealmDuplicateNames()) {
474 user = this.getUser(name);
475 } else {
476 user = Security.getUserManager().getUser(name);
477 }
478 if (user != null) {
479 throw new IllegalArgumentException("User with name " + name + " already exists.");
480 }
481 }
482
483
484
485
486 @Deprecated
487 protected Content createUserNode(String name) throws RepositoryException {
488 final String path = "/" + getRealmName();
489 final String userName = name;
490 Node userNode = MgnlContext.doInSystemContext(new SilentSessionOp<Node>(getRepositoryName()) {
491 @Override
492 public Node doExec(Session session) throws RepositoryException {
493 return createUserNode(path, userName, session);
494 }
495
496 @Override
497 public String toString() {
498 return getClass().getName() + " createUSerNode(name)";
499 }
500 });
501 return ContentUtil.asContent(userNode);
502
503 }
504
505 protected Node createUserNode(String path, String userName, Session session) throws RepositoryException {
506 return session.getNode(path).addNode(userName, NodeTypes.User.NAME);
507 }
508
509
510
511
512
513
514 @Deprecated
515 protected HierarchyManager getHierarchyManager() {
516 return MgnlContext.getSystemContext().getHierarchyManager(RepositoryConstants.USERS);
517 }
518
519 private Node addWrite(String parentPath, String property, Node acls) throws PathNotFoundException, RepositoryException, AccessDeniedException {
520 Node acl = acls.addNode(nodeNameHelper.getUniqueName(acls.getSession(), acls.getPath(), "0"), NodeTypes.ContentNode.NAME);
521 acl.setProperty("path", parentPath + "/" + property);
522 acl.setProperty("permissions", Permission.ALL);
523 return acl;
524 }
525
526 @Override
527 public void updateLastAccessTimestamp(final User user) throws UnsupportedOperationException {
528 final String workspaceName = getRepositoryName();
529 try {
530 MgnlContext.doInSystemContext(new MgnlContext.LockingOp(workspaceName, ((MgnlUser) user).getPath()) {
531
532 @Override
533 public void doExec() throws RepositoryException {
534 Session session = MgnlContext.getJCRSession(workspaceName);
535 String path = ((MgnlUser) user).getPath();
536 log.debug("update access timestamp for {}", user.getName());
537 try {
538 Node userNode = session.getNode(path);
539 userNode = NodeUtil.deepUnwrap(userNode, MgnlPropertySettingNodeWrapper.class);
540 PropertyUtil.updateOrCreate(userNode, "lastaccess", new GregorianCalendar());
541 session.save();
542 } catch (RepositoryException e) {
543 session.refresh(false);
544 }
545 return;
546 }
547
548 @Override
549 public String toString() {
550 return getClass().getName() + " updateLastAccessTimestamp(user)";
551 }
552 });
553 } catch (LockException e) {
554 log.debug("Failed to lock node for last access timestamp update for user {} with {}", user.getName(), e.getMessage(), e);
555 } catch (RepositoryException e) {
556 log.error("Failed to update user {} last access time stamp with {}", user.getName(), e.getMessage(), e);
557 }
558 }
559
560 protected User newUserInstance(Node privilegedUserNode) throws ValueFormatException, PathNotFoundException, RepositoryException {
561 if (privilegedUserNode == null) {
562 return null;
563 }
564 Set<String> roles = collectUniquePropertyNames(privilegedUserNode, ROLES_NODE_NAME, RepositoryConstants.USER_ROLES, false);
565 Set<String> groups = collectUniquePropertyNames(privilegedUserNode, GROUPS_NODE_NAME, RepositoryConstants.USER_GROUPS, false);
566
567 final String userName = privilegedUserNode.getName();
568
569 final GroupManager groupManager = Components.getComponent(SecuritySupport.class).getGroupManager();
570
571 Set<String> allGroups = aggregateDirectAndTransitiveGroups(groups, userName, groupManager);
572 Set<String> allRoles = aggregateDirectAndTransitiveRoles(roles, allGroups, userName, groupManager);
573
574
575 Map<String, String> properties = new HashMap<>();
576 for (PropertyIterator iter = new FilteringPropertyIterator(privilegedUserNode.getProperties(), new JCRMgnlPropertyHidingPredicate()); iter.hasNext();) {
577 Property prop = iter.nextProperty();
578
579 properties.put(prop.getName(), prop.getString());
580 }
581 return new MgnlUser(userName, getRealmName(), groups, roles, properties, privilegedUserNode.getPath(), privilegedUserNode.getIdentifier(), allGroups, allRoles);
582 }
583
584 @Override
585 protected String getRepositoryName() {
586 return RepositoryConstants.USERS;
587 }
588
589
590
591
592 @Override
593 public Map<String, ACL> getACLs(final User user) {
594 if (!(user instanceof MgnlUser)) {
595 return null;
596 }
597 return super.getACLs(user.getName());
598 }
599
600 @Override
601 public User addRole(User user, String roleName) {
602 try {
603 super.add(user.getName(), roleName, NODE_ROLES);
604 } catch (PrincipalNotFoundException e) {
605
606 return null;
607 }
608 return getUser(user.getName());
609 }
610
611
612
613
614 protected Set<String> collectUniquePropertyNames(Node rootNode, String subnodeName, String repositoryName, boolean isDeep) {
615 final SortedSet<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
616 String path = null;
617 try {
618 path = rootNode.getPath();
619 final Node node = rootNode.getNode(subnodeName);
620 collectUniquePropertyNames(node, repositoryName, subnodeName, set, isDeep);
621 collectUniquePropertyNames(rootNode.getNode(subnodeName), repositoryName, subnodeName, set, isDeep);
622 } catch (PathNotFoundException e) {
623 log.debug("{} does not have any {}", path, repositoryName);
624 } catch (Throwable t) {
625 log.error("Failed to read {} or sub node {} in repository {}", path, subnodeName, repositoryName, t);
626 }
627 return set;
628 }
629
630 private void collectUniquePropertyNames(final Node node, final String repositoryName, final String subnodeName, final Collection<String> set, final boolean isDeep) throws RepositoryException {
631 if (!MgnlContext.isSystemInstance()) {
632 if (log.isDebugEnabled()) {
633 log.debug("Collecting user properties in user context. List might not include all properties. Please check the calling code (see stacktrace)", new Exception());
634 } else {
635 log.warn("Collecting user properties in user context. List might not include all properties. Please check the calling code (stacktrace will be printed for this call when debug logging is enabled)");
636 }
637 }
638 Session session = MgnlContext.getJCRSession(repositoryName);
639 for (PropertyIterator iter = new FilteringPropertyIterator(node.getProperties(), new JCRMgnlPropertyHidingPredicate()); iter.hasNext();) {
640 Property property = iter.nextProperty();
641 final String uuid = property.getString();
642 try {
643 final Node targetNode = session.getNodeByIdentifier(uuid);
644 set.add(targetNode.getName());
645 if (isDeep && targetNode.hasNode(subnodeName)) {
646 collectUniquePropertyNames(targetNode.getNode(subnodeName), repositoryName, subnodeName, set, true);
647 }
648 } catch (ItemNotFoundException t) {
649 final String path = property.getPath();
650
651 log.warn("Can't find {} node by UUID {} referred by node {}", repositoryName, t.getMessage(), path);
652 log.debug("Failed while reading node by UUID", t);
653
654
655 }
656 }
657 }
658
659 @Override
660 public User addGroup(User user, String groupName) {
661 try {
662 super.add(user.getName(), groupName, NODE_GROUPS);
663 } catch (PrincipalNotFoundException e) {
664
665 return null;
666 }
667 return getUser(user.getName());
668 }
669
670 @Override
671 public User removeGroup(User user, String groupName) {
672 try {
673 super.remove(user.getName(), groupName, NODE_GROUPS);
674 } catch (PrincipalNotFoundException e) {
675
676 return null;
677 }
678 return getUser(user.getName());
679 }
680
681 @Override
682 public User removeRole(User user, String roleName) {
683 try {
684 super.remove(user.getName(), roleName, NODE_ROLES);
685 } catch (PrincipalNotFoundException e) {
686
687 return null;
688 }
689 return getUser(user.getName());
690 }
691
692 @Override
693 public Collection<String> getUsersWithGroup(final String groupName) {
694 return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
695
696 @Override
697 public Collection<String> doExec(Session session) throws RepositoryException {
698 final Node groupNode = findPrincipalNode(groupName, MgnlContext.getJCRSession(RepositoryConstants.USER_GROUPS), NodeTypes.Group.NAME);
699 return findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(session, groupNode, GROUPS_NODE_NAME);
700 }
701
702 @Override
703 public String toString() {
704 return "get group " + groupName;
705 }
706 });
707 }
708
709 @Override
710 public Collection<String> getUsersWithRole(final String roleName) {
711 return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
712
713 @Override
714 public Collection<String> doExec(Session session) throws RepositoryException {
715 final Node roleNode = findPrincipalNode(roleName, MgnlContext.getJCRSession(RepositoryConstants.USER_ROLES), NodeTypes.Role.NAME);
716 return findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(session, roleNode, ROLES_NODE_NAME);
717 }
718
719 @Override
720 public String toString() {
721 return "get role " + roleName;
722 }
723 });
724 }
725
726 @Override
727 public Collection<String> getUsersWithGroup(String groupName, boolean transitive) {
728 if (!transitive) {
729 return getUsersWithGroup(groupName);
730 }
731
732 Set<String> users = new HashSet<>();
733
734 GroupManager man = SecuritySupport.Factory.getInstance().getGroupManager();
735 Collection<String> groupNames = man.getAllSubGroups(groupName);
736 groupNames.add(groupName);
737 for (String transitiveGroup : groupNames) {
738 Collection<String> userNames = getUsersWithGroup(transitiveGroup);
739 users.addAll(userNames);
740 }
741 return users;
742 }
743
744
745
746
747
748 protected Set<String> aggregateDirectAndTransitiveRoles(Collection<String> roles, Collection<String> allGroups, String name, GroupManager groupManager) {
749
750 final Set<String> set = new HashSet<>(roles);
751
752
753 allGroups.forEach(group -> {
754 try {
755 set.addAll(groupManager.getGroup(group).getRoles());
756 } catch (javax.jcr.AccessDeniedException e) {
757 log.debug("Skipping denied group {} for user {}", group, name, e);
758 } catch (UnsupportedOperationException e) {
759 log.debug("Skipping unsupported getGroup() for group {} and user {}", group, name, e);
760 }
761 });
762
763 return set.stream()
764 .sorted(String::compareToIgnoreCase)
765 .collect(Collectors.toSet());
766 }
767
768
769
770
771
772 protected Set<String> aggregateDirectAndTransitiveGroups(Collection<String> groups, String name, GroupManager groupManager) {
773 final Set<String> set = new HashSet<>();
774
775
776 addSubgroups(set, groupManager, groups, name);
777
778 return set.stream()
779 .sorted(String::compareToIgnoreCase)
780 .collect(Collectors.toSet());
781 }
782
783
784
785
786 private void addSubgroups(final Set<String> allGroups, GroupManager groupManager, Collection<String> groups, String name) {
787 groups.forEach(groupName -> {
788
789 if (!allGroups.contains(groupName)) {
790 allGroups.add(groupName);
791 try {
792 Group group = groupManager.getGroup(groupName);
793 if (group == null) {
794 log.error("Failed to resolve group {} for user {}.", groupName, name);
795 return;
796 }
797 Collection<String> subgroups = group.getGroups();
798
799 addSubgroups(allGroups, groupManager, subgroups, name);
800 } catch (javax.jcr.AccessDeniedException e) {
801 log.debug("Skipping denied group {} for user {}", groupName, name, e);
802 } catch (UnsupportedOperationException e) {
803 log.debug("Skipping unsupported getGroup() for group {} and user {}", groupName, name, e);
804 }
805 }
806 });
807 }
808 }