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