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