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}