001/* 002 * Copyright 2021 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.execution; 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; 024 025import org.apache.avalon.framework.activity.Initializable; 026import org.apache.avalon.framework.component.Component; 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.io.FileUtils; 031import org.apache.commons.lang3.StringUtils; 032import org.apache.commons.lang3.Strings; 033import org.apache.excalibur.source.SourceException; 034import org.apache.excalibur.source.SourceResolver; 035import org.apache.excalibur.source.TraversableSource; 036import org.apache.excalibur.source.impl.FileSource; 037 038import org.ametys.core.cache.AbstractCacheManager; 039import org.ametys.core.cache.Cache; 040import org.ametys.core.file.FileHelper; 041import org.ametys.core.group.GroupIdentity; 042import org.ametys.core.right.ProfileAssignmentStorage.AnonymousOrAnyConnectedKeys; 043import org.ametys.core.right.ProfileAssignmentStorage.UserOrGroup; 044import org.ametys.core.right.ProfileAssignmentStorageExtensionPoint; 045import org.ametys.core.right.RightManager; 046import org.ametys.core.right.RightManager.RightResult; 047import org.ametys.core.ui.Callable; 048import org.ametys.core.user.CurrentUserProvider; 049import org.ametys.core.user.UserIdentity; 050import org.ametys.plugins.core.user.UserHelper; 051import org.ametys.plugins.extraction.ExtractionConstants; 052import org.ametys.plugins.extraction.rights.ExtractionAccessController; 053import org.ametys.runtime.i18n.I18nizableText; 054import org.ametys.runtime.plugin.component.AbstractLogEnabled; 055 056/** 057 * Object representing the extraction definition file content 058 */ 059public class ExtractionDAO extends AbstractLogEnabled implements Serviceable, Component, Initializable 060{ 061 /** The Avalon role */ 062 public static final String ROLE = ExtractionDAO.class.getName(); 063 064 /** Extraction author cache id */ 065 private static final String EXTRACTION_AUTHOR_CACHE = ExtractionDAO.class.getName() + "$extractionAuthor"; 066 067 private CurrentUserProvider _userProvider; 068 private RightManager _rightManager; 069 private SourceResolver _sourceResolver; 070 private ExtractionDefinitionReader _definitionReader; 071 private ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageEP; 072 private CurrentUserProvider _currentUserProvider; 073 private AbstractCacheManager _cacheManager; 074 private UserHelper _userHelper; 075 private TraversableSource _root; 076 private FileHelper _fileHelper; 077 078 public void service(ServiceManager manager) throws ServiceException 079 { 080 _userProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 081 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 082 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 083 _definitionReader = (ExtractionDefinitionReader) manager.lookup(ExtractionDefinitionReader.ROLE); 084 _profileAssignmentStorageEP = (ProfileAssignmentStorageExtensionPoint) manager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE); 085 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 086 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 087 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 088 _fileHelper = (FileHelper) manager.lookup(FileHelper.ROLE); 089 } 090 091 public void initialize() throws Exception 092 { 093 _root = (TraversableSource) _sourceResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR); 094 095 _cacheManager.createRequestCache(EXTRACTION_AUTHOR_CACHE, 096 new I18nizableText("plugin.extraction", "PLUGINS_EXTRACTION_CACHE_DEFINITION_AUTHOR_LABEL"), 097 new I18nizableText("plugin.extraction", "PLUGINS_EXTRACTION_CACHE_DEFINITION_AUTHOR_DESCRIPTION"), 098 true); 099 } 100 101 /** 102 * Get the root container properties 103 * @return The root container properties 104 * @throws IOException If an error occurred while reading folder 105 */ 106 @Callable(rights = Callable.NO_CHECK_REQUIRED) // required for bus message 107 public Map<String, Object> getRootProperties() throws IOException 108 { 109 TraversableSource rootDir = (TraversableSource) _sourceResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR); 110 return getExtractionContainerProperties(rootDir, rootDir, true); 111 } 112 113 /** 114 * Get the extraction folder properties 115 * @param relPath the relative folder path 116 * @return the extraction container properties 117 * @throws IOException if an error occured 118 */ 119 @Callable (rights = Callable.NO_CHECK_REQUIRED) // required for bus message 120 public Map<String, Object> getExtractionContainerProperties(String relPath) throws IOException 121 { 122 TraversableSource root = (TraversableSource) _sourceResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR); 123 TraversableSource folderSrc = (FileSource) _sourceResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR + relPath); 124 return getExtractionContainerProperties(root, folderSrc, true); 125 } 126 127 /** 128 * Get extraction container properties 129 * @param root the root of extraction definitions 130 * @param folder the source of the extraction container 131 * @param withRights true to include rights information 132 * @return The extraction container properties 133 */ 134 public Map<String, Object> getExtractionContainerProperties(TraversableSource root, TraversableSource folder, boolean withRights) 135 { 136 Map<String, Object> infos = new HashMap<>(); 137 138 infos.put("type", "collection"); 139 infos.put("isRoot", folder.getURI().equals(root.getURI())); 140 infos.put("name", folder.getName()); 141 infos.put("path", _getRelativePath(root, folder)); 142 143 if (withRights) 144 { 145 UserIdentity currentUser = _userProvider.getUser(); 146 infos.put("canRead", canRead(currentUser, folder)); 147 infos.put("canRename", canRename(currentUser, folder)); 148 infos.put("canWrite", canWrite(currentUser, folder)); 149 infos.put("canDelete", canDelete(currentUser, folder)); 150 infos.put("canAssignRights", canAssignRights(currentUser, folder)); 151 } 152 153 return infos; 154 } 155 156 private String _getRelativePath (TraversableSource root, TraversableSource file) 157 { 158 String relPath = StringUtils.substringAfter(file.getURI(), root.getURI()); 159 return Strings.CS.removeEnd(relPath, "/"); 160 } 161 162 /** 163 * Get the extraction properties 164 * @param relDefinitionFilePath the relative fiel path 165 * @return the extraction properties 166 * @throws Exception if an error occured 167 */ 168 @Callable (rights = Callable.NO_CHECK_REQUIRED) // required for bus message 169 public Map<String, Object> getExtractionProperties(String relDefinitionFilePath) throws Exception 170 { 171 FileSource root = (FileSource) _sourceResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR); 172 FileSource fileSource = (FileSource) _sourceResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR + relDefinitionFilePath); 173 Extraction extraction = _definitionReader.readExtractionDefinitionFile(fileSource.getFile()); 174 return getExtractionProperties(extraction, root, fileSource, true); 175 } 176 177 /** 178 * Get extraction properties 179 * @param extraction the extraction 180 * @param root the root of extraction definitions 181 * @param file the source of the extraction 182 * @param withRights true to include rights information 183 * @return The extraction properties 184 */ 185 public Map<String, Object> getExtractionProperties(Extraction extraction, TraversableSource root, TraversableSource file, boolean withRights) 186 { 187 Map<String, Object> infos = new HashMap<>(); 188 189 infos.put("name", file.getName()); 190 infos.put("path", _getRelativePath(root, file)); 191 192 infos.put("descriptionId", extraction.getDescriptionId()); 193 194 UserIdentity author = extraction.getAuthor(); 195 infos.put("author", _userHelper.user2json(author)); 196 197 if (withRights) 198 { 199 UserIdentity currentUser = _userProvider.getUser(); 200 infos.put("canRead", canRead(currentUser, file)); 201 infos.put("canWrite", canWrite(currentUser, file)); 202 infos.put("canDelete", canDelete(currentUser, file)); 203 infos.put("canAssignRights", canAssignRights(currentUser, file)); 204 } 205 206 return infos; 207 } 208 209 /** 210 * Check if a folder has a descendant in read access for a given user 211 * @param userIdentity the user 212 * @param folder the source of the extraction container 213 * @return <code>true</code> if the folder has a descendant in read access, <code>false</code> otherwise 214 */ 215 public Boolean hasAnyReadableDescendant(UserIdentity userIdentity, TraversableSource folder) 216 { 217 try 218 { 219 if (folder.exists()) 220 { 221 for (TraversableSource child : (Collection<TraversableSource>) folder.getChildren()) 222 { 223 if (child.isCollection()) 224 { 225 if (canRead(userIdentity, child) || hasAnyReadableDescendant(userIdentity, child)) 226 { 227 return true; 228 } 229 } 230 else if (child.getName().endsWith(".xml") && canRead(userIdentity, child)) 231 { 232 return true; 233 } 234 } 235 } 236 237 return false; 238 } 239 catch (SourceException e) 240 { 241 throw new RuntimeException("Cannot list child elements of " + folder.getURI(), e); 242 } 243 } 244 245 /** 246 * Check if a folder have descendant in write access for a given user 247 * @param userIdentity the user identity 248 * @param folder the source of the extraction container 249 * @return true if the user have write right for at least one child of this container 250 */ 251 public Boolean hasAnyWritableDescendant(UserIdentity userIdentity, TraversableSource folder) 252 { 253 return hasAnyWritableDescendant(userIdentity, folder, false); 254 } 255 256 /** 257 * Check if a folder have descendant in write access for a given user 258 * @param userIdentity the user identity 259 * @param folder the source of the extraction container 260 * @param ignoreExtraction true to ignore extraction file from search (rights will check only on containers) 261 * @return true if the user have write right for at least one child of this container 262 */ 263 public Boolean hasAnyWritableDescendant(UserIdentity userIdentity, TraversableSource folder, boolean ignoreExtraction) 264 { 265 try 266 { 267 if (folder.exists()) 268 { 269 for (TraversableSource child : (Collection<TraversableSource>) folder.getChildren()) 270 { 271 if (child.isCollection()) 272 { 273 if (canWrite(userIdentity, child) || hasAnyWritableDescendant(userIdentity, child)) 274 { 275 return true; 276 } 277 } 278 else if (!ignoreExtraction && child.getName().endsWith(".xml") && canWrite(userIdentity, child)) 279 { 280 return true; 281 } 282 } 283 } 284 285 return false; 286 } 287 catch (SourceException e) 288 { 289 throw new RuntimeException("Cannot list child elements of " + folder.getURI(), e); 290 } 291 } 292 293 /** 294 * Checks if a folder has descendants in the right assignment access for a given user 295 * @param userIdentity the user 296 * @param folder the source of the extraction container 297 * @return <code>true</code> if the folder has descendant, <code>false</code> otherwise 298 */ 299 public boolean hasAnyAssignableDescendant(UserIdentity userIdentity, TraversableSource folder) 300 { 301 try 302 { 303 if (folder.exists()) 304 { 305 for (TraversableSource child : (Collection<TraversableSource>) folder.getChildren()) 306 { 307 if (child.isCollection()) 308 { 309 if (canAssignRights(userIdentity, child) || hasAnyAssignableDescendant(userIdentity, child)) 310 { 311 return true; 312 } 313 } 314 else if (child.getName().endsWith(".xml")) 315 { 316 if (canAssignRights(userIdentity, child)) 317 { 318 return true; 319 } 320 } 321 } 322 } 323 324 return false; 325 } 326 catch (SourceException e) 327 { 328 throw new RuntimeException("Cannot list child elements of " + folder.getURI(), e); 329 } 330 } 331 332 /** 333 * Check if a user has read rights on an extraction container or file 334 * @param userIdentity the user 335 * @param source the source of the extraction container or file 336 * @return <code>true</code> if the user has read rights on an extraction container, <code>false</code> otherwise 337 */ 338 public boolean canRead(UserIdentity userIdentity, TraversableSource source) 339 { 340 return _rightManager.hasReadAccess(userIdentity, source) || canWrite(userIdentity, source); 341 } 342 343 /** 344 * Check if a user has write rights on an extraction container or an extraction 345 * @param userIdentity the user 346 * @param source the source of the extraction file or extration container 347 * @return <code>true</code> if the user has write rights on an extraction container, <code>false</code> otherwise 348 */ 349 public boolean canWrite(UserIdentity userIdentity, TraversableSource source) 350 { 351 return canWrite(userIdentity, source, false); 352 } 353 354 /** 355 * Determines if the user can rename an extraction container 356 * @param userIdentity the user 357 * @param folder the extraction container 358 * @return true if the user can delete the extraction container 359 */ 360 public boolean canRename(UserIdentity userIdentity, TraversableSource folder) 361 { 362 try 363 { 364 return !_isRoot(folder) // is not root 365 && canWrite(userIdentity, folder) // has write access 366 && canWrite(userIdentity, (TraversableSource) folder.getParent()); // has write access on parent 367 } 368 catch (SourceException e) 369 { 370 throw new RuntimeException("Unable to determine user rights on the extraction container " + folder.getURI(), e); 371 } 372 } 373 374 /** 375 * Determines if the user can delete an extraction container or the extraction file 376 * @param userIdentity the user 377 * @param source the extraction container or the extraction file 378 * @return true if the user can delete the extraction container 379 */ 380 public boolean canDelete(UserIdentity userIdentity, TraversableSource source) 381 { 382 try 383 { 384 return !_isRoot(source) // is not root 385 && canWrite(userIdentity, (TraversableSource) source.getParent()) // has write access on parent 386 && canWrite(userIdentity, source, true); // has write access on itselft and each descendant 387 } 388 catch (SourceException e) 389 { 390 throw new RuntimeException("Unable to determine user rights on extraction container or file at uri " + source.getURI(), e); 391 } 392 } 393 394 /** 395 * Check if a user has write access on an extraction container 396 * @param userIdentity the user user identity 397 * @param source the extraction container or the extraction file 398 * @param recursively true to check write access on all descendants recursively 399 * @return true if the user has write access on the extraction container 400 */ 401 public boolean canWrite(UserIdentity userIdentity, TraversableSource source, boolean recursively) 402 { 403 boolean hasRight = _rightManager.hasRight(userIdentity, ExtractionConstants.MODIFY_EXTRACTION_RIGHT_ID, source) == RightResult.RIGHT_ALLOW; 404 if (!hasRight) 405 { 406 return false; 407 } 408 409 try 410 { 411 if (recursively && source.isCollection()) 412 { 413 for (TraversableSource child : (Collection<TraversableSource>) source.getChildren()) 414 { 415 hasRight = hasRight && canWrite(userIdentity, child); 416 417 if (!hasRight) 418 { 419 return false; 420 } 421 } 422 } 423 424 return hasRight; 425 } 426 catch (SourceException e) 427 { 428 throw new RuntimeException("Unable to determine user rights on extraction container " + source.getURI(), e); 429 } 430 } 431 432 /** 433 * Check if a user can edit rights on an extraction container or an extraction file 434 * @param userIdentity the user 435 * @param source the source of the extraction container or file 436 * @return true if the user can edit rights on an extraction container or file 437 */ 438 public boolean canAssignRights(UserIdentity userIdentity, TraversableSource source) 439 { 440 try 441 { 442 return _rightManager.hasRight(userIdentity, "Runtime_Rights_Rights_Handle", "/cms") == RightResult.RIGHT_ALLOW 443 || !_isRoot(source) // is not root 444 && canWrite(userIdentity, (TraversableSource) source.getParent()) // has write access on parent 445 && canWrite(userIdentity, source, true); // has write access on itselft and each descendant 446 } 447 catch (SourceException e) 448 { 449 throw new RuntimeException("Unable to determine the user rights on the extraction container or file at uri " + source.getURI(), e); 450 } 451 } 452 453 /** 454 * Determines if the extraction container is the root node 455 * @param folder the extraction container 456 * @return true if is root 457 */ 458 protected boolean _isRoot(TraversableSource folder) 459 { 460 return trimLastFileSeparator(_root.getURI()).equals(trimLastFileSeparator(folder.getURI())); 461 } 462 463 /** 464 * Get the path for rights of an extraction container or file 465 * @param source the source of extraction container or file 466 * @return the path for rights 467 */ 468 public String getExtractionRightPath(TraversableSource source) 469 { 470 String rootURI = trimLastFileSeparator(_root.getURI()); 471 String sourceURI = source.getURI(); 472 473 if (!sourceURI.startsWith(rootURI)) 474 { 475 // The source is an extraction source 476 return null; 477 } 478 479 // Get only the part after the root folder to get the relative path 480 String relPath = StringUtils.substringAfter(trimLastFileSeparator(sourceURI), rootURI); 481 482 // In some case, relPath can start with a /, we need to trim it to test if it is an empty path corresponding to the root 483 if (relPath.startsWith("/")) 484 { 485 relPath = StringUtils.substringAfter(relPath, "/"); 486 } 487 488 return StringUtils.isEmpty(relPath) ? ExtractionAccessController.ROOT_CONTEXT : ExtractionAccessController.ROOT_CONTEXT + "/" + relPath; 489 } 490 491 /** 492 * Get the source corresponding to the right context of an extraction container or file 493 * @param rightContext The rights context such as '/extraction-dir/path/to/file 494 * @return the resolved source file or null if the given context is not an extraction context 495 * @throws IOException if an error occured 496 */ 497 public TraversableSource getExtractionSource(String rightContext) throws IOException 498 { 499 if (rightContext.startsWith(ExtractionAccessController.ROOT_CONTEXT)) 500 { 501 String relPath = StringUtils.substringAfter(rightContext, ExtractionAccessController.ROOT_CONTEXT); 502 String fileUri = ExtractionConstants.DEFINITIONS_DIR + relPath; 503 return (TraversableSource) _sourceResolver.resolveURI(fileUri); 504 } 505 506 return null; 507 } 508 509 /** 510 * Copy rights from one context to another one 511 * @param sourceContext the source context 512 * @param targetContext the target context 513 */ 514 public void copyRights(String sourceContext, String targetContext) 515 { 516 // Get the mapping between users and profiles 517 Map<UserIdentity, Map<UserOrGroup, Set<String>>> profilesForUsers = _profileAssignmentStorageEP.getProfilesForUsers(sourceContext, null); 518 // Copy allowed user assignment profiles to new context 519 profilesForUsers.entrySet() 520 .forEach(entry -> _copyAllowedUsers(entry.getKey(), entry.getValue().get(UserOrGroup.ALLOWED), targetContext)); 521 // Copy denied user assignment profiles to new context 522 profilesForUsers.entrySet() 523 .forEach(entry -> _copyDeniedUsers(entry.getKey(), entry.getValue().get(UserOrGroup.DENIED), targetContext)); 524 525 // Get the mapping between groups and profiles 526 Map<GroupIdentity, Map<UserOrGroup, Set<String>>> profilesForGroups = _profileAssignmentStorageEP.getProfilesForGroups(sourceContext, null); 527 // Copy allowed group assignment profiles to new context 528 profilesForGroups.entrySet() 529 .forEach(entry -> _copyAllowedGroups(entry.getKey(), entry.getValue().get(UserOrGroup.ALLOWED), targetContext)); 530 // Copy denied group assignment profiles to new context 531 profilesForGroups.entrySet() 532 .forEach(entry -> _copyDeniedGroups(entry.getKey(), entry.getValue().get(UserOrGroup.DENIED), targetContext)); 533 534 // Get the mapping between anonymous or any connected user and profiles 535 Map<AnonymousOrAnyConnectedKeys, Set<String>> profilesForAnonymousOrAnyConnectedUser = _profileAssignmentStorageEP.getProfilesForAnonymousAndAnyConnectedUser(sourceContext); 536 // Copy allowed anonymous user assignment profiles to new context 537 profilesForAnonymousOrAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_ALLOWED) 538 .forEach(profileId -> _profileAssignmentStorageEP.allowProfileToAnonymous(profileId, targetContext)); 539 // Copy denied anonymous user assignment profiles to new context 540 profilesForAnonymousOrAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_DENIED) 541 .forEach(profileId -> _profileAssignmentStorageEP.denyProfileToAnonymous(profileId, targetContext)); 542 // Copy allowed any connected user assignment profiles to new context 543 profilesForAnonymousOrAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_ALLOWED) 544 .forEach(profileId -> _profileAssignmentStorageEP.allowProfileToAnyConnectedUser(profileId, targetContext)); 545 // Copy denied any connected user assignment profiles to new context 546 profilesForAnonymousOrAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_DENIED) 547 .forEach(profileId -> _profileAssignmentStorageEP.denyProfileToAnyConnectedUser(profileId, targetContext)); 548 } 549 550 private void _copyAllowedUsers(UserIdentity userIdentity, Set<String> profiles, String context) 551 { 552 profiles.forEach(profile -> _profileAssignmentStorageEP.allowProfileToUser(userIdentity, profile, context)); 553 } 554 555 private void _copyDeniedUsers(UserIdentity userIdentity, Set<String> profiles, String context) 556 { 557 profiles.forEach(profile -> _profileAssignmentStorageEP.denyProfileToUser(userIdentity, profile, context)); 558 } 559 560 private void _copyAllowedGroups(GroupIdentity groupIdentity, Set<String> profiles, String context) 561 { 562 profiles.forEach(profile -> _profileAssignmentStorageEP.allowProfileToGroup(groupIdentity, profile, context)); 563 } 564 565 private void _copyDeniedGroups(GroupIdentity groupIdentity, Set<String> profiles, String context) 566 { 567 profiles.forEach(profile -> _profileAssignmentStorageEP.denyProfileToGroup(groupIdentity, profile, context)); 568 } 569 570 /** 571 * Delete rights from a context 572 * @param context the context 573 */ 574 public void deleteRights(String context) 575 { 576 // Get the mapping between users and profiles 577 Map<UserIdentity, Map<UserOrGroup, Set<String>>> profilesForUsers = _profileAssignmentStorageEP.getProfilesForUsers(context, null); 578 // Copy allowed user assignment profiles to new context 579 profilesForUsers.entrySet() 580 .forEach(entry -> _removeAllowedUsers(entry.getKey(), entry.getValue().get(UserOrGroup.ALLOWED), context)); 581 // Copy denied user assignment profiles to new context 582 profilesForUsers.entrySet() 583 .forEach(entry -> _removeDeniedUsers(entry.getKey(), entry.getValue().get(UserOrGroup.DENIED), context)); 584 585 // Get the mapping between groups and profiles 586 Map<GroupIdentity, Map<UserOrGroup, Set<String>>> profilesForGroups = _profileAssignmentStorageEP.getProfilesForGroups(context, null); 587 // Copy allowed group assignment profiles to new context 588 profilesForGroups.entrySet() 589 .forEach(entry -> _removeAllowedGroups(entry.getKey(), entry.getValue().get(UserOrGroup.ALLOWED), context)); 590 // Copy denied group assignment profiles to new context 591 profilesForGroups.entrySet() 592 .forEach(entry -> _removeDeniedGroups(entry.getKey(), entry.getValue().get(UserOrGroup.DENIED), context)); 593 594 // Get the mapping between anonymous or any connected user and profiles 595 Map<AnonymousOrAnyConnectedKeys, Set<String>> profilesForAnonymousOrAnyConnectedUser = _profileAssignmentStorageEP.getProfilesForAnonymousAndAnyConnectedUser(context); 596 // Copy allowed anonymous user assignment profiles to new context 597 profilesForAnonymousOrAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_ALLOWED) 598 .forEach(profileId -> _profileAssignmentStorageEP.removeAllowedProfileFromAnonymous(profileId, context)); 599 // Copy denied anonymous user assignment profiles to new context 600 profilesForAnonymousOrAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANONYMOUS_DENIED) 601 .forEach(profileId -> _profileAssignmentStorageEP.removeDeniedProfileFromAnonymous(profileId, context)); 602 // Copy allowed any connected user assignment profiles to new context 603 profilesForAnonymousOrAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_ALLOWED) 604 .forEach(profileId -> _profileAssignmentStorageEP.removeAllowedProfileFromAnyConnectedUser(profileId, context)); 605 // Copy denied any connected user assignment profiles to new context 606 profilesForAnonymousOrAnyConnectedUser.get(AnonymousOrAnyConnectedKeys.ANYCONNECTEDUSER_DENIED) 607 .forEach(profileId -> _profileAssignmentStorageEP.removeDeniedProfileFromAnyConnectedUser(profileId, context)); 608 } 609 610 private void _removeAllowedUsers(UserIdentity userIdentity, Set<String> profiles, String context) 611 { 612 profiles.forEach(profile -> _profileAssignmentStorageEP.removeAllowedProfileFromUser(userIdentity, profile, context)); 613 } 614 615 private void _removeDeniedUsers(UserIdentity userIdentity, Set<String> profiles, String context) 616 { 617 profiles.forEach(profile -> _profileAssignmentStorageEP.removeDeniedProfileFromUser(userIdentity, profile, context)); 618 } 619 620 private void _removeAllowedGroups(GroupIdentity groupIdentity, Set<String> profiles, String context) 621 { 622 profiles.forEach(profile -> _profileAssignmentStorageEP.removeAllowedProfileFromGroup(groupIdentity, profile, context)); 623 } 624 625 private void _removeDeniedGroups(GroupIdentity groupIdentity, Set<String> profiles, String context) 626 { 627 profiles.forEach(profile -> _profileAssignmentStorageEP.removeDeniedProfileFromGroup(groupIdentity, profile, context)); 628 } 629 630 /** 631 * Move an extraction file or folder inside a given directory 632 * 633 * @param srcRelPath The relative URI of file/folder to move 634 * @param targetRelPath The target relative URI of file/folder to move 635 * @return a result map with the name and uri of moved file in case of 636 * success. 637 * @throws IOException If an error occurred manipulating the source 638 */ 639 @Callable (rights = ExtractionConstants.MODIFY_EXTRACTION_RIGHT_ID) 640 public Map<String, Object> moveOrRenameExtractionDefinitionFile(String srcRelPath, String targetRelPath) throws IOException 641 { 642 Map<String, Object> result = new HashMap<>(); 643 644 FileSource srcFile = null; 645 FileSource targetFile = null; 646 try 647 { 648 srcFile = (FileSource) _sourceResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR + srcRelPath); 649 targetFile = (FileSource) _sourceResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR + targetRelPath); 650 651 String sourceContext = ExtractionAccessController.ROOT_CONTEXT + "/" + srcRelPath; 652 String targetContext = ExtractionAccessController.ROOT_CONTEXT + "/" + targetRelPath; 653 654 result = _moveOrRenameSource(srcFile, targetFile, sourceContext, targetContext); 655 656 if (result.containsKey("uri")) 657 { 658 String newURI = (String) result.get("uri"); 659 String path = newURI.substring(_root.getURI().length()); 660 result.put("path", path); 661 } 662 } 663 finally 664 { 665 _sourceResolver.release(srcFile); 666 _sourceResolver.release(targetFile); 667 } 668 669 return result; 670 } 671 672 /** 673 * Move a file or folder 674 * 675 * @param sourceFile The file/folder to move 676 * @param targetFile The target file 677 * @param sourceContext the source context 678 * @param targetContext the target context 679 * @return a result map with the name and uri of moved file in case of 680 * success. 681 * @throws IOException If an error occurred manipulating the source 682 */ 683 private Map<String, Object> _moveOrRenameSource(FileSource sourceFile, FileSource targetFile, String sourceContext, String targetContext) throws IOException 684 { 685 Map<String, Object> result = new HashMap<>(); 686 687 // Check if the user try to move files outside the root folder 688 if (!Strings.CS.startsWith(sourceFile.getURI(), _root.getURI()) || !Strings.CS.startsWith(targetFile.getURI(), _root.getURI())) 689 { 690 result.put("success", false); 691 result.put("error", "no-exists"); 692 693 getLogger().error("User '{}' tried to move parameter file outside of the root extraction directory.", _currentUserProvider.getUser()); 694 695 return result; 696 } 697 698 if (!sourceFile.exists()) 699 { 700 result.put("success", false); 701 result.put("error", "no-exists"); 702 return result; 703 } 704 705 if (targetFile.exists()) 706 { 707 // If both files are equals, there is no need to rename or move it 708 if (sourceFile.getFile().equals(targetFile.getFile())) 709 { 710 result.put("success", true); 711 result.put("name", targetFile.getName()); 712 result.put("uri", targetFile.getURI()); 713 return result; 714 } 715 else 716 { 717 result.put("success", false); 718 result.put("error", "already-exists"); 719 return result; 720 } 721 } 722 723 copyRightsRecursively(sourceContext, targetContext, sourceFile); 724 if (sourceFile.getFile().isFile()) 725 { 726 FileUtils.moveFile(sourceFile.getFile(), targetFile.getFile()); 727 } 728 else 729 { 730 FileUtils.moveDirectory(sourceFile.getFile(), targetFile.getFile()); 731 } 732 deleteRightsRecursively(sourceContext, targetFile); 733 734 result.put("success", true); 735 result.put("name", targetFile.getName()); 736 result.put("uri", targetFile.getURI()); 737 738 return result; 739 } 740 741 /** 742 * Copy rights from one context to another one 743 * @param sourceContext the source context 744 * @param targetContext the target context 745 * @param file the source of the file to copy 746 */ 747 public void copyRightsRecursively(String sourceContext, String targetContext, TraversableSource file) 748 { 749 copyRights(sourceContext, targetContext); 750 if (file.isCollection()) 751 { 752 try 753 { 754 for (TraversableSource child : (Collection<TraversableSource>) file.getChildren()) 755 { 756 copyRightsRecursively(sourceContext + "/" + child.getName(), targetContext + "/" + child.getName(), child); 757 } 758 } 759 catch (SourceException e) 760 { 761 throw new RuntimeException("Cannot list child elements of " + file.getURI(), e); 762 } 763 } 764 } 765 766 /** 767 * Copy rights from one context to another one 768 * @param context the context 769 * @param file the source of the file to copy 770 */ 771 public void deleteRightsRecursively(String context, TraversableSource file) 772 { 773 deleteRights(context); 774 if (file.isCollection()) 775 { 776 try 777 { 778 for (TraversableSource child : (Collection<TraversableSource>) file.getChildren()) 779 { 780 deleteRightsRecursively(context + "/" + child.getName(), child); 781 } 782 } 783 catch (SourceException e) 784 { 785 throw new RuntimeException("Cannot list child elements of " + file.getURI(), e); 786 } 787 } 788 } 789 790 /** 791 * Get the author of extraction 792 * @param extractionPath the path of the extraction 793 * @return the author 794 */ 795 public UserIdentity getAuthor(FileSource extractionPath) 796 { 797 return _getExtractionAuthorCache().get(extractionPath, path -> _getUserIdentityByExtractionFile(path)); 798 799 } 800 801 private UserIdentity _getUserIdentityByExtractionFile(FileSource extractionPath) 802 { 803 try 804 { 805 Extraction extraction = _definitionReader.readExtractionDefinitionFile(extractionPath.getFile()); 806 return extraction.getAuthor(); 807 } 808 catch (Exception e) 809 { 810 throw new RuntimeException("Cannot read extraction " + extractionPath, e); 811 } 812 } 813 814 private Cache<FileSource, UserIdentity> _getExtractionAuthorCache() 815 { 816 return this._cacheManager.get(EXTRACTION_AUTHOR_CACHE); 817 } 818 819 /** 820 * Remove the last separator from the uri if it has any 821 * @param uri the uri 822 * @return the uri without any ending separator 823 */ 824 public static String trimLastFileSeparator(String uri) 825 { 826 return Strings.CS.endsWith(uri, "/") ? StringUtils.substringBeforeLast(uri, "/") : uri; 827 } 828 829 /** 830 * Get the path of all children that match the provided value. 831 * @param path the path to the extraction to consider as root 832 * @param value the value 833 * @return the list of path 834 */ 835 @Callable(rights = {"Runtime_Rights_Rights_Handle", "Extraction_Rights_ExecuteExtraction"}) // assignment for the tree in assignment tool 836 public List<String> getFilteredPath(String path, String value) 837 { 838 try 839 { 840 TraversableSource currentSrc = (TraversableSource) _sourceResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR + (path.length() > 0 ? "/" + path : "")); 841 842 List<String> result = _fileHelper.filterSources(currentSrc, value); 843 return result.stream() 844 .map(this::_toRelativePath) 845 .toList(); 846 } 847 catch (IOException e) 848 { 849 getLogger().error("Failed to filter extraction definition at path '" + path + "'", e); 850 return List.of(); 851 } 852 } 853 854 private String _toRelativePath(String absoluteURI) 855 { 856 // the root URI has a trailing slash or not depending on the existence of the folder at start time 857 // always remove the trailing slash so that we have a consistent behavior 858 return StringUtils.substringAfter(trimLastFileSeparator(absoluteURI), trimLastFileSeparator(_root.getURI())); 859 } 860}