1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.Path;
38 import info.magnolia.cms.security.auth.ACL;
39 import info.magnolia.cms.util.SimpleUrlPattern;
40 import info.magnolia.cms.util.UrlPattern;
41 import info.magnolia.context.MgnlContext;
42 import info.magnolia.jcr.util.NodeTypes;
43 import info.magnolia.module.InstallContextImpl;
44 import info.magnolia.module.InstallStatus;
45 import info.magnolia.objectfactory.Components;
46 import info.magnolia.repository.RepositoryConstants;
47
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.HashMap;
52 import java.util.LinkedList;
53 import java.util.List;
54 import java.util.Map;
55
56 import javax.jcr.ItemNotFoundException;
57 import javax.jcr.Node;
58 import javax.jcr.NodeIterator;
59 import javax.jcr.PathNotFoundException;
60 import javax.jcr.Property;
61 import javax.jcr.PropertyIterator;
62 import javax.jcr.RepositoryException;
63 import javax.jcr.Session;
64 import javax.jcr.ValueFormatException;
65 import javax.jcr.query.Query;
66
67 import org.apache.commons.lang3.StringUtils;
68 import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator;
69 import org.apache.jackrabbit.commons.predicate.NodeTypePredicate;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72
73
74
75
76 public abstract class RepositoryBackedSecurityManager {
77
78 private static final Logger log = LoggerFactory.getLogger(RepositoryBackedSecurityManager.class);
79
80
81
82
83 static final String GROUPS_NODE_NAME = "groups";
84
85
86
87
88 static final String ROLES_NODE_NAME = "roles";
89
90
91 public boolean hasAny(final String principalName, final String resourceName, final String resourceTypeName) {
92 long start = System.currentTimeMillis();
93 try {
94 final String sessionName = (StringUtils.equalsIgnoreCase(resourceTypeName, NODE_ROLES)) ? RepositoryConstants.USER_ROLES : RepositoryConstants.USER_GROUPS;
95
96
97
98 final Collection<String> groupsOrRoles = MgnlContext.doInSystemContext(new JCRSessionOp<Collection<String>>(getRepositoryName()) {
99
100 @Override
101 public Collection<String> exec(Session session) throws RepositoryException {
102 List<String> list = new ArrayList<String>();
103 Node principal = findPrincipalNode(principalName, session);
104 if(principal == null) {
105 log.debug("No User '{}' found in repository", principalName);
106 return list;
107 }
108 Node groupsOrRoles = principal.getNode(resourceTypeName);
109
110 for (PropertyIterator props = groupsOrRoles.getProperties(); props.hasNext();) {
111 Property property = props.nextProperty();
112 try {
113
114 list.add(property.getString());
115 } catch (ItemNotFoundException e) {
116 log.debug("Role [{}] does not exist in the {} repository", resourceName, resourceTypeName);
117 } catch (IllegalArgumentException e) {
118 log.debug("{} has invalid value", property.getPath());
119 } catch (ValueFormatException e) {
120 log.debug("{} has invalid value", property.getPath());
121 }
122 }
123 return list;
124 }
125 });
126
127
128
129 return MgnlContext.doInSystemContext(new JCRSessionOp<Boolean>(sessionName) {
130
131 @Override
132 public Boolean exec(Session session) throws RepositoryException {
133 for (String groupOrRole : groupsOrRoles) {
134
135 try {
136 if (session.getNodeByIdentifier(groupOrRole).getName().equalsIgnoreCase(resourceName)) {
137 return true;
138 }
139 } catch (RepositoryException e) {
140 log.debug("Role [{}] does not exist in the ROLES repository", resourceName);
141 }
142 }
143 return false;
144 }});
145
146 } catch (RepositoryException e) {
147
148 log.debug(e.getMessage(), e);
149 } finally {
150 log.debug("checked {} for {} in {}ms.", resourceName, resourceTypeName, (System.currentTimeMillis() - start));
151 }
152 return false;
153 }
154
155
156
157
158
159
160
161
162 protected void add(final String principalName, final String resourceName, final String resourceTypeName) throws PrincipalNotFoundException {
163 try {
164 final String nodeID = getLinkedResourceId(resourceName, resourceTypeName);
165
166 if (!hasAny(principalName, resourceName, resourceTypeName)) {
167 Session session = MgnlContext.getJCRSession(getRepositoryName());
168 Node principalNode = findPrincipalNode(principalName, session);
169 if (principalNode == null) {
170 throw new PrincipalNotFoundException("Principal " + principalName + " of type " + resourceTypeName + " was not found.");
171 }
172 if (!principalNode.hasNode(resourceTypeName)) {
173 principalNode.addNode(resourceTypeName, NodeTypes.ContentNode.NAME);
174 }
175 Node node = principalNode.getNode(resourceTypeName);
176
177
178 String newName = Path.getUniqueLabel(session, node.getPath(), "0");
179 node.setProperty(newName, nodeID);
180 session.save();
181 }
182 }
183 catch (RepositoryException e) {
184 log.error("failed to add {} {} to [{}]", resourceTypeName, resourceName, principalName, e);
185 }
186 }
187
188 private String getLinkedResourceId(final String resourceName, final String resourceTypeName) throws AccessDeniedException {
189 final String nodeID;
190 if (StringUtils.equalsIgnoreCase(resourceTypeName, NODE_ROLES)) {
191 Role role = SecuritySupport.Factory.getInstance().getRoleManager().getRole(resourceName);
192 if (role == null) {
193 log.warn("Invalid role requested: {}", resourceName);
194 nodeID = null;
195 }
196 else {
197 nodeID = role.getId();
198 }
199 }
200 else {
201 Group group = SecuritySupport.Factory.getInstance().getGroupManager().getGroup(resourceName);
202 if (group == null) {
203 log.warn("Invalid group requested: {}", resourceName);
204 nodeID = null;
205 }
206 else {
207 nodeID = group.getId();
208 }
209 }
210 return nodeID;
211 }
212
213 protected String getResourceName(final String resourceId) {
214 try {
215 return MgnlContext.getJCRSession(getRepositoryName()).getNodeByIdentifier(resourceId).getName();
216 } catch (ItemNotFoundException e) {
217
218 return null;
219 }
220 catch (RepositoryException e) {
221 log.error(e.getMessage(), e);
222 }
223 return null;
224 }
225
226
227
228
229
230
231
232
233 protected void remove(final String principalName, final String resourceName, final String resourceTypeName) throws PrincipalNotFoundException {
234 try {
235 final String nodeID = getLinkedResourceId(resourceName, resourceTypeName);
236
237 if (hasAny(principalName, resourceName, resourceTypeName)) {
238 Session session = MgnlContext.getJCRSession(getRepositoryName());
239 Node principalNode = findPrincipalNode(principalName, session);
240 if (!principalNode.hasNode(resourceTypeName)) {
241 throw new PrincipalNotFoundException("Principal " + principalName + " of type " + resourceTypeName + " was not found.");
242 }
243 Node node = principalNode.getNode(resourceTypeName);
244 for (PropertyIterator iter = node.getProperties(); iter.hasNext();) {
245 Property nodeData = iter.nextProperty();
246
247 try {
248 if (nodeData.getString().equals(nodeID)) {
249 nodeData.remove();
250 session.save();
251
252 }
253 } catch (IllegalArgumentException e) {
254 log.debug("{} has invalid value", nodeData.getPath());
255 } catch (ValueFormatException e) {
256 log.debug("{} has invalid value", nodeData.getPath());
257 }
258 }
259 }
260 }
261 catch (RepositoryException e) {
262 log.error("failed to remove {} {} from [{}]", resourceTypeName, resourceName, principalName, e);
263 }
264 }
265
266 protected abstract String getRepositoryName();
267
268 protected abstract Node findPrincipalNode(String principalName, Session session) throws RepositoryException;
269
270 protected Node findPrincipalNode(String principalName, Session session, String primaryNodeType) throws RepositoryException {
271 return findPrincipalNode(principalName, session, primaryNodeType, null);
272 }
273
274
275
276
277
278
279 protected Node findPrincipalNode(String principalName, Session session, String primaryNodeType, Node startNode) throws RepositoryException {
280 final boolean isInstallationPhase = InstallStatus.inProgress.equals(Components.getComponent(InstallContextImpl.class).getStatus());
281 final long start = System.currentTimeMillis();
282
283 final Node principalNode = isInstallationPhase ? findPrincipalNodeByTraversal(principalName, session, primaryNodeType, startNode) : findPrincipalNodeByQuery(principalName, session, primaryNodeType, startNode);
284 log.debug("Retrieving node took {}ms (isInstallationPhase: {}): path = {}", System.currentTimeMillis() - start, isInstallationPhase, principalNode == null ? "<null>" : principalNode.getPath());
285
286 if (principalNode == null) {
287 log.debug("Could not find principal node '{}' of primary type '{}' under startnode '{}' in workspace '{}'.", principalName, primaryNodeType, startNode == null ? "/" : startNode.getPath(), session.getWorkspace().getName());
288 }
289 return principalNode;
290 }
291
292
293
294
295 Node findPrincipalNodeByQuery(String principalName, Session session, String primaryNodeType, Node startNode) throws RepositoryException {
296 final Node root = startNode == null ? session.getRootNode() : startNode;
297
298 final StringBuilder builder = new StringBuilder("select * from [").append(primaryNodeType).append("] where name() = '").append(principalName).append("'");
299
300 if (!"/".equals(root.getPath())) {
301 builder.append(" and isdescendantnode(['").append(root.getPath()).append("'])");
302 }
303
304 final String queryString = builder.toString();
305 log.debug("Executing query \"{}\".", queryString);
306
307 final Query query = session.getWorkspace().getQueryManager().createQuery(queryString, Query.JCR_SQL2);
308 final NodeIterator iterator = query.execute().getNodes();
309 final Node user = iterator.hasNext() ? iterator.nextNode() : null;
310 if (iterator.hasNext()) {
311 log.error("Query found more than one node of type \"{}\" with name \"{}\" under the root path \"{}\".", primaryNodeType, principalName, root.getPath());
312 }
313 return user;
314 }
315
316
317
318
319 Node findPrincipalNodeByTraversal(String principalName, Session session, String primaryNodeType, Node startNode) throws RepositoryException {
320 final Node root = startNode == null ? session.getRootNode() : startNode;
321 log.debug("Traversing to find nodes of type \"{}\" with name \"{}\" under the root path \"{}\".", primaryNodeType, principalName, root.getPath());
322
323 final LinkedList<Node> nodes = new LinkedList<Node>();
324
325 for (NodeIterator iterator = root.getNodes(); iterator.hasNext(); ) {
326 final Node node = iterator.nextNode();
327 if (!node.getName().startsWith(NodeTypes.JCR_PREFIX) && !node.getName().startsWith(NodeTypes.REP_PREFIX)) {
328 nodes.add(node);
329 }
330 }
331
332 Node principalNode = null;
333 while (!nodes.isEmpty()) {
334 final Node node = nodes.removeFirst();
335
336 if (node.getName().equals(principalName) && node.getPrimaryNodeType().getName().equals(primaryNodeType)) {
337 if (principalNode != null) {
338 log.error("Traversal found more than one node of type \"{}\" with name \"{}\" under the root path \"{}\".", primaryNodeType, principalName, root.getPath());
339 break;
340 }
341 principalNode = node;
342 }
343
344 int i = 0;
345 for (NodeIterator iterator = node.getNodes(); iterator.hasNext(); ) {
346 nodes.add(i++, iterator.nextNode());
347 }
348 }
349 return principalNode;
350 }
351
352 public Map<String, ACL> getACLs(final String principalName) {
353 return MgnlContext.doInSystemContext(new SilentSessionOp<Map<String,ACL>>(getRepositoryName()) {
354 @Override
355 public Map<String, ACL> doExec(Session session) throws Throwable {
356 Node node = findPrincipalNode(principalName, session);
357 if(node == null){
358 return Collections.emptyMap();
359 }
360 return getACLs(node);
361 }});
362 }
363
364 protected Map<String, ACL> getACLs(Node node) throws RepositoryException, ValueFormatException, PathNotFoundException {
365 Map<String, ACL> principalList = new HashMap<String, ACL>();
366 NodeIterator it = new FilteringNodeIterator(node.getNodes(), new NodeTypePredicate(NodeTypes.ContentNode.NAME, true));
367 while (it.hasNext()) {
368 Node aclEntry = it.nextNode();
369 if (!aclEntry.getName().startsWith("acl")) {
370 continue;
371 }
372 String name = StringUtils.substringAfter(aclEntry.getName(), "acl_");
373
374 List<Permission> permissionList = new ArrayList<Permission>();
375
376 NodeIterator permissionIterator = new FilteringNodeIterator(aclEntry.getNodes(), new NodeTypePredicate(NodeTypes.ContentNode.NAME, true));
377 while (permissionIterator.hasNext()) {
378 Node map = permissionIterator.nextNode();
379 String path = map.getProperty("path").getString();
380 UrlPattern p = new SimpleUrlPattern(path);
381 Permission permission = new PermissionImpl();
382 permission.setPattern(p);
383 permission.setPermissions(map.getProperty("permissions").getLong());
384 permissionList.add(permission);
385 }
386
387 ACL acl;
388
389
390 if (principalList.containsKey(name)) {
391 acl = principalList.get(name);
392 permissionList.addAll(acl.getList());
393 }
394 acl = new ACLImpl(name, permissionList);
395 principalList.put(name, acl);
396
397 }
398 return principalList;
399 }
400
401
402
403
404
405 protected NodeIterator findPrincipalNodes(final Node node, final String nodeType) throws RepositoryException {
406 final StringBuilder builder = new StringBuilder("select * from [").append(nodeType).append("]");
407 if (!"/".equals(node.getPath())) {
408 builder.append(" where isdescendantnode(['").append(node.getPath()).append("'])");
409 }
410 final String queryString = builder.toString();
411 log.debug("Executing query \"{}\".", queryString);
412
413 final Query query = node.getSession().getWorkspace().getQueryManager().createQuery(queryString, Query.JCR_SQL2);
414 return query.execute().getNodes();
415 }
416
417 protected Collection<String> findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(final Session session, final Node groupOrRoleNode, final String parentNodeName) throws RepositoryException {
418 if (groupOrRoleNode == null) {
419 return Collections.EMPTY_LIST;
420 }
421
422 final StringBuilder builder = new StringBuilder("select * from [").append(NodeTypes.ContentNode.NAME).append("] as s");
423 builder.append(" where contains(s.*, '").append(groupOrRoleNode.getIdentifier()).append("')");
424 final String queryString = builder.toString();
425 log.debug("Executing query \"{}\".", queryString);
426
427 final Query query = session.getWorkspace().getQueryManager().createQuery(queryString, Query.JCR_SQL2);
428 final NodeIterator nodeIterator = query.execute().getNodes();
429
430 final Collection<String> matches = new ArrayList<String>();
431 Node current;
432 while (nodeIterator.hasNext()) {
433 current = nodeIterator.nextNode();
434 if (parentNodeName.equals(current.getName())) {
435 matches.add(current.getParent().getName());
436 }
437 }
438 return matches;
439 }
440
441 }