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 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.jcr.ItemNotFoundException;
59 import javax.jcr.Node;
60 import javax.jcr.NodeIterator;
61 import javax.jcr.PathNotFoundException;
62 import javax.jcr.Property;
63 import javax.jcr.PropertyIterator;
64 import javax.jcr.RepositoryException;
65 import javax.jcr.Session;
66 import javax.jcr.ValueFormatException;
67 import javax.jcr.query.InvalidQueryException;
68 import javax.jcr.query.Query;
69
70 import org.apache.commons.lang3.StringUtils;
71 import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator;
72 import org.apache.jackrabbit.commons.predicate.NodeTypePredicate;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
75
76
77
78
79 public abstract class RepositoryBackedSecurityManager {
80
81 private static final Logger log = LoggerFactory.getLogger(RepositoryBackedSecurityManager.class);
82
83
84
85
86 static final String GROUPS_NODE_NAME = "groups";
87
88
89
90
91 static final String ROLES_NODE_NAME = "roles";
92
93
94 public boolean hasAny(final String principalName, final String resourceName, final String resourceTypeName) {
95 long start = System.currentTimeMillis();
96 try {
97 final String sessionName = (StringUtils.equalsIgnoreCase(resourceTypeName, NODE_ROLES)) ? RepositoryConstants.USER_ROLES : RepositoryConstants.USER_GROUPS;
98
99
100
101 final Collection<String> groupsOrRoles = MgnlContext.doInSystemContext(new JCRSessionOp<Collection<String>>(getRepositoryName()) {
102
103 @Override
104 public Collection<String> exec(Session session) throws RepositoryException {
105 List<String> list = new ArrayList<>();
106 Node principal = findPrincipalNode(principalName, session);
107 if (principal == null) {
108 log.debug("No User '{}' found in repository", principalName);
109 return list;
110 }
111 Node groupsOrRoles = principal.getNode(resourceTypeName);
112
113 for (PropertyIterator props = groupsOrRoles.getProperties(); props.hasNext(); ) {
114 Property property = props.nextProperty();
115 try {
116
117 list.add(property.getString());
118 } catch (ItemNotFoundException e) {
119 log.debug("Role [{}] does not exist in the {} repository", resourceName, resourceTypeName);
120 } catch (IllegalArgumentException | 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 == null || !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 | ValueFormatException e) {
252 log.debug("{} has invalid value", nodeData.getPath());
253 }
254 }
255 }
256 } catch (RepositoryException e) {
257 log.error("failed to remove {} {} from [{}]", resourceTypeName, resourceName, principalName, e);
258 }
259 }
260
261 protected abstract String getRepositoryName();
262
263 protected abstract Node findPrincipalNode(String principalName, Session session) throws RepositoryException;
264
265 protected Node findPrincipalNode(String principalName, Session session, String primaryNodeType) throws RepositoryException {
266 return findPrincipalNode(principalName, session, primaryNodeType, null);
267 }
268
269
270
271
272
273
274 protected Node findPrincipalNode(String principalName, Session session, String primaryNodeType, Node startNode) throws RepositoryException {
275 final boolean isInstallationPhase = InstallStatus.inProgress.equals(Components.getComponent(InstallContextImpl.class).getStatus());
276 final long start = System.currentTimeMillis();
277
278 final Node principalNode = isInstallationPhase ? findPrincipalNodeByTraversal(principalName, session, primaryNodeType, startNode) : findPrincipalNodeByQuery(principalName, session, primaryNodeType, startNode);
279 log.debug("Retrieving node took {}ms (isInstallationPhase: {}): path = {}", System.currentTimeMillis() - start, isInstallationPhase, principalNode == null ? "<null>" : principalNode.getPath());
280
281 if (principalNode == null) {
282 log.debug("Could not find principal node '{}' of primary type '{}' under startnode '{}' in workspace '{}'.", principalName, primaryNodeType, startNode == null ? "/" : startNode.getPath(), session.getWorkspace().getName());
283 }
284 return principalNode;
285 }
286
287
288
289
290 Node findPrincipalNodeByQuery(String principalName, Session session, String primaryNodeType, Node startNode) throws RepositoryException {
291 final Node root = startNode == null ? session.getRootNode() : startNode;
292
293 final String escapedPrincipalName = EscapeUtil.escapeSql(principalName);
294
295 final StringBuilder builder = new StringBuilder("select * from [").append(primaryNodeType).append("] where name() = '").append(escapedPrincipalName).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 try {
305 final Query query = session.getWorkspace().getQueryManager().createQuery(queryString, Query.JCR_SQL2);
306 final NodeIterator iterator = query.execute().getNodes();
307 final Node user = iterator.hasNext() ? iterator.nextNode() : null;
308 if (iterator.hasNext()) {
309 log.error("Query found more than one node of type \"{}\" with name \"{}\" under the root path \"{}\".", primaryNodeType, principalName, root.getPath());
310 }
311 return user;
312 } catch (InvalidQueryException e) {
313
314 log.debug("Invalid query when searching for group [{}]. Query: {}", principalName, queryString);
315 }
316 return null;
317 }
318
319
320
321
322 Node findPrincipalNodeByTraversal(String principalName, Session session, String primaryNodeType, Node startNode) throws RepositoryException {
323 final Node root = startNode == null ? session.getRootNode() : startNode;
324 log.debug("Traversing to find nodes of type \"{}\" with name \"{}\" under the root path \"{}\".", primaryNodeType, principalName, root.getPath());
325
326 final LinkedList<Node> nodes = new LinkedList<>();
327
328 for (NodeIterator iterator = root.getNodes(); iterator.hasNext(); ) {
329 final Node node = iterator.nextNode();
330 if (!node.getName().startsWith(NodeTypes.JCR_PREFIX) && !node.getName().startsWith(NodeTypes.REP_PREFIX)) {
331 nodes.add(node);
332 }
333 }
334
335 Node principalNode = null;
336 while (!nodes.isEmpty()) {
337 final Node node = nodes.removeFirst();
338
339 if (node.getName().equals(principalName) && node.getPrimaryNodeType().getName().equals(primaryNodeType)) {
340 if (principalNode != null) {
341 log.error("Traversal found more than one node of type \"{}\" with name \"{}\" under the root path \"{}\".", primaryNodeType, principalName, root.getPath());
342 break;
343 }
344 principalNode = node;
345 }
346
347 int i = 0;
348 for (NodeIterator iterator = node.getNodes(); iterator.hasNext(); ) {
349 nodes.add(i++, iterator.nextNode());
350 }
351 }
352 return principalNode;
353 }
354
355 public Map<String, ACL> getACLs(final String principalName) {
356 return MgnlContext.doInSystemContext(new SilentSessionOp<Map<String, ACL>>(getRepositoryName()) {
357 @Override
358 public Map<String, ACL> doExec(Session session) throws Throwable {
359 Node node = findPrincipalNode(principalName, session);
360 if (node == null) {
361 return Collections.emptyMap();
362 }
363 return getACLs(node);
364 }
365 });
366 }
367
368 protected Map<String, ACL> getACLs(Node node) throws RepositoryException, ValueFormatException, PathNotFoundException {
369 Map<String, ACL> principalList = new HashMap<>();
370 NodeIterator it = new FilteringNodeIterator(node.getNodes(), new NodeTypePredicate(NodeTypes.ContentNode.NAME, true));
371 while (it.hasNext()) {
372 Node aclEntry = it.nextNode();
373 if (!aclEntry.getName().startsWith("acl")) {
374 continue;
375 }
376 String name = StringUtils.substringAfter(aclEntry.getName(), "acl_");
377
378 List<Permission> permissionList = new ArrayList<>();
379
380 NodeIterator permissionIterator = new FilteringNodeIterator(aclEntry.getNodes(), new NodeTypePredicate(NodeTypes.ContentNode.NAME, true));
381 while (permissionIterator.hasNext()) {
382 Node map = permissionIterator.nextNode();
383 String path = map.getProperty("path").getString();
384 UrlPattern p = new SimpleUrlPattern(path);
385 Permission permission = new PermissionImpl();
386 permission.setPattern(p);
387 permission.setPermissions(map.getProperty("permissions").getLong());
388 permissionList.add(permission);
389 }
390
391 ACL acl;
392
393
394 if (principalList.containsKey(name)) {
395 acl = principalList.get(name);
396 permissionList.addAll(acl.getList());
397 }
398 acl = new ACLImpl(name, permissionList);
399 principalList.put(name, acl);
400
401 }
402 return principalList;
403 }
404
405
406
407
408
409 protected NodeIterator findPrincipalNodes(final Node node, final String nodeType) throws RepositoryException {
410 final StringBuilder builder = new StringBuilder("select * from [").append(nodeType).append("]");
411 if (!"/".equals(node.getPath())) {
412 builder.append(" where isdescendantnode(['").append(node.getPath()).append("'])");
413 }
414 final String queryString = builder.toString();
415 log.debug("Executing query \"{}\".", queryString);
416
417 final Query query = node.getSession().getWorkspace().getQueryManager().createQuery(queryString, Query.JCR_SQL2);
418 return query.execute().getNodes();
419 }
420
421 protected Collection<String> findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(final Session session, final Node groupOrRoleNode, final String parentNodeName) throws RepositoryException {
422 if (groupOrRoleNode == null) {
423 return Collections.EMPTY_LIST;
424 }
425
426 final StringBuilder builder = new StringBuilder("select * from [").append(NodeTypes.ContentNode.NAME).append("] as s");
427 builder.append(" where contains(s.*, '").append(groupOrRoleNode.getIdentifier()).append("')");
428 final String queryString = builder.toString();
429 log.debug("Executing query \"{}\".", queryString);
430
431 final Query query = session.getWorkspace().getQueryManager().createQuery(queryString, Query.JCR_SQL2);
432 final NodeIterator nodeIterator = query.execute().getNodes();
433
434 final Collection<String> matches = new ArrayList<>();
435 Node current;
436 while (nodeIterator.hasNext()) {
437 current = nodeIterator.nextNode();
438 if (parentNodeName.equals(current.getName())) {
439 matches.add(current.getParent().getName());
440 }
441 }
442 return matches;
443 }
444
445 }