View Javadoc

1   /**
2    * This file Copyright (c) 2011 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.context.MgnlContext.VoidOp;
44  import info.magnolia.repository.RepositoryConstants;
45  
46  import java.util.ArrayList;
47  import java.util.Collection;
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                         }
111                     }
112                     return list;
113                 }
114             });
115 
116 
117             // check if any of the assigned IDs match the requested name
118             return MgnlContext.doInSystemContext(new JCRSessionOp<Boolean>(sessionName) {
119 
120                 @Override
121                 public Boolean exec(Session session) throws RepositoryException {
122                     for (String groupOrRole : groupsOrRoles) {
123                         // check for the existence of this ID
124                         try {
125                             if (session.getNodeByIdentifier(groupOrRole).getName().equalsIgnoreCase(resourceName)) {
126                                 return true;
127                             }
128                         } catch (ItemNotFoundException e) {
129                             log.debug("Role [{}] does not exist in the ROLES repository", resourceName);
130                         }
131                     }
132                     return false;
133                 }});
134 
135         } catch (RepositoryException e) {
136             // Item not found or access denied ...
137             log.debug(e.getMessage(), e);
138         } finally {
139             log.debug("checked {} for {} in {}ms.", new Object[] {resourceName, resourceTypeName, (System.currentTimeMillis() - start)});
140         }
141         return false;
142     }
143 
144     /**
145      * Adds link to a resource (group or role) to the principal (user or group).
146      * This call is lenient and will not throw exception in case principal doesn't exist! Instead it will simply return without making any change.
147      * @param principalName name of the user or group to be updated
148      * @param resourceName name of the group or role to be added
149      * @param resourceTypeName type of the added resource (group or role) {@link #NODE_ROLES}, {@link #NODE_GROUPS}
150      * @throws PrincipalNotFoundException
151      */
152     protected void add(final String principalName, final String resourceName, final String resourceTypeName) throws PrincipalNotFoundException {
153         try {
154             final String nodeID = getLinkedResourceId(resourceName, resourceTypeName);
155 
156             if (!hasAny(principalName, resourceName, resourceTypeName)) {
157                 Session session = MgnlContext.getJCRSession(getRepositoryName());
158                 Node principalNode = findPrincipalNode(principalName, session);
159                 if (principalNode == null) {
160                     throw new PrincipalNotFoundException("Principal " + principalName + " of type " + resourceTypeName + " was not found.");
161                 }
162                 if (!principalNode.hasNode(resourceTypeName)) {
163                     principalNode.addNode(resourceTypeName, ItemType.CONTENTNODE.getSystemName());
164                 }
165                 Node node = principalNode.getNode(resourceTypeName);
166                 // add corresponding ID
167                 // used only to get the unique label
168                 String newName = Path.getUniqueLabel(session, node.getPath(), "0");
169                 node.setProperty(newName, nodeID);
170                 session.save();
171             }
172         }
173         catch (RepositoryException e) {
174             log.error("failed to add " + resourceTypeName + " "+ resourceName + " to  [" + principalName + "]", e);
175         }
176     }
177 
178     private String getLinkedResourceId(final String resourceName, final String resourceTypeName) throws AccessDeniedException {
179         final String nodeID;
180         if (StringUtils.equalsIgnoreCase(resourceTypeName, NODE_ROLES)) {
181             Role role = SecuritySupport.Factory.getInstance().getRoleManager().getRole(resourceName);
182             if (role == null) {
183                 log.warn("Invalid role requested: {}", resourceName);
184                 nodeID = null;
185             }
186             else {
187                 nodeID = role.getId();
188             }
189         }
190         else {
191             nodeID = SecuritySupport.Factory.getInstance().getGroupManager().getGroup(resourceName).getId();
192         }
193         return nodeID;
194     }
195 
196     protected String getResourceName(final String resourceId) {
197         try {
198             return MgnlContext.getJCRSession(getRepositoryName()).getNodeByIdentifier(resourceId).getName();
199         } catch (RepositoryException e) {
200             log.error(e.getMessage(), e);
201         }
202         return null;
203     }
204 
205     protected void remove(final String principalName, final String resourceName, final String resourceTypeName) {
206         // FIXME: need same thing as add() method
207         try {
208             final String nodeID = getLinkedResourceId(resourceName, resourceTypeName);
209 
210             if (hasAny(principalName, resourceName, resourceTypeName)) {
211                 MgnlContext.doInSystemContext(new SilentSessionOp<VoidOp>(getRepositoryName()) {
212 
213                     @Override
214                     public VoidOp doExec(Session session) throws RepositoryException {
215                         Node principalNode = findPrincipalNode(principalName, session);
216                         if (!principalNode.hasNode(resourceTypeName)) {
217                             log.debug("resource type {} is not set for principal {}", resourceTypeName, principalName);
218                             return null;
219                         }
220                         Node node = principalNode.getNode(resourceTypeName);
221                         for (PropertyIterator iter = node.getProperties(); iter.hasNext();) {
222                             Property nodeData = iter.nextProperty();
223                             // check for the existence of this ID
224                             try {
225                                 if (nodeData.getString().equals(nodeID)) {
226                                     nodeData.remove();
227                                     // do not break here ... if resource was ever added multiple times remove all occurrences
228                                 }
229                             } catch (IllegalArgumentException e) {
230                                 log.debug("{} has invalid value", nodeData.getPath());
231                             }
232                         }
233                         return null;
234                     }});
235             }
236         }
237         catch (RepositoryException e) {
238             log.error("failed to remove " + resourceTypeName + " "+ resourceName + " from [" + principalName + "]", e);
239         }
240     }
241 
242     protected abstract String getRepositoryName();
243 
244     protected abstract Node findPrincipalNode(String principalName, Session session) throws RepositoryException;
245 
246     public Map<String, ACL> getACLs(final String principalName) {
247         return MgnlContext.doInSystemContext(new SilentSessionOp<Map<String,ACL>>(getRepositoryName()) {
248             @Override
249             public Map<String, ACL> doExec(Session session) throws Throwable {
250                 Node node = findPrincipalNode(principalName, session);
251                 return getACLs(node);
252             }});
253     }
254 
255     protected Map<String, ACL> getACLs(Node node) throws RepositoryException, ValueFormatException, PathNotFoundException {
256         Map<String, ACL> principalList = new HashMap<String, ACL>();
257         NodeIterator it = new FilteringNodeIterator(node.getNodes(), new NodeTypePredicate(ItemType.CONTENTNODE.getSystemName(), true));
258         while (it.hasNext()) {
259             Node aclEntry = it.nextNode();
260             if (!aclEntry.getName().startsWith("acl")) {
261                 continue;
262             }
263             String name = StringUtils.substringAfter(aclEntry.getName(), "acl_");
264 
265             List<Permission> permissionList = new ArrayList<Permission>();
266             // add acl
267             NodeIterator permissionIterator = new FilteringNodeIterator(aclEntry.getNodes(), new NodeTypePredicate(ItemType.CONTENTNODE.getSystemName(), true));
268             while (permissionIterator.hasNext()) {
269                 Node map = permissionIterator.nextNode();
270                 String path = map.getProperty("path").getString();
271                 UrlPattern p = new SimpleUrlPattern(path);
272                 Permission permission = new PermissionImpl();
273                 permission.setPattern(p);
274                 permission.setPermissions(map.getProperty("permissions").getLong());
275                 permissionList.add(permission);
276             }
277 
278             ACL acl;
279             // get the existing acl object if created before with some
280             // other role
281             if (principalList.containsKey(name)) {
282                 acl = principalList.get(name);
283                 permissionList.addAll(acl.getList());
284             }
285             acl = new ACLImpl(name, permissionList);
286             principalList.put(name, acl);
287 
288         }
289         return principalList;
290     }
291 
292 }