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.plugins.core.impl.right;
017
018import java.util.HashMap;
019import java.util.Map;
020import java.util.Set;
021
022import org.ametys.core.group.GroupIdentity;
023import org.ametys.core.right.AccessController;
024import org.ametys.core.user.UserIdentity;
025
026/**
027 * Abstract {@link AccessController} for a hierarchical type of object. 
028 * @param <T> The class of a supported object, from which you can retrieve its parent with {@link #_getParents(Object)}
029 */
030public abstract class AbstractHierarchicalAccessController<T> extends AbstractProfileStorageBasedAccessController
031{
032    /**
033     * Gets the parents of the object. Must return null when the object is the "root" (where "root" means here the root of the hierarchy of the access controller)
034     * @param object The object
035     * @return the parents of the object, or null if the object is the "root" of the hierarchy
036     */
037    protected abstract Set<T> _getParents(T object);
038    
039    @Override
040    public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
041    {
042        Map<String, AccessResult> permissions = super.getPermissionByRight(user, userGroups, object);
043        
044        @SuppressWarnings("unchecked")
045        Set<T> parents = _getParents((T) object);
046        if (parents != null)
047        {
048            // Determine parents permissions
049            Map<String, AccessResult> parentsPermissions = new HashMap<>();
050            for (T parent : parents)
051            {
052                Map<String, AccessResult> parentPermissions = getPermissionByRight(user, userGroups, parent);
053                for (String rightId : parentPermissions.keySet())
054                {
055                    parentsPermissions.put(rightId, AccessResult.merge(parentPermissions.get(rightId), parentsPermissions.get(rightId)));
056                }
057            }
058            
059            // Apply it to local permission
060            for (String rightId : parentsPermissions.keySet())
061            {
062                if (!permissions.containsKey(rightId) || permissions.get(rightId) == AccessResult.UNKNOWN)
063                {
064                    permissions.put(rightId, parentsPermissions.get(rightId));
065                }
066            }
067        }
068        
069        return permissions;
070    }
071
072    @SuppressWarnings("unchecked")
073    @Override
074    protected AccessResult _getPermission(UserIdentity user, Set<GroupIdentity> userGroups, Set<String> profilesIds, Object object, Object convertedObject)
075    {
076        // Let's manage the cache by ourselves, to avoid useless call to #_getParents
077        // Try to retrieve in second cache (do not delegate the cache reading to the superclass because we known the unknown result is final
078        Map<UserIdentity, AccessResult> cacheResult = (Map<UserIdentity, AccessResult>) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.USER);
079        if (cacheResult != null && cacheResult.containsKey(user))
080        {
081            return cacheResult.get(user);
082        }
083
084        AccessResult permission = super._getPermission(user, userGroups, profilesIds, object, convertedObject);
085        
086        if (permission == AccessResult.UNKNOWN)
087        {
088            Set<T> parents = _getParents((T) object);
089            if (parents != null)
090            {
091                for (T parent : parents)
092                {
093                    Object convertedParentObject = _convertContext(parent);
094                    AccessResult parentResult = _getPermission(user, userGroups, profilesIds, parent, convertedParentObject);
095                    permission = AccessResult.merge(permission, parentResult);
096                }
097                
098                if (permission != AccessResult.UNKNOWN)
099                {
100                    if (cacheResult == null)
101                    {
102                        cacheResult = (Map<UserIdentity, AccessResult>) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.USER);
103                        cacheResult = cacheResult == null ? new HashMap<>() : cacheResult;
104                    }
105                    cacheResult.put(user, permission);
106                    _putInSecondCache(profilesIds, convertedObject, cacheResult, CacheKind.USER);
107                }
108            }
109        }
110        
111        return permission;
112    }
113    
114    @Override
115    protected AccessResult _getPermissionForAnonymous(Set<String> profilesIds, Object object, Object convertedObject)
116    {
117        // Let's manage the cache by ourselves, to avoid useless call to #_getParents
118        // Try to retrieve in second cache (do not delegate the cache reading to the superclass because we known the unknown result is final
119        AccessResult cacheResult = (AccessResult) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.ANONYMOUS);
120        if (cacheResult != null)
121        {
122            return cacheResult;
123        }
124
125        AccessResult permission = super._getPermissionForAnonymous(profilesIds, object, convertedObject);
126        
127        if (permission == AccessResult.UNKNOWN)
128        {
129            @SuppressWarnings("unchecked")
130            Set<T> parents = _getParents((T) object);
131            if (parents != null)
132            {
133                for (T parent : parents)
134                {
135                    Object convertedParentObject = _convertContext(parent);
136                    AccessResult parentResult = _getPermissionForAnonymous(profilesIds, parent, convertedParentObject);
137                    permission = AccessResult.merge(permission, parentResult);
138                }
139                
140                if (permission != AccessResult.UNKNOWN)
141                {
142                    // LET'S ADD THE ANSWER TO THE CACHE... because #_getParents can be long to execute
143                    _putInSecondCache(profilesIds, convertedObject, permission, CacheKind.ANONYMOUS);
144                }
145            }
146        }
147
148        return permission;
149    }
150    
151    @Override
152    protected AccessResult _getPermissionForAnyConnectedUser(Set<String> profilesIds, Object object, Object convertedObject)
153    {
154        // Let's manage the cache by ourselves, to avoid useless call to #_getParents
155        // Try to retrieve in second cache (do not delegate the cache reading to the superclass because we known the unknown result is final
156        AccessResult cacheResult = (AccessResult) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.ANY_CONNECTED_USER);
157        if (cacheResult != null)
158        {
159            return cacheResult;
160        }
161        
162        AccessResult permission = super._getPermissionForAnyConnectedUser(profilesIds, object, convertedObject);
163        
164        if (permission == AccessResult.UNKNOWN)
165        {
166            @SuppressWarnings("unchecked")
167            Set<T> parents = _getParents((T) object);
168            if (parents != null)
169            {
170                for (T parent : parents)
171                {
172                    Object convertedParentObject = _convertContext(parent);
173                    AccessResult parentResult = _getPermissionForAnyConnectedUser(profilesIds, parent, convertedParentObject);
174                    permission = AccessResult.merge(permission, parentResult);
175                }
176                
177                if (permission != AccessResult.UNKNOWN)
178                {
179                    // LET'S ADD THE ANSWER TO THE CACHE... because #_getParents can be long to execute
180                    _putInSecondCache(profilesIds, convertedObject, permission, CacheKind.ANY_CONNECTED_USER);
181                }
182            }
183        }
184
185        return permission;
186    }
187
188    @Override
189    protected Map<UserIdentity, AccessResult> _getPermissionByUser(Set<String> profilesIds, Object object, Object convertedObject)
190    {
191        // Let's manage the cache by ourselves, to avoid useless call to #_getParents
192        // Try to retrieve in second cache (do not delegate the cache reading to the superclass because we known the unknown result is final
193        @SuppressWarnings("unchecked")
194        Map<UserIdentity, AccessResult> cacheResult = (Map<UserIdentity, AccessResult>) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.USERS);
195        if (cacheResult != null)
196        {
197            return cacheResult;
198        }
199
200        Map<UserIdentity, AccessResult> permissions = super._getPermissionByUser(profilesIds, object, convertedObject);
201        
202        @SuppressWarnings("unchecked")
203        Set<T> parents = _getParents((T) object);
204        if (parents != null)
205        {
206            // Determine parents permissions
207            Map<UserIdentity, AccessResult> parentsPermissions = new HashMap<>();
208            for (T parent : parents)
209            {
210                Object convertedParentObject = _convertContext(parent);
211                Map<UserIdentity, AccessResult> parentPermissions = _getPermissionByUser(profilesIds, parent, convertedParentObject);
212                for (UserIdentity user : parentPermissions.keySet())
213                {
214                    parentsPermissions.put(user, AccessResult.merge(parentPermissions.get(user), parentsPermissions.get(user)));
215                }
216            }
217            
218            // Apply it to local permission
219            for (UserIdentity user : parentsPermissions.keySet())
220            {
221                if (!permissions.containsKey(user) || permissions.get(user) == AccessResult.UNKNOWN)
222                {
223                    permissions.put(user, parentsPermissions.get(user));
224                }
225            }
226        }
227        
228        _putInSecondCache(profilesIds, convertedObject, permissions, CacheKind.USERS);
229
230        return permissions;
231    }
232
233    @Override
234    protected Map<GroupIdentity, AccessResult> _getPermissionByGroup(Set<String> profilesIds, Object object, Object convertedObject)
235    {
236        // Let's manage the cache by ourselves, to avoid useless call to #_getParents
237        // Try to retrieve in second cache (do not delegate the cache reading to the superclass because we known the unknown result is final
238        @SuppressWarnings("unchecked")
239        Map<GroupIdentity, AccessResult> cacheResult = (Map<GroupIdentity, AccessResult>) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.GROUPS);
240        if (cacheResult != null)
241        {
242            return cacheResult;
243        }
244
245        Map<GroupIdentity, AccessResult> permissions = super._getPermissionByGroup(profilesIds, object, convertedObject);
246        
247        @SuppressWarnings("unchecked")
248        Set<T> parents = _getParents((T) object);
249        if (parents != null)
250        {
251            // Determine parents permissions
252            Map<GroupIdentity, AccessResult> parentsPermissions = new HashMap<>();
253            for (T parent : parents)
254            {
255                Object convertedParentObject = _convertContext(parent);
256                Map<GroupIdentity, AccessResult> parentPermissions = _getPermissionByGroup(profilesIds, parent, convertedParentObject);
257                for (GroupIdentity group : parentPermissions.keySet())
258                {
259                    parentsPermissions.put(group, AccessResult.merge(parentPermissions.get(group), parentsPermissions.get(group)));
260                }
261            }
262            
263            // Apply it to local permission
264            for (GroupIdentity group : parentsPermissions.keySet())
265            {
266                if (!permissions.containsKey(group) || permissions.get(group) == AccessResult.UNKNOWN)
267                {
268                    permissions.put(group, parentsPermissions.get(group));
269                }
270            }
271        }
272        
273        _putInSecondCache(profilesIds, convertedObject, permissions, CacheKind.GROUPS);
274
275        return permissions;
276    }
277}