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}