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.cms.indexing.solr;
017
018import java.util.HashMap;
019import java.util.Map;
020import java.util.Map.Entry;
021import java.util.Set;
022import java.util.stream.Collectors;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028import org.apache.solr.common.SolrInputDocument;
029
030import org.ametys.cms.content.indexing.solr.SolrFieldNames;
031import org.ametys.core.group.GroupIdentity;
032import org.ametys.core.right.AccessController.AccessResult;
033import org.ametys.core.right.ProfileAssignmentStorageExtensionPoint;
034import org.ametys.core.user.UserIdentity;
035
036/**
037 * Component indexing rights (hierarchical computed) for an object.
038 * @param <T> The type of object
039 */
040public abstract class AbstractSolrHierarchicalRightIndexer<T> implements Component, Serviceable, SolrFieldNames
041{
042    /** The extension point for the profile assignement storages */
043    protected ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageEP;
044    
045    @Override
046    public void service(ServiceManager serviceManager) throws ServiceException
047    {
048        _profileAssignmentStorageEP = (ProfileAssignmentStorageExtensionPoint) serviceManager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE);
049    }
050    
051    /**
052     * Indexes the ACLs of the object (resolving the hierarchy)
053     * @param object The object to index
054     * @param document The document to index
055     * @param profiles The profiles to check
056     */
057    protected void indexAcls(T object, SolrInputDocument document, Set<String> profiles)
058    {
059        AccessResult permissionForAnonymous = _getPermissionForAnonymous(profiles, object);
060        switch (permissionForAnonymous)
061        {
062            case ANONYMOUS_ALLOWED:
063                // Any anonymous user is allowed
064                document.addField(ACL_READ_ALLOW_ANONYMOUS, "true");
065                return;
066            case ANONYMOUS_DENIED:
067                // Any anonymous user is denied
068                document.addField(ACL_READ_ALLOW_ANONYMOUS, "false");
069                break;
070            case UNKNOWN:
071            default:
072                // Unknown
073                document.addField(ACL_READ_ALLOW_ANONYMOUS, "unknown");
074                break;
075        }
076        
077        AccessResult permissionForAnyConnectedUser = _getPermissionForAnyConnectedUser(profiles, object);
078        switch (permissionForAnyConnectedUser)
079        {
080            case ANY_CONNECTED_ALLOWED:
081                // Any connected user is allowed
082                document.addField(ACL_READ_ALLOW_ANY_CONNECTED_USER, "true");
083                break;
084            case ANY_CONNECTED_DENIED:
085                // Any connected user is denied
086                document.addField(ACL_READ_ALLOW_ANY_CONNECTED_USER, "false");
087                break;
088            case UNKNOWN:
089            default:
090                // Unknown
091                document.addField(ACL_READ_ALLOW_ANY_CONNECTED_USER, "unknown");
092                break;
093        }
094        
095        Map<UserIdentity, AccessResult> permissionsByUser = _getPermissionsByUser(profiles, object);
096        
097        Set<UserIdentity> allowedUsersOnObj = permissionsByUser.entrySet().stream()
098                .filter(entry -> AccessResult.USER_ALLOWED.equals(entry.getValue()))
099                .map(Entry::getKey)
100                .collect(Collectors.toSet());
101        
102        Set<UserIdentity> deniedUsersOnObj = permissionsByUser.entrySet().stream()
103                .filter(entry -> AccessResult.USER_DENIED.equals(entry.getValue()))
104                .map(Entry::getKey)
105                .collect(Collectors.toSet());
106        
107        
108        Map<GroupIdentity, AccessResult> permissionsByGroup = _getPermissionsByGroup(profiles, object);
109        
110        Set<GroupIdentity> allowedGroupsOnObj = permissionsByGroup.entrySet().stream()
111                .filter(entry -> AccessResult.GROUP_ALLOWED.equals(entry.getValue()))
112                .map(Entry::getKey)
113                .collect(Collectors.toSet());
114        
115        Set<GroupIdentity> deniedGroupsOnObj = permissionsByGroup.entrySet().stream()
116                .filter(entry -> AccessResult.GROUP_DENIED.equals(entry.getValue()))
117                .map(Entry::getKey)
118                .collect(Collectors.toSet());
119        
120        document.addField(ACL_READ_ALLOWED_USERS, _userIdentitiesToStrings(allowedUsersOnObj));
121        document.addField(ACL_READ_DENIED_USERS, _userIdentitiesToStrings(deniedUsersOnObj));
122        document.addField(ACL_READ_ALLOWED_GROUPS, _groupIdentitiesToStrings(allowedGroupsOnObj));
123        document.addField(ACL_READ_DENIED_GROUPS, _groupIdentitiesToStrings(deniedGroupsOnObj));
124    }
125    
126    /**
127     * Gets the parent of the object
128     * @param object The object
129     * @return the parent of the object
130     */
131    protected abstract Set<T> _getParents(T object);
132    
133    /**
134     * Gets the permissions for anonymous of the given object, resolving the hierarchy
135     * @param profileIds The profiles to check
136     * @param object The object
137     * @return the permissions for anonymous
138     */
139    protected AccessResult _getPermissionForAnonymous(Set<String> profileIds, T object)
140    {
141        // Get permission on object itself
142        AccessResult permission = _profileAssignmentStorageEP.getPermissionForAnonymous(profileIds, object);
143        
144        if (AccessResult.UNKNOWN.equals(permission))
145        {
146            // Add recursively the permission given by parent
147            Set<T> parents = _getParents(object);
148            if (parents != null)
149            {
150                for (T parent : parents)
151                {
152                    AccessResult parentResult = _getPermissionForAnonymous(profileIds, parent);
153                    permission = AccessResult.merge(permission, parentResult);
154                }
155            }
156        }
157        
158        return permission;
159    }
160    
161    /**
162     * Gets the permissions for any connected user of the given object, resolving the hierarchy
163     * @param profileIds The profiles to check
164     * @param object The object
165     * @return the permissions for any connected user
166     */
167    protected AccessResult _getPermissionForAnyConnectedUser(Set<String> profileIds, T object)
168    {
169        // Get permission on object itself
170        AccessResult permission = _profileAssignmentStorageEP.getPermissionForAnyConnectedUser(profileIds, object);
171        
172        if (AccessResult.UNKNOWN.equals(permission))
173        {
174            // Add recursively the permission given by parent
175            Set<T> parents = _getParents(object);
176            if (parents != null)
177            {
178                for (T parent : parents)
179                {
180                    AccessResult parentResult = _getPermissionForAnyConnectedUser(profileIds, parent);
181                    permission = AccessResult.merge(permission, parentResult);
182                }
183            }
184        }
185        
186        return permission;
187    }
188    
189    /**
190     * Gets the permissions by user of the given object, resolving the hierarchy
191     * @param profileIds The profiles to check
192     * @param object The object
193     * @return the permissions by user
194     */
195    protected Map<UserIdentity, AccessResult> _getPermissionsByUser(Set<String> profileIds, T object)
196    {
197        // Get permissions on object itself
198        Map<UserIdentity, AccessResult> permissionsByUser = _profileAssignmentStorageEP.getPermissionsByUser(profileIds, object);
199        
200        // Add recursively the permission given by parent
201        Set<T> parents = _getParents(object);
202        if (parents != null)
203        {
204            Map<UserIdentity, AccessResult> parentsResult = new HashMap<>();
205            for (T parent : parents)
206            {
207                Map<UserIdentity, AccessResult> parentResult = _getPermissionsByUser(profileIds, parent);
208                for (UserIdentity user : parentResult.keySet())
209                {
210                    parentsResult.put(user, AccessResult.merge(parentsResult.get(user), parentResult.get(user)));
211                }
212            }
213            
214            for (UserIdentity user : parentsResult.keySet())
215            {
216                if (!permissionsByUser.containsKey(user) || AccessResult.UNKNOWN.equals(permissionsByUser.get(user)))
217                {
218                    permissionsByUser.put(user, AccessResult.merge(parentsResult.get(user)));
219                }
220            }
221        }
222        
223        return permissionsByUser;
224    }
225    
226    /**
227     * Gets the permissions by group of the given object, resolving the hierarchy
228     * @param profileIds The profiles to check
229     * @param object The object
230     * @return the permissions by group
231     */
232    protected Map<GroupIdentity, AccessResult> _getPermissionsByGroup(Set<String> profileIds, T object)
233    {
234        // Get permissions on object itself
235        Map<GroupIdentity, AccessResult> permissionsByGroup = _profileAssignmentStorageEP.getPermissionsByGroup(profileIds, object);
236        
237        // Add recursively the permission given by parent
238        Set<T> parents = _getParents(object);
239        if (parents != null)
240        {
241            Map<GroupIdentity, AccessResult> parentsResult = new HashMap<>();
242            for (T parent : parents)
243            {
244                Map<GroupIdentity, AccessResult> parentResult = _getPermissionsByGroup(profileIds, parent);
245                for (GroupIdentity group : parentResult.keySet())
246                {
247                    parentsResult.put(group, AccessResult.merge(parentsResult.get(group), parentResult.get(group)));
248                }
249            }
250            
251            for (GroupIdentity group : parentsResult.keySet())
252            {
253                if (!permissionsByGroup.containsKey(group) || AccessResult.UNKNOWN.equals(permissionsByGroup.get(group)))
254                {
255                    permissionsByGroup.put(group, AccessResult.merge(parentsResult.get(group)));
256                }
257            }
258        }        
259        
260        return permissionsByGroup;
261    }
262    
263    /**
264     * Converts a set of identities to a set of their string representations
265     * @param userIdentities The identities
266     * @return a set of string representing the identities
267     */
268    protected Set<String> _userIdentitiesToStrings(Set<UserIdentity> userIdentities)
269    {
270        return userIdentities.stream().map(UserIdentity::userIdentityToString).collect(Collectors.toSet());
271    }
272    
273    /**
274     * Converts a set of identities to a set of their string representations
275     * @param groupIdentities The identities
276     * @return a set of string representing the identities
277     */
278    protected Set<String> _groupIdentitiesToStrings(Set<GroupIdentity> groupIdentities)
279    {
280        return groupIdentities.stream().map(GroupIdentity::groupIdentityToString).collect(Collectors.toSet());
281    }
282}