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