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