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