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.ArrayList; 020import java.util.Collections; 021import java.util.Enumeration; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.Optional; 028import java.util.Set; 029import java.util.stream.Collectors; 030 031import org.apache.avalon.framework.activity.Initializable; 032import org.apache.avalon.framework.component.Component; 033import org.apache.avalon.framework.configuration.Configurable; 034import org.apache.avalon.framework.configuration.Configuration; 035import org.apache.avalon.framework.configuration.ConfigurationException; 036import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 037import org.apache.avalon.framework.context.Context; 038import org.apache.avalon.framework.context.ContextException; 039import org.apache.avalon.framework.context.Contextualizable; 040import org.apache.avalon.framework.service.ServiceException; 041import org.apache.avalon.framework.service.ServiceManager; 042import org.apache.avalon.framework.service.Serviceable; 043import org.apache.avalon.framework.thread.ThreadSafe; 044import org.apache.cocoon.components.ContextHelper; 045import org.apache.cocoon.environment.Request; 046import org.apache.commons.lang3.StringUtils; 047import org.apache.excalibur.source.Source; 048import org.apache.excalibur.source.SourceResolver; 049 050import org.ametys.core.cache.AbstractCacheManager; 051import org.ametys.core.cache.Cache; 052import org.ametys.core.group.GroupDirectoryDAO; 053import org.ametys.core.group.GroupIdentity; 054import org.ametys.core.group.GroupManager; 055import org.ametys.core.right.AccessController.AccessResult; 056import org.ametys.core.right.AccessController.ExplanationObject; 057import org.ametys.core.right.AccessController.Permission; 058import org.ametys.core.user.CurrentUserProvider; 059import org.ametys.core.user.UserIdentity; 060import org.ametys.core.user.UserManager; 061import org.ametys.core.user.population.PopulationContextHelper; 062import org.ametys.core.user.population.UserPopulationDAO; 063import org.ametys.plugins.core.impl.cache.AbstractCacheKey; 064import org.ametys.runtime.i18n.I18nizableText; 065import org.ametys.runtime.plugin.component.AbstractLogEnabled; 066 067/** 068 * Abstraction for testing a right associated with a resource and a user from a single source. 069 */ 070public class RightManager extends AbstractLogEnabled implements Serviceable, Configurable, ThreadSafe, Component, Contextualizable, Initializable 071{ 072 /** For avalon service manager */ 073 public static final String ROLE = RightManager.class.getName(); 074 /** The id of the READER profile */ 075 public static final String READER_PROFILE_ID = "READER"; 076 077 /** The instance of ObjectUserIdentity for anonymous */ 078 protected static final UserIdentity __ANONYMOUS_USER_IDENTITY = new UserIdentity(null, null); 079 /** The instance of ObjectUserIdentity for any connected user */ 080 protected static final UserIdentity __ANY_CONNECTED_USER_IDENTITY = new UserIdentity("", ""); 081 082 private static final String CACHE_1 = RightManager.class.getName() + "$Cache-1"; 083 private static final String CACHE_2 = RightManager.class.getName() + "$Cache-2"; 084 085 /** Avalon ServiceManager */ 086 protected ServiceManager _manager; 087 /** Avalon SourceResolver */ 088 protected SourceResolver _resolver; 089 /** The rights' list container */ 090 protected RightsExtensionPoint _rightsEP; 091 /** The extension point for the Right Context Convertors */ 092 protected RightContextConvertorExtensionPoint _rightContextConvertorEP; 093 /** The extension point for Access Controllers */ 094 protected AccessControllerExtensionPoint _accessControllerEP; 095 /** The user manager */ 096 protected UserManager _userManager; 097 /** The group manager */ 098 protected GroupManager _groupManager; 099 /** The DAO for user populations */ 100 protected UserPopulationDAO _userPopulationDAO; 101 /** The DAO for group directories */ 102 protected GroupDirectoryDAO _groupDirectoryDAO; 103 /** The current user provider */ 104 protected CurrentUserProvider _currentUserProvider; 105 /** The rights DAO */ 106 protected RightProfilesDAO _profilesDAO; 107 /** Cache Manager */ 108 protected AbstractCacheManager _cacheManager; 109 110 private Context _context; 111 private List<String> _webinfRights = new ArrayList<>(); 112 113 /** 114 * Enumeration of all possible values returned by hasRight(user, right, context) 115 */ 116 public enum RightResult 117 { 118 /** 119 * Indicates that a given user has the required right. 120 */ 121 RIGHT_ALLOW, 122 123 /** 124 * Indicates that a given user does NOT have the required right. 125 */ 126 RIGHT_DENY, 127 128 /** 129 * Indicates that the system knows nothing about the fact that a given user has a right or not. 130 */ 131 RIGHT_UNKNOWN; 132 } 133 134 @Override 135 public void contextualize(Context context) throws ContextException 136 { 137 _context = context; 138 } 139 140 @Override 141 public void service(ServiceManager manager) throws ServiceException 142 { 143 _manager = manager; 144 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 145 _groupManager = (GroupManager) manager.lookup(GroupManager.ROLE); 146 _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE); 147 _groupDirectoryDAO = (GroupDirectoryDAO) manager.lookup(GroupDirectoryDAO.ROLE); 148 _rightsEP = (RightsExtensionPoint) manager.lookup(RightsExtensionPoint.ROLE); 149 _rightContextConvertorEP = (RightContextConvertorExtensionPoint) manager.lookup(RightContextConvertorExtensionPoint.ROLE); 150 _accessControllerEP = (AccessControllerExtensionPoint) manager.lookup(AccessControllerExtensionPoint.ROLE); 151 _resolver = (SourceResolver) _manager.lookup(SourceResolver.ROLE); 152 _currentUserProvider = (CurrentUserProvider) _manager.lookup(CurrentUserProvider.ROLE); 153 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 154 } 155 156 public void initialize() throws Exception 157 { 158 _cacheManager.createRequestCache(CACHE_1, 159 new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_MANAGER_CACHE_1_LABEL"), 160 new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_MANAGER_CACHE_1_DESCRIPTION"), 161 true); 162 _cacheManager.createRequestCache(CACHE_2, 163 new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_MANAGER_CACHE_2_LABEL"), 164 new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_MANAGER_CACHE_2_DESCRIPTION"), 165 true); 166 } 167 168 /** 169 * Returns the DAO for profiles 170 * @return The DAO 171 */ 172 protected RightProfilesDAO _getProfileDAO () 173 { 174 try 175 { 176 if (_profilesDAO == null) 177 { 178 _profilesDAO = (RightProfilesDAO) _manager.lookup(RightProfilesDAO.ROLE); 179 } 180 return _profilesDAO; 181 } 182 catch (ServiceException e) 183 { 184 throw new RuntimeException("Failed to retrieve the DAO for profiles", e); 185 } 186 } 187 188 @Override 189 public void configure(Configuration configuration) throws ConfigurationException 190 { 191 Configuration rightsConfiguration = configuration.getChild("rights"); 192 193 String externalFile = rightsConfiguration.getAttribute("config", null); 194 if (externalFile != null) 195 { 196 Source source = null; 197 try 198 { 199 source = _resolver.resolveURI("context://" + externalFile); 200 201 if (source.exists()) 202 { 203 Configuration externalConfiguration; 204 try (InputStream is = source.getInputStream();) 205 { 206 externalConfiguration = new DefaultConfigurationBuilder().build(is); 207 } 208 209 configureRights(externalConfiguration); 210 } 211 else if (getLogger().isInfoEnabled()) 212 { 213 getLogger().info("The optional external rights file '" + externalFile + "' is missing."); 214 } 215 } 216 catch (Exception e) 217 { 218 String message = "An error occured while retriving external file '" + externalFile + "'"; 219 getLogger().error(message, e); 220 throw new ConfigurationException(message, configuration, e); 221 } 222 finally 223 { 224 if (source != null) 225 { 226 _resolver.release(source); 227 } 228 } 229 } 230 else 231 { 232 configureRights(rightsConfiguration); 233 } 234 } 235 236 private void configureRights(Configuration configuration) throws ConfigurationException 237 { 238 Configuration[] rights = configuration.getChildren("right"); 239 for (Configuration rightConf : rights) 240 { 241 Right right = _rightsEP.addRight("application", rightConf); 242 _webinfRights.add(right.getId()); 243 } 244 } 245 246 /** 247 * Getter for external rights ids 248 * @return a list of all the rights' ids from rights.xml file 249 */ 250 public List<String> getExternalRightIds() 251 { 252 return _webinfRights; 253 } 254 255 /** 256 * Add a right to the right EP 257 * @param id the id for the new right 258 * @param label the label for the new right 259 * @param description the description for the new right 260 * @param category the category for the new right 261 */ 262 public void addExternalRight(String id, String label, String description, String category) 263 { 264 _rightsEP.addRight(id, new I18nizableText(label), new I18nizableText(description), new I18nizableText("application", category)); 265 _webinfRights.add(id); 266 } 267 268 /** 269 * Remove the right from the rightEP 270 * @param id the right to delete's id 271 */ 272 public void removeExternalRight(String id) 273 { 274 _rightsEP.removeRight(id); 275 _webinfRights.remove(id); 276 } 277 278 /* --------- */ 279 /* HAS RIGHT */ 280 /* --------- */ 281 282 /** 283 * Checks a permission for the current logged user, on a given object (or context).<br> 284 * If null, it checks if there is at least one object with this permission 285 * @param rightId The name of the right to check. Cannot be null. 286 * @param object The object to check the right. Can be null to search on any object. 287 * @return {@link RightResult#RIGHT_ALLOW}, {@link RightResult#RIGHT_DENY} or {@link RightResult#RIGHT_UNKNOWN} 288 * @throws RightsException if an error occurs. 289 */ 290 public RightResult currentUserHasRight(String rightId, Object object) throws RightsException 291 { 292 return hasRight(_currentUserProvider.getUser(), rightId, object); 293 } 294 295 /** 296 * Checks a permission for a user, on a given object (or context).<br> 297 * If null, it checks if there is at least one object with this permission 298 * @param userIdentity The user identity. Can be null for anonymous 299 * @param rightId The name of the right to check. Cannot be null. 300 * @param object The object to check the right. Can be null to search on any object. 301 * @return {@link RightResult#RIGHT_ALLOW}, {@link RightResult#RIGHT_DENY} or {@link RightResult#RIGHT_UNKNOWN} 302 * @throws RightsException if an error occurs. 303 */ 304 public RightResult hasRight(UserIdentity userIdentity, String rightId, Object object) throws RightsException 305 { 306 UserIdentity objectUserIdentity = userIdentity == null ? __ANONYMOUS_USER_IDENTITY : userIdentity; 307 308 return _hasRight(objectUserIdentity, rightId, object); 309 } 310 311 /** 312 * Gets the right result for anonymous with given right on given object context 313 * @param rightId The id of the right 314 * @param object The object to check 315 * @return the right result for anonymous with given profile on given object context 316 */ 317 public RightResult hasAnonymousRight(String rightId, Object object) 318 { 319 return _hasRight(__ANONYMOUS_USER_IDENTITY, rightId, object); 320 } 321 322 /** 323 * Gets the right result for any connected user with given profile on given object context 324 * @param rightId The right id to test 325 * @param object The object to check 326 * @return the right result for any connected user with given profile on given object context 327 */ 328 public RightResult hasAnyConnectedUserRight(String rightId, Object object) 329 { 330 return _hasRight(__ANY_CONNECTED_USER_IDENTITY, rightId, object); 331 } 332 333 private RightResult _hasRight(UserIdentity userIdentity, String rightId, Object object) 334 { 335 getLogger().debug("Try to determine if user {} has the right '{}' on the object context {}", userIdentity, rightId, object); 336 337 if (StringUtils.isBlank(rightId)) 338 { 339 throw new RightsException("The rightId cannot be null"); 340 } 341 342 return _hasRightOrRead(userIdentity, rightId, object); 343 } 344 345 private RightResult _hasRightOrRead(UserIdentity userIdentity, String rightId, Object object) 346 { 347 if (object == null) 348 { 349 return _hasRightOrRead(userIdentity, rightId); 350 } 351 352 // Try to retrieve in first cache (the one which manages non-null contexts) 353 RightResult cacheResult = _hasRightResultInFirstCache(userIdentity, rightId, object); 354 if (cacheResult != null) 355 { 356 return cacheResult; 357 } 358 359 // Retrieve groups the user belongs to 360 Set<GroupIdentity> groups = _getGroups(userIdentity); 361 362 // Get the objects to check 363 Set<Object> objects = _rightContextConvertorEP.getConvertedObjects(object); 364 365 // Retrieve the set of AccessResult 366 Set<AccessResult> accessResults = _getAccessResults(userIdentity, groups, rightId, objects); 367 368 // Compute access 369 AccessResult access = AccessResult.merge(accessResults); 370 371 RightResult rightResult = access.toRightResult(); 372 _putInFirstCache(userIdentity, rightId, object, rightResult); 373 374 return rightResult; 375 } 376 377 /** 378 * Has the user/anonymous/anyconnected the non null right on any content of the current workspace? 379 * @param userIdentity The user connecter or the value for anonymous or any connected user 380 * @param rightId The right id to test. Can be null to test read access 381 * @return The computed right result 382 */ 383 private RightResult _hasRightOrRead(UserIdentity userIdentity, String rightId) 384 { 385 // Resolve contexts 386 Set<Object> workspacesContexts = _rightContextConvertorEP.getConvertedObjects("/${WorkspaceName}"); 387 388 // Try to retrieve in second cache (the one which manages null context) 389 RightResult cacheResult = _hasRightResultInSecondCache(workspacesContexts, userIdentity, rightId); 390 if (cacheResult != null) 391 { 392 return cacheResult; 393 } 394 395 // Retrieve groups the user belongs to 396 Set<GroupIdentity> groups = _getGroups(userIdentity); 397 398 RightResult rightResult = RightResult.RIGHT_UNKNOWN; 399 for (String controllerId : _accessControllerEP.getExtensionsIds()) 400 { 401 AccessController accessController = _accessControllerEP.getExtension(controllerId); 402 try 403 { 404 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 405 { 406 if (rightId == null ? accessController.hasAnonymousAnyReadAccessPermissionOnWorkspace(workspacesContexts) : accessController.hasAnonymousAnyPermissionOnWorkspace(workspacesContexts, rightId)) 407 { 408 rightResult = RightResult.RIGHT_ALLOW; 409 break; 410 } 411 } 412 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 413 { 414 if (rightId == null ? accessController.hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(workspacesContexts) : accessController.hasAnyConnectedUserAnyPermissionOnWorkspace(workspacesContexts, rightId)) 415 { 416 rightResult = RightResult.RIGHT_ALLOW; 417 break; 418 } 419 } 420 else 421 { 422 if (rightId == null ? accessController.hasUserAnyReadAccessPermissionOnWorkspace(workspacesContexts, userIdentity, groups) : accessController.hasUserAnyPermissionOnWorkspace(workspacesContexts, userIdentity, groups, rightId)) 423 { 424 rightResult = RightResult.RIGHT_ALLOW; 425 break; 426 } 427 } 428 } 429 catch (Exception e) 430 { 431 getLogger().error("An error occured with controller '{}'. Thus, this controller will be ignored.", controllerId, e); 432 } 433 } 434 435 getLogger().debug("Right result found for [{}, {}, {}] => {}", workspacesContexts, userIdentity, rightId, rightResult); 436 _putInSecondCache(workspacesContexts, userIdentity, rightId, rightResult); 437 return rightResult; 438 } 439 440 private Set<AccessResult> _getAccessResults(UserIdentity userIdentity, Set<GroupIdentity> groups, String rightId, Set<Object> objects) 441 { 442 Set<AccessResult> accessResults = new HashSet<>(); 443 for (Object obj : objects) 444 { 445 for (AccessController accessController : _accessControllerEP.getSupportingExtensions(obj)) 446 { 447 try 448 { 449 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 450 { 451 accessResults.add(rightId == null ? accessController.getReadAccessPermissionForAnonymous(obj) : accessController.getPermissionForAnonymous(rightId, obj)); 452 } 453 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 454 { 455 accessResults.add(rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(obj) : accessController.getPermissionForAnyConnectedUser(rightId, obj)); 456 } 457 else 458 { 459 accessResults.add(rightId == null ? accessController.getReadAccessPermission(userIdentity, groups, obj) : accessController.getPermission(userIdentity, groups, rightId, obj)); 460 } 461 } 462 catch (Exception e) 463 { 464 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessController.getId(), obj, e); 465 } 466 } 467 } 468 469 return accessResults; 470 } 471 472 /* --------------- */ 473 /* HAS READ ACCESS */ 474 /* --------------- */ 475 476 /** 477 * Returns true if the current user has READ access on the given object 478 * @param object The object to check the right. Can be null to search on any object. 479 * @return true if the given user has READ access on the given object 480 */ 481 public boolean currentUserHasReadAccess(Object object) 482 { 483 return hasReadAccess(_currentUserProvider.getUser(), object); 484 } 485 486 /** 487 * Returns true if the given user has READ access on the given object 488 * @param userIdentity The user identity. Cannot be null. 489 * @param object The object to check the right. Can be null to search on any object. 490 * @return true if the given user has READ access on the given object 491 */ 492 public boolean hasReadAccess(UserIdentity userIdentity, Object object) 493 { 494 UserIdentity objectUserIdentity = userIdentity == null ? __ANONYMOUS_USER_IDENTITY : userIdentity; 495 496 return _hasRightOrRead(objectUserIdentity, null, object) == RightResult.RIGHT_ALLOW; 497 } 498 499 /** 500 * Returns true if the object is not restricted, i.e. an anonymous user has READ access (is allowed) on the object 501 * @param object The object to check. Cannot be null 502 * @return true if the object is restricted, i.e. an anonymous user has READ access (is allowed) on the object 503 */ 504 public boolean hasAnonymousReadAccess(Object object) 505 { 506 return _hasRightOrRead(__ANONYMOUS_USER_IDENTITY, null, object) == RightResult.RIGHT_ALLOW; 507 } 508 509 /** 510 * Returns true if any connected user has READ access allowed on the object 511 * @param object The object to check. Cannot be null 512 * @return true if any connected user has READ access allowed on the object 513 */ 514 public boolean hasAnyConnectedUserReadAccess(Object object) 515 { 516 return _hasRightOrRead(__ANY_CONNECTED_USER_IDENTITY, null, object) == RightResult.RIGHT_ALLOW; 517 } 518 519 /* ------------- */ 520 /* ALLOWED USERS */ 521 /* ------------- */ 522 523 /** 524 * Get the list of users that have a particular right in a particular context. 525 * @param rightId The name of the right to check. Cannot be null. 526 * @param object The object to check the right. Cannot be null. 527 * @return The list of users allowed with that right as a Set of String (user identities). 528 * @throws RightsException if an error occurs. 529 */ 530 public AllowedUsers getAllowedUsers(String rightId, Object object) 531 { 532 if (StringUtils.isBlank(rightId)) 533 { 534 throw new RightsException("The rightId cannot be null"); 535 } 536 537 return _getAllowedUsers(rightId, object); 538 } 539 540 /** 541 * Get the users with a READ access on given object 542 * @param object The object 543 * @return The representation of allowed users 544 */ 545 public AllowedUsers getReadAccessAllowedUsers(Object object) 546 { 547 return _getAllowedUsers(null, object); 548 } 549 550 private AllowedUsers _getAllowedUsers(String rightId, Object object) 551 { 552 Optional.ofNullable(object).orElseThrow(() -> 553 { 554 return new RightsException("The object cannot be null"); 555 }); 556 557 // Get the objects to check 558 Set<Object> objects = _rightContextConvertorEP.getConvertedObjects(object); 559 560 // For each object, retrieve the allowed and denied users/groups 561 Boolean isAnyConnectedAllowed = null; // unknown 562 Set<UserIdentity> allAllowedUsers = new HashSet<>(); 563 Set<UserIdentity> allDeniedUsers = new HashSet<>(); 564 Set<GroupIdentity> allAllowedGroups = new HashSet<>(); 565 Set<GroupIdentity> allDeniedGroups = new HashSet<>(); 566 567 for (Object obj : objects) 568 { 569 for (AccessController accessController : _accessControllerEP.getSupportingExtensions(obj)) 570 { 571 try 572 { 573 if ((rightId == null ? accessController.getReadAccessPermissionForAnonymous(obj) : accessController.getPermissionForAnonymous(rightId, obj)) == AccessResult.ANONYMOUS_ALLOWED) 574 { 575 // Any anonymous user is allowed 576 return new AllowedUsers(true, false, null, null, null, null, _userManager, _groupManager, null); 577 } 578 579 AccessResult permissionForAnyConnectedUser = rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(obj) : accessController.getPermissionForAnyConnectedUser(rightId, obj); 580 if (permissionForAnyConnectedUser == AccessResult.ANY_CONNECTED_DENIED) 581 { 582 // For having any connected user allowed, you need to not have the denied access for one object 583 isAnyConnectedAllowed = Boolean.FALSE; 584 } 585 else if (isAnyConnectedAllowed == null && permissionForAnyConnectedUser == AccessResult.ANY_CONNECTED_ALLOWED) 586 { 587 isAnyConnectedAllowed = Boolean.TRUE; 588 } 589 590 Map<UserIdentity, AccessResult> permissionsByUser = rightId == null ? accessController.getReadAccessPermissionByUser(obj) : accessController.getPermissionByUser(rightId, obj); 591 592 Set<UserIdentity> allowedUsersOnObj = permissionsByUser.entrySet().stream() 593 .filter(entry -> AccessResult.USER_ALLOWED.equals(entry.getValue())) 594 .map(Entry::getKey) 595 .collect(Collectors.toSet()); 596 allAllowedUsers.addAll(allowedUsersOnObj); 597 598 Set<UserIdentity> deniedUsersOnObj = permissionsByUser.entrySet().stream() 599 .filter(entry -> AccessResult.USER_DENIED.equals(entry.getValue())) 600 .map(Entry::getKey) 601 .collect(Collectors.toSet()); 602 allDeniedUsers.addAll(deniedUsersOnObj); 603 604 605 Map<GroupIdentity, AccessResult> permissionsByGroup = rightId == null ? accessController.getReadAccessPermissionByGroup(obj) : accessController.getPermissionByGroup(rightId, obj); 606 607 Set<GroupIdentity> allowedGroupsOnObj = permissionsByGroup.entrySet().stream() 608 .filter(entry -> AccessResult.GROUP_ALLOWED.equals(entry.getValue())) 609 .map(Entry::getKey) 610 .collect(Collectors.toSet()); 611 allAllowedGroups.addAll(allowedGroupsOnObj); 612 613 Set<GroupIdentity> deniedGroupsOnObj = permissionsByGroup.entrySet().stream() 614 .filter(entry -> AccessResult.GROUP_DENIED.equals(entry.getValue())) 615 .map(Entry::getKey) 616 .collect(Collectors.toSet()); 617 allDeniedGroups.addAll(deniedGroupsOnObj); 618 } 619 catch (Exception e) 620 { 621 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessController.getId(), obj, e); 622 } 623 } 624 } 625 626 Request request = ContextHelper.getRequest(_context); 627 @SuppressWarnings("unchecked") 628 List<String> populationContexts = (List<String>) request.getAttribute(PopulationContextHelper.POPULATION_CONTEXTS_REQUEST_ATTR); 629 630 // Then, return the AllowedUsers object 631 return new AllowedUsers(false, isAnyConnectedAllowed != null && isAnyConnectedAllowed.booleanValue(), allAllowedUsers, allDeniedUsers, allAllowedGroups, allDeniedGroups, _userManager, _groupManager, populationContexts != null ? new HashSet<>(populationContexts) : new HashSet<>()); 632 } 633 634 /* --------------- */ 635 /* GET USER RIGHTS */ 636 /* --------------- */ 637 638 /** 639 * Get the list of rights a user is allowed, on a particular object. 640 * @param userIdentity the user identity. Cannot be null. 641 * @param object The object to check the right. Cannot be null. 642 * @return The list of rights as a Set of String (id). 643 * @throws RightsException if an error occurs. 644 */ 645 public Set<String> getUserRights(UserIdentity userIdentity, Object object) throws RightsException 646 { 647 if (userIdentity == null) 648 { 649 throw new RightsException("The userIdentity cannot be null"); 650 } 651 else if (object == null) 652 { 653 throw new RightsException("The object cannot be null"); 654 } 655 656 // Get the objects to check 657 Set<Object> objects = _rightContextConvertorEP.getConvertedObjects(object); 658 659 // Retrieve groups the user belongs to 660 Set<GroupIdentity> groups = _groupManager.getUserGroups(userIdentity); 661 662 // Gets the access by rights 663 Map<String, AccessResult> accessResultsByRight = _getAccessResultByRight(userIdentity, groups, objects); 664 665 // Keep only positive rights 666 Set<String> allowedRights = accessResultsByRight.entrySet().stream().filter(entry -> entry.getValue().toRightResult() == RightResult.RIGHT_ALLOW).map(entry -> entry.getKey()).collect(Collectors.toSet()); 667 return allowedRights; 668 } 669 670 /** 671 * clear all caches related to RightManager and AccesControllers 672 */ 673 public void clearCaches() 674 { 675 Request request = ContextHelper.getRequest(_context); 676 677 Enumeration<String> attrNames = request.getAttributeNames(); 678 while (attrNames.hasMoreElements()) 679 { 680 String attrName = attrNames.nextElement(); 681 if (attrName != null && attrName.startsWith(AbstractCacheManager.ROLE + "$" + RightManager.ROLE + "$")) 682 { 683 Cache cache = (Cache) request.getAttribute(attrName); 684 if (cache != null) 685 { 686 cache.invalidateAll(); 687 } 688 } 689 } 690 } 691 692 private Map<String, AccessResult> _getAccessResultByRight(UserIdentity userIdentity, Set<GroupIdentity> groups, Set<Object> objects) 693 { 694 Map<String, AccessResult> result = new HashMap<>(); 695 696 for (Object obj : objects) 697 { 698 for (AccessController accessController : _accessControllerEP.getSupportingExtensions(obj)) 699 { 700 try 701 { 702 // Update the result map 703 Map<String, AccessResult> permissionsByRight = accessController.getPermissionByRight(userIdentity, groups, obj); 704 for (String rightId : permissionsByRight.keySet()) 705 { 706 result.put(rightId, AccessResult.merge(result.get(rightId), permissionsByRight.get(rightId))); 707 } 708 } 709 catch (Exception e) 710 { 711 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessController.getId(), obj, e); 712 } 713 } 714 } 715 716 return result; 717 } 718 719 /* ------- */ 720 /* PRIVATE */ 721 /* ------- */ 722 private Set<GroupIdentity> _getGroups(UserIdentity userIdentity) 723 { 724 if (userIdentity == __ANONYMOUS_USER_IDENTITY || userIdentity == __ANY_CONNECTED_USER_IDENTITY) 725 { 726 return Collections.EMPTY_SET; 727 } 728 else 729 { 730 Set<GroupIdentity> userGroups = _groupManager.getUserGroups(userIdentity); 731 return userGroups; 732 } 733 } 734 735 736 737 private RightResult _hasRightResultInFirstCache(UserIdentity userIdentity, String rightId, Object object) 738 { 739 740 Cache<Cache1Key, RightResult> mapCache = _cacheManager.get(CACHE_1); 741 Cache1Key key = Cache1Key.of(userIdentity, rightId, object); 742 if (mapCache.hasKey(key)) 743 { 744 RightResult cacheResult = mapCache.get(key); 745 getLogger().debug("Find entry in cache for [{}, {}, {}] => {}", userIdentity, rightId, object, cacheResult); 746 return cacheResult; 747 } 748 getLogger().debug("Did not find entry in cache for [{}, {}, {}]", userIdentity, rightId, object); 749 return null; 750 } 751 752 private void _putInFirstCache(UserIdentity userIdentity, String rightId, Object object, RightResult rightResult) 753 { 754 Cache<Cache1Key, RightResult> mapCache = _cacheManager.get(CACHE_1); 755 if (mapCache != null) 756 { 757 mapCache.put(Cache1Key.of(userIdentity, rightId, object), rightResult); 758 } 759 } 760 761 private RightResult _hasRightResultInSecondCache(Set<Object> workspacesContexts, UserIdentity userIdentity, String rightId) 762 { 763 Cache<Cache2Key, RightResult> mapCache = _cacheManager.get(CACHE_2); 764 Cache2Key key = Cache2Key.of(userIdentity, rightId, workspacesContexts); 765 if (mapCache != null) 766 { 767 if (mapCache.hasKey(key)) 768 { 769 RightResult cacheResult = mapCache.get(key); 770 getLogger().debug("Find entry in cache2 for [{}, {}, {}] => {}", workspacesContexts, userIdentity, rightId, cacheResult); 771 return cacheResult; 772 } 773 } 774 775 getLogger().debug("Did not find entry in cache2 for [{}, {}, {}]", workspacesContexts, userIdentity, rightId); 776 return null; 777 } 778 779 private void _putInSecondCache(Set<Object> workspacesContexts, UserIdentity userIdentity, String rightId, RightResult rightResult) 780 { 781 Cache<Cache2Key, RightResult> mapCache = _cacheManager.get(CACHE_2); 782 783 if (mapCache != null) 784 { 785 mapCache.put(Cache2Key.of(userIdentity, rightId, workspacesContexts), rightResult); 786 } 787 } 788 789 /** 790 * Explain why a given user has a right on a context. 791 * 792 * This method will return the explanations provided by every supporting access controller. 793 * Merging the access result of every explanation will return the same result as the 794 * {@link #_hasRight(UserIdentity, String, Object)} method. 795 * 796 * @implNote this method implementation should mirror the {@link #hasRight(UserIdentity, String, Object)} method 797 * without using cache. 798 * 799 * @param userIdentity the user identity or null for anonymous 800 * @param rightId the right to explain or null for read right. 801 * @param object the object to check right on. Can't be null. 802 * @return the list of explanation. 803 */ 804 public List<AccessExplanation> explain(UserIdentity userIdentity, String rightId, Object object) 805 { 806 UserIdentity objectUserIdentity = userIdentity == null ? __ANONYMOUS_USER_IDENTITY : userIdentity; 807 808 if (object == null) 809 { 810 throw new RightsException("The object cannot be null"); 811 } 812 813 // Retrieve groups the user belongs to 814 Set<GroupIdentity> groups = _getGroups(objectUserIdentity); 815 816 // Get the objects to check 817 Set<Object> objects = _rightContextConvertorEP.getConvertedObjects(object); 818 819 return _explainAccessResults(objectUserIdentity, groups, rightId, objects); 820 } 821 822 /** 823 * Iterate on the objects and access controller to retrieve the explanations of all the supported controllers 824 * @return the explanations 825 */ 826 private List<AccessExplanation> _explainAccessResults(UserIdentity userIdentity, Set<GroupIdentity> groups, String rightId, Set<Object> objects) 827 { 828 List<AccessExplanation> accessExplanations = new ArrayList<>(); 829 830 for (Object object : objects) 831 { 832 for (AccessController accessController : _accessControllerEP.getSupportingExtensions(object)) 833 { 834 try 835 { 836 AccessExplanation explanation = _getAccessControllerExplanation(accessController, userIdentity, groups, rightId, object); 837 838 accessExplanations.add(explanation); 839 } 840 catch (Exception e) 841 { 842 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessController.getId(), object, e); 843 } 844 } 845 } 846 847 return accessExplanations; 848 } 849 850 /** 851 * Get all the permissions that concern a given user. 852 * 853 * The permissions are organized by object context and are paired with a list of explanation 854 * returned by the {@link AccessController} that granted the permission. 855 * 856 * @param userIdentity the user identity 857 * @return all the permissions of a user. 858 */ 859 public Map<ExplanationObject, Map<Permission, List<AccessExplanation>>> getAllPermissions(UserIdentity userIdentity) 860 { 861 // Resolve contexts 862 Set<Object> workspacesContexts = _rightContextConvertorEP.getConvertedObjects("/${WorkspaceName}"); 863 864 // Retrieve groups the user belongs to 865 Set<GroupIdentity> groups = _groupManager.getUserGroups(userIdentity); 866 867 Map<ExplanationObject, Map<Permission, List<AccessExplanation>>> permissionsForUser = new HashMap<>(); 868 869 for (String controllerId : _accessControllerEP.getExtensionsIds()) 870 { 871 AccessController accessController = _accessControllerEP.getExtension(controllerId); 872 873 try 874 { 875 Map<ExplanationObject, Map<Permission, AccessExplanation>> controllerPermissionsForUser = accessController.explainAllPermissions(userIdentity, groups, workspacesContexts); 876 for (ExplanationObject context : controllerPermissionsForUser.keySet()) 877 { 878 Map<Permission, List<AccessExplanation>> contextExplanations = permissionsForUser.computeIfAbsent(context, o -> new HashMap<>()); 879 Map<Permission, AccessExplanation> contextPermissions = controllerPermissionsForUser.get(context); 880 for (Permission permission : contextPermissions.keySet()) 881 { 882 List<AccessExplanation> rightExplanation = contextExplanations.computeIfAbsent(permission, str -> new ArrayList<>()); 883 rightExplanation.add(contextPermissions.get(permission)); 884 } 885 } 886 } 887 catch (Exception e) 888 { 889 getLogger().error("An error occured while retrieving the permission for the controller '" + controllerId + "'. The controller will be ignored.", e); 890 } 891 } 892 893 return permissionsForUser; 894 } 895 896 private AccessExplanation _getAccessControllerExplanation(AccessController accessController, UserIdentity userIdentity, Set<GroupIdentity> groups, String rightId, Object object) 897 { 898 AccessExplanation explanation; 899 try 900 { 901 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 902 { 903 explanation = rightId == null ? accessController.explainReadAccessPermissionForAnonymous(object) : accessController.explainPermissionForAnonymous(rightId, object); 904 } 905 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 906 { 907 explanation = rightId == null ? accessController.explainReadAccessPermissionForAnyConnectedUser(object) : accessController.explainPermissionForAnyConnectedUser(rightId, object); 908 } 909 else 910 { 911 explanation = rightId == null ? accessController.explainReadAccessPermission(userIdentity, groups, object) : accessController.explainPermission(userIdentity, groups, rightId, object); 912 } 913 } 914 catch (Exception e) 915 { 916 // Try to fallback to the get permission method in case of failure 917 // An explanation without all the supported access controller 918 // may be false. By including a less detailed explanation, we make sure the 919 // general explanation stay correct 920 AccessResult result; 921 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 922 { 923 result = rightId == null ? accessController.getReadAccessPermissionForAnonymous(object) : accessController.getPermissionForAnonymous(rightId, object); 924 } 925 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 926 { 927 result = rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(object) : accessController.getPermissionForAnyConnectedUser(rightId, object); 928 } 929 else 930 { 931 result = rightId == null ? accessController.getReadAccessPermission(userIdentity, groups, object) : accessController.getPermission(userIdentity, groups, rightId, object); 932 } 933 934 explanation = AccessController.getDefaultAccessExplanation(accessController.getId(), result); 935 getLogger().warn("An error occured while explaining access with controller '{}' for object {}. A generic explanation was returned.", accessController.getId(), object, e); 936 } 937 return explanation; 938 } 939 940 /** 941 * Get the detail of all the permission granted on a given context 942 * The permissions are returned organized in 4 different groups: 943 * permission granted to anonymous, any connected, groups, users. 944 * 945 * @param object the object to check permissions 946 * @return the permissions on context 947 */ 948 public ContextPermissions explainAllPermissions(Object object) 949 { 950 Map<Permission, List<AccessExplanation>> permissionsForAnonymous = new HashMap<>(); 951 Map<Permission, List<AccessExplanation>> permissionsForAnyConnected = new HashMap<>(); 952 Map<UserIdentity, Map<Permission, List<AccessExplanation>>> permissionsByUser = new HashMap<>(); 953 Map<GroupIdentity, Map<Permission, List<AccessExplanation>>> permissionsByGroup = new HashMap<>(); 954 955 // Get the objects to check 956 Set<Object> objects = _rightContextConvertorEP.getConvertedObjects(object); 957 958 for (Object objectToCheck : objects) 959 { 960 for (String controllerId : _accessControllerEP.getExtensionsIds()) 961 { 962 AccessController accessController = _accessControllerEP.getExtension(controllerId); 963 try 964 { 965 if (accessController.supports(objectToCheck)) 966 { 967 Map<Permission, AccessExplanation> controllerPermissionsForAnonymous = accessController.explainAllPermissionsForAnonymous(objectToCheck); 968 for (Permission permission : controllerPermissionsForAnonymous.keySet()) 969 { 970 List<AccessExplanation> rightExplanation = permissionsForAnonymous.computeIfAbsent(permission, str -> new ArrayList<>()); 971 rightExplanation.add(controllerPermissionsForAnonymous.get(permission)); 972 } 973 974 Map<Permission, AccessExplanation> controllerPermissionsForAnyConnected = accessController.explainAllPermissionsForAnyConnected(objectToCheck); 975 for (Permission permission : controllerPermissionsForAnyConnected.keySet()) 976 { 977 List<AccessExplanation> rightExplanation = permissionsForAnyConnected.computeIfAbsent(permission, str -> new ArrayList<>()); 978 rightExplanation.add(controllerPermissionsForAnyConnected.get(permission)); 979 } 980 981 Map<UserIdentity, Map<Permission, AccessExplanation>> controllerPermissionsByUser = accessController.explainAllPermissionsByUser(objectToCheck); 982 for (UserIdentity user: controllerPermissionsByUser.keySet()) 983 { 984 Map<Permission, List<AccessExplanation>> permissionsForUser = permissionsByUser.computeIfAbsent(user, u -> new HashMap<>()); 985 Map<Permission, AccessExplanation> controllerPermissionsForUser = controllerPermissionsByUser.get(user); 986 for (Permission permission : controllerPermissionsForUser.keySet()) 987 { 988 List<AccessExplanation> rightExplanation = permissionsForUser.computeIfAbsent(permission, str -> new ArrayList<>()); 989 rightExplanation.add(controllerPermissionsForUser.get(permission)); 990 } 991 } 992 993 Map<GroupIdentity, Map<Permission, AccessExplanation>> controllerPermissionsByGroup = accessController.explainAllPermissionsByGroup(objectToCheck); 994 for (GroupIdentity group: controllerPermissionsByGroup.keySet()) 995 { 996 Map<Permission, List<AccessExplanation>> permissionsForUser = permissionsByGroup.computeIfAbsent(group, u -> new HashMap<>()); 997 Map<Permission, AccessExplanation> controllerPermissionsForGroup = controllerPermissionsByGroup.get(group); 998 for (Permission permission : controllerPermissionsForGroup.keySet()) 999 { 1000 List<AccessExplanation> rightExplanation = permissionsForUser.computeIfAbsent(permission, str -> new ArrayList<>()); 1001 rightExplanation.add(controllerPermissionsForGroup.get(permission)); 1002 } 1003 } 1004 } 1005 } 1006 catch (Exception e) 1007 { 1008 getLogger().error("An error occured with controller '{}' while explaining permission for '{}'. Thus, this controller will be ignored.", controllerId, objectToCheck.toString(), e); 1009 } 1010 } 1011 } 1012 1013 return new ContextPermissions(permissionsForAnonymous, permissionsForAnyConnected, permissionsByUser, permissionsByGroup); 1014 } 1015 1016 /** 1017 * Record to describe all the permissions granted on a context 1018 * @param permissionsForAnonymous the permissions for anonymous user 1019 * @param permissionsForAnyConnected the permissions for any connected user 1020 * @param permissionsByUser the permissions for specific users by user identity 1021 * @param permissionsByGroup the permissions for specific groups by group identity 1022 */ 1023 public record ContextPermissions( 1024 Map<Permission, List<AccessExplanation>> permissionsForAnonymous, 1025 Map<Permission, List<AccessExplanation>> permissionsForAnyConnected, 1026 Map<UserIdentity, Map<Permission, List<AccessExplanation>>> permissionsByUser, 1027 Map<GroupIdentity, Map<Permission, List<AccessExplanation>>> permissionsByGroup) { } 1028 1029 static class Cache1Key extends AbstractCacheKey 1030 { 1031 Cache1Key(UserIdentity userIdentity, String rightId, Object object) 1032 { 1033 super(userIdentity, rightId, object); 1034 } 1035 1036 static Cache1Key of(UserIdentity userIdentity, String rightId, Object object) 1037 { 1038 return new Cache1Key(userIdentity, StringUtils.defaultString(rightId), object); 1039 } 1040 1041 } 1042 static class Cache2Key extends AbstractCacheKey 1043 { 1044 Cache2Key(UserIdentity userIdentity, String rightId, Set<Object> workspacesContexts) 1045 { 1046 super(userIdentity, rightId, workspacesContexts); 1047 } 1048 1049 static Cache2Key of(UserIdentity userIdentity, String rightId, Set<Object> workspacesContexts) 1050 { 1051 return new Cache2Key(userIdentity, StringUtils.defaultString(rightId), workspacesContexts); 1052 } 1053 1054 } 1055}