001/*
002 *  Copyright 2024 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.rights;
017
018import java.io.IOException;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023import java.util.stream.Stream;
024
025import org.apache.avalon.framework.activity.Initializable;
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.collections.MapUtils;
030import org.apache.excalibur.source.SourceException;
031import org.apache.excalibur.source.SourceResolver;
032import org.apache.excalibur.source.impl.FileSource;
033
034import org.ametys.cms.contenttype.ContentTypesHelper;
035import org.ametys.cms.repository.Content;
036import org.ametys.core.group.GroupIdentity;
037import org.ametys.core.right.AccessController;
038import org.ametys.core.right.AccessExplanation;
039import org.ametys.core.right.RightsException;
040import org.ametys.core.user.UserIdentity;
041import org.ametys.plugins.core.impl.right.AbstractRightBasedAccessController;
042import org.ametys.plugins.extraction.ExtractionConstants;
043import org.ametys.plugins.extraction.execution.Extraction;
044import org.ametys.plugins.extraction.execution.ExtractionDAO;
045import org.ametys.runtime.i18n.I18nizableText;
046
047
048/**
049 * {@link AccessController} to allow read access and handle for author of a extraction file
050 *
051 */
052public class ExtractionAuthorAccessController extends AbstractRightBasedAccessController implements Serviceable, Initializable
053{
054    private static final List<String> __AUTHOR_RIGHTS = List.of(ExtractionConstants.MODIFY_EXTRACTION_RIGHT_ID, "Workflow_Rights_Edition_Online");
055    
056    private SourceResolver _srcResolver;
057    private String _rootPath;
058    private ExtractionDAO _extractionDAO;
059    private ContentTypesHelper _contentTypesHelper;
060    
061    public void service(ServiceManager manager) throws ServiceException
062    {
063        _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
064        _extractionDAO = (ExtractionDAO) manager.lookup(ExtractionDAO.ROLE);
065        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
066    }
067    
068    public void initialize() throws Exception
069    {
070        FileSource rootDir = (FileSource) _srcResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR);
071        if (rootDir.getFile().exists())
072        {
073            _rootPath = rootDir.getFile().getPath();
074        }
075    }
076    
077    public boolean isSupported(Object object)
078    {
079        return object instanceof Extraction
080                || object instanceof FileSource fileSource && !fileSource.isCollection() && fileSource.getFile().getPath().startsWith(_rootPath)
081                || object instanceof Content content && _contentTypesHelper.isInstanceOf(content, ExtractionConstants.DESCRIPTION_CONTENT_TYPE_ID);
082    }
083    
084    public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object)
085    {
086        if (user.equals(_getAuthor(object)))
087        {
088            return __AUTHOR_RIGHTS.contains(rightId) ? AccessResult.USER_ALLOWED : AccessResult.UNKNOWN;
089        }
090        
091        return AccessResult.UNKNOWN;
092    }
093
094    public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
095    {
096        return user.equals(_getAuthor(object)) ? AccessResult.USER_ALLOWED : AccessResult.UNKNOWN;
097    }
098
099    /**
100     * If creator, access to a list of rights
101     */
102    public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
103    {
104        Map<String, AccessResult> permissionByRight = new HashMap<>();
105        
106        if (user.equals(_getAuthor(object)))
107        {
108            for (String rightId : __AUTHOR_RIGHTS)
109            {
110                permissionByRight.put(rightId, AccessResult.USER_ALLOWED);
111            }
112        }
113        
114        return permissionByRight;
115    }
116
117    public AccessResult getPermissionForAnonymous(String rightId, Object object)
118    {
119        return AccessResult.UNKNOWN;
120    }
121
122    public AccessResult getReadAccessPermissionForAnonymous(Object object)
123    {
124        return AccessResult.UNKNOWN;
125    }
126
127    public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object)
128    {
129        return AccessResult.UNKNOWN;
130    }
131
132    public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object)
133    {
134        return AccessResult.UNKNOWN;
135    }
136
137    /**
138     * If right requested is in the list, the creator is added the list of USER_ALLOWED
139     */
140    public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object)
141    {
142        Map<UserIdentity, AccessResult> permissionByUser = new HashMap<>();
143        
144        if (__AUTHOR_RIGHTS.contains(rightId))
145        {
146            UserIdentity extractionAuthor = _getAuthor(object);
147            permissionByUser.put(extractionAuthor, AccessResult.USER_ALLOWED);
148        }
149        return permissionByUser;
150    }
151
152    public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object)
153    {
154        return MapUtils.EMPTY_MAP;
155    }
156
157    public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object)
158    {
159        return MapUtils.EMPTY_MAP;
160    }
161
162    public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object)
163    {
164        return MapUtils.EMPTY_MAP;
165    }
166
167    public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId)
168    {
169        return false;
170    }
171
172    public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups)
173    {
174        return false;
175    }
176
177    public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
178    {
179        return false;
180    }
181
182    public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
183    {
184        return false;
185    }
186
187    public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
188    {
189        return false;
190    }
191
192    public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
193    {
194        return false;
195    }
196    
197    @Override
198    public AccessExplanation getStandardAccessExplanation(AccessResult permission, Object object)
199    {
200        switch (permission)
201        {
202            case USER_ALLOWED:
203            case UNKNOWN:
204                if (object instanceof Content)
205                {
206                    return new AccessExplanation(
207                            getId(),
208                            permission,
209                            new I18nizableText("plugin.extraction", "PLUGINS_EXTRACTION_CONTENT_AUTHOR_ACCESS_CONTROLLER_" + permission.name() + "_EXPLANATION",
210                                    Map.of("title", getObjectLabel(object)))
211                            );
212                }
213                else
214                {
215                    return new AccessExplanation(
216                            getId(),
217                            permission,
218                            new I18nizableText("plugin.extraction", "PLUGINS_EXTRACTION_AUTHOR_ACCESS_CONTROLLER_" + permission.name() + "_EXPLANATION",
219                                    Map.of("title", getObjectLabel(object)))
220                            );
221                }
222            default:
223                return super.getStandardAccessExplanation(permission, object);
224        }
225    }
226    
227    private UserIdentity _getAuthor(Object object)
228    {
229        if (object instanceof Extraction extraction)
230        {
231            return extraction.getAuthor();
232        }
233        else if (object instanceof FileSource fileSource)
234        {
235            return _extractionDAO.getAuthor(fileSource);
236        }
237        else if (object instanceof Content content)
238        {
239            return content.getCreator();
240        }
241        return null;
242    }
243    
244    private String _getExtractionName(Object object)
245    {
246        if (object instanceof Extraction extraction)
247        {
248            return extraction.getFileName();
249        }
250        else if (object instanceof FileSource fileSource)
251        {
252            return fileSource.getFile().getName();
253        }
254        else if (object instanceof Content content)
255        {
256            return content.getTitle();
257        }
258        return null;
259    }
260
261    public I18nizableText getObjectLabel(Object object)
262    {
263        String extractionName = _getExtractionName(object);
264        if (extractionName != null)
265        {
266            return new I18nizableText(extractionName);
267        }
268        throw new RightsException("Unsupported context: " + object.toString());
269    }
270
271    public I18nizableText getObjectCategory(Object object)
272    {
273        return ExtractionAccessController.EXTRACTION_CONTEXT_CATEGORY;
274    }
275
276    @Override
277    protected Iterable< ? extends Object> getHandledObjects(UserIdentity identity, Set<GroupIdentity> groups)
278    {
279        // FIXME context are not merged with the ExtractionAccessController
280        // because one handles FileSource and the other handles String
281        try
282        {
283            FileSource rootDir = (FileSource) _srcResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR);
284            if (rootDir.getFile().exists())
285            {
286                Stream<FileSource> definitions = _getDefinitions(rootDir);
287                return definitions.toList();
288            }
289        }
290        catch (IOException e)
291        {
292            getLogger().warn("Failed to compute the list of extractions");
293        }
294
295        return List.of();
296    }
297
298    private Stream<FileSource> _getDefinitions(FileSource source)
299    {
300        // TODO filter author
301        if (source.isCollection())
302        {
303            try
304            {
305                return source.getChildren().stream()
306                    .filter(FileSource.class::isInstance)
307                    .flatMap(src -> _getDefinitions((FileSource) src));
308            }
309            catch (SourceException e)
310            {
311                getLogger().warn("Failed to compute the list of extractions");
312                return Stream.of();
313            }
314        }
315        else
316        {
317            return Stream.of(source);
318        }
319    }
320    
321    public ExplanationObject getExplanationObject(Object object)
322    {
323        if (object instanceof FileSource source)
324        {
325            return new ExplanationObject(
326                    // we convert the source to the right path to be able to merge with ExtractionAccessController
327                    _extractionDAO.getExtractionRightPath(source),
328                    getObjectLabel(object),
329                    getObjectCategory(object)
330                    );
331        }
332        return super.getExplanationObject(object);
333    }
334}