001/*
002 *  Copyright 2017 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.workspaces.cmis;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.math.BigInteger;
021import java.nio.charset.StandardCharsets;
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Set;
025
026import javax.servlet.http.HttpServletRequest;
027
028import org.apache.chemistry.opencmis.commons.data.Acl;
029import org.apache.chemistry.opencmis.commons.data.ContentStream;
030import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
031import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
032import org.apache.chemistry.opencmis.commons.data.ObjectData;
033import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList;
034import org.apache.chemistry.opencmis.commons.data.ObjectParentData;
035import org.apache.chemistry.opencmis.commons.data.Properties;
036import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
037import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
038import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
039import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
040import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
041import org.apache.chemistry.opencmis.commons.enums.VersioningState;
042import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
043import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
044import org.apache.chemistry.opencmis.commons.impl.server.AbstractCmisService;
045import org.apache.chemistry.opencmis.commons.server.CallContext;
046import org.apache.chemistry.opencmis.commons.spi.Holder;
047import org.apache.commons.io.IOUtils;
048import org.apache.excalibur.source.Source;
049
050import org.ametys.core.user.UserIdentity;
051import org.ametys.core.util.URIUtils;
052import org.ametys.plugins.repository.AmetysObject;
053import org.ametys.plugins.repository.AmetysObjectIterable;
054import org.ametys.plugins.workspaces.project.objects.Project;
055
056/**
057 * AbstractCmisService implementation
058 */
059public class CmisServiceImpl extends AbstractCmisService
060{
061    private CmisServiceFactory _factory;
062    private CallContext _context;
063    private CmisRepository _repository;
064    private boolean _isAuthenticated;
065    /**
066     * CmisServiceImpl implementation
067     * @param context the call context
068     * @param factory the factory used to build this
069     */
070    public CmisServiceImpl(CallContext context, CmisServiceFactory factory)
071    {
072        this._factory = factory;
073        this._context = context;
074        this._isAuthenticated = false;
075        this._repository = new CmisRepository();
076    }
077    
078    private Project authenticateAndGetProject(String encodedProjectId)
079    {
080        String decodedProjectId = URIUtils.decode(URIUtils.decode(encodedProjectId));
081        Project project = this.getProject(decodedProjectId, _factory);
082        UserIdentity user = this.authenticate(getCallContext(), project.getName(), _factory);
083        Set<String> grantedSites = _factory.getSiteManager().getGrantedSites(user);
084        
085        if (user != null && grantedSites.contains(project.getName()) && isDocumentModuleActivated(project))
086        {
087            return project;
088        }
089        
090        throw new CmisPermissionDeniedException("Authentication process failure");
091    }
092    
093    @Override
094    public List<RepositoryInfo> getRepositoryInfos(ExtensionsData extension)
095    {
096        List<RepositoryInfo> result = new ArrayList<>();
097
098        AmetysObjectIterable<Project> projects = _factory.getProjectManager().getProjects();
099        
100        this.authenticate(getCallContext(), null, _factory);
101        UserIdentity user = _factory.getCurrentUserProvider().getUser();
102        
103        Set<String> grantedSites = _factory.getSiteManager().getGrantedSites(user);
104        for (Project project : projects)
105        {
106            if (user != null && grantedSites.contains(project.getName()) && isDocumentModuleActivated(project))
107            {
108                result.add(_repository.getRepositoryInfo(getCallContext(), project, _factory));
109            }
110        }
111        return result;
112    }
113
114    @Override
115    public TypeDefinitionList getTypeChildren(String repositoryId, String typeId, Boolean includePropertyDefinitions, BigInteger maxItems, BigInteger skipCount,
116            ExtensionsData extension)
117    {
118        authenticateAndGetProject(repositoryId);
119        return _repository.getTypeChildren(getCallContext(), typeId, includePropertyDefinitions, maxItems, skipCount, _factory);
120    }
121
122    @Override
123    public TypeDefinition getTypeDefinition(String repositoryId, String typeId, ExtensionsData extension)
124    {
125        authenticateAndGetProject(repositoryId);
126        return _repository.getTypeDefinition(getCallContext(), typeId, _factory);
127    }
128
129    @Override
130    public ObjectInFolderList getChildren(String repositoryId, String folderId, String filter, String orderBy, Boolean includeAllowableActions,
131            IncludeRelationships includeRelationships, String renditionFilter, Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension)
132    {
133        Project project = authenticateAndGetProject(repositoryId);
134        String decodedFolderId = URIUtils.decode(URIUtils.decode(folderId));
135        return _repository.getChildren(getCallContext(), decodedFolderId, project, renditionFilter, includeAllowableActions, includePathSegment, maxItems, skipCount, this, _factory);
136    }
137
138    @Override
139    public List<ObjectParentData> getObjectParents(String repositoryId, String objectId, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
140            String renditionFilter, Boolean includeRelativePathSegment, ExtensionsData extension)
141    {
142        Project project = authenticateAndGetProject(repositoryId);
143        String decodedObjectId = URIUtils.decode(URIUtils.decode(objectId));
144        return _repository.getObjectParents(getCallContext(), decodedObjectId, project, renditionFilter, includeAllowableActions, includeRelativePathSegment, this, _factory);
145    }
146
147    @Override
148    public ObjectData getObject(String repositoryId, String objectId, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
149            String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension)
150    {
151        Project project = authenticateAndGetProject(repositoryId);
152        String decodedObjectId = URIUtils.decode(URIUtils.decode(objectId));
153        return _repository.getObject(getCallContext(), project, decodedObjectId, null, renditionFilter, includeAllowableActions, includeAcl, this, _factory);
154    }
155    
156    @Override
157    public ContentStream getContentStream(String repositoryId, String objectId, String streamId, BigInteger offset,
158            BigInteger length, ExtensionsData extension)
159    {
160        authenticateAndGetProject(repositoryId);
161        String decodedObjectId = URIUtils.decode(URIUtils.decode(objectId));
162        return _repository.getContentStream(getCallContext(), decodedObjectId, offset, length, _factory);
163    }
164    
165    @Override
166    public ObjectData getObjectByPath(String repositoryId, String path, String filter, Boolean includeAllowableActions,
167            IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
168            Boolean includeAcl, ExtensionsData extension)
169    {
170        Project project = authenticateAndGetProject(repositoryId);
171        return _repository.getObjectByPath(getCallContext(), project, path, renditionFilter, includeAllowableActions, includeAcl, this, _factory);
172    }
173    
174    @Override
175    public String createFolder(String repositoryId, Properties properties, String folderId, List<String> policies,
176            Acl addAces, Acl removeAces, ExtensionsData extension)
177    {
178        Project project = authenticateAndGetProject(repositoryId);
179        String decodedFolderId = URIUtils.decode(URIUtils.decode(folderId));
180        return _repository.createFolder(getCallContext(), properties, project, decodedFolderId, _factory);
181    }
182    
183    @Override
184    public String createDocument(String repositoryId, Properties properties, String folderId,
185            ContentStream contentStream, VersioningState versioningState, List<String> policies, Acl addAces,
186            Acl removeAces, ExtensionsData extension)
187    {
188        Project project = authenticateAndGetProject(repositoryId);
189        String decodedFolderId = URIUtils.decode(URIUtils.decode(folderId));
190        return _repository.createDocument(getCallContext(), properties, project, decodedFolderId, contentStream, versioningState, _factory);
191    }
192
193    @Override
194    public void deleteObjectOrCancelCheckOut(String repositoryId, String objectId, Boolean allVersions,
195            ExtensionsData extension)
196    {
197        authenticateAndGetProject(repositoryId);
198        String decodedObjectId = URIUtils.decode(URIUtils.decode(objectId));
199        _repository.deleteObject(getCallContext(), decodedObjectId, _factory);
200    }
201    
202    @Override
203    public FailedToDeleteData deleteTree(String repositoryId, String folderId, Boolean allVersions,
204            UnfileObject unfileObjects, Boolean continueOnFailure, ExtensionsData extension)
205    {
206        Project project = authenticateAndGetProject(repositoryId);
207        String decodedFolderId = URIUtils.decode(URIUtils.decode(folderId));
208        return _repository.deleteTree(getCallContext(), project, decodedFolderId, _factory);
209    }
210    
211    @Override
212    public ObjectData getFolderParent(String repositoryId, String folderId, String filter, ExtensionsData extension)
213    {
214        Project project = authenticateAndGetProject(repositoryId);
215        String decodedFolderId = URIUtils.decode(URIUtils.decode(folderId));
216        return _repository.getFolderParent(getCallContext(), project, decodedFolderId, filter, this, _factory);
217    }
218    
219    @Override
220    public void updateProperties(String repositoryId, Holder<String> objectId, Holder<String> changeToken,
221            Properties properties, ExtensionsData extension)
222    {
223        Project project = authenticateAndGetProject(repositoryId);
224        _repository.updateProperties(getCallContext(), project, objectId, properties, this, _factory);
225    }
226    
227    @Override
228    public void setContentStream(String repositoryId, Holder<String> objectId, Boolean overwriteFlag,
229            Holder<String> changeToken, ContentStream contentStream, ExtensionsData extension)
230    {
231        Project project = authenticateAndGetProject(repositoryId);
232        _repository.changeContentStream(getCallContext(), project, objectId, overwriteFlag, contentStream, false, _factory);
233    }
234    
235    @Override
236    public void moveObject(String repositoryId, Holder<String> objectId, String targetFolderId, String sourceFolderId,
237            ExtensionsData extension)
238    {
239        Project project = authenticateAndGetProject(repositoryId);
240        _repository.moveObject(getCallContext(), project, objectId, targetFolderId, this, _factory);
241    }
242    
243    private UserIdentity authenticate(CallContext context, String projectName, CmisServiceFactory factory)
244    {
245        if (this._isAuthenticated)
246        {
247            return _factory.getCurrentUserProvider().getUser();
248        }
249        
250        if (context == null)
251        {
252            throw new CmisPermissionDeniedException("No user context!");
253        }
254        
255        String userName = context.getUsername();
256        String password = context.getPassword();
257        
258        Source source = null;
259        try
260        {
261            if (projectName == null)
262            {
263                source = factory.getSourceResolver().resolveURI("cocoon:/_authenticate?token=" + URIUtils.encodeParameter(password));
264            }
265            else
266            {
267                HttpServletRequest request = (HttpServletRequest) context.get(CallContext.HTTP_SERVLET_REQUEST);
268                request.setAttribute("projectName", projectName);
269                source = factory.getSourceResolver().resolveURI("cocoon:/_authenticate/" + projectName + "?token=" + URIUtils.encodeParameter(password));
270            }
271            try (InputStream is = source.getInputStream())
272            {
273                IOUtils.toString(is, StandardCharsets.UTF_8);
274            }
275        }
276        catch (IOException e)
277        {
278            throw new CmisPermissionDeniedException("Authentication process failure", e);
279        }
280        finally 
281        {
282            factory.getSourceResolver().release(source);
283        }
284        
285        UserIdentity user = _factory.getCurrentUserProvider().getUser();
286        if (user == null || !user.getLogin().equals(userName))
287        {
288            throw new CmisPermissionDeniedException("Authentication process failure");
289        }
290        this._isAuthenticated = true;
291        return user;
292    }
293    
294    private Project getProject(String projectId, CmisServiceFactory factory)
295    {
296        AmetysObject repositoryAmetysObject = factory.getResolver().resolveById(projectId);
297        if (repositoryAmetysObject instanceof Project)
298        {
299            return (Project) repositoryAmetysObject;
300        }
301        else
302        {
303            throw new CmisInvalidArgumentException("Project is not valid.");
304        }
305    }
306    
307    /**
308     * Sets the call context.
309     * 
310     * This method should only be called by the service factory.
311     * @param context context
312     */
313    public void setCallContext(CallContext context)
314    {
315        this._context = context;
316    }
317
318    /**
319     * Gets the call context.
320     * @return CallContext
321     */
322    public CallContext getCallContext()
323    {
324        return _context;
325    }
326    
327    private boolean isDocumentModuleActivated(Project project)
328    {
329        return _factory.getProjectManager().isModuleActivated(project, _factory.getDocumentModule().getId());
330    }
331
332}