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.Collection;
019import java.util.Collections;
020import java.util.Comparator;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Optional;
026import java.util.Set;
027import java.util.stream.Collectors;
028
029import org.apache.commons.collections.CollectionUtils;
030import org.apache.commons.lang3.tuple.Pair;
031
032import org.ametys.core.group.GroupIdentity;
033import org.ametys.core.right.AccessController.AccessResult;
034import org.ametys.core.right.ProfileAssignmentStorage.AnonymousOrAnyConnectedKeys;
035import org.ametys.core.right.ProfileAssignmentStorage.UserOrGroup;
036import org.ametys.core.user.UserIdentity;
037import org.ametys.runtime.plugin.ExtensionPoint;
038import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint;
039
040/**
041 * {@link ExtensionPoint} handling {@link ProfileAssignmentStorage}s.
042 */
043public class ProfileAssignmentStorageExtensionPoint extends AbstractThreadSafeComponentExtensionPoint<ProfileAssignmentStorage>
044{
045    /** Avalon Role */
046    public static final String ROLE = ProfileAssignmentStorageExtensionPoint.class.getName();
047    
048    /* ---------- */
049    /* PUBLIC API */
050    /* ---------- */
051    
052    /**
053     * Gets the permissions a user has, given some groups and profiles, on an object.
054     * @param user The user
055     * @param userGroups The groups
056     * @param profileIds The ids of the profiles
057     * @param object The object
058     * @return the permissions a user has, given some groups and profiles on an object.
059     */
060    public Map<String, AccessResult> getPermissions(UserIdentity user, Set<GroupIdentity> userGroups, Set<String> profileIds, Object object)
061    {
062        getLogger().debug("Try to determine permissions for user '{}' and groups {} on context {} for profiles [{}]", user, userGroups, object, profileIds);
063        
064        Map<String, AccessResult> results = new HashMap<>();
065        
066        // 1) First initialize the access results with the allowed profiles for Anonymous
067        Map<AnonymousOrAnyConnectedKeys, Set<String>> profilesForAnonymousAndAnyConnectedUser = getProfilesForAnonymousAndAnyConnectedUser(object);
068        
069        Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_ALLOWED)).orElse(Set.of())
070            .stream()
071            .filter(profileIds::contains)
072            .forEach(p -> results.putIfAbsent(p, AccessResult.ANONYMOUS_ALLOWED));
073        
074        if (results.size() == profileIds.size())
075        {
076            // Stop process here if access were determined for all profiles
077            _logResult(user, userGroups, object, results);
078            return results;
079        }
080        
081        // 2) Add to access results the denied profiles for user (among the still undetermined profiles)
082        Map<UserIdentity, Map<UserOrGroup, Set<String>>> profilesForUsers = getProfilesForUsers(object, user);
083        Optional.ofNullable(profilesForUsers.get(user)).map(a -> a.get(UserOrGroup.DENIED)).orElse(Set.of())
084            .stream()
085            .filter(profileIds::contains)
086            .forEach(p -> results.putIfAbsent(p, AccessResult.USER_DENIED));
087
088        if (results.size() == profileIds.size())
089        {
090            // Stop process here if access were determined for all profiles
091            _logResult(user, userGroups, object, results);
092            return results;
093        }
094        
095        // 3) Add to access results the allowed profiles for user (among the still undetermined profiles)
096        Optional.ofNullable(profilesForUsers.get(user)).map(a -> a.get(UserOrGroup.ALLOWED)).orElse(Set.of())
097            .stream()
098            .filter(profileIds::contains)
099            .forEach(p -> results.putIfAbsent(p, AccessResult.USER_ALLOWED));
100
101        if (results.size() == profileIds.size())
102        {
103            // Stop process here if access were determined for all profiles
104            _logResult(user, userGroups, object, results);
105            return results;
106        }
107        
108        // 4) Add to access results the denied profiles for group (among the still undetermined profiles)
109        Map<GroupIdentity, Map<UserOrGroup, Set<String>>> profilesForGroups = getProfilesForGroups(object, userGroups);
110        for (GroupIdentity userGroup : userGroups)
111        {
112            Optional.ofNullable(profilesForGroups.get(userGroup)).map(a -> a.get(UserOrGroup.DENIED)).orElse(Set.of())
113                .stream()
114                .filter(profileIds::contains)
115                .forEach(p -> results.putIfAbsent(p, AccessResult.GROUP_DENIED));
116        }
117
118        if (results.size() == profileIds.size())
119        {
120            // Stop process here if access were determined for all profiles
121            _logResult(user, userGroups, object, results);
122            return results;
123        }
124        
125        // 5) Add to access results the allowed profiles for group (among the still undetermined profiles)
126        for (GroupIdentity userGroup : userGroups)
127        {
128            Optional.ofNullable(profilesForGroups.get(userGroup)).map(a -> a.get(UserOrGroup.ALLOWED)).orElse(Set.of())
129                .stream()
130                .filter(profileIds::contains)
131                .forEach(p -> results.putIfAbsent(p, AccessResult.GROUP_ALLOWED));
132        }
133        
134        if (results.size() == profileIds.size())
135        {
136            // Stop process here if access were determined for all profiles
137            _logResult(user, userGroups, object, results);
138            return results;
139        }
140        
141        // 6) Add to access results the denied profiles for any connected user (among the still undetermined profiles)
142        Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_DENIED)).orElse(Set.of())
143            .stream()
144            .filter(profileIds::contains)
145            .forEach(p -> results.putIfAbsent(p, AccessResult.ANY_CONNECTED_DENIED));
146
147        if (results.size() == profileIds.size())
148        {
149            // Stop process here if access were determined for all profiles
150            _logResult(user, userGroups, object, results);
151            return results;
152        }
153        
154        // 7) Add to access results the allowed profiles for any connected user (among the still undetermined profiles)
155        Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_ALLOWED)).orElse(Set.of())
156            .stream()
157            .filter(profileIds::contains)
158            .forEach(p -> results.putIfAbsent(p, AccessResult.ANY_CONNECTED_ALLOWED));
159
160        if (results.size() == profileIds.size())
161        {
162            // Stop process here if access were determined for all profiles
163            _logResult(user, userGroups, object, results);
164            return results;
165        }
166        
167        // 8) Add to access results the denied profiles for any connected user (among the still undetermined profiles)
168        Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_DENIED)).orElse(Set.of())
169            .stream()
170            .filter(profileIds::contains)
171            .forEach(p -> results.putIfAbsent(p, AccessResult.ANONYMOUS_DENIED));
172
173        if (results.size() == profileIds.size())
174        {
175            // Stop process here if access were determined for all profiles
176            _logResult(user, userGroups, object, results);
177            return results;
178        }
179        
180        // 9) Finally, add to access results the profiles for which no access could be determined
181        for (String profileId : profileIds)
182        {
183            results.putIfAbsent(profileId, AccessResult.UNKNOWN);
184        }
185        
186        _logResult(user, userGroups, object, results);
187        return results;
188    }
189    
190    private void _logResult(UserIdentity user, Set<GroupIdentity> userGroups, Object object, Map<String, AccessResult> accessResultsByProfile)
191    {
192        getLogger().debug("Access result found for {} and groups {} on context '{}' are {}", user, userGroups, object, accessResultsByProfile);
193    }    
194    
195    /**
196     * Returns some profiles that are matching if the user has a permission on at least one object, given some groups and profiles
197     * @param rootContexts The root contexts object where to seek
198     * @param user The user
199     * @param userGroups The groups
200     * @param profileIds The ids of the profiles
201     * @return If the Set is empty, it means any connected user has no matching profile.<br>
202     *         If the Set is non empty, it contains at least one of the given profile BUT it may not contains all the matching profiles for anyconnected user AND it can contains some other profiles that were not in the given profiles
203     */
204    public Set<String> hasUserAnyPermission(Set<? extends Object> rootContexts, UserIdentity user, Set<GroupIdentity> userGroups, Set<String> profileIds)
205    {
206        getLogger().debug("Try to determine permissions on any context for user '{}' and groups {} with profiles {}", user, userGroups, profileIds);
207        
208        List<ProfileAssignmentStorage> sortedPas = getExtensionsIds().stream()
209                .map(this::getExtension)
210                .sorted(Comparator.comparing(ProfileAssignmentStorage::getPriority))
211                .collect(Collectors.toList());
212        
213        Set<? extends Object> remainingRootsToTest = new HashSet<>(rootContexts);
214        for (ProfileAssignmentStorage profileAssignmentStorage : sortedPas)
215        {
216            if (!remainingRootsToTest.isEmpty())
217            {
218                Set<? extends Object> filteredContexts = remainingRootsToTest.stream().filter(profileAssignmentStorage::isRootContextSupported).collect(Collectors.toSet());
219                if (!filteredContexts.isEmpty())
220                {
221                    Set<String> hasUserAnyPermission = _hasUserAnyPermission(profileAssignmentStorage, filteredContexts, user, userGroups, profileIds);
222                    if (!hasUserAnyPermission.isEmpty())
223                    {
224                        getLogger().debug("Find permission on any context for user '{}' and groups {} with profiles {}", user, userGroups, profileIds);
225                        return hasUserAnyPermission;
226                    }
227                }
228                // Remove the already supported contexts
229                remainingRootsToTest.removeAll(filteredContexts);
230            }
231        }
232        
233        getLogger().debug("Find no permission on any context for user '{}' and groups {} with profiles {}", user, userGroups, profileIds);
234        return Set.of();
235    }
236    
237    private Set<String> _hasUserAnyPermission(ProfileAssignmentStorage profileAssignmentStorage, Set<? extends Object> rootContexts, UserIdentity user, Set<GroupIdentity> userGroups, Set<String> profileIds)
238    {
239        if (profileIds.isEmpty() || rootContexts.isEmpty())
240        {
241            return Set.of();
242        }
243        
244        
245        // 1) Search at least one profile in "allowed-anonymous-profiles"
246        Set<String> hasAnonymousAnyAllowedProfile = profileAssignmentStorage.hasAnonymousAnyAllowedProfile(rootContexts, profileIds);
247        if (!hasAnonymousAnyAllowedProfile.isEmpty())
248        {
249            return hasAnonymousAnyAllowedProfile;
250        }
251        
252        // 2) Search at least one profile in "allowed-profiles" for user
253        Set<String> hasUserAnyAllowedProfile = profileAssignmentStorage.hasUserAnyAllowedProfile(rootContexts, user, profileIds);
254        if (!hasUserAnyAllowedProfile.isEmpty())
255        {
256            return hasUserAnyAllowedProfile;
257        }
258        
259        // 3) Search at least one profile in "allowed-profiles" for groups
260        Set<String> hasGroupAnyAllowedProfile = profileAssignmentStorage.hasGroupAnyAllowedProfile(rootContexts, userGroups, profileIds);
261        if (!hasGroupAnyAllowedProfile.isEmpty())
262        {
263            return hasGroupAnyAllowedProfile;
264        }
265            
266        // 4) Search at least one profile in "allowed-any-connected-profiles"
267        Set<String> hasAnyConnectedAnyAllowedProfile = profileAssignmentStorage.hasAnyConnectedAnyAllowedProfile(rootContexts, profileIds);
268        if (!hasAnyConnectedAnyAllowedProfile.isEmpty())
269        {
270            return hasAnyConnectedAnyAllowedProfile;
271        }
272        
273        // 5) Not found, return nothing
274        return Set.of();
275    }
276
277    /**
278     * Returns some profiles that are matching if anybody has a permission on at least one object, given some profiles
279     * @param rootContexts The root contexts object where to seek
280     * @param profileIds The ids of the profiles
281     * @return If the Set is empty, it means anonymous has no matching profile.<br>
282     *         If the Set is non empty, it contains at least one of the given profile BUT it may not contains all the matching profiles for anonymous AND it can contains some other profiles that were not in the given profiles
283     */
284    public Set<String> hasAnonymousAnyPermission(Set<? extends Object> rootContexts, Set<String> profileIds)
285    {
286        getLogger().debug("Try to determine permissions on any context for anonymous with profiles {}", profileIds);
287        
288        List<ProfileAssignmentStorage> sortedPas = getExtensionsIds().stream()
289                .map(this::getExtension)
290                .sorted(Comparator.comparing(ProfileAssignmentStorage::getPriority))
291                .collect(Collectors.toList());
292        
293        Set<? extends Object> remainingRootsToTest = new HashSet<>(rootContexts);
294        for (ProfileAssignmentStorage profileAssignmentStorage : sortedPas)
295        {
296            if (!remainingRootsToTest.isEmpty())
297            {
298                Set<? extends Object> filteredContexts = remainingRootsToTest.stream().filter(profileAssignmentStorage::isRootContextSupported).collect(Collectors.toSet());
299                if (!filteredContexts.isEmpty())
300                {
301                    Set<String> hasAnonymousAnyPermission = _hasAnonymousAnyPermission(profileAssignmentStorage, filteredContexts, profileIds);
302                    if (!hasAnonymousAnyPermission.isEmpty())
303                    {
304                        getLogger().debug("Find permission on any context for anonymous with profiles {}", profileIds);
305                        return hasAnonymousAnyPermission;
306                    }
307                }
308                // Remove the already supported contexts
309                remainingRootsToTest.removeAll(filteredContexts);
310            }
311        }
312        
313        getLogger().debug("Find no permission on any context for anonymous with profiles {}", profileIds);
314        return Set.of();
315    }
316    
317    private Set<String> _hasAnonymousAnyPermission(ProfileAssignmentStorage profileAssignmentStorage, Set< ? extends Object> rootContexts, Set<String> profileIds)
318    {
319        if (profileIds.isEmpty() || rootContexts.isEmpty())
320        {
321            return Set.of();
322        }
323        
324        // Search at least one profile in "allowed-anonymous-profiles", if found return true
325        return profileAssignmentStorage.hasAnonymousAnyAllowedProfile(rootContexts, profileIds);
326    }
327
328    /**
329     * Returns some profiles that are matching if any connected user has a permission on at least one object, given some profiles
330     * @param rootContexts The root contexts object where to seek
331     * @param profileIds The ids of the profiles
332     * @return If the Set is empty, it means the user has no matching profile.<br>
333     *         If the Set is non empty, it contains at least one of the given profile BUT it may not contains all the matching profiles for the user AND it can contains some other profiles that were not in the given profiles
334     */
335    public Set<String> hasAnyConnectedUserAnyPermission(Set<? extends Object> rootContexts, Set<String> profileIds)
336    {
337        getLogger().debug("Try to determine permissions on any context for any connected user with profiles {}", profileIds);
338        
339        List<ProfileAssignmentStorage> sortedPas = getExtensionsIds().stream()
340                .map(this::getExtension)
341                .sorted(Comparator.comparing(ProfileAssignmentStorage::getPriority))
342                .collect(Collectors.toList());
343        
344        Set<? extends Object> remainingRootsToTest = new HashSet<>(rootContexts);
345        for (ProfileAssignmentStorage profileAssignmentStorage : sortedPas)
346        {
347            if (!remainingRootsToTest.isEmpty())
348            {
349                Set<? extends Object> filteredContexts = remainingRootsToTest.stream().filter(profileAssignmentStorage::isRootContextSupported).collect(Collectors.toSet());
350                if (!filteredContexts.isEmpty())
351                {
352                    Set<String> hasAnyConnectedUserAnyPermission = _hasAnyConnectedUserAnyPermission(profileAssignmentStorage, filteredContexts, profileIds);
353                    if (!hasAnyConnectedUserAnyPermission.isEmpty())
354                    {
355                        getLogger().debug("Find permission on any context for any connected user with profiles {}", profileIds);
356                        return hasAnyConnectedUserAnyPermission;
357                    }
358                }
359                // Remove the already supported contexts
360                remainingRootsToTest.removeAll(filteredContexts);
361            }
362        }
363        
364        getLogger().debug("Find no permission on any context for any connected user with profiles {}", profileIds);
365        return Set.of();
366    }
367    
368    private Set<String> _hasAnyConnectedUserAnyPermission(ProfileAssignmentStorage profileAssignmentStorage, Set< ? extends Object> rootContexts, Set<String> profileIds)
369    {
370        if (profileIds.isEmpty() || rootContexts.isEmpty())
371        {
372            return Set.of();
373        }
374        
375        
376        // 1) Search at least one profile in "allowed-anonymous-profiles", if found return true
377        Set<String> hasAnonymousAnyAllowedProfile = profileAssignmentStorage.hasAnonymousAnyAllowedProfile(rootContexts, profileIds);
378        if (!hasAnonymousAnyAllowedProfile.isEmpty())
379        {
380            return hasAnonymousAnyAllowedProfile;
381        }
382            
383        // 2) Search at least one profile in "allowed-any-connected-profiles", if found return true
384        Set<String> hasAnyConnectedAnyAllowedProfile = profileAssignmentStorage.hasAnyConnectedAnyAllowedProfile(rootContexts, profileIds);
385        if (!hasAnyConnectedAnyAllowedProfile.isEmpty())
386        {
387            return hasAnyConnectedAnyAllowedProfile;
388        }
389        
390        // 3) Not found, return false
391        return Set.of();
392    }
393
394    /**
395     * Gets the permissions a user has on an object, for every profile in the application.
396     * @param user The user
397     * @param userGroups The groups
398     * @param object The object
399     * @return the permissions a user has on an object, for every profile in the application.
400     */
401    public Map<String, AccessResult> getPermissionsByProfile(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
402    {
403        getLogger().debug("Try to determine permissions for each profile on context {} for user '{}' and groups {}", object, user, userGroups);
404        
405        Map<String, AccessResult> result = new HashMap<>();
406        
407        // Allowed profiles for anonymous
408        Map<AnonymousOrAnyConnectedKeys, Set<String>> profilesForAnonymousAndAnyConnectedUser = getProfilesForAnonymousAndAnyConnectedUser(object);
409        Set<String> anonymousAllowed = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_ALLOWED)).orElse(Set.of());
410        Set<String> anonymousDenied = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_DENIED)).orElse(Set.of());
411        ((Collection<String>) CollectionUtils.removeAll(anonymousAllowed, anonymousDenied))
412            .forEach(profile -> result.putIfAbsent(profile, AccessResult.ANONYMOUS_ALLOWED));
413        
414        // Denied profiles for user
415        Map<UserIdentity, Map<UserOrGroup, Set<String>>> profilesForUsers = getProfilesForUsers(object, user);
416        Optional.ofNullable(profilesForUsers.get(user)).map(a -> a.get(UserOrGroup.DENIED)).orElse(Set.of())
417            .forEach(profile -> result.putIfAbsent(profile, AccessResult.USER_DENIED));
418        
419        // Allowed profiles for user
420        Optional.ofNullable(profilesForUsers.get(user)).map(a -> a.get(UserOrGroup.ALLOWED)).orElse(Set.of())
421            .forEach(profile -> result.putIfAbsent(profile, AccessResult.USER_ALLOWED));
422        
423        // Denied profiles for groups
424        Map<GroupIdentity, Map<UserOrGroup, Set<String>>> profilesForGroups = getProfilesForGroups(object, userGroups);
425        for (GroupIdentity userGroup : userGroups)
426        {
427            Optional.ofNullable(profilesForGroups.get(userGroup)).map(a -> a.get(UserOrGroup.DENIED)).orElse(Set.of())
428                .forEach(profile -> result.putIfAbsent(profile, AccessResult.GROUP_DENIED));
429        }
430        
431        // Allowed profiles for groups
432        for (GroupIdentity userGroup : userGroups)
433        {
434            Optional.ofNullable(profilesForGroups.get(userGroup)).map(a -> a.get(UserOrGroup.ALLOWED)).orElse(Set.of())
435                .forEach(profile -> result.putIfAbsent(profile, AccessResult.GROUP_ALLOWED));
436        }
437        
438        // Denied profiles for any connected user
439        Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_DENIED)).orElse(Set.of())
440            .forEach(profile -> result.putIfAbsent(profile, AccessResult.ANY_CONNECTED_DENIED));
441        
442        // Allowed profiles for any connected user
443        Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_ALLOWED)).orElse(Set.of())
444            .forEach(profile -> result.putIfAbsent(profile, AccessResult.ANY_CONNECTED_ALLOWED));
445        
446        // Denied profiles for anonymous
447        anonymousDenied
448            .forEach(profile -> result.putIfAbsent(profile, AccessResult.ANONYMOUS_DENIED));
449        
450        getLogger().debug("The permissions by profile on context {} for user '{}' and groups {} are : {}", object, user, userGroups, result);
451        return result;
452    }
453    
454    /**
455     * Gets the permissions for Anonymous for the given profiles
456     * @param profileIds The profiles to get permissions on
457     * @param object The object
458     * @return the access result for each profile
459     */
460    public AccessResult getPermissionForAnonymous (Set<String> profileIds, Object object)
461    {
462        getLogger().debug("Try to determine permission for Anonymous on context {} and profiles {}", object, profileIds);
463     
464        Map<AnonymousOrAnyConnectedKeys, Set<String>> profilesForAnonymousAndAnyConnectedUser = getProfilesForAnonymousAndAnyConnectedUser(object);
465        
466        Set<String> deniedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_DENIED)).orElse(Set.of());
467        Set<String> allowedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_ALLOWED)).orElse(Set.of());
468        
469        AccessResult result = AccessResult.UNKNOWN;
470        
471        for (String profileId : profileIds)
472        {
473            if (deniedProfiles.contains(profileId))
474            {
475                return AccessResult.ANONYMOUS_DENIED;
476            }
477            else if (allowedProfiles.contains(profileId))
478            {
479                result = AccessResult.ANONYMOUS_ALLOWED;
480            }
481        }
482        
483        return result;
484    }
485    
486    /**
487     * Gets the permissions for Anonymous for the given profiles
488     * @param profileIds The profiles to get permissions on
489     * @param object The object
490     * @return the access result for each profile
491     */
492    public AccessResult getPermissionForAnyConnectedUser (Set<String> profileIds, Object object)
493    {
494        getLogger().debug("Try to determine permission for AnyConnectedUser on context {} and profiles {}", object, profileIds);
495     
496        Map<AnonymousOrAnyConnectedKeys, Set<String>> profilesForAnonymousAndAnyConnectedUser = getProfilesForAnonymousAndAnyConnectedUser(object);
497        
498        Set<String> deniedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_DENIED)).orElse(Set.of());
499        Set<String> allowedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_ALLOWED)).orElse(Set.of());
500        
501        AccessResult result = AccessResult.UNKNOWN;
502        
503        for (String profileId : profileIds)
504        {
505            if (deniedProfiles.contains(profileId))
506            {
507                return AccessResult.ANY_CONNECTED_DENIED;
508            }
509            else if (allowedProfiles.contains(profileId))
510            {
511                result = AccessResult.ANY_CONNECTED_ALLOWED;
512            }
513        }
514        
515        return result;
516    }
517    
518    private AccessResult _getPermissionsByUser(Map<UserOrGroup, Set<String>> userProfiles, Set<String> profileIds)
519    {
520        if (userProfiles != null)
521        {
522            Set<String> deniedProfiles = userProfiles.get(UserOrGroup.DENIED);
523            if (deniedProfiles != null)
524            {
525                if (deniedProfiles.stream().anyMatch(p -> profileIds.contains(p)))
526                {
527                    return AccessResult.USER_DENIED;
528                }
529            }
530    
531            Set<String> allowedProfiles = userProfiles.get(UserOrGroup.ALLOWED);
532            if (allowedProfiles != null)
533            {
534                if (allowedProfiles.stream().anyMatch(p -> profileIds.contains(p)))
535                {
536                    return AccessResult.USER_ALLOWED;
537                }
538            }
539        }
540
541        return null;
542    }
543    
544    /**
545     * Gets the permission by user only on an object, according to the given profiles. It does not take account of the groups of the user, etc.
546     * @param profileIds The ids of the profiles
547     * @param object The object
548     * @return the permission by user only on an object, according to the given profiles
549     */
550    public Map<UserIdentity, AccessResult> getPermissionsByUser(Set<String> profileIds, Object object)
551    {
552        getLogger().debug("Try to determine permissions by users on context {} and profiles {}", object, profileIds);
553        
554        Map<UserIdentity, AccessResult> result = getProfilesForUsers(object, null)
555            .entrySet()
556            .stream()
557            .map(entry -> Pair.of(entry.getKey(), _getPermissionsByUser(entry.getValue(), profileIds)))
558            .filter(pair -> pair.getRight() != null)
559            .collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
560        
561        getLogger().debug("The permissions by users on context {} and profiles {} are: {}", object, profileIds, result);
562        return result;
563    }
564    
565    private AccessResult _getPermissionsByGroup(Map<UserOrGroup, Set<String>> groupProfiles, Set<String> profileIds)
566    {
567        if (groupProfiles != null)
568        {
569            Set<String> deniedProfiles = groupProfiles.get(UserOrGroup.DENIED);
570            if (deniedProfiles != null)
571            {
572                if (deniedProfiles.stream().anyMatch(p -> profileIds.contains(p)))
573                {
574                    return AccessResult.GROUP_DENIED;
575                }
576            }
577    
578            Set<String> allowedProfiles = groupProfiles.get(UserOrGroup.ALLOWED);
579            if (allowedProfiles != null)
580            {
581                if (allowedProfiles.stream().anyMatch(p -> profileIds.contains(p)))
582                {
583                    return AccessResult.GROUP_ALLOWED;
584                }
585            }
586        }
587
588        return null;
589    }
590    
591    /**
592     * Gets the permission by group only on an object, according to the given profiles.
593     * @param profileIds The ids of the profiles
594     * @param object The object
595     * @return the permission by group only on an object, according to the given profiles
596     */
597    public Map<GroupIdentity, AccessResult> getPermissionsByGroup(Set<String> profileIds, Object object)
598    {
599        getLogger().debug("Try to determine permissions by groups on context {} and profiles {}", object, profileIds);
600        
601        Map<GroupIdentity, AccessResult> result = getProfilesForGroups(object, null)
602            .entrySet()
603            .stream()
604            .map(entry -> Pair.of(entry.getKey(), _getPermissionsByGroup(entry.getValue(), profileIds)))
605            .filter(pair -> pair.getRight() != null)
606            .collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
607        
608        getLogger().debug("The permissions by groups on context {} and profiles {} are: ", object, profileIds, result);
609        return result;
610    }
611    
612    /* ------- */
613    /* STORAGE */
614    /* ------- */
615
616    /**
617     * Gets the allowed profiles any connected user has on the given object
618     * @param context The object
619     * @return a map containing allowed/denied profiles that anonymous and any connected user has on the given object
620     */
621    public Map<AnonymousOrAnyConnectedKeys, Set<String>> getProfilesForAnonymousAndAnyConnectedUser(Object context)
622    {
623        return _getFirstProfileAssignmentStorage(context)
624                .map(pas -> pas.getProfilesForAnonymousAndAnyConnectedUser(context))
625                .orElse(Map.of());
626    }
627    
628    /**
629     * Gets the users that have allowed profiles assigned on the given object
630     * @param context The object to test 
631     * @param user The user to get profiles for. Can be null to get profiles for all users that have rights
632     * @return The map of allowed users with their assigned allowed/denied profiles
633     */
634    public Map<UserIdentity, Map<UserOrGroup, Set<String>>> getProfilesForUsers(Object context, UserIdentity user)
635    {
636        return _getFirstProfileAssignmentStorage(context)
637                .map(pas -> pas.getProfilesForUsers(context, user))
638                .orElse(Map.of());
639    }
640    
641    /**
642     * Gets the groups that have allowed profiles assigned on the given object
643     * @param context The object to test 
644     * @param groups The group to get profiles for. Can be null to get profiles for all groups that have rights
645     * @return The map of allowed/denied groups with their assigned profiles
646     */
647    public Map<GroupIdentity, Map<UserOrGroup, Set<String>>> getProfilesForGroups(Object context, Set<GroupIdentity> groups)
648    {
649        return _getFirstProfileAssignmentStorage(context)
650                .map(pas -> pas.getProfilesForGroups(context, groups))
651                .orElse(Map.of());
652    }
653    
654    /* ------------- */
655    /* ANY CONNECTED */
656    /* ------------- */
657    
658    /**
659     * Adds allowed profile any connected user has on the given object
660     * @param context The object context
661     * @param profileId The profile to add
662     */
663    public void allowProfileToAnyConnectedUser(String profileId, Object context)
664    {
665        _getFirstModifiableProfileAssignmentStorage(context)
666                .ifPresent(pas -> pas.addAllowedProfilesForAnyConnectedUser(context, Collections.singleton(profileId)));
667    }
668    
669    /**
670     * Adds denied profile any connected user has on the given object
671     * @param profileId The profile to add
672     * @param context The object context
673     */
674    public void denyProfileToAnyConnectedUser(String profileId, Object context)
675    {
676        _getFirstModifiableProfileAssignmentStorage(context)
677                .ifPresent(pas -> pas.addDeniedProfilesForAnyConnectedUser(context, Collections.singleton(profileId)));
678    }
679    
680    /**
681     * Removes allowed profile any connected user has on the given object
682     * @param profileId The profile to remove
683     * @param context The object context
684     */
685    public void removeAllowedProfileFromAnyConnectedUser(String profileId, Object context)
686    {
687        _getFirstModifiableProfileAssignmentStorage(context)
688                .ifPresent(pas -> pas.removeAllowedProfilesForAnyConnectedUser(context, Collections.singleton(profileId)));
689    }
690    
691    /**
692     * Removes denied profile any connected user has on the given object
693     * @param context The object context
694     * @param profileId The profile to remove
695     */
696    public void removeDeniedProfileFromAnyConnectedUser(String profileId, Object context)
697    {
698        _getFirstModifiableProfileAssignmentStorage(context)
699                .ifPresent(pas -> pas.removeDeniedProfilesForAnyConnectedUser(context, Collections.singleton(profileId)));
700    }
701    
702    /* --------- */
703    /* ANONYMOUS */
704    /* --------- */
705    
706    /**
707     * Adds allowed profile an anonymous user has on the given object
708     * @param profileId The profile to add
709     * @param context The object context
710     */
711    public void allowProfileToAnonymous(String profileId, Object context)
712    {
713        _getFirstModifiableProfileAssignmentStorage(context)
714                .ifPresent(pas -> pas.addAllowedProfilesForAnonymous(context, Collections.singleton(profileId)));
715    }
716    
717    /**
718     * Adds denied profile an anonymous user has on the given object
719     * @param profileId The profile to add
720     * @param context The object context
721     */
722    public void denyProfileToAnonymous(String profileId, Object context)
723    {
724        _getFirstModifiableProfileAssignmentStorage(context)
725                .ifPresent(pas -> pas.addDeniedProfilesForAnonymous(context, Collections.singleton(profileId)));
726    }
727    
728    /**
729     * Removes allowed profile an anonymous user has on the given object
730     * @param profileId The profile to remove
731     * @param context The object context
732     */
733    public void removeAllowedProfileFromAnonymous(String profileId, Object context)
734    {
735        _getFirstModifiableProfileAssignmentStorage(context)
736                .ifPresent(pas -> pas.removeAllowedProfilesForAnonymous(context, Collections.singleton(profileId)));
737    }
738    
739    /**
740     * Removes denied profile an anonymous user has on the given object
741     * @param context The object context
742     * @param profileId The profile to remove
743     */
744    public void removeDeniedProfileFromAnonymous(String profileId, Object context)
745    {
746        _getFirstModifiableProfileAssignmentStorage(context)
747                .ifPresent(pas -> pas.removeDeniedProfilesForAnonymous(context, Collections.singleton(profileId)));
748    }
749    
750    /* ----- */
751    /* USERS */
752    /* ----- */
753    
754    /**
755     * Allows a user to a profile on a given object
756     * @param user The user to add
757     * @param profileId The id of the profile
758     * @param context The object context
759     */
760    public void allowProfileToUser(UserIdentity user, String profileId, Object context)
761    {
762        _getFirstModifiableProfileAssignmentStorage(context)
763                .ifPresent(pas -> pas.addAllowedUsers(Collections.singleton(user), context, profileId));
764    }
765    
766    /**
767     * Denies a user to a profile on a given object
768     * @param user The user to add
769     * @param profileId The id of the profile
770     * @param context The object context
771     */
772    public void denyProfileToUser(UserIdentity user, String profileId, Object context)
773    {
774        _getFirstModifiableProfileAssignmentStorage(context)
775                .ifPresent(pas -> pas.addDeniedUsers(Collections.singleton(user), context, profileId));
776    }
777    
778    /**
779     * Removes the association between a user and an allowed profile on a given object
780     * @param user The user to remove
781     * @param context The object context
782     * @param profileId The id of the profile
783     */
784    public void removeAllowedProfileFromUser(UserIdentity user, String profileId, Object context)
785    {
786        _getFirstModifiableProfileAssignmentStorage(context)
787                .ifPresent(pas -> pas.removeAllowedUsers(Collections.singleton(user), context, profileId));
788    }
789    
790    /**
791     * Removes the association between a user and a denied profile on a given object
792     * @param user The user to remove
793     * @param profileId The id of the profile
794     * @param context The object context
795     */
796    public void removeDeniedProfileFromUser(UserIdentity user, String profileId, Object context)
797    {
798        _getFirstModifiableProfileAssignmentStorage(context)
799                .ifPresent(pas -> pas.removeDeniedUsers(Collections.singleton(user), context, profileId));
800    }
801    
802    
803    /* ------ */
804    /* GROUPS */
805    /* ------ */
806    
807    /**
808     * Allows a group to a profile on a given object
809     * @param group The group to add
810     * @param profileId The id of the profile
811     * @param context The object context
812     */
813    public void allowProfileToGroup(GroupIdentity group, String profileId, Object context)
814    {
815        _getFirstModifiableProfileAssignmentStorage(context)
816                .ifPresent(pas -> pas.addAllowedGroups(Collections.singleton(group), context, profileId));
817    }
818    
819    /**
820     * Denies a group to a profile on a given object
821     * @param group The group to add
822     * @param profileId The id of the profile
823     * @param context The object context
824     */
825    public void denyProfileToGroup(GroupIdentity group, String profileId, Object context)
826    {
827        _getFirstModifiableProfileAssignmentStorage(context)
828                .ifPresent(pas -> pas.addDeniedGroups(Collections.singleton(group), context, profileId));
829    }
830    
831    /**
832     * Removes the association between a group and an allowed profile on a given object
833     * @param group The group to remove
834     * @param profileId The id of the profile
835     * @param context The object context
836     */
837    public void removeAllowedProfileFromGroup(GroupIdentity group, String profileId, Object context)
838    {
839        _getFirstModifiableProfileAssignmentStorage(context)
840                .ifPresent(pas -> pas.removeAllowedGroups(Collections.singleton(group), context, profileId));
841    }
842    
843    /**
844     * Removes the association between a group and a denied profile on a given object
845     * @param group The group to remove
846     * @param profileId The id of the profile
847     * @param context The object context
848     */
849    public void removeDeniedProfileFromGroup(GroupIdentity group, String profileId, Object context)
850    {
851        _getFirstModifiableProfileAssignmentStorage(context)
852                .ifPresent(pas -> pas.removeDeniedGroups(Collections.singleton(group), context, profileId));
853    }
854    
855    /* ----------- */
856    /* INHERITANCE */
857    /* ----------- */
858    /**
859     * Determines if the inheritance of permissions is disallowed on a given context
860     * @param context The object context
861     * @return true if the inheritance is disallowed
862     */
863    public boolean isInheritanceDisallowed(Object context)
864    {
865        return _getFirstProfileAssignmentStorage(context)
866            .map(pas -> pas.isInheritanceDisallowed(context))
867            .orElse(Boolean.FALSE);
868    }
869    
870    /**
871     * Allow or disallow the inheritance of permissions on a given context
872     * @param context The object context
873     * @param disallow true to disallow the inheritance
874     */
875    public void disallowInheritance(Object context, boolean disallow)
876    {
877        _getFirstModifiableProfileAssignmentStorage(context)
878            .ifPresent(pas -> pas.disallowInheritance(context, disallow));
879    }
880    
881    /* -------------------------- */
882    /* PRIVATE CONVENIENT METHODS */
883    /* -------------------------- */
884    
885    private Optional<ProfileAssignmentStorage> _getFirstProfileAssignmentStorage(Object object)
886    {
887        return getExtensionsIds().stream()
888                .map(this::getExtension)
889                .sorted(Comparator.comparing(ProfileAssignmentStorage::getPriority))
890                .filter(pas -> pas.isSupported(object))
891                .findFirst();
892    }
893    
894    private Optional<ModifiableProfileAssignmentStorage> _getFirstModifiableProfileAssignmentStorage(Object object)
895    {
896        return getExtensionsIds().stream()
897                .map(this::getExtension)
898                .sorted(Comparator.comparing(ProfileAssignmentStorage::getPriority))
899                .filter(pas -> pas.isSupported(object) && pas instanceof ModifiableProfileAssignmentStorage)
900                .map(ModifiableProfileAssignmentStorage.class::cast)
901                .findFirst();
902    }
903}