001/* 002 * Copyright 2023 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.odf.rights; 017 018import java.util.HashSet; 019import java.util.List; 020import java.util.Map; 021import java.util.Objects; 022import java.util.Set; 023import java.util.stream.Collectors; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.avalon.framework.service.Serviceable; 028import org.apache.commons.lang.StringUtils; 029 030import org.ametys.cms.repository.Content; 031import org.ametys.core.group.GroupIdentity; 032import org.ametys.core.right.AccessController; 033import org.ametys.core.right.AccessExplanation; 034import org.ametys.core.right.RightProfilesDAO; 035import org.ametys.core.right.RightsException; 036import org.ametys.core.user.UserIdentity; 037import org.ametys.odf.ProgramItem; 038import org.ametys.odf.data.EducationalPath; 039import org.ametys.odf.orgunit.OrgUnit; 040import org.ametys.odf.rights.ODFRightHelper.ContextualizedContent; 041import org.ametys.odf.rights.ODFRightHelper.ContextualizedPermissionContext; 042import org.ametys.odf.tree.ODFContentsTreeHelper; 043import org.ametys.plugins.repository.AmetysObjectResolver; 044import org.ametys.runtime.i18n.I18nizableText; 045import org.ametys.runtime.plugin.component.PluginAware; 046 047/** 048 * {@link AccessController} for a ODF {@link ContextualizedContent} based on ODF role 049 * 050 */ 051public abstract class AbstractODFRoleForContextualizedContentAccessController implements AccessController, Serviceable, PluginAware 052{ 053 /** The rights profile DAO */ 054 protected RightProfilesDAO _rightProfileDAO; 055 /** The ODF contents tree helper */ 056 protected ODFContentsTreeHelper _odfContentsTreeHelper; 057 /** The ametys resolver */ 058 protected AmetysObjectResolver _resolver; 059 /** The ODF right helper */ 060 protected ODFRightHelper _odfRightHelper; 061 062 private String _id; 063 064 public void service(ServiceManager smanager) throws ServiceException 065 { 066 _rightProfileDAO = (RightProfilesDAO) smanager.lookup(RightProfilesDAO.ROLE); 067 _odfContentsTreeHelper = (ODFContentsTreeHelper) smanager.lookup(ODFContentsTreeHelper.ROLE); 068 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 069 _odfRightHelper = (ODFRightHelper) smanager.lookup(org.ametys.odf.rights.ODFRightHelper.ROLE); 070 } 071 072 public void setPluginInfo(String pluginName, String featureName, String id) 073 { 074 _id = id; 075 } 076 077 public String getId() 078 { 079 return _id; 080 } 081 082 public boolean supports(Object object) 083 { 084 return object instanceof ContextualizedContent; 085 } 086 087 /** 088 * Get the parents of the content for rights purpose 089 * @param content the content 090 * @param permissionCtx the permission context 091 * @return the parents of content 092 */ 093 protected Set<Content> getParents(Content content, ContextualizedPermissionContext permissionCtx) 094 { 095 if (content instanceof ProgramItem programItem) 096 { 097 EducationalPath educationalPath = permissionCtx.getEducationalPath(); 098 if (educationalPath == null) 099 { 100 return Set.of(); 101 } 102 103 List<ProgramItem> programItemsInPath = educationalPath.getProgramItems(_resolver); 104 105 ProgramItem parent = programItemsInPath.getLast(); 106 107 // Update educational path in permission contexte 108 permissionCtx.withEducationalPath(_removeLast(educationalPath)); 109 110 111 Set<Content> parents = new HashSet<>(); 112 parents.add((Content) parent); 113 114 // Add orgunits 115 List<String> ouIds = programItem.getOrgUnits(); 116 parents.addAll(ouIds.stream() 117 .filter(Objects::nonNull) 118 .filter(_resolver::hasAmetysObjectForId) 119 .map(_resolver::resolveById) 120 .map(OrgUnit.class::cast) 121 .collect(Collectors.toSet())); 122 123 return parents; 124 } 125 else if (content instanceof OrgUnit ou) 126 { 127 OrgUnit parentOrgUnit = ou.getParentOrgUnit(); 128 if (parentOrgUnit != null) 129 { 130 return Set.of(parentOrgUnit); 131 } 132 } 133 134 return Set.of(); 135 } 136 137 /** 138 * Get the permission context 139 * @param contextualizedContent the initial contextualized content 140 * @return the permission context. 141 */ 142 protected ContextualizedPermissionContext getPermissionContext(ContextualizedContent contextualizedContent) 143 { 144 EducationalPath educationalPath = contextualizedContent.path(); 145 Content initialContent = contextualizedContent.content(); 146 147 // Remove initial content from education path 148 List<ProgramItem> programItemsInPath = educationalPath.getProgramItems(_resolver); 149 if (programItemsInPath.getLast().getId().equals(initialContent.getId())) 150 { 151 educationalPath = _removeLast(educationalPath); 152 } 153 return new ContextualizedPermissionContext(initialContent, educationalPath); 154 } 155 156 private EducationalPath _removeLast(EducationalPath educationalPath) 157 { 158 List<ProgramItem> programItemsInPath = educationalPath.getProgramItems(_resolver); 159 List<ProgramItem> subList = programItemsInPath.subList(0, programItemsInPath.size() - 1); 160 if (!subList.isEmpty()) 161 { 162 return EducationalPath.of(subList.toArray(ProgramItem[]::new)); 163 } 164 return null; 165 } 166 167 public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object) 168 { 169 if (object instanceof ContextualizedContent contextualizedContent) 170 { 171 return _getPermission(user, userGroups, rightId, contextualizedContent.content(), getPermissionContext(contextualizedContent)); 172 } 173 174 return AccessResult.UNKNOWN; 175 } 176 177 private AccessResult _getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Content object, ContextualizedPermissionContext permissionCtx) 178 { 179 List<String> rights = getRightsInTargetProfile(); 180 if (rights.contains(rightId)) 181 { 182 Set<UserIdentity> allowedUsers = getLocalAllowedUsers(object); 183 if (allowedUsers.contains(user)) 184 { 185 return AccessResult.USER_ALLOWED; 186 } 187 } 188 189 AccessResult permission = AccessResult.UNKNOWN; 190 191 Set<Content> parents = getParents(object, permissionCtx); 192 if (parents != null) 193 { 194 for (Content parent : parents) 195 { 196 AccessResult parentResult = _getPermission(user, userGroups, rightId, parent, permissionCtx); 197 permission = AccessResult.merge(permission, parentResult); 198 } 199 } 200 201 return permission; 202 } 203 204 /** 205 * Get the rights hold by target profile 206 * @return the rights hold by target profile 207 */ 208 protected synchronized List<String> getRightsInTargetProfile() 209 { 210 String profileId = getTargetProfileId(); 211 return StringUtils.isNotBlank(profileId) ? _rightProfileDAO.getRights(profileId) : List.of(); 212 } 213 214 /** 215 * Get the id of target profile 216 * @return the id of target profile 217 */ 218 protected abstract String getTargetProfileId(); 219 220 /** 221 * Get the allowed users for this content taking into account the content itself and its parents 222 * @param content the ODF content (program item or orgunit) 223 * @param permissionCtx the permission context 224 * @return the allowed users. Empty if no user is allowed on this content 225 */ 226 protected Set<UserIdentity> getAllowedUsers(Content content, ContextualizedPermissionContext permissionCtx) 227 { 228 Set<UserIdentity> allowedUsers = getLocalAllowedUsers(content); 229 230 Set<Content> parents = getParents(content, permissionCtx); 231 if (parents != null) 232 { 233 for (Content parent : parents) 234 { 235 allowedUsers.addAll(getAllowedUsers(parent, permissionCtx)); 236 } 237 } 238 239 return allowedUsers; 240 } 241 242 /** 243 * Get the local allowed users for this content 244 * @param content the ODF content (program item or orgunit) 245 * @return the allowed users. Empty if no user is allowed on this content 246 */ 247 protected abstract Set<UserIdentity> getLocalAllowedUsers(Content content); 248 249 public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object) 250 { 251 return AccessResult.UNKNOWN; 252 } 253 254 public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object) 255 { 256 if (object instanceof ContextualizedContent contextualizedContent) 257 { 258 Set<UserIdentity> allowedUsers = getAllowedUsers((Content) object, getPermissionContext(contextualizedContent)); 259 if (allowedUsers.contains(user)) 260 { 261 return getRightsInTargetProfile().stream() 262 .collect(Collectors.toMap(r -> r, r -> AccessResult.USER_ALLOWED)); 263 } 264 } 265 266 return Map.of(); 267 } 268 269 /** 270 * Get the attribute path for role 271 * @return the attribute path for role 272 */ 273 protected abstract String getRoleAttributePath(); 274 275 public AccessResult getPermissionForAnonymous(String rightId, Object object) 276 { 277 return AccessResult.UNKNOWN; 278 } 279 280 public AccessResult getReadAccessPermissionForAnonymous(Object object) 281 { 282 return AccessResult.UNKNOWN; 283 } 284 285 public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object) 286 { 287 return AccessResult.UNKNOWN; 288 } 289 290 public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object) 291 { 292 return AccessResult.UNKNOWN; 293 } 294 295 public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object) 296 { 297 if (object instanceof ContextualizedContent contextualizedContent && getRightsInTargetProfile().contains(rightId)) 298 { 299 Set<UserIdentity> allowedUsers = getAllowedUsers((Content) object, getPermissionContext(contextualizedContent)); 300 if (allowedUsers != null) 301 { 302 return allowedUsers.stream() 303 .collect(Collectors.toMap(user -> user, user -> AccessResult.USER_ALLOWED)); 304 } 305 } 306 return Map.of(); 307 } 308 309 public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object) 310 { 311 return Map.of(); 312 } 313 314 public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object) 315 { 316 return Map.of(); 317 } 318 319 public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object) 320 { 321 return Map.of(); 322 } 323 324 public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId) 325 { 326 return false; 327 } 328 329 public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups) 330 { 331 return false; 332 } 333 334 public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId) 335 { 336 return false; 337 } 338 339 public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts) 340 { 341 return false; 342 } 343 344 public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId) 345 { 346 return false; 347 } 348 349 public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts) 350 { 351 return false; 352 } 353 354 @Override 355 public AccessExplanation explainPermission(UserIdentity user, Set<GroupIdentity> groups, String rightId, Object object) 356 { 357 return AccessController.getDefaultAccessExplanation(getId(), AccessResult.UNKNOWN); 358 } 359 360 @Override 361 public Map<ExplanationObject, Map<Permission, AccessExplanation>> explainAllPermissions(UserIdentity identity, Set<GroupIdentity> groups, Set<Object> workspacesContexts) 362 { 363 return Map.of(); 364 } 365 366 public I18nizableText getObjectLabel(Object object) 367 { 368 if (object instanceof Content content) 369 { 370 return ODFContentHierarchicalAccessController.getContentObjectLabel(content, _odfContentsTreeHelper); 371 } 372 throw new RightsException("Unsupported object: " + object.toString()); 373 } 374 375 public I18nizableText getObjectCategory(Object object) 376 { 377 return ODFContentHierarchicalAccessController.ODF_CONTEXT_CATEGORY; 378 } 379 380 public Map<Permission, AccessExplanation> explainAllPermissionsForAnonymous(Object object) 381 { 382 return Map.of(); 383 } 384 385 public Map<Permission, AccessExplanation> explainAllPermissionsForAnyConnected(Object object) 386 { 387 return Map.of(); 388 } 389 390 public Map<UserIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByUser(Object object) 391 { 392 return Map.of(); 393 } 394 395 public Map<GroupIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByGroup(Object object) 396 { 397 return Map.of(); 398 } 399}