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