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.util.Arrays; 019import java.util.Collection; 020import java.util.Comparator; 021import java.util.Map; 022import java.util.Set; 023 024import org.apache.commons.lang3.StringUtils; 025 026import org.ametys.core.group.GroupIdentity; 027import org.ametys.core.right.RightManager.RightResult; 028import org.ametys.core.user.UserIdentity; 029import org.ametys.runtime.i18n.I18nizableText; 030import org.ametys.runtime.plugin.component.Supporter; 031 032/** 033 * This interface is for computing the rights a user has. 034 */ 035public interface AccessController extends Supporter<Object> 036{ 037 /** 038 * The access result when looking for a right 039 */ 040 public enum AccessResult 041 { 042 /* Note: the order of the values are important ! */ 043 044 /** If the user is allowed because the right is allowed for any anonymous user */ 045 ANONYMOUS_ALLOWED, 046 /** If the user is directly denied */ 047 USER_DENIED, 048 /** If the user is directly allowed */ 049 USER_ALLOWED, 050 /** If the user is denied through its groups and not directly */ 051 GROUP_DENIED, 052 /** If the user is allowed through its groups and not directly */ 053 GROUP_ALLOWED, 054 /** If the user is denied because the right is denied for any connected user */ 055 ANY_CONNECTED_DENIED, 056 /** If the user is allowed because the right is allowed for any connected user */ 057 ANY_CONNECTED_ALLOWED, 058 /** If the user is denied because the right is denied for any anonymous user */ 059 ANONYMOUS_DENIED, 060 /** Cannot determine */ 061 UNKNOWN; 062 063 /** 064 * Convert the value to an access result 065 * @return denied enumerated will be converted to deny, allow to allow and unknown to unknown 066 */ 067 public RightResult toRightResult() 068 { 069 switch (this) 070 { 071 case USER_DENIED: 072 case GROUP_DENIED: 073 case ANY_CONNECTED_DENIED: 074 case ANONYMOUS_DENIED: 075 return RightResult.RIGHT_DENY; 076 case USER_ALLOWED: 077 case GROUP_ALLOWED: 078 case ANY_CONNECTED_ALLOWED: 079 case ANONYMOUS_ALLOWED: 080 return RightResult.RIGHT_ALLOW; 081 case UNKNOWN: 082 default: 083 return RightResult.RIGHT_UNKNOWN; 084 } 085 } 086 087 /** 088 * Merge several access results to keep only the more important 089 * @param accessResults The access results to merge. Cannot be null but can be empty. 090 * @return The more important access result 091 */ 092 public static AccessResult merge(Collection<AccessResult> accessResults) 093 { 094 return accessResults.stream().filter(r -> r != null).min(Comparator.naturalOrder()).orElse(AccessResult.UNKNOWN); 095 } 096 097 /** 098 * Merge several access results to keep only the more important 099 * @param accessResults The access results to merge. Cannot be null but can be empty. 100 * @return The more important access result 101 */ 102 public static AccessResult merge(AccessResult... accessResults) 103 { 104 return Arrays.stream(accessResults).filter(r -> r != null).min(Comparator.naturalOrder()).orElse(AccessResult.UNKNOWN); 105 } 106 } 107 108 /** 109 * Get the id of this controller 110 * @return the id of this controller 111 */ 112 public String getId(); 113 114 /** 115 * Gets the kind of access a user has on an object for a given right 116 * @param user The user. Cannot be null. 117 * @param userGroups The groups the user belongs to 118 * @param rightId The id of the right of the user 119 * @param object The context object to check the access 120 * @return the kind of access a user has on an object for a right 121 */ 122 public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object); 123 124 /** 125 * Gets the kind of access a user has on an object for thye read access 126 * @param user The user. Cannot be null. 127 * @param userGroups The groups the user belongs to 128 * @param object The context object to check the access 129 * @return the kind of access a user has on an object for the read access 130 */ 131 public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object); 132 133 /** 134 * Gets the kind of access a user has on an object for all rights 135 * @param user The user. Cannot be null. 136 * @param userGroups The groups the user belongs to 137 * @param object The context object to check the access 138 * @return the kind of access a user has on an object for all rights 139 */ 140 public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object); 141 142 /** 143 * Gets the permission for Anonymous only on an object for a given right 144 * @param rightId The id of the right to check 145 * @param object The object 146 * @return the permission for Anonymous only on an object for a given right 147 */ 148 public AccessResult getPermissionForAnonymous(String rightId, Object object); 149 150 /** 151 * Gets the read access permission for Anonymous only on an object 152 * @param object The object 153 * @return the read access permission for Anonymous only on an object 154 */ 155 public AccessResult getReadAccessPermissionForAnonymous(Object object); 156 157 /** 158 * Gets the permission for any connected user only on an object for a given right 159 * @param rightId The id of the right to check 160 * @param object The object 161 * @return the permission for any connected user only on an object for a given right 162 */ 163 public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object); 164 165 /** 166 * Gets the read access permission for any connected user only on an object 167 * @param object The object 168 * @return the read access permission for any connected user only on an object 169 */ 170 public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object); 171 172 /** 173 * Gets the permission by user only on an object for the given right. It does not take account of the groups of the user, etc. 174 * @param rightId The id of the right to check 175 * @param object The object 176 * @return the permission by user only on an object for the given right 177 */ 178 public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object); 179 180 /** 181 * Gets the read access permission by user only on an object. It does not take account of the groups of the user, etc. 182 * @param object The object 183 * @return the read access permission by user only on an object 184 */ 185 public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object); 186 187 /** 188 * Gets the permission by group only on an object for the given right. 189 * @param rightId The id of the right to check 190 * @param object The object 191 * @return the permission by group only on an object for the given right 192 */ 193 public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object); 194 195 /** 196 * Gets the read access permission by group only on an object. 197 * @param object The object 198 * @return the read access permission by group only on an object 199 */ 200 public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object); 201 202 /** 203 * Returns true if the user has a permission on at least one object, directly or though groups, for a given rights and if the object is attached to the given context that is /${WorkspaceName} and its conversions.<br> 204 * @param workspacesContexts The contexts to tests such as {"/${WorkspaceName}", "/repository", "/admin"} 205 * @param user The user 206 * @param userGroups The groups 207 * @param rightId The id of the right to check 208 * @return true if the user has a permission on at least one object, directly or though groups, for a given right 209 */ 210 public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId); 211 212 /** 213 * Returns true if the user has a read access permission on at least one object, directly or though groups, for a given rights and if the object is attached to the given context that is /${WorkspaceName} and its conversions.<br> 214 * @param workspacesContexts The contexts to tests such as {"/${WorkspaceName}", "/repository", "/admin"} 215 * @param user The user 216 * @param userGroups The groups 217 * @return true if the user has a permission on at least one object, directly or though groups, for a given right 218 */ 219 public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups); 220 221 /** 222 * Returns true if anonymous has a permission on at least one object, directly or though groups, for a given rights and if the object is attached to the given context that is /${WorkspaceName} and its conversions.<br> 223 * @param workspacesContexts The contexts to tests such as {"/${WorkspaceName}", "/repository", "/admin"} 224 * @param rightId The id of the right to check 225 * @return true if anonymous has a permission on at least one object, directly or though groups, for a given right 226 */ 227 public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId); 228 229 /** 230 * Returns true if anonymous has a read access permission on at least one object, directly or though groups, for a given rights and if the object is attached to the given context that is /${WorkspaceName} and its conversions.<br> 231 * @param workspacesContexts The contexts to tests such as {"/${WorkspaceName}", "/repository", "/admin"} 232 * @return true if anonymous has a permission on at least one object, directly or though groups, for a given right 233 */ 234 public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts); 235 236 /** 237 * Returns true if any connected user has a permission on at least one object, directly or though groups, for a given rights and if the object is attached to the given context that is /${WorkspaceName} and its conversions.<br> 238 * @param workspacesContexts The contexts to tests such as {"/${WorkspaceName}", "/repository", "/admin"} 239 * @param rightId The id of the right to check 240 * @return true if any connected user has a permission on at least one object, directly or though groups, for a given right 241 */ 242 public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId); 243 244 /** 245 * Returns true if any connected user has a read access permission on at least one object, directly or though groups, for a given rights and if the object is attached to the given context that is /${WorkspaceName} and its conversions.<br> 246 * @param workspacesContexts The contexts to tests such as {"/${WorkspaceName}", "/repository", "/admin"} 247 * @return true if any connected user has a permission on at least one object, directly or though groups, for a given right 248 */ 249 public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts); 250 251 /** 252 * Explain the read access permission for anonymous on the given object. 253 * 254 * The access result in the explanation MUST be the same value as the one returned by 255 * {@link #getReadAccessPermissionForAnonymous(Object)}. And the explanation should described the 256 * actual object that granted the right to allow final user to see if any context conversion happened 257 * @param object the object to test 258 * @return an explanation of the access 259 */ 260 public default AccessExplanation explainReadAccessPermissionForAnonymous(Object object) 261 { 262 return getDefaultAccessExplanation(getId(), getReadAccessPermissionForAnonymous(object)); 263 } 264 /** 265 * Explain the permission for anonymous on the given object. 266 * 267 * The access result in the explanation MUST be the same value as the one returned by 268 * {@link #getPermissionForAnonymous(String, Object)}. And the explanation should described the 269 * actual object that granted the right to allow final user to see if any context conversion happened 270 * @param rightId the right to test 271 * @param object the object to test 272 * @return an explanation of the access 273 */ 274 public default AccessExplanation explainPermissionForAnonymous(String rightId, Object object) 275 { 276 return getDefaultAccessExplanation(getId(), getPermissionForAnonymous(rightId, object)); 277 } 278 279 /** 280 * Explain the read access permission for any connected user on the given object. 281 * 282 * The access result in the explanation MUST be the same value as the one returned by 283 * {@link #getReadAccessPermissionForAnyConnectedUser(Object)}. And the explanation should described the 284 * actual object that granted the right to allow final user to see if any context conversion happened 285 * @param object the object to test 286 * @return an explanation of the access 287 */ 288 public default AccessExplanation explainReadAccessPermissionForAnyConnectedUser(Object object) 289 { 290 return getDefaultAccessExplanation(getId(), getReadAccessPermissionForAnyConnectedUser(object)); 291 } 292 293 /** 294 * Explain the permission for any connected user on the given object. 295 * 296 * The access result in the explanation MUST be the same value as the one returned by 297 * {@link #getPermissionForAnyConnectedUser(String, Object)}. And the explanation should described the 298 * actual object that granted the right to allow final user to see if any context conversion happened 299 * @param rightId the right to test 300 * @param object the object to test 301 * @return an explanation of the access 302 */ 303 public default AccessExplanation explainPermissionForAnyConnectedUser(String rightId, Object object) 304 { 305 return getDefaultAccessExplanation(getId(), getPermissionForAnyConnectedUser(rightId, object)); 306 } 307 308 /** 309 * Explain the read access permission for a user on the given object. 310 * 311 * The access result in the explanation MUST be the same value as the one returned by 312 * {@link #getReadAccessPermission(UserIdentity, Set, Object)}. And the explanation should described the 313 * actual object that granted the right to allow final user to see if any context conversion happened 314 * @param user the user to test 315 * @param groups the groups of the user 316 * @param object the object to test 317 * @return an explanation of the access 318 */ 319 public default AccessExplanation explainReadAccessPermission(UserIdentity user, Set<GroupIdentity> groups, Object object) 320 { 321 return getDefaultAccessExplanation(getId(), getReadAccessPermission(user, groups, object)); 322 } 323 324 /** 325 * Explain the permission for a user on the given object. 326 * 327 * The access result in the explanation MUST be the same value as the one returned by 328 * {@link #getPermission(UserIdentity, Set, String, Object)}. And the explanation should described the 329 * actual object that granted the right to allow final user to see if any context conversion happened 330 * @param user the user to test 331 * @param groups the groups of the user 332 * @param rightId the right to test 333 * @param object the object to test 334 * @return an explanation of the access 335 */ 336 public default AccessExplanation explainPermission(UserIdentity user, Set<GroupIdentity> groups, String rightId, Object object) 337 { 338 return getDefaultAccessExplanation(getId(), getPermission(user, groups, rightId, object)); 339 } 340 341 /** 342 * Build a default explanation for an access result provided by a controller. 343 * 344 * This method should be used as a fallback. 345 * AccessController should provide their own explanation with more details 346 * @param controllerId the access controller id 347 * @param result the access result 348 * @return an label describing the result 349 */ 350 public static AccessExplanation getDefaultAccessExplanation(String controllerId, AccessResult result) 351 { 352 I18nizableText label = new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHTS_EXPLAIN_TOOL_RESULT_" + result.name(), Map.of("controllerId", new I18nizableText(controllerId))); 353 return new AccessExplanation(controllerId, result, label); 354 } 355 356 /** 357 * Get {@link AccessExplanation} for each permission given to the user by this access controller. 358 * 359 * Returns a pair of permission/access explanation for each object with a granted or denied permission to this user by this access controller. 360 * 361 * Each explanation should be equivalent to calling the {@link #explainPermission(UserIdentity, Set, String, Object)} 362 * or {@link #explainReadAccessPermission(UserIdentity, Set, Object)} for the user, on the object with the corresponding right 363 * 364 * @param identity the user identity 365 * @param groups the groups the user belongs to. 366 * @return all the user's permissions handled by this controller 367 */ 368 public Map<ExplanationObject, Map<Permission, AccessExplanation>> explainAllPermissions(UserIdentity identity, Set<GroupIdentity> groups); 369 370 371 /** 372 * Get the explanation object representing the object 373 * @param object the object 374 * @return the explanation object 375 */ 376 public default ExplanationObject getExplanationObject(Object object) 377 { 378 return new ExplanationObject( 379 object, 380 getObjectLabel(object), 381 getObjectCategory(object), 382 getObjectPriority(object) 383 ); 384 } 385 386 /** 387 * Get a label describing the object handled by this access controller 388 * @param object the object 389 * @return the label 390 */ 391 public abstract I18nizableText getObjectLabel(Object object); 392 393 394 /** 395 * Get a label classifying the object handled by this access controller 396 * @param object the object 397 * @return the label 398 */ 399 public I18nizableText getObjectCategory(Object object); 400 401 402 /** 403 * Get the priority of the object to order it in its category 404 * @param object the object 405 * @return the priority 406 */ 407 public default int getObjectPriority(Object object) 408 { 409 return 0; 410 } 411 412 413 /** 414 * A object with an associated label, category and order. 415 * 416 * @param object the object 417 * @param label a label for the context represented by the object 418 * @param category the category the context belongs to 419 * @param order order of the context in the category 420 */ 421 public record ExplanationObject(Object object, I18nizableText label, I18nizableText category, int order) { 422 /** 423 * An object with an associated label and category. 424 * 425 * @param object the object 426 * @param label a label for the context represented by the object 427 * @param category the category the context belongs to 428 */ 429 public ExplanationObject(Object object, I18nizableText label, I18nizableText category) 430 { 431 this(object, label, category, 0); 432 } 433 434 @Override 435 public final boolean equals(Object other) 436 { 437 if (other instanceof ExplanationObject otherContext) 438 { 439 return object.equals(otherContext.object()) 440 && (category == null && otherContext.category() == null 441 || category != null && category.equals(otherContext.category())); 442 } 443 return false; 444 } 445 446 @Override 447 public final int hashCode() 448 { 449 return object.hashCode() + (category != null ? 23 * category.hashCode() : 0); 450 } 451 } 452 453 /** 454 * A permission given by an AccessController 455 * @param type the type of permission given 456 * @param id the id of the permission (right's id or profile's id). Can be null for types {@link PermissionType#READ} or {@link PermissionType#ALL_RIGHTS} 457 */ 458 public record Permission(PermissionType type, String id) { 459 @Override 460 public String toString() 461 { 462 if (StringUtils.isNotBlank(id)) 463 { 464 return type.name() + "#" + id; 465 } 466 else 467 { 468 return type.name(); 469 } 470 } 471 /** 472 * A type of permission 473 */ 474 public enum PermissionType 475 { 476 /** Read access */ 477 READ, 478 /** A single right */ 479 RIGHT, 480 /** A profile */ 481 PROFILE, 482 /** All rights */ 483 ALL_RIGHTS 484 } 485 } 486 487 /** 488 * Get all the permissions granted to an anonymous user on the object. 489 * @param object the right context to check. it must be supported by the controller (see {@link #supports} 490 * @return the permissions with their associated explanation 491 */ 492 public Map<Permission, AccessExplanation> explainAllPermissionsForAnonymous(Object object); 493 494 /** 495 * Get all the permissions granted to any connected user on the object. 496 * The result must only include permissions specific to any connected user 497 * 498 * @param object the right context to check. it must be supported by the controller (see {@link #supports} 499 * @return the permissions with their associated explanation 500 */ 501 public Map<Permission, AccessExplanation> explainAllPermissionsForAnyConnected(Object object); 502 503 /** 504 * Get all the permissions granted to users on the object. 505 * The result must only include permissions specific to a user 506 * 507 * @param object the right context to check. it must be supported by the controller (see {@link #supports} 508 * @return the permissions with their associated explanation for each user 509 */ 510 public Map<UserIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByUser(Object object); 511 512 /** 513 * Get all the permissions granted to groups on the object. 514 * The result must only include permissions specific to a group 515 * 516 * @param object the right context to check. it must be supported by the controller (see {@link #supports} 517 * @return the permissions with their associated explanation for each group 518 */ 519 public Map<GroupIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByGroup(Object object); 520}