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.Objects;
024import java.util.Optional;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.configuration.Configuration;
029import org.apache.avalon.framework.configuration.ConfigurationException;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032
033import org.ametys.core.ObservationConstants;
034import org.ametys.core.group.GroupDirectoryDAO;
035import org.ametys.core.group.GroupIdentity;
036import org.ametys.core.group.GroupManager;
037import org.ametys.core.observation.Event;
038import org.ametys.core.observation.ObservationManager;
039import org.ametys.core.right.Profile;
040import org.ametys.core.right.ProfileAssignmentStorage.AnonymousOrAnyConnectedKeys;
041import org.ametys.core.right.ProfileAssignmentStorage.UserOrGroup;
042import org.ametys.core.right.ProfileAssignmentStorageExtensionPoint;
043import org.ametys.core.right.RightAssignmentContext;
044import org.ametys.core.right.RightAssignmentContextExtensionPoint;
045import org.ametys.core.right.RightManager;
046import org.ametys.core.right.RightManager.RightResult;
047import org.ametys.core.right.RightProfilesDAO;
048import org.ametys.core.right.RightsException;
049import org.ametys.core.ui.Callable;
050import org.ametys.core.ui.ClientSideElement;
051import org.ametys.core.ui.ClientSideElementHelper;
052import org.ametys.core.ui.StaticClientSideElement;
053import org.ametys.core.user.UserIdentity;
054import org.ametys.plugins.core.user.UserHelper;
055
056/**
057 * {@link ClientSideElement} for the tool displaying the profile assignments
058 */
059public class ProfileAssignmentsToolClientSideElement extends StaticClientSideElement
060{
061    /** The profile assignment storage component */
062    protected ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageEP;
063    /** The extension point for right assignment contexts */
064    protected RightAssignmentContextExtensionPoint _rightAssignmentContextEP;
065    /** The DAO for group directories */
066    protected GroupDirectoryDAO _groupDirectoryDAO;
067    /** The group manager */
068    protected GroupManager _groupManager;
069    /** The observation manager */
070    protected ObservationManager _observationManager;
071    /** The user helper */
072    protected UserHelper _userHelper;
073    /** The profile DAO */
074    protected RightProfilesDAO _profileDAO;
075    
076    /** The list of right contexts id handle by the tool */
077    private Set<String> _rightContexts;
078    private boolean _readerProfileOnly;
079    
080    /**
081     * Enumeration of all possible access types
082     */
083    public enum AccessType
084    {
085        /**
086         * Indicates that the access is allowed
087         */
088        ALLOW 
089        {
090            @Override
091            public String toString()
092            {
093                return "allow";
094            }
095        },
096        /**
097         * Indicates that the access is denied
098         */
099        DENY 
100        {
101            @Override
102            public String toString()
103            {
104                return "deny";
105            }
106        },
107        /**
108         * Indicates that the access is allowed by inheritance
109         */
110        INHERITED_ALLOW 
111        {
112            @Override
113            public String toString()
114            {
115                return "inherited_allow";
116            }
117        },
118        /**
119         * Indicates that the access is denied by inheritance
120         */
121        INHERITED_DENY 
122        {
123            @Override
124            public String toString()
125            {
126                return "inherited_deny";
127            }
128        },
129        /**
130         * Indicates that the access can not be determined
131         */
132        UNKNOWN 
133        {
134            @Override
135            public String toString()
136            {
137                return "unknown";
138            }
139        }
140    }
141    
142    /**
143     * Enumeration of all possible target types
144     */
145    public enum TargetType 
146    {
147        /**
148         * Indicates that the target is the anonymous user
149         */
150        ANONYMOUS 
151        {
152            @Override
153            public String toString()
154            {
155                return "anonymous";
156            }
157        },
158        /**
159         * Indicates that the target is the anonymous user
160         */
161        ANYCONNECTED_USER 
162        {
163            @Override
164            public String toString()
165            {
166                return "anyconnected_user";
167            }
168        },
169        /**
170         * Indicates that the target is a user
171         */
172        USER 
173        {
174            @Override
175            public String toString()
176            {
177                return "user";
178            }
179        },
180        /**
181         * Indicates that the target is a group
182         */
183        GROUP 
184        {
185            @Override
186            public String toString()
187            {
188                return "group";
189            }
190        }
191    }
192
193    @Override
194    public void service(ServiceManager smanager) throws ServiceException
195    {
196        super.service(smanager);
197        _profileAssignmentStorageEP = (ProfileAssignmentStorageExtensionPoint) smanager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE);
198        _rightAssignmentContextEP = (RightAssignmentContextExtensionPoint) smanager.lookup(RightAssignmentContextExtensionPoint.ROLE);
199        _groupDirectoryDAO = (GroupDirectoryDAO) smanager.lookup(GroupDirectoryDAO.ROLE);
200        _groupManager = (GroupManager) smanager.lookup(GroupManager.ROLE);
201        _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE);
202        _userHelper = (UserHelper) smanager.lookup(UserHelper.ROLE);
203        _profileDAO = (RightProfilesDAO) smanager.lookup(RightProfilesDAO.ROLE);
204    }
205    
206    @Override
207    public void configure(Configuration configuration) throws ConfigurationException
208    {
209        super.configure(configuration);
210        
211        _configureRightContextIds(configuration);
212        
213        _readerProfileOnly = configuration.getChild("class").getChild("reader-profile-only").getValueAsBoolean(false);
214    }
215    
216    /**
217     * Configure the restricted right context ids
218     * @param configuration the global configuration
219     * @throws ConfigurationException The configuration is incorrect
220     */
221    protected void _configureRightContextIds(Configuration configuration) throws ConfigurationException
222    {
223        _rightContexts = new HashSet<>();
224        
225        Configuration[] rightCtxConfig = configuration.getChild("class").getChild("right-contexts").getChildren("right-context");
226        
227        for (Configuration conf : rightCtxConfig)
228        {
229            _rightContexts.add(conf.getValue());
230        }
231    }
232    
233    /**
234     * Get the right assignments contexts handled by this tool
235     * @return the right assignments contexts
236     */
237    protected Set<RightAssignmentContext> getRightAssignmentContexts()
238    {
239        if (!_rightContexts.isEmpty())
240        {
241            return _rightContexts
242                    .stream()
243                    .map(id -> _rightAssignmentContextEP.getExtension(id))
244                    .filter(Objects::nonNull)
245                    .collect(Collectors.toSet());
246        }
247        else
248        {
249            // No restriction on handle contexts, get all contexts except private contexts
250            return _rightAssignmentContextEP.getExtensionsIds()
251                    .stream()
252                    .map(id -> _rightAssignmentContextEP.getExtension(id))
253                    .filter(ctx -> !ctx.isPrivate())
254                    .collect(Collectors.toSet());
255        }
256    }
257    
258    @Override
259    public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters)
260    {
261        List<Script> scripts = super.getScripts(ignoreRights, contextParameters);
262        
263        if (scripts.size() > 0)
264        {
265            Script script = ClientSideElementHelper.cloneScript(scripts.get(0));
266            
267            Map<String, Object> jsClasses = new HashMap<>();
268            script.getParameters().put("classes", jsClasses);
269            
270            Set<RightAssignmentContext> rightAssignmentContexts = getRightAssignmentContexts();
271            for (RightAssignmentContext rightAssignmentContext : rightAssignmentContexts)
272            {
273                List<Script> rightAssignmentContextScripts = rightAssignmentContext.getScripts(ignoreRights, contextParameters);
274                int index = 0;
275                for (Script rightAssignmentContextScript: rightAssignmentContextScripts)
276                {
277                    Map<String, Object> classInfo = new HashMap<>();
278                    classInfo.put("className", rightAssignmentContextScript.getScriptClassname());
279                    classInfo.put("serverId", rightAssignmentContext.getId());
280                    classInfo.put("parameters", rightAssignmentContextScript.getParameters());
281                    jsClasses.put(rightAssignmentContext.getId() + "-" + index++, classInfo);
282                    
283                    script.getScriptFiles().addAll(rightAssignmentContextScript.getScriptFiles());
284                    script.getCSSFiles().addAll(rightAssignmentContextScript.getCSSFiles());
285                }
286            }
287            
288            scripts = new ArrayList<>();
289            scripts.add(script);
290        }
291        
292        return scripts;
293    }
294    
295    /**
296     * Gets the groups of a user as JSON
297     * @param login The login of the user
298     * @param populationId The population of the user
299     * @return the groups of a user as JSON
300     */
301    @Callable
302    public List<Map<String, Object>> getUserGroups(String login, String populationId)
303    {
304        return _groupManager.getUserGroups(new UserIdentity(login, populationId)).stream()
305            .map(this::_groupToJson)
306            .collect(Collectors.toList());
307    }
308    
309    private Map<String, Object> _groupToJson(GroupIdentity groupIdentity)
310    {
311        Map<String, Object> result = new HashMap<>();
312        result.put("groupId", groupIdentity.getId());
313        result.put("groupDirectory", groupIdentity.getDirectoryId());
314        return result;
315    }
316    
317    /**
318     * Save some changes made client-side.
319     * @param rightAssignmentCtxId The id of the right assignment context
320     * @param jsContext The JS object context
321     * @param assignmentsInfo The list of all the changes to make. Each map in the list must contain the following keys:
322     * <ol>
323     * <li><b>profileId</b> for the id of the profile (as a string)</li>
324     * <li><b>assignment</b> for the kind of assignment (can be ACCESS_TYPE_ALLOW, ACCESS_TYPE_DENY...)</li>
325     * <li><b>assignmentType</b> expects one of these four strings: "user", "group", "anonymous", "anyConnectedUser"</li>
326     * <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>
327     * </ol>
328     * @return a map containing 3 objects :
329     * success : boolean, everything is saved
330     * successInfos : elements from assignmentsInfo that are saved
331     * errorInfos : elements from assignmentsInfo that are not saved
332     */
333    @SuppressWarnings("unchecked")
334    @Callable
335    public Map<String, Object> saveChanges(String rightAssignmentCtxId, Object jsContext, List<Map<String, Object>> assignmentsInfo)
336    {
337        Map<String, Object> result = new HashMap<>();
338        List<Map<String, Object>> successInfos = new ArrayList<>();
339        List<Map<String, Object>> errorInfos = new ArrayList<>();
340        
341        UserIdentity user = _currentUserProvider.getUser();
342        Set<GroupIdentity> groups = _groupManager.getUserGroups(user);
343        
344        if (!_checkRightAssignmentContext(rightAssignmentCtxId))
345        {
346            throw new RightsException("The user '" + user + "' try to assign profile on unauthorized context '" + rightAssignmentCtxId + "'");
347        }
348        
349        Map<String, String> rights = getRights(Map.of());
350        // Remove delegation rights because it is a specific right, not sufficient to allow right assignment
351        rights.remove("CMS_Rights_Delegate_Rights");
352        
353        RightAssignmentContext rightAssignmentContext = _rightAssignmentContextEP.getExtension(rightAssignmentCtxId);
354        Object context = rightAssignmentContext.convertJSContext(jsContext);
355        
356        boolean hasHandleRight = hasRight(rights, context);
357        
358        Set<String> updatedProfiles = new HashSet<>();
359        
360        String contextIdentifier = rightAssignmentContext.getContextIdentifier(context);
361        for (Map<String, Object> assignmentInfo : assignmentsInfo)
362        {
363            String profileId = (String) assignmentInfo.get("profileId");
364            
365            if (_readerProfileOnly && !RightManager.READER_PROFILE_ID.equals(profileId))
366            {
367                throw new RightsException("The user '" + user + "' try to the assign profile '" + profileId + "' on the object context '" + jsContext + "' but only the reader profile is allowed for the right assignment context '" + rightAssignmentCtxId + "'");
368            }
369            
370            String assignment = (String) assignmentInfo.get("assignment");
371            String targetType = (String) assignmentInfo.get("targetType");
372
373            Profile profile = _profileDAO.getProfile(profileId);
374            assignmentInfo.put("profileLabel", profile.getLabel());
375            
376            if (hasHandleRight || canDelegateRights(user, groups, profileId, assignment, context))
377            {
378                updatedProfiles.add(profileId);
379                Map<String, String> identity = (Map<String, String>) assignmentInfo.get("identity");
380                _saveChange(context, profileId, assignment, targetType, identity);
381                successInfos.add(assignmentInfo);
382            }
383            else
384            {
385                errorInfos.add(assignmentInfo);
386            }
387        }
388        _notifyObservers(context, contextIdentifier, updatedProfiles);
389
390        result.put("successInfos", successInfos);
391        result.put("errorInfos", errorInfos);
392        result.put("success", errorInfos.isEmpty());
393        
394        return result;
395    }
396    
397    /**
398     * Determine following the right parameter if the user has right to access this feature
399     * 
400     * @param rights The rights (name, context) to check. Can be empty.
401     * @param context The object context
402     * @return true if the user has the right or if there is not right and false otherwise
403     */
404    protected boolean hasRight(Map<String, String> rights, Object context)
405    {
406        return hasRight(rights);
407    }
408    
409    /**
410     * Returns true if right assignment context is part of handled right assignment contexts
411     * @param rightAssignmentCtxId the id of right assignment context
412     * @return true if it right assignment context is allowed
413     */
414    protected boolean _checkRightAssignmentContext(String rightAssignmentCtxId)
415    {
416        Set<String> rightAssignmentCtxIds = getRightAssignmentContexts().stream().map(RightAssignmentContext::getId).collect(Collectors.toSet());
417        return rightAssignmentCtxIds.contains(rightAssignmentCtxId);
418    }
419    
420    /**
421     * Apply assignments and inheritance status on given contexts. The existing assignments will be reset for all targets
422     * @param rightAssignmentCtxId The id of the right assignment context
423     * @param targetContexts The JS object contexts with their inheritance enable status
424     * @param assignmentsInfo The list of all the new assignments. Each map in the list must contain the following keys:
425     * <ol>
426     * <li><b>profileId</b> for the id of the profile (as a string)</li>
427     * <li><b>assignment</b> for the kind of assignment (can be ACCESS_TYPE_ALLOW, ACCESS_TYPE_DENY...)</li>
428     * <li><b>assignmentType</b> expects one of these four strings: "user", "group", "anonymous", "anyConnectedUser"</li>
429     * <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>
430     * </ol>
431     * @param disallowInheritance true to disallow the inheritance, false to allow.
432     * @return true if assignments were correctly applied
433     */
434    @SuppressWarnings("unchecked")
435    @Callable
436    public boolean applyAssignments(String rightAssignmentCtxId, List<Map<String, Object>> targetContexts, List<Map<String, Object>> assignmentsInfo, boolean disallowInheritance)
437    {
438        UserIdentity user = _currentUserProvider.getUser();
439        if (!hasRight(getRights(Map.of())))
440        {
441            getLogger().error("The user '" + user + "' try to apply assignments without sufficient rights");
442            return false;
443        }
444        
445        if (!_checkRightAssignmentContext(rightAssignmentCtxId))
446        {
447            throw new RightsException("The user '" + user + "' tries to apply assignments on an unauthorized context '" + rightAssignmentCtxId + "'");
448        }
449        
450        Set<String> allProfileIds = _profileDAO.getProfiles().stream()
451                .map(Profile::getId)
452                .collect(Collectors.toSet());
453        
454        // Compute the targets targeted by the new assignments
455        Set<AssignmentTarget> assignmentTargets = _getAssignmentTargets(assignmentsInfo);
456        
457        RightAssignmentContext rightAssignmentContext = _rightAssignmentContextEP.getExtension(rightAssignmentCtxId);
458        
459        for (Map<String, Object> targetContext : targetContexts)
460        {
461            String jsContext = (String) targetContext.get("context");
462            
463            Object context = rightAssignmentContext.convertJSContext(jsContext);
464            
465            // First reset existings assignments for each targets
466            for (AssignmentTarget assignmentTarget : assignmentTargets)
467            {
468                _resetAssignments(allProfileIds, context, assignmentTarget.targetType(), assignmentTarget.identity());
469            }
470            
471            // Then apply new assignments
472            for (Map<String, Object> assignmentInfo : assignmentsInfo)
473            {
474                String profileId = (String) assignmentInfo.get("profileId");
475                if (_readerProfileOnly && !RightManager.READER_PROFILE_ID.equals(profileId))
476                {
477                    throw new RightsException("The user '" + user + "' try to apply the profile '" + profileId + "' on the object context '" + jsContext + "' but only the reader profile is allowed for the right assignment context '" + rightAssignmentCtxId + "'");
478                }
479                String targetType = (String) assignmentInfo.get("targetType");
480                Map<String, String> identity = (Map<String, String>) assignmentInfo.get("identity");
481                
482                // Apply new assignments
483                String assignment = (String) assignmentInfo.get("assignment");
484                _saveChange(context, profileId, assignment, targetType, identity);
485            }
486            
487            // Apply inheritance (if available)
488            boolean inheritanceAvailable = (boolean) targetContext.get("inheritanceAvailable");
489            if (inheritanceAvailable)
490            {
491                _profileAssignmentStorageEP.disallowInheritance(context, disallowInheritance);
492            }
493            
494            // Notify observers with all profiles
495            String contextIdentifier = rightAssignmentContext.getContextIdentifier(context);
496            _notifyObservers(context, contextIdentifier, allProfileIds);
497        }
498        
499        return true;
500    }
501    
502    @SuppressWarnings("unchecked")
503    private Set<AssignmentTarget> _getAssignmentTargets(List<Map<String, Object>> assignmentsInfo)
504    {
505        Set<AssignmentTarget> targets = new HashSet<>();
506        for (Map<String, Object> assignmentInfo : assignmentsInfo)
507        {
508            String targetType = (String) assignmentInfo.get("targetType");
509            switch (TargetType.valueOf(targetType.toUpperCase()))
510            {
511                case ANONYMOUS:
512                case ANYCONNECTED_USER:
513                    targets.add(new AssignmentTarget(targetType, null));
514                    break;
515                case USER:
516                case GROUP:
517                    Map<String, String> identity = (Map<String, String>) assignmentInfo.get("identity");
518                    targets.add(new AssignmentTarget(targetType, identity));
519                    break;
520                default:
521                    break;
522            }
523        }
524        
525        return targets;
526    }
527        
528    record AssignmentTarget(String targetType, Map<String, String> identity) { /* empty */ }
529    
530    /**
531     * Only allow user with right CMS_Rights_Delegate_Rights to add a profil that this user already have on this context
532     * @param user user that want to de an assignment
533     * @param groups groups of the user
534     * @param profileId profil impacted
535     * @param assignment assignment see {@link AccessType}
536     * @param context context of the assignment
537     * @return <code>true</code> if user can de this assignment
538     */
539    protected boolean canDelegateRights(UserIdentity user, Set<GroupIdentity> groups, String profileId, String assignment , Object context)
540    {
541        AccessType accessType = assignment != null ? AccessType.valueOf(assignment.toUpperCase()) : AccessType.UNKNOWN;
542        return accessType == AccessType.ALLOW
543                && _rightManager.hasRight(user, "CMS_Rights_Delegate_Rights", "/${WorkspaceName}") == RightResult.RIGHT_ALLOW
544                && _profileAssignmentStorageEP.getPermissions(user, groups, Set.of(profileId), context).get(profileId).toRightResult() == RightResult.RIGHT_ALLOW;
545    }
546    
547    /**
548     * Notify observers after modifying profile assignments.
549     * @param context The context
550     * @param contextIdentifier The context identifier
551     * @param profileIds The profiles
552     */
553    protected void _notifyObservers(Object context, String contextIdentifier, Set<String> profileIds)
554    {
555        _observationManager.notify(new Event(ObservationConstants.EVENT_ACL_UPDATED, _currentUserProvider.getUser(), _getEventParams(context, contextIdentifier, profileIds)));
556    }
557    
558    /**
559     * Get the event params for notification.
560     * @param context The context
561     * @param contextIdentifier The context identifier
562     * @param profileIds The profiles
563     * @return the event params
564     */
565    protected Map<String, Object> _getEventParams(Object context, String contextIdentifier, Set<String> profileIds)
566    {
567        Map<String, Object> eventParams = new HashMap<>();
568        eventParams.put(ObservationConstants.ARGS_ACL_CONTEXT, context);
569        eventParams.put(ObservationConstants.ARGS_ACL_CONTEXT_IDENTIFIER, contextIdentifier);
570        eventParams.put(ObservationConstants.ARGS_ACL_PROFILES, profileIds);
571        return eventParams;
572    }
573    
574    /**
575     * Determines if the inheritance of assignments is disallowed on the given context
576     * @param rightAssignmentCtxId The id of the right assignment context
577     * @param jsContext The JS object context
578     * @return <code>true</code> if the inheritance is disallowed.
579     */
580    @Callable
581    public boolean isInheritanceDisallowed(String rightAssignmentCtxId, Object jsContext)
582    {
583        RightAssignmentContext rightCtx = _rightAssignmentContextEP.getExtension(rightAssignmentCtxId);
584        Object context = rightCtx.convertJSContext(jsContext);
585        
586        return _profileAssignmentStorageEP.isInheritanceDisallowed(context);
587    }
588    
589    /**
590     * Allow or disallow the inheritance of assignments on the given context
591     * @param rightAssignmentCtxId The id of the right assignment context
592     * @param jsContext The JS object context
593     * @param disallow true to disallow the inheritance, false to allow.
594     */
595    @Callable
596    public void disallowInheritance(String rightAssignmentCtxId, Object jsContext, boolean disallow)
597    {
598        RightAssignmentContext rightCtx = _rightAssignmentContextEP.getExtension(rightAssignmentCtxId);
599        Object context = rightCtx.convertJSContext(jsContext);
600        String contextIdentifier = rightCtx.getContextIdentifier(context);
601        
602        _profileAssignmentStorageEP.disallowInheritance(context, disallow);
603        
604        // notify observers for all profiles
605        Set<String> profileIds = _profileDAO.getProfiles().stream()
606                .map(Profile::getId)
607                .collect(Collectors.toSet());
608        
609        _notifyObservers(context, contextIdentifier, profileIds);
610    }
611
612    /**
613     * Get the first permission given by inheritance for a object context and profiles
614     * @param rightAssignmentCtxId The id of the right assignment context
615     * @param jsContext The JS object context
616     * @param profileIds The list of profiles
617     * @param targetType The type of target : anonymous, any connected users, a user or a group
618     * @param identity The identity of the target. Can be null if the target is anonymous or any connected users
619     * @return The first access type given by inheritance for each profile
620     */
621    @Callable
622    public Map<String, String> getInheritedAssignments (String rightAssignmentCtxId, Object jsContext, List<String> profileIds, String targetType, Map<String, String> identity)
623    {
624        RightAssignmentContext rightCtx = _rightAssignmentContextEP.getExtension(rightAssignmentCtxId);
625        Object context = rightCtx.convertJSContext(jsContext);
626        
627        if (_profileAssignmentStorageEP.isInheritanceDisallowed(context))
628        {
629            return profileIds.stream()
630                .collect(Collectors.toMap(profileId -> profileId, profileId -> AccessType.UNKNOWN.toString()));
631        }
632        else
633        {
634            return profileIds.stream()
635                    .collect(Collectors.toMap(profileId -> profileId, profileId -> getInheritedAssignment(rightAssignmentCtxId, jsContext, profileId, targetType, identity)));
636        }
637    }
638    
639    /**
640     * Get the first permission given by inheritance for a object context and a specific profile
641     * @param rightAssignmentCtxId The id of the right assignment context
642     * @param jsContext The JS object context
643     * @param profileId The id of profile 
644     * @param targetType The type of target : anonymous, any connected users, a user or a group
645     * @param identity The identity of the target. Can be null if the target is anonymous or any connected users
646     * @return The first access type given by inheritance
647     */
648    @Callable
649    public String getInheritedAssignment (String rightAssignmentCtxId, Object jsContext, String profileId, String targetType, Map<String, String> identity)
650    {
651        RightAssignmentContext rightCtx = _rightAssignmentContextEP.getExtension(rightAssignmentCtxId);
652        Object context = rightCtx.convertJSContext(jsContext);
653        
654        if (_profileAssignmentStorageEP.isInheritanceDisallowed(context))
655        {
656            return AccessType.UNKNOWN.toString();
657        }
658        else
659        {
660            switch (TargetType.valueOf(targetType.toUpperCase()))
661            {
662                case ANONYMOUS:
663                    return _getInheritedAssignmentForAnonymous(rightCtx, context, profileId);
664                case ANYCONNECTED_USER:
665                    return _getInheritedAssignmentForAnyconnected(rightCtx, context, profileId);
666                case USER:
667                    UserIdentity user = _userHelper.json2userIdentity(identity);
668                    return _getInheritedAssignmentForUser(rightCtx, context, profileId, user);
669                case GROUP:
670                    GroupIdentity group = new GroupIdentity(identity.get("groupId"), identity.get("groupDirectory"));
671                    return _getInheritedAssignmentForGroup(rightCtx, context, profileId, group);
672                default:
673                    return AccessType.UNKNOWN.toString();
674            }
675        }
676    }
677    
678    private String _getInheritedAssignmentForAnonymous (RightAssignmentContext extension, Object context, String profileId)
679    {
680        String value = AccessType.UNKNOWN.toString();
681        
682        Set<Object> parentContexts = extension.getParentContexts(context);
683        if (parentContexts != null)
684        {
685            for (Object parentContext : parentContexts)
686            {
687                Map<AnonymousOrAnyConnectedKeys, Set<String>> profilesForAnonymousAndAnyConnectedUser = _profileAssignmentStorageEP.getProfilesForAnonymousAndAnyConnectedUser(parentContexts);
688                
689                Set<String> deniedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_DENIED)).orElse(Set.of());
690                if (deniedProfiles.contains(profileId))
691                {
692                    return AccessType.INHERITED_DENY.toString();
693                }
694                
695                Set<String> allowedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_ALLOWED)).orElse(Set.of());
696                if (allowedProfiles.contains(profileId))
697                {
698                    value = AccessType.INHERITED_ALLOW.toString();
699                }
700    
701                String parentsValue = _getInheritedAssignmentForAnonymous(extension, parentContext, profileId);
702                if (!AccessType.UNKNOWN.toString().equals(parentsValue))
703                {
704                    value = parentsValue;
705                }
706            }
707        }
708        
709        return value;
710    }
711    
712    private String _getInheritedAssignmentForAnyconnected(RightAssignmentContext extension, Object context, String profileId)
713    {
714        String value = AccessType.UNKNOWN.toString();
715        
716        Set<Object> parentContexts = extension.getParentContexts(context);
717        if (parentContexts != null)
718        {
719            for (Object parentContext : parentContexts)
720            {
721                Map<AnonymousOrAnyConnectedKeys, Set<String>> profilesForAnonymousAndAnyConnectedUser = _profileAssignmentStorageEP.getProfilesForAnonymousAndAnyConnectedUser(parentContexts);
722                
723                Set<String> deniedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_DENIED)).orElse(Set.of());
724                if (deniedProfiles.contains(profileId))
725                {
726                    return AccessType.INHERITED_DENY.toString();
727                }
728                
729                Set<String> allowedProfiles = Optional.ofNullable(profilesForAnonymousAndAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_ALLOWED)).orElse(Set.of());
730                if (allowedProfiles.contains(profileId))
731                {
732                    value = AccessType.INHERITED_ALLOW.toString();
733                }
734    
735                String parentsValue = _getInheritedAssignmentForAnyconnected(extension, parentContext, profileId);
736                if (!AccessType.UNKNOWN.toString().equals(parentsValue))
737                {
738                    value = parentsValue;
739                }
740            }
741        }
742        
743        return value;
744    }
745
746    private String _getInheritedAssignmentForUser(RightAssignmentContext extension, Object context, String profileId, UserIdentity user)
747    {
748        String value = AccessType.UNKNOWN.toString();
749
750        Set<Object> parentContexts = extension.getParentContexts(context);
751        if (parentContexts != null)
752        {
753            for (Object parentContext : parentContexts)
754            {
755                Map<UserIdentity, Map<UserOrGroup, Set<String>>> profilesForUsers = _profileAssignmentStorageEP.getProfilesForUsers(parentContext, user);
756                
757                Set<String> deniedProfiles = Optional.ofNullable(profilesForUsers.get(user)).map(a -> a.get(UserOrGroup.DENIED)).orElse(Set.of());
758                if (deniedProfiles.contains(profileId))
759                {
760                    return AccessType.INHERITED_DENY.toString();
761                }
762                
763                Set<String> allowedProfiles = Optional.ofNullable(profilesForUsers.get(user)).map(a -> a.get(UserOrGroup.ALLOWED)).orElse(Set.of());
764                if (allowedProfiles.contains(profileId))
765                {
766                    value = AccessType.INHERITED_ALLOW.toString();
767                }
768                
769                String parentsValue = _getInheritedAssignmentForUser(extension, parentContext, profileId, user);
770                if (!AccessType.UNKNOWN.toString().equals(parentsValue))
771                {
772                    value = parentsValue;
773                }
774            }
775        }
776        
777        return value;
778    }
779    
780    private String _getInheritedAssignmentForGroup(RightAssignmentContext extension, Object context, String profileId, GroupIdentity group)
781    {
782        String value = AccessType.UNKNOWN.toString();
783
784        Set<Object> parentContexts = extension.getParentContexts(context);
785        if (parentContexts != null)
786        {        
787            for (Object parentContext : parentContexts)
788            {
789                Map<GroupIdentity, Map<UserOrGroup, Set<String>>> profilesForGroups = _profileAssignmentStorageEP.getProfilesForGroups(parentContext, Set.of(group));
790                
791                Set<String> deniedProfiles = Optional.ofNullable(profilesForGroups.get(group)).map(a -> a.get(UserOrGroup.DENIED)).orElse(Set.of());
792                if (deniedProfiles.contains(profileId))
793                {
794                    return AccessType.INHERITED_DENY.toString();
795                }
796                
797                Set<String> allowedProfiles = Optional.ofNullable(profilesForGroups.get(group)).map(a -> a.get(UserOrGroup.ALLOWED)).orElse(Set.of());
798                if (allowedProfiles.contains(profileId))
799                {
800                    value = AccessType.INHERITED_ALLOW.toString();
801                }
802                
803                String parentsValue = _getInheritedAssignmentForGroup(extension, parentContext, profileId, group);
804                if (!AccessType.UNKNOWN.toString().equals(parentsValue))
805                {
806                    value = parentsValue;
807                }
808            }
809        }
810        
811        return value;
812    }
813    
814    private void _resetAssignments(Set<String> profileIds, Object context, String targetType, Map<String, String> identity)
815    {
816        TargetType type = TargetType.valueOf(targetType.toUpperCase());
817        for (String profileId : profileIds)
818        {
819            switch (type)
820            {
821                case ANONYMOUS:
822                    _profileAssignmentStorageEP.removeAllowedProfileFromAnonymous(profileId, context);
823                    _profileAssignmentStorageEP.removeDeniedProfileFromAnonymous(profileId, context);
824                    break;
825                case ANYCONNECTED_USER:
826                    _profileAssignmentStorageEP.removeAllowedProfileFromAnyConnectedUser(profileId, context);
827                    _profileAssignmentStorageEP.removeDeniedProfileFromAnyConnectedUser(profileId, context);
828                    break;
829                case USER:
830                    UserIdentity user = _userHelper.json2userIdentity(identity);
831                    _profileAssignmentStorageEP.removeAllowedProfileFromUser(user, profileId, context);
832                    _profileAssignmentStorageEP.removeDeniedProfileFromUser(user, profileId, context);
833                    break;
834                case GROUP:
835                    GroupIdentity group = new GroupIdentity(identity.get("groupId"), identity.get("groupDirectory"));
836                    _profileAssignmentStorageEP.removeAllowedProfileFromGroup(group, profileId, context);
837                    _profileAssignmentStorageEP.removeDeniedProfileFromGroup(group, profileId, context);
838                    break;
839                default:
840                    break;
841            }
842        }
843    }
844    
845    private void _saveChange(Object context, String profileId, String assignment, String targetType, Map<String, String> identity)
846    {
847        AccessType accessType = assignment != null ? AccessType.valueOf(assignment.toUpperCase()) : AccessType.UNKNOWN;
848        switch (TargetType.valueOf(targetType.toUpperCase()))
849        {
850            case ANONYMOUS:
851                switch (accessType)
852                {
853                    case ALLOW:
854                        _profileAssignmentStorageEP.removeDeniedProfileFromAnonymous(profileId, context);
855                        _profileAssignmentStorageEP.allowProfileToAnonymous(profileId, context);
856                        break;
857                    case DENY:
858                        _profileAssignmentStorageEP.removeAllowedProfileFromAnonymous(profileId, context);
859                        _profileAssignmentStorageEP.denyProfileToAnonymous(profileId, context);
860                        break;
861                    default:
862                        _profileAssignmentStorageEP.removeAllowedProfileFromAnonymous(profileId, context);
863                        _profileAssignmentStorageEP.removeDeniedProfileFromAnonymous(profileId, context);
864                        break;
865                }
866                break;
867                
868            case ANYCONNECTED_USER:
869                switch (accessType)
870                {
871                    case ALLOW:
872                        _profileAssignmentStorageEP.removeDeniedProfileFromAnyConnectedUser(profileId, context);
873                        _profileAssignmentStorageEP.allowProfileToAnyConnectedUser(profileId, context);
874                        break;
875                    case DENY:
876                        _profileAssignmentStorageEP.removeAllowedProfileFromAnyConnectedUser(profileId, context);
877                        _profileAssignmentStorageEP.denyProfileToAnyConnectedUser(profileId, context);
878                        break;
879                    default:
880                        _profileAssignmentStorageEP.removeAllowedProfileFromAnyConnectedUser(profileId, context);
881                        _profileAssignmentStorageEP.removeDeniedProfileFromAnyConnectedUser(profileId, context);
882                        break;
883                }
884                break;
885                
886            case USER:
887                UserIdentity user = _userHelper.json2userIdentity(identity);
888                switch (accessType)
889                {
890                    case ALLOW:
891                        _profileAssignmentStorageEP.removeDeniedProfileFromUser(user, profileId, context);
892                        _profileAssignmentStorageEP.allowProfileToUser(user, profileId, context);
893                        break;
894                    case DENY:    
895                        _profileAssignmentStorageEP.removeAllowedProfileFromUser(user, profileId, context);
896                        _profileAssignmentStorageEP.denyProfileToUser(user, profileId, context);
897                        break;
898                    default:
899                        _profileAssignmentStorageEP.removeAllowedProfileFromUser(user, profileId, context);
900                        _profileAssignmentStorageEP.removeDeniedProfileFromUser(user, profileId, context);
901                        break;
902                }
903                break;
904                
905            case GROUP:
906                GroupIdentity group = new GroupIdentity(identity.get("groupId"), identity.get("groupDirectory"));
907                switch (accessType)
908                {
909                    case ALLOW:
910                        _profileAssignmentStorageEP.removeDeniedProfileFromGroup(group, profileId, context);
911                        _profileAssignmentStorageEP.allowProfileToGroup(group, profileId, context);
912                        break;
913                    case DENY:  
914                        _profileAssignmentStorageEP.removeAllowedProfileFromGroup(group, profileId, context);
915                        _profileAssignmentStorageEP.denyProfileToGroup(group, profileId, context);
916                        break;
917                    default:
918                        _profileAssignmentStorageEP.removeAllowedProfileFromGroup(group, profileId, context);
919                        _profileAssignmentStorageEP.removeDeniedProfileFromGroup(group, profileId, context);
920                        break;
921                }
922                break;
923            default:
924                break;
925        }
926    }
927    
928    
929}