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