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
425 if (Realm.REALM_ADMIN.getName().equals(getRealmName())) {
426 addWrite(handle, PROPERTY_TIMEZONE, acls);
427 }
428 session.save();
429 return new MgnlUser(userNode.getName(), getRealmName(), Collections.emptySet(), Collections.emptySet(), Collections.emptyMap(), userNode.getPath(), userNode.getIdentifier(), Collections.emptySet(), Collections.emptySet());
430 }
431
432 @Override
433 public String toString() {
434 return "create user " + name;
435 }
436 });
437 }
438
439 @Override
440 public User changePassword(final User user, final String newPassword) {
441 return MgnlContext.doInSystemContext(new SilentSessionOp<User>(getRepositoryName()) {
442
443 @Override
444 public User doExec(Session session) throws RepositoryException {
445 Node userNode = findPrincipalNode(user.getName(), session);
446 if (userNode != null) {
447 setPasswordProperty(userNode, newPassword);
448
449 return newUserInstance(userNode);
450 }
451 return null;
452 }
453
454 @Override
455 public String toString() {
456 return "change password of user " + user.getName();
457 }
458 });
459 }
460
461 protected void setPasswordProperty(Node userNode, String clearPassword) throws RepositoryException {
462 userNode.setProperty(PROPERTY_PASSWORD, encodePassword(clearPassword));
463 userNode.getSession().save();
464 }
465
466 protected String encodePassword(String clearPassword) {
467 return SecurityUtil.getBCrypt(clearPassword);
468 }
469
470 protected void validateUsername(String name) {
471 if (StringUtils.isBlank(name)) {
472 throw new IllegalArgumentException(name + " is not a valid username.");
473 }
474
475 User user;
476 if (isAllowCrossRealmDuplicateNames()) {
477 user = this.getUser(name);
478 } else {
479 user = Security.getUserManager().getUser(name);
480 }
481 if (user != null) {
482 throw new IllegalArgumentException("User with name " + name + " already exists.");
483 }
484 }
485
486
487
488
489 @Deprecated
490 protected Content createUserNode(String name) throws RepositoryException {
491 final String path = "/" + getRealmName();
492 final String userName = name;
493 Node userNode = MgnlContext.doInSystemContext(new SilentSessionOp<Node>(getRepositoryName()) {
494 @Override
495 public Node doExec(Session session) throws RepositoryException {
496 return createUserNode(path, userName, session);
497 }
498
499 @Override
500 public String toString() {
501 return getClass().getName() + " createUSerNode(name)";
502 }
503 });
504 return ContentUtil.asContent(userNode);
505
506 }
507
508 protected Node createUserNode(String path, String userName, Session session) throws RepositoryException {
509 return session.getNode(path).addNode(userName, NodeTypes.User.NAME);
510 }
511
512
513
514
515
516
517 @Deprecated
518 protected HierarchyManager getHierarchyManager() {
519 return MgnlContext.getSystemContext().getHierarchyManager(RepositoryConstants.USERS);
520 }
521
522 private Node addWrite(String parentPath, String property, Node acls) throws PathNotFoundException, RepositoryException, AccessDeniedException {
523 Node acl = acls.addNode(nodeNameHelper.getUniqueName(acls.getSession(), acls.getPath(), "0"), NodeTypes.ContentNode.NAME);
524 acl.setProperty("path", parentPath + "/" + property);
525 acl.setProperty("permissions", Permission.ALL);
526 return acl;
527 }
528
529 @Override
530 public void updateLastAccessTimestamp(final User user) throws UnsupportedOperationException {
531 final String workspaceName = getRepositoryName();
532 try {
533 MgnlContext.doInSystemContext(new MgnlContext.LockingOp(workspaceName, ((MgnlUser) user).getPath()) {
534
535 @Override
536 public void doExec() throws RepositoryException {
537 Session session = MgnlContext.getJCRSession(workspaceName);
538 String path = ((MgnlUser) user).getPath();
539 log.debug("update access timestamp for {}", user.getName());
540 try {
541 Node userNode = session.getNode(path);
542 userNode = NodeUtil.deepUnwrap(userNode, MgnlPropertySettingNodeWrapper.class);
543 PropertyUtil.updateOrCreate(userNode, "lastaccess", new GregorianCalendar());
544 session.save();
545 } catch (RepositoryException e) {
546 session.refresh(false);
547 }
548 return;
549 }
550
551 @Override
552 public String toString() {
553 return getClass().getName() + " updateLastAccessTimestamp(user)";
554 }
555 });
556 } catch (LockException e) {
557 log.debug("Failed to lock node for last access timestamp update for user {} with {}", user.getName(), e.getMessage(), e);
558 } catch (RepositoryException e) {
559 log.error("Failed to update user {} last access time stamp with {}", user.getName(), e.getMessage(), e);
560 }
561 }
562
563 protected User newUserInstance(Node privilegedUserNode) throws ValueFormatException, PathNotFoundException, RepositoryException {
564 if (privilegedUserNode == null) {
565 return null;
566 }
567 Set<String> roles = collectUniquePropertyNames(privilegedUserNode, ROLES_NODE_NAME, RepositoryConstants.USER_ROLES, false);
568 Set<String> groups = collectUniquePropertyNames(privilegedUserNode, GROUPS_NODE_NAME, RepositoryConstants.USER_GROUPS, false);
569
570 final String userName = privilegedUserNode.getName();
571
572 final GroupManager groupManager = Components.getComponent(SecuritySupport.class).getGroupManager();
573
574 Set<String> allGroups = aggregateDirectAndTransitiveGroups(groups, userName, groupManager);
575 Set<String> allRoles = aggregateDirectAndTransitiveRoles(roles, allGroups, userName, groupManager);
576
577
578 Map<String, String> properties = new HashMap<>();
579 for (PropertyIterator iter = new FilteringPropertyIterator(privilegedUserNode.getProperties(), new JCRMgnlPropertyHidingPredicate()); iter.hasNext();) {
580 Property prop = iter.nextProperty();
581
582 properties.put(prop.getName(), prop.getString());
583 }
584 return new MgnlUser(userName, getRealmName(), groups, roles, properties, privilegedUserNode.getPath(), privilegedUserNode.getIdentifier(), allGroups, allRoles);
585 }
586
587 @Override
588 protected String getRepositoryName() {
589 return RepositoryConstants.USERS;
590 }
591
592
593
594
595 @Override
596 public Map<String, ACL> getACLs(final User user) {
597 if (!(user instanceof MgnlUser)) {
598 return null;
599 }
600 return super.getACLs(user.getName());
601 }
602
603 @Override
604 public User addRole(User user, String roleName) {
605 try {
606 super.add(user.getName(), roleName, NODE_ROLES);
607 } catch (PrincipalNotFoundException e) {
608
609 return null;
610 }
611 return getUser(user.getName());
612 }
613
614
615
616
617 protected Set<String> collectUniquePropertyNames(Node rootNode, String subnodeName, String repositoryName, boolean isDeep) {
618 final SortedSet<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
619 String path = null;
620 try {
621 path = rootNode.getPath();
622 final Node node = rootNode.getNode(subnodeName);
623 collectUniquePropertyNames(node, repositoryName, subnodeName, set, isDeep);
624 collectUniquePropertyNames(rootNode.getNode(subnodeName), repositoryName, subnodeName, set, isDeep);
625 } catch (PathNotFoundException e) {
626 log.debug("{} does not have any {}", path, repositoryName);
627 } catch (Throwable t) {
628 log.error("Failed to read {} or sub node {} in repository {}", path, subnodeName, repositoryName, t);
629 }
630 return set;
631 }
632
633 private void collectUniquePropertyNames(final Node node, final String repositoryName, final String subnodeName, final Collection<String> set, final boolean isDeep) throws RepositoryException {
634 if (!MgnlContext.isSystemInstance()) {
635 if (log.isDebugEnabled()) {
636 log.debug("Collecting user properties in user context. List might not include all properties. Please check the calling code (see stacktrace)", new Exception());
637 } else {
638 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)");
639 }
640 }
641 Session session = MgnlContext.getJCRSession(repositoryName);
642 for (PropertyIterator iter = new FilteringPropertyIterator(node.getProperties(), new JCRMgnlPropertyHidingPredicate()); iter.hasNext();) {
643 Property property = iter.nextProperty();
644 final String uuid = property.getString();
645 try {
646 final Node targetNode = session.getNodeByIdentifier(uuid);
647 set.add(targetNode.getName());
648 if (isDeep && targetNode.hasNode(subnodeName)) {
649 collectUniquePropertyNames(targetNode.getNode(subnodeName), repositoryName, subnodeName, set, true);
650 }
651 } catch (ItemNotFoundException t) {
652 final String path = property.getPath();
653
654 log.warn("Can't find {} node by UUID {} referred by node {}", repositoryName, t.getMessage(), path);
655 log.debug("Failed while reading node by UUID", t);
656
657
658 }
659 }
660 }
661
662 @Override
663 public User addGroup(User user, String groupName) {
664 try {
665 super.add(user.getName(), groupName, NODE_GROUPS);
666 } catch (PrincipalNotFoundException e) {
667
668 return null;
669 }
670 return getUser(user.getName());
671 }
672
673 @Override
674 public User removeGroup(User user, String groupName) {
675 try {
676 super.remove(user.getName(), groupName, NODE_GROUPS);
677 } catch (PrincipalNotFoundException e) {
678
679 return null;
680 }
681 return getUser(user.getName());
682 }
683
684 @Override
685 public User removeRole(User user, String roleName) {
686 try {
687 super.remove(user.getName(), roleName, NODE_ROLES);
688 } catch (PrincipalNotFoundException e) {
689
690 return null;
691 }
692 return getUser(user.getName());
693 }
694
695 @Override
696 public Collection<String> getUsersWithGroup(final String groupName) {
697 return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
698
699 @Override
700 public Collection<String> doExec(Session session) throws RepositoryException {
701 final Node groupNode = findPrincipalNode(groupName, MgnlContext.getJCRSession(RepositoryConstants.USER_GROUPS), NodeTypes.Group.NAME);
702 return findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(session, groupNode, GROUPS_NODE_NAME);
703 }
704
705 @Override
706 public String toString() {
707 return "get group " + groupName;
708 }
709 });
710 }
711
712 @Override
713 public Collection<String> getUsersWithRole(final String roleName) {
714 return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
715
716 @Override
717 public Collection<String> doExec(Session session) throws RepositoryException {
718 final Node roleNode = findPrincipalNode(roleName, MgnlContext.getJCRSession(RepositoryConstants.USER_ROLES), NodeTypes.Role.NAME);
719 return findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(session, roleNode, ROLES_NODE_NAME);
720 }
721
722 @Override
723 public String toString() {
724 return "get role " + roleName;
725 }
726 });
727 }
728
729 @Override
730 public Collection<String> getUsersWithGroup(String groupName, boolean transitive) {
731 if (!transitive) {
732 return getUsersWithGroup(groupName);
733 }
734
735 Set<String> users = new HashSet<>();
736
737 GroupManager man = SecuritySupport.Factory.getInstance().getGroupManager();
738 Collection<String> groupNames = man.getAllSubGroups(groupName);
739 groupNames.add(groupName);
740 for (String transitiveGroup : groupNames) {
741 Collection<String> userNames = getUsersWithGroup(transitiveGroup);
742 users.addAll(userNames);
743 }
744 return users;
745 }
746
747
748
749
750
751 protected Set<String> aggregateDirectAndTransitiveRoles(Collection<String> roles, Collection<String> allGroups, String name, GroupManager groupManager) {
752
753 final Set<String> set = new HashSet<>(roles);
754
755
756 allGroups.forEach(group -> {
757 try {
758 set.addAll(groupManager.getGroup(group).getRoles());
759 } catch (javax.jcr.AccessDeniedException e) {
760 log.debug("Skipping denied group {} for user {}", group, name, e);
761 } catch (UnsupportedOperationException e) {
762 log.debug("Skipping unsupported getGroup() for group {} and user {}", group, name, e);
763 }
764 });
765
766 return set.stream()
767 .sorted(String::compareToIgnoreCase)
768 .collect(Collectors.toSet());
769 }
770
771
772
773
774
775 protected Set<String> aggregateDirectAndTransitiveGroups(Collection<String> groups, String name, GroupManager groupManager) {
776 final Set<String> set = new HashSet<>();
777
778
779 addSubgroups(set, groupManager, groups, name);
780
781 return set.stream()
782 .sorted(String::compareToIgnoreCase)
783 .collect(Collectors.toSet());
784 }
785
786
787
788
789 private void addSubgroups(final Set<String> allGroups, GroupManager groupManager, Collection<String> groups, String name) {
790 groups.forEach(groupName -> {
791
792 if (!allGroups.contains(groupName)) {
793 allGroups.add(groupName);
794 try {
795 Group group = groupManager.getGroup(groupName);
796 if (group == null) {
797 log.error("Failed to resolve group {} for user {}.", groupName, name);
798 return;
799 }
800 Collection<String> subgroups = group.getGroups();
801
802 addSubgroups(allGroups, groupManager, subgroups, name);
803 } catch (javax.jcr.AccessDeniedException e) {
804 log.debug("Skipping denied group {} for user {}", groupName, name, e);
805 } catch (UnsupportedOperationException e) {
806 log.debug("Skipping unsupported getGroup() for group {} and user {}", groupName, name, e);
807 }
808 }
809 });
810 }
811 }