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.RightManager;
036import org.ametys.core.right.RightManager.RightResult;
037import org.ametys.core.right.RightProfilesDAO;
038import org.ametys.core.ui.Callable;
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.core.user.UserIdentity;
041import org.ametys.plugins.explorer.ExplorerNode;
042import org.ametys.plugins.explorer.resources.ModifiableResourceCollection;
043import org.ametys.plugins.repository.AmetysObject;
044import org.ametys.plugins.repository.AmetysObjectResolver;
045import org.ametys.plugins.workspaces.WorkspacesConstants;
046import org.ametys.plugins.workspaces.WorkspacesHelper;
047import org.ametys.plugins.workspaces.about.AboutWorkspaceModule;
048import org.ametys.plugins.workspaces.alert.AlertWorkspaceModule;
049import org.ametys.plugins.workspaces.calendars.CalendarWorkspaceModule;
050import org.ametys.plugins.workspaces.documents.DocumentWorkspaceModule;
051import org.ametys.plugins.workspaces.members.MembersWorkspaceModule;
052import org.ametys.plugins.workspaces.minisite.MiniSiteWorkspaceModule;
053import org.ametys.plugins.workspaces.news.NewsWorkspaceModule;
054import org.ametys.plugins.workspaces.project.ProjectConstants;
055import org.ametys.plugins.workspaces.project.ProjectManager;
056import org.ametys.plugins.workspaces.project.ProjectManager.UnknownCatalogSiteException;
057import org.ametys.plugins.workspaces.project.modules.WorkspaceModule;
058import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint;
059import org.ametys.plugins.workspaces.project.objects.Project;
060import org.ametys.plugins.workspaces.wall.WallContentModule;
061import org.ametys.runtime.authentication.AccessDeniedException;
062import org.ametys.runtime.config.Config;
063import org.ametys.runtime.plugin.component.AbstractLogEnabled;
064import org.ametys.web.repository.page.SitemapElement;
065import org.ametys.web.repository.page.ZoneItem;
066import org.ametys.web.repository.page.ZoneItem.ZoneType;
067
068/**
069 * Helper related to rights management for projects.
070 */
071public class ProjectRightHelper extends AbstractLogEnabled implements Serviceable, Component
072{
073    /** Avalon Role */
074    public static final String ROLE = ProjectRightHelper.class.getName();
075    
076    @Obsolete // For v1 project only
077    private static final String __PROJECT_RIGHT_PROFILE = "PROJECT";
078    
079    /** Ametys object resolver */
080    protected AmetysObjectResolver _resolver;
081    
082    /** Project manager */
083    protected ProjectManager _projectManager;
084    
085    /** Right manager */
086    protected RightManager _rightManager;
087    
088    /** Right profiles manager */
089    protected RightProfilesDAO _rightProfilesDao;
090    
091    /** Current user provider */
092    protected CurrentUserProvider _currentUserProvider;
093    
094    /** Workspace Module ExtensionPoint */
095    protected WorkspaceModuleExtensionPoint _workspaceModuleEP;
096    
097    /** Association ContentTypeId, Module */
098    protected Map<String, WorkspaceModule> _contentTypesToModule;
099
100    /** Module managers EP */
101    protected WorkspaceModuleExtensionPoint _moduleManagerEP;
102
103    /** Workspace helper */
104    protected WorkspacesHelper _workspaceHelper;
105    
106    private Set<String> _profileIds;
107    
108    @Override
109    public void service(ServiceManager manager) throws ServiceException
110    {
111        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
112        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
113        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
114        _rightProfilesDao = (RightProfilesDAO) manager.lookup(RightProfilesDAO.ROLE);
115        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
116        _moduleManagerEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE);
117        _workspaceHelper = (WorkspacesHelper) manager.lookup(WorkspacesHelper.ROLE);
118        
119        _workspaceModuleEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE);
120        _contentTypesToModule = Map.of(
121                WorkspacesConstants.WALL_CONTENT_CONTENT_TYPE_ID, _workspaceModuleEP.getModule(WallContentModule.WALLCONTENT_MODULE_ID),
122                WorkspacesConstants.PROJECT_NEWS_CONTENT_TYPE_ID, _workspaceModuleEP.getModule(NewsWorkspaceModule.NEWS_MODULE_ID),
123                WorkspacesConstants.PROJECT_ALERT_CONTENT_TYPE_ID, _workspaceModuleEP.getModule(AlertWorkspaceModule.ALERT_MODULE_ID),
124                WorkspacesConstants.PROJECT_ARTICLE_CONTENT_TYPE, _workspaceModuleEP.getModule(MiniSiteWorkspaceModule.MINISITE_MODULE_ID),
125                WorkspacesConstants.ABOUT_CONTENT_TYPE, _workspaceModuleEP.getModule(AboutWorkspaceModule.ABOUT_MODULE_ID)
126        );
127    }
128    
129    /**
130     * The association of all project content types and associated modules
131     * @return The association
132     */
133    public Map<String, WorkspaceModule> getProjectContentTypesAndModules()
134    {
135        return _contentTypesToModule;
136    }
137    
138    /**
139     * Retrieves all project profiles ids given the "profile list" configuration parameter
140     * Profile order is guaranteed to be the same as in the configuration parameter.
141     * @return the projects
142     */
143    public synchronized Set<String> getProfilesIds()
144    {
145        if (_profileIds == null)
146        {
147            String rawProjectProfileIds = StringUtils.defaultString(Config.getInstance().getValue("workspaces.profile.list"));
148            _profileIds = Arrays.stream(StringUtils.split(rawProjectProfileIds, ',')).collect(Collectors.toSet());
149        }
150        return _profileIds;
151    }
152    
153    /**
154     * Retrieves all project profile given the "profile list" configuration parameter
155     * Profile order is guaranteed to be the same as in the configuration parameter.
156     * @return the projects
157     */
158    public Set<Profile> getProfiles()
159    {
160        // getProfiles(null) to get only shared profile
161        Map<String, Profile> profileMap = _rightProfilesDao.getProfiles(null).stream().collect(Collectors.toMap(Profile::getId, item -> item));
162        
163        // Collect project profiles (unexisting entries are filtered out).
164        return getProfilesIds().stream()
165            .map(id ->
166            {
167                Profile p = profileMap.get(id);
168                
169                // log null entries
170                if (p == null)
171                {
172                    getLogger().warn("Could not find profile with id '{}'.", id);
173                }
174                
175                return p;
176            })
177            .filter(Objects::nonNull)
178            .collect(Collectors.toSet());
179    }
180    
181    /**
182     * Get the list of profiles and the list of modules available for rights affectation in the project.
183     * @param projectName The project to check if the modules are activated. Can be null to ignore
184     * @return the project rights data
185     */
186    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
187    public Map<String, Object> getProjectRightsData(String projectName)
188    {
189
190        Project project = projectName != null ? _projectManager.getProject(projectName) : null;
191        
192        if (!canEditMember(project))
193        {
194            throw new AccessDeniedException("User '" + _currentUserProvider.getUser() + "' tried to do read operation without convenient right");
195        }
196        
197        // profiles
198        List<Object> profiles = getProfiles()
199                .stream()
200                .map(this::_getProfileRightData)
201                .collect(Collectors.toList());
202
203
204        // modules
205        Stream<Map<String, Object>> stream = _moduleManagerEP.getExtensionsIds().stream().map(moduleId -> _moduleManagerEP.getExtension(moduleId)).map(module -> _getModuleRightData(project, module));
206        List<Object> modules = stream.filter(Objects::nonNull).collect(Collectors.toList());
207        
208        Map<String, Object> result = new HashMap<>();
209        result.put("profiles", profiles);
210        result.put("modules", modules);
211        
212        return result;
213    }
214    
215    private Map<String, Object> _getProfileRightData(Profile profile)
216    {
217        Map<String, Object> data = new HashMap<>();
218        data.put("id", profile.getId());
219        data.put("label", profile.getLabel());
220        return data;
221    }
222    
223    private Map<String, Object> _getModuleRightData(Project project, WorkspaceModule module)
224    {
225        if (project != null && !_projectManager.isModuleActivated(project, module.getId()))
226        {
227            return null;
228        }
229        
230        Map<String, Object> data = new HashMap<>();
231        data.put("id", module.getId());
232        data.put("label", module.getModuleTitle());
233        return data;
234    }
235    
236    /**
237     * Determines if the current user can view the members of a project
238     * @param project the project
239     * @return true if user can view members
240     */
241    public boolean canViewMembers(Project project)
242    {
243        return _rightManager.currentUserHasReadAccess(project);
244    }
245
246    /**
247     * Determines if the current user has right to add member on project
248     * @param project the project
249     * @return true if user can add member
250     */
251    public boolean canAddMember(Project project)
252    {
253        MembersWorkspaceModule module = _moduleManagerEP.getModule(MembersWorkspaceModule.MEMBERS_MODULE_ID);
254        if (project != null && module != null && _projectManager.isModuleActivated(project, module.getId()))
255        {
256            AmetysObject moduleRoot = module.getModuleRoot(project, false);
257            return moduleRoot != null && _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_ADD_MEMBER, moduleRoot) == RightResult.RIGHT_ALLOW;
258        }
259        
260        return false;
261    }
262
263    /**
264     * Determines if the current user has right to edit member on project
265     * @param project the project
266     * @return true if user can edit member
267     */
268    public boolean canEditMember(Project project)
269    {
270        return canAddMember(project);
271    }
272    
273    /**
274     * Determines if the current user has right to add member on project
275     * @param project the project
276     * @return true if user can remove member
277     */
278    public boolean canRemoveMember(Project project)
279    {
280        return _hasRightOnMembers(project, ProjectConstants.RIGHT_PROJECT_REMOVE_MEMBER);
281    }
282    
283    private boolean _hasRightOnMembers(Project project, String rightId)
284    {
285        MembersWorkspaceModule module = _moduleManagerEP.getModule(MembersWorkspaceModule.MEMBERS_MODULE_ID);
286        if (module != null && _projectManager.isModuleActivated(project, module.getId()))
287        {
288            AmetysObject moduleRoot = module.getModuleRoot(project, false);
289            return moduleRoot != null && _rightManager.currentUserHasRight(rightId, moduleRoot) == RightResult.RIGHT_ALLOW;
290        }
291        
292        return false;
293    }
294    
295    /**
296     * Determines if the current user has right to add tags on project
297     * @param project the project
298     * @return true if user can add tags
299     */
300    public boolean canAddTag(Project project)
301    {
302        return _hasRightOnTagsOrPlaces(project, ProjectConstants.RIGHT_PROJECT_ADD_TAG);
303    }
304    
305    /**
306     * Determines if the current user has right to remove tags on project
307     * @param project the project
308     * @return true if user can remove tags
309     */
310    public boolean canRemoveTag(Project project)
311    {
312        return _hasRightOnTagsOrPlaces(project, ProjectConstants.RIGHT_PROJECT_DELETE_TAG);
313    }
314    
315    private boolean _hasRightOnTagsOrPlaces(Project project, String rightId)
316    {
317        WorkspaceModule module = _moduleManagerEP.getModule(CalendarWorkspaceModule.CALENDAR_MODULE_ID);
318        if (module != null && _projectManager.isModuleActivated(project, module.getId()))
319        {
320            AmetysObject moduleRoot = module.getModuleRoot(project, false);
321            if (moduleRoot != null && _rightManager.currentUserHasRight(rightId, moduleRoot) == RightResult.RIGHT_ALLOW)
322            {
323                return true;
324            }
325        }
326        
327        module = _moduleManagerEP.getModule(DocumentWorkspaceModule.DOCUMENT_MODULE_ID);
328        if (module != null && _projectManager.isModuleActivated(project, module.getId()))
329        {
330            AmetysObject moduleRoot = module.getModuleRoot(project, false);
331            if (moduleRoot != null && _rightManager.currentUserHasRight(rightId, moduleRoot) == RightResult.RIGHT_ALLOW)
332            {
333                return true;
334            }
335        }
336        
337        return false;
338    }
339    
340    /**
341     * Determines if current user has read access to module on current project
342     * @param moduleId the module id
343     * @return true if current user has read access, false otherwise
344     */
345    public boolean hasReadAccessOnModule(String moduleId)
346    {
347        return hasReadAccessOnModule(_workspaceHelper.getProjectFromRequest(), moduleId);
348    }
349    
350    /**
351     * Determines if current user has read access to module on given project
352     * @param project the project
353     * @param moduleId the module id
354     * @return true if current user has right, false otherwise
355     */
356    public boolean hasReadAccessOnModule(Project project, String moduleId)
357    {
358        return hasReadAccessOnModule(project, moduleId, _currentUserProvider.getUser());
359    }
360    
361    /**
362     * Determines if current user has read access to module on given project
363     * @param project the project
364     * @param moduleId the module id
365     * @param userIdentity the user identity
366     * @return true if current user has right, false otherwise
367     */
368    public boolean hasReadAccessOnModule(Project project, String moduleId, UserIdentity userIdentity)
369    {
370        WorkspaceModule module = _workspaceModuleEP.getModule(moduleId);
371        if (module != null && project != null)
372        {
373            ModifiableResourceCollection moduleRoot = module.getModuleRoot(project, false);
374            if (moduleRoot != null)
375            {
376                return  _rightManager.hasReadAccess(userIdentity, moduleRoot);
377            }
378        }
379        return false;
380    }
381    
382    /**
383     * Determines if current user has a given right on module on current project
384     * @param rightId the right to check
385     * @param moduleId the module id
386     * @return true if current user has read access, false otherwise
387     */
388    public boolean hasRightOnModule(String rightId, String moduleId)
389    {
390        return hasRightOnModule(_workspaceHelper.getProjectFromRequest(), rightId, moduleId);
391    }
392    
393    /**
394     * Determines if current user has a given right on module on given project
395     * @param project the project
396     * @param rightId the right to check
397     * @param moduleId the module id
398     * @return true if current user has right, false otherwise
399     */
400    public boolean hasRightOnModule(Project project, String rightId, String moduleId)
401    {
402        WorkspaceModule module = _workspaceModuleEP.getModule(moduleId);
403        if (module != null && project != null)
404        {
405            ModifiableResourceCollection moduleRoot = module.getModuleRoot(project, false);
406            if (moduleRoot != null)
407            {
408                return  _rightManager.currentUserHasRight(rightId, moduleRoot) == RightResult.RIGHT_ALLOW;
409            }
410        }
411    
412        return false;
413    }
414    
415    /**
416     * Test if the current user has the right on the project
417     * @param rightId The right id
418     * @param project The project
419     * @return true if has right
420     */
421    public boolean hasRight(String rightId, Project project)
422    {
423        return _rightManager.hasRight(_currentUserProvider.getUser(), rightId, project) == RightResult.RIGHT_ALLOW;
424    }
425    
426    /**
427     * Test if the current user has a read access on current project
428     * @return true if has read access
429     */
430    public boolean hasReadAccess()
431    {
432        return hasReadAccess(_workspaceHelper.getProjectFromRequest());
433    }
434    
435    /**
436     * Test if the current user has a read access on the project
437     * @param project The project
438     * @return true if has read access
439     */
440    public boolean hasReadAccess(Project project)
441    {
442        return project != null && _rightManager.hasReadAccess(_currentUserProvider.getUser(), project);
443    }
444    
445    /**
446     * Test if the current user has the right on an explorer node
447     * @param rightId The right id
448     * @param explorerNode The explorer node
449     * @return true if has right
450     */
451    public boolean hasRight(String rightId, ExplorerNode explorerNode)
452    {
453        return _rightManager.hasRight(_currentUserProvider.getUser(), rightId, explorerNode) == RightResult.RIGHT_ALLOW;
454    }
455    
456    /**
457     * Test if current user has read access on catalog site
458     * @param zoneItem the zoneItem holding the catalog service. Cannot be null
459     * @return true if current user has read access
460     */
461    public boolean hasCatalogReadAccess(ZoneItem zoneItem)
462    {
463        SitemapElement sitemapElement = zoneItem.getZone().getSitemapElement();
464        return _isCatalogService(zoneItem) && _rightManager.currentUserHasReadAccess(sitemapElement);
465    }
466    
467    private boolean _isCatalogService(ZoneItem zoneItem)
468    {
469        try
470        {
471            SitemapElement page = zoneItem.getZone().getSitemapElement();
472            if (page.getSiteName().equals(_projectManager.getCatalogSiteName()))
473            {
474                return zoneItem.getType() == ZoneType.SERVICE && "org.ametys.plugins.workspaces.service.ProjectsCatalogue".equals(zoneItem.getServiceId());
475            }
476        }
477        catch (UnknownCatalogSiteException e)
478        {
479            // Ignore the no catalog sitename exception
480        }
481        
482        return false;
483    }
484}