View Javadoc
1   /**
2    * This file Copyright (c) 2011-2014 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_ROLES;
37  import info.magnolia.cms.core.ItemType;
38  import info.magnolia.cms.core.Path;
39  import info.magnolia.cms.security.auth.ACL;
40  import info.magnolia.cms.util.SimpleUrlPattern;
41  import info.magnolia.cms.util.UrlPattern;
42  import info.magnolia.context.MgnlContext;
43  import info.magnolia.repository.RepositoryConstants;
44  
45  import java.util.ArrayList;
46  import java.util.Collection;
47  import java.util.Collections;
48  import java.util.HashMap;
49  import java.util.List;
50  import java.util.Map;
51  
52  import javax.jcr.ItemNotFoundException;
53  import javax.jcr.Node;
54  import javax.jcr.NodeIterator;
55  import javax.jcr.PathNotFoundException;
56  import javax.jcr.Property;
57  import javax.jcr.PropertyIterator;
58  import javax.jcr.RepositoryException;
59  import javax.jcr.Session;
60  import javax.jcr.ValueFormatException;
61  
62  import org.apache.commons.lang.StringUtils;
63  import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator;
64  import org.apache.jackrabbit.commons.predicate.NodeTypePredicate;
65  import org.slf4j.Logger;
66  import org.slf4j.LoggerFactory;
67  
68  /**
69   * Common parent class for repo based security managers.
70   * @author had
71   * @version $Id: $
72   */
73  public abstract class RepositoryBackedSecurityManager {
74  
75      private static final Logger log = LoggerFactory.getLogger(RepositoryBackedSecurityManager.class);
76  
77      public boolean hasAny(final String principalName, final String resourceName, final String resourceTypeName) {
78          long start = System.currentTimeMillis();
79          try {
80              String sessionName;
81              if (StringUtils.equalsIgnoreCase(resourceTypeName, NODE_ROLES)) {
82                  sessionName = RepositoryConstants.USER_ROLES;
83              } else {
84                  sessionName = RepositoryConstants.USER_GROUPS;
85              }
86  
87              // this is an original code from old ***Managers.
88              // TODO: If you ever need to speed it up, turn it around - retrieve group or role by its name and read its ID, then loop through IDs this user has assigned to find out if he has that one or not.
89              final Collection<String> groupsOrRoles = MgnlContext.doInSystemContext(new JCRSessionOp<Collection<String>>(getRepositoryName()) {
90  
91                  @Override
92                  public Collection<String> exec(Session session) throws RepositoryException {
93                      List<String> list = new ArrayList<String>();
94                      Node principal = findPrincipalNode(principalName, session);
95                      if(principal == null) {
96                          log.debug("No User '"+principalName+"' found in repository");
97                          return list;
98                      }
99                      Node groupsOrRoles = principal.getNode(resourceTypeName);
100 
101                     for (PropertyIterator props = groupsOrRoles.getProperties(); props.hasNext();) {
102                         Property property = props.nextProperty();
103                         try {
104                             // just get all the IDs of given type assigned to the principal
105                             list.add(property.getString());
106                         } catch (ItemNotFoundException e) {
107                             log.debug("Role [{}] does not exist in the {} repository", resourceName, resourceTypeName);
108                         } catch (IllegalArgumentException e) {
109                             log.debug("{} has invalid value", property.getPath());
110                         } catch (ValueFormatException e) {
111                             log.debug("{} has invalid value", property.getPath());
112                         }
113                     }
114                     return list;
115                 }
116             });
117 
118 
119             // check if any of the assigned IDs match the requested name
120             return MgnlContext.doInSystemContext(new JCRSessionOp<Boolean>(sessionName) {
121 
122                 @Override
123                 public Boolean exec(Session session) throws RepositoryException {
124                     for (String groupOrRole : groupsOrRoles) {
125                         // check for the existence of this ID
126                         try {
127                             if (session.getNodeByIdentifier(groupOrRole).getName().equalsIgnoreCase(resourceName)) {
128                                 return true;
129                             }
130                         } catch (RepositoryException e ) {
131                             log.debug("Role [{}] does not exist in the ROLES repository", resourceName);
132                         }
133                     }
134                     return false;
135                 }});
136 
137         } catch (RepositoryException e) {
138             // Item not found or access denied ...
139             log.debug(e.getMessage(), e);
140         } finally {
141             log.debug("checked {} for {} in {}ms.", new Object[] {resourceName, resourceTypeName, (System.currentTimeMillis() - start)});
142         }
143         return false;
144     }
145 
146     /**
147      * Adds link to a resource (group or role) to the principal (user or group).
148      * This call is lenient and will not throw exception in case principal doesn't exist! Instead it will simply return without making any change.
149      * @param principalName name of the user or group to be updated
150      * @param resourceName name of the group or role to be added
151      * @param resourceTypeName type of the added resource (group or role) {@link #NODE_ROLES}, {@link #NODE_GROUPS}
152      * @throws PrincipalNotFoundException
153      */
154     protected void add(final String principalName, final String resourceName, final String resourceTypeName) throws PrincipalNotFoundException {
155         try {
156             final String nodeID = getLinkedResourceId(resourceName, resourceTypeName);
157 
158             if (!hasAny(principalName, resourceName, resourceTypeName)) {
159                 Session session = MgnlContext.getJCRSession(getRepositoryName());
160                 Node principalNode = findPrincipalNode(principalName, session);
161                 if (principalNode == null) {
162                     throw new PrincipalNotFoundException("Principal " + principalName + " of type " + resourceTypeName + " was not found.");
163                 }
164                 if (!principalNode.hasNode(resourceTypeName)) {
165                     principalNode.addNode(resourceTypeName, ItemType.CONTENTNODE.getSystemName());
166                 }
167                 Node node = principalNode.getNode(resourceTypeName);
168                 // add corresponding ID
169                 // used only to get the unique label
170                 String newName = Path.getUniqueLabel(session, node.getPath(), "0");
171                 node.setProperty(newName, nodeID);
172                 session.save();
173             }
174         }
175         catch (RepositoryException e) {
176             log.error("failed to add " + resourceTypeName + " "+ resourceName + " to  [" + principalName + "]", e);
177         }
178     }
179 
180     private String getLinkedResourceId(final String resourceName, final String resourceTypeName) throws AccessDeniedException {
181         final String nodeID;
182         if (StringUtils.equalsIgnoreCase(resourceTypeName, NODE_ROLES)) {
183             Role role = SecuritySupport.Factory.getInstance().getRoleManager().getRole(resourceName);
184             if (role == null) {
185                 log.warn("Invalid role requested: {}", resourceName);
186                 nodeID = null;
187             }
188             else {
189                 nodeID = role.getId();
190             }
191         }
192         else {
193             Group group = SecuritySupport.Factory.getInstance().getGroupManager().getGroup(resourceName);
194             if (group == null) {
195                 log.warn("Invalid group requested: {}", resourceName);
196                 nodeID = null;
197             }
198             else {
199                 nodeID = group.getId();
200             }
201         }
202         return nodeID;
203     }
204 
205     protected String getResourceName(final String resourceId) {
206         try {
207             return MgnlContext.getJCRSession(getRepositoryName()).getNodeByIdentifier(resourceId).getName();
208         } catch (ItemNotFoundException e) {
209             // referenced node doesn't exist
210             return null;
211         }
212         catch (RepositoryException e) {
213             log.error(e.getMessage(), e);
214         }
215         return null;
216     }
217 
218     /**
219      * This call is lenient and will not throw exception in case principal doesn't exist! Instead it will simply return without making any change.
220      * 
221      * @param principalName
222      *            name of the user or group to be updated
223      * @param resourceName
224      *            name of the group or role to be added
225      * @param resourceTypeName
226      *            type of the added resource (group or role) {@link #NODE_ROLES}, {@link #NODE_GROUPS}
227      * @throws PrincipalNotFoundException
228      */
229     protected void remove(final String principalName, final String resourceName, final String resourceTypeName) throws PrincipalNotFoundException {
230         try {
231             final String nodeID = getLinkedResourceId(resourceName, resourceTypeName);
232 
233             if (hasAny(principalName, resourceName, resourceTypeName)) {
234                 Session session = MgnlContext.getJCRSession(getRepositoryName());
235                 Node principalNode = findPrincipalNode(principalName, session);
236                 if (!principalNode.hasNode(resourceTypeName)) {
237                     throw new PrincipalNotFoundException("Principal " + principalName + " of type " + resourceTypeName + " was not found.");
238                 }
239                 Node node = principalNode.getNode(resourceTypeName);
240                 for (PropertyIterator iter = node.getProperties(); iter.hasNext();) {
241                     Property nodeData = iter.nextProperty();
242                     // check for the existence of this ID
243                     try {
244                         if (nodeData.getString().equals(nodeID)) {
245                             nodeData.remove();
246                             session.save();
247                             // do not break here ... if resource was ever added multiple times remove all occurrences
248                         }
249                     } catch (IllegalArgumentException e) {
250                         log.debug("{} has invalid value", nodeData.getPath());
251                     } catch (ValueFormatException e) {
252                         log.debug("{} has invalid value", nodeData.getPath());
253                     }
254                 }
255             }
256         }
257         catch (RepositoryException e) {
258             log.error("failed to remove " + resourceTypeName + " "+ resourceName + " from [" + principalName + "]", e);
259         }
260     }
261 
262     protected abstract String getRepositoryName();
263 
264     protected abstract Node findPrincipalNode(String principalName, Session session) throws RepositoryException;
265 
266     public Map<String, ACL> getACLs(final String principalName) {
267         return MgnlContext.doInSystemContext(new SilentSessionOp<Map<String,ACL>>(getRepositoryName()) {
268             @Override
269             public Map<String, ACL> doExec(Session session) throws Throwable {
270                 Node node = findPrincipalNode(principalName, session);
271                 if(node == null){
272                     return Collections.emptyMap();
273                 }
274                 return getACLs(node);
275             }});
276     }
277 
278     protected Map<String, ACL> getACLs(Node node) throws RepositoryException, ValueFormatException, PathNotFoundException {
279         Map<String, ACL> principalList = new HashMap<String, ACL>();
280         NodeIterator it = new FilteringNodeIterator(node.getNodes(), new NodeTypePredicate(ItemType.CONTENTNODE.getSystemName(), true));
281         while (it.hasNext()) {
282             Node aclEntry = it.nextNode();
283             if (!aclEntry.getName().startsWith("acl")) {
284                 continue;
285             }
286             String name = StringUtils.substringAfter(aclEntry.getName(), "acl_");
287 
288             List<Permission> permissionList = new ArrayList<Permission>();
289             // add acl
290             NodeIterator permissionIterator = new FilteringNodeIterator(aclEntry.getNodes(), new NodeTypePredicate(ItemType.CONTENTNODE.getSystemName(), true));
291             while (permissionIterator.hasNext()) {
292                 Node map = permissionIterator.nextNode();
293                 String path = map.getProperty("path").getString();
294                 UrlPattern p = new SimpleUrlPattern(path);
295                 Permission permission = new PermissionImpl();
296                 permission.setPattern(p);
297                 permission.setPermissions(map.getProperty("permissions").getLong());
298                 permissionList.add(permission);
299             }
300 
301             ACL acl;
302             // get the existing acl object if created before with some
303             // other role
304             if (principalList.containsKey(name)) {
305                 acl = principalList.get(name);
306                 permissionList.addAll(acl.getList());
307             }
308             acl = new ACLImpl(name, permissionList);
309             principalList.put(name, acl);
310 
311         }
312         return principalList;
313     }
314 
315 }