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