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.odf.rights; 017 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Set; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.avalon.framework.service.Serviceable; 027 028import org.ametys.cms.repository.Content; 029import org.ametys.cms.repository.ContentQueryHelper; 030import org.ametys.cms.repository.ContentTypeExpression; 031import org.ametys.cms.repository.DefaultContent; 032import org.ametys.core.group.GroupIdentity; 033import org.ametys.core.right.AccessController; 034import org.ametys.core.right.AccessController.Permission.PermissionType; 035import org.ametys.core.right.AccessExplanation; 036import org.ametys.core.right.RightProfilesDAO; 037import org.ametys.core.right.RightsException; 038import org.ametys.core.user.UserIdentity; 039import org.ametys.odf.ODFHelper; 040import org.ametys.odf.ProgramItem; 041import org.ametys.odf.course.CourseFactory; 042import org.ametys.odf.courselist.CourseListFactory; 043import org.ametys.odf.coursepart.CoursePart; 044import org.ametys.odf.coursepart.CoursePartFactory; 045import org.ametys.odf.orgunit.OrgUnit; 046import org.ametys.odf.orgunit.OrgUnitFactory; 047import org.ametys.odf.program.ContainerFactory; 048import org.ametys.odf.program.ProgramFactory; 049import org.ametys.odf.program.SubProgramFactory; 050import org.ametys.odf.tree.ODFContentsTreeHelper; 051import org.ametys.plugins.repository.AmetysObjectIterable; 052import org.ametys.plugins.repository.AmetysObjectResolver; 053import org.ametys.plugins.repository.query.expression.AndExpression; 054import org.ametys.plugins.repository.query.expression.Expression; 055import org.ametys.plugins.repository.query.expression.Expression.Operator; 056import org.ametys.plugins.repository.query.expression.ExpressionContext; 057import org.ametys.plugins.repository.query.expression.UserExpression; 058import org.ametys.runtime.i18n.I18nizableText; 059import org.ametys.runtime.i18n.I18nizableTextParameter; 060import org.ametys.runtime.plugin.component.AbstractLogEnabled; 061import org.ametys.runtime.plugin.component.PluginAware; 062 063/** 064 * This access controller give access the content's creator, regardless of the required right, 065 * if and only if the ODF content is still orphan (during creation process for example) 066 */ 067public class ODFOrphanContentAccessController extends AbstractLogEnabled implements AccessController, Serviceable, PluginAware 068{ 069 /** The right profile DAO */ 070 protected RightProfilesDAO _profileDAO; 071 /** The ametys object resolver */ 072 protected AmetysObjectResolver _resolver; 073 /** The ODF contents tree helper */ 074 protected ODFContentsTreeHelper _odfContentsTreeHelper; 075 private ODFHelper _odfHelper; 076 private String _id; 077 078 public void service(ServiceManager manager) throws ServiceException 079 { 080 _odfContentsTreeHelper = (ODFContentsTreeHelper) manager.lookup(ODFContentsTreeHelper.ROLE); 081 _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE); 082 _profileDAO = (RightProfilesDAO) manager.lookup(RightProfilesDAO.ROLE); 083 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 084 } 085 086 public void setPluginInfo(String pluginName, String featureName, String id) 087 { 088 _id = id; 089 } 090 091 public String getId() 092 { 093 return _id; 094 } 095 096 public boolean supports(Object object) 097 { 098 return object instanceof ProgramItem || object instanceof OrgUnit || object instanceof CoursePart; 099 } 100 101 /** 102 * Determines if the object is a orphan program item (without parent) 103 * @param object the object 104 * @return true if the object is a orphan program item 105 */ 106 protected boolean _isOrphan(Object object) 107 { 108 if (object instanceof ProgramItem) 109 { 110 List<ProgramItem> parentProgramItems = _odfHelper.getParentProgramItems((ProgramItem) object); 111 return parentProgramItems.isEmpty(); 112 } 113 else if (object instanceof OrgUnit) 114 { 115 return ((OrgUnit) object).getParentOrgUnit() == null; 116 } 117 else if (object instanceof CoursePart) 118 { 119 return ((CoursePart) object).getCourses().isEmpty(); 120 } 121 122 return false; 123 } 124 125 /** 126 * Get the user permission on object 127 * @param user the user 128 * @param object the object 129 * @return The access result 130 */ 131 protected AccessResult _getUserPermission(UserIdentity user, Object object) 132 { 133 if (_isOrphan(object) && !_hasOrgUnit(object)) 134 { 135 if (user.equals(((Content) object).getCreator())) 136 { 137 return AccessResult.USER_ALLOWED; 138 } 139 } 140 return AccessResult.UNKNOWN; 141 } 142 143 /** 144 * Determines if the object has a orgunit 145 * @param object the object 146 * @return true if the object is attach to a orgunit 147 */ 148 protected boolean _hasOrgUnit(Object object) 149 { 150 if (object instanceof ProgramItem programItem) 151 { 152 return !programItem.getOrgUnits().isEmpty(); 153 } 154 return false; 155 } 156 157 /** 158 * Get the permission by users 159 * @param object the object 160 * @return the permission by users 161 */ 162 protected Map<UserIdentity, AccessResult> _getPermissionByUser(Object object) 163 { 164 Map<UserIdentity, AccessResult> permissions = new HashMap<>(); 165 if (_isOrphan(object) && !_hasOrgUnit(object)) 166 { 167 permissions.put(((Content) object).getCreator(), AccessResult.USER_ALLOWED); 168 } 169 return permissions; 170 } 171 172 @Override 173 public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object) 174 { 175 return _getUserPermission(user, object); 176 } 177 178 @Override 179 public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object) 180 { 181 return _getUserPermission(user, object); 182 } 183 184 @Override 185 public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object) 186 { 187 return Collections.EMPTY_MAP; 188 } 189 190 @Override 191 public AccessResult getPermissionForAnonymous(String rightId, Object object) 192 { 193 return AccessResult.UNKNOWN; 194 } 195 196 public AccessResult getReadAccessPermissionForAnonymous(Object object) 197 { 198 return AccessResult.UNKNOWN; 199 } 200 201 @Override 202 public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object) 203 { 204 return AccessResult.UNKNOWN; 205 } 206 207 @Override 208 public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object) 209 { 210 return AccessResult.UNKNOWN; 211 } 212 213 @Override 214 public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object) 215 { 216 return _getPermissionByUser(object); 217 } 218 219 @Override 220 public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object) 221 { 222 return _getPermissionByUser(object); 223 } 224 225 @Override 226 public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object) 227 { 228 return Collections.EMPTY_MAP; 229 } 230 231 @Override 232 public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object) 233 { 234 return Collections.EMPTY_MAP; 235 } 236 237 @Override 238 public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId) 239 { 240 return false; 241 } 242 243 @Override 244 public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups) 245 { 246 return false; 247 } 248 249 @Override 250 public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId) 251 { 252 return false; 253 } 254 255 @Override 256 public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts) 257 { 258 return false; 259 } 260 261 @Override 262 public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId) 263 { 264 return false; 265 } 266 267 @Override 268 public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts) 269 { 270 return false; 271 } 272 273 public AccessExplanation explainReadAccessPermission(UserIdentity user, Set<GroupIdentity> groups, Object object) 274 { 275 return _getAccessExplanation(getReadAccessPermission(user, groups, object), object); 276 } 277 278 public AccessExplanation explainPermission(UserIdentity user, Set<GroupIdentity> groups, String rightId, Object object) 279 { 280 return _getAccessExplanation(getPermission(user, groups, rightId, object), object); 281 } 282 283 private AccessExplanation _getAccessExplanation(AccessResult permission, Object object) 284 { 285 if (permission == AccessResult.USER_ALLOWED) 286 { 287 return new AccessExplanation(getId(), permission, 288 new I18nizableText("plugin.odf", "PLUGINS_ODF_ORPHAN_ACCESS_CONTROLLER_EXPLANATION", _getI18nParameters(object)) 289 ); 290 } 291 return AccessController.getDefaultAccessExplanation(getId(), permission); 292 } 293 294 /** 295 * Get the context label 296 * @param object the context 297 * @return the label 298 */ 299 protected Map<String, I18nizableTextParameter> _getI18nParameters(Object object) 300 { 301 Map<String, I18nizableTextParameter> params = new HashMap<>(); 302 if (object instanceof Content content) 303 { 304 params.put("title", new I18nizableText(content.getTitle())); 305 } 306 307 if (object instanceof ProgramItem item) 308 { 309 params.put("code", new I18nizableText(item.getDisplayCode())); 310 } 311 else if (object instanceof OrgUnit orgunit) 312 { 313 params.put("code", new I18nizableText(orgunit.getDisplayCode())); 314 } 315 else if (object instanceof CoursePart part) 316 { 317 params.put("code", new I18nizableText(part.getDisplayCode())); 318 } 319 return params; 320 } 321 322 @Override 323 public Map<ExplanationObject, Map<Permission, AccessExplanation>> explainAllPermissions(UserIdentity identity, Set<GroupIdentity> groups, Set<Object> workspacesContexts) 324 { 325 Map<ExplanationObject, Map<Permission, AccessExplanation>> result = new HashMap<>(); 326 327 if (workspacesContexts.contains("/cms")) 328 { 329 330 UserExpression userExpression = new UserExpression(DefaultContent.METADATA_CREATOR, Operator.EQ, identity, ExpressionContext.newInstance().withInternal(true)); 331 Expression contentTypeExpression = new ContentTypeExpression(Operator.EQ, ProgramFactory.PROGRAM_CONTENT_TYPE, SubProgramFactory.SUBPROGRAM_CONTENT_TYPE, CourseFactory.COURSE_CONTENT_TYPE, ContainerFactory.CONTAINER_CONTENT_TYPE, CoursePartFactory.COURSE_PART_CONTENT_TYPE, CourseListFactory.COURSE_LIST_CONTENT_TYPE, OrgUnitFactory.ORGUNIT_CONTENT_TYPE); 332 String query = ContentQueryHelper.getContentXPathQuery(new AndExpression(userExpression, contentTypeExpression)); 333 AmetysObjectIterable<Content> contents = _resolver.query(query); 334 335 for (Content content : contents) 336 { 337 Map<Permission, AccessExplanation> contextResult = new HashMap<>(); 338 AccessExplanation explanation = explainPermission(identity, groups, null, content); 339 if (explanation.accessResult() != AccessResult.UNKNOWN) 340 { 341 contextResult.put(new Permission(PermissionType.ALL_RIGHTS, null), explanation); 342 } 343 344 explanation = explainReadAccessPermission(identity, groups, content); 345 if (explanation.accessResult() != AccessResult.UNKNOWN) 346 { 347 contextResult.put(new Permission(PermissionType.READ, null), explanation); 348 } 349 350 if (!contextResult.isEmpty()) 351 { 352 ExplanationObject explanationContext = getExplanationObject(content); 353 result.put(explanationContext, contextResult); 354 } 355 } 356 } 357 358 return result; 359 } 360 361 public Map<Permission, AccessExplanation> explainAllPermissionsForAnonymous(Object object) 362 { 363 return Map.of(); 364 } 365 366 public Map<Permission, AccessExplanation> explainAllPermissionsForAnyConnected(Object object) 367 { 368 return Map.of(); 369 } 370 371 public Map<UserIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByUser(Object object) 372 { 373 UserIdentity creator = ((Content) object).getCreator(); 374 375 AccessResult access = _getUserPermission(creator, object); 376 if (access == AccessResult.UNKNOWN) 377 { 378 return Map.of(); 379 } 380 381 AccessExplanation explanation = _getAccessExplanation(access, object); 382 return Map.of( 383 creator, 384 Map.of( 385 new Permission(PermissionType.READ, null), explanation, 386 new Permission(PermissionType.ALL_RIGHTS, null), explanation 387 ) 388 ); 389 } 390 391 public Map<GroupIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByGroup(Object object) 392 { 393 return Map.of(); 394 } 395 396 public I18nizableText getObjectLabel(Object object) 397 { 398 if (object instanceof Content content) 399 { 400 return ODFContentHierarchicalAccessController.getContentObjectLabel(content, _odfContentsTreeHelper); 401 } 402 throw new RightsException("Unsupported context: " + object.toString()); 403 } 404 405 public I18nizableText getObjectCategory(Object object) 406 { 407 return ODFContentHierarchicalAccessController.ODF_CONTEXT_CATEGORY; 408 } 409}