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 String id = rightConf.getAttribute("id", ""); 242 243 Configuration labelConf = rightConf.getChild("label"); 244 String label = labelConf.getValue(""); 245 I18nizableText i18nLabel = _getI18nTextFromConf(labelConf, label); 246 247 Configuration descConf = rightConf.getChild("description"); 248 String description = descConf.getValue(""); 249 I18nizableText i18nDescription = _getI18nTextFromConf(descConf, description); 250 251 String category = rightConf.getChild("category").getValue(""); 252 I18nizableText i18nCategory = new I18nizableText("application", category); 253 254 if (id.length() == 0 || label.length() == 0 || description.length() == 0 || category.length() == 0) 255 { 256 String message = "Error in " + RightManager.class.getName() + " configuration: attribute 'id' and elements 'label', 'description' and 'category' are mandatory."; 257 getLogger().error(message); 258 throw new ConfigurationException(message, configuration); 259 } 260 _rightsEP.addRight(id, i18nLabel, i18nDescription, i18nCategory); 261 _webinfRights.add(id); 262 } 263 } 264 265 /** 266 * Getter for external rights ids 267 * @return a list of all the rights' ids from rights.xml file 268 */ 269 public List<String> getExternalRightIds() 270 { 271 return _webinfRights; 272 } 273 274 private I18nizableText _getI18nTextFromConf(Configuration config, String label) 275 { 276 return I18nizableText.isI18n(config) 277 ? new I18nizableText("application", label) 278 : new I18nizableText(label); 279 } 280 281 /** 282 * Add a right to the right EP 283 * @param id the id for the new right 284 * @param label the label for the new right 285 * @param description the description for the new right 286 * @param category the category for the new right 287 */ 288 public void addExternalRight(String id, String label, String description, String category) 289 { 290 _rightsEP.addRight(id, new I18nizableText(label), new I18nizableText(description), new I18nizableText("application", category)); 291 _webinfRights.add(id); 292 } 293 294 /** 295 * Remove the right from the rightEP 296 * @param id the right to delete's id 297 */ 298 public void removeExternalRight(String id) 299 { 300 _rightsEP.removeRight(id); 301 _webinfRights.remove(id); 302 } 303 304 /* --------- */ 305 /* HAS RIGHT */ 306 /* --------- */ 307 308 /** 309 * Checks a permission for the current logged user, on a given object (or context).<br> 310 * If null, it checks if there is at least one object with this permission 311 * @param rightId The name of the right to check. Cannot be null. 312 * @param object The object to check the right. Can be null to search on any object. 313 * @return {@link RightResult#RIGHT_ALLOW}, {@link RightResult#RIGHT_DENY} or {@link RightResult#RIGHT_UNKNOWN} 314 * @throws RightsException if an error occurs. 315 */ 316 public RightResult currentUserHasRight(String rightId, Object object) throws RightsException 317 { 318 return hasRight(_currentUserProvider.getUser(), rightId, object); 319 } 320 321 /** 322 * Checks a permission for a user, on a given object (or context).<br> 323 * If null, it checks if there is at least one object with this permission 324 * @param userIdentity The user identity. Can be null for anonymous 325 * @param rightId The name of the right to check. Cannot be null. 326 * @param object The object to check the right. Can be null to search on any object. 327 * @return {@link RightResult#RIGHT_ALLOW}, {@link RightResult#RIGHT_DENY} or {@link RightResult#RIGHT_UNKNOWN} 328 * @throws RightsException if an error occurs. 329 */ 330 public RightResult hasRight(UserIdentity userIdentity, String rightId, Object object) throws RightsException 331 { 332 UserIdentity objectUserIdentity = userIdentity == null ? __ANONYMOUS_USER_IDENTITY : userIdentity; 333 334 return _hasRight(objectUserIdentity, rightId, object); 335 } 336 337 /** 338 * Gets the right result for anonymous with given right on given object context 339 * @param rightId The id of the right 340 * @param object The object to check 341 * @return the right result for anonymous with given profile on given object context 342 */ 343 public RightResult hasAnonymousRight(String rightId, Object object) 344 { 345 return _hasRight(__ANONYMOUS_USER_IDENTITY, rightId, object); 346 } 347 348 /** 349 * Gets the right result for any connected user with given profile on given object context 350 * @param rightId The right id to test 351 * @param object The object to check 352 * @return the right result for any connected user with given profile on given object context 353 */ 354 public RightResult hasAnyConnectedUserRight(String rightId, Object object) 355 { 356 return _hasRight(__ANY_CONNECTED_USER_IDENTITY, rightId, object); 357 } 358 359 private RightResult _hasRight(UserIdentity userIdentity, String rightId, Object object) 360 { 361 getLogger().debug("Try to determine if user {} has the right '{}' on the object context {}", userIdentity, rightId, object); 362 363 if (StringUtils.isBlank(rightId)) 364 { 365 throw new RightsException("The rightId cannot be null"); 366 } 367 368 return _hasRightOrRead(userIdentity, rightId, object); 369 } 370 371 private RightResult _hasRightOrRead(UserIdentity userIdentity, String rightId, Object object) 372 { 373 if (object == null) 374 { 375 return _hasRightOrRead(userIdentity, rightId); 376 } 377 378 // Try to retrieve in first cache (the one which manages non-null contexts) 379 RightResult cacheResult = _hasRightResultInFirstCache(userIdentity, rightId, object); 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 // Get the objects to check 389 Set<Object> objects = _getConvertedObjects(object, new HashSet<>()); 390 391 // Retrieve the set of AccessResult 392 Set<AccessResult> accessResults = _getAccessResults(userIdentity, groups, rightId, objects); 393 394 // Compute access 395 AccessResult access = AccessResult.merge(accessResults); 396 397 RightResult rightResult = access.toRightResult(); 398 _putInFirstCache(userIdentity, rightId, object, rightResult); 399 400 return rightResult; 401 } 402 403 /** 404 * Has the user/anonymous/anyconnected the non null right on any content of the current workspace? 405 * @param userIdentity The user connecter or the value for anonymous or any connected user 406 * @param rightId The right id to test. Can be null to test read access 407 * @return The computed right result 408 */ 409 private RightResult _hasRightOrRead(UserIdentity userIdentity, String rightId) 410 { 411 // Resolve contexts 412 Set<Object> workspacesContexts = _getConvertedObjects("/${WorkspaceName}", new HashSet<>()); 413 414 // Try to retrieve in second cache (the one which manages null context) 415 RightResult cacheResult = _hasRightResultInSecondCache(workspacesContexts, userIdentity, rightId); 416 if (cacheResult != null) 417 { 418 return cacheResult; 419 } 420 421 // Retrieve groups the user belongs to 422 Set<GroupIdentity> groups = _getGroups(userIdentity); 423 424 RightResult rightResult = RightResult.RIGHT_UNKNOWN; 425 for (String controllerId : _accessControllerEP.getExtensionsIds()) 426 { 427 AccessController accessController = _accessControllerEP.getExtension(controllerId); 428 try 429 { 430 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 431 { 432 if (rightId == null ? accessController.hasAnonymousAnyReadAccessPermissionOnWorkspace(workspacesContexts) : accessController.hasAnonymousAnyPermissionOnWorkspace(workspacesContexts, rightId)) 433 { 434 rightResult = RightResult.RIGHT_ALLOW; 435 break; 436 } 437 } 438 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 439 { 440 if (rightId == null ? accessController.hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(workspacesContexts) : accessController.hasAnyConnectedUserAnyPermissionOnWorkspace(workspacesContexts, rightId)) 441 { 442 rightResult = RightResult.RIGHT_ALLOW; 443 break; 444 } 445 } 446 else 447 { 448 if (rightId == null ? accessController.hasUserAnyReadAccessPermissionOnWorkspace(workspacesContexts, userIdentity, groups) : accessController.hasUserAnyPermissionOnWorkspace(workspacesContexts, userIdentity, groups, rightId)) 449 { 450 rightResult = RightResult.RIGHT_ALLOW; 451 break; 452 } 453 } 454 } 455 catch (Exception e) 456 { 457 getLogger().error("An error occured with controller '{}'. Thus, this controller will be ignored.", controllerId, e); 458 } 459 } 460 461 getLogger().debug("Right result found for [{}, {}, {}] => {}", workspacesContexts, userIdentity, rightId, rightResult); 462 _putInSecondCache(workspacesContexts, userIdentity, rightId, rightResult); 463 return rightResult; 464 } 465 466 private Set<AccessResult> _getAccessResults(UserIdentity userIdentity, Set<GroupIdentity> groups, String rightId, Set<Object> objects) 467 { 468 Set<AccessResult> accessResults = new HashSet<>(); 469 for (Object obj : objects) 470 { 471 for (AccessController accessController : _accessControllerEP.getSupportingExtensions(obj)) 472 { 473 try 474 { 475 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 476 { 477 accessResults.add(rightId == null ? accessController.getReadAccessPermissionForAnonymous(obj) : accessController.getPermissionForAnonymous(rightId, obj)); 478 } 479 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 480 { 481 accessResults.add(rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(obj) : accessController.getPermissionForAnyConnectedUser(rightId, obj)); 482 } 483 else 484 { 485 accessResults.add(rightId == null ? accessController.getReadAccessPermission(userIdentity, groups, obj) : accessController.getPermission(userIdentity, groups, rightId, obj)); 486 } 487 } 488 catch (Exception e) 489 { 490 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessController.getId(), obj, e); 491 } 492 } 493 } 494 495 return accessResults; 496 } 497 498 /* --------------- */ 499 /* HAS READ ACCESS */ 500 /* --------------- */ 501 502 /** 503 * Returns true if the current user has READ access on the given object 504 * @param object The object to check the right. Can be null to search on any object. 505 * @return true if the given user has READ access on the given object 506 */ 507 public boolean currentUserHasReadAccess(Object object) 508 { 509 return hasReadAccess(_currentUserProvider.getUser(), object); 510 } 511 512 /** 513 * Returns true if the given user has READ access on the given object 514 * @param userIdentity The user identity. Cannot be null. 515 * @param object The object to check the right. Can be null to search on any object. 516 * @return true if the given user has READ access on the given object 517 */ 518 public boolean hasReadAccess(UserIdentity userIdentity, Object object) 519 { 520 UserIdentity objectUserIdentity = userIdentity == null ? __ANONYMOUS_USER_IDENTITY : userIdentity; 521 522 return _hasRightOrRead(objectUserIdentity, null, object) == RightResult.RIGHT_ALLOW; 523 } 524 525 /** 526 * Returns true if the object is not restricted, i.e. an anonymous user has READ access (is allowed) on the object 527 * @param object The object to check. Cannot be null 528 * @return true if the object is restricted, i.e. an anonymous user has READ access (is allowed) on the object 529 */ 530 public boolean hasAnonymousReadAccess(Object object) 531 { 532 return _hasRightOrRead(__ANONYMOUS_USER_IDENTITY, null, object) == RightResult.RIGHT_ALLOW; 533 } 534 535 /** 536 * Returns true if any connected user has READ access allowed on the object 537 * @param object The object to check. Cannot be null 538 * @return true if any connected user has READ access allowed on the object 539 */ 540 public boolean hasAnyConnectedUserReadAccess(Object object) 541 { 542 return _hasRightOrRead(__ANY_CONNECTED_USER_IDENTITY, null, object) == RightResult.RIGHT_ALLOW; 543 } 544 545 /* ------------- */ 546 /* ALLOWED USERS */ 547 /* ------------- */ 548 549 /** 550 * Get the list of users that have a particular right in a particular context. 551 * @param rightId The name of the right to check. Cannot be null. 552 * @param object The object to check the right. Cannot be null. 553 * @return The list of users allowed with that right as a Set of String (user identities). 554 * @throws RightsException if an error occurs. 555 */ 556 public AllowedUsers getAllowedUsers(String rightId, Object object) 557 { 558 if (StringUtils.isBlank(rightId)) 559 { 560 throw new RightsException("The rightId cannot be null"); 561 } 562 563 return _getAllowedUsers(rightId, object); 564 } 565 566 /** 567 * Get the users with a READ access on given object 568 * @param object The object 569 * @return The representation of allowed users 570 */ 571 public AllowedUsers getReadAccessAllowedUsers(Object object) 572 { 573 return _getAllowedUsers(null, object); 574 } 575 576 private AllowedUsers _getAllowedUsers(String rightId, Object object) 577 { 578 Optional.ofNullable(object).orElseThrow(() -> 579 { 580 return new RightsException("The object cannot be null"); 581 }); 582 583 // Get the objects to check 584 Set<Object> objects = _getConvertedObjects(object, new HashSet<>()); 585 586 // For each object, retrieve the allowed and denied users/groups 587 Boolean isAnyConnectedAllowed = null; // unknown 588 Set<UserIdentity> allAllowedUsers = new HashSet<>(); 589 Set<UserIdentity> allDeniedUsers = new HashSet<>(); 590 Set<GroupIdentity> allAllowedGroups = new HashSet<>(); 591 Set<GroupIdentity> allDeniedGroups = new HashSet<>(); 592 593 for (Object obj : objects) 594 { 595 for (AccessController accessController : _accessControllerEP.getSupportingExtensions(obj)) 596 { 597 try 598 { 599 if ((rightId == null ? accessController.getReadAccessPermissionForAnonymous(obj) : accessController.getPermissionForAnonymous(rightId, obj)) == AccessResult.ANONYMOUS_ALLOWED) 600 { 601 // Any anonymous user is allowed 602 return new AllowedUsers(true, false, null, null, null, null, _userManager, _groupManager, null); 603 } 604 605 AccessResult permissionForAnyConnectedUser = rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(obj) : accessController.getPermissionForAnyConnectedUser(rightId, obj); 606 if (permissionForAnyConnectedUser == AccessResult.ANY_CONNECTED_DENIED) 607 { 608 // For having any connected user allowed, you need to not have the denied access for one object 609 isAnyConnectedAllowed = Boolean.FALSE; 610 } 611 else if (isAnyConnectedAllowed == null && permissionForAnyConnectedUser == AccessResult.ANY_CONNECTED_ALLOWED) 612 { 613 isAnyConnectedAllowed = Boolean.TRUE; 614 } 615 616 Map<UserIdentity, AccessResult> permissionsByUser = rightId == null ? accessController.getReadAccessPermissionByUser(obj) : accessController.getPermissionByUser(rightId, obj); 617 618 Set<UserIdentity> allowedUsersOnObj = permissionsByUser.entrySet().stream() 619 .filter(entry -> AccessResult.USER_ALLOWED.equals(entry.getValue())) 620 .map(Entry::getKey) 621 .collect(Collectors.toSet()); 622 allAllowedUsers.addAll(allowedUsersOnObj); 623 624 Set<UserIdentity> deniedUsersOnObj = permissionsByUser.entrySet().stream() 625 .filter(entry -> AccessResult.USER_DENIED.equals(entry.getValue())) 626 .map(Entry::getKey) 627 .collect(Collectors.toSet()); 628 allDeniedUsers.addAll(deniedUsersOnObj); 629 630 631 Map<GroupIdentity, AccessResult> permissionsByGroup = rightId == null ? accessController.getReadAccessPermissionByGroup(obj) : accessController.getPermissionByGroup(rightId, obj); 632 633 Set<GroupIdentity> allowedGroupsOnObj = permissionsByGroup.entrySet().stream() 634 .filter(entry -> AccessResult.GROUP_ALLOWED.equals(entry.getValue())) 635 .map(Entry::getKey) 636 .collect(Collectors.toSet()); 637 allAllowedGroups.addAll(allowedGroupsOnObj); 638 639 Set<GroupIdentity> deniedGroupsOnObj = permissionsByGroup.entrySet().stream() 640 .filter(entry -> AccessResult.GROUP_DENIED.equals(entry.getValue())) 641 .map(Entry::getKey) 642 .collect(Collectors.toSet()); 643 allDeniedGroups.addAll(deniedGroupsOnObj); 644 } 645 catch (Exception e) 646 { 647 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessController.getId(), obj, e); 648 } 649 } 650 } 651 652 Request request = ContextHelper.getRequest(_context); 653 @SuppressWarnings("unchecked") 654 List<String> populationContexts = (List<String>) request.getAttribute(PopulationContextHelper.POPULATION_CONTEXTS_REQUEST_ATTR); 655 656 // Then, return the AllowedUsers object 657 return new AllowedUsers(false, isAnyConnectedAllowed != null && isAnyConnectedAllowed.booleanValue(), allAllowedUsers, allDeniedUsers, allAllowedGroups, allDeniedGroups, _userManager, _groupManager, populationContexts != null ? new HashSet<>(populationContexts) : new HashSet<>()); 658 } 659 660 /* --------------- */ 661 /* GET USER RIGHTS */ 662 /* --------------- */ 663 664 /** 665 * Get the list of rights a user is allowed, on a particular object. 666 * @param userIdentity the user identity. Cannot be null. 667 * @param object The object to check the right. Cannot be null. 668 * @return The list of rights as a Set of String (id). 669 * @throws RightsException if an error occurs. 670 */ 671 public Set<String> getUserRights(UserIdentity userIdentity, Object object) throws RightsException 672 { 673 if (userIdentity == null) 674 { 675 throw new RightsException("The userIdentity cannot be null"); 676 } 677 else if (object == null) 678 { 679 throw new RightsException("The object cannot be null"); 680 } 681 682 // Get the objects to check 683 Set<Object> objects = _getConvertedObjects(object, new HashSet<>()); 684 685 // Retrieve groups the user belongs to 686 Set<GroupIdentity> groups = _groupManager.getUserGroups(userIdentity); 687 688 // Gets the access by rights 689 Map<String, AccessResult> accessResultsByRight = _getAccessResultByRight(userIdentity, groups, objects); 690 691 // Keep only positive rights 692 Set<String> allowedRights = accessResultsByRight.entrySet().stream().filter(entry -> entry.getValue().toRightResult() == RightResult.RIGHT_ALLOW).map(entry -> entry.getKey()).collect(Collectors.toSet()); 693 return allowedRights; 694 } 695 696 /** 697 * clear all caches related to RightManager and AccesControllers 698 */ 699 public void clearCaches() 700 { 701 Request request = ContextHelper.getRequest(_context); 702 703 Enumeration<String> attrNames = request.getAttributeNames(); 704 while (attrNames.hasMoreElements()) 705 { 706 String attrName = attrNames.nextElement(); 707 if (attrName != null && attrName.startsWith(AbstractCacheManager.ROLE + "$" + RightManager.ROLE + "$")) 708 { 709 Cache cache = (Cache) request.getAttribute(attrName); 710 if (cache != null) 711 { 712 cache.invalidateAll(); 713 } 714 } 715 } 716 } 717 718 private Map<String, AccessResult> _getAccessResultByRight(UserIdentity userIdentity, Set<GroupIdentity> groups, Set<Object> objects) 719 { 720 Map<String, AccessResult> result = new HashMap<>(); 721 722 for (Object obj : objects) 723 { 724 for (AccessController accessController : _accessControllerEP.getSupportingExtensions(obj)) 725 { 726 try 727 { 728 // Update the result map 729 Map<String, AccessResult> permissionsByRight = accessController.getPermissionByRight(userIdentity, groups, obj); 730 for (String rightId : permissionsByRight.keySet()) 731 { 732 result.put(rightId, AccessResult.merge(result.get(rightId), permissionsByRight.get(rightId))); 733 } 734 } 735 catch (Exception e) 736 { 737 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessController.getId(), obj, e); 738 } 739 } 740 } 741 742 return result; 743 } 744 745 /* ------- */ 746 /* PRIVATE */ 747 /* ------- */ 748 749 private Set<Object> _getConvertedObjects(Object object, Set<Object> alreadyHandledObjects) 750 { 751 Set<Object> finalObjects = new HashSet<>(); 752 753 if (!alreadyHandledObjects.contains(object)) 754 { 755 alreadyHandledObjects.add(object); 756 757 Set<Object> objects = _rightContextConvertorEP.getExtensionsIds().stream() 758 .map(_rightContextConvertorEP::getExtension) 759 .flatMap(convertor -> convertor.convert(object).stream()) 760 .collect(Collectors.toSet()); 761 762 finalObjects.addAll(objects); 763 finalObjects.add(object); 764 765 for (Object convertedObject : objects) 766 { 767 finalObjects.addAll(_getConvertedObjects(convertedObject, alreadyHandledObjects)); 768 } 769 } 770 771 return finalObjects; 772 } 773 774 private Set<GroupIdentity> _getGroups(UserIdentity userIdentity) 775 { 776 if (userIdentity == __ANONYMOUS_USER_IDENTITY || userIdentity == __ANY_CONNECTED_USER_IDENTITY) 777 { 778 return Collections.EMPTY_SET; 779 } 780 else 781 { 782 Set<GroupIdentity> userGroups = _groupManager.getUserGroups(userIdentity); 783 return userGroups; 784 } 785 } 786 787 788 789 private RightResult _hasRightResultInFirstCache(UserIdentity userIdentity, String rightId, Object object) 790 { 791 792 Cache<Cache1Key, RightResult> mapCache = _cacheManager.get(CACHE_1); 793 Cache1Key key = Cache1Key.of(userIdentity, rightId, object); 794 if (mapCache.hasKey(key)) 795 { 796 RightResult cacheResult = mapCache.get(key); 797 getLogger().debug("Find entry in cache for [{}, {}, {}] => {}", userIdentity, rightId, object, cacheResult); 798 return cacheResult; 799 } 800 getLogger().debug("Did not find entry in cache for [{}, {}, {}]", userIdentity, rightId, object); 801 return null; 802 } 803 804 private void _putInFirstCache(UserIdentity userIdentity, String rightId, Object object, RightResult rightResult) 805 { 806 Cache<Cache1Key, RightResult> mapCache = _cacheManager.get(CACHE_1); 807 if (mapCache != null) 808 { 809 mapCache.put(Cache1Key.of(userIdentity, rightId, object), rightResult); 810 } 811 } 812 813 private RightResult _hasRightResultInSecondCache(Set<Object> workspacesContexts, UserIdentity userIdentity, String rightId) 814 { 815 Cache<Cache2Key, RightResult> mapCache = _cacheManager.get(CACHE_2); 816 Cache2Key key = Cache2Key.of(userIdentity, rightId, workspacesContexts); 817 if (mapCache != null) 818 { 819 if (mapCache.hasKey(key)) 820 { 821 RightResult cacheResult = mapCache.get(key); 822 getLogger().debug("Find entry in cache2 for [{}, {}, {}] => {}", workspacesContexts, userIdentity, rightId, cacheResult); 823 return cacheResult; 824 } 825 } 826 827 getLogger().debug("Did not find entry in cache2 for [{}, {}, {}]", workspacesContexts, userIdentity, rightId); 828 return null; 829 } 830 831 private void _putInSecondCache(Set<Object> workspacesContexts, UserIdentity userIdentity, String rightId, RightResult rightResult) 832 { 833 Cache<Cache2Key, RightResult> mapCache = _cacheManager.get(CACHE_2); 834 835 if (mapCache != null) 836 { 837 mapCache.put(Cache2Key.of(userIdentity, rightId, workspacesContexts), rightResult); 838 } 839 } 840 841 /** 842 * Explain why a given user has a right on a context. 843 * 844 * This method will return the explanations provided by every supporting access controller. 845 * Merging the access result of every explanation will return the same result as the 846 * {@link #_hasRight(UserIdentity, String, Object)} method. 847 * 848 * @implNote this method implementation should mirror the {@link #hasRight(UserIdentity, String, Object)} method 849 * without using cache. 850 * 851 * @param userIdentity the user identity or null for anonymous 852 * @param rightId the right to explain or null for read right. 853 * @param object the object to check right on. Can't be null. 854 * @return the list of explanation. 855 */ 856 public List<AccessExplanation> explain(UserIdentity userIdentity, String rightId, Object object) 857 { 858 UserIdentity objectUserIdentity = userIdentity == null ? __ANONYMOUS_USER_IDENTITY : userIdentity; 859 860 if (object == null) 861 { 862 throw new RightsException("The object cannot be null"); 863 } 864 865 // Retrieve groups the user belongs to 866 Set<GroupIdentity> groups = _getGroups(objectUserIdentity); 867 868 // Get the objects to check 869 Set<Object> objects = _getConvertedObjects(object, new HashSet<>()); 870 871 return _explainAccessResults(objectUserIdentity, groups, rightId, objects); 872 } 873 874 /** 875 * Iterate on the objects and access controller to retrieve the explanations of all the supported controllers 876 * @return the explanations 877 */ 878 private List<AccessExplanation> _explainAccessResults(UserIdentity userIdentity, Set<GroupIdentity> groups, String rightId, Set<Object> objects) 879 { 880 List<AccessExplanation> accessExplanations = new ArrayList<>(); 881 882 for (Object object : objects) 883 { 884 for (AccessController accessController : _accessControllerEP.getSupportingExtensions(object)) 885 { 886 try 887 { 888 AccessExplanation explanation = _getAccessControllerExplanation(accessController, userIdentity, groups, rightId, object); 889 890 accessExplanations.add(explanation); 891 } 892 catch (Exception e) 893 { 894 getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", accessController.getId(), object, e); 895 } 896 } 897 } 898 899 return accessExplanations; 900 } 901 902 /** 903 * Get all the permissions that concern a given user. 904 * 905 * The permissions are organized by object context and are paired with a list of explanation 906 * returned by the {@link AccessController} that granted the permission. 907 * 908 * @param userIdentity the user identity 909 * @return all the permissions of a user. 910 */ 911 public Map<ExplanationObject, Map<Permission, List<AccessExplanation>>> getAllPermissions(UserIdentity userIdentity) 912 { 913 // Retrieve groups the user belongs to 914 Set<GroupIdentity> groups = _groupManager.getUserGroups(userIdentity); 915 916 Map<ExplanationObject, Map<Permission, List<AccessExplanation>>> permissionsForUser = new HashMap<>(); 917 918 for (String controllerId : _accessControllerEP.getExtensionsIds()) 919 { 920 AccessController accessController = _accessControllerEP.getExtension(controllerId); 921 922 try 923 { 924 Map<ExplanationObject, Map<Permission, AccessExplanation>> controllerPermissionsForUser = accessController.explainAllPermissions(userIdentity, groups); 925 for (ExplanationObject context : controllerPermissionsForUser.keySet()) 926 { 927 Map<Permission, List<AccessExplanation>> contextExplanations = permissionsForUser.computeIfAbsent(context, o -> new HashMap<>()); 928 Map<Permission, AccessExplanation> contextPermissions = controllerPermissionsForUser.get(context); 929 for (Permission permission : contextPermissions.keySet()) 930 { 931 List<AccessExplanation> rightExplanation = contextExplanations.computeIfAbsent(permission, str -> new ArrayList<>()); 932 rightExplanation.add(contextPermissions.get(permission)); 933 } 934 } 935 } 936 catch (Exception e) 937 { 938 getLogger().error("An error occured while retrieving the permission for the controller '" + controllerId + "'. The controller will be ignored.", e); 939 } 940 } 941 942 return permissionsForUser; 943 } 944 945 private AccessExplanation _getAccessControllerExplanation(AccessController accessController, UserIdentity userIdentity, Set<GroupIdentity> groups, String rightId, Object object) 946 { 947 AccessExplanation explanation; 948 try 949 { 950 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 951 { 952 explanation = rightId == null ? accessController.explainReadAccessPermissionForAnonymous(object) : accessController.explainPermissionForAnonymous(rightId, object); 953 } 954 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 955 { 956 explanation = rightId == null ? accessController.explainReadAccessPermissionForAnyConnectedUser(object) : accessController.explainPermissionForAnyConnectedUser(rightId, object); 957 } 958 else 959 { 960 explanation = rightId == null ? accessController.explainReadAccessPermission(userIdentity, groups, object) : accessController.explainPermission(userIdentity, groups, rightId, object); 961 } 962 } 963 catch (Exception e) 964 { 965 // Try to fallback to the get permission method in case of failure 966 // An explanation without all the supported access controller 967 // may be false. By including a less detailed explanation, we make sure the 968 // general explanation stay correct 969 AccessResult result; 970 if (userIdentity == __ANONYMOUS_USER_IDENTITY) 971 { 972 result = rightId == null ? accessController.getReadAccessPermissionForAnonymous(object) : accessController.getPermissionForAnonymous(rightId, object); 973 } 974 else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY) 975 { 976 result = rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(object) : accessController.getPermissionForAnyConnectedUser(rightId, object); 977 } 978 else 979 { 980 result = rightId == null ? accessController.getReadAccessPermission(userIdentity, groups, object) : accessController.getPermission(userIdentity, groups, rightId, object); 981 } 982 983 explanation = AccessController.getDefaultAccessExplanation(accessController.getId(), result); 984 getLogger().warn("An error occured while explaining access with controller '{}' for object {}. A generic explanation was returned.", accessController.getId(), object, e); 985 } 986 return explanation; 987 } 988 989 /** 990 * Get the detail of all the permission granted on a given context 991 * The permissions are returned organized in 4 different groups: 992 * permission granted to anonymous, any connected, groups, users. 993 * 994 * @param object the object to check permissions 995 * @return the permissions on context 996 */ 997 public ContextPermissions explainAllPermissions(Object object) 998 { 999 Map<Permission, List<AccessExplanation>> permissionsForAnonymous = new HashMap<>(); 1000 Map<Permission, List<AccessExplanation>> permissionsForAnyConnected = new HashMap<>(); 1001 Map<UserIdentity, Map<Permission, List<AccessExplanation>>> permissionsByUser = new HashMap<>(); 1002 Map<GroupIdentity, Map<Permission, List<AccessExplanation>>> permissionsByGroup = new HashMap<>(); 1003 1004 // Get the objects to check 1005 Set<Object> objects = _getConvertedObjects(object, new HashSet<>()); 1006 1007 for (Object objectToCheck : objects) 1008 { 1009 for (String controllerId : _accessControllerEP.getExtensionsIds()) 1010 { 1011 AccessController accessController = _accessControllerEP.getExtension(controllerId); 1012 try 1013 { 1014 if (accessController.supports(objectToCheck)) 1015 { 1016 Map<Permission, AccessExplanation> controllerPermissionsForAnonymous = accessController.explainAllPermissionsForAnonymous(objectToCheck); 1017 for (Permission permission : controllerPermissionsForAnonymous.keySet()) 1018 { 1019 List<AccessExplanation> rightExplanation = permissionsForAnonymous.computeIfAbsent(permission, str -> new ArrayList<>()); 1020 rightExplanation.add(controllerPermissionsForAnonymous.get(permission)); 1021 } 1022 1023 Map<Permission, AccessExplanation> controllerPermissionsForAnyConnected = accessController.explainAllPermissionsForAnyConnected(objectToCheck); 1024 for (Permission permission : controllerPermissionsForAnyConnected.keySet()) 1025 { 1026 List<AccessExplanation> rightExplanation = permissionsForAnyConnected.computeIfAbsent(permission, str -> new ArrayList<>()); 1027 rightExplanation.add(controllerPermissionsForAnyConnected.get(permission)); 1028 } 1029 1030 Map<UserIdentity, Map<Permission, AccessExplanation>> controllerPermissionsByUser = accessController.explainAllPermissionsByUser(objectToCheck); 1031 for (UserIdentity user: controllerPermissionsByUser.keySet()) 1032 { 1033 Map<Permission, List<AccessExplanation>> permissionsForUser = permissionsByUser.computeIfAbsent(user, u -> new HashMap<>()); 1034 Map<Permission, AccessExplanation> controllerPermissionsForUser = controllerPermissionsByUser.get(user); 1035 for (Permission permission : controllerPermissionsForUser.keySet()) 1036 { 1037 List<AccessExplanation> rightExplanation = permissionsForUser.computeIfAbsent(permission, str -> new ArrayList<>()); 1038 rightExplanation.add(controllerPermissionsForUser.get(permission)); 1039 } 1040 } 1041 1042 Map<GroupIdentity, Map<Permission, AccessExplanation>> controllerPermissionsByGroup = accessController.explainAllPermissionsByGroup(objectToCheck); 1043 for (GroupIdentity group: controllerPermissionsByGroup.keySet()) 1044 { 1045 Map<Permission, List<AccessExplanation>> permissionsForUser = permissionsByGroup.computeIfAbsent(group, u -> new HashMap<>()); 1046 Map<Permission, AccessExplanation> controllerPermissionsForGroup = controllerPermissionsByGroup.get(group); 1047 for (Permission permission : controllerPermissionsForGroup.keySet()) 1048 { 1049 List<AccessExplanation> rightExplanation = permissionsForUser.computeIfAbsent(permission, str -> new ArrayList<>()); 1050 rightExplanation.add(controllerPermissionsForGroup.get(permission)); 1051 } 1052 } 1053 } 1054 } 1055 catch (Exception e) 1056 { 1057 getLogger().error("An error occured with controller '{}' while explaining permission for '{}'. Thus, this controller will be ignored.", controllerId, objectToCheck.toString(), e); 1058 } 1059 } 1060 } 1061 1062 return new ContextPermissions(permissionsForAnonymous, permissionsForAnyConnected, permissionsByUser, permissionsByGroup); 1063 } 1064 1065 /** 1066 * Record to describe all the permissions granted on a context 1067 * @param permissionsForAnonymous the permissions for anonymous user 1068 * @param permissionsForAnyConnected the permissions for any connected user 1069 * @param permissionsByUser the permissions for specific users by user identity 1070 * @param permissionsByGroup the permissions for specific groups by group identity 1071 */ 1072 public record ContextPermissions( 1073 Map<Permission, List<AccessExplanation>> permissionsForAnonymous, 1074 Map<Permission, List<AccessExplanation>> permissionsForAnyConnected, 1075 Map<UserIdentity, Map<Permission, List<AccessExplanation>>> permissionsByUser, 1076 Map<GroupIdentity, Map<Permission, List<AccessExplanation>>> permissionsByGroup) { } 1077 1078 static class Cache1Key extends AbstractCacheKey 1079 { 1080 Cache1Key(UserIdentity userIdentity, String rightId, Object object) 1081 { 1082 super(userIdentity, rightId, object); 1083 } 1084 1085 static Cache1Key of(UserIdentity userIdentity, String rightId, Object object) 1086 { 1087 return new Cache1Key(userIdentity, StringUtils.defaultString(rightId), object); 1088 } 1089 1090 } 1091 static class Cache2Key extends AbstractCacheKey 1092 { 1093 Cache2Key(UserIdentity userIdentity, String rightId, Set<Object> workspacesContexts) 1094 { 1095 super(userIdentity, rightId, workspacesContexts); 1096 } 1097 1098 static Cache2Key of(UserIdentity userIdentity, String rightId, Set<Object> workspacesContexts) 1099 { 1100 return new Cache2Key(userIdentity, StringUtils.defaultString(rightId), workspacesContexts); 1101 } 1102 1103 } 1104}