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}