Merged FILE-FOLDER-API (5.2.0) to HEAD (5.2)

127312 jkaabimofrad: SFS-577, SFS-581: Fixed the shared-link no-auth APIs issue with multi-tenancy.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@127316 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Jamal Kaabi-Mofrad
2016-05-23 10:11:10 +00:00
parent 1fcd057a28
commit 0c017176a3
7 changed files with 239 additions and 16 deletions

View File

@@ -1116,6 +1116,7 @@
<property name="thumbnailService" ref="ThumbnailService" /> <property name="thumbnailService" ref="ThumbnailService" />
<property name="scriptThumbnailService" ref="thumbnailServiceScript" /> <property name="scriptThumbnailService" ref="thumbnailServiceScript" />
<property name="serviceRegistry" ref="ServiceRegistry" /> <property name="serviceRegistry" ref="ServiceRegistry" />
<property name="tenantService" ref="tenantService"/>
</bean> </bean>
<bean id="Renditions" class="org.springframework.aop.framework.ProxyFactoryBean"> <bean id="Renditions" class="org.springframework.aop.framework.ProxyFactoryBean">

View File

@@ -157,6 +157,16 @@ public interface Nodes
*/ */
BinaryResource getContent(String fileNodeId, Parameters parameters, boolean recordActivity); BinaryResource getContent(String fileNodeId, Parameters parameters, boolean recordActivity);
/**
* Download file content.
*
* @param nodeRef the content nodeRef
* @param parameters
* @param recordActivity true, if an activity post is required.
* @return
*/
BinaryResource getContent(NodeRef nodeRef, Parameters parameters, boolean recordActivity);
/** /**
* Uploads file content (updates existing node with new content). * Uploads file content (updates existing node with new content).
* *
@@ -215,7 +225,7 @@ public interface Nodes
String PATH_SHARED = "-shared-"; String PATH_SHARED = "-shared-";
String OP_CREATE = "create"; String OP_CREATE = "create";
String OP_DELETE= "delete"; String OP_DELETE = "delete";
String OP_UPDATE = "update"; String OP_UPDATE = "update";
String PARAM_RELATIVE_PATH = "relativePath"; String PARAM_RELATIVE_PATH = "relativePath";
@@ -244,6 +254,4 @@ public interface Nodes
String PARAM_VERSION_MAJOR = "majorVersion"; // true if major, false if minor String PARAM_VERSION_MAJOR = "majorVersion"; // true if major, false if minor
String PARAM_VERSION_COMMENT = "comment"; String PARAM_VERSION_COMMENT = "comment";
String PARAM_RENDITIONS = "renditions";
} }

View File

@@ -23,6 +23,7 @@ import org.alfresco.rest.api.model.Rendition;
import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.content.BinaryResource;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.cmr.repository.NodeRef;
/** /**
* Renditions API * Renditions API
@@ -31,6 +32,8 @@ import org.alfresco.rest.framework.resource.parameters.Parameters;
*/ */
public interface Renditions public interface Renditions
{ {
String PARAM_STATUS = "status";
/** /**
* Lists all available renditions includes those that have been created and those that are yet to be created. * Lists all available renditions includes those that have been created and those that are yet to be created.
* *
@@ -70,5 +73,13 @@ public interface Renditions
*/ */
BinaryResource getContent(String nodeId, String renditionId, Parameters parameters); BinaryResource getContent(String nodeId, String renditionId, Parameters parameters);
String PARAM_STATUS = "status"; /**
* Downloads rendition.
*
* @param sourceNodeRef the source nodeRef
* @param renditionId the rendition id
* @param parameters the {@link Parameters} object to get the parameters passed into the request
* @return the rendition stream
*/
BinaryResource getContent(NodeRef sourceNodeRef, String renditionId, Parameters parameters);
} }

View File

@@ -1950,15 +1950,20 @@ public class NodesImpl implements Nodes
public BinaryResource getContent(String fileNodeId, Parameters parameters, boolean recordActivity) public BinaryResource getContent(String fileNodeId, Parameters parameters, boolean recordActivity)
{ {
final NodeRef nodeRef = validateNode(fileNodeId); final NodeRef nodeRef = validateNode(fileNodeId);
return getContent(nodeRef, parameters, recordActivity);
}
if (! nodeMatches(nodeRef, Collections.singleton(ContentModel.TYPE_CONTENT), null, false)) @Override
public BinaryResource getContent(NodeRef nodeRef, Parameters parameters, boolean recordActivity)
{
if (!nodeMatches(nodeRef, Collections.singleton(ContentModel.TYPE_CONTENT), null, false))
{ {
throw new InvalidArgumentException("NodeId of content is expected: "+nodeRef.getId()); throw new InvalidArgumentException("NodeId of content is expected: " + nodeRef.getId());
} }
Map<QName, Serializable> nodeProps = nodeService.getProperties(nodeRef); Map<QName, Serializable> nodeProps = nodeService.getProperties(nodeRef);
ContentData cd = (ContentData)nodeProps.get(ContentModel.PROP_CONTENT); ContentData cd = (ContentData) nodeProps.get(ContentModel.PROP_CONTENT);
String name = (String)nodeProps.get(ContentModel.PROP_NAME); String name = (String) nodeProps.get(ContentModel.PROP_NAME);
org.alfresco.rest.framework.resource.content.ContentInfo ci = null; org.alfresco.rest.framework.resource.content.ContentInfo ci = null;
String mimeType = null; String mimeType = null;
@@ -1982,7 +1987,7 @@ public class NodesImpl implements Nodes
} }
else else
{ {
logger.warn("Ignored attachment=false for "+fileNodeId+" since "+mimeType+" is not in the whitelist for non-attach content types"); logger.warn("Ignored attachment=false for "+nodeRef.getId()+" since "+mimeType+" is not in the whitelist for non-attach content types");
} }
} }
} }
@@ -1990,7 +1995,7 @@ public class NodesImpl implements Nodes
if (recordActivity) if (recordActivity)
{ {
final ActivityInfo activityInfo = getActivityInfo(getParentNodeRef(nodeRef), nodeRef); final ActivityInfo activityInfo = getActivityInfo(getParentNodeRef(nodeRef), nodeRef);
postActivity(Activity_Type.DOWNLOADED, activityInfo, true); postActivity(Activity_Type.DOWNLOADED, activityInfo, true);
} }

View File

@@ -230,15 +230,13 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean
throw new InvalidNodeRefException(nodeRef); throw new InvalidNodeRefException(nodeRef);
} }
String nodeId = nodeRef.getId();
if (renditionId != null) if (renditionId != null)
{ {
return renditions.getContent(nodeId, renditionId, parameters); return renditions.getContent(nodeRef, renditionId, parameters);
} }
else else
{ {
return nodes.getContent(nodeId, parameters, false); return nodes.getContent(nodeRef, parameters, false);
} }
} }
}, networkTenantDomain); }, networkTenantDomain);

View File

@@ -21,6 +21,7 @@ package org.alfresco.rest.api.impl;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.query.PagingResults; import org.alfresco.query.PagingResults;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.thumbnail.ThumbnailDefinition; import org.alfresco.repo.thumbnail.ThumbnailDefinition;
import org.alfresco.repo.thumbnail.ThumbnailHelper; import org.alfresco.repo.thumbnail.ThumbnailHelper;
import org.alfresco.repo.thumbnail.ThumbnailRegistry; import org.alfresco.repo.thumbnail.ThumbnailRegistry;
@@ -96,6 +97,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
private NamespaceService namespaceService; private NamespaceService namespaceService;
private ServiceRegistry serviceRegistry; private ServiceRegistry serviceRegistry;
private ResourceLoader resourceLoader; private ResourceLoader resourceLoader;
private TenantService tenantService;
public void setNodes(Nodes nodes) public void setNodes(Nodes nodes)
{ {
@@ -123,12 +125,18 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
this.resourceLoader = resourceLoader; this.resourceLoader = resourceLoader;
} }
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
public void init() public void init()
{ {
PropertyCheck.mandatory(this, "nodes", nodes); PropertyCheck.mandatory(this, "nodes", nodes);
PropertyCheck.mandatory(this, "thumbnailService", thumbnailService); PropertyCheck.mandatory(this, "thumbnailService", thumbnailService);
PropertyCheck.mandatory(this, "scriptThumbnailService", scriptThumbnailService); PropertyCheck.mandatory(this, "scriptThumbnailService", scriptThumbnailService);
PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry); PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry);
PropertyCheck.mandatory(this, "tenantService", tenantService);
this.nodeService = serviceRegistry.getNodeService(); this.nodeService = serviceRegistry.getNodeService();
this.actionService = serviceRegistry.getActionService(); this.actionService = serviceRegistry.getActionService();
@@ -288,7 +296,13 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
public BinaryResource getContent(String nodeId, String renditionId, Parameters parameters) public BinaryResource getContent(String nodeId, String renditionId, Parameters parameters)
{ {
final NodeRef sourceNodeRef = validateSourceNode(nodeId); final NodeRef sourceNodeRef = validateSourceNode(nodeId);
final NodeRef renditionNodeRef = getRenditionByName(sourceNodeRef, renditionId, parameters); return getContent(sourceNodeRef, renditionId, parameters);
}
@Override
public BinaryResource getContent(NodeRef sourceNodeRef, String renditionId, Parameters parameters)
{
NodeRef renditionNodeRef = getRenditionByName(sourceNodeRef, renditionId, parameters);
// By default set attachment header (with rendition Id) unless attachment=false // By default set attachment header (with rendition Id) unless attachment=false
boolean attach = true; boolean attach = true;
@@ -382,7 +396,8 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
{ {
return null; return null;
} }
return nodeRefRendition.getChildRef();
return tenantService.getName(nodeRef, nodeRefRendition.getChildRef());
} }
protected Rendition toApiRendition(NodeRef renditionNodeRef) protected Rendition toApiRendition(NodeRef renditionNodeRef)

View File

@@ -27,6 +27,9 @@ import org.alfresco.rest.api.impl.QuickShareLinksImpl;
import org.alfresco.rest.api.model.QuickShareLink; import org.alfresco.rest.api.model.QuickShareLink;
import org.alfresco.rest.api.nodes.NodesEntityResource; import org.alfresco.rest.api.nodes.NodesEntityResource;
import org.alfresco.rest.api.quicksharelinks.QuickShareLinkEntityResource; import org.alfresco.rest.api.quicksharelinks.QuickShareLinkEntityResource;
import org.alfresco.rest.api.tests.RepoService.TestNetwork;
import org.alfresco.rest.api.tests.RepoService.TestPerson;
import org.alfresco.rest.api.tests.RepoService.TestSite;
import org.alfresco.rest.api.tests.client.HttpResponse; import org.alfresco.rest.api.tests.client.HttpResponse;
import org.alfresco.rest.api.tests.client.PublicApiClient.Paging; import org.alfresco.rest.api.tests.client.PublicApiClient.Paging;
import org.alfresco.rest.api.tests.client.data.Document; import org.alfresco.rest.api.tests.client.data.Document;
@@ -35,8 +38,10 @@ import org.alfresco.rest.api.tests.client.data.QuickShareLinkEmailRequest;
import org.alfresco.rest.api.tests.client.data.Rendition; import org.alfresco.rest.api.tests.client.data.Rendition;
import org.alfresco.rest.api.tests.util.MultiPartBuilder; import org.alfresco.rest.api.tests.util.MultiPartBuilder;
import org.alfresco.rest.api.tests.util.RestApiUtil; import org.alfresco.rest.api.tests.util.RestApiUtil;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.site.SiteVisibility;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -68,6 +73,19 @@ public class SharedLinkApiTest extends AbstractBaseApiTest
{ {
private static final String URL_SHARED_LINKS = "shared-links"; private static final String URL_SHARED_LINKS = "shared-links";
TestNetwork networkOne;
/**
* User one from network one
*/
private TestPerson userOneN1;
/**
* User two from network one
*/
private TestPerson userTwoN1;
private TestSite userOneN1Site;
private String user1; private String user1;
private String user2; private String user2;
private List<String> users = new ArrayList<>(); private List<String> users = new ArrayList<>();
@@ -91,6 +109,12 @@ public class SharedLinkApiTest extends AbstractBaseApiTest
// so the tests for the specific network would work. // so the tests for the specific network would work.
users.add(user1); users.add(user1);
users.add(user2); users.add(user2);
networkOne = getTestFixture().getRandomNetwork();
userOneN1 = networkOne.createUser();
userTwoN1 = networkOne.createUser();
userOneN1Site = createSite(networkOne, userOneN1, SiteVisibility.PRIVATE);
} }
@After @After
@@ -131,6 +155,7 @@ public class SharedLinkApiTest extends AbstractBaseApiTest
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>} * {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>}
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/content} * {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/content}
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/renditions} * {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/renditions}
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/renditions/<renditionId>/content}
* *
*/ */
@Test @Test
@@ -661,6 +686,166 @@ public class SharedLinkApiTest extends AbstractBaseApiTest
} }
} }
/**
* Tests shared links to file (content) in a multi-tenant system.
*
* <p>POST:</p>
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links}
*
* <p>DELETE:</p>
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>}
*
* <p>GET:</p>
* The following do not require authentication
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>}
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/content}
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/renditions}
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/renditions/<renditionId>/content}
*
*/
@Test
public void testSharedLinkCreateGetDelete_MultiTenant() throws Exception
{
// As userOneN1
AuthenticationUtil.setFullyAuthenticatedUser(userOneN1.getId());
NodeRef docLibNodeRef = userOneN1Site.getContainerNodeRef(("documentLibrary"));
String docLibNodeId = docLibNodeRef.getId();
String folderName = "folder" + System.currentTimeMillis() + "_1";
String folderId = createFolder(userOneN1.getId(), docLibNodeId, folderName, null).getId();
// create doc d1 - pdf
String fileName1 = "quick" + RUNID + "_1.pdf";
File file1 = getResourceFile("quick.pdf");
byte[] file1_originalBytes = Files.readAllBytes(Paths.get(file1.getAbsolutePath()));
String file1_MimeType = MimetypeMap.MIMETYPE_PDF;
MultiPartBuilder.MultiPartRequest reqBody = MultiPartBuilder.create()
.setFileData(new MultiPartBuilder.FileData(fileName1, file1, file1_MimeType))
.build();
HttpResponse response = post(getNodeChildrenUrl(folderId), userOneN1.getId(), reqBody.getBody(), null, reqBody.getContentType(), 201);
Document doc1 = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
String d1Id = doc1.getId();
assertNotNull(d1Id);
// create shared link to document 1
Map<String, String> body = new HashMap<>();
body.put("nodeId", d1Id);
response = post(URL_SHARED_LINKS, userOneN1.getId(), toJsonAsStringNonNull(body), 201);
QuickShareLink resp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), QuickShareLink.class);
String shared1Id = resp.getId();
assertNotNull(shared1Id);
assertEquals(d1Id, resp.getNodeId());
assertEquals(fileName1, resp.getName());
assertEquals(file1_MimeType, resp.getContent().getMimeType());
assertEquals(userOneN1.getId(), resp.getSharedByUser().getId());
// allowable operations not included - no params
response = getSingle(QuickShareLinkEntityResource.class, userOneN1.getId(), shared1Id, null, 200);
resp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), QuickShareLink.class);
assertNull(resp.getAllowableOperations());
// unauth access to get shared link info
Map<String, String> params = Collections.singletonMap("include", "allowableOperations"); // note: this will be ignore for unauth access
response = getSingle(QuickShareLinkEntityResource.class, null, shared1Id, params, 200);
resp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), QuickShareLink.class);
assertEquals(shared1Id, resp.getId());
assertEquals(fileName1, resp.getName());
assertEquals(d1Id, resp.getNodeId());
assertNull(resp.getAllowableOperations()); // include is ignored
// unauth access to file 1 content (via shared link)
response = getSingle(QuickShareLinkEntityResource.class, null, shared1Id + "/content", null, 200);
assertArrayEquals(file1_originalBytes, response.getResponseAsBytes());
Map<String, String> responseHeaders = response.getHeaders();
assertNotNull(responseHeaders);
assertEquals(file1_MimeType + ";charset=UTF-8", responseHeaders.get("Content-Type"));
assertNotNull(responseHeaders.get("Expires"));
assertEquals("attachment; filename=\"" + fileName1 + "\"; filename*=UTF-8''" + fileName1 + "", responseHeaders.get("Content-Disposition"));
String lastModifiedHeader = responseHeaders.get(LAST_MODIFIED_HEADER);
assertNotNull(lastModifiedHeader);
// Test 304 response
Map<String, String> headers = Collections.singletonMap(IF_MODIFIED_SINCE_HEADER, lastModifiedHeader);
getSingle(URL_SHARED_LINKS, null, shared1Id + "/content", null, headers, 304);
// unauth access to file 1 content (via shared link) - without Content-Disposition header (attachment=false)
params = new HashMap<>();
params.put("attachment", "false");
response = getSingle(QuickShareLinkEntityResource.class, null, shared1Id + "/content", params, 200);
assertArrayEquals(file1_originalBytes, response.getResponseAsBytes());
responseHeaders = response.getHeaders();
assertNotNull(responseHeaders);
assertEquals(file1_MimeType + ";charset=UTF-8", responseHeaders.get("Content-Type"));
assertNotNull(responseHeaders.get(LAST_MODIFIED_HEADER));
assertNotNull(responseHeaders.get("Expires"));
assertNull(responseHeaders.get("Content-Disposition"));
// -ve shared link rendition tests
{
// -ve test - try to get non-existent rendition content
getSingle(QuickShareLinkEntityResource.class, null, shared1Id + "/renditions/doclib/content", null, 404);
// -ve test - try to get unregistered rendition content
getSingle(QuickShareLinkEntityResource.class, null, shared1Id + "/renditions/dummy/content", null, 404);
}
// unauth access to get shared link renditions info (available => CREATED renditions only)
response = getAll(URL_SHARED_LINKS + "/" + shared1Id + "/renditions", null, null, 200);
List<Rendition> renditions = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Rendition.class);
assertEquals(0, renditions.size());
// create rendition of pdf doc - note: for some reason create rendition of txt doc fail on build m/c (TBC) ?
Rendition rendition = createAndGetRendition(userOneN1.getId(), d1Id, "doclib");
assertNotNull(rendition);
assertEquals(Rendition.RenditionStatus.CREATED, rendition.getStatus());
// unauth access to get shared link renditions info (available => CREATED renditions only)
response = getAll(URL_SHARED_LINKS + "/" + shared1Id + "/renditions", null, null, 200);
renditions = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Rendition.class);
assertEquals(1, renditions.size());
assertEquals(Rendition.RenditionStatus.CREATED, renditions.get(0).getStatus());
assertEquals("doclib", renditions.get(0).getId());
// unauth access to get shared link file rendition content
response = getSingle(QuickShareLinkEntityResource.class, null, shared1Id + "/renditions/doclib/content", null, 200);
assertTrue(response.getResponseAsBytes().length > 0);
responseHeaders = response.getHeaders();
assertNotNull(responseHeaders);
assertEquals(MimetypeMap.MIMETYPE_IMAGE_PNG + ";charset=UTF-8", responseHeaders.get("Content-Type"));
assertNotNull(responseHeaders.get(LAST_MODIFIED_HEADER));
assertNotNull(responseHeaders.get("Expires"));
String docName = "doclib";
assertEquals("attachment; filename=\"" + docName + "\"; filename*=UTF-8''" + docName + "", responseHeaders.get("Content-Disposition"));
// unauth access to get shared link file rendition content - without Content-Disposition header (attachment=false)
params = new HashMap<>();
params.put("attachment", "false");
response = getSingle(QuickShareLinkEntityResource.class, null, shared1Id + "/renditions/doclib/content", params, 200);
assertTrue(response.getResponseAsBytes().length > 0);
responseHeaders = response.getHeaders();
assertNotNull(responseHeaders);
assertEquals(MimetypeMap.MIMETYPE_IMAGE_PNG + ";charset=UTF-8", responseHeaders.get("Content-Type"));
assertNotNull(responseHeaders.get("Expires"));
assertNull(responseHeaders.get("Content-Disposition"));
lastModifiedHeader = responseHeaders.get(LAST_MODIFIED_HEADER);
assertNotNull(lastModifiedHeader);
// Test 304 response
headers = Collections.singletonMap(IF_MODIFIED_SINCE_HEADER, lastModifiedHeader);
getSingle(URL_SHARED_LINKS, null, shared1Id + "/renditions/doclib/content", null, headers, 304);
// -ve test - userTwoN1 cannot delete shared link
delete(URL_SHARED_LINKS, userTwoN1.getId(), shared1Id, 403);
// -ve test - unauthenticated
delete(URL_SHARED_LINKS, null, shared1Id, 401);
// delete shared link
delete(URL_SHARED_LINKS, userOneN1.getId(), shared1Id, 204);
}
@Override @Override
public String getScope() public String getScope()
{ {