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