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 (String accessControllerId : _accessControllerEP.getExtensionsIds()) 446 { 447 try 448 { 449 AccessController accessController = _accessControllerEP.getExtension(accessControllerId); 450 if (accessController.supports(obj)) 451 { 452 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 453 { 454 accessResults.add(rightId == null ? accessController.getReadAccessPermissionForAnonymous(obj) : accessController.getPermissionForAnonymous(rightId, obj)); 455 } 456 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 457 { 458 accessResults.add(rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(obj) : accessController.getPermissionForAnyConnectedUser(rightId, obj)); 459 } 460 else 461 { 462 accessResults.add(rightId == null ? accessController.getReadAccessPermission(userIdentity, groups, obj) : accessController.getPermission(userIdentity, groups, rightId, obj)); 463 } 464 } 465 } 466 catch (Exception e) 467 { 468 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessControllerId, obj, e); 469 } 470 } 471 } 472 473 return accessResults; 474 } 475 476 /* --------------- */ 477 /* HAS READ ACCESS */ 478 /* --------------- */ 479 480 /** 481 * Returns true if the current user has READ access on the given object 482 * @param object The object to check the right. Can be null to search on any object. 483 * @return true if the given user has READ access on the given object 484 */ 485 public boolean currentUserHasReadAccess(Object object) 486 { 487 return hasReadAccess(_currentUserProvider.getUser(), object); 488 } 489 490 /** 491 * Returns true if the given user has READ access on the given object 492 * @param userIdentity The user identity. Cannot be null. 493 * @param object The object to check the right. Can be null to search on any object. 494 * @return true if the given user has READ access on the given object 495 */ 496 public boolean hasReadAccess(UserIdentity userIdentity, Object object) 497 { 498 UserIdentity objectUserIdentity = userIdentity == null ? __ANONYMOUS_USER_IDENTITY : userIdentity; 499 500 return _hasRightOrRead(objectUserIdentity, null, object) == RightResult.RIGHT_ALLOW; 501 } 502 503 /** 504 * Returns true if the object is not restricted, i.e. an anonymous user has READ access (is allowed) on the object 505 * @param object The object to check. Cannot be null 506 * @return true if the object is restricted, i.e. an anonymous user has READ access (is allowed) on the object 507 */ 508 public boolean hasAnonymousReadAccess(Object object) 509 { 510 return _hasRightOrRead(__ANONYMOUS_USER_IDENTITY, null, object) == RightResult.RIGHT_ALLOW; 511 } 512 513 /** 514 * Returns true if any connected user has READ access allowed on the object 515 * @param object The object to check. Cannot be null 516 * @return true if any connected user has READ access allowed on the object 517 */ 518 public boolean hasAnyConnectedUserReadAccess(Object object) 519 { 520 return _hasRightOrRead(__ANY_CONNECTED_USER_IDENTITY, null, object) == RightResult.RIGHT_ALLOW; 521 } 522 523 /* ------------- */ 524 /* ALLOWED USERS */ 525 /* ------------- */ 526 527 /** 528 * Get the list of users that have a particular right in a particular context. 529 * @param rightId The name of the right to check. Cannot be null. 530 * @param object The object to check the right. Cannot be null. 531 * @return The list of users allowed with that right as a Set of String (user identities). 532 * @throws RightsException if an error occurs. 533 */ 534 public AllowedUsers getAllowedUsers(String rightId, Object object) 535 { 536 if (StringUtils.isBlank(rightId)) 537 { 538 throw new RightsException("The rightId cannot be null"); 539 } 540 541 return _getAllowedUsers(rightId, object); 542 } 543 544 /** 545 * Get the users with a READ access on given object 546 * @param object The object 547 * @return The representation of allowed users 548 */ 549 public AllowedUsers getReadAccessAllowedUsers(Object object) 550 { 551 return _getAllowedUsers(null, object); 552 } 553 554 private AllowedUsers _getAllowedUsers(String rightId, Object object) 555 { 556 Optional.ofNullable(object).orElseThrow(() -> 557 { 558 return new RightsException("The object cannot be null"); 559 }); 560 561 // Get the objects to check 562 Set<Object> objects = _rightContextConvertorEP.getConvertedObjects(object); 563 564 // For each object, retrieve the allowed and denied users/groups 565 Boolean isAnyConnectedAllowed = null; // unknown 566 Set<UserIdentity> allAllowedUsers = new HashSet<>(); 567 Set<UserIdentity> allDeniedUsers = new HashSet<>(); 568 Set<GroupIdentity> allAllowedGroups = new HashSet<>(); 569 Set<GroupIdentity> allDeniedGroups = new HashSet<>(); 570 571 for (Object obj : objects) 572 { 573 for (String accessControllerId : _accessControllerEP.getExtensionsIds()) 574 { 575 try 576 { 577 AccessController accessController = _accessControllerEP.getExtension(accessControllerId); 578 if (accessController.supports(obj)) 579 { 580 if ((rightId == null ? accessController.getReadAccessPermissionForAnonymous(obj) : accessController.getPermissionForAnonymous(rightId, obj)) == AccessResult.ANONYMOUS_ALLOWED) 581 { 582 // Any anonymous user is allowed 583 return new AllowedUsers(true, false, null, null, null, null, _userManager, _groupManager, null); 584 } 585 586 AccessResult permissionForAnyConnectedUser = rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(obj) : accessController.getPermissionForAnyConnectedUser(rightId, obj); 587 if (permissionForAnyConnectedUser == AccessResult.ANY_CONNECTED_DENIED) 588 { 589 // For having any connected user allowed, you need to not have the denied access for one object 590 isAnyConnectedAllowed = Boolean.FALSE; 591 } 592 else if (isAnyConnectedAllowed == null && permissionForAnyConnectedUser == AccessResult.ANY_CONNECTED_ALLOWED) 593 { 594 isAnyConnectedAllowed = Boolean.TRUE; 595 } 596 597 Map<UserIdentity, AccessResult> permissionsByUser = rightId == null ? accessController.getReadAccessPermissionByUser(obj) : accessController.getPermissionByUser(rightId, obj); 598 599 Set<UserIdentity> allowedUsersOnObj = permissionsByUser.entrySet().stream() 600 .filter(entry -> AccessResult.USER_ALLOWED.equals(entry.getValue())) 601 .map(Entry::getKey) 602 .collect(Collectors.toSet()); 603 allAllowedUsers.addAll(allowedUsersOnObj); 604 605 Set<UserIdentity> deniedUsersOnObj = permissionsByUser.entrySet().stream() 606 .filter(entry -> AccessResult.USER_DENIED.equals(entry.getValue())) 607 .map(Entry::getKey) 608 .collect(Collectors.toSet()); 609 allDeniedUsers.addAll(deniedUsersOnObj); 610 611 612 Map<GroupIdentity, AccessResult> permissionsByGroup = rightId == null ? accessController.getReadAccessPermissionByGroup(obj) : accessController.getPermissionByGroup(rightId, obj); 613 614 Set<GroupIdentity> allowedGroupsOnObj = permissionsByGroup.entrySet().stream() 615 .filter(entry -> AccessResult.GROUP_ALLOWED.equals(entry.getValue())) 616 .map(Entry::getKey) 617 .collect(Collectors.toSet()); 618 allAllowedGroups.addAll(allowedGroupsOnObj); 619 620 Set<GroupIdentity> deniedGroupsOnObj = permissionsByGroup.entrySet().stream() 621 .filter(entry -> AccessResult.GROUP_DENIED.equals(entry.getValue())) 622 .map(Entry::getKey) 623 .collect(Collectors.toSet()); 624 allDeniedGroups.addAll(deniedGroupsOnObj); 625 } 626 } 627 catch (Exception e) 628 { 629 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessControllerId, obj, e); 630 } 631 } 632 } 633 634 Request request = ContextHelper.getRequest(_context); 635 @SuppressWarnings("unchecked") 636 List<String> populationContexts = (List<String>) request.getAttribute(PopulationContextHelper.POPULATION_CONTEXTS_REQUEST_ATTR); 637 638 // Then, return the AllowedUsers object 639 return new AllowedUsers(false, isAnyConnectedAllowed != null && isAnyConnectedAllowed.booleanValue(), allAllowedUsers, allDeniedUsers, allAllowedGroups, allDeniedGroups, _userManager, _groupManager, populationContexts != null ? new HashSet<>(populationContexts) : new HashSet<>()); 640 } 641 642 /* --------------- */ 643 /* GET USER RIGHTS */ 644 /* --------------- */ 645 646 /** 647 * Get the list of rights a user is allowed, on a particular object. 648 * @param userIdentity the user identity. Cannot be null. 649 * @param object The object to check the right. Cannot be null. 650 * @return The list of rights as a Set of String (id). 651 * @throws RightsException if an error occurs. 652 */ 653 public Set<String> getUserRights(UserIdentity userIdentity, Object object) throws RightsException 654 { 655 if (userIdentity == null) 656 { 657 throw new RightsException("The userIdentity cannot be null"); 658 } 659 else if (object == null) 660 { 661 throw new RightsException("The object cannot be null"); 662 } 663 664 // Get the objects to check 665 Set<Object> objects = _rightContextConvertorEP.getConvertedObjects(object); 666 667 // Retrieve groups the user belongs to 668 Set<GroupIdentity> groups = _groupManager.getUserGroups(userIdentity); 669 670 // Gets the access by rights 671 Map<String, AccessResult> accessResultsByRight = _getAccessResultByRight(userIdentity, groups, objects); 672 673 // Keep only positive rights 674 Set<String> allowedRights = accessResultsByRight.entrySet().stream().filter(entry -> entry.getValue().toRightResult() == RightResult.RIGHT_ALLOW).map(entry -> entry.getKey()).collect(Collectors.toSet()); 675 return allowedRights; 676 } 677 678 /** 679 * clear all caches related to RightManager and AccesControllers 680 */ 681 public void clearCaches() 682 { 683 Request request = ContextHelper.getRequest(_context); 684 685 Enumeration<String> attrNames = request.getAttributeNames(); 686 while (attrNames.hasMoreElements()) 687 { 688 String attrName = attrNames.nextElement(); 689 if (attrName != null && attrName.startsWith(AbstractCacheManager.ROLE + "$" + RightManager.ROLE + "$")) 690 { 691 Cache cache = (Cache) request.getAttribute(attrName); 692 if (cache != null) 693 { 694 cache.invalidateAll(); 695 } 696 } 697 } 698 } 699 700 private Map<String, AccessResult> _getAccessResultByRight(UserIdentity userIdentity, Set<GroupIdentity> groups, Set<Object> objects) 701 { 702 Map<String, AccessResult> result = new HashMap<>(); 703 704 for (Object obj : objects) 705 { 706 for (String accessControllerId : _accessControllerEP.getExtensionsIds()) 707 { 708 try 709 { 710 AccessController accessController = _accessControllerEP.getExtension(accessControllerId); 711 if (accessController.supports(obj)) 712 { 713 // Update the result map 714 Map<String, AccessResult> permissionsByRight = accessController.getPermissionByRight(userIdentity, groups, obj); 715 for (String rightId : permissionsByRight.keySet()) 716 { 717 result.put(rightId, AccessResult.merge(result.get(rightId), permissionsByRight.get(rightId))); 718 } 719 } 720 } 721 catch (Exception e) 722 { 723 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessControllerId, obj, e); 724 } 725 } 726 } 727 728 return result; 729 } 730 731 /* ------- */ 732 /* PRIVATE */ 733 /* ------- */ 734 private Set<GroupIdentity> _getGroups(UserIdentity userIdentity) 735 { 736 if (userIdentity == __ANONYMOUS_USER_IDENTITY || userIdentity == __ANY_CONNECTED_USER_IDENTITY) 737 { 738 return Collections.EMPTY_SET; 739 } 740 else 741 { 742 Set<GroupIdentity> userGroups = _groupManager.getUserGroups(userIdentity); 743 return userGroups; 744 } 745 } 746 747 748 749 private RightResult _hasRightResultInFirstCache(UserIdentity userIdentity, String rightId, Object object) 750 { 751 752 Cache<Cache1Key, RightResult> mapCache = _cacheManager.get(CACHE_1); 753 Cache1Key key = Cache1Key.of(userIdentity, rightId, object); 754 if (mapCache.hasKey(key)) 755 { 756 RightResult cacheResult = mapCache.get(key); 757 getLogger().debug("Find entry in cache for [{}, {}, {}] => {}", userIdentity, rightId, object, cacheResult); 758 return cacheResult; 759 } 760 getLogger().debug("Did not find entry in cache for [{}, {}, {}]", userIdentity, rightId, object); 761 return null; 762 } 763 764 private void _putInFirstCache(UserIdentity userIdentity, String rightId, Object object, RightResult rightResult) 765 { 766 Cache<Cache1Key, RightResult> mapCache = _cacheManager.get(CACHE_1); 767 if (mapCache != null) 768 { 769 mapCache.put(Cache1Key.of(userIdentity, rightId, object), rightResult); 770 } 771 } 772 773 private RightResult _hasRightResultInSecondCache(Set<Object> workspacesContexts, UserIdentity userIdentity, String rightId) 774 { 775 Cache<Cache2Key, RightResult> mapCache = _cacheManager.get(CACHE_2); 776 Cache2Key key = Cache2Key.of(userIdentity, rightId, workspacesContexts); 777 if (mapCache != null) 778 { 779 if (mapCache.hasKey(key)) 780 { 781 RightResult cacheResult = mapCache.get(key); 782 getLogger().debug("Find entry in cache2 for [{}, {}, {}] => {}", workspacesContexts, userIdentity, rightId, cacheResult); 783 return cacheResult; 784 } 785 } 786 787 getLogger().debug("Did not find entry in cache2 for [{}, {}, {}]", workspacesContexts, userIdentity, rightId); 788 return null; 789 } 790 791 private void _putInSecondCache(Set<Object> workspacesContexts, UserIdentity userIdentity, String rightId, RightResult rightResult) 792 { 793 Cache<Cache2Key, RightResult> mapCache = _cacheManager.get(CACHE_2); 794 795 if (mapCache != null) 796 { 797 mapCache.put(Cache2Key.of(userIdentity, rightId, workspacesContexts), rightResult); 798 } 799 } 800 801 /** 802 * Explain why a given user has a right on a context. 803 * 804 * This method will return the explanations provided by every supporting access controller. 805 * Merging the access result of every explanation will return the same result as the 806 * {@link #_hasRight(UserIdentity, String, Object)} method. 807 * 808 * @implNote this method implementation should mirror the {@link #hasRight(UserIdentity, String, Object)} method 809 * without using cache. 810 * 811 * @param userIdentity the user identity or null for anonymous 812 * @param rightId the right to explain or null for read right. 813 * @param object the object to check right on. Can't be null. 814 * @return the list of explanation. 815 */ 816 public List<AccessExplanation> explain(UserIdentity userIdentity, String rightId, Object object) 817 { 818 UserIdentity objectUserIdentity = userIdentity == null ? __ANONYMOUS_USER_IDENTITY : userIdentity; 819 820 if (object == null) 821 { 822 throw new RightsException("The object cannot be null"); 823 } 824 825 // Retrieve groups the user belongs to 826 Set<GroupIdentity> groups = _getGroups(objectUserIdentity); 827 828 // Get the objects to check 829 Set<Object> objects = _rightContextConvertorEP.getConvertedObjects(object); 830 831 return _explainAccessResults(objectUserIdentity, groups, rightId, objects); 832 } 833 834 /** 835 * Iterate on the objects and access controller to retrieve the explanations of all the supported controllers 836 * @return the explanations 837 */ 838 private List<AccessExplanation> _explainAccessResults(UserIdentity userIdentity, Set<GroupIdentity> groups, String rightId, Set<Object> objects) 839 { 840 List<AccessExplanation> accessExplanations = new ArrayList<>(); 841 842 for (Object object : objects) 843 { 844 for (String accessControllerId : _accessControllerEP.getExtensionsIds()) 845 { 846 try 847 { 848 AccessController accessController = _accessControllerEP.getExtension(accessControllerId); 849 if (accessController.supports(object)) 850 { 851 AccessExplanation explanation = _getAccessControllerExplanation(accessController, userIdentity, groups, rightId, object); 852 853 accessExplanations.add(explanation); 854 } 855 } 856 catch (Exception e) 857 { 858 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessControllerId, object, e); 859 } 860 } 861 } 862 863 return accessExplanations; 864 } 865 866 /** 867 * Get all the permissions that concern a given user. 868 * 869 * The permissions are organized by object context and are paired with a list of explanation 870 * returned by the {@link AccessController} that granted the permission. 871 * 872 * @param userIdentity the user identity 873 * @return all the permissions of a user. 874 */ 875 public Map<ExplanationObject, Map<Permission, List<AccessExplanation>>> getAllPermissions(UserIdentity userIdentity) 876 { 877 // Resolve contexts 878 Set<Object> workspacesContexts = _rightContextConvertorEP.getConvertedObjects("/${WorkspaceName}"); 879 880 // Retrieve groups the user belongs to 881 Set<GroupIdentity> groups = _groupManager.getUserGroups(userIdentity); 882 883 Map<ExplanationObject, Map<Permission, List<AccessExplanation>>> permissionsForUser = new HashMap<>(); 884 885 for (String controllerId : _accessControllerEP.getExtensionsIds()) 886 { 887 AccessController accessController = _accessControllerEP.getExtension(controllerId); 888 889 try 890 { 891 Map<ExplanationObject, Map<Permission, AccessExplanation>> controllerPermissionsForUser = accessController.explainAllPermissions(userIdentity, groups, workspacesContexts); 892 for (ExplanationObject context : controllerPermissionsForUser.keySet()) 893 { 894 Map<Permission, List<AccessExplanation>> contextExplanations = permissionsForUser.computeIfAbsent(context, o -> new HashMap<>()); 895 Map<Permission, AccessExplanation> contextPermissions = controllerPermissionsForUser.get(context); 896 for (Permission permission : contextPermissions.keySet()) 897 { 898 List<AccessExplanation> rightExplanation = contextExplanations.computeIfAbsent(permission, str -> new ArrayList<>()); 899 rightExplanation.add(contextPermissions.get(permission)); 900 } 901 } 902 } 903 catch (Exception e) 904 { 905 getLogger().error("An error occured while retrieving the permission for the controller '" + controllerId + "'. The controller will be ignored.", e); 906 } 907 } 908 909 return permissionsForUser; 910 } 911 912 private AccessExplanation _getAccessControllerExplanation(AccessController accessController, UserIdentity userIdentity, Set<GroupIdentity> groups, String rightId, Object object) 913 { 914 AccessExplanation explanation; 915 try 916 { 917 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 918 { 919 explanation = rightId == null ? accessController.explainReadAccessPermissionForAnonymous(object) : accessController.explainPermissionForAnonymous(rightId, object); 920 } 921 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 922 { 923 explanation = rightId == null ? accessController.explainReadAccessPermissionForAnyConnectedUser(object) : accessController.explainPermissionForAnyConnectedUser(rightId, object); 924 } 925 else 926 { 927 explanation = rightId == null ? accessController.explainReadAccessPermission(userIdentity, groups, object) : accessController.explainPermission(userIdentity, groups, rightId, object); 928 } 929 } 930 catch (Exception e) 931 { 932 // Try to fallback to the get permission method in case of failure 933 // An explanation without all the supported access controller 934 // may be false. By including a less detailed explanation, we make sure the 935 // general explanation stay correct 936 AccessResult result; 937 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 938 { 939 result = rightId == null ? accessController.getReadAccessPermissionForAnonymous(object) : accessController.getPermissionForAnonymous(rightId, object); 940 } 941 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 942 { 943 result = rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(object) : accessController.getPermissionForAnyConnectedUser(rightId, object); 944 } 945 else 946 { 947 result = rightId == null ? accessController.getReadAccessPermission(userIdentity, groups, object) : accessController.getPermission(userIdentity, groups, rightId, object); 948 } 949 950 explanation = AccessController.getDefaultAccessExplanation(accessController.getId(), result); 951 getLogger().warn("An error occured while explaining access with controller '{}' for object {}. A generic explanation was returned.", accessController.getId(), object, e); 952 } 953 return explanation; 954 } 955 956 /** 957 * Get the detail of all the permission granted on a given context 958 * The permissions are returned organized in 4 different groups: 959 * permission granted to anonymous, any connected, groups, users. 960 * 961 * @param object the object to check permissions 962 * @return the permissions on context 963 */ 964 public ContextPermissions explainAllPermissions(Object object) 965 { 966 Map<Permission, List<AccessExplanation>> permissionsForAnonymous = new HashMap<>(); 967 Map<Permission, List<AccessExplanation>> permissionsForAnyConnected = new HashMap<>(); 968 Map<UserIdentity, Map<Permission, List<AccessExplanation>>> permissionsByUser = new HashMap<>(); 969 Map<GroupIdentity, Map<Permission, List<AccessExplanation>>> permissionsByGroup = new HashMap<>(); 970 971 // Get the objects to check 972 Set<Object> objects = _rightContextConvertorEP.getConvertedObjects(object); 973 974 for (Object objectToCheck : objects) 975 { 976 for (String controllerId : _accessControllerEP.getExtensionsIds()) 977 { 978 AccessController accessController = _accessControllerEP.getExtension(controllerId); 979 try 980 { 981 if (accessController.supports(objectToCheck)) 982 { 983 Map<Permission, AccessExplanation> controllerPermissionsForAnonymous = accessController.explainAllPermissionsForAnonymous(objectToCheck); 984 for (Permission permission : controllerPermissionsForAnonymous.keySet()) 985 { 986 List<AccessExplanation> rightExplanation = permissionsForAnonymous.computeIfAbsent(permission, str -> new ArrayList<>()); 987 rightExplanation.add(controllerPermissionsForAnonymous.get(permission)); 988 } 989 990 Map<Permission, AccessExplanation> controllerPermissionsForAnyConnected = accessController.explainAllPermissionsForAnyConnected(objectToCheck); 991 for (Permission permission : controllerPermissionsForAnyConnected.keySet()) 992 { 993 List<AccessExplanation> rightExplanation = permissionsForAnyConnected.computeIfAbsent(permission, str -> new ArrayList<>()); 994 rightExplanation.add(controllerPermissionsForAnyConnected.get(permission)); 995 } 996 997 Map<UserIdentity, Map<Permission, AccessExplanation>> controllerPermissionsByUser = accessController.explainAllPermissionsByUser(objectToCheck); 998 for (UserIdentity user: controllerPermissionsByUser.keySet()) 999 { 1000 Map<Permission, List<AccessExplanation>> permissionsForUser = permissionsByUser.computeIfAbsent(user, u -> new HashMap<>()); 1001 Map<Permission, AccessExplanation> controllerPermissionsForUser = controllerPermissionsByUser.get(user); 1002 for (Permission permission : controllerPermissionsForUser.keySet()) 1003 { 1004 List<AccessExplanation> rightExplanation = permissionsForUser.computeIfAbsent(permission, str -> new ArrayList<>()); 1005 rightExplanation.add(controllerPermissionsForUser.get(permission)); 1006 } 1007 } 1008 1009 Map<GroupIdentity, Map<Permission, AccessExplanation>> controllerPermissionsByGroup = accessController.explainAllPermissionsByGroup(objectToCheck); 1010 for (GroupIdentity group: controllerPermissionsByGroup.keySet()) 1011 { 1012 Map<Permission, List<AccessExplanation>> permissionsForUser = permissionsByGroup.computeIfAbsent(group, u -> new HashMap<>()); 1013 Map<Permission, AccessExplanation> controllerPermissionsForGroup = controllerPermissionsByGroup.get(group); 1014 for (Permission permission : controllerPermissionsForGroup.keySet()) 1015 { 1016 List<AccessExplanation> rightExplanation = permissionsForUser.computeIfAbsent(permission, str -> new ArrayList<>()); 1017 rightExplanation.add(controllerPermissionsForGroup.get(permission)); 1018 } 1019 } 1020 } 1021 } 1022 catch (Exception e) 1023 { 1024 getLogger().error("An error occured with controller '{}' while explaining permission for '{}'. Thus, this controller will be ignored.", controllerId, objectToCheck.toString(), e); 1025 } 1026 } 1027 } 1028 1029 return new ContextPermissions(permissionsForAnonymous, permissionsForAnyConnected, permissionsByUser, permissionsByGroup); 1030 } 1031 1032 /** 1033 * Record to describe all the permissions granted on a context 1034 * @param permissionsForAnonymous the permissions for anonymous user 1035 * @param permissionsForAnyConnected the permissions for any connected user 1036 * @param permissionsByUser the permissions for specific users by user identity 1037 * @param permissionsByGroup the permissions for specific groups by group identity 1038 */ 1039 public record ContextPermissions( 1040 Map<Permission, List<AccessExplanation>> permissionsForAnonymous, 1041 Map<Permission, List<AccessExplanation>> permissionsForAnyConnected, 1042 Map<UserIdentity, Map<Permission, List<AccessExplanation>>> permissionsByUser, 1043 Map<GroupIdentity, Map<Permission, List<AccessExplanation>>> permissionsByGroup) { } 1044 1045 static class Cache1Key extends AbstractCacheKey 1046 { 1047 Cache1Key(UserIdentity userIdentity, String rightId, Object object) 1048 { 1049 super(userIdentity, rightId, object); 1050 } 1051 1052 static Cache1Key of(UserIdentity userIdentity, String rightId, Object object) 1053 { 1054 return new Cache1Key(userIdentity, StringUtils.defaultString(rightId), object); 1055 } 1056 1057 } 1058 static class Cache2Key extends AbstractCacheKey 1059 { 1060 Cache2Key(UserIdentity userIdentity, String rightId, Set<Object> workspacesContexts) 1061 { 1062 super(userIdentity, rightId, workspacesContexts); 1063 } 1064 1065 static Cache2Key of(UserIdentity userIdentity, String rightId, Set<Object> workspacesContexts) 1066 { 1067 return new Cache2Key(userIdentity, StringUtils.defaultString(rightId), workspacesContexts); 1068 } 1069 1070 } 1071}