View Javadoc
1   /**
2    * This file Copyright (c) 2003-2016 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.*;
37  
38  import info.magnolia.context.MgnlContext;
39  import info.magnolia.jcr.iterator.FilteringPropertyIterator;
40  import info.magnolia.jcr.util.NodeTypes;
41  import info.magnolia.jcr.util.NodeUtil;
42  import info.magnolia.repository.RepositoryConstants;
43  
44  import java.util.ArrayList;
45  import java.util.Collection;
46  import java.util.Collections;
47  import java.util.HashSet;
48  import java.util.List;
49  
50  import javax.jcr.ItemNotFoundException;
51  import javax.jcr.Node;
52  import javax.jcr.NodeIterator;
53  import javax.jcr.Property;
54  import javax.jcr.PropertyIterator;
55  import javax.jcr.RepositoryException;
56  import javax.jcr.Session;
57  
58  import org.apache.commons.lang3.StringUtils;
59  import org.slf4j.Logger;
60  import org.slf4j.LoggerFactory;
61  
62  /**
63   * Group manager working directly with JCR API and returning simple groups (no JCR node aware).
64   */
65  public class MgnlGroupManager extends RepositoryBackedSecurityManager implements GroupManager {
66  
67      private static final Logger log = LoggerFactory.getLogger(MgnlGroupManager.class);
68  
69      @Override
70      public Group createGroup(final String name) throws AccessDeniedException {
71          return createGroup(null, name);
72      }
73  
74      /**
75       * Creates a new group in a specific folder. The folder must exist.
76       *
77       * @throws IllegalArgumentException if the name is not valid or if a group with this name already exists
78       * @throws UnsupportedOperationException if the implementation does not support writing
79       */
80      public Group createGroup(final String path, final String name) throws AccessDeniedException {
81          validateGroupName(name);
82          return MgnlContext.doInSystemContext(new SilentSessionOp<MgnlGroup>(getRepositoryName()) {
83  
84              @Override
85              public MgnlGroup doExec(Session session) throws RepositoryException {
86                  String parentPath = StringUtils.defaultString(path, "/");
87                  Node groupNode = session.getNode(parentPath).addNode(name, NodeTypes.Group.NAME);
88                  session.save();
89                  return new MgnlGroup(groupNode.getIdentifier(), groupNode.getName(), Collections.EMPTY_LIST, Collections.EMPTY_LIST);
90              }
91  
92              @Override
93              public String toString() {
94                  return "create group " + name;
95              }
96          });
97      }
98  
99      @Override
100     public Group getGroup(final String name) throws AccessDeniedException {
101         return MgnlContext.doInSystemContext(new SilentSessionOp<Group>(getRepositoryName()) {
102 
103             @Override
104             public Group doExec(Session session) throws RepositoryException {
105                 Node privilegedGroupNode = findPrincipalNode(name, session);
106                 if (privilegedGroupNode == null) {
107                     return null;
108                 }
109                 return newGroupInstance(privilegedGroupNode);
110             }
111 
112             @Override
113             public String toString() {
114                 return "get group " + name;
115             }
116         });
117     }
118 
119     @Override
120     public Collection<Group> getAllGroups() {
121         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<Group>>(getRepositoryName()) {
122 
123             @Override
124             public Collection<Group> doExec(Session session) throws RepositoryException {
125                 List<Group> groups = new ArrayList<Group>();
126                 Node rootNode = session.getNode("/");
127                 findAllGroupsInFolder(rootNode, groups);
128                 return groups;
129             }
130 
131             @Override
132             public String toString() {
133                 return "get all groups";
134             }
135 
136         });
137     }
138 
139     @Override
140     public Collection<String> getAllGroups(final String name) {
141         return getAllSuperGroups(name);
142     }
143 
144     protected Group newGroupInstance(Node node) throws RepositoryException {
145         // remove duplicates
146         Collection<String> groups = new HashSet<String>();
147         if (node.hasNode(NODE_GROUPS)) {
148             for (PropertyIterator iter = new FilteringPropertyIterator(node.getNode(NODE_GROUPS).getProperties(), NodeUtil.ALL_PROPERTIES_EXCEPT_JCR_AND_MGNL_FILTER); iter.hasNext(); ) {
149                 Property subgroup = iter.nextProperty();
150                 String resources = getResourceName(subgroup.getString());
151                 if (resources != null) {
152                     groups.add(resources);
153                 }
154             }
155         }
156         Collection<String> roles = new HashSet<String>();
157         if (node.hasNode(NODE_ROLES)) {
158             RoleManager roleMan = SecuritySupport.Factory.getInstance().getRoleManager();
159             for (PropertyIterator iter = new FilteringPropertyIterator(node.getNode(NODE_ROLES).getProperties(), NodeUtil.ALL_PROPERTIES_EXCEPT_JCR_AND_MGNL_FILTER); iter.hasNext(); ) {
160                 Property role = iter.nextProperty();
161                 try {
162                     String roleName = roleMan.getRoleNameById(role.getString());
163                     if (roleName != null) {
164                         roles.add(roleName);
165                     }
166                 } catch (ItemNotFoundException e) {
167                     log.warn("assigned role {} doesn't exist.", role.getString());
168                 }
169             }
170         }
171         MgnlGroup group = new MgnlGroup(node.getIdentifier(), node.getName(), groups, roles);
172         return group;
173     }
174 
175     /**
176      * Helper method to find a group. Uses JCR Query.
177      * This will return null if group doesn't exist.
178      */
179     @Override
180     protected Node findPrincipalNode(String principalName, Session session) throws RepositoryException {
181         return findPrincipalNode(principalName, session, NodeTypes.Group.NAME);
182     }
183 
184     @Override
185     protected String getRepositoryName() {
186         return RepositoryConstants.USER_GROUPS;
187     }
188 
189     @Override
190     public Group addRole(Group group, String roleName) throws AccessDeniedException {
191         try {
192             add(group.getName(), roleName, NODE_ROLES);
193         } catch (PrincipalNotFoundException e) {
194             // group doesn't exist in this GM
195             return null;
196         }
197         return getGroup(group.getName());
198     }
199 
200     @Override
201     public Group addGroup(Group group, String groupName) throws AccessDeniedException {
202         try {
203             add(group.getName(), groupName, NODE_GROUPS);
204         } catch (PrincipalNotFoundException e) {
205             // group doesn't exist in this GM
206             return null;
207         }
208         return getGroup(groupName);
209     }
210 
211     /**
212      * Finds all groups located in the provided node or in sub-folders within it and adds them to the given collection.
213      * 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).
214      */
215     protected void findAllGroupsInFolder(Node node, Collection<Group> addTo) throws RepositoryException {
216         final NodeIterator nodesIter = findPrincipalNodes(node, NodeTypes.Group.NAME);
217         while (nodesIter.hasNext()) {
218             addTo.add(newGroupInstance(nodesIter.nextNode()));
219         }
220     }
221 
222     protected void validateGroupName(String name) throws AccessDeniedException {
223         if (StringUtils.isBlank(name)) {
224             throw new IllegalArgumentException(name + " is not a valid group name.");
225         }
226 
227         Group group = Security.getGroupManager().getGroup(name);
228 
229         if (group != null) {
230             throw new IllegalArgumentException("Group with name " + name + " already exists.");
231         }
232     }
233 
234     @Override
235     public Collection<String> getGroupsWithGroup(final String groupName) {
236         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
237 
238             @Override
239             public Collection<String> doExec(Session session) throws RepositoryException {
240                 final Node groupNode = findPrincipalNode(groupName, session);
241                 return findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(session, groupNode, GROUPS_NODE_NAME);
242             }
243 
244             @Override
245             public String toString() {
246                 return "get group " + groupName;
247             }
248         });
249     }
250 
251 
252     @Override
253     public Collection<String> getGroupsWithRole(final String roleName) {
254         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
255 
256             @Override
257             public Collection<String> doExec(Session session) throws RepositoryException {
258                 final Node principalNode = findPrincipalNode(roleName, MgnlContext.getJCRSession(RepositoryConstants.USER_ROLES), NodeTypes.Role.NAME);
259                 return findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(session, principalNode, ROLES_NODE_NAME);
260             }
261 
262             @Override
263             public String toString() {
264                 return "get group with role " + roleName;
265             }
266         });
267     }
268 
269     @Override
270     public Group removeGroup(Group group, String groupName) throws AccessDeniedException {
271         try {
272             super.remove(group.getName(), groupName, NODE_GROUPS);
273         } catch (PrincipalNotFoundException e) {
274             throw new IllegalArgumentException("group doesn't exist in this GM" + e);
275         }
276         return getGroup(group.getName());
277     }
278 
279     @Override
280     public Group removeRole(Group group, String roleName) throws AccessDeniedException {
281         try {
282             super.remove(group.getName(), roleName, NODE_ROLES);
283         } catch (PrincipalNotFoundException e) {
284             throw new IllegalArgumentException("role doesn't exist in this GM" + e);
285         }
286         return getGroup(group.getName());
287     }
288 
289     /**
290      * Returns all super-groups of the given group, i.e. both directly assigned and transitively assigned super-groups.
291      * <p>Given the following groups:</p>
292      * <pre>
293      * /
294      * ├── people
295      * │   └──employees ("employees" is directly assigned to the "people" super-group)
296      * │      └──developers ("developers" is directly assigned to the "employees" super-group; and is thus transitively assigned to the "people" super-group)
297      *
298      * #getAllSuperGroups("people")  = []
299      * #getAllSuperGroups("employees")  = ["people"]
300      * #getAllSuperGroups("developers")  = ["employees","people"]
301      * </pre>
302      */
303     public Collection<String> getAllSuperGroups(final String groupName) throws UnsupportedOperationException {
304         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
305 
306             List<String> groups;
307 
308             @Override
309             public Collection<String> doExec(Session session) throws RepositoryException {
310                 Group group = getGroup(groupName);
311                 if (group == null) {
312                     return null;
313                 }
314                 groups = new ArrayList<>();
315                 collectSuperGroups(group);
316 
317                 return groups;
318             }
319 
320             /**
321              * Recursively collect all super-groups.
322              */
323             private void collectSuperGroups(Group group) throws AccessDeniedException {
324                 for (String superGroupName : group.getGroups()) {
325                     Group superGroup = getGroup(superGroupName);
326                     if (superGroup != null && !groups.contains(superGroup.getName())) {
327                         groups.add(superGroup.getName());
328                         collectSuperGroups(superGroup);
329                     }
330                 }
331             }
332 
333             @Override
334             public String toString() {
335                 return "get all super groups";
336             }
337         });
338     }
339 
340     /**
341      * Returns all sub-groups of the given group.
342      * <p>Given the following groups:</p>
343      * <pre>
344      * /
345      * ├── people
346      * │   └──employees ("employees" is directly assigned to the "people" super-group)
347      * │      └──developers ("developers" is directly assigned to the "employees" super-group; and is thus transitively assigned to the "people" super-group)
348      *
349      * #getAllSubGroups("people")  = ["developers","developers"]
350      * #getAllSubGroups("employees")  = ["developers"]
351      * #getAllSubGroups("developers")  = []
352      * </pre>
353      */
354     public Collection<String> getAllSubGroups(final String groupName) throws UnsupportedOperationException {
355 
356         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
357 
358             List<String> allSubGroupNames;
359 
360             @Override
361             public Collection<String> doExec(Session session) throws RepositoryException {
362                 allSubGroupNames = new ArrayList<>();
363                 collectSubGroups(groupName);
364                 return allSubGroupNames;
365             }
366 
367             /**
368              * Recursively collect all sub-groups.
369              */
370             private void collectSubGroups(String groupName) throws AccessDeniedException {
371                 Collection<String> allSubGroups = getGroupsWithGroup(groupName);
372                 for (String subGroup : allSubGroups) {
373                     // Check if this group was not already added to prevent infinite loops
374                     if (!allSubGroupNames.contains(subGroup)) {
375                         allSubGroupNames.add(subGroup);
376                         collectSubGroups(subGroup);
377                     }
378                 }
379             }
380 
381             @Override
382             public String toString() {
383                 return "get all sub groups";
384             }
385         });
386     }
387 }