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.ui.right;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.Optional;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.parameters.Parameters;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.cocoon.acting.ServiceableAction;
032import org.apache.cocoon.environment.ObjectModelHelper;
033import org.apache.cocoon.environment.Redirector;
034import org.apache.cocoon.environment.Request;
035import org.apache.cocoon.environment.SourceResolver;
036
037import org.ametys.core.cocoon.JSonReader;
038import org.ametys.core.group.Group;
039import org.ametys.core.group.GroupDirectoryDAO;
040import org.ametys.core.group.GroupIdentity;
041import org.ametys.core.group.GroupManager;
042import org.ametys.core.right.ProfileAssignmentStorage.AnonymousOrAnyConnectedKeys;
043import org.ametys.core.right.ProfileAssignmentStorage.UserOrGroup;
044import org.ametys.core.right.ProfileAssignmentStorageExtensionPoint;
045import org.ametys.core.right.RightAssignmentContext;
046import org.ametys.core.right.RightAssignmentContextExtensionPoint;
047import org.ametys.core.right.RightProfilesDAO;
048import org.ametys.core.ui.right.ProfileAssignmentsToolClientSideElement.AccessType;
049import org.ametys.core.ui.right.ProfileAssignmentsToolClientSideElement.TargetType;
050import org.ametys.core.user.User;
051import org.ametys.core.user.UserIdentity;
052import org.ametys.core.user.UserManager;
053import org.ametys.core.user.population.UserPopulationDAO;
054
055/**
056 * Action for generating the grid for profile assignments
057 */
058public class GetProfileAssignmentsAction extends ServiceableAction
059{
060    /** The profile assignment storage component */
061    protected ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageEP;
062    /** The extension point for right assignment contexts */
063    protected RightAssignmentContextExtensionPoint _rightAssignmentContextEP;
064    /** The profiles DAO */
065    protected RightProfilesDAO _profilesDAO;
066    /** The DAO for user populations */
067    protected UserPopulationDAO _userPopulationDAO;
068    /** The user manager */
069    protected UserManager _userManager;
070    /** The DAO for group directories */
071    protected GroupDirectoryDAO _groupDirectoryDAO;
072    /** The group manager */
073    protected GroupManager _groupManager;
074
075    @Override
076    public void service(ServiceManager smanager) throws ServiceException
077    {
078        super.service(smanager);
079        _profileAssignmentStorageEP = (ProfileAssignmentStorageExtensionPoint) smanager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE);
080        _rightAssignmentContextEP = (RightAssignmentContextExtensionPoint) smanager.lookup(RightAssignmentContextExtensionPoint.ROLE);
081        _userPopulationDAO = (UserPopulationDAO) smanager.lookup(UserPopulationDAO.ROLE);
082        _userManager = (UserManager) smanager.lookup(UserManager.ROLE);
083        _groupDirectoryDAO = (GroupDirectoryDAO) smanager.lookup(GroupDirectoryDAO.ROLE);
084        _groupManager = (GroupManager) smanager.lookup(GroupManager.ROLE);
085    }
086    
087    @SuppressWarnings("unchecked")
088    @Override
089    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
090    {
091        if (_profilesDAO == null)
092        {
093            _profilesDAO = (RightProfilesDAO) manager.lookup(RightProfilesDAO.ROLE);
094        }
095        
096        Map<String, Object> result = new HashMap<>();
097        Request request = ObjectModelHelper.getRequest(objectModel);
098        
099        Map jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
100        
101        String rightAssignmentCtxId = (String) jsParameters.get("rightAssignmentContextId");
102        RightAssignmentContext rightCtx = _rightAssignmentContextEP.getExtension(rightAssignmentCtxId);
103        
104        Object jsContext = jsParameters.get("context");
105        Object context = rightCtx.convertJSContext(jsContext);
106        
107        List<String> profileIds = (List<String>) jsParameters.get("profileIds");
108        if (profileIds == null)
109        {
110            // Get the identifiers of all existing profiles
111            profileIds = _profilesDAO.getProfiles().stream().map(profile -> profile.getId()).collect(Collectors.toList());
112        }
113        
114        List<Map<String, Object>> assignments = new ArrayList<>();
115        
116        assignments.add(_getAssignmentForAnonymous(rightCtx, context, profileIds));
117        assignments.add(_getAssignmentForAnyConnectedUser(rightCtx, context, profileIds));
118        assignments.addAll(_getAssignmentForUsers(rightCtx, context, context, profileIds).values());
119        assignments.addAll(_getAssignmentForGroups(rightCtx, context, context, profileIds).values());
120        
121        result.put("assignments", assignments);
122        
123        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
124        
125        return EMPTY_MAP;
126    }
127    
128    private Map<String, Object> _getAssignmentForAnonymous(RightAssignmentContext rightCtx, Object context, List<String> profileIds)
129    {
130        Map<String, Object> assignment = new HashMap<>();
131        
132        assignment.put("targetType", TargetType.ANONYMOUS.toString());
133        
134        for (String profileId : profileIds)
135        {
136            _getAssignmentForAnonymous(rightCtx, assignment, context, context, profileId);
137        }
138        
139        return assignment;
140    }
141    
142    private void _getAssignmentForAnonymous(RightAssignmentContext rightCtx, Map<String, Object> assignment, Object initialContext, Object currentContext, String profileId)
143    {
144        Map<AnonymousOrAnyConnectedKeys, Set<String>> profilesForAnonymousAndAnyConnectedUser = _profileAssignmentStorageEP.getProfilesForAnonymousAndAnyConnectedUser(currentContext);
145
146        Set<String> deniedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_DENIED)).orElse(Set.of());
147        if (deniedProfiles.contains(profileId))
148        {
149            assignment.put(profileId, currentContext == initialContext ? AccessType.DENY.toString() : AccessType.INHERITED_DENY.toString());
150            return;
151        }
152        
153        Set<String> allowedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_ALLOWED)).orElse(Set.of());
154        if (allowedProfiles.contains(profileId))
155        {
156            assignment.put(profileId, currentContext == initialContext ? AccessType.ALLOW.toString() : AccessType.INHERITED_ALLOW.toString());
157            return;
158        }
159        
160        if (!_profileAssignmentStorageEP.isInheritanceDisallowed(currentContext))
161        {
162            // Can not determine assignment on current context, up to parent context
163            Set<Object> parentContexts = rightCtx.getParentContexts(currentContext); 
164            if (parentContexts != null)
165            {
166                for (Object parentContext : parentContexts)
167                {
168                    Map<String, Object> parentAssignment = new HashMap<>();
169                    _getAssignmentForAnonymous(rightCtx, parentAssignment, initialContext, parentContext, profileId);
170                    
171                    String parentValue = (String) parentAssignment.get(profileId);
172                    
173                    if (AccessType.INHERITED_DENY.toString().equals(parentValue))
174                    {
175                        assignment.put(profileId, AccessType.INHERITED_DENY.toString());
176                        return;
177                    }
178                    else if (AccessType.INHERITED_ALLOW.toString().equals(parentValue))
179                    {
180                        assignment.put(profileId, AccessType.INHERITED_ALLOW.toString());
181                    }
182                }
183            }
184        }
185    }
186    
187    private Map<String, Object> _getAssignmentForAnyConnectedUser(RightAssignmentContext rightCtx, Object context, List<String> profileIds)
188    {
189        Map<String, Object> assignment = new HashMap<>();
190        
191        assignment.put("targetType", TargetType.ANYCONNECTED_USER.toString());
192        
193        for (String profileId : profileIds)
194        {
195            _getAssignmentForAnyConnectedUser(rightCtx, assignment, context, context, profileId);
196        }
197        
198        return assignment;
199    }
200    
201    private void _getAssignmentForAnyConnectedUser(RightAssignmentContext rightCtx, Map<String, Object> assignment, Object initialContext, Object currentContext, String profileId)
202    {
203        Map<AnonymousOrAnyConnectedKeys, Set<String>> profilesForAnonymousAndAnyConnectedUser = _profileAssignmentStorageEP.getProfilesForAnonymousAndAnyConnectedUser(currentContext);
204
205        Set<String> deniedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_DENIED)).orElse(Set.of());
206        if (deniedProfiles.contains(profileId))
207        {
208            assignment.put(profileId, currentContext == initialContext ? AccessType.DENY.toString() : AccessType.INHERITED_DENY.toString());
209            return;
210        }
211        
212        Set<String> allowedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_ALLOWED)).orElse(Set.of());
213        if (allowedProfiles.contains(profileId))
214        {
215            assignment.put(profileId, currentContext == initialContext ? AccessType.ALLOW.toString() : AccessType.INHERITED_ALLOW.toString());
216            return;
217        }
218        
219        if (!_profileAssignmentStorageEP.isInheritanceDisallowed(currentContext))
220        {   
221            // Can not determines assignment on current context, up to parent context
222            Set<Object> parentContexts = rightCtx.getParentContexts(currentContext); 
223            if (parentContexts != null)
224            {
225                for (Object parentContext : parentContexts)
226                {
227                    Map<String, Object> parentAssignment = new HashMap<>();
228                    _getAssignmentForAnyConnectedUser(rightCtx, parentAssignment, initialContext, parentContext, profileId);
229                    
230                    String parentValue = (String) parentAssignment.get(profileId);
231                    
232                    if (AccessType.INHERITED_DENY.toString().equals(parentValue))
233                    {
234                        assignment.put(profileId, AccessType.INHERITED_DENY.toString());
235                        return;
236                    }
237                    else if (AccessType.INHERITED_ALLOW.toString().equals(parentValue))
238                    {
239                        assignment.put(profileId, AccessType.INHERITED_ALLOW.toString());
240                    }
241                }
242            }
243        }
244    }    
245    
246    private Map<UserIdentity, Map<String, Object>> _getAssignmentForUsers(RightAssignmentContext rightCtx, Object initialContext, Object context, List<String> profileIds)
247    {
248        Map<UserIdentity, Map<String, Object>> assignments = new LinkedHashMap<>();
249        
250        // Get all user with a denied profile on current context
251        Map<UserIdentity, Map<UserOrGroup, Set<String>>> profilesForUsers = _profileAssignmentStorageEP.getProfilesForUsers(context, null);
252        for (Entry<UserIdentity, Map<UserOrGroup, Set<String>>> entry : profilesForUsers.entrySet())
253        {
254            _getAssignementForDeniedUser(initialContext, profileIds, assignments, context, Optional.ofNullable(entry.getValue().get(UserOrGroup.DENIED)).orElse(Set.of()), entry.getKey());
255        }
256        
257        // Get all user with a allowed profile on current context
258        for (Entry<UserIdentity, Map<UserOrGroup, Set<String>>> entry : profilesForUsers.entrySet())
259        {
260            _getAssignementForAllowedUser(initialContext, profileIds, assignments, context, Optional.ofNullable(entry.getValue().get(UserOrGroup.ALLOWED)).orElse(Set.of()), entry.getKey());
261        }
262
263        if (!_profileAssignmentStorageEP.isInheritanceDisallowed(context))
264        {
265            // Up to parent context
266            Set<Object> parentContexts = rightCtx.getParentContexts(context);
267            if (parentContexts != null)
268            {        
269                Map<UserIdentity, Map<String, Object>> allParentsAssignments = new HashMap<>();
270                for (Object parentContext : parentContexts)
271                {
272                    Map<UserIdentity, Map<String, Object>> parentAssignment = _getAssignmentForUsers(rightCtx, initialContext, parentContext, profileIds);
273                    _mergeUser(allParentsAssignments, parentAssignment, false);
274                }
275                
276                _mergeUser(assignments, allParentsAssignments, true);
277            }
278        }
279        
280        return assignments;
281    }
282
283    private void _getAssignementForAllowedUser(Object context, List<String> profileIds, Map<UserIdentity, Map<String, Object>> assignments, Object currentContext,
284            Set<String> allowedProfilesForUser, UserIdentity userIdentity)
285    {
286        // Check if the user still exits
287        User user = _userManager.getUser(userIdentity);
288        if (user != null)
289        { 
290            if (!assignments.containsKey(userIdentity))
291            {
292                assignments.put(userIdentity, _user2json(user));
293            }
294            
295            Map<String, Object> userAssignment = assignments.get(userIdentity);
296            
297            for (String profileId : allowedProfilesForUser)
298            {
299                if (profileIds.contains(profileId) && !userAssignment.containsKey(profileId))
300                {
301                    userAssignment.put(profileId, currentContext == context ? AccessType.ALLOW.toString() : AccessType.INHERITED_ALLOW.toString());
302                }
303            }
304        }
305    }
306
307    private void _getAssignementForDeniedUser(Object context, List<String> profileIds, Map<UserIdentity, Map<String, Object>> assignments, Object currentContext,
308            Set<String> deniedProfilesForUser, UserIdentity userIdentity)
309    {
310        // Check if the user still exits
311        User user = _userManager.getUser(userIdentity);
312        if (user != null)
313        { 
314            if (!assignments.containsKey(userIdentity))
315            {
316                Map<String, Object> user2json = _user2json(user);
317                if (user2json != null)
318                {
319                    assignments.put(userIdentity, user2json);
320                }
321            }
322            
323            Map<String, Object> userAssignment = assignments.get(userIdentity);
324            
325            for (String profileId : deniedProfilesForUser)
326            {
327                if (profileIds.contains(profileId) && !userAssignment.containsKey(profileId))
328                {
329                    userAssignment.put(profileId, currentContext == context ? AccessType.DENY.toString() : AccessType.INHERITED_DENY.toString());
330                }
331            }
332        }
333    }
334    
335    private Map<GroupIdentity, Map<String, Object>> _getAssignmentForGroups(RightAssignmentContext rightCtx, Object initialContext, Object context, List<String> profileIds)
336    {
337        Map<GroupIdentity, Map<String, Object>> assignments = new LinkedHashMap<>();
338        
339        // Get all user with a denied profile on current context
340        Map<GroupIdentity, Map<UserOrGroup, Set<String>>> profilesForGroups = _profileAssignmentStorageEP.getProfilesForGroups(context, null);
341        for (Entry<GroupIdentity, Map<UserOrGroup, Set<String>>> entry : profilesForGroups.entrySet())
342        {
343            _getAssignmentForDeniedGroup(initialContext, profileIds, assignments, context, Optional.ofNullable(entry.getValue().get(UserOrGroup.DENIED)).orElse(Set.of()), entry.getKey());
344        }
345        
346        // Get all user with a allowed profile on current context
347        for (Entry<GroupIdentity, Map<UserOrGroup, Set<String>>> entry : profilesForGroups.entrySet())
348        {
349            _getAssignementForAllowedGroup(initialContext, profileIds, assignments, context, Optional.ofNullable(entry.getValue().get(UserOrGroup.ALLOWED)).orElse(Set.of()), entry.getKey());
350        }
351        
352        if (!_profileAssignmentStorageEP.isInheritanceDisallowed(context))
353        {
354            // Up to parent context
355            Set<Object> parentContexts = rightCtx.getParentContexts(context);
356            if (parentContexts != null)
357            {        
358                Map<GroupIdentity, Map<String, Object>> allParentsAssignments = new HashMap<>();
359                for (Object parentContext : parentContexts)
360                {
361                    Map<GroupIdentity, Map<String, Object>> parentAssignment = _getAssignmentForGroups(rightCtx, initialContext, parentContext, profileIds);
362                    _mergeGroup(allParentsAssignments, parentAssignment, false);
363                }
364                
365                _mergeGroup(assignments, allParentsAssignments, true);
366            }
367        }
368        
369        return assignments;
370    }
371    
372    private void _mergeUser(Map<UserIdentity, Map<String, Object>> finalAssignments, Map<UserIdentity, Map<String, Object>> parentsAssignments, boolean finalListPrevails)
373    {
374        for (UserIdentity identity : parentsAssignments.keySet())
375        {
376            if (!finalAssignments.containsKey(identity))
377            {
378                // Was not existing... overwrite
379                finalAssignments.put(identity, parentsAssignments.get(identity));
380            }
381            else
382            {
383                // Was existing... merge
384                for (String profileId : parentsAssignments.get(identity).keySet())
385                {
386                    Object assignmentsValue = finalAssignments.get(identity).get(profileId);
387                    Object allParentsAssignmentsValue = parentsAssignments.get(identity).get(profileId);
388                    
389                    if (assignmentsValue == null 
390                            || AccessType.UNKNOWN.toString().equals(assignmentsValue)
391                            || !finalListPrevails && AccessType.INHERITED_ALLOW.toString().equals(assignmentsValue) && !AccessType.UNKNOWN.toString().equals(allParentsAssignmentsValue))
392                    {
393                        finalAssignments.get(identity).put(profileId, allParentsAssignmentsValue);
394                    }
395                }
396            }
397        }
398    }
399    private void _mergeGroup(Map<GroupIdentity, Map<String, Object>> finalAssignments, Map<GroupIdentity, Map<String, Object>> parentsAssignments, boolean finalListPrevails)
400    {
401        for (GroupIdentity identity : parentsAssignments.keySet())
402        {
403            if (!finalAssignments.containsKey(identity))
404            {
405                // Was not existing... overwrite
406                finalAssignments.put(identity, parentsAssignments.get(identity));
407            }
408            else
409            {
410                // Was existing... merge
411                for (String profileId : parentsAssignments.get(identity).keySet())
412                {
413                    Object assignmentsValue = finalAssignments.get(identity).get(profileId);
414                    Object allParentsAssignmentsValue = parentsAssignments.get(identity).get(profileId);
415                    
416                    if (assignmentsValue == null 
417                            || AccessType.UNKNOWN.toString().equals(assignmentsValue)
418                            || !finalListPrevails && AccessType.INHERITED_ALLOW.toString().equals(assignmentsValue) && !AccessType.UNKNOWN.toString().equals(allParentsAssignmentsValue))
419                    {
420                        finalAssignments.get(identity).put(profileId, allParentsAssignmentsValue);
421                    }
422                }
423            }
424        }
425    }
426
427    private void _getAssignementForAllowedGroup(Object context, List<String> profileIds, Map<GroupIdentity, Map<String, Object>> assignments, Object currentContext,
428            Set<String> allowedProfilesForGroup, GroupIdentity gpIdentity)
429    {
430        Group group = _groupManager.getGroup(gpIdentity.getDirectoryId(), gpIdentity.getId());
431        if (group != null)
432        {
433            if (!assignments.containsKey(gpIdentity))
434            {
435                assignments.put(gpIdentity, _group2json(group));
436            }
437            
438            Map<String, Object> gpAssignment = assignments.get(gpIdentity);
439            
440            for (String profileId : allowedProfilesForGroup)
441            {
442                if (profileIds.contains(profileId) && !gpAssignment.containsKey(profileId))
443                {
444                    gpAssignment.put(profileId, currentContext == context ? AccessType.ALLOW.toString() : AccessType.INHERITED_ALLOW.toString());
445                }
446            }
447        }
448    }
449
450    private void _getAssignmentForDeniedGroup(Object context, List<String> profileIds, Map<GroupIdentity, Map<String, Object>> assignments, Object currentContext,
451            Set<String> deniedProfilesForGroup, GroupIdentity gpIdentity)
452    {
453        Group group = _groupManager.getGroup(gpIdentity.getDirectoryId(), gpIdentity.getId());
454        if (group != null)
455        {
456            if (!assignments.containsKey(gpIdentity))
457            {
458                assignments.put(gpIdentity, _group2json(group));
459            }
460            
461            Map<String, Object> gpAssignment = assignments.get(gpIdentity);
462            
463            for (String profileId : deniedProfilesForGroup)
464            {
465                if (profileIds.contains(profileId) && !gpAssignment.containsKey(profileId))
466                {
467                    gpAssignment.put(profileId, currentContext == context ? AccessType.DENY.toString() : AccessType.INHERITED_DENY.toString());
468                }
469            }
470        }
471    }
472    
473    private Map<String, Object> _user2json(User user)
474    {
475        Map<String, Object> assignment = new HashMap<>();
476        assignment.put("targetType", TargetType.USER.toString());
477        
478        String login = user.getIdentity().getLogin();
479        String populationId = user.getIdentity().getPopulationId();
480        assignment.put("login", login);
481        assignment.put("populationId", populationId);
482        assignment.put("populationLabel", _userPopulationDAO.getUserPopulation(populationId).getLabel());
483        assignment.put("groups", _groupManager.getUserGroups(user.getIdentity()).stream()
484                                    .map(this::_userGroup2json)
485                                    .collect(Collectors.toList()));
486        assignment.put("userSortableName", user.getSortableName());
487        
488        return assignment;
489    }
490   
491    
492    private Map<String, Object> _group2json(Group group)
493    {
494        Map<String, Object> assignment = new HashMap<>();
495        assignment.put("targetType", TargetType.GROUP.toString());
496        
497        String groupId = group.getIdentity().getId();
498        String directoryId = group.getIdentity().getDirectoryId();
499        assignment.put("groupId", groupId);
500        assignment.put("groupDirectory", directoryId);
501        assignment.put("groupDirectoryLabel", _groupDirectoryDAO.getGroupDirectory(directoryId).getLabel());
502        assignment.put("groupLabel", group.getLabel());
503        
504        return assignment;
505    }
506    
507    private Map<String, Object> _userGroup2json(GroupIdentity group)
508    {
509        Map<String, Object> result = new HashMap<>();
510        result.put("groupId", group.getId());
511        result.put("groupDirectory", group.getDirectoryId());
512        return result;
513    }
514}