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