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