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