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_CONNECTED_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_CONNECTED_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, new HashSet<>());
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}", new HashSet<>());
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)
395                {
396                    if (rightId == null ? accessController.hasAnonymousAnyReadAccessPermissionOnWorkspace(workspacesContexts) : accessController.hasAnonymousAnyPermissionOnWorkspace(workspacesContexts, rightId))
397                    {
398                        rightResult = RightResult.RIGHT_ALLOW;
399                        break;
400                    }
401                }
402                else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY)
403                {
404                    if (rightId == null ? accessController.hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(workspacesContexts) : accessController.hasAnyConnectedUserAnyPermissionOnWorkspace(workspacesContexts, rightId))
405                    {
406                        rightResult = RightResult.RIGHT_ALLOW;
407                        break;
408                    }
409                }
410                else
411                {
412                    if (rightId == null ? accessController.hasUserAnyReadAccessPermissionOnWorkspace(workspacesContexts, userIdentity, groups) : accessController.hasUserAnyPermissionOnWorkspace(workspacesContexts, userIdentity, groups, rightId))
413                    {
414                        rightResult = RightResult.RIGHT_ALLOW;
415                        break;
416                    }
417                }
418            }
419            catch (Exception e)
420            {
421                getLogger().error("An error occured with controller '{}'. Thus, this controller will be ignored.", controllerId, e);
422            }
423        }
424        
425        getLogger().debug("Right result found for [{}, {}, {}] => {}", workspacesContexts, userIdentity, rightId, rightResult);
426        _putInSecondCache(workspacesContexts, userIdentity, rightId, rightResult);
427        return rightResult;
428    }
429    
430    private Set<AccessResult> _getAccessResults(UserIdentity userIdentity, Set<GroupIdentity> groups, String rightId, Set<Object> objects)
431    {
432        Set<AccessResult> accessResults = new HashSet<>();
433        for (Object obj : objects)
434        {
435            for (String controllerId : _accessControllerEP.getExtensionsIds())
436            {
437                AccessController accessController = _accessControllerEP.getExtension(controllerId);
438                try
439                {
440                    if (accessController.isSupported(obj))
441                    {
442                        if (userIdentity == __ANONYMOUS_USER_IDENTITY)
443                        {
444                            accessResults.add(rightId == null ? accessController.getReadAccessPermissionForAnonymous(obj) : accessController.getPermissionForAnonymous(rightId, obj));
445                        }
446                        else if (userIdentity == __ANY_CONNECTED_USER_IDENTITY)
447                        {
448                            accessResults.add(rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(obj) : accessController.getPermissionForAnyConnectedUser(rightId, obj));
449                        }
450                        else
451                        {
452                            accessResults.add(rightId == null ? accessController.getReadAccessPermission(userIdentity, groups, obj) : accessController.getPermission(userIdentity, groups, rightId, obj));
453                        }
454                    }
455                    else
456                    {
457                        accessResults.add(AccessResult.UNKNOWN);
458                    }
459                }
460                catch (Exception e)
461                {
462                    getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", controllerId, obj, e);
463                }
464            }
465        }
466        
467        return accessResults;
468    }
469    
470    /* --------------- */
471    /* HAS READ ACCESS */
472    /* --------------- */
473    
474    /**
475     * Returns true if the current user has READ access on the given object
476     * @param object The object to check the right. Can be null to search on any object.
477     * @return true if the given user has READ access on the given object
478     */
479    public boolean currentUserHasReadAccess(Object object)
480    {
481        return hasReadAccess(_currentUserProvider.getUser(), object);
482    }
483
484    /**
485     * Returns true if the given user has READ access on the given object
486     * @param userIdentity The user identity. Cannot be null.
487     * @param object The object to check the right. Can be null to search on any object.
488     * @return true if the given user has READ access on the given object
489     */
490    public boolean hasReadAccess(UserIdentity userIdentity, Object object)
491    {
492        UserIdentity objectUserIdentity = userIdentity == null ? __ANONYMOUS_USER_IDENTITY : userIdentity;
493
494        return _hasRightOrRead(objectUserIdentity, null, object) == RightResult.RIGHT_ALLOW;
495    }
496    
497    /**
498     * Returns true if the object is not restricted, i.e. an anonymous user has READ access (is allowed) on the object
499     * @param object The object to check. Cannot be null
500     * @return true if the object is restricted, i.e. an anonymous user has READ access (is allowed) on the object
501     */
502    public boolean hasAnonymousReadAccess(Object object)
503    {
504        return _hasRightOrRead(__ANONYMOUS_USER_IDENTITY, null, object) == RightResult.RIGHT_ALLOW;
505    }
506    
507    /**
508     * Returns true if any connected user has READ access allowed on the object
509     * @param object The object to check. Cannot be null
510     * @return true if any connected user has READ access allowed on the object
511     */
512    public boolean hasAnyConnectedUserReadAccess(Object object)
513    {
514        return _hasRightOrRead(__ANY_CONNECTED_USER_IDENTITY, null, object) == RightResult.RIGHT_ALLOW;
515    }
516    
517    /* ------------- */
518    /* ALLOWED USERS */
519    /* ------------- */
520
521    /**
522     * Get the list of users that have a particular right in a particular context.
523     * @param rightId The name of the right to check. Cannot be null.
524     * @param object The object to check the right. Cannot be null.
525     * @return The list of users allowed with that right as a Set of String (user identities).
526     * @throws RightsException if an error occurs.
527     */
528    public AllowedUsers getAllowedUsers(String rightId, Object object)
529    {
530        if (StringUtils.isBlank(rightId))
531        {
532            throw new RightsException("The rightId cannot be null");
533        }
534
535        return _getAllowedUsers(rightId, object);
536    }
537    
538    /**
539     * Get the users with a READ access on given object
540     * @param object The object
541     * @return The representation of allowed users 
542     */
543    public AllowedUsers getReadAccessAllowedUsers(Object object)
544    {
545        return _getAllowedUsers(null, object);
546    }
547    
548    private AllowedUsers _getAllowedUsers(String rightId, Object object)
549    {
550        Optional.ofNullable(object).orElseThrow(() -> 
551        {
552            return new RightsException("The object cannot be null");
553        });
554        
555        // Get the objects to check
556        Set<Object> objects = _getConvertedObjects(object, new HashSet<>());
557        
558        // For each object, retrieve the allowed and denied users/groups
559        Boolean isAnyConnectedAllowed = null; // unknown
560        Set<UserIdentity> allAllowedUsers = new HashSet<>();
561        Set<UserIdentity> allDeniedUsers = new HashSet<>();
562        Set<GroupIdentity> allAllowedGroups = new HashSet<>();
563        Set<GroupIdentity> allDeniedGroups = new HashSet<>();
564        
565        for (Object obj : objects)
566        {
567            for (String controllerId : _accessControllerEP.getExtensionsIds())
568            {
569                AccessController accessController = _accessControllerEP.getExtension(controllerId);
570                try
571                {
572                    if (accessController.isSupported(obj))
573                    {
574                        if ((rightId == null ? accessController.getReadAccessPermissionForAnonymous(obj) : accessController.getPermissionForAnonymous(rightId, obj)) == AccessResult.ANONYMOUS_ALLOWED)
575                        {
576                            // Any anonymous user is allowed
577                            return new AllowedUsers(true, false, null, null, null, null, _userManager, _groupManager, null);
578                        }
579                        
580                        AccessResult permissionForAnyConnectedUser = rightId == null ? accessController.getReadAccessPermissionForAnyConnectedUser(obj) : accessController.getPermissionForAnyConnectedUser(rightId, obj);
581                        if (permissionForAnyConnectedUser == AccessResult.ANY_CONNECTED_DENIED)
582                        {
583                            // For having any connected user allowed, you need to not have the denied access for one object
584                            isAnyConnectedAllowed = Boolean.FALSE;
585                        }
586                        else if (isAnyConnectedAllowed == null && permissionForAnyConnectedUser == AccessResult.ANY_CONNECTED_ALLOWED)
587                        {
588                            isAnyConnectedAllowed = Boolean.TRUE;
589                        }
590                        
591                        Map<UserIdentity, AccessResult> permissionsByUser = rightId == null ? accessController.getReadAccessPermissionByUser(obj) : accessController.getPermissionByUser(rightId, obj);
592                        
593                        Set<UserIdentity> allowedUsersOnObj = permissionsByUser.entrySet().stream()
594                                .filter(entry -> AccessResult.USER_ALLOWED.equals(entry.getValue()))
595                                .map(Entry::getKey)
596                                .collect(Collectors.toSet());
597                        allAllowedUsers.addAll(allowedUsersOnObj);
598                        
599                        Set<UserIdentity> deniedUsersOnObj = permissionsByUser.entrySet().stream()
600                                .filter(entry -> AccessResult.USER_DENIED.equals(entry.getValue()))
601                                .map(Entry::getKey)
602                                .collect(Collectors.toSet());
603                        allDeniedUsers.addAll(deniedUsersOnObj);
604                        
605                        
606                        Map<GroupIdentity, AccessResult> permissionsByGroup = rightId == null ? accessController.getReadAccessPermissionByGroup(obj) : accessController.getPermissionByGroup(rightId, obj);
607                        
608                        Set<GroupIdentity> allowedGroupsOnObj = permissionsByGroup.entrySet().stream()
609                                .filter(entry -> AccessResult.GROUP_ALLOWED.equals(entry.getValue()))
610                                .map(Entry::getKey)
611                                .collect(Collectors.toSet());
612                        allAllowedGroups.addAll(allowedGroupsOnObj);
613                        
614                        Set<GroupIdentity> deniedGroupsOnObj = permissionsByGroup.entrySet().stream()
615                                .filter(entry -> AccessResult.GROUP_DENIED.equals(entry.getValue()))
616                                .map(Entry::getKey)
617                                .collect(Collectors.toSet());
618                        allDeniedGroups.addAll(deniedGroupsOnObj);
619                    }
620                }
621                catch (Exception e)
622                {
623                    getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", controllerId, obj, e);
624                }
625            }
626        }
627        
628        Request request = ContextHelper.getRequest(_context);
629        @SuppressWarnings("unchecked")
630        List<String> populationContexts = (List<String>) request.getAttribute(PopulationContextHelper.POPULATION_CONTEXTS_REQUEST_ATTR);
631        
632        // Then, return the AllowedUsers object
633        return new AllowedUsers(false, isAnyConnectedAllowed != null && isAnyConnectedAllowed.booleanValue(), allAllowedUsers, allDeniedUsers, allAllowedGroups, allDeniedGroups, _userManager, _groupManager, populationContexts != null ? new HashSet<>(populationContexts) : new HashSet<>());
634    }
635    
636    /* --------------- */
637    /* GET USER RIGHTS */
638    /* --------------- */
639    
640    /**
641     * Get the list of rights a user is allowed, on a particular object.
642     * @param userIdentity the user identity. Cannot be null.
643     * @param object The object to check the right. Cannot be null.
644     * @return The list of rights as a Set of String (id).
645     * @throws RightsException if an error occurs.
646     */
647    public Set<String> getUserRights(UserIdentity userIdentity, Object object) throws RightsException
648    {
649        if (userIdentity == null)
650        {
651            throw new RightsException("The userIdentity cannot be null");
652        }
653        else if (object == null)
654        {
655            throw new RightsException("The object cannot be null");
656        }
657        
658        // Get the objects to check
659        Set<Object> objects = _getConvertedObjects(object, new HashSet<>());
660        
661        // Retrieve groups the user belongs to
662        Set<GroupIdentity> groups = _groupManager.getUserGroups(userIdentity);
663        
664        // Gets the access by rights
665        Map<String, AccessResult> accessResultsByRight = _getAccessResultByRight(userIdentity, groups, objects);
666        
667        // Keep only positive rights
668        Set<String> allowedRights = accessResultsByRight.entrySet().stream().filter(entry -> entry.getValue().toRightResult() == RightResult.RIGHT_ALLOW).map(entry -> entry.getKey()).collect(Collectors.toSet());
669        return allowedRights;
670    }
671    
672    private Map<String, AccessResult> _getAccessResultByRight(UserIdentity userIdentity, Set<GroupIdentity> groups, Set<Object> objects)
673    {
674        Map<String, AccessResult> result = new HashMap<>();
675        
676        for (Object obj : objects)
677        {
678            for (String controllerId : _accessControllerEP.getExtensionsIds())
679            {
680                AccessController accessController = _accessControllerEP.getExtension(controllerId);
681                try
682                {
683                    if (accessController.isSupported(obj))
684                    {
685                        // Update the result map
686                        Map<String, AccessResult> permissionsByRight = accessController.getPermissionByRight(userIdentity, groups, obj);
687                        for (String rightId : permissionsByRight.keySet())
688                        {
689                            result.put(rightId, AccessResult.merge(result.get(rightId), permissionsByRight.get(rightId)));
690                        }
691                    }
692                }
693                catch (Exception e)
694                {
695                    getLogger().error("An error occured with controller '{}' for object {}. Thus, this controller will be ignored.", controllerId, obj, e);
696                }
697            }
698        }
699        
700        return result;
701    }
702
703    /* ------- */
704    /* PRIVATE */
705    /* ------- */
706    
707    private Set<Object> _getConvertedObjects(Object object, Set<Object> alreadyHandledObjects)
708    {
709        Set<Object> finalObjects = new HashSet<>();
710        
711        if (!alreadyHandledObjects.contains(object))
712        {
713            alreadyHandledObjects.add(object);
714            
715            Set<Object> objects = _rightContextConvertorEP.getExtensionsIds().stream()
716                    .map(_rightContextConvertorEP::getExtension)
717                    .flatMap(convertor -> convertor.convert(object).stream())
718                    .collect(Collectors.toSet());
719            
720            finalObjects.addAll(objects);
721            finalObjects.add(object);
722    
723            for (Object convertedObject : objects)
724            {
725                finalObjects.addAll(_getConvertedObjects(convertedObject, alreadyHandledObjects));
726            }
727        }
728        
729        return finalObjects;
730    }
731    
732    private Set<GroupIdentity> _getGroups(UserIdentity userIdentity)
733    {
734        if (userIdentity == __ANONYMOUS_USER_IDENTITY || userIdentity == __ANY_CONNECTED_USER_IDENTITY)
735        {
736            return Collections.EMPTY_SET;
737        }
738        else
739        {
740            Set<GroupIdentity> userGroups = _groupManager.getUserGroups(userIdentity);
741            return userGroups;
742        }
743    }
744
745    
746    
747    private RightResult _hasRightResultInFirstCache(UserIdentity userIdentity, String rightId, Object object)
748    {
749        @SuppressWarnings("unchecked")
750        Map<UserIdentity, Map<String, Map<Object, RightResult>>> mapCache = getCache(CACHE_1, false);
751        if (mapCache != null)
752        {
753            if (mapCache.containsKey(userIdentity))
754            {
755                Map<String, Map<Object, RightResult>> mapRight = mapCache.get(userIdentity);
756                if (mapRight.containsKey(rightId))
757                {
758                    Map<Object, RightResult> mapContext = mapRight.get(rightId);
759                    if (mapContext.containsKey(object))
760                    {
761                        RightResult cacheResult = mapContext.get(object); 
762                        getLogger().debug("Find entry in cache for [{}, {}, {}] => {}", userIdentity, rightId, object, cacheResult);
763                        return cacheResult;
764                    }
765                }
766            }
767        }
768        
769        getLogger().debug("Did not find entry in cache for [{}, {}, {}]", userIdentity, rightId, object);
770        return null;
771    }
772    
773    private void _putInFirstCache(UserIdentity userIdentity, String rightId, Object object, RightResult rightResult)
774    {
775        @SuppressWarnings("unchecked")
776        Map<UserIdentity, Map<String, Map<Object, RightResult>>> mapCache = getCache(CACHE_1, true);
777        if (mapCache != null)
778        {
779            if (!mapCache.containsKey(userIdentity))
780            {
781                mapCache.put(userIdentity, new HashMap<>());
782            }
783            Map<String, Map<Object, RightResult>> mapRight = mapCache.get(userIdentity);
784            
785            if (!mapRight.containsKey(rightId))
786            {
787                mapRight.put(rightId, new HashMap<>());
788            }
789            Map<Object, RightResult> mapContext = mapRight.get(rightId);
790            
791            mapContext.put(object, rightResult);
792        }
793    }
794    
795    private RightResult _hasRightResultInSecondCache(Set<Object> workspacesContexts, UserIdentity userIdentity, String rightId)
796    {
797        @SuppressWarnings("unchecked")
798        Map<UserIdentity, Map<String, Map<Object, RightResult>>> mapCache = getCache(CACHE_2, false);
799        if (mapCache != null)
800        {
801            if (mapCache.containsKey(userIdentity))
802            {
803                Map<String, Map<Object, RightResult>> mapRight = mapCache.get(userIdentity);
804                if (mapRight.containsKey(rightId))
805                {
806                    Map<Object, RightResult> resultPerContext = mapRight.get(rightId);
807                    if (resultPerContext.containsKey(workspacesContexts))
808                    {
809                        RightResult cacheResult = resultPerContext.get(workspacesContexts);
810                        getLogger().debug("Find entry in cache2 for [{}, {}, {}] => {}", workspacesContexts, userIdentity, rightId, cacheResult);
811                        return cacheResult;
812                    }
813                }
814            }
815        }
816        
817        getLogger().debug("Did not find entry in cache2 for [{}, {}, {}]", workspacesContexts, userIdentity, rightId);
818        return null;
819    }
820    
821    private void _putInSecondCache(Set<Object> workspacesContexts, UserIdentity userIdentity, String rightId, RightResult rightResult)
822    {
823        @SuppressWarnings("unchecked")
824        Map<UserIdentity, Map<String, Map<Object, RightResult>>> mapCache = getCache(CACHE_2, true);
825        if (mapCache != null)
826        {
827            if (!mapCache.containsKey(userIdentity))
828            {
829                mapCache.put(userIdentity, new HashMap<>());
830            }
831            Map<String, Map<Object, RightResult>> mapRights = mapCache.get(userIdentity);
832            
833            if (!mapRights.containsKey(rightId))
834            {
835                mapRights.put(rightId, new HashMap<>());            
836            }
837            Map<Object, RightResult> mapResult = mapRights.get(rightId);
838            
839            mapResult.put(workspacesContexts, rightResult);
840        }
841    }
842    
843    /**
844     * Get the RightManager cache. Use this to store your information on rights
845     * @param cacheKey The cache key
846     * @param createIfUnexisting Creates a new HashMap if the cache does not exists yet
847     * @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
848     */
849    public Map getCache(String cacheKey, boolean createIfUnexisting)
850    {
851        Request request;
852        try
853        {
854            request = ContextHelper.getRequest(_context);
855        }
856        catch (CascadingRuntimeException e)
857        {
858            return null;
859        }
860        
861        if (request == null)
862        {
863            return null;
864        }
865        
866        @SuppressWarnings("unchecked")
867        Map<String, Map> cache = (Map<String, Map>) request.getAttribute(CACHE_REQUEST_ATTRIBUTE_NAME);
868        if (cache == null)
869        {
870            if (!createIfUnexisting)
871            {
872                return null;
873            }
874            
875            cache = new HashMap<>();
876            request.setAttribute(CACHE_REQUEST_ATTRIBUTE_NAME, cache);
877        }
878        
879        Map mapCache = cache.get(cacheKey);
880        if (mapCache == null && createIfUnexisting)
881        {
882            mapCache = new HashMap<>();
883            cache.put(cacheKey, mapCache);
884        }
885        return mapCache;
886    }
887}