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}