001/* 002 * Copyright 2024 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.extraction.rights; 017 018import java.io.IOException; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.stream.Stream; 025 026import org.apache.avalon.framework.activity.Initializable; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030import org.apache.commons.collections.MapUtils; 031import org.apache.commons.lang3.StringUtils; 032import org.apache.excalibur.source.SourceException; 033import org.apache.excalibur.source.SourceResolver; 034import org.apache.excalibur.source.impl.FileSource; 035 036import org.ametys.cms.contenttype.ContentTypesHelper; 037import org.ametys.cms.repository.Content; 038import org.ametys.core.group.GroupIdentity; 039import org.ametys.core.right.AccessController; 040import org.ametys.core.right.AccessExplanation; 041import org.ametys.core.right.RightsException; 042import org.ametys.core.user.UserIdentity; 043import org.ametys.plugins.core.impl.right.AbstractRightBasedAccessController; 044import org.ametys.plugins.extraction.ExtractionConstants; 045import org.ametys.plugins.extraction.execution.Extraction; 046import org.ametys.plugins.extraction.execution.ExtractionDAO; 047import org.ametys.runtime.i18n.I18nizableText; 048 049 050/** 051 * {@link AccessController} to allow read access and handle for author of a extraction file 052 * 053 */ 054public class ExtractionAuthorAccessController extends AbstractRightBasedAccessController implements Serviceable, Initializable 055{ 056 private static final List<String> __AUTHOR_RIGHTS = List.of(ExtractionConstants.MODIFY_EXTRACTION_RIGHT_ID, "Workflow_Rights_Edition_Online"); 057 058 private SourceResolver _srcResolver; 059 private String _rootPath; 060 private ExtractionDAO _extractionDAO; 061 private ContentTypesHelper _contentTypesHelper; 062 063 public void service(ServiceManager manager) throws ServiceException 064 { 065 _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 066 _extractionDAO = (ExtractionDAO) manager.lookup(ExtractionDAO.ROLE); 067 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 068 } 069 070 public void initialize() throws Exception 071 { 072 FileSource rootDir = (FileSource) _srcResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR); 073 // use the URI. The path is not available if the definitions folder is not created at start time 074 _rootPath = rootDir.getURI(); 075 } 076 077 public boolean supports(Object object) 078 { 079 return object instanceof Extraction 080 // a fileSource that don't exist is not a collection. 081 // not checking that it exists leads to the root being supported if not created 082 || object instanceof FileSource fileSource && fileSource.exists() && !fileSource.isCollection() && fileSource.getURI().startsWith(_rootPath) 083 || object instanceof Content content && _contentTypesHelper.isInstanceOf(content, ExtractionConstants.DESCRIPTION_CONTENT_TYPE_ID); 084 } 085 086 public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object) 087 { 088 if (user.equals(_getAuthor(object))) 089 { 090 return __AUTHOR_RIGHTS.contains(rightId) ? AccessResult.USER_ALLOWED : AccessResult.UNKNOWN; 091 } 092 093 return AccessResult.UNKNOWN; 094 } 095 096 public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object) 097 { 098 return user.equals(_getAuthor(object)) ? AccessResult.USER_ALLOWED : AccessResult.UNKNOWN; 099 } 100 101 /** 102 * If creator, access to a list of rights 103 */ 104 public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object) 105 { 106 Map<String, AccessResult> permissionByRight = new HashMap<>(); 107 108 if (user.equals(_getAuthor(object))) 109 { 110 for (String rightId : __AUTHOR_RIGHTS) 111 { 112 permissionByRight.put(rightId, AccessResult.USER_ALLOWED); 113 } 114 } 115 116 return permissionByRight; 117 } 118 119 public AccessResult getPermissionForAnonymous(String rightId, Object object) 120 { 121 return AccessResult.UNKNOWN; 122 } 123 124 public AccessResult getReadAccessPermissionForAnonymous(Object object) 125 { 126 return AccessResult.UNKNOWN; 127 } 128 129 public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object) 130 { 131 return AccessResult.UNKNOWN; 132 } 133 134 public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object) 135 { 136 return AccessResult.UNKNOWN; 137 } 138 139 /** 140 * If right requested is in the list, the creator is added the list of USER_ALLOWED 141 */ 142 public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object) 143 { 144 Map<UserIdentity, AccessResult> permissionByUser = new HashMap<>(); 145 146 if (__AUTHOR_RIGHTS.contains(rightId)) 147 { 148 UserIdentity extractionAuthor = _getAuthor(object); 149 permissionByUser.put(extractionAuthor, AccessResult.USER_ALLOWED); 150 } 151 return permissionByUser; 152 } 153 154 public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object) 155 { 156 return MapUtils.EMPTY_MAP; 157 } 158 159 public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object) 160 { 161 return MapUtils.EMPTY_MAP; 162 } 163 164 public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object) 165 { 166 return MapUtils.EMPTY_MAP; 167 } 168 169 public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId) 170 { 171 return false; 172 } 173 174 public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups) 175 { 176 return false; 177 } 178 179 public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId) 180 { 181 return false; 182 } 183 184 public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts) 185 { 186 return false; 187 } 188 189 public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId) 190 { 191 return false; 192 } 193 194 public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts) 195 { 196 return false; 197 } 198 199 @Override 200 protected AccessExplanation _getAccessExplanation(AccessResult result, Object object, UserIdentity user, Set<GroupIdentity> groups, String rightId) 201 { 202 switch (result) 203 { 204 case USER_ALLOWED: 205 case UNKNOWN: 206 if (object instanceof Content) 207 { 208 return new AccessExplanation( 209 getId(), 210 result, 211 new I18nizableText("plugin.extraction", "PLUGINS_EXTRACTION_CONTENT_AUTHOR_ACCESS_CONTROLLER_" + result.name() + "_EXPLANATION", 212 Map.of("title", getObjectLabel(object))) 213 ); 214 } 215 else 216 { 217 return new AccessExplanation( 218 getId(), 219 result, 220 new I18nizableText("plugin.extraction", "PLUGINS_EXTRACTION_AUTHOR_ACCESS_CONTROLLER_" + result.name() + "_EXPLANATION", 221 Map.of("title", getObjectLabel(object))) 222 ); 223 } 224 default: 225 return AccessController.getDefaultAccessExplanation(getId(), result); 226 } 227 } 228 229 private UserIdentity _getAuthor(Object object) 230 { 231 if (object instanceof Extraction extraction) 232 { 233 return extraction.getAuthor(); 234 } 235 else if (object instanceof FileSource fileSource) 236 { 237 return _extractionDAO.getAuthor(fileSource); 238 } 239 else if (object instanceof Content content) 240 { 241 return content.getCreator(); 242 } 243 return null; 244 } 245 246 private String _getExtractionName(Object object) 247 { 248 if (object instanceof FileSource fileSource) 249 { 250 String target = StringUtils.substringAfter(_extractionDAO.getExtractionRightPath(fileSource), ExtractionAccessController.ROOT_CONTEXT); 251 target = StringUtils.replace(target.substring(1), "/", " > "); 252 return target; 253 } 254 // We can't include the hierarchy from the extraction or content 255 // But it shouldn't be visible as there is nothing display the explanation for it 256 else if (object instanceof Extraction extraction) 257 { 258 return extraction.getFileName(); 259 } 260 else if (object instanceof Content content) 261 { 262 return content.getTitle(); 263 } 264 return null; 265 } 266 267 public I18nizableText getObjectLabel(Object object) 268 { 269 String extractionName = _getExtractionName(object); 270 if (extractionName != null) 271 { 272 return new I18nizableText(extractionName); 273 } 274 throw new RightsException("Unsupported context: " + object.toString()); 275 } 276 277 public I18nizableText getObjectCategory(Object object) 278 { 279 return ExtractionAccessController.EXTRACTION_CONTEXT_CATEGORY; 280 } 281 282 @Override 283 protected Iterable< ? extends Object> getHandledObjects(UserIdentity identity, Set<GroupIdentity> groups) 284 { 285 try 286 { 287 FileSource rootDir = (FileSource) _srcResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR); 288 if (rootDir.getFile().exists()) 289 { 290 Stream<FileSource> definitions = _getDefinitions(rootDir); 291 return definitions.toList(); 292 } 293 } 294 catch (IOException e) 295 { 296 getLogger().warn("Failed to compute the list of extractions"); 297 } 298 299 return List.of(); 300 } 301 302 private Stream<FileSource> _getDefinitions(FileSource source) 303 { 304 if (source.isCollection()) 305 { 306 try 307 { 308 return source.getChildren().stream() 309 .filter(FileSource.class::isInstance) 310 .flatMap(src -> _getDefinitions((FileSource) src)); 311 } 312 catch (SourceException e) 313 { 314 getLogger().warn("Failed to compute the list of extractions"); 315 return Stream.of(); 316 } 317 } 318 else 319 { 320 return Stream.of(source); 321 } 322 } 323 324 public ExplanationObject getExplanationObject(Object object) 325 { 326 if (object instanceof FileSource source) 327 { 328 return new ExplanationObject( 329 // we convert the source to the right path to be able to merge with ExtractionAccessController 330 _extractionDAO.getExtractionRightPath(source), 331 getObjectLabel(object), 332 getObjectCategory(object) 333 ); 334 } 335 return super.getExplanationObject(object); 336 } 337 338 @Override 339 protected Collection<String> getHandledRights() 340 { 341 return __AUTHOR_RIGHTS; 342 } 343}