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.ui.right;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028
029import org.ametys.core.ObservationConstants;
030import org.ametys.core.group.GroupDirectoryDAO;
031import org.ametys.core.group.GroupIdentity;
032import org.ametys.core.group.GroupManager;
033import org.ametys.core.observation.Event;
034import org.ametys.core.observation.ObservationManager;
035import org.ametys.core.right.ProfileAssignmentStorageExtensionPoint;
036import org.ametys.core.right.RightAssignmentContext;
037import org.ametys.core.right.RightAssignmentContextExtensionPoint;
038import org.ametys.core.right.RightManager.RightResult;
039import org.ametys.core.right.RightsException;
040import org.ametys.core.ui.Callable;
041import org.ametys.core.ui.ClientSideElement;
042import org.ametys.core.ui.ClientSideElementHelper;
043import org.ametys.core.ui.StaticClientSideElement;
044import org.ametys.core.user.UserIdentity;
045import org.ametys.plugins.core.user.UserHelper;
046
047import com.google.common.collect.Sets;
048
049/**
050 * {@link ClientSideElement} for the tool displaying the profile assignments
051 */
052public class ProfileAssignmentsToolClientSideElement extends StaticClientSideElement
053{
054    /** The profile assignment storage component */
055    protected ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageEP;
056    /** The extension point for right assignment contexts */
057    protected RightAssignmentContextExtensionPoint _rightAssignmentContextEP;
058    /** The DAO for group directories */
059    protected GroupDirectoryDAO _groupDirectoryDAO;
060    /** The group manager */
061    protected GroupManager _groupManager;
062    /** The observation manager */
063    protected ObservationManager _observationManager;
064    /** The user helper */
065    protected UserHelper _userHelper;
066    
067    /**
068     * Enumeration of all possible access types
069     */
070    public enum AccessType
071    {
072        /**
073         * Indicates that the access is allowed
074         */
075        ALLOW 
076        {
077            @Override
078            public String toString()
079            {
080                return "allow";
081            }
082        },
083        /**
084         * Indicates that the access is denied
085         */
086        DENY 
087        {
088            @Override
089            public String toString()
090            {
091                return "deny";
092            }
093        },
094        /**
095         * Indicates that the access is allowed by inheritance
096         */
097        INHERITED_ALLOW 
098        {
099            @Override
100            public String toString()
101            {
102                return "inherited_allow";
103            }
104        },
105        /**
106         * Indicates that the access is denied by inheritance
107         */
108        INHERITED_DENY 
109        {
110            @Override
111            public String toString()
112            {
113                return "inherited_deny";
114            }
115        },
116        /**
117         * Indicates that the access can not be determined
118         */
119        UNKNOWN 
120        {
121            @Override
122            public String toString()
123            {
124                return "unknown";
125            }
126        }
127    }
128    
129    /**
130     * Enumeration of all possible target types
131     */
132    public enum TargetType 
133    {
134        /**
135         * Indicates that the target is the anonymous user
136         */
137        ANONYMOUS 
138        {
139            @Override
140            public String toString()
141            {
142                return "anonymous";
143            }
144        },
145        /**
146         * Indicates that the target is the anonymous user
147         */
148        ANYCONNECTED_USER 
149        {
150            @Override
151            public String toString()
152            {
153                return "anyconnected_user";
154            }
155        },
156        /**
157         * Indicates that the target is a user
158         */
159        USER 
160        {
161            @Override
162            public String toString()
163            {
164                return "user";
165            }
166        },
167        /**
168         * Indicates that the target is a group
169         */
170        GROUP 
171        {
172            @Override
173            public String toString()
174            {
175                return "group";
176            }
177        }
178    }
179
180    @Override
181    public void service(ServiceManager smanager) throws ServiceException
182    {
183        super.service(smanager);
184        _profileAssignmentStorageEP = (ProfileAssignmentStorageExtensionPoint) smanager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE);
185        _rightAssignmentContextEP = (RightAssignmentContextExtensionPoint) smanager.lookup(RightAssignmentContextExtensionPoint.ROLE);
186        _groupDirectoryDAO = (GroupDirectoryDAO) smanager.lookup(GroupDirectoryDAO.ROLE);
187        _groupManager = (GroupManager) smanager.lookup(GroupManager.ROLE);
188        _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE);
189        _userHelper = (UserHelper) smanager.lookup(UserHelper.ROLE);
190    }
191    
192    @SuppressWarnings("unchecked")
193    @Override
194    public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters)
195    {
196        List<Script> scripts = super.getScripts(ignoreRights, contextParameters);
197        
198        if (scripts.size() > 0)
199        {
200            Script script = ClientSideElementHelper.cloneScript(scripts.get(0));
201            
202            Map<String, Object> jsClasses = new HashMap<>();
203            script.getParameters().put("classes", jsClasses);
204            
205            boolean excludePrivate = true;
206            Set<String> rightContextIds = _rightAssignmentContextEP.getExtensionsIds();
207            if (script.getParameters().containsKey("right-contexts"))
208            {
209                excludePrivate = false;
210                // Restrict the right contexts to the configured right contexts if exist
211                Object ctxConfig = ((Map<String, Object>) script.getParameters().get("right-contexts")).get("right-context");
212                if (ctxConfig instanceof List)
213                {
214                    rightContextIds = new HashSet<>((List<String>) ctxConfig);
215                }
216                else
217                {
218                    rightContextIds = Sets.newHashSet((String) ctxConfig);
219                }
220            }
221            
222            for (String rightContextId: rightContextIds)
223            {
224                RightAssignmentContext rightAssignmentContext = _rightAssignmentContextEP.getExtension(rightContextId);
225                
226                if (!excludePrivate || !rightAssignmentContext.isPrivate())
227                {
228                    List<Script> rightAssignmentContextScripts = rightAssignmentContext.getScripts(ignoreRights, contextParameters);
229                    int index = 0;
230                    for (Script rightAssignmentContextScript: rightAssignmentContextScripts)
231                    {
232                        Map<String, Object> classInfo = new HashMap<>();
233                        classInfo.put("className", rightAssignmentContextScript.getScriptClassname());
234                        classInfo.put("serverId", rightContextId);
235                        classInfo.put("parameters", rightAssignmentContextScript.getParameters());
236                        jsClasses.put(rightContextId + "-" + index++, classInfo);
237                        
238                        script.getScriptFiles().addAll(rightAssignmentContextScript.getScriptFiles());
239                        script.getCSSFiles().addAll(rightAssignmentContextScript.getCSSFiles());
240                    }
241                }
242                
243            }
244            
245            scripts = new ArrayList<>();
246            scripts.add(script);
247        }
248        
249        return scripts;
250    }
251    
252    /**
253     * Gets the groups of a user as JSON
254     * @param login The login of the user
255     * @param populationId The population of the user
256     * @return the groups of a user as JSON
257     */
258    @Callable
259    public List<Map<String, Object>> getUserGroups(String login, String populationId)
260    {
261        return _groupManager.getUserGroups(new UserIdentity(login, populationId)).stream()
262            .map(this::_groupToJson)
263            .collect(Collectors.toList());
264    }
265    
266    private Map<String, Object> _groupToJson(GroupIdentity groupIdentity)
267    {
268        Map<String, Object> result = new HashMap<>();
269        result.put("groupId", groupIdentity.getId());
270        result.put("groupDirectory", groupIdentity.getDirectoryId());
271        return result;
272    }
273    
274    /**
275     * Save some changes made client-side.
276     * @param rightAssignmentCtxId The id of the right assignment context
277     * @param jsContext The JS object context
278     * @param assignmentsInfo The list of all the changes to make. Each map in the list must contain the following keys:
279     * <ol>
280     * <li><b>profileId</b> for the id of the profile (as a string)</li>
281     * <li><b>assignment</b> for the kind of assignment (can be ACCESS_TYPE_ALLOW, ACCESS_TYPE_DENY...)</li>
282     * <li><b>assignmentType</b> expects one of these four strings: "user", "group", "anonymous", "anyConnectedUser"</li>
283     * <li><b>identity</b> Can be null if assignmentType is "anonymous" or "anyConnectedUser". If "user", must be a map with the keys "login" and "populationId". If "group", must be a map with the keys "groupId" and "groupDirectory"</li>
284     * </ol>
285     */
286    @SuppressWarnings("unchecked")
287    @Callable
288    public void saveChanges(String rightAssignmentCtxId, Object jsContext, List<Map<String, Object>> assignmentsInfo)
289    {
290        if (_rightManager.hasRight(_currentUserProvider.getUser(), "Runtime_Rights_Rights_Handle", "/${WorkspaceName}") != RightResult.RIGHT_ALLOW)
291        {
292            throw new RightsException("The user '" + _currentUserProvider.getUser() + "' try to assign profile without sufficient rights");
293        }
294        
295        Set<String> updatedProfiles = new HashSet<>();
296        RightAssignmentContext rightAssignmentContext = _rightAssignmentContextEP.getExtension(rightAssignmentCtxId);
297        Object context = rightAssignmentContext.convertJSContext(jsContext);
298        String contextIdentifier = rightAssignmentContext.getContextIdentifier(context);
299        for (Map<String, Object> assignmentInfo : assignmentsInfo)
300        {
301            String profileId = (String) assignmentInfo.get("profileId");
302            updatedProfiles.add(profileId);
303            String assignment = (String) assignmentInfo.get("assignment");
304            String targetType = (String) assignmentInfo.get("targetType");
305            Map<String, String> identity = (Map<String, String>) assignmentInfo.get("identity");
306            _saveChange(context, profileId, assignment, targetType, identity);
307        }
308        _notifyObservers(context, contextIdentifier, updatedProfiles);
309    }
310    
311    private void _notifyObservers(Object context, String contextIdentifier, Set<String> profileIds)
312    {
313        Map<String, Object> eventParams = new HashMap<>();
314        eventParams.put(ObservationConstants.ARGS_ACL_CONTEXT, context);
315        eventParams.put(ObservationConstants.ARGS_ACL_CONTEXT_IDENTIFIER, contextIdentifier);
316        eventParams.put(ObservationConstants.ARGS_ACL_PROFILES, profileIds);
317        
318        _observationManager.notify(new Event(ObservationConstants.EVENT_ACL_UPDATED, _currentUserProvider.getUser(), eventParams));
319    }
320
321    /**
322     * Get the first permission given by inheritance for a object context and profiles
323     * @param rightAssignmentCtxId The id of the right assignment context
324     * @param jsContext The JS object context
325     * @param profileIds The list of profiles
326     * @param targetType The type of target : anonymous, any connected users, a user or a group
327     * @param identity The identity of the target. Can be null if the target is anonymous or any connected users
328     * @return The first access type given by inheritance for each profile
329     */
330    @Callable
331    public Map<String, String> getInheritedAssignments (String rightAssignmentCtxId, Object jsContext, List<String> profileIds, String targetType, Map<String, String> identity)
332    {
333        Map<String, String> assignments = new HashMap<>();
334        
335        for (String profileId : profileIds)
336        {
337            assignments.put(profileId, getInheritedAssignment(rightAssignmentCtxId, jsContext, profileId, targetType, identity));
338        }
339        return assignments;
340    }
341    
342    /**
343     * Get the first permission given by inheritance for a object context and a specific profile
344     * @param rightAssignmentCtxId The id of the right assignment context
345     * @param jsContext The JS object context
346     * @param profileId The id of profile 
347     * @param targetType The type of target : anonymous, any connected users, a user or a group
348     * @param identity The identity of the target. Can be null if the target is anonymous or any connected users
349     * @return The first access type given by inheritance
350     */
351    @Callable
352    public String getInheritedAssignment (String rightAssignmentCtxId, Object jsContext, String profileId, String targetType, Map<String, String> identity)
353    {
354        RightAssignmentContext rightCtx = _rightAssignmentContextEP.getExtension(rightAssignmentCtxId);
355        Object context = rightCtx.convertJSContext(jsContext);
356        
357        switch (TargetType.valueOf(targetType.toUpperCase()))
358        {
359            case ANONYMOUS:
360                return _getInheritedAssignmentForAnonymous(rightCtx, context, profileId);
361            case ANYCONNECTED_USER:
362                return _getInheritedAssignmentForAnyconnected(rightCtx, context, profileId);
363            case USER:
364                UserIdentity user = _userHelper.json2userIdentity(identity);
365                return _getInheritedAssignmentForUser(rightCtx, context, profileId, user);
366            case GROUP:
367                GroupIdentity group = new GroupIdentity(identity.get("groupId"), identity.get("groupDirectory"));
368                return _getInheritedAssignmentForGroup(rightCtx, context, profileId, group);
369            default:
370                return AccessType.UNKNOWN.toString();
371        }
372    }
373    
374    private String _getInheritedAssignmentForAnonymous (RightAssignmentContext extension, Object context, String profileId)
375    {
376        String value = AccessType.UNKNOWN.toString();
377        
378        Set<Object> parentContexts = extension.getParentContexts(context);
379        if (parentContexts != null)
380        {
381            for (Object parentContext : parentContexts)
382            {
383                Set<String> deniedProfiles = _profileAssignmentStorageEP.getDeniedProfilesForAnonymous(parentContext);
384                if (deniedProfiles.contains(profileId))
385                {
386                    return AccessType.INHERITED_DENY.toString();
387                }
388                
389                Set<String> allowedProfiles = _profileAssignmentStorageEP.getAllowedProfilesForAnonymous(parentContext);
390                if (allowedProfiles.contains(profileId))
391                {
392                    value = AccessType.INHERITED_ALLOW.toString();
393                }
394    
395                String parentsValue = _getInheritedAssignmentForAnonymous(extension, parentContext, profileId);
396                if (!AccessType.UNKNOWN.toString().equals(parentsValue))
397                {
398                    value = parentsValue;
399                }
400            }
401        }
402        
403        return value;
404    }
405    
406    private String _getInheritedAssignmentForAnyconnected(RightAssignmentContext extension, Object context, String profileId)
407    {
408        String value = AccessType.UNKNOWN.toString();
409        
410        Set<Object> parentContexts = extension.getParentContexts(context);
411        if (parentContexts != null)
412        {
413            for (Object parentContext : parentContexts)
414            {
415                Set<String> deniedProfiles = _profileAssignmentStorageEP.getDeniedProfilesForAnyConnectedUser(parentContext);
416                if (deniedProfiles.contains(profileId))
417                {
418                    return AccessType.INHERITED_DENY.toString();
419                }
420                
421                Set<String> allowedProfiles = _profileAssignmentStorageEP.getAllowedProfilesForAnyConnectedUser(parentContext);
422                if (allowedProfiles.contains(profileId))
423                {
424                    value = AccessType.INHERITED_ALLOW.toString();
425                }
426    
427                String parentsValue = _getInheritedAssignmentForAnyconnected(extension, parentContext, profileId);
428                if (!AccessType.UNKNOWN.toString().equals(parentsValue))
429                {
430                    value = parentsValue;
431                }
432            }
433        }
434        
435        return value;
436    }
437
438    private String _getInheritedAssignmentForUser(RightAssignmentContext extension, Object context, String profileId, UserIdentity user)
439    {
440        String value = AccessType.UNKNOWN.toString();
441
442        Set<Object> parentContexts = extension.getParentContexts(context);
443        if (parentContexts != null)
444        {
445            for (Object parentContext : parentContexts)
446            {
447                // FIXME Optimization _profileAssignmentStorageEP.getDeniedProfilesForUser(parentContext, user)
448                Map<UserIdentity, Set<String>> deniedProfiles = _profileAssignmentStorageEP.getDeniedProfilesForUsers(parentContext);
449                if (deniedProfiles.containsKey(user) && deniedProfiles.get(user).contains(profileId))
450                {
451                    return AccessType.INHERITED_DENY.toString();
452                }
453                
454                Map<UserIdentity, Set<String>> allowedProfiles = _profileAssignmentStorageEP.getAllowedProfilesForUsers(parentContext);
455                if (allowedProfiles.containsKey(user) && allowedProfiles.get(user).contains(profileId))
456                {
457                    value = AccessType.INHERITED_ALLOW.toString();
458                }
459                
460                String parentsValue = _getInheritedAssignmentForUser(extension, parentContext, profileId, user);
461                if (!AccessType.UNKNOWN.toString().equals(parentsValue))
462                {
463                    value = parentsValue;
464                }
465            }
466        }
467        
468        return value;
469    }
470    
471    private String _getInheritedAssignmentForGroup(RightAssignmentContext extension, Object context, String profileId, GroupIdentity group)
472    {
473        String value = AccessType.UNKNOWN.toString();
474
475        Set<Object> parentContexts = extension.getParentContexts(context);
476        if (parentContexts != null)
477        {        
478            for (Object parentContext : parentContexts)
479            {
480                // FIXME Optimization _profileAssignmentStorageEP.getDeniedProfilesForUser(parentContext, user)
481                Map<GroupIdentity, Set<String>> deniedProfiles = _profileAssignmentStorageEP.getDeniedProfilesForGroups(parentContext);
482                if (deniedProfiles.containsKey(group) && deniedProfiles.get(group).contains(profileId))
483                {
484                    return AccessType.INHERITED_DENY.toString();
485                }
486                
487                Map<GroupIdentity, Set<String>> allowedProfiles = _profileAssignmentStorageEP.getAllowedProfilesForGroups(parentContext);
488                if (allowedProfiles.containsKey(group) && allowedProfiles.get(group).contains(profileId))
489                {
490                    value = AccessType.INHERITED_ALLOW.toString();
491                }
492                
493                String parentsValue = _getInheritedAssignmentForGroup(extension, parentContext, profileId, group);
494                if (!AccessType.UNKNOWN.toString().equals(parentsValue))
495                {
496                    value = parentsValue;
497                }
498            }
499        }
500        
501        return value;
502    }
503    
504    private void _saveChange(Object context, String profileId, String assignment, String targetType, Map<String, String> identity)
505    {
506        AccessType accessType = assignment != null ? AccessType.valueOf(assignment.toUpperCase()) : AccessType.UNKNOWN;
507        switch (TargetType.valueOf(targetType.toUpperCase()))
508        {
509            case ANONYMOUS:
510                switch (accessType)
511                {
512                    case ALLOW:
513                        _profileAssignmentStorageEP.removeDeniedProfileFromAnonymous(profileId, context);
514                        _profileAssignmentStorageEP.allowProfileToAnonymous(profileId, context);
515                        break;
516                    case DENY:
517                        _profileAssignmentStorageEP.removeAllowedProfileFromAnonymous(profileId, context);
518                        _profileAssignmentStorageEP.denyProfileToAnonymous(profileId, context);
519                        break;
520                    default:
521                        _profileAssignmentStorageEP.removeAllowedProfileFromAnonymous(profileId, context);
522                        _profileAssignmentStorageEP.removeDeniedProfileFromAnonymous(profileId, context);
523                        break;
524                }
525                break;
526                
527            case ANYCONNECTED_USER:
528                switch (accessType)
529                {
530                    case ALLOW:
531                        _profileAssignmentStorageEP.removeDeniedProfileFromAnyConnectedUser(profileId, context);
532                        _profileAssignmentStorageEP.allowProfileToAnyConnectedUser(profileId, context);
533                        break;
534                    case DENY:
535                        _profileAssignmentStorageEP.removeAllowedProfileFromAnyConnectedUser(profileId, context);
536                        _profileAssignmentStorageEP.denyProfileToAnyConnectedUser(profileId, context);
537                        break;
538                    default:
539                        _profileAssignmentStorageEP.removeAllowedProfileFromAnyConnectedUser(profileId, context);
540                        _profileAssignmentStorageEP.removeDeniedProfileFromAnyConnectedUser(profileId, context);
541                        break;
542                }
543                break;
544                
545            case USER:
546                UserIdentity user = _userHelper.json2userIdentity(identity);
547                switch (accessType)
548                {
549                    case ALLOW:
550                        _profileAssignmentStorageEP.removeDeniedProfileFromUser(user, profileId, context);
551                        _profileAssignmentStorageEP.allowProfileToUser(user, profileId, context);
552                        break;
553                    case DENY:    
554                        _profileAssignmentStorageEP.removeAllowedProfileFromUser(user, profileId, context);
555                        _profileAssignmentStorageEP.denyProfileToUser(user, profileId, context);
556                        break;
557                    default:
558                        _profileAssignmentStorageEP.removeAllowedProfileFromUser(user, profileId, context);
559                        _profileAssignmentStorageEP.removeDeniedProfileFromUser(user, profileId, context);
560                        break;
561                }
562                break;
563                
564            case GROUP:
565                GroupIdentity group = new GroupIdentity(identity.get("groupId"), identity.get("groupDirectory"));
566                switch (accessType)
567                {
568                    case ALLOW:
569                        _profileAssignmentStorageEP.removeDeniedProfileFromGroup(group, profileId, context);
570                        _profileAssignmentStorageEP.allowProfileToGroup(group, profileId, context);
571                        break;
572                    case DENY:  
573                        _profileAssignmentStorageEP.removeAllowedProfileFromGroup(group, profileId, context);
574                        _profileAssignmentStorageEP.denyProfileToGroup(group, profileId, context);
575                        break;
576                    default:
577                        _profileAssignmentStorageEP.removeAllowedProfileFromGroup(group, profileId, context);
578                        _profileAssignmentStorageEP.removeDeniedProfileFromGroup(group, profileId, context);
579                        break;
580                }
581                break;
582            default:
583                break;
584        }
585    }
586    
587    
588}