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 getDirectSubGroups(groupName);
237     }
238 
239     @Override
240     public Collection<String> getDirectSubGroups(String groupName) {
241         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
242 
243             @Override
244             public Collection<String> doExec(Session session) throws RepositoryException {
245                 final Node groupNode = findPrincipalNode(groupName, session);
246                 return findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(session, groupNode, GROUPS_NODE_NAME);
247             }
248 
249             @Override
250             public String toString() {
251                 return "get direct sub groups " + groupName;
252             }
253         });
254     }
255 
256     @Override
257     public Collection<String> getDirectSuperGroups(String groupName) {
258         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
259 
260             @Override
261             public Collection<String> doExec(Session session) throws RepositoryException {
262                 Group group = getGroup(groupName);
263                 return group != null ? group.getGroups() : new ArrayList<>();
264             }
265 
266             @Override
267             public String toString() {
268                 return "get direct super groups " + groupName;
269             }
270         });
271     }
272 
273     @Override
274     public Collection<String> getGroupsWithRole(final String roleName) {
275         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
276 
277             @Override
278             public Collection<String> doExec(Session session) throws RepositoryException {
279                 final Node principalNode = findPrincipalNode(roleName, MgnlContext.getJCRSession(RepositoryConstants.USER_ROLES), NodeTypes.Role.NAME);
280                 return findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(session, principalNode, ROLES_NODE_NAME);
281             }
282 
283             @Override
284             public String toString() {
285                 return "get group with role " + roleName;
286             }
287         });
288     }
289 
290     @Override
291     public Group removeGroup(Group group, String groupName) throws AccessDeniedException {
292         try {
293             super.remove(group.getName(), groupName, NODE_GROUPS);
294         } catch (PrincipalNotFoundException e) {
295             throw new IllegalArgumentException("group doesn't exist in this GM" + e);
296         }
297         return getGroup(group.getName());
298     }
299 
300     @Override
301     public Group removeRole(Group group, String roleName) throws AccessDeniedException {
302         try {
303             super.remove(group.getName(), roleName, NODE_ROLES);
304         } catch (PrincipalNotFoundException e) {
305             throw new IllegalArgumentException("role doesn't exist in this GM" + e);
306         }
307         return getGroup(group.getName());
308     }
309 
310     @Override
311     public Collection<String> getAllSuperGroups(final String groupName) throws UnsupportedOperationException {
312         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
313 
314             List<String> groups;
315 
316             @Override
317             public Collection<String> doExec(Session session) throws RepositoryException {
318                 Group group = getGroup(groupName);
319                 if (group == null) {
320                     return null;
321                 }
322                 groups = new ArrayList<>();
323                 collectSuperGroups(group);
324 
325                 return groups;
326             }
327 
328             /**
329              * Recursively collect all super-groups.
330              */
331             private void collectSuperGroups(Group group) throws AccessDeniedException {
332                 for (String superGroupName : group.getGroups()) {
333                     Group superGroup = getGroup(superGroupName);
334                     if (superGroup != null && !groups.contains(superGroup.getName())) {
335                         groups.add(superGroup.getName());
336                         collectSuperGroups(superGroup);
337                     }
338                 }
339             }
340 
341             @Override
342             public String toString() {
343                 return "get all super groups";
344             }
345         });
346     }
347 
348     @Override
349     public Collection<String> getAllSubGroups(final String groupName) throws UnsupportedOperationException {
350 
351         return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<String>>(getRepositoryName()) {
352 
353             List<String> allSubGroupNames;
354 
355             @Override
356             public Collection<String> doExec(Session session) throws RepositoryException {
357                 allSubGroupNames = new ArrayList<>();
358                 collectSubGroups(groupName);
359                 return allSubGroupNames;
360             }
361 
362             /**
363              * Recursively collect all sub-groups.
364              */
365             private void collectSubGroups(String groupName) throws AccessDeniedException {
366                 Collection<String> allSubGroups = getDirectSubGroups(groupName);
367                 for (String subGroup : allSubGroups) {
368                     // Check if this group was not already added to prevent infinite loops
369                     if (!allSubGroupNames.contains(subGroup)) {
370                         allSubGroupNames.add(subGroup);
371                         collectSubGroups(subGroup);
372                     }
373                 }
374             }
375 
376             @Override
377             public String toString() {
378                 return "get all sub groups";
379             }
380         });
381     }
382 }