View Javadoc
1   /**
2    * This file Copyright (c) 2003-2017 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.cms.security;
35  
36  import static info.magnolia.cms.security.SecurityConstants.NODE_GROUPS;
37  import static info.magnolia.cms.security.SecurityConstants.NODE_ROLES;
38  
39  import info.magnolia.cms.core.Content;
40  import info.magnolia.cms.core.HierarchyManager;
41  import info.magnolia.cms.security.auth.ACL;
42  import info.magnolia.cms.util.ContentUtil;
43  import info.magnolia.context.MgnlContext;
44  import info.magnolia.jcr.iterator.FilteringPropertyIterator;
45  import info.magnolia.jcr.predicate.JCRMgnlPropertyHidingPredicate;
46  import info.magnolia.jcr.util.NodeNameHelper;
47  import info.magnolia.jcr.util.NodeTypes;
48  import info.magnolia.jcr.util.NodeUtil;
49  import info.magnolia.jcr.util.PropertyUtil;
50  import info.magnolia.jcr.wrapper.MgnlPropertySettingNodeWrapper;
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  
66  import javax.inject.Inject;
67  import javax.jcr.ItemNotFoundException;
68  import javax.jcr.Node;
69  import javax.jcr.NodeIterator;
70  import javax.jcr.PathNotFoundException;
71  import javax.jcr.Property;
72  import javax.jcr.PropertyIterator;
73  import javax.jcr.RepositoryException;
74  import javax.jcr.Session;
75  import javax.jcr.Value;
76  import javax.jcr.ValueFormatException;
77  import javax.jcr.lock.LockException;
78  import javax.security.auth.Subject;
79  
80  import org.apache.commons.lang3.StringUtils;
81  import org.apache.jackrabbit.JcrConstants;
82  import org.slf4j.Logger;
83  import org.slf4j.LoggerFactory;
84  
85  /**
86   * Manages the users stored in Magnolia itself.
87   */
88  public class MgnlUserManager extends RepositoryBackedSecurityManager implements UserManager {
89  
90      private static final Logger log = LoggerFactory.getLogger(MgnlUserManager.class);
91  
92      public static final String PROPERTY_EMAIL = "email";
93      public static final String PROPERTY_LANGUAGE = "language";
94      public static final String PROPERTY_LASTACCESS = "lastaccess";
95      public static final String PROPERTY_PASSWORD = "pswd";
96      public static final String PROPERTY_TITLE = "title";
97      public static final String PROPERTY_ENABLED = "enabled";
98      public static final String PROPERTY_TIMEZONE = "timezone";
99  
100     public static final String NODE_ACLUSERS = "acl_users";
101 
102     private String realmName;
103 
104     private boolean allowCrossRealmDuplicateNames = false;
105 
106     private int maxFailedLoginAttempts;
107 
108     private int lockTimePeriod;
109 
110     /**
111      * There should be no need to instantiate this class except maybe for testing. Manual instantiation might cause manager not to be initialized properly.
112      */
113     @Inject
114     public MgnlUserManager(NodeNameHelper nodeNameHelper) {
115         super(nodeNameHelper);
116     }
117 
118     /**
119      * @deprecated since 5.5.3, use {@link #MgnlUserManager(NodeNameHelper)} instead.
120      */
121     @Deprecated
122     public MgnlUserManager() {
123     }
124 
125     @Override
126     public void setMaxFailedLoginAttempts(int maxFailedLoginAttempts) {
127         this.maxFailedLoginAttempts = maxFailedLoginAttempts;
128     }
129 
130     @Override
131     public int getMaxFailedLoginAttempts() {
132         return maxFailedLoginAttempts;
133     }
134 
135     @Override
136     public int getLockTimePeriod() {
137         return lockTimePeriod;
138     }
139 
140     @Override
141     public void setLockTimePeriod(int lockTimePeriod) {
142         this.lockTimePeriod = lockTimePeriod;
143     }
144 
145     @Override
146     public User setProperty(final User user, final String propertyName, final Value propertyValue) {
147         return MgnlContext.doInSystemContext(new SilentSessionOp<User>(getRepositoryName()) {
148 
149             @Override
150             public User doExec(Session session) throws RepositoryException {
151                 String path = ((MgnlUser) user).getPath();
152                 Node userNode;
153                 try {
154                     userNode = session.getNode(path);
155                     // setting value to null would remove existing properties anyway, so no need to create a
156                     // not-yet-existing-one first and then set it to null.
157                     if (propertyValue != null || PropertyUtil.getPropertyOrNull(userNode, propertyName) != null) {
158                         if (StringUtils.equals(propertyName, PROPERTY_PASSWORD)) {
159                             setPasswordProperty(userNode, propertyValue.getString());
160                         } else {
161                             userNode.setProperty(propertyName, propertyValue);
162                             session.save();
163                         }
164                     }
165                 } catch (RepositoryException e) {
166                     session.refresh(false);
167                     log.error("Property {} can't be changed. {}", propertyName, e.getMessage());
168                     return user;
169                 }
170                 return newUserInstance(userNode);
171             }
172 
173             @Override
174             public String toString() {
175                 return getClass().getName() + " setProperty(user, propertyName, Value propertyValue)";
176             }
177         });
178     }
179 
180     @Override
181     public User setProperty(final User user, final String propertyName, final String propertyValue) {
182         return MgnlContext.doInSystemContext(new SilentSessionOp<User>(getRepositoryName()) {
183 
184             @Override
185             public User doExec(Session session) throws RepositoryException {
186                 String path = ((MgnlUser) user).getPath();
187                 Node userNode;
188                 try {
189                     userNode = session.getNode(path);
190                     // setting value to null would remove existing properties anyway, so no need to create a
191                     // not-yet-existing-one first and then set it to null.
192                     if (propertyName != null) {
193                         if (StringUtils.equals(propertyName, PROPERTY_PASSWORD)) {
194                             setPasswordProperty(userNode, propertyValue);
195                         } else {
196                             userNode.setProperty(propertyName, propertyValue);
197                             session.save();
198                         }
199                     }
200                 } catch (RepositoryException e) {
201                     session.refresh(false);
202                     log.error("Property {} can't be changed. {}", propertyName, e.getMessage());
203                     return user;
204                 }
205                 return newUserInstance(userNode);
206             }
207 
208             @Override
209             public String toString() {
210                 return getClass().getName() + " setProperty(user, propertyName, String propertyValue)";
211             }
212         });
213     }
214 
215     public void setRealmName(String name) {
216         this.realmName = name;
217     }
218 
219     public String getRealmName() {
220         return realmName;
221     }
222 
223     public void setAllowCrossRealmDuplicateNames(boolean allowCrossRealmDuplicateNames) {
224         this.allowCrossRealmDuplicateNames = allowCrossRealmDuplicateNames;
225     }
226 
227     public boolean isAllowCrossRealmDuplicateNames() {
228         return allowCrossRealmDuplicateNames;
229     }
230 
231     /**
232      * Get the user object. Uses a search
233      *
234      * @param name name of the user to retrieve
235      * @return the user object
236      */
237     @Override
238     public User getUser(final String name) {
239         try {
240             // method is called internally as well as externally, we do not need to wrap it multiple times unnecessarily.
241             if (MgnlContext.isSystemInstance()) {
242                 return getUser(name, MgnlContext.getJCRSession(getRepositoryName()));
243             } else {
244                 return MgnlContext.doInSystemContext(new JCRSessionOp<User>(getRepositoryName()) {
245                     @Override
246                     public User exec(Session session) throws RepositoryException {
247                         return getUser(name, session);
248                     }
249 
250                     @Override
251                     public String toString() {
252                         return "retrieve user " + name;
253                     }
254                 });
255             }
256         } catch (RepositoryException e) {
257             log.error("Could not retrieve user with name: {}", name, e);
258         }
259         return null;
260     }
261 
262     private User getUser(String name, Session session) throws RepositoryException {
263         Node priviledgedUserNode = findPrincipalNode(name, session);
264         return newUserInstance(priviledgedUserNode);
265     }
266 
267     /**
268      * Get the user object. Uses a search
269      *
270      * @param id user identifier
271      * @return the user object
272      */
273     @Override
274     public User getUserById(final String id) {
275         try {
276             return MgnlContext.doInSystemContext(new JCRSessionOp<User>(getRepositoryName()) {
277                 @Override
278                 public User exec(Session session) throws RepositoryException {
279                     Node priviledgedUserNode = session.getNodeByIdentifier(id);
280                     return newUserInstance(priviledgedUserNode);
281                 }
282 
283                 @Override
284                 public String toString() {
285                     return "retrieve user with id " + id;
286                 }
287             });
288         } catch (RepositoryException e) {
289             log.error("Could not retrieve user with id: {}", id, e);
290         }
291         return null;
292     }
293 
294     @Override
295     public User getUser(Subject subject) throws UnsupportedOperationException {
296         // this could be the case if no one is logged in yet
297         if (subject == null) {
298             log.debug("subject not set.");
299             return new DummyUser();
300         }
301 
302         Set<User> principalSet = subject.getPrincipals(User.class);
303         Iterator<User> entityIterator = principalSet.iterator();
304         if (!entityIterator.hasNext()) {
305             // happens when JCR authentication module set to optional and user doesn't exist in magnolia
306             log.debug("user name not contained in principal set.");
307             return new DummyUser();
308         }
309         return entityIterator.next();
310     }
311 
312     /**
313      * Helper method to find a user in a certain realm. Uses JCR Query.
314      * This will return null if user doesn't exist in realm.
315      */
316     @Override
317     protected Node findPrincipalNode(String name, Session session) throws RepositoryException {
318         final String realmName = getRealmName();
319         // the all realm searches the repository
320         final Node startNode = (Realm.REALM_ALL.getName().equals(realmName)) ? session.getRootNode() : session.getNode("/" + realmName);
321 
322         return findPrincipalNode(name, session, NodeTypes.User.NAME, startNode);
323     }
324 
325     protected User getFromRepository(final String name) throws RepositoryException {
326         return MgnlContext.doInSystemContext(new SilentSessionOp<User>(getRepositoryName()) {
327 
328             @Override
329             public User doExec(Session session) throws RepositoryException {
330                 Node userNode = findPrincipalNode(name, session);
331                 return newUserInstance(userNode);
332             }
333 
334             @Override
335             public String toString() {
336                 return "Retrieve user [" + name + "] from repository.";
337             }
338         });
339     }
340 
341     /**
342      * SystemUserManager does this.
343      */
344     @Override
345     public User getSystemUser() throws UnsupportedOperationException {
346         throw new UnsupportedOperationException();
347     }
348 
349     /**
350      * SystemUserManager does this.
351      */
352     @Override
353     public User getAnonymousUser() throws UnsupportedOperationException {
354         throw new UnsupportedOperationException();
355     }
356 
357     /**
358      * Get all users managed by this user manager.
359      */
360     @Override
361     public Collection<User> getAllUsers() {
362         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<User>>(getRepositoryName()) {
363 
364             @Override
365             public Collection<User> doExec(Session session) throws RepositoryException {
366                 List<User> users = new ArrayList<User>();
367                 Node node = session.getNode("/" + realmName);
368                 findAllUsersInFolder(node, users);
369                 return users;
370             }
371 
372             @Override
373             public String toString() {
374                 return "get all users";
375             }
376 
377         });
378     }
379 
380     /**
381      * Finds all users located in the provided node or in sub-folders within it and adds them to the given collection.
382      * As this method bases on a method using jcr queries to find nodes, it might not see nodes that have been created but not saved yet (i.e. during installation of a module).
383      */
384     public void findAllUsersInFolder(Node node, Collection<User> addTo) throws RepositoryException {
385         final NodeIterator nodesIter = findPrincipalNodes(node, NodeTypes.User.NAME);
386         while (nodesIter.hasNext()) {
387             addTo.add(newUserInstance(nodesIter.nextNode()));
388         }
389     }
390 
391     @Override
392     public User createUser(final String name, final String pw) {
393         return this.createUser(null, name, pw);
394     }
395 
396     @Override
397     public User createUser(final String path, final String name, final String pw) throws UnsupportedOperationException {
398         validateUsername(name);
399         return MgnlContext.doInSystemContext(new SilentSessionOp<MgnlUser>(getRepositoryName()) {
400 
401             @Override
402             public MgnlUser doExec(Session session) throws RepositoryException {
403                 String uPath = path == null ? "/" + getRealmName() : path;
404                 Node userNode = createUserNode(uPath, name, session);
405                 userNode.addMixin(JcrConstants.MIX_LOCKABLE);
406                 userNode.setProperty("name", name);
407                 setPasswordProperty(userNode, pw);
408                 userNode.setProperty("language", "en");
409 
410                 final String handle = userNode.getPath();
411                 final Node acls = userNode.addNode(NODE_ACLUSERS, NodeTypes.ContentNode.NAME);
412                 // read only access to the node itself
413                 Node acl = acls.addNode(nodeNameHelper.getUniqueName(session, acls.getPath(), "0"), NodeTypes.ContentNode.NAME);
414                 acl.setProperty("path", handle);
415                 acl.setProperty("permissions", Permission.READ);
416                 // those who had access to their nodes should get access to their own props
417                 addWrite(handle, PROPERTY_EMAIL, acls);
418                 addWrite(handle, PROPERTY_LANGUAGE, acls);
419                 addWrite(handle, PROPERTY_LASTACCESS, acls);
420                 addWrite(handle, PROPERTY_PASSWORD, acls);
421                 addWrite(handle, PROPERTY_TITLE, acls);
422                 addWrite(handle, PROPERTY_TIMEZONE, acls);
423                 session.save();
424                 return new MgnlUser(userNode.getName(), getRealmName(), Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_MAP, userNode.getPath(), userNode.getIdentifier());
425             }
426 
427             @Override
428             public String toString() {
429                 return "create user " + name;
430             }
431         });
432     }
433 
434     @Override
435     public User changePassword(final User user, final String newPassword) {
436         return MgnlContext.doInSystemContext(new SilentSessionOp<User>(getRepositoryName()) {
437 
438             @Override
439             public User doExec(Session session) throws RepositoryException {
440                 Node userNode = findPrincipalNode(user.getName(), session);
441                 if (userNode != null) {
442                     setPasswordProperty(userNode, newPassword);
443 
444                     return newUserInstance(userNode);
445                 }
446                 return null;
447             }
448 
449             @Override
450             public String toString() {
451                 return "change password of user " + user.getName();
452             }
453         });
454     }
455 
456     protected void setPasswordProperty(Node userNode, String clearPassword) throws RepositoryException {
457         userNode.setProperty(PROPERTY_PASSWORD, encodePassword(clearPassword));
458         userNode.getSession().save();
459     }
460 
461     protected String encodePassword(String clearPassword) {
462         return SecurityUtil.getBCrypt(clearPassword);
463     }
464 
465     protected void validateUsername(String name) {
466         if (StringUtils.isBlank(name)) {
467             throw new IllegalArgumentException(name + " is not a valid username.");
468         }
469 
470         User user;
471         if (isAllowCrossRealmDuplicateNames()) {
472             user = this.getUser(name);
473         } else {
474             user = Security.getUserManager().getUser(name);
475         }
476         if (user != null) {
477             throw new IllegalArgumentException("User with name " + name + " already exists.");
478         }
479     }
480 
481     /**
482      * @deprecated since 5.3.2 use {@link #createUserNode(String, String, javax.jcr.Session)} instead
483      */
484     @Deprecated
485     protected Content createUserNode(String name) throws RepositoryException {
486         final String path = "/" + getRealmName();
487         final String userName = name;
488         Node userNode = MgnlContext.doInSystemContext(new SilentSessionOp<Node>(getRepositoryName()) {
489             @Override
490             public Node doExec(Session session) throws RepositoryException {
491                 return createUserNode(path, userName, session);
492             }
493 
494             @Override
495             public String toString() {
496                 return getClass().getName() + " createUSerNode(name)";
497             }
498         });
499         return ContentUtil.asContent(userNode);
500 
501     }
502 
503     protected Node createUserNode(String path, String userName, Session session) throws RepositoryException {
504         return session.getNode(path).addNode(userName, NodeTypes.User.NAME);
505     }
506 
507     /**
508      * Return the HierarchyManager for the user workspace (through the system context).
509      *
510      * @deprecated since 5.3.2 without replacement
511      */
512     @Deprecated
513     protected HierarchyManager getHierarchyManager() {
514         return MgnlContext.getSystemContext().getHierarchyManager(RepositoryConstants.USERS);
515     }
516 
517     private Node addWrite(String parentPath, String property, Node acls) throws PathNotFoundException, RepositoryException, AccessDeniedException {
518         Node acl = acls.addNode(nodeNameHelper.getUniqueName(acls.getSession(), acls.getPath(), "0"), NodeTypes.ContentNode.NAME);
519         acl.setProperty("path", parentPath + "/" + property);
520         acl.setProperty("permissions", Permission.ALL);
521         return acl;
522     }
523 
524     @Override
525     public void updateLastAccessTimestamp(final User user) throws UnsupportedOperationException {
526         final String workspaceName = getRepositoryName();
527         try {
528             MgnlContext.doInSystemContext(new MgnlContext.LockingOp(workspaceName, ((MgnlUser) user).getPath()) {
529 
530                 @Override
531                 public void doExec() throws RepositoryException {
532                     Session session = MgnlContext.getJCRSession(workspaceName);
533                     String path = ((MgnlUser) user).getPath();
534                     log.debug("update access timestamp for {}", user.getName());
535                     try {
536                         Node userNode = session.getNode(path);
537                         userNode = NodeUtil.deepUnwrap(userNode, MgnlPropertySettingNodeWrapper.class);
538                         PropertyUtil.updateOrCreate(userNode, "lastaccess", new GregorianCalendar());
539                         session.save();
540                     } catch (RepositoryException e) {
541                         session.refresh(false);
542                     }
543                     return;
544                 }
545 
546                 @Override
547                 public String toString() {
548                     return getClass().getName() + " updateLastAccessTimestamp(user)";
549                 }
550             });
551         } catch (LockException e) {
552             log.debug("Failed to lock node for last access timestamp update for user {} with {}", user.getName(), e.getMessage(), e);
553         } catch (RepositoryException e) {
554             log.error("Failed to update user {} last access time stamp with {}", user.getName(), e.getMessage(), e);
555         }
556     }
557 
558     protected User newUserInstance(Node privilegedUserNode) throws ValueFormatException, PathNotFoundException, RepositoryException {
559         if (privilegedUserNode == null) {
560             return null;
561         }
562         Set<String> roles = collectUniquePropertyNames(privilegedUserNode, ROLES_NODE_NAME, RepositoryConstants.USER_ROLES, false);
563         Set<String> groups = collectUniquePropertyNames(privilegedUserNode, GROUPS_NODE_NAME, RepositoryConstants.USER_GROUPS, false);
564 
565         Map<String, String> properties = new HashMap<String, String>();
566         for (PropertyIterator iter = new FilteringPropertyIterator(privilegedUserNode.getProperties(), new JCRMgnlPropertyHidingPredicate()); iter.hasNext();) {
567             Property prop = iter.nextProperty();
568             // TODO: should we check and skip binary props in case someone adds image to the user?
569             properties.put(prop.getName(), prop.getString());
570         }
571 
572         MgnlUser user = new MgnlUser(privilegedUserNode.getName(), getRealmName(), groups, roles, properties, privilegedUserNode.getPath(), privilegedUserNode.getIdentifier());
573         return user;
574     }
575 
576     @Override
577     protected String getRepositoryName() {
578         return RepositoryConstants.USERS;
579     }
580 
581     /**
582      * Sets access control list from a list of roles under the provided content object.
583      */
584     @Override
585     public Map<String, ACL> getACLs(final User user) {
586         if (!(user instanceof MgnlUser)) {
587             return null;
588         }
589         return super.getACLs(user.getName());
590     }
591 
592     @Override
593     public User addRole(User user, String roleName) {
594         try {
595             super.add(user.getName(), roleName, NODE_ROLES);
596         } catch (PrincipalNotFoundException e) {
597             // user doesn't exist in this UM
598             return null;
599         }
600         return getUser(user.getName());
601     }
602 
603     /**
604      * Collects all property names of given type, sorting them (case insensitive) and removing duplicates in the process.
605      */
606     private Set<String> collectUniquePropertyNames(Node rootNode, String subnodeName, String repositoryName, boolean isDeep) {
607         final SortedSet<String> set = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
608         String path = null;
609         try {
610             path = rootNode.getPath();
611             final Node node = rootNode.getNode(subnodeName);
612             collectUniquePropertyNames(node, repositoryName, subnodeName, set, isDeep);
613             collectUniquePropertyNames(rootNode.getNode(subnodeName), repositoryName, subnodeName, set, isDeep);
614         } catch (PathNotFoundException e) {
615             log.debug("{} does not have any {}", path, repositoryName);
616         } catch (Throwable t) {
617             log.error("Failed to read {} or sub node {} in repository {}", path, subnodeName, repositoryName, t);
618         }
619         return set;
620     }
621 
622     private void collectUniquePropertyNames(final Node node, final String repositoryName, final String subnodeName, final Collection<String> set, final boolean isDeep) throws RepositoryException {
623         if (!MgnlContext.isSystemInstance()) {
624             if (log.isDebugEnabled()) {
625                 log.debug("Collecting user properties in user context. List might not include all properties. Please check the calling code (see stacktrace)", new Exception());
626             } else {
627                 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)");
628             }
629         }
630         Session session = MgnlContext.getJCRSession(repositoryName);
631         for (PropertyIterator iter = new FilteringPropertyIterator(node.getProperties(), new JCRMgnlPropertyHidingPredicate()); iter.hasNext();) {
632             Property property = iter.nextProperty();
633             final String uuid = property.getString();
634             try {
635                 final Node targetNode = session.getNodeByIdentifier(uuid);
636                 set.add(targetNode.getName());
637                 if (isDeep && targetNode.hasNode(subnodeName)) {
638                     collectUniquePropertyNames(targetNode.getNode(subnodeName), repositoryName, subnodeName, set, true);
639                 }
640             } catch (ItemNotFoundException t) {
641                 final String path = property.getPath();
642                 // TODO: why we are using UUIDs here? shouldn't be better to use group names, since uuids can change???
643                 log.warn("Can't find {} node by UUID {} referred by node {}", repositoryName, t.getMessage(), path);
644                 log.debug("Failed while reading node by UUID", t);
645                 // we continue since it can happen that target node is removed
646                 // - UUID's are kept as simple strings thus have no referential integrity
647             }
648         }
649     }
650 
651     @Override
652     public User addGroup(User user, String groupName) {
653         try {
654             super.add(user.getName(), groupName, NODE_GROUPS);
655         } catch (PrincipalNotFoundException e) {
656             // user doesn't exist in this UM
657             return null;
658         }
659         return getUser(user.getName());
660     }
661 
662     @Override
663     public User removeGroup(User user, String groupName) {
664         try {
665             super.remove(user.getName(), groupName, NODE_GROUPS);
666         } catch (PrincipalNotFoundException e) {
667             // user doesn't exist in this UM
668             return null;
669         }
670         return getUser(user.getName());
671     }
672 
673     @Override
674     public User removeRole(User user, String roleName) {
675         try {
676             super.remove(user.getName(), roleName, NODE_ROLES);
677         } catch (PrincipalNotFoundException e) {
678             // user doesn't exist in this UM
679             return null;
680         }
681         return getUser(user.getName());
682     }
683 
684     @Override
685     public Collection<String> getUsersWithGroup(final String groupName) {
686         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
687 
688             @Override
689             public Collection<String> doExec(Session session) throws RepositoryException {
690                 final Node groupNode = findPrincipalNode(groupName, MgnlContext.getJCRSession(RepositoryConstants.USER_GROUPS), NodeTypes.Group.NAME);
691                 return findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(session, groupNode, GROUPS_NODE_NAME);
692             }
693 
694             @Override
695             public String toString() {
696                 return "get group " + groupName;
697             }
698         });
699     }
700 
701     @Override
702     public Collection<String> getUsersWithRole(final String roleName) {
703         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
704 
705             @Override
706             public Collection<String> doExec(Session session) throws RepositoryException {
707                 final Node roleNode = findPrincipalNode(roleName, MgnlContext.getJCRSession(RepositoryConstants.USER_ROLES), NodeTypes.Role.NAME);
708                 return findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(session, roleNode, ROLES_NODE_NAME);
709             }
710 
711             @Override
712             public String toString() {
713                 return "get role " + roleName;
714             }
715         });
716     }
717 
718     @Override
719     public Collection<String> getUsersWithGroup(String groupName, boolean transitive) {
720         if (!transitive) {
721             return getUsersWithGroup(groupName);
722         }
723 
724         Set<String> users = new HashSet<>();
725         // FYI: can't inject securitySupport or get static instance of SecuritySupport during the initialization phase.
726         GroupManager man = SecuritySupport.Factory.getInstance().getGroupManager();
727         Collection<String> groupNames = man.getAllSubGroups(groupName);
728         groupNames.add(groupName);
729         for (String transitiveGroup : groupNames) {
730             Collection<String> userNames = getUsersWithGroup(transitiveGroup);
731             users.addAll(userNames);
732         }
733         return users;
734     }
735 }