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    /**
040     * Determines if the inheritance of permissions is disallowed on the given object
041     * @param object The object
042     * @return true if the inheritance of permissions is disallowed on the given object
043     */
044    protected boolean isInheritanceDisallowed(T object)
045    {
046        return _profileAssignmentStorageEP.isInheritanceDisallowed(object);
047    }
048    
049    @Override
050    public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
051    {
052        Map<String, AccessResult> permissions = super.getPermissionByRight(user, userGroups, object);
053        
054        @SuppressWarnings("unchecked")
055        boolean inheritanceDisallowed = isInheritanceDisallowed((T) object);
056        if (!inheritanceDisallowed)
057        {
058            @SuppressWarnings("unchecked")
059            Set<T> parents = _getParents((T) object);
060            if (parents != null && !_profileAssignmentStorageEP.isInheritanceDisallowed(object))
061            {
062                // Determine parents permissions
063                Map<String, AccessResult> parentsPermissions = new HashMap<>();
064                for (T parent : parents)
065                {
066                    Map<String, AccessResult> parentPermissions = getPermissionByRight(user, userGroups, parent);
067                    for (String rightId : parentPermissions.keySet())
068                    {
069                        parentsPermissions.put(rightId, AccessResult.merge(parentPermissions.get(rightId), parentsPermissions.get(rightId)));
070                    }
071                }
072                
073                // Apply it to local permission
074                for (String rightId : parentsPermissions.keySet())
075                {
076                    if (!permissions.containsKey(rightId) || permissions.get(rightId) == AccessResult.UNKNOWN)
077                    {
078                        permissions.put(rightId, parentsPermissions.get(rightId));
079                    }
080                }
081            }
082        }
083        
084        return permissions;
085    }
086
087    @SuppressWarnings("unchecked")
088    @Override
089    protected AccessResult _getPermission(UserIdentity user, Set<GroupIdentity> userGroups, Set<String> profilesIds, Object object, Object convertedObject)
090    {
091        // Let's manage the cache by ourselves, to avoid useless call to #_getParents
092        // Try to retrieve in second cache (do not delegate the cache reading to the superclass because we known the unknown result is final
093        Map<UserIdentity, AccessResult> cacheResult = (Map<UserIdentity, AccessResult>) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.USER);
094        if (cacheResult != null && cacheResult.containsKey(user))
095        {
096            return cacheResult.get(user);
097        }
098
099        AccessResult permission = super._getPermission(user, userGroups, profilesIds, object, convertedObject);
100        
101        boolean inheritanceDisallowed = isInheritanceDisallowed((T) object);
102        
103        if (permission == AccessResult.UNKNOWN && !inheritanceDisallowed)
104        {
105            Set<T> parents = _getParents((T) object);
106            if (parents != null)
107            {
108                for (T parent : parents)
109                {
110                    Object convertedParentObject = _convertContext(parent);
111                    AccessResult parentResult = _getPermission(user, userGroups, profilesIds, parent, convertedParentObject);
112                    permission = AccessResult.merge(permission, parentResult);
113                }
114                
115                if (permission != AccessResult.UNKNOWN)
116                {
117                    if (cacheResult == null)
118                    {
119                        cacheResult = (Map<UserIdentity, AccessResult>) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.USER);
120                        cacheResult = cacheResult == null ? new HashMap<>() : cacheResult;
121                    }
122                    cacheResult.put(user, permission);
123                    _putInSecondCache(profilesIds, convertedObject, cacheResult, CacheKind.USER);
124                }
125            }
126        }
127        
128        return permission;
129    }
130    
131    @Override
132    protected AccessResult _getPermissionForAnonymous(Set<String> profilesIds, Object object, Object convertedObject)
133    {
134        // Let's manage the cache by ourselves, to avoid useless call to #_getParents
135        // Try to retrieve in second cache (do not delegate the cache reading to the superclass because we known the unknown result is final
136        AccessResult cacheResult = (AccessResult) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.ANONYMOUS);
137        if (cacheResult != null)
138        {
139            return cacheResult;
140        }
141
142        AccessResult permission = super._getPermissionForAnonymous(profilesIds, object, convertedObject);
143        
144        @SuppressWarnings("unchecked")
145        boolean inheritanceDisallowed = isInheritanceDisallowed((T) object);
146        
147        if (permission == AccessResult.UNKNOWN && !inheritanceDisallowed)
148        {
149            @SuppressWarnings("unchecked")
150            Set<T> parents = _getParents((T) object);
151            if (parents != null)
152            {
153                for (T parent : parents)
154                {
155                    Object convertedParentObject = _convertContext(parent);
156                    AccessResult parentResult = _getPermissionForAnonymous(profilesIds, parent, convertedParentObject);
157                    permission = AccessResult.merge(permission, parentResult);
158                }
159                
160                if (permission != AccessResult.UNKNOWN)
161                {
162                    // LET'S ADD THE ANSWER TO THE CACHE... because #_getParents can be long to execute
163                    _putInSecondCache(profilesIds, convertedObject, permission, CacheKind.ANONYMOUS);
164                }
165            }
166        }
167
168        return permission;
169    }
170    
171    @Override
172    protected AccessResult _getPermissionForAnyConnectedUser(Set<String> profilesIds, Object object, Object convertedObject)
173    {
174        // Let's manage the cache by ourselves, to avoid useless call to #_getParents
175        // Try to retrieve in second cache (do not delegate the cache reading to the superclass because we known the unknown result is final
176        AccessResult cacheResult = (AccessResult) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.ANY_CONNECTED_USER);
177        if (cacheResult != null)
178        {
179            return cacheResult;
180        }
181        
182        AccessResult permission = super._getPermissionForAnyConnectedUser(profilesIds, object, convertedObject);
183        
184        @SuppressWarnings("unchecked")
185        boolean inheritanceDisallowed = isInheritanceDisallowed((T) object);
186        
187        if (permission == AccessResult.UNKNOWN && !inheritanceDisallowed)
188        {
189            @SuppressWarnings("unchecked")
190            Set<T> parents = _getParents((T) object);
191            if (parents != null)
192            {
193                for (T parent : parents)
194                {
195                    Object convertedParentObject = _convertContext(parent);
196                    AccessResult parentResult = _getPermissionForAnyConnectedUser(profilesIds, parent, convertedParentObject);
197                    permission = AccessResult.merge(permission, parentResult);
198                }
199                
200                if (permission != AccessResult.UNKNOWN)
201                {
202                    // LET'S ADD THE ANSWER TO THE CACHE... because #_getParents can be long to execute
203                    _putInSecondCache(profilesIds, convertedObject, permission, CacheKind.ANY_CONNECTED_USER);
204                }
205            }
206        }
207
208        return permission;
209    }
210
211    @Override
212    protected Map<UserIdentity, AccessResult> _getPermissionByUser(Set<String> profilesIds, Object object, Object convertedObject)
213    {
214        // Let's manage the cache by ourselves, to avoid useless call to #_getParents
215        // Try to retrieve in second cache (do not delegate the cache reading to the superclass because we known the unknown result is final
216        @SuppressWarnings("unchecked")
217        Map<UserIdentity, AccessResult> cacheResult = (Map<UserIdentity, AccessResult>) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.USERS);
218        if (cacheResult != null)
219        {
220            return cacheResult;
221        }
222
223        Map<UserIdentity, AccessResult> permissions = super._getPermissionByUser(profilesIds, object, convertedObject);
224        
225        @SuppressWarnings("unchecked")
226        boolean inheritanceDisallowed = isInheritanceDisallowed((T) object);
227        if (!inheritanceDisallowed)
228        {
229            @SuppressWarnings("unchecked")
230            Set<T> parents = _getParents((T) object);
231            if (parents != null)
232            {
233                // Determine parents permissions
234                Map<UserIdentity, AccessResult> parentsPermissions = new HashMap<>();
235                for (T parent : parents)
236                {
237                    Object convertedParentObject = _convertContext(parent);
238                    Map<UserIdentity, AccessResult> parentPermissions = _getPermissionByUser(profilesIds, parent, convertedParentObject);
239                    for (UserIdentity user : parentPermissions.keySet())
240                    {
241                        parentsPermissions.put(user, AccessResult.merge(parentPermissions.get(user), parentsPermissions.get(user)));
242                    }
243                }
244                
245                // Apply it to local permission
246                for (UserIdentity user : parentsPermissions.keySet())
247                {
248                    if (!permissions.containsKey(user) || permissions.get(user) == AccessResult.UNKNOWN)
249                    {
250                        permissions.put(user, parentsPermissions.get(user));
251                    }
252                }
253            }
254        }
255        
256        _putInSecondCache(profilesIds, convertedObject, permissions, CacheKind.USERS);
257
258        return permissions;
259    }
260
261    @Override
262    protected Map<GroupIdentity, AccessResult> _getPermissionByGroup(Set<String> profilesIds, Object object, Object convertedObject)
263    {
264        // Let's manage the cache by ourselves, to avoid useless call to #_getParents
265        // Try to retrieve in second cache (do not delegate the cache reading to the superclass because we known the unknown result is final
266        @SuppressWarnings("unchecked")
267        Map<GroupIdentity, AccessResult> cacheResult = (Map<GroupIdentity, AccessResult>) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.GROUPS);
268        if (cacheResult != null)
269        {
270            return cacheResult;
271        }
272
273        Map<GroupIdentity, AccessResult> permissions = super._getPermissionByGroup(profilesIds, object, convertedObject);
274        
275        @SuppressWarnings("unchecked")
276        boolean inheritanceDisallowed = isInheritanceDisallowed((T) object);
277        if (!inheritanceDisallowed)
278        {
279            @SuppressWarnings("unchecked")
280            Set<T> parents = _getParents((T) object);
281            if (parents != null)
282            {
283                // Determine parents permissions
284                Map<GroupIdentity, AccessResult> parentsPermissions = new HashMap<>();
285                for (T parent : parents)
286                {
287                    Object convertedParentObject = _convertContext(parent);
288                    Map<GroupIdentity, AccessResult> parentPermissions = _getPermissionByGroup(profilesIds, parent, convertedParentObject);
289                    for (GroupIdentity group : parentPermissions.keySet())
290                    {
291                        parentsPermissions.put(group, AccessResult.merge(parentPermissions.get(group), parentsPermissions.get(group)));
292                    }
293                }
294                
295                // Apply it to local permission
296                for (GroupIdentity group : parentsPermissions.keySet())
297                {
298                    if (!permissions.containsKey(group) || permissions.get(group) == AccessResult.UNKNOWN)
299                    {
300                        permissions.put(group, parentsPermissions.get(group));
301                    }
302                }
303            }
304        }
305        
306        _putInSecondCache(profilesIds, convertedObject, permissions, CacheKind.GROUPS);
307
308        return permissions;
309    }
310}