001/* 002 * Copyright 2016 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.core.right; 017 018import java.io.InputStream; 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Map.Entry; 025import java.util.Optional; 026import java.util.Set; 027import java.util.stream.Collectors; 028 029import org.apache.avalon.framework.CascadingRuntimeException; 030import org.apache.avalon.framework.component.Component; 031import org.apache.avalon.framework.configuration.Configurable; 032import org.apache.avalon.framework.configuration.Configuration; 033import org.apache.avalon.framework.configuration.ConfigurationException; 034import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 035import org.apache.avalon.framework.context.Context; 036import org.apache.avalon.framework.context.ContextException; 037import org.apache.avalon.framework.context.Contextualizable; 038import org.apache.avalon.framework.service.ServiceException; 039import org.apache.avalon.framework.service.ServiceManager; 040import org.apache.avalon.framework.service.Serviceable; 041import org.apache.avalon.framework.thread.ThreadSafe; 042import org.apache.cocoon.components.ContextHelper; 043import org.apache.cocoon.environment.Request; 044import org.apache.commons.lang3.StringUtils; 045import org.apache.excalibur.source.Source; 046import org.apache.excalibur.source.SourceResolver; 047 048import org.ametys.core.group.GroupDirectoryDAO; 049import org.ametys.core.group.GroupIdentity; 050import org.ametys.core.group.GroupManager; 051import org.ametys.core.right.AccessController.AccessResult; 052import org.ametys.core.user.CurrentUserProvider; 053import org.ametys.core.user.UserIdentity; 054import org.ametys.core.user.UserManager; 055import org.ametys.core.user.population.PopulationContextHelper; 056import org.ametys.core.user.population.UserPopulationDAO; 057import org.ametys.runtime.i18n.I18nizableText; 058import org.ametys.runtime.plugin.component.AbstractLogEnabled; 059 060/** 061 * Abstraction for testing a right associated with a resource and a user from a single source. 062 */ 063public class RightManager extends AbstractLogEnabled implements Serviceable, Configurable, ThreadSafe, Component, Contextualizable 064{ 065 /** For avalon service manager */ 066 public static final String ROLE = RightManager.class.getName(); 067 /** The id of the READER profile */ 068 public static final String READER_PROFILE_ID = "READER"; 069 /** The id of the READER profile */ 070 public static final String CACHE_REQUEST_ATTRIBUTE_NAME = RightManager.class.getName() + "$Cache"; 071 072 /** The instance of ObjectUserIdentity for anonymous */ 073 protected static final UserIdentity __ANONYMOUS_USER_IDENTITY = null; 074 /** The instance of ObjectUserIdentity for any connected user */ 075 protected static final UserIdentity __ANY_CONNECTED_USER_IDENTITY = new UserIdentity(null, null); 076 077 /** 078 * This first cache is for right result on non-null contexts when calling {@link #hasRight(UserIdentity, String, Object)} 079 * 080 * { 081 * UserIdentity : 082 * { 083 * RightId : 084 * { 085 * Context : RightResult 086 * } 087 * } 088 * } 089 */ 090 private static final String CACHE_1 = RightManager.class.getName() + "$Cache-1"; 091 /** 092 * This second cache is for right result on null contexts when calling {@link #hasRight(UserIdentity, String, Object)} 093 * 094 * { 095 * UserIdentity : 096 * { 097 * RightId : 098 * { 099 * WorkspaceContexts : RightResult 100 * } 101 * } 102 * } 103 */ 104 private static final String CACHE_2 = RightManager.class.getName() + "$Cache-2"; 105 106 /** Avalon ServiceManager */ 107 protected ServiceManager _manager; 108 /** Avalon SourceResolver */ 109 protected SourceResolver _resolver; 110 /** The rights' list container */ 111 protected RightsExtensionPoint _rightsEP; 112 /** The extension point for the Right Context Convertors */ 113 protected RightContextConvertorExtensionPoint _rightContextConvertorEP; 114 /** The extension point for Access Controllers */ 115 protected AccessControllerExtensionPoint _accessControllerEP; 116 /** The user manager */ 117 protected UserManager _userManager; 118 /** The group manager */ 119 protected GroupManager _groupManager; 120 /** The DAO for user populations */ 121 protected UserPopulationDAO _userPopulationDAO; 122 /** The DAO for group directories */ 123 protected GroupDirectoryDAO _groupDirectoryDAO; 124 /** The current user provider */ 125 protected CurrentUserProvider _currentUserProvider; 126 /** The rights DAO */ 127 protected RightProfilesDAO _profilesDAO; 128 129 private Context _context; 130 131 /** 132 * Enumeration of all possible values returned by hasRight(user, right, context) 133 */ 134 public enum RightResult 135 { 136 /** 137 * Indicates that a given user has the required right. 138 */ 139 RIGHT_ALLOW, 140 141 /** 142 * Indicates that a given user does NOT have the required right. 143 */ 144 RIGHT_DENY, 145 146 /** 147 * Indicates that the system knows nothing about the fact that a given user has a right or not. 148 */ 149 RIGHT_UNKNOWN; 150 } 151 152 @Override 153 public void contextualize(Context context) throws ContextException 154 { 155 _context = context; 156 } 157 158 @Override 159 public void service(ServiceManager manager) throws ServiceException 160 { 161 _manager = manager; 162 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 163 _groupManager = (GroupManager) manager.lookup(GroupManager.ROLE); 164 _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE); 165 _groupDirectoryDAO = (GroupDirectoryDAO) manager.lookup(GroupDirectoryDAO.ROLE); 166 _rightsEP = (RightsExtensionPoint) manager.lookup(RightsExtensionPoint.ROLE); 167 _rightContextConvertorEP = (RightContextConvertorExtensionPoint) manager.lookup(RightContextConvertorExtensionPoint.ROLE); 168 _accessControllerEP = (AccessControllerExtensionPoint) manager.lookup(AccessControllerExtensionPoint.ROLE); 169 _resolver = (SourceResolver) _manager.lookup(SourceResolver.ROLE); 170 _currentUserProvider = (CurrentUserProvider) _manager.lookup(CurrentUserProvider.ROLE); 171 } 172 173 /** 174 * Returns the DAO for profiles 175 * @return The DAO 176 */ 177 protected RightProfilesDAO _getProfileDAO () 178 { 179 try 180 { 181 if (_profilesDAO == null) 182 { 183 _profilesDAO = (RightProfilesDAO) _manager.lookup(RightProfilesDAO.ROLE); 184 } 185 return _profilesDAO; 186 } 187 catch (ServiceException e) 188 { 189 throw new RuntimeException("Failed to retrieve the DAO for profiles", e); 190 } 191 } 192 193 @Override 194 public void configure(Configuration configuration) throws ConfigurationException 195 { 196 Configuration rightsConfiguration = configuration.getChild("rights"); 197 198 String externalFile = rightsConfiguration.getAttribute("config", null); 199 if (externalFile != null) 200 { 201 Source source = null; 202 try 203 { 204 source = _resolver.resolveURI("context://" + externalFile); 205 206 if (source.exists()) 207 { 208 Configuration externalConfiguration; 209 try (InputStream is = source.getInputStream();) 210 { 211 externalConfiguration = new DefaultConfigurationBuilder().build(is); 212 } 213 214 configureRights(externalConfiguration); 215 } 216 else if (getLogger().isInfoEnabled()) 217 { 218 getLogger().info("The optional external rights file '" + externalFile + "' is missing."); 219 } 220 } 221 catch (Exception e) 222 { 223 String message = "An error occured while retriving external file '" + externalFile + "'"; 224 getLogger().error(message, e); 225 throw new ConfigurationException(message, configuration, e); 226 } 227 finally 228 { 229 if (source != null) 230 { 231 _resolver.release(source); 232 } 233 } 234 } 235 else 236 { 237 configureRights(rightsConfiguration); 238 } 239 } 240 241 private void configureRights(Configuration configuration) throws ConfigurationException 242 { 243 Configuration[] rights = configuration.getChildren("right"); 244 for (Configuration rightConf : rights) 245 { 246 String id = rightConf.getAttribute("id", ""); 247 248 String label = rightConf.getChild("label").getValue(""); 249 I18nizableText i18nLabel = new I18nizableText("application", label); 250 251 String description = rightConf.getChild("description").getValue(""); 252 I18nizableText i18nDescription = new I18nizableText("application", description); 253 254 String category = rightConf.getChild("category").getValue(""); 255 I18nizableText i18nCategory = new I18nizableText("application", category); 256 257 if (id.length() == 0 || label.length() == 0 || description.length() == 0 || category.length() == 0) 258 { 259 String message = "Error in " + RightManager.class.getName() + " configuration: attribute 'id' and elements 'label', 'description' and 'category' are mandatory."; 260 getLogger().error(message); 261 throw new ConfigurationException(message, configuration); 262 } 263 264 _rightsEP.addRight(id, i18nLabel, i18nDescription, i18nCategory); 265 } 266 } 267 268 /* --------- */ 269 /* HAS RIGHT */ 270 /* --------- */ 271 272 /** 273 * Checks a permission for the current logged user, on a given object (or context).<br> 274 * If null, it checks if there is at least one object with this permission 275 * @param rightId The name of the right to check. Cannot be null. 276 * @param object The object to check the right. Can be null to search on any object. 277 * @return {@link RightResult#RIGHT_ALLOW}, {@link RightResult#RIGHT_DENY} or {@link RightResult#RIGHT_UNKNOWN} 278 * @throws RightsException if an error occurs. 279 */ 280 public RightResult currentUserHasRight(String rightId, Object object) throws RightsException 281 { 282 return hasRight(_currentUserProvider.getUser(), rightId, object); 283 } 284 285 /** 286 * Checks a permission for a user, on a given object (or context).<br> 287 * If null, it checks if there is at least one object with this permission 288 * @param userIdentity The user identity. Can be null for anonymous 289 * @param rightId The name of the right to check. Cannot be null. 290 * @param object The object to check the right. Can be null to search on any object. 291 * @return {@link RightResult#RIGHT_ALLOW}, {@link RightResult#RIGHT_DENY} or {@link RightResult#RIGHT_UNKNOWN} 292 * @throws RightsException if an error occurs. 293 */ 294 public RightResult hasRight(UserIdentity userIdentity, String rightId, Object object) throws RightsException 295 { 296 UserIdentity objectUserIdentity = userIdentity == null ? __ANONYMOUS_USER_IDENTITY : userIdentity; 297 298 return _hasRight(objectUserIdentity, rightId, object); 299 } 300 301 /** 302 * Gets the right result for anonymous with given right on given object context 303 * @param rightId The id of the right 304 * @param object The object to check 305 * @return the right result for anonymous with given profile on given object context 306 */ 307 public RightResult hasAnonymousRight(String rightId, Object object) 308 { 309 return _hasRight(__ANONYMOUS_USER_IDENTITY, rightId, object); 310 } 311 312 /** 313 * Gets the right result for any connected user with given profile on given object context 314 * @param rightId The right id to test 315 * @param object The object to check 316 * @return the right result for any connected user with given profile on given object context 317 */ 318 public RightResult hasAnyConnectedUserRight(String rightId, Object object) 319 { 320 return _hasRight(__ANY_CONNECTED_USER_IDENTITY, rightId, object); 321 } 322 323 private RightResult _hasRight(UserIdentity userIdentity, String rightId, Object object) 324 { 325 getLogger().debug("Try to determine if user {} has the right '{}' on the object context {}", userIdentity, rightId, object); 326 327 if (StringUtils.isBlank(rightId)) 328 { 329 throw new RightsException("The rightId cannot be null"); 330 } 331 332 return _hasRightOrRead(userIdentity, rightId, object); 333 } 334 335 private RightResult _hasRightOrRead(UserIdentity userIdentity, String rightId, Object object) 336 { 337 if (object == null) 338 { 339 return _hasRightOrRead(userIdentity, rightId); 340 } 341 342 // Try to retrieve in first cache (the one which manages non-null contexts) 343 RightResult cacheResult = _hasRightResultInFirstCache(userIdentity, rightId, object); 344 if (cacheResult != null) 345 { 346 return cacheResult; 347 } 348 349 // Retrieve groups the user belongs to 350 Set<GroupIdentity> groups = _getGroups(userIdentity); 351 352 // Get the objects to check 353 Set<Object> objects = _getConvertedObjects(object, new HashSet<>()); 354 355 // Retrieve the set of AccessResult 356 Set<AccessResult> accessResults = _getAccessResults(userIdentity, groups, rightId, objects); 357 358 // Compute access 359 AccessResult access = AccessResult.merge(accessResults); 360 361 RightResult rightResult = access.toRightResult(); 362 _putInFirstCache(userIdentity, rightId, object, rightResult); 363 364 return rightResult; 365 } 366 367 /** 368 * Has the user/anonymous/anyconnected the non null right on any content of the current workspace? 369 * @param userIdentity The user connecter or the value for anonymous or any connected user 370 * @param rightId The right id to test. Can be null to test read access 371 * @return The computed right result 372 */ 373 private RightResult _hasRightOrRead(UserIdentity userIdentity, String rightId) 374 { 375 // Resolve contexts 376 Set<Object> workspacesContexts = _getConvertedObjects("/${WorkspaceName}", new HashSet<>()); 377 378 // Try to retrieve in second cache (the one which manages null context) 379 RightResult cacheResult = _hasRightResultInSecondCache(workspacesContexts, userIdentity, rightId); 380 if (cacheResult != null) 381 { 382 return cacheResult; 383 } 384 385 // Retrieve groups the user belongs to 386 Set<GroupIdentity> groups = _getGroups(userIdentity); 387 388 RightResult rightResult = RightResult.RIGHT_UNKNOWN; 389 for (String controllerId : _accessControllerEP.getExtensionsIds()) 390 { 391 AccessController accessController = _accessControllerEP.getExtension(controllerId); 392 try 393 { 394 if (userIdentity == __ANONYMOUS_USER_IDENTITY && (rightId == null ? accessController.hasAnonymousAnyReadAccessPermissionOnWorkspace(workspacesContexts) : accessController.hasAnonymousAnyPermissionOnWorkspace(workspacesContexts, rightId)) 395 || userIdentity == __ANY_CONNECTED_USER_IDENTITY && (rightId == null ? accessController.hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(workspacesContexts) : accessController.hasAnyConnectedUserAnyPermissionOnWorkspace(workspacesContexts, rightId)) 396 || (rightId == null ? accessController.hasUserAnyReadAccessPermissionOnWorkspace(workspacesContexts, userIdentity, groups) : accessController.hasUserAnyPermissionOnWorkspace(workspacesContexts, userIdentity, groups, rightId))) 397 { 398 rightResult = RightResult.RIGHT_ALLOW; 399 break; 400 } 401 } 402 catch (Exception e) 403 { 404 getLogger().error("An error occured with controller '{}'. Thus, this controller will be ignored.", controllerId, e); 405 } 406 } 407 408 getLogger().debug("Right result found for [{}, {}, {}] => {}", workspacesContexts, userIdentity, rightId, rightResult); 409 _putInSecondCache(workspacesContexts, userIdentity, rightId, rightResult); 410 return rightResult; 411 } 412 413 private Set<AccessResult> _getAccessResults(UserIdentity userIdentity, Set<GroupIdentity> groups, String rightId, Set<Object> objects) 414 { 415 Set<AccessResult> accessResults = new HashSet<>(); 416 for (Object obj : objects) 417 { 418 for (String controllerId : _accessControllerEP.getExtensionsIds()) 419 { 420 AccessController accessController = _accessControllerEP.getExtension(controllerId); 421 try 422 { 423 if (accessController.isSupported(obj)) 424 { 425 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 426 { 427 accessResults.add(rightId == null ? accessController.getReadAccessPermissionForAnonymous(obj) : accessController.getPermissionForAnonymous(rightId, obj)); 428 } 429 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 430 { 431 accessResults.add(rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(obj) : accessController.getPermissionForAnyConnectedUser(rightId, obj)); 432 } 433 else 434 { 435 accessResults.add(rightId == null ? accessController.getReadAccessPermission(userIdentity, groups, obj) : accessController.getPermission(userIdentity, groups, rightId, obj)); 436 } 437 } 438 else 439 { 440 accessResults.add(AccessResult.UNKNOWN); 441 } 442 } 443 catch (Exception e) 444 { 445 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", controllerId, obj, e); 446 } 447 } 448 } 449 450 return accessResults; 451 } 452 453 /* --------------- */ 454 /* HAS READ ACCESS */ 455 /* --------------- */ 456 457 /** 458 * Returns true if the current user has READ access on the given object 459 * @param object The object to check the right. Can be null to search on any object. 460 * @return true if the given user has READ access on the given object 461 */ 462 public boolean currentUserHasReadAccess(Object object) 463 { 464 return hasReadAccess(_currentUserProvider.getUser(), object); 465 } 466 467 /** 468 * Returns true if the given user has READ access on the given object 469 * @param userIdentity The user identity. Cannot be null. 470 * @param object The object to check the right. Can be null to search on any object. 471 * @return true if the given user has READ access on the given object 472 */ 473 public boolean hasReadAccess(UserIdentity userIdentity, Object object) 474 { 475 UserIdentity objectUserIdentity = userIdentity == null ? __ANONYMOUS_USER_IDENTITY : userIdentity; 476 477 return _hasRightOrRead(objectUserIdentity, null, object) == RightResult.RIGHT_ALLOW; 478 } 479 480 /** 481 * Returns true if the object is not restricted, i.e. an anonymous user has READ access (is allowed) on the object 482 * @param object The object to check. Cannot be null 483 * @return true if the object is restricted, i.e. an anonymous user has READ access (is allowed) on the object 484 */ 485 public boolean hasAnonymousReadAccess(Object object) 486 { 487 return _hasRightOrRead(__ANONYMOUS_USER_IDENTITY, null, object) == RightResult.RIGHT_ALLOW; 488 } 489 490 /** 491 * Returns true if any connected user has READ access allowed on the object 492 * @param object The object to check. Cannot be null 493 * @return true if any connected user has READ access allowed on the object 494 */ 495 public boolean hasAnyConnectedUserReadAccess(Object object) 496 { 497 return _hasRightOrRead(__ANY_CONNECTED_USER_IDENTITY, null, object) == RightResult.RIGHT_ALLOW; 498 } 499 500 /* ------------- */ 501 /* ALLOWED USERS */ 502 /* ------------- */ 503 504 /** 505 * Get the list of users that have a particular right in a particular context. 506 * @param rightId The name of the right to check. Cannot be null. 507 * @param object The object to check the right. Cannot be null. 508 * @return The list of users allowed with that right as a Set of String (user identities). 509 * @throws RightsException if an error occurs. 510 */ 511 public AllowedUsers getAllowedUsers(String rightId, Object object) 512 { 513 if (StringUtils.isBlank(rightId)) 514 { 515 throw new RightsException("The rightId cannot be null"); 516 } 517 518 return _getAllowedUsers(rightId, object); 519 } 520 521 /** 522 * Get the users with a READ access on given object 523 * @param object The object 524 * @return The representation of allowed users 525 */ 526 public AllowedUsers getReadAccessAllowedUsers(Object object) 527 { 528 return _getAllowedUsers(null, object); 529 } 530 531 private AllowedUsers _getAllowedUsers(String rightId, Object object) 532 { 533 Optional.ofNullable(object).orElseThrow(() -> 534 { 535 return new RightsException("The object cannot be null"); 536 }); 537 538 // Get the objects to check 539 Set<Object> objects = _getConvertedObjects(object, new HashSet<>()); 540 541 // For each object, retrieve the allowed and denied users/groups 542 Boolean isAnyConnectedAllowed = null; // unknown 543 Set<UserIdentity> allAllowedUsers = new HashSet<>(); 544 Set<UserIdentity> allDeniedUsers = new HashSet<>(); 545 Set<GroupIdentity> allAllowedGroups = new HashSet<>(); 546 Set<GroupIdentity> allDeniedGroups = new HashSet<>(); 547 548 for (Object obj : objects) 549 { 550 for (String controllerId : _accessControllerEP.getExtensionsIds()) 551 { 552 AccessController accessController = _accessControllerEP.getExtension(controllerId); 553 try 554 { 555 if (accessController.isSupported(obj)) 556 { 557 if ((rightId == null ? accessController.getReadAccessPermissionForAnonymous(obj) : accessController.getPermissionForAnonymous(rightId, obj)) == AccessResult.ANONYMOUS_ALLOWED) 558 { 559 // Any anonymous user is allowed 560 return new AllowedUsers(true, false, null, null, null, null, _userManager, _groupManager, null); 561 } 562 563 AccessResult permissionForAnyConnectedUser = rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(obj) : accessController.getPermissionForAnyConnectedUser(rightId, obj); 564 if (permissionForAnyConnectedUser == AccessResult.ANY_CONNECTED_DENIED) 565 { 566 // For having any connected user allowed, you need to not have the denied access for one object 567 isAnyConnectedAllowed = Boolean.FALSE; 568 } 569 else if (isAnyConnectedAllowed == null && permissionForAnyConnectedUser == AccessResult.ANY_CONNECTED_ALLOWED) 570 { 571 isAnyConnectedAllowed = Boolean.TRUE; 572 } 573 574 Map<UserIdentity, AccessResult> permissionsByUser = rightId == null ? accessController.getReadAccessPermissionByUser(obj) : accessController.getPermissionByUser(rightId, obj); 575 576 Set<UserIdentity> allowedUsersOnObj = permissionsByUser.entrySet().stream() 577 .filter(entry -> AccessResult.USER_ALLOWED.equals(entry.getValue())) 578 .map(Entry::getKey) 579 .collect(Collectors.toSet()); 580 allAllowedUsers.addAll(allowedUsersOnObj); 581 582 Set<UserIdentity> deniedUsersOnObj = permissionsByUser.entrySet().stream() 583 .filter(entry -> AccessResult.USER_DENIED.equals(entry.getValue())) 584 .map(Entry::getKey) 585 .collect(Collectors.toSet()); 586 allDeniedUsers.addAll(deniedUsersOnObj); 587 588 589 Map<GroupIdentity, AccessResult> permissionsByGroup = rightId == null ? accessController.getReadAccessPermissionByGroup(obj) : accessController.getPermissionByGroup(rightId, obj); 590 591 Set<GroupIdentity> allowedGroupsOnObj = permissionsByGroup.entrySet().stream() 592 .filter(entry -> AccessResult.GROUP_ALLOWED.equals(entry.getValue())) 593 .map(Entry::getKey) 594 .collect(Collectors.toSet()); 595 allAllowedGroups.addAll(allowedGroupsOnObj); 596 597 Set<GroupIdentity> deniedGroupsOnObj = permissionsByGroup.entrySet().stream() 598 .filter(entry -> AccessResult.GROUP_DENIED.equals(entry.getValue())) 599 .map(Entry::getKey) 600 .collect(Collectors.toSet()); 601 allDeniedGroups.addAll(deniedGroupsOnObj); 602 } 603 } 604 catch (Exception e) 605 { 606 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", controllerId, obj, e); 607 } 608 } 609 } 610 611 Request request = ContextHelper.getRequest(_context); 612 @SuppressWarnings("unchecked") 613 List<String> populationContexts = (List<String>) request.getAttribute(PopulationContextHelper.POPULATION_CONTEXTS_REQUEST_ATTR); 614 615 // Then, return the AllowedUsers object 616 return new AllowedUsers(false, isAnyConnectedAllowed != null && isAnyConnectedAllowed.booleanValue(), allAllowedUsers, allDeniedUsers, allAllowedGroups, allDeniedGroups, _userManager, _groupManager, populationContexts != null ? new HashSet<>(populationContexts) : new HashSet<>()); 617 } 618 619 /* --------------- */ 620 /* GET USER RIGHTS */ 621 /* --------------- */ 622 623 /** 624 * Get the list of rights a user is allowed, on a particular object. 625 * @param userIdentity the user identity. Cannot be null. 626 * @param object The object to check the right. Cannot be null. 627 * @return The list of rights as a Set of String (id). 628 * @throws RightsException if an error occurs. 629 */ 630 public Set<String> getUserRights(UserIdentity userIdentity, Object object) throws RightsException 631 { 632 if (userIdentity == null) 633 { 634 throw new RightsException("The userIdentity cannot be null"); 635 } 636 else if (object == null) 637 { 638 throw new RightsException("The object cannot be null"); 639 } 640 641 // Get the objects to check 642 Set<Object> objects = _getConvertedObjects(object, new HashSet<>()); 643 644 // Retrieve groups the user belongs to 645 Set<GroupIdentity> groups = _groupManager.getUserGroups(userIdentity); 646 647 // Gets the access by rights 648 Map<String, AccessResult> accessResultsByRight = _getAccessResultByRight(userIdentity, groups, objects); 649 650 // Keep only positive rights 651 Set<String> allowedRights = accessResultsByRight.entrySet().stream().filter(entry -> entry.getValue().toRightResult() == RightResult.RIGHT_ALLOW).map(entry -> entry.getKey()).collect(Collectors.toSet()); 652 return allowedRights; 653 } 654 655 private Map<String, AccessResult> _getAccessResultByRight(UserIdentity userIdentity, Set<GroupIdentity> groups, Set<Object> objects) 656 { 657 Map<String, AccessResult> result = new HashMap<>(); 658 659 for (Object obj : objects) 660 { 661 for (String controllerId : _accessControllerEP.getExtensionsIds()) 662 { 663 AccessController accessController = _accessControllerEP.getExtension(controllerId); 664 try 665 { 666 if (accessController.isSupported(obj)) 667 { 668 // Update the result map 669 Map<String, AccessResult> permissionsByRight = accessController.getPermissionByRight(userIdentity, groups, obj); 670 for (String rightId : permissionsByRight.keySet()) 671 { 672 result.put(rightId, AccessResult.merge(result.get(rightId), permissionsByRight.get(rightId))); 673 } 674 } 675 } 676 catch (Exception e) 677 { 678 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", controllerId, obj, e); 679 } 680 } 681 } 682 683 return result; 684 } 685 686 /* ------- */ 687 /* PRIVATE */ 688 /* ------- */ 689 690 private Set<Object> _getConvertedObjects(Object object, Set<Object> alreadyHandledObjects) 691 { 692 Set<Object> finalObjects = new HashSet<>(); 693 694 if (!alreadyHandledObjects.contains(object)) 695 { 696 alreadyHandledObjects.add(object); 697 698 Set<Object> objects = _rightContextConvertorEP.getExtensionsIds().stream() 699 .map(_rightContextConvertorEP::getExtension) 700 .flatMap(convertor -> convertor.convert(object).stream()) 701 .collect(Collectors.toSet()); 702 703 finalObjects.addAll(objects); 704 finalObjects.add(object); 705 706 for (Object convertedObject : objects) 707 { 708 finalObjects.addAll(_getConvertedObjects(convertedObject, alreadyHandledObjects)); 709 } 710 } 711 712 return finalObjects; 713 } 714 715 private Set<GroupIdentity> _getGroups(UserIdentity userIdentity) 716 { 717 if (userIdentity == __ANONYMOUS_USER_IDENTITY || userIdentity == __ANY_CONNECTED_USER_IDENTITY) 718 { 719 return Collections.EMPTY_SET; 720 } 721 else 722 { 723 Set<GroupIdentity> userGroups = _groupManager.getUserGroups(userIdentity); 724 return userGroups; 725 } 726 } 727 728 729 730 private RightResult _hasRightResultInFirstCache(UserIdentity userIdentity, String rightId, Object object) 731 { 732 @SuppressWarnings("unchecked") 733 Map<UserIdentity, Map<String, Map<Object, RightResult>>> mapCache = getCache(CACHE_1, false); 734 if (mapCache != null) 735 { 736 if (mapCache.containsKey(userIdentity)) 737 { 738 Map<String, Map<Object, RightResult>> mapRight = mapCache.get(userIdentity); 739 if (mapRight.containsKey(rightId)) 740 { 741 Map<Object, RightResult> mapContext = mapRight.get(rightId); 742 if (mapContext.containsKey(object)) 743 { 744 RightResult cacheResult = mapContext.get(object); 745 getLogger().debug("Find entry in cache for [{}, {}, {}] => {}", userIdentity, rightId, object, cacheResult); 746 return cacheResult; 747 } 748 } 749 } 750 } 751 752 getLogger().debug("Did not find entry in cache for [{}, {}, {}]", userIdentity, rightId, object); 753 return null; 754 } 755 756 private void _putInFirstCache(UserIdentity userIdentity, String rightId, Object object, RightResult rightResult) 757 { 758 @SuppressWarnings("unchecked") 759 Map<UserIdentity, Map<String, Map<Object, RightResult>>> mapCache = getCache(CACHE_1, true); 760 if (mapCache != null) 761 { 762 if (!mapCache.containsKey(userIdentity)) 763 { 764 mapCache.put(userIdentity, new HashMap<>()); 765 } 766 Map<String, Map<Object, RightResult>> mapRight = mapCache.get(userIdentity); 767 768 if (!mapRight.containsKey(rightId)) 769 { 770 mapRight.put(rightId, new HashMap<>()); 771 } 772 Map<Object, RightResult> mapContext = mapRight.get(rightId); 773 774 mapContext.put(object, rightResult); 775 } 776 } 777 778 private RightResult _hasRightResultInSecondCache(Set<Object> workspacesContexts, UserIdentity userIdentity, String rightId) 779 { 780 @SuppressWarnings("unchecked") 781 Map<UserIdentity, Map<String, Map<Object, RightResult>>> mapCache = getCache(CACHE_2, false); 782 if (mapCache != null) 783 { 784 if (mapCache.containsKey(userIdentity)) 785 { 786 Map<String, Map<Object, RightResult>> mapRight = mapCache.get(userIdentity); 787 if (mapRight.containsKey(rightId)) 788 { 789 Map<Object, RightResult> resultPerContext = mapRight.get(rightId); 790 if (resultPerContext.containsKey(workspacesContexts)) 791 { 792 RightResult cacheResult = resultPerContext.get(workspacesContexts); 793 getLogger().debug("Find entry in cache2 for [{}, {}, {}] => {}", workspacesContexts, userIdentity, rightId, cacheResult); 794 return cacheResult; 795 } 796 } 797 } 798 } 799 800 getLogger().debug("Did not find entry in cache2 for [{}, {}, {}]", workspacesContexts, userIdentity, rightId); 801 return null; 802 } 803 804 private void _putInSecondCache(Set<Object> workspacesContexts, UserIdentity userIdentity, String rightId, RightResult rightResult) 805 { 806 @SuppressWarnings("unchecked") 807 Map<UserIdentity, Map<String, Map<Object, RightResult>>> mapCache = getCache(CACHE_2, true); 808 if (mapCache != null) 809 { 810 if (!mapCache.containsKey(userIdentity)) 811 { 812 mapCache.put(userIdentity, new HashMap<>()); 813 } 814 Map<String, Map<Object, RightResult>> mapRights = mapCache.get(userIdentity); 815 816 if (!mapRights.containsKey(rightId)) 817 { 818 mapRights.put(rightId, new HashMap<>()); 819 } 820 Map<Object, RightResult> mapResult = mapRights.get(rightId); 821 822 mapResult.put(workspacesContexts, rightResult); 823 } 824 } 825 826 /** 827 * Get the RightManager cache. Use this to store your information on rights 828 * @param cacheKey The cache key 829 * @param createIfUnexisting Creates a new HashMap if the cache does not exists yet 830 * @return The existing cache. Can be null if there is no cache and createIfUnexisting is false, but can also be null if there is no running request to store the cache 831 */ 832 public Map getCache(String cacheKey, boolean createIfUnexisting) 833 { 834 Request request; 835 try 836 { 837 request = ContextHelper.getRequest(_context); 838 } 839 catch (CascadingRuntimeException e) 840 { 841 return null; 842 } 843 844 if (request == null) 845 { 846 return null; 847 } 848 849 @SuppressWarnings("unchecked") 850 Map<String, Map> cache = (Map<String, Map>) request.getAttribute(CACHE_REQUEST_ATTRIBUTE_NAME); 851 if (cache == null) 852 { 853 if (!createIfUnexisting) 854 { 855 return null; 856 } 857 858 cache = new HashMap<>(); 859 request.setAttribute(CACHE_REQUEST_ATTRIBUTE_NAME, cache); 860 } 861 862 Map mapCache = cache.get(cacheKey); 863 if (mapCache == null && createIfUnexisting) 864 { 865 mapCache = new HashMap<>(); 866 cache.put(cacheKey, mapCache); 867 } 868 return mapCache; 869 } 870}