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