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