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<>();
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 | ValueFormatException e) {
119 log.debug("{} has invalid value", property.getPath());
120 }
121 }
122 return list;
123 }
124 });
125
126
127
128 return MgnlContext.doInSystemContext(new JCRSessionOp<Boolean>(sessionName) {
129
130 @Override
131 public Boolean exec(Session session) throws RepositoryException {
132 for (String groupOrRole : groupsOrRoles) {
133
134 try {
135 if (session.getNodeByIdentifier(groupOrRole).getName().equalsIgnoreCase(resourceName)) {
136 return true;
137 }
138 } catch (RepositoryException e) {
139 log.debug("Role [{}] does not exist in the ROLES repository", resourceName);
140 }
141 }
142 return false;
143 }
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
163 protected void add(final String principalName, final String resourceName, final String resourceTypeName) throws PrincipalNotFoundException {
164 try {
165 final String nodeID = getLinkedResourceId(resourceName, resourceTypeName);
166
167 if (!hasAny(principalName, resourceName, resourceTypeName)) {
168 Session session = MgnlContext.getJCRSession(getRepositoryName());
169 Node principalNode = findPrincipalNode(principalName, session);
170 if (principalNode == null) {
171 throw new PrincipalNotFoundException("Principal " + principalName + " of type " + resourceTypeName + " was not found.");
172 }
173 if (!principalNode.hasNode(resourceTypeName)) {
174 principalNode.addNode(resourceTypeName, NodeTypes.ContentNode.NAME);
175 }
176 Node node = principalNode.getNode(resourceTypeName);
177
178
179 String newName = Path.getUniqueLabel(session, node.getPath(), "0");
180 node.setProperty(newName, nodeID);
181 session.save();
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 } else {
196 nodeID = role.getId();
197 }
198 } else {
199 Group group = SecuritySupport.Factory.getInstance().getGroupManager().getGroup(resourceName);
200 if (group == null) {
201 log.warn("Invalid group requested: {}", resourceName);
202 nodeID = null;
203 } else {
204 nodeID = group.getId();
205 }
206 }
207 return nodeID;
208 }
209
210 protected String getResourceName(final String resourceId) {
211 try {
212 return MgnlContext.getJCRSession(getRepositoryName()).getNodeByIdentifier(resourceId).getName();
213 } catch (ItemNotFoundException e) {
214
215 return null;
216 } catch (RepositoryException e) {
217 log.error(e.getMessage(), e);
218 }
219 return null;
220 }
221
222
223
224
225
226
227
228
229 protected void remove(final String principalName, final String resourceName, final String resourceTypeName) throws PrincipalNotFoundException {
230 try {
231 final String nodeID = getLinkedResourceId(resourceName, resourceTypeName);
232
233 if (hasAny(principalName, resourceName, resourceTypeName)) {
234 Session session = MgnlContext.getJCRSession(getRepositoryName());
235 Node principalNode = findPrincipalNode(principalName, session);
236 if (!principalNode.hasNode(resourceTypeName)) {
237 throw new PrincipalNotFoundException("Principal " + principalName + " of type " + resourceTypeName + " was not found.");
238 }
239 Node node = principalNode.getNode(resourceTypeName);
240 for (PropertyIterator iter = node.getProperties(); iter.hasNext(); ) {
241 Property nodeData = iter.nextProperty();
242
243 try {
244 if (nodeData.getString().equals(nodeID)) {
245 nodeData.remove();
246 session.save();
247
248 }
249 } catch (IllegalArgumentException | ValueFormatException e) {
250 log.debug("{} has invalid value", nodeData.getPath());
251 }
252 }
253 }
254 } catch (RepositoryException e) {
255 log.error("failed to remove {} {} from [{}]", resourceTypeName, resourceName, principalName, e);
256 }
257 }
258
259 protected abstract String getRepositoryName();
260
261 protected abstract Node findPrincipalNode(String principalName, Session session) throws RepositoryException;
262
263 protected Node findPrincipalNode(String principalName, Session session, String primaryNodeType) throws RepositoryException {
264 return findPrincipalNode(principalName, session, primaryNodeType, null);
265 }
266
267
268
269
270
271
272 protected Node findPrincipalNode(String principalName, Session session, String primaryNodeType, Node startNode) throws RepositoryException {
273 final boolean isInstallationPhase = InstallStatus.inProgress.equals(Components.getComponent(InstallContextImpl.class).getStatus());
274 final long start = System.currentTimeMillis();
275
276 final Node principalNode = isInstallationPhase ? findPrincipalNodeByTraversal(principalName, session, primaryNodeType, startNode) : findPrincipalNodeByQuery(principalName, session, primaryNodeType, startNode);
277 log.debug("Retrieving node took {}ms (isInstallationPhase: {}): path = {}", System.currentTimeMillis() - start, isInstallationPhase, principalNode == null ? "<null>" : principalNode.getPath());
278
279 if (principalNode == null) {
280 log.debug("Could not find principal node '{}' of primary type '{}' under startnode '{}' in workspace '{}'.", principalName, primaryNodeType, startNode == null ? "/" : startNode.getPath(), session.getWorkspace().getName());
281 }
282 return principalNode;
283 }
284
285
286
287
288 Node findPrincipalNodeByQuery(String principalName, Session session, String primaryNodeType, Node startNode) throws RepositoryException {
289 final Node root = startNode == null ? session.getRootNode() : startNode;
290
291 final StringBuilder builder = new StringBuilder("select * from [").append(primaryNodeType).append("] where name() = '").append(principalName).append("'");
292
293 if (!"/".equals(root.getPath())) {
294 builder.append(" and isdescendantnode(['").append(root.getPath()).append("'])");
295 }
296
297 final String queryString = builder.toString();
298 log.debug("Executing query \"{}\".", queryString);
299
300 final Query query = session.getWorkspace().getQueryManager().createQuery(queryString, Query.JCR_SQL2);
301 final NodeIterator iterator = query.execute().getNodes();
302 final Node user = iterator.hasNext() ? iterator.nextNode() : null;
303 if (iterator.hasNext()) {
304 log.error("Query found more than one node of type \"{}\" with name \"{}\" under the root path \"{}\".", primaryNodeType, principalName, root.getPath());
305 }
306 return user;
307 }
308
309
310
311
312 Node findPrincipalNodeByTraversal(String principalName, Session session, String primaryNodeType, Node startNode) throws RepositoryException {
313 final Node root = startNode == null ? session.getRootNode() : startNode;
314 log.debug("Traversing to find nodes of type \"{}\" with name \"{}\" under the root path \"{}\".", primaryNodeType, principalName, root.getPath());
315
316 final LinkedList<Node> nodes = new LinkedList<>();
317
318 for (NodeIterator iterator = root.getNodes(); iterator.hasNext(); ) {
319 final Node node = iterator.nextNode();
320 if (!node.getName().startsWith(NodeTypes.JCR_PREFIX) && !node.getName().startsWith(NodeTypes.REP_PREFIX)) {
321 nodes.add(node);
322 }
323 }
324
325 Node principalNode = null;
326 while (!nodes.isEmpty()) {
327 final Node node = nodes.removeFirst();
328
329 if (node.getName().equals(principalName) && node.getPrimaryNodeType().getName().equals(primaryNodeType)) {
330 if (principalNode != null) {
331 log.error("Traversal found more than one node of type \"{}\" with name \"{}\" under the root path \"{}\".", primaryNodeType, principalName, root.getPath());
332 break;
333 }
334 principalNode = node;
335 }
336
337 int i = 0;
338 for (NodeIterator iterator = node.getNodes(); iterator.hasNext(); ) {
339 nodes.add(i++, iterator.nextNode());
340 }
341 }
342 return principalNode;
343 }
344
345 public Map<String, ACL> getACLs(final String principalName) {
346 return MgnlContext.doInSystemContext(new SilentSessionOp<Map<String, ACL>>(getRepositoryName()) {
347 @Override
348 public Map<String, ACL> doExec(Session session) throws Throwable {
349 Node node = findPrincipalNode(principalName, session);
350 if (node == null) {
351 return Collections.emptyMap();
352 }
353 return getACLs(node);
354 }
355 });
356 }
357
358 protected Map<String, ACL> getACLs(Node node) throws RepositoryException, ValueFormatException, PathNotFoundException {
359 Map<String, ACL> principalList = new HashMap<>();
360 NodeIterator it = new FilteringNodeIterator(node.getNodes(), new NodeTypePredicate(NodeTypes.ContentNode.NAME, true));
361 while (it.hasNext()) {
362 Node aclEntry = it.nextNode();
363 if (!aclEntry.getName().startsWith("acl")) {
364 continue;
365 }
366 String name = StringUtils.substringAfter(aclEntry.getName(), "acl_");
367
368 List<Permission> permissionList = new ArrayList<>();
369
370 NodeIterator permissionIterator = new FilteringNodeIterator(aclEntry.getNodes(), new NodeTypePredicate(NodeTypes.ContentNode.NAME, true));
371 while (permissionIterator.hasNext()) {
372 Node map = permissionIterator.nextNode();
373 String path = map.getProperty("path").getString();
374 UrlPattern p = new SimpleUrlPattern(path);
375 Permission permission = new PermissionImpl();
376 permission.setPattern(p);
377 permission.setPermissions(map.getProperty("permissions").getLong());
378 permissionList.add(permission);
379 }
380
381 ACL acl;
382
383
384 if (principalList.containsKey(name)) {
385 acl = principalList.get(name);
386 permissionList.addAll(acl.getList());
387 }
388 acl = new ACLImpl(name, permissionList);
389 principalList.put(name, acl);
390
391 }
392 return principalList;
393 }
394
395
396
397
398
399 protected NodeIterator findPrincipalNodes(final Node node, final String nodeType) throws RepositoryException {
400 final StringBuilder builder = new StringBuilder("select * from [").append(nodeType).append("]");
401 if (!"/".equals(node.getPath())) {
402 builder.append(" where isdescendantnode(['").append(node.getPath()).append("'])");
403 }
404 final String queryString = builder.toString();
405 log.debug("Executing query \"{}\".", queryString);
406
407 final Query query = node.getSession().getWorkspace().getQueryManager().createQuery(queryString, Query.JCR_SQL2);
408 return query.execute().getNodes();
409 }
410
411 protected Collection<String> findUsersOrGroupsHavingAssignedGroupOrRoleWithUid(final Session session, final Node groupOrRoleNode, final String parentNodeName) throws RepositoryException {
412 if (groupOrRoleNode == null) {
413 return Collections.EMPTY_LIST;
414 }
415
416 final StringBuilder builder = new StringBuilder("select * from [").append(NodeTypes.ContentNode.NAME).append("] as s");
417 builder.append(" where contains(s.*, '").append(groupOrRoleNode.getIdentifier()).append("')");
418 final String queryString = builder.toString();
419 log.debug("Executing query \"{}\".", queryString);
420
421 final Query query = session.getWorkspace().getQueryManager().createQuery(queryString, Query.JCR_SQL2);
422 final NodeIterator nodeIterator = query.execute().getNodes();
423
424 final Collection<String> matches = new ArrayList<>();
425 Node current;
426 while (nodeIterator.hasNext()) {
427 current = nodeIterator.nextNode();
428 if (parentNodeName.equals(current.getName())) {
429 matches.add(current.getParent().getName());
430 }
431 }
432 return matches;
433 }
434
435 }