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