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