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.workspaces.project.rights;
017
018import java.util.Arrays;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Objects;
023import java.util.Set;
024import java.util.stream.Collectors;
025import java.util.stream.Stream;
026
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang3.StringUtils;
032import org.apache.http.annotation.Obsolete;
033
034import org.ametys.core.right.Profile;
035import org.ametys.core.right.ProfileAssignmentStorageExtensionPoint;
036import org.ametys.core.right.RightManager;
037import org.ametys.core.right.RightManager.RightResult;
038import org.ametys.core.right.RightProfilesDAO;
039import org.ametys.core.ui.Callable;
040import org.ametys.core.user.CurrentUserProvider;
041import org.ametys.plugins.explorer.ExplorerNode;
042import org.ametys.plugins.explorer.resources.Resource;
043import org.ametys.plugins.repository.AmetysObject;
044import org.ametys.plugins.repository.AmetysObjectResolver;
045import org.ametys.plugins.repository.UnknownAmetysObjectException;
046import org.ametys.plugins.workspaces.calendars.CalendarWorkspaceModule;
047import org.ametys.plugins.workspaces.documents.DocumentWorkspaceModule;
048import org.ametys.plugins.workspaces.members.MembersWorkspaceModule;
049import org.ametys.plugins.workspaces.members.JCRProjectMember;
050import org.ametys.plugins.workspaces.members.JCRProjectMember.MemberType;
051import org.ametys.plugins.workspaces.project.ProjectManager;
052import org.ametys.plugins.workspaces.project.modules.WorkspaceModule;
053import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint;
054import org.ametys.plugins.workspaces.project.objects.Project;
055import org.ametys.runtime.config.Config;
056import org.ametys.runtime.i18n.I18nizableText;
057import org.ametys.runtime.plugin.component.AbstractLogEnabled;
058import org.ametys.runtime.plugin.component.PluginAware;
059
060/**
061 * Helper related to rights management for projects.
062 */
063public class ProjectRightHelper extends AbstractLogEnabled implements Serviceable, Component, PluginAware
064{
065    /** Avalon Role */
066    public static final String ROLE = ProjectRightHelper.class.getName();
067    
068    @Obsolete // For v1 project only
069    private static final String __PROJECT_RIGHT_PROFILE = "PROJECT";
070    
071    /** Project Right to add a member to a project */
072    private static final String __RIGHT_ADD_MEMBER = "Plugins_Workspaces_Rights_Service_Module_Members_Add";
073    
074    /** Project Right to remove a member from the project */
075    private static final String __RIGHT_REMOVE_MEMBER = "Plugins_Workspaces_Rights_Service_Module_Members_Remove";
076    
077    /** Right to add a tag */
078    private static final String __RIGHT_ADD_TAG = "Plugins_Workspaces_Rights_Project_Add_Tag";
079    /** Right to delete a tag */
080    private static final String __RIGHT_DELETE_TAG = "Plugins_Workspaces_Rights_Project_Delete_Tag";
081    /** Right to add a place */
082    private static final String __RIGHT_ADD_PLACE = "Plugins_Workspaces_Rights_Project_Add_Place";
083    /** Right to delete a place */
084    private static final String __RIGHT_DELETE_PLACE = "Plugins_Workspaces_Rights_Project_Delete_Place";
085    
086    /** Ametys object resolver */
087    protected AmetysObjectResolver _resolver;
088    
089    /** Project manager */
090    protected ProjectManager _projectManager;
091    
092    /** Right manager */
093    protected RightManager _rightManager;
094    
095    /** Right profiles manager */
096    protected RightProfilesDAO _rightProfilesDao;
097    
098    /** Current user provider */
099    protected CurrentUserProvider _currentUserProvider;
100    
101    /** Module managers EP */
102    protected WorkspaceModuleExtensionPoint _moduleManagerEP;
103
104    /** The profile storage */
105    private ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageEP;
106
107    private String _pluginName;
108
109    
110    @Override
111    public void service(ServiceManager manager) throws ServiceException
112    {
113        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
114        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
115        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
116        _rightProfilesDao = (RightProfilesDAO) manager.lookup(RightProfilesDAO.ROLE);
117        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
118        _moduleManagerEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE);
119        _profileAssignmentStorageEP = (ProfileAssignmentStorageExtensionPoint) manager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE);
120    }
121    
122    public void setPluginInfo(String pluginName, String featureName, String id)
123    {
124        _pluginName = pluginName;
125    }
126    /**
127     * Retrieves all project profile given the "profile list" configuration parameter
128     * Profile order is guaranteed to be the same as in the configuration parameter.
129     * @return the projects
130     */
131    public List<Profile> getProfileList()
132    {
133        Map<String, Profile> profileMap = _rightProfilesDao.getProfiles().stream().collect(Collectors.toMap(Profile::getId, item -> item));
134        String rawProjectProfileIds = StringUtils.defaultString(Config.getInstance().getValue("workspaces.profile.list"));
135        
136        // Collect project profiles (unexisting entries are filtered out).
137        return Arrays.stream(StringUtils.split(rawProjectProfileIds, ','))
138            .map(id -> 
139            {
140                Profile p = profileMap.get(id);
141                
142                // log null entries
143                if (p == null)
144                {
145                    getLogger().warn("Could not find profile with id '{}'.", id);
146                }
147                return p;
148            })
149            .filter(Objects::nonNull)
150            .collect(Collectors.toList());
151    }
152    
153    /**
154     * Get the first allowed profile on project's module for a given member
155     * @param project the project
156     * @param module The workspace module
157     * @param member the member
158     * @return the first allowed profile or null if not found
159     */
160    public String getAllowedProfileOnModule (Project project, WorkspaceModule module, JCRProjectMember member)
161    {
162        List<String> profileIds = getProfileList()
163                .stream()
164                .map(p -> p.getId())
165                .collect(Collectors.toList());
166        
167        AmetysObject moduleObject = module.getModuleRoot(project, false);
168        Set<String> allowedProfilesForUser = MemberType.GROUP.toString().equals(member.getType())
169                ? _profileAssignmentStorageEP.getAllowedProfilesForGroup(moduleObject, member.getGroup())
170                : _profileAssignmentStorageEP.getAllowedProfilesForUser(moduleObject, member.getUser());
171        
172        for (String allowedProfile : allowedProfilesForUser)
173        {
174            if (profileIds.contains(allowedProfile))
175            {
176                // Get the first allowed profile among the project's members profiles
177                return allowedProfile;
178            }
179        }
180        
181        return null;
182    }
183    
184    /**
185     * Get the list of profiles and the list of modules available for rights affectation in the project.
186     * @param projectName The project to check if the modules are activated. Can be null to ignore
187     * @return the project rights data
188     */
189    @Callable
190    public Map<String, Object> getProjectRightsData(String projectName)
191    {
192        return getProjectRightsData(projectName, false);
193    }
194    
195    /**
196     * Get the list of profiles and the list of modules available for rights affectation in the project.
197     * @param projectName The project to check if the modules are activated. Can be null to ignore
198     * @param includeAllModules True to include modules not manager by the WorkspaceModuleManagerExtensionPoint
199     * @return the project rights data
200     */
201    @Callable
202    @Obsolete // For v1 projects only
203    public Map<String, Object> getProjectRightsData(String projectName, boolean includeAllModules)
204    {
205        // profiles
206        List<Object> profiles = getProfileList()
207            .stream()
208            .map(this::_getProfileRightData)
209            .collect(Collectors.toList());
210        
211        
212        Project project = projectName != null ? _projectManager.getProject(projectName) : null;
213        
214        // modules
215        Stream<Map<String, Object>> stream = _moduleManagerEP.getExtensionsIds().stream().map(moduleId -> _moduleManagerEP.getExtension(moduleId)).map(module -> _getModuleRightData(project, module));
216        List<Object> modules = stream.filter(Objects::nonNull).collect(Collectors.toList());
217        
218        Map<String, Object> result = new HashMap<>();
219        result.put("profiles", profiles);
220        result.put("modules", modules);
221        
222        return result;
223    }
224    
225    private Map<String, Object> _getProfileRightData(Profile profile)
226    {
227        Map<String, Object> data = new HashMap<>();
228        data.put("id", profile.getId());
229        data.put("label", profile.getLabel());
230        return data;
231    }
232    
233    private Map<String, Object> _getModuleRightData(Project project, WorkspaceModule module)
234    {
235        if (project != null && !_projectManager.isModuleActivated(project, module.getId()))
236        {
237            return null;
238        }
239        
240        Map<String, Object> data = new HashMap<>();
241        data.put("id", module.getId());
242        data.put("label", module.getModuleTitle());
243        return data;
244    }    
245    
246    // For v1 projects only
247    @Obsolete 
248    private Map<String, Object> _getProjectRightData()
249    {
250        Map<String, Object> data = new HashMap<>();
251        data.put("id", __PROJECT_RIGHT_PROFILE);
252        data.put("label", new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_SERVICE_PROFILE_TO_PROJECT_LABEL"));
253        return data;
254    }
255    
256    /**
257     * Determines if the current user can view the members of a project
258     * @param project the project
259     * @return true if user can view members
260     */
261    public boolean canViewMembers(Project project)
262    {
263        return _rightManager.currentUserHasReadAccess(project);
264    }
265    
266    /**
267     * Determines if the current user has right to add member on project
268     * @param project the project
269     * @return true if user can add member
270     */
271    public boolean canAddMember(Project project)
272    {
273        MembersWorkspaceModule module = _moduleManagerEP.getModule(MembersWorkspaceModule.MEMBERS_MODULE_ID);
274        if (module != null && _projectManager.isModuleActivated(project, module.getId()))
275        {
276            AmetysObject moduleRoot = module.getModuleRoot(project, false);
277            return moduleRoot != null && _rightManager.currentUserHasRight(__RIGHT_ADD_MEMBER, moduleRoot) == RightResult.RIGHT_ALLOW;
278        }
279        
280        return false;
281    }
282    
283    /**
284     * Determines if the current user has right to edit member on project
285     * @param project the project
286     * @return true if user can edit member
287     */
288    public boolean canEditMember(Project project)
289    {
290        return canAddMember(project);
291    }
292    
293    /**
294     * Determines if the current user has right to add member on project
295     * @param project the project
296     * @return true if user can remove member
297     */
298    public boolean canRemoveMember(Project project)
299    {
300        return _hasRightOnMembers(project, __RIGHT_REMOVE_MEMBER);
301    }
302    
303    private boolean _hasRightOnMembers(Project project, String rightId)
304    {
305        MembersWorkspaceModule module = _moduleManagerEP.getModule(MembersWorkspaceModule.MEMBERS_MODULE_ID);
306        if (module != null && _projectManager.isModuleActivated(project, module.getId()))
307        {
308            AmetysObject moduleRoot = module.getModuleRoot(project, false);
309            return moduleRoot != null && _rightManager.currentUserHasRight(rightId, moduleRoot) == RightResult.RIGHT_ALLOW;
310        }
311        
312        return false;
313    }
314    
315    /**
316     * Determines if the current user has right to add tags on project
317     * @param project the project
318     * @return true if user can add tags
319     */
320    public boolean canAddTag(Project project)
321    {
322        return _hasRightOnTagsOrPlaces(project, __RIGHT_ADD_TAG);
323    }
324    
325    /**
326     * Determines if the current user has right to remove tags on project
327     * @param project the project
328     * @return true if user can remove tags
329     */
330    public boolean canRemoveTag(Project project)
331    {
332        return _hasRightOnTagsOrPlaces(project, __RIGHT_DELETE_TAG);
333    }
334    
335    /**
336     * Determines if the current user has right to add places on project
337     * @param project the project
338     * @return true if user can add places
339     */
340    public boolean canAddPlace(Project project)
341    {
342        return _hasRightOnTagsOrPlaces(project, __RIGHT_ADD_PLACE);
343    }
344    
345    /**
346     * Determines if the current user has right to remove places on project
347     * @param project the project
348     * @return true if user can remove places
349     */
350    public boolean canRemovePlace(Project project)
351    {
352        return _hasRightOnTagsOrPlaces(project, __RIGHT_DELETE_PLACE);
353    }
354    
355    private boolean _hasRightOnTagsOrPlaces(Project project, String rightId)
356    {
357        WorkspaceModule module = _moduleManagerEP.getModule(CalendarWorkspaceModule.CALENDAR_MODULE_ID);
358        if (module != null && _projectManager.isModuleActivated(project, module.getId()))
359        {
360            AmetysObject moduleRoot = module.getModuleRoot(project, false);
361            if (moduleRoot != null && _rightManager.currentUserHasRight(rightId, moduleRoot) == RightResult.RIGHT_ALLOW)
362            {
363                return true;
364            }
365        }
366        
367        module = _moduleManagerEP.getModule(DocumentWorkspaceModule.DODUMENT_MODULE_ID);
368        if (module != null && _projectManager.isModuleActivated(project, module.getId()))
369        {
370            AmetysObject moduleRoot = module.getModuleRoot(project, false);
371            if (moduleRoot != null && _rightManager.currentUserHasRight(rightId, moduleRoot) == RightResult.RIGHT_ALLOW)
372            {
373                return true;
374            }
375        }
376        
377        return false;
378    }
379    
380    /**
381     * Test if the current user has the right on the project
382     * @param rightId The right id
383     * @param projectId The project id
384     * @return true if has right
385     */
386    @Callable
387    public boolean hasRightOnProject(String rightId, String projectId)
388    {
389        try
390        {
391            return hasRight(rightId, _resolver.<Project>resolveById(projectId));
392        }
393        catch (UnknownAmetysObjectException e)
394        {
395            getLogger().warn("Trying to test right on an unknown project. Requested id : {}", projectId, e);
396            return false;
397        }
398    }
399    
400    /**
401     * Test if the current user has the right on the project
402     * @param rightId The right id
403     * @param project The project
404     * @return true if has right
405     */
406    public boolean hasRight(String rightId, Project project)
407    {
408        return _rightManager.hasRight(_currentUserProvider.getUser(), rightId, project) == RightResult.RIGHT_ALLOW;
409    }
410    
411    /**
412     * Test if the current user has a read access on the project
413     * @param projectId The project id
414     * @return true if has read access
415     */
416    @Callable
417    public boolean hasReadAccessOnProject(String projectId)
418    {
419        try
420        {
421            return hasReadAccess(_resolver.<Project>resolveById(projectId));
422        }
423        catch (UnknownAmetysObjectException e)
424        {
425            getLogger().warn("Trying to test read access on an unknown project. Requested id : {}", projectId, e);
426            return false;
427        }
428    }
429    
430    /**
431     * Test if the current user has a read access on the project
432     * @param project The project
433     * @return true if has read access
434     */
435    public boolean hasReadAccess(Project project)
436    {
437        return _rightManager.hasReadAccess(_currentUserProvider.getUser(), project);
438    }
439    
440    /**
441     * Test if the current user has the right on an explorer node
442     * @param rightId The right id
443     * @param explorerNodeId The explorer node id
444     * @return true if has right
445     */
446    @Callable
447    public boolean hasRightOnExplorerNode(String rightId, String explorerNodeId)
448    {
449        try
450        {
451            return hasRight(rightId, _resolver.<ExplorerNode>resolveById(explorerNodeId));
452        }
453        catch (UnknownAmetysObjectException e)
454        {
455            getLogger().warn("Trying to test right on an unknown explorer node. Requested id : {}", explorerNodeId, e);
456            return false;
457        }
458    }
459    
460    /**
461     * Test if the current user has the right on an explorer node
462     * @param rightId The right id
463     * @param explorerNode The explorer node
464     * @return true if has right
465     */
466    public boolean hasRight(String rightId, ExplorerNode explorerNode)
467    {
468        return _rightManager.hasRight(_currentUserProvider.getUser(), rightId, explorerNode) == RightResult.RIGHT_ALLOW;
469    }
470    
471    /**
472     * Test if the current user has a read access on an explorer node
473     * @param explorerNodeId The explorer node id
474     * @return true if has read access
475     */
476    @Callable
477    public boolean hasReadAccessOnExplorerNode(String explorerNodeId)
478    {
479        try
480        {
481            return hasReadAccess(_resolver.<ExplorerNode>resolveById(explorerNodeId));
482        }
483        catch (UnknownAmetysObjectException e)
484        {
485            getLogger().warn("Trying to test read access on an unknown explorer node. Requested id : {}", explorerNodeId, e);
486            return false;
487        }
488    }
489    
490    /**
491     * Test if the current user has a read access on an explorer node
492     * @param explorerNode The explorer node
493     * @return true if has read access
494     */
495    public boolean hasReadAccess(ExplorerNode explorerNode)
496    {
497        return _rightManager.currentUserHasReadAccess(explorerNode);
498    }
499    
500    /**
501     * Test if the current user has a read access on a resource
502     * @param resource The resource
503     * @return true if has read access
504     */
505    public boolean hasReadAccess(Resource resource)
506    {
507        return _rightManager.currentUserHasReadAccess(resource);
508    }
509}