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