001/* 002 * Copyright 2020 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.repository.jcr; 017 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.Map; 022import java.util.Set; 023import java.util.stream.Collectors; 024 025import javax.jcr.AccessDeniedException; 026import javax.jcr.ItemNotFoundException; 027import javax.jcr.Node; 028import javax.jcr.NodeIterator; 029import javax.jcr.PathNotFoundException; 030import javax.jcr.Repository; 031import javax.jcr.RepositoryException; 032import javax.jcr.Session; 033import javax.jcr.Value; 034import javax.jcr.lock.Lock; 035import javax.jcr.lock.LockManager; 036import javax.jcr.query.Query; 037 038import org.apache.avalon.framework.component.Component; 039import org.apache.avalon.framework.service.ServiceException; 040import org.apache.avalon.framework.service.ServiceManager; 041import org.apache.avalon.framework.service.Serviceable; 042import org.apache.jackrabbit.util.ISO9075; 043import org.apache.jackrabbit.util.Text; 044import org.slf4j.Logger; 045 046import org.ametys.core.group.GroupIdentity; 047import org.ametys.core.right.ProfileAssignmentStorage.AnonymousOrAnyConnectedKeys; 048import org.ametys.core.right.ProfileAssignmentStorage.UserOrGroup; 049import org.ametys.core.user.UserIdentity; 050import org.ametys.core.util.LambdaUtils; 051import org.ametys.plugins.repository.ACLAmetysObject; 052import org.ametys.plugins.repository.AmetysObject; 053import org.ametys.plugins.repository.AmetysObjectResolver; 054import org.ametys.plugins.repository.AmetysRepositoryException; 055import org.ametys.plugins.repository.ModifiableACLAmetysObject; 056import org.ametys.plugins.repository.ModifiableACLAmetysObjectProfileAssignmentStorage; 057import org.ametys.plugins.repository.RepositoryConstants; 058import org.ametys.plugins.repository.provider.AbstractRepository; 059import org.ametys.plugins.repository.query.expression.Expression; 060import org.ametys.plugins.repository.query.expression.OrExpression; 061import org.ametys.plugins.repository.query.expression.Expression.LogicalOperator; 062import org.ametys.runtime.plugin.component.LogEnabled; 063 064/** 065 * Helper for implementing {@link ModifiableACLAmetysObject} in JCR under its node. 066 */ 067public class ACLJCRAmetysObjectHelper implements Component, Serviceable, LogEnabled 068{ 069 /** The AmetysObject resolver */ 070 protected static AmetysObjectResolver _resolver; 071 /** The repository */ 072 protected static Repository _repository; 073 074 private static final String __NODE_NAME_ROOT_ACL = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":acl"; 075 private static final String __NODETYPE_ROOT_ACL = RepositoryConstants.NAMESPACE_PREFIX + ":acl"; 076 077 private static final String __NODE_NAME_ACL_USERS = "users"; 078 private static final String __NODE_NAME_ACL_GROUPS = "groups"; 079 private static final String __NODETYPE_ACL_USER = RepositoryConstants.NAMESPACE_PREFIX + ":acl-user"; 080 private static final String __NODETYPE_ACL_GROUP = RepositoryConstants.NAMESPACE_PREFIX + ":acl-group"; 081 private static final String __NODETYPE_UNSTRUCTURED = "nt:unstructured"; 082 083 private static final String __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":allowed-any-connected-profiles"; 084 private static final String __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":denied-any-connected-profiles"; 085 private static final String __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":allowed-anonymous-profiles"; 086 private static final String __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":denied-anonymous-profiles"; 087 088 private static final String __PROPERTY_NAME_ALLOWED_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":allowed-profiles"; 089 private static final String __PROPERTY_NAME_DENIED_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":denied-profiles"; 090 091 private static final String __PROPERTY_NAME_DISALLOW_INHERITANCE = RepositoryConstants.NAMESPACE_PREFIX + ":disallow-inheritance"; 092 093 private static final Map<AnonymousOrAnyConnectedKeys, Set<String>> __ANONYMOUS_OR_ANYCONNECTEDUSER_NORIGHT = Map.of( 094 AnonymousOrAnyConnectedKeys.ANONYMOUS_ALLOWED, Set.of(), 095 AnonymousOrAnyConnectedKeys.ANONYMOUS_DENIED, Set.of(), 096 AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_ALLOWED, Set.of(), 097 AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_DENIED, Set.of()); 098 private static final Map<UserOrGroup, Set<String>> __USER_OR_GROUP_NORIGHT = Map.of( 099 UserOrGroup.ALLOWED, Set.of(), 100 UserOrGroup.DENIED, Set.of()); 101 102 103 private static Logger _logger; 104 105 @Override 106 public void service(ServiceManager manager) throws ServiceException 107 { 108 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 109 _repository = (Repository) manager.lookup(AbstractRepository.ROLE); 110 } 111 112 public void setLogger(Logger logger) 113 { 114 _logger = logger; 115 } 116 117 118 /* -------------- */ 119 /* HAS PERMISSION */ 120 /* -------------- */ 121 122 private static Set<String> _convertNodeToPath(Set<? extends Object> rootNodes) 123 { 124 return rootNodes.stream().filter(JCRAmetysObject.class::isInstance).map(JCRAmetysObject.class::cast).map(LambdaUtils.wrap(ao -> ISO9075.encodePath(ao.getNode().getPath()))).collect(Collectors.toSet()); 125 } 126 127 128 /** 129 * Returns some profiles that are matching if any ACL Ametys object has one of the given profiles as allowed for the user 130 * @param user The user 131 * @param profileIds The ids of the profiles to check 132 * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search. 133 * @return If the Set is empty, it means the user has no matching profile.<br> 134 * If the Set is non empty, it contains at least one of the given profile BUT it may not contains all the matching profiles for the user AND it can contains some other profiles that were not in the given profiles 135 */ 136 public static Set<String> hasUserAnyAllowedProfile(Set<? extends Object> rootNodes, UserIdentity user, Set<String> profileIds) 137 { 138 Expression expr = new AllowedProfileExpression(profileIds.toArray(new String[profileIds.size()])); 139 for (String rootPath : _convertNodeToPath(rootNodes)) 140 { 141 NodeIterator nodes = getACLUsers(user, rootPath, expr); 142 143 if (nodes.hasNext()) 144 { 145 // To be complete we could loop on all results, but we only want to answer the question and return additional data if we can 146 Node userNode = nodes.nextNode(); 147 return _getProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES); 148 } 149 } 150 return Set.of(); 151 } 152 153 /** 154 * Returns some profiles that are matching if any ACL Ametys object has one of the given profiles as allowed for the group 155 * @param groups The groups 156 * @param profileIds The ids of the profiles 157 * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search. 158 * @return If the Set is empty, it means the group has no matching profile.<br> 159 * If the Set is non empty, it contains at least one of the given profile BUT it may not contains all the matching profiles for the group AND it can contains some other profiles that were not in the given profiles 160 */ 161 public static Set<String> hasGroupAnyAllowedProfile(Set<? extends Object> rootNodes, Set<GroupIdentity> groups, Set<String> profileIds) 162 { 163 if (!groups.isEmpty()) 164 { 165 Expression expr = new AllowedProfileExpression(profileIds.toArray(new String[profileIds.size()])); 166 for (String rootPath : _convertNodeToPath(rootNodes)) 167 { 168 // Approximative query (to be fast) 169 NodeIterator nodes = _getApprochingACLGroups(groups, rootPath, expr); 170 171 while (nodes.hasNext()) 172 { 173 Node groupNode = nodes.nextNode(); 174 175 // As the query was a fast approximative request, we now check if the result is fine 176 String groupId; 177 String directoryId; 178 try 179 { 180 groupId = Text.unescapeIllegalJcrChars(groupNode.getName()); 181 directoryId = groupNode.getParent().getName(); 182 } 183 catch (RepositoryException ex) 184 { 185 throw new AmetysRepositoryException("An error occured getting group information", ex); 186 } 187 188 if (groups.contains(new GroupIdentity(groupId, directoryId))) 189 { 190 // To be complete we could loop on all results, but we only want to answer the question and return additional data if we can 191 return _getProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES); 192 } 193 } 194 } 195 } 196 197 return Set.of(); 198 } 199 200 /** 201 * Returns some profiles that are matching if any ACL Ametys object has one of the given profiles as allowed for any connected user 202 * @param profileIds The ids of the profiles 203 * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search. 204 * @return If the Set is empty, it means any connected user has no matching profile.<br> 205 * If the Set is non empty, it contains at least one of the given profile BUT it may not contains all the matching profiles for anyconnected user AND it can contains some other profiles that were not in the given profiles 206 */ 207 public static Set<String> hasAnyConnectedAnyAllowedProfile(Set<? extends Object> rootNodes, Set<String> profileIds) 208 { 209 Expression expr = new AnyConnectedAllowedProfileExpression(profileIds.toArray(new String[profileIds.size()])); 210 for (String rootPath : _convertNodeToPath(rootNodes)) 211 { 212 NodeIterator nodes = getACLRoots(rootPath, expr); 213 214 if (nodes.hasNext()) 215 { 216 // To be complete we could loop on all results, but we only want to answer the question and return additional data if we can 217 Node aclNode = nodes.nextNode(); 218 return _getProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES); 219 } 220 } 221 return Set.of(); 222 } 223 224 225 /** 226 * Returns some profiles that are matching if any ACL Ametys object has one of the given profiles as allowed for anonymous 227 * @param profileIds The ids of the profiles 228 * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search. 229 * @return If the Set is empty, it means anonymous has no matching profile.<br> 230 * If the Set is non empty, it contains at least one of the given profile BUT it may not contains all the matching profiles for anonymous AND it can contains some other profiles that were not in the given profiles 231 */ 232 public static Set<String> hasAnonymousAnyAllowedProfile(Set<? extends Object> rootNodes, Set<String> profileIds) 233 { 234 Expression expr = new AnonymousAllowedProfileExpression(profileIds.toArray(new String[profileIds.size()])); 235 for (String rootPath : _convertNodeToPath(rootNodes)) 236 { 237 NodeIterator nodes = getACLRoots(rootPath, expr); 238 239 if (nodes.hasNext()) 240 { 241 // To be complete we could loop on all results, but we only want to answer the question and return additional data if we can 242 Node aclNode = nodes.nextNode(); 243 return _getProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES); 244 } 245 } 246 return Set.of(); 247 } 248 249 /** 250 * Gets all contexts with stored profiles (allowed or denied) for anonymous or any connected user and for each, a description of the permission 251 * @return a map associating a context object to the stored profile for each permission 252 */ 253 public static Map<Object, Map<AnonymousOrAnyConnectedKeys, Set<String>>> getAllProfilesForAnonymousAndAnyConnectedUser() 254 { 255 Map<Object, Map<AnonymousOrAnyConnectedKeys, Set<String>>> result = new HashMap<>(); 256 // Only retrieve node with assignments to anonymous or any connected 257 Expression predicate = new OrExpression( 258 () -> "@" + __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES, 259 () -> "@" + __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES, 260 () -> "@" + __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES, 261 () -> "@" + __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES 262 ); 263 NodeIterator nodes = getACLRoots(null, predicate); 264 265 while (nodes.hasNext()) 266 { 267 Node aclNode = nodes.nextNode(); 268 try 269 { 270 Map<AnonymousOrAnyConnectedKeys, Set<String>> aoResult = new HashMap<>(); 271 Set<String> allowedAnonymous = _getProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES); 272 if (!allowedAnonymous.isEmpty()) 273 { 274 aoResult.put(AnonymousOrAnyConnectedKeys.ANONYMOUS_ALLOWED, allowedAnonymous); 275 } 276 277 Set<String> deniedAnonymous = _getProperty(aclNode, __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES); 278 if (!deniedAnonymous.isEmpty()) 279 { 280 aoResult.put(AnonymousOrAnyConnectedKeys.ANONYMOUS_DENIED, deniedAnonymous); 281 } 282 Set<String> allowedAny = _getProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES); 283 if (!allowedAny.isEmpty()) 284 { 285 aoResult.put(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_ALLOWED, allowedAny); 286 } 287 Set<String> deniedAny = _getProperty(aclNode, __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES); 288 if (!deniedAny.isEmpty()) 289 { 290 aoResult.put(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_DENIED, deniedAny); 291 } 292 293 if (!aoResult.isEmpty()) 294 { 295 AmetysObject ao = _getAmetysObjectFromACLNode(aclNode); 296 result.put(ao, aoResult); 297 } 298 } 299 catch (RepositoryException e) 300 { 301 _logger.error("Failed to retrieve object for acl node " + aclNode.toString() + ". The node will be ignored."); 302 } 303 } 304 return result; 305 } 306 307 /** 308 * Gets all context with stored profiles (allowed or denied) for the groups and for each, a description of the permission 309 * Gets the groups that have allowed profiles assigned on the given object 310 * @param groups The groups to get profiles for. 311 * @return The map of context with their assigned permissions 312 */ 313 public static Map<Object, Map<GroupIdentity, Map<UserOrGroup, Set<String>>>> getAllProfilesForGroups(Set<GroupIdentity> groups) 314 { 315 Map<Object, Map<GroupIdentity, Map<UserOrGroup, Set<String>>>> result = new HashMap<>(); 316 if (!groups.isEmpty()) 317 { 318 // Approximative query (to be fast) 319 NodeIterator nodes = _getApprochingACLGroups(groups, null, null); 320 321 while (nodes.hasNext()) 322 { 323 Node groupNode = nodes.nextNode(); 324 325 // As the query was a fast approximative request, we now check if the result is fine 326 String groupId; 327 String directoryId; 328 try 329 { 330 groupId = Text.unescapeIllegalJcrChars(groupNode.getName()); 331 directoryId = groupNode.getParent().getName(); 332 } 333 catch (RepositoryException ex) 334 { 335 throw new AmetysRepositoryException("An error occured getting group information", ex); 336 } 337 338 GroupIdentity currentGroup = new GroupIdentity(groupId, directoryId); 339 if (groups.contains(currentGroup)) 340 { 341 try 342 { 343 344 // Determine the group permissions 345 Map<UserOrGroup, Set<String>> groupPermissions = new HashMap<>(); 346 347 Set<String> allowedProfiles = _getProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES); 348 if (!allowedProfiles.isEmpty()) 349 { 350 groupPermissions.put(UserOrGroup.ALLOWED, allowedProfiles); 351 } 352 Set<String> deniedProfiles = _getProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES); 353 if (!deniedProfiles.isEmpty()) 354 { 355 groupPermissions.put(UserOrGroup.DENIED, deniedProfiles); 356 } 357 358 // Only add actual permissions to the result 359 if (!groupPermissions.isEmpty()) 360 { 361 AmetysObject ao = _getAmetysObjectFromACLNode(groupNode); 362 // The ametys object could already be in the result map having permissions from an other group 363 Map<GroupIdentity, Map<UserOrGroup, Set<String>>> objectPermissions = result.computeIfAbsent(ao, k -> new HashMap<>()); 364 // There can only be one node per group, so we don't need to retrieve existing value 365 objectPermissions.put(currentGroup, groupPermissions); 366 } 367 } 368 catch (RepositoryException e) 369 { 370 _logger.error("Failed to retrieve object for group acl node " + groupNode.toString() + ". The node will be ignored."); 371 } 372 } 373 } 374 } 375 return result; 376 } 377 378 /** 379 * Gets all context with stored profiles (allowed or denied) for the user and for each, a description of the permission 380 * @param user The user to get profiles for. 381 * @return The map of context with their assigned allowed/denied profiles 382 */ 383 public static Map<Object, Map<UserOrGroup, Set<String>>> getAllProfilesForUser(UserIdentity user) 384 { 385 Map<Object, Map<UserOrGroup, Set<String>>> result = new HashMap<>(); 386 NodeIterator nodes = getACLUsers(user, null, null); 387 388 while (nodes.hasNext()) 389 { 390 Node userNode = nodes.nextNode(); 391 try 392 { 393 Map<UserOrGroup, Set<String>> aoResult = new HashMap<>(); 394 Set<String> allowedProfiles = _getProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES); 395 if (!allowedProfiles.isEmpty()) 396 { 397 aoResult.put(UserOrGroup.ALLOWED, allowedProfiles); 398 } 399 Set<String> deniedProfiles = _getProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES); 400 if (!deniedProfiles.isEmpty()) 401 { 402 aoResult.put(UserOrGroup.DENIED, deniedProfiles); 403 } 404 405 if (!aoResult.isEmpty()) 406 { 407 AmetysObject ao = _getAmetysObjectFromACLNode(userNode); 408 result.put(ao, aoResult); 409 } 410 } 411 catch (RepositoryException e) 412 { 413 _logger.error("Failed to retrieve object for user acl node " + userNode.toString() + ". The node will be ignored."); 414 } 415 } 416 return result; 417 } 418 419 private static AmetysObject _getAmetysObjectFromACLNode(Node node) throws RepositoryException, ItemNotFoundException, AccessDeniedException 420 { 421 switch (node.getPrimaryNodeType().getName()) 422 { 423 case __NODETYPE_ROOT_ACL: 424 return _resolver.resolve(node.getParent(), false); 425 case __NODETYPE_ACL_USER: 426 case __NODETYPE_ACL_GROUP: 427 return _resolver.resolve(node.getParent().getParent().getParent().getParent(), false); 428 default: 429 return null; 430 } 431 } 432 433 /** 434 * Returns all ACL root objects (ametys:acl nodes) 435 * @param rootPath The root path to restrict the search. Can be null. 436 * @param predicat The predicat expression. Can be null. 437 * @return The ACL root objects 438 */ 439 public static NodeIterator getACLRoots (String rootPath, Expression predicat) 440 { 441 StringBuilder sb = new StringBuilder("/jcr:root"); 442 443 if (rootPath != null) 444 { 445 sb.append(rootPath); 446 } 447 448 sb.append("//element(*, ").append(__NODETYPE_ROOT_ACL).append(")"); 449 450 if (predicat != null) 451 { 452 sb.append("[").append(predicat.build()).append("]"); 453 } 454 455 return _query(sb.toString()); 456 } 457 458 /** 459 * Returns all ACL objects for a given user (ametys:acl-user nodes) 460 * @param user The user 461 * @param rootPath The root path to restrict the search. Can be null. 462 * @param predicat The predicat expression. Can be null. 463 * @return The ACL user objects for user 464 */ 465 public static NodeIterator getACLUsers (UserIdentity user, String rootPath, Expression predicat) 466 { 467 StringBuilder sb = new StringBuilder("/jcr:root"); 468 469 if (rootPath != null) 470 { 471 sb.append(rootPath); 472 } 473 474 sb.append("//element(*, ").append(__NODETYPE_ROOT_ACL).append(")") 475 .append("/").append(__NODE_NAME_ACL_USERS) 476 .append("/").append(user.getPopulationId()) 477 .append("/").append(ISO9075.encode(user.getLogin())); 478 479 if (predicat != null) 480 { 481 sb.append("[").append(predicat.build()).append("]"); 482 } 483 484 String jcrQuery = sb.toString(); 485 return _query(jcrQuery); 486 } 487 488 /** 489 * Returns all ACL objects for users (ametys:acl-user nodes) 490 * @param predicat The predicat expression. Can be null. 491 * @return The ACL user objects for users 492 */ 493 public static NodeIterator getACLUsers (Expression predicat) 494 { 495 StringBuilder sb = new StringBuilder(); 496 497 sb.append("//element(*, ").append(__NODETYPE_ACL_USER).append(")"); 498 499 if (predicat != null) 500 { 501 sb.append("[").append(predicat.build()).append("]"); 502 } 503 504 return _query(sb.toString()); 505 } 506 507 /** 508 * Returns all ACL objects for groups (ametys:acl-group nodes) 509 * @param predicat The predicat expression. Can be null. 510 * @return The ACL group objects for groups 511 */ 512 public static NodeIterator getACLGroups (Expression predicat) 513 { 514 StringBuilder sb = new StringBuilder(); 515 516 sb.append("//element(*, ").append(__NODETYPE_ACL_GROUP).append(")"); 517 518 if (predicat != null) 519 { 520 sb.append("[").append(predicat.build()).append("]"); 521 } 522 523 return _query(sb.toString()); 524 } 525 526 private static NodeIterator _getApprochingACLGroups (Set<GroupIdentity> groups, String rootPath, Expression predicat) 527 { 528 StringBuilder sb = new StringBuilder("/jcr:root"); 529 530 if (rootPath != null) 531 { 532 sb.append(rootPath); 533 } 534 535 sb.append("//element(*, ametys:acl-group)[("); 536 537 sb.append(groups.stream() 538 .map(GroupIdentity::getId) 539 .map(Text::escapeIllegalJcrChars) 540 .map(ISO9075::encode) // used to support nodeName with number (id of SQL Group) 541 .map(nodeName -> "fn:name()='" + nodeName + "'") 542 .collect(Collectors.joining(LogicalOperator.OR.toString()))); 543 sb.append(")"); 544 545 if (predicat != null) 546 { 547 sb.append(LogicalOperator.AND.toString()).append(predicat.build()); 548 } 549 550 sb.append("]"); 551 552 return _query(sb.toString()); 553 } 554 555 /** 556 * Returns all ACL objects for a given group (ametys:acl-group nodes) 557 * @param group The group 558 * @param rootPath The root path to restrict the search. Can be null. 559 * @param predicat The predicat expression. Can be null. 560 * @return The ACL user objects for groups 561 */ 562 public static NodeIterator getACLGroups (GroupIdentity group, String rootPath, Expression predicat) 563 { 564 StringBuilder sb = new StringBuilder("/jcr:root"); 565 566 if (rootPath != null) 567 { 568 sb.append(rootPath); 569 } 570 571 sb.append("//element(*, ").append(__NODETYPE_ROOT_ACL).append(")") 572 .append("/").append(__NODE_NAME_ACL_GROUPS) 573 .append("/").append(group.getDirectoryId()) 574 .append("/").append(ISO9075.encode(Text.escapeIllegalJcrChars(group.getId()))); 575 576 if (predicat != null) 577 { 578 sb.append("[").append(predicat.build()).append("]"); 579 } 580 581 return _query(sb.toString()); 582 } 583 584 private static NodeIterator _query (String jcrQuery) 585 { 586 Session session = null; 587 try 588 { 589 session = _repository.login(); 590 long t1 = System.currentTimeMillis(); 591 @SuppressWarnings("deprecation") 592 Query query = session.getWorkspace().getQueryManager().createQuery(jcrQuery, Query.XPATH); 593 if (_logger.isInfoEnabled()) 594 { 595 _logger.info("ACLJCR query '" + jcrQuery + "' executed in " + (System.currentTimeMillis() - t1) + " ms"); 596 } 597 return query.execute().getNodes(); 598 } 599 catch (RepositoryException ex) 600 { 601 if (session != null) 602 { 603 session.logout(); 604 } 605 throw new AmetysRepositoryException("An error occured executing the JCR query : " + jcrQuery, ex); 606 } 607 } 608 609 /* ------------------------------------------- */ 610 /* PROFILES FOR ANY CONNECTED USER / ANONYMOUS */ 611 /* ------------------------------------------- */ 612 613 /** 614 * Helper for {@link ACLAmetysObject#getProfilesForAnonymousAndAnyConnectedUser} 615 * @param node The JCR node for the Ametys object 616 * @return a map containing allowed/denied profiles that anonymous and any connected user has on the given object 617 */ 618 public static Map<AnonymousOrAnyConnectedKeys, Set<String>> getProfilesForAnonymousAndAnyConnectedUser(Node node) 619 { 620 Node aclNode = _getACLNode(node); 621 if (aclNode == null) 622 { 623 return __ANONYMOUS_OR_ANYCONNECTEDUSER_NORIGHT; 624 } 625 else 626 { 627 return Map.of(AnonymousOrAnyConnectedKeys.ANONYMOUS_ALLOWED, _getProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES), 628 AnonymousOrAnyConnectedKeys.ANONYMOUS_DENIED, _getProperty(aclNode, __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES), 629 AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_ALLOWED, _getProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES), 630 AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_DENIED, _getProperty(aclNode, __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES)); 631 } 632 } 633 634 /** 635 * Helper for {@link ModifiableACLAmetysObject#addAllowedProfilesForAnyConnectedUser(Set)} 636 * @param node The JCR node for the Ametys object 637 * @param profileIds The profiles to add 638 */ 639 public static void addAllowedProfilesForAnyConnectedUser(Node node, Set<String> profileIds) 640 { 641 Node aclNode = _getOrCreateACLNode(node); 642 for (String profile : profileIds) 643 { 644 _addProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES, profile); 645 } 646 _save(node); 647 } 648 649 /** 650 * Helper for {@link ModifiableACLAmetysObject#removeAllowedProfilesForAnyConnectedUser(Set)} 651 * @param node The JCR node for the Ametys object 652 * @param profileIds The profiles to remove 653 */ 654 public static void removeAllowedProfilesForAnyConnectedUser(Node node, Set<String> profileIds) 655 { 656 Node aclNode = _getOrCreateACLNode(node); 657 for (String profile : profileIds) 658 { 659 _removeProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES, profile); 660 } 661 _save(node); 662 } 663 664 /** 665 * Helper for {@link ModifiableACLAmetysObject#addDeniedProfilesForAnyConnectedUser(Set)} 666 * @param node The JCR node for the Ametys object 667 * @param profileIds The profiles to add 668 */ 669 public static void addDeniedProfilesForAnyConnectedUser(Node node, Set<String> profileIds) 670 { 671 Node aclNode = _getOrCreateACLNode(node); 672 for (String profile : profileIds) 673 { 674 _addProperty(aclNode, __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES, profile); 675 } 676 _save(node); 677 } 678 679 /** 680 * Helper for {@link ModifiableACLAmetysObject#removeDeniedProfilesForAnyConnectedUser(Set)} 681 * @param node The JCR node for the Ametys object 682 * @param profileIds The profiles to remove 683 */ 684 public static void removeDeniedProfilesForAnyConnectedUser(Node node, Set<String> profileIds) 685 { 686 Node aclNode = _getOrCreateACLNode(node); 687 for (String profile : profileIds) 688 { 689 _removeProperty(aclNode, __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES, profile); 690 } 691 _save(node); 692 } 693 694 /** 695 * Helper for {@link ModifiableACLAmetysObject#addAllowedProfilesForAnonymous(Set)} 696 * @param node The JCR node for the Ametys object 697 * @param profileIds The profiles to add 698 */ 699 public static void addAllowedProfilesForAnonymous(Node node, Set<String> profileIds) 700 { 701 Node aclNode = _getOrCreateACLNode(node); 702 for (String profile : profileIds) 703 { 704 _addProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES, profile); 705 } 706 _save(node); 707 } 708 709 /** 710 * Helper for {@link ModifiableACLAmetysObject#removeAllowedProfilesForAnonymous(Set)} 711 * @param node The JCR node for the Ametys object 712 * @param profileIds The profiles to remove 713 */ 714 public static void removeAllowedProfilesForAnonymous(Node node, Set<String> profileIds) 715 { 716 Node aclNode = _getOrCreateACLNode(node); 717 for (String profile : profileIds) 718 { 719 _removeProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES, profile); 720 } 721 _save(node); 722 } 723 724 /** 725 * Helper for {@link ModifiableACLAmetysObject#addDeniedProfilesForAnyConnectedUser(Set)} 726 * @param node The JCR node for the Ametys object 727 * @param profileIds The profiles to add 728 */ 729 public static void addDeniedProfilesForAnonymous(Node node, Set<String> profileIds) 730 { 731 Node aclNode = _getOrCreateACLNode(node); 732 for (String profile : profileIds) 733 { 734 _addProperty(aclNode, __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES, profile); 735 } 736 _save(node); 737 } 738 739 /** 740 * Helper for {@link ModifiableACLAmetysObject#removeDeniedProfilesForAnyConnectedUser(Set)} 741 * @param node The JCR node for the Ametys object 742 * @param profileIds The profiles to remove 743 */ 744 public static void removeDeniedProfilesForAnonymous(Node node, Set<String> profileIds) 745 { 746 Node aclNode = _getOrCreateACLNode(node); 747 for (String profile : profileIds) 748 { 749 _removeProperty(aclNode, __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES, profile); 750 } 751 _save(node); 752 } 753 754 755 /* ------------------- */ 756 /* MANAGEMENT OF USERS */ 757 /* ------------------- */ 758 /** 759 * Helper for {@link ACLAmetysObject#getProfilesForUsers} 760 * @param node The JCR node for the Ametys object 761 * @param user The user to get profiles for. Can be null to get profiles for all users that have rights 762 * @return The map of allowed users with their assigned allowed/denied profiles 763 */ 764 public static Map<UserIdentity, Map<UserOrGroup, Set<String>>> getProfilesForUsers(Node node, UserIdentity user) 765 { 766 if (user == null) 767 { 768 try 769 { 770 Node usersNode = _getUsersNode(node); 771 if (usersNode == null) 772 { 773 return Map.of(); 774 } 775 776 Map<UserIdentity, Map<UserOrGroup, Set<String>>> result = new HashMap<>(); 777 778 NodeIterator populationsIterator = usersNode.getNodes(); 779 while (populationsIterator.hasNext()) 780 { 781 Node populationNode = populationsIterator.nextNode(); 782 NodeIterator usersIterator = populationNode.getNodes(); 783 while (usersIterator.hasNext()) 784 { 785 Node userNode = usersIterator.nextNode(); 786 Set<String> allowedProfiles = _getProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES); 787 Set<String> deniedProfiles = _getProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES); 788 if (!allowedProfiles.isEmpty() || !deniedProfiles.isEmpty()) 789 { 790 result.put(new UserIdentity(userNode.getName(), populationNode.getName()), 791 Map.of(UserOrGroup.ALLOWED, allowedProfiles, 792 UserOrGroup.DENIED, deniedProfiles)); 793 } 794 } 795 } 796 797 return result; 798 } 799 catch (RepositoryException e) 800 { 801 throw new AmetysRepositoryException("Unable to get allowed/denied users", e); 802 } 803 } 804 else 805 { 806 Node userNode = _getUserNode(node, user); 807 if (userNode == null) 808 { 809 return Map.of(user, __USER_OR_GROUP_NORIGHT); 810 } 811 else 812 { 813 return Map.of(user, 814 Map.of(UserOrGroup.ALLOWED, _getProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES), 815 UserOrGroup.DENIED, _getProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES))); 816 } 817 } 818 } 819 820 /** 821 * Helper for {@link ModifiableACLAmetysObject#addAllowedUsers(Set, String)} 822 * @param users The users to add 823 * @param node The JCR node for the Ametys object 824 * @param profileId The id of the profile 825 */ 826 public static void addAllowedUsers(Set<UserIdentity> users, Node node, String profileId) 827 { 828 for (UserIdentity userIdentity : users) 829 { 830 Node userNode = _getOrCreateUserNode(node, userIdentity); 831 _addProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId); 832 } 833 _save(node); 834 } 835 836 /** 837 * Helper for {@link ModifiableACLAmetysObject#removeAllowedUsers(Set, String)} 838 * @param users The users to remove 839 * @param node The JCR node for the Ametys object 840 * @param profileId The id of the profile 841 */ 842 public static void removeAllowedUsers(Set<UserIdentity> users, Node node, String profileId) 843 { 844 for (UserIdentity userIdentity : users) 845 { 846 Node userNode = _getOrCreateUserNode(node, userIdentity); 847 _removeProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId); 848 } 849 _save(node); 850 } 851 852 /** 853 * Helper for {@link ModifiableACLAmetysObject#removeAllowedGroups(Set)} 854 * @param users The users to remove 855 * @param node The JCR node for the Ametys object 856 */ 857 public static void removeAllowedUsers(Set<UserIdentity> users, Node node) 858 { 859 for (UserIdentity userIdentity : users) 860 { 861 Node userNode = _getOrCreateUserNode(node, userIdentity); 862 _setProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES, Collections.EMPTY_SET); 863 } 864 _save(node); 865 } 866 867 /** 868 * Helper for {@link ModifiableACLAmetysObject#addDeniedUsers(Set, String)} 869 * @param users The users to add 870 * @param node The JCR node for the Ametys object 871 * @param profileId The id of the profile 872 */ 873 public static void addDeniedUsers(Set<UserIdentity> users, Node node, String profileId) 874 { 875 for (UserIdentity userIdentity : users) 876 { 877 Node userNode = _getOrCreateUserNode(node, userIdentity); 878 _addProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES, profileId); 879 } 880 _save(node); 881 } 882 883 /** 884 * Helper for {@link ModifiableACLAmetysObject#removeDeniedUsers(Set, String)} 885 * @param users The users to remove 886 * @param node The JCR node for the Ametys object 887 * @param profileId The id of the profile 888 */ 889 public static void removeDeniedUsers(Set<UserIdentity> users, Node node, String profileId) 890 { 891 for (UserIdentity userIdentity : users) 892 { 893 Node userNode = _getOrCreateUserNode(node, userIdentity); 894 _removeProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES, profileId); 895 } 896 _save(node); 897 } 898 899 /** 900 * Helper for {@link ModifiableACLAmetysObject#removeDeniedUsers(Set)} 901 * @param users The users to remove 902 * @param node The JCR node for the Ametys object 903 */ 904 public static void removeDeniedUsers(Set<UserIdentity> users, Node node) 905 { 906 for (UserIdentity userIdentity : users) 907 { 908 Node userNode = _getOrCreateUserNode(node, userIdentity); 909 _setProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES, Collections.EMPTY_SET); 910 } 911 _save(node); 912 } 913 914 /* -------------------- */ 915 /* MANAGEMENT OF GROUPS */ 916 /* -------------------- */ 917 918 /** 919 * Helper for {@link ACLAmetysObject#getProfilesForGroups} 920 * @param node The JCR node for the Ametys object 921 * @param groups The group to get profiles for. Can be null to get profiles for all groups that have rights 922 * @return The map of allowed/denied groups with their assigned profiles 923 */ 924 public static Map<GroupIdentity, Map<UserOrGroup, Set<String>>> getProfilesForGroups(Node node, Set<GroupIdentity> groups) 925 { 926 try 927 { 928 if (groups != null && groups.isEmpty()) 929 { 930 return Map.of(); 931 } 932 933 Node groupsNode = _getGroupsNode(node); 934 if (groupsNode == null) 935 { 936 return Map.of(); 937 } 938 939 Map<GroupIdentity, Map<UserOrGroup, Set<String>>> result = new HashMap<>(); 940 941 if (groups == null) 942 { 943 NodeIterator groupDirectoriesIterator = groupsNode.getNodes(); 944 while (groupDirectoriesIterator.hasNext()) 945 { 946 Node groupDirectoryNode = groupDirectoriesIterator.nextNode(); 947 NodeIterator groupsIterator = groupDirectoryNode.getNodes(); 948 while (groupsIterator.hasNext()) 949 { 950 Node groupNode = groupsIterator.nextNode(); 951 Set<String> allowedProfiles = _getProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES); 952 Set<String> deniedProfiles = _getProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES); 953 if (!allowedProfiles.isEmpty() || !deniedProfiles.isEmpty()) 954 { 955 result.put(new GroupIdentity(Text.unescapeIllegalJcrChars(groupNode.getName()), groupDirectoryNode.getName()), 956 Map.of(UserOrGroup.ALLOWED, allowedProfiles, 957 UserOrGroup.DENIED, deniedProfiles)); 958 } 959 } 960 } 961 } 962 else 963 { 964 Map<String, Node> groupsNodeByDirectoryIdCache = new HashMap<>(); 965 966 for (GroupIdentity group : groups) 967 { 968 Node directoryNode = groupsNodeByDirectoryIdCache.computeIfAbsent(group.getDirectoryId(), LambdaUtils.wrap(directoryId -> groupsNode.hasNode(directoryId) ? groupsNode.getNode(directoryId) : null)); 969 970 String groupNodeName = Text.escapeIllegalJcrChars(group.getId()); 971 if (directoryNode != null && directoryNode.hasNode(groupNodeName)) 972 { 973 Node groupNode = directoryNode.getNode(groupNodeName); 974 975 result.put(group, 976 Map.of(UserOrGroup.ALLOWED, _getProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES), 977 UserOrGroup.DENIED, _getProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES))); 978 } 979 } 980 } 981 982 return result; 983 } 984 catch (RepositoryException e) 985 { 986 throw new AmetysRepositoryException("Unable to get allowed/denied groups", e); 987 } 988 } 989 990 /** 991 * Helper for {@link ModifiableACLAmetysObject#addAllowedGroups(Set, String)} 992 * @param groups The groups to add 993 * @param node The JCR node for the Ametys object 994 * @param profileId The id of the profile 995 */ 996 public static void addAllowedGroups(Set<GroupIdentity> groups, Node node, String profileId) 997 { 998 for (GroupIdentity groupIdentity : groups) 999 { 1000 Node groupNode = _getOrCreateGroupNode(node, groupIdentity); 1001 _addProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId); 1002 } 1003 _save(node); 1004 } 1005 1006 /** 1007 * Helper for {@link ModifiableACLAmetysObject#removeAllowedGroups(Set, String)} 1008 * @param groups The groups to remove 1009 * @param node The JCR node for the Ametys object 1010 * @param profileId The id of the profile 1011 */ 1012 public static void removeAllowedGroups(Set<GroupIdentity> groups, Node node, String profileId) 1013 { 1014 for (GroupIdentity groupIdentity : groups) 1015 { 1016 Node groupNode = _getOrCreateGroupNode(node, groupIdentity); 1017 _removeProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId); 1018 } 1019 _save(node); 1020 } 1021 1022 /** 1023 * Helper for {@link ModifiableACLAmetysObject#removeAllowedGroups(Set)} 1024 * @param groups The groups to remove 1025 * @param node The JCR node for the Ametys object 1026 */ 1027 public static void removeAllowedGroups(Set<GroupIdentity> groups, Node node) 1028 { 1029 for (GroupIdentity groupIdentity : groups) 1030 { 1031 Node groupNode = _getOrCreateGroupNode(node, groupIdentity); 1032 _setProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES, Collections.EMPTY_SET); 1033 } 1034 _save(node); 1035 } 1036 1037 /** 1038 * Helper for {@link ModifiableACLAmetysObject#addDeniedGroups(Set, String)} 1039 * @param groups The groups to add 1040 * @param node The JCR node for the Ametys object 1041 * @param profileId The id of the profile 1042 */ 1043 public static void addDeniedGroups(Set<GroupIdentity> groups, Node node, String profileId) 1044 { 1045 for (GroupIdentity groupIdentity : groups) 1046 { 1047 Node groupNode = _getOrCreateGroupNode(node, groupIdentity); 1048 _addProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES, profileId); 1049 } 1050 _save(node); 1051 } 1052 1053 /** 1054 * Helper for {@link ModifiableACLAmetysObject#removeDeniedGroups(Set, String)} 1055 * @param groups The groups to remove 1056 * @param node The JCR node for the Ametys object 1057 * @param profileId The id of the profile 1058 */ 1059 public static void removeDeniedGroups(Set<GroupIdentity> groups, Node node, String profileId) 1060 { 1061 for (GroupIdentity groupIdentity : groups) 1062 { 1063 Node groupNode = _getOrCreateGroupNode(node, groupIdentity); 1064 _removeProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES, profileId); 1065 } 1066 _save(node); 1067 } 1068 1069 /** 1070 * Helper for {@link ModifiableACLAmetysObject#removeDeniedGroups(Set)} 1071 * @param groups The groups to remove 1072 * @param node The JCR node for the Ametys object 1073 */ 1074 public static void removeDeniedGroups(Set<GroupIdentity> groups, Node node) 1075 { 1076 for (GroupIdentity groupIdentity : groups) 1077 { 1078 Node groupNode = _getOrCreateGroupNode(node, groupIdentity); 1079 _setProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES, Collections.EMPTY_SET); 1080 } 1081 _save(node); 1082 } 1083 1084 1085 /* ------ */ 1086 /* REMOVE */ 1087 /* ------ */ 1088 1089 /** 1090 * Helper for {@link ModifiableACLAmetysObjectProfileAssignmentStorage#removeProfile(String)} 1091 * @param profileId The id of the profile 1092 */ 1093 public static void removeProfile(String profileId) 1094 { 1095 // Remove this profile set as allowed or denied in users 1096 Expression expr = new OrExpression(new AllowedProfileExpression(profileId), new DeniedProfileExpression(profileId)); 1097 NodeIterator users = getACLUsers(expr); 1098 while (users.hasNext()) 1099 { 1100 Node userNode = (Node) users.next(); 1101 _removeProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId); 1102 _removeProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES, profileId); 1103 _save(userNode); 1104 } 1105 1106 // Remove this profile set as allowed or denied in groups 1107 NodeIterator groups = getACLGroups(expr); 1108 while (groups.hasNext()) 1109 { 1110 Node groupNode = (Node) groups.next(); 1111 _removeProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId); 1112 _removeProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES, profileId); 1113 _save(groupNode); 1114 } 1115 1116 // Remove this profile set as allowed or denied for anonymous and any connected 1117 expr = new OrExpression(new AnonymousAllowedProfileExpression(profileId), new AnonymousDeniedProfileExpression(profileId), new AnyConnectedAllowedProfileExpression(profileId), new AnyConnectedDeniedProfileExpression(profileId)); 1118 NodeIterator nodes = getACLRoots(null, expr); 1119 while (nodes.hasNext()) 1120 { 1121 Node node = (Node) nodes.next(); 1122 _removeProperty(node, __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES, profileId); 1123 _removeProperty(node, __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES, profileId); 1124 _removeProperty(node, __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES, profileId); 1125 _removeProperty(node, __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES, profileId); 1126 _save(node); 1127 } 1128 } 1129 1130 /** 1131 * Helper for {@link ModifiableACLAmetysObjectProfileAssignmentStorage#removeUser(UserIdentity)} 1132 * @param user The user 1133 */ 1134 public static void removeUser(UserIdentity user) 1135 { 1136 NodeIterator users = getACLUsers(user, null, null); 1137 1138 while (users.hasNext()) 1139 { 1140 Node userNode = (Node) users.next(); 1141 try 1142 { 1143 userNode.remove(); 1144 _save(userNode); 1145 } 1146 catch (RepositoryException e) 1147 { 1148 throw new AmetysRepositoryException(e); 1149 } 1150 } 1151 } 1152 1153 /** 1154 * Helper for {@link ModifiableACLAmetysObjectProfileAssignmentStorage#removeGroup(GroupIdentity)} 1155 * @param group The group 1156 */ 1157 public static void removeGroup(GroupIdentity group) 1158 { 1159 NodeIterator groups = getACLGroups(group, null, null); 1160 while (groups.hasNext()) 1161 { 1162 Node gpNode = (Node) groups.next(); 1163 try 1164 { 1165 gpNode.remove(); 1166 _save(gpNode); 1167 } 1168 catch (RepositoryException e) 1169 { 1170 throw new AmetysRepositoryException(e); 1171 } 1172 } 1173 } 1174 1175 /* --------------- */ 1176 /* INHERITANCE */ 1177 /* --------------- */ 1178 /** 1179 * Helper for {@link ACLAmetysObject#isInheritanceDisallowed()} 1180 * @param node The JCR node for the Ametys object 1181 * @return true if the inheritance is disallow of the given node 1182 */ 1183 public static boolean isInheritanceDisallowed(Node node) 1184 { 1185 try 1186 { 1187 Node aclNode = _getACLNode(node); 1188 if (aclNode != null && aclNode.hasProperty(__PROPERTY_NAME_DISALLOW_INHERITANCE)) 1189 { 1190 return aclNode.getProperty(__PROPERTY_NAME_DISALLOW_INHERITANCE).getBoolean(); 1191 } 1192 return false; 1193 } 1194 catch (RepositoryException e) 1195 { 1196 throw new AmetysRepositoryException("Unable to get " + __PROPERTY_NAME_DISALLOW_INHERITANCE + " property", e); 1197 } 1198 } 1199 1200 /** 1201 * Helper for {@link ModifiableACLAmetysObject#disallowInheritance(boolean)} 1202 * @param node The JCR node for the Ametys object 1203 * @param disallow true to disallow the inheritance, false otherwise 1204 */ 1205 public static void disallowInheritance(Node node, boolean disallow) 1206 { 1207 Node aclNode = _getOrCreateACLNode(node); 1208 try 1209 { 1210 aclNode.setProperty(__PROPERTY_NAME_DISALLOW_INHERITANCE, disallow); 1211 } 1212 catch (RepositoryException e) 1213 { 1214 throw new AmetysRepositoryException("Unable to set " + __PROPERTY_NAME_DISALLOW_INHERITANCE + " property", e); 1215 } 1216 _save(node); 1217 } 1218 1219 1220 /* --------------- */ 1221 /* PRIVATE METHODS */ 1222 /* --------------- */ 1223 1224 private static void _checkLock(Node node) throws AmetysRepositoryException 1225 { 1226 try 1227 { 1228 if (node.isLocked()) 1229 { 1230 LockManager lockManager = node.getSession().getWorkspace().getLockManager(); 1231 1232 Lock lock = lockManager.getLock(node.getPath()); 1233 Node lockHolder = lock.getNode(); 1234 1235 lockManager.addLockToken(lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString()); 1236 } 1237 } 1238 catch (RepositoryException e) 1239 { 1240 throw new AmetysRepositoryException("Unable to add lock token on ACL node", e); 1241 } 1242 } 1243 1244 private static Node _getOrCreateACLNode(Node node) 1245 { 1246 try 1247 { 1248 if (node.hasNode(__NODE_NAME_ROOT_ACL)) 1249 { 1250 return node.getNode(__NODE_NAME_ROOT_ACL); 1251 } 1252 else 1253 { 1254 _checkLock(node); 1255 return node.addNode(__NODE_NAME_ROOT_ACL, __NODETYPE_ROOT_ACL); 1256 } 1257 } 1258 catch (RepositoryException e) 1259 { 1260 throw new AmetysRepositoryException("Error while getting root ACL node.", e); 1261 } 1262 } 1263 1264 private static Node _getACLNode(Node node) 1265 { 1266 try 1267 { 1268 if (node.hasNode(__NODE_NAME_ROOT_ACL)) 1269 { 1270 return node.getNode(__NODE_NAME_ROOT_ACL); 1271 } 1272 else 1273 { 1274 return null; 1275 } 1276 } 1277 catch (RepositoryException e) 1278 { 1279 throw new AmetysRepositoryException("Error while getting root ACL node.", e); 1280 } 1281 } 1282 1283 private static Node _getOrCreateUsersNode(Node node) 1284 { 1285 try 1286 { 1287 Node aclNode = _getOrCreateACLNode(node); 1288 if (aclNode.hasNode(__NODE_NAME_ACL_USERS)) 1289 { 1290 return aclNode.getNode(__NODE_NAME_ACL_USERS); 1291 } 1292 else 1293 { 1294 return aclNode.addNode(__NODE_NAME_ACL_USERS, __NODETYPE_UNSTRUCTURED); 1295 } 1296 } 1297 catch (RepositoryException e) 1298 { 1299 throw new AmetysRepositoryException("Error while getting 'users' ACL node.", e); 1300 } 1301 } 1302 1303 private static Node _getUserNode(Node node, UserIdentity user) 1304 { 1305 try 1306 { 1307 Node aclNode = _getACLNode(node); 1308 if (aclNode != null && aclNode.hasNode(__NODE_NAME_ACL_USERS)) 1309 { 1310 Node aclUsersNode = aclNode.getNode(__NODE_NAME_ACL_USERS); 1311 if (aclUsersNode.hasNode(user.getPopulationId())) 1312 { 1313 Node popNode = aclUsersNode.getNode(user.getPopulationId()); 1314 if (popNode.hasNode(user.getLogin())) 1315 { 1316 return popNode.getNode(user.getLogin()); 1317 } 1318 } 1319 } 1320 1321 return null; 1322 } 1323 catch (RepositoryException e) 1324 { 1325 throw new AmetysRepositoryException("Error while getting 'users' ACL node.", e); 1326 } 1327 } 1328 1329 private static Node _getUsersNode(Node node) 1330 { 1331 try 1332 { 1333 Node aclNode = _getACLNode(node); 1334 if (aclNode != null && aclNode.hasNode(__NODE_NAME_ACL_USERS)) 1335 { 1336 return aclNode.getNode(__NODE_NAME_ACL_USERS); 1337 } 1338 else 1339 { 1340 return null; 1341 } 1342 } 1343 catch (RepositoryException e) 1344 { 1345 throw new AmetysRepositoryException("Error while getting 'users' ACL node.", e); 1346 } 1347 } 1348 1349 private static Node _getOrCreateGroupsNode(Node node) 1350 { 1351 try 1352 { 1353 Node aclNode = _getOrCreateACLNode(node); 1354 if (aclNode.hasNode(__NODE_NAME_ACL_GROUPS)) 1355 { 1356 return aclNode.getNode(__NODE_NAME_ACL_GROUPS); 1357 } 1358 else 1359 { 1360 return aclNode.addNode(__NODE_NAME_ACL_GROUPS, __NODETYPE_UNSTRUCTURED); 1361 } 1362 } 1363 catch (RepositoryException e) 1364 { 1365 throw new AmetysRepositoryException("Error while getting 'groups' ACL node.", e); 1366 } 1367 } 1368 1369 private static Node _getGroupsNode(Node node) 1370 { 1371 try 1372 { 1373 Node aclNode = _getACLNode(node); 1374 if (aclNode != null && aclNode.hasNode(__NODE_NAME_ACL_GROUPS)) 1375 { 1376 return aclNode.getNode(__NODE_NAME_ACL_GROUPS); 1377 } 1378 else 1379 { 1380 return null; 1381 } 1382 } 1383 catch (RepositoryException e) 1384 { 1385 throw new AmetysRepositoryException("Error while getting 'groups' ACL node.", e); 1386 } 1387 } 1388 1389 private static Node _getOrCreateUserNode(Node node, UserIdentity userIdentity) 1390 { 1391 try 1392 { 1393 Node usersNode = _getOrCreateUsersNode(node); 1394 String population = userIdentity.getPopulationId(); 1395 String login = userIdentity.getLogin(); 1396 1397 if (usersNode.hasNode(population)) 1398 { 1399 Node populationNode = usersNode.getNode(population); 1400 if (populationNode.hasNode(login)) 1401 { 1402 return populationNode.getNode(login); 1403 } 1404 else 1405 { 1406 return populationNode.addNode(login, __NODETYPE_ACL_USER); 1407 } 1408 } 1409 else 1410 { 1411 return usersNode.addNode(population, __NODETYPE_UNSTRUCTURED).addNode(login, __NODETYPE_ACL_USER); 1412 } 1413 } 1414 catch (RepositoryException e) 1415 { 1416 throw new AmetysRepositoryException(String.format("Error while getting 'user' ACL node for %s.", userIdentity.toString()), e); 1417 } 1418 } 1419 1420 private static Node _getOrCreateGroupNode(Node node, GroupIdentity groupIdentity) 1421 { 1422 try 1423 { 1424 Node groupsNode = _getOrCreateGroupsNode(node); 1425 String directoryId = groupIdentity.getDirectoryId(); 1426 String id = Text.escapeIllegalJcrChars(groupIdentity.getId()); 1427 1428 if (groupsNode.hasNode(directoryId)) 1429 { 1430 Node populationNode = groupsNode.getNode(directoryId); 1431 if (populationNode.hasNode(id)) 1432 { 1433 return populationNode.getNode(id); 1434 } 1435 else 1436 { 1437 return populationNode.addNode(id, __NODETYPE_ACL_GROUP); 1438 } 1439 } 1440 else 1441 { 1442 return groupsNode.addNode(directoryId, __NODETYPE_UNSTRUCTURED).addNode(id, __NODETYPE_ACL_GROUP); 1443 } 1444 } 1445 catch (RepositoryException e) 1446 { 1447 throw new AmetysRepositoryException(String.format("Error while getting 'group' ACL node for %s.", groupIdentity.toString()), e); 1448 } 1449 } 1450 1451 private static Set<String> _getProperty(Node node, String propertyName) 1452 { 1453 try 1454 { 1455 Value[] values = node.getProperty(propertyName).getValues(); 1456 Set<String> result = new HashSet<>(); 1457 for (Value value : values) 1458 { 1459 result.add(value.getString()); 1460 } 1461 return result; 1462 } 1463 catch (PathNotFoundException e) 1464 { 1465 return new HashSet<>(); 1466 } 1467 catch (RepositoryException e) 1468 { 1469 throw new AmetysRepositoryException("Unable to get " + propertyName + " property", e); 1470 } 1471 } 1472 1473 private static void _setProperty(Node node, String propertyName, Set<String> profiles) 1474 { 1475 try 1476 { 1477 node.setProperty(propertyName, profiles.toArray(new String[profiles.size()])); 1478 } 1479 catch (RepositoryException e) 1480 { 1481 throw new AmetysRepositoryException("Unable to set " + propertyName + " property", e); 1482 } 1483 } 1484 1485 private static void _addProperty(Node node, String propertyName, String profileToAdd) 1486 { 1487 Set<String> profiles = _getProperty(node, propertyName); 1488 if (!profiles.contains(profileToAdd)) 1489 { 1490 profiles.add(profileToAdd); 1491 _setProperty(node, propertyName, profiles); 1492 } 1493 } 1494 1495 private static void _removeProperty(Node node, String propertyName, String profileToRemove) 1496 { 1497 Set<String> profiles = _getProperty(node, propertyName); 1498 if (profiles.contains(profileToRemove)) 1499 { 1500 profiles.remove(profileToRemove); 1501 _setProperty(node, propertyName, profiles); 1502 } 1503 } 1504 1505 private static void _save(Node node) 1506 { 1507 try 1508 { 1509 node.getSession().save(); 1510 } 1511 catch (RepositoryException e) 1512 { 1513 throw new AmetysRepositoryException("Unable to save changes", e); 1514 } 1515 } 1516 1517 /* ---------------------------------------*/ 1518 /* JCR EXPRESSIONS FOR PROFILES */ 1519 /* ---------------------------------------*/ 1520 1521 static class AllowedProfileExpression extends ACLProfileExpression 1522 { 1523 public AllowedProfileExpression (String ... profileIds) 1524 { 1525 super(__PROPERTY_NAME_ALLOWED_PROFILES, profileIds); 1526 } 1527 } 1528 1529 static class DeniedProfileExpression extends ACLProfileExpression 1530 { 1531 public DeniedProfileExpression (String ... profileIds) 1532 { 1533 super(__PROPERTY_NAME_DENIED_PROFILES, profileIds); 1534 } 1535 } 1536 1537 static class AnyConnectedDeniedProfileExpression extends ACLProfileExpression 1538 { 1539 public AnyConnectedDeniedProfileExpression (String ... profileIds) 1540 { 1541 super(__PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES, profileIds); 1542 } 1543 } 1544 1545 static class AnyConnectedAllowedProfileExpression extends ACLProfileExpression 1546 { 1547 public AnyConnectedAllowedProfileExpression (String ... profileIds) 1548 { 1549 super(__PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES, profileIds); 1550 } 1551 } 1552 1553 static class AnonymousDeniedProfileExpression extends ACLProfileExpression 1554 { 1555 public AnonymousDeniedProfileExpression (String ... profileIds) 1556 { 1557 super(__PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES, profileIds); 1558 } 1559 } 1560 1561 static class AnonymousAllowedProfileExpression extends ACLProfileExpression 1562 { 1563 public AnonymousAllowedProfileExpression (String ... profileIds) 1564 { 1565 super(__PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES, profileIds); 1566 } 1567 } 1568 1569 static class ACLProfileExpression implements Expression 1570 { 1571 private String[] _profileIds; 1572 private String _propertyName; 1573 1574 public ACLProfileExpression (String propertyName, String ... profileIds) 1575 { 1576 _propertyName = propertyName; 1577 _profileIds = profileIds; 1578 } 1579 1580 @Override 1581 public String build() 1582 { 1583 boolean isFirst = true; 1584 StringBuilder sb = new StringBuilder("("); 1585 1586 for (String profileId : _profileIds) 1587 { 1588 if (isFirst) 1589 { 1590 isFirst = false; 1591 } 1592 else 1593 { 1594 sb.append(LogicalOperator.OR.toString()); 1595 } 1596 1597 sb.append("@") 1598 .append(_propertyName) 1599 .append(Operator.EQ) 1600 .append("'").append(profileId).append("'"); 1601 } 1602 1603 if (isFirst) 1604 { 1605 return ""; 1606 } 1607 else 1608 { 1609 return sb.append(")").toString(); 1610 } 1611 } 1612 } 1613}