diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml
index 8265ddda86..3dfdafb932 100644
--- a/config/alfresco/public-rest-context.xml
+++ b/config/alfresco/public-rest-context.xml
@@ -1116,6 +1116,7 @@
+
diff --git a/source/java/org/alfresco/rest/api/Nodes.java b/source/java/org/alfresco/rest/api/Nodes.java
index f67b8ccb19..19a15b2cec 100644
--- a/source/java/org/alfresco/rest/api/Nodes.java
+++ b/source/java/org/alfresco/rest/api/Nodes.java
@@ -157,6 +157,16 @@ public interface Nodes
*/
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).
*
@@ -215,7 +225,7 @@ public interface Nodes
String PATH_SHARED = "-shared-";
String OP_CREATE = "create";
- String OP_DELETE= "delete";
+ String OP_DELETE = "delete";
String OP_UPDATE = "update";
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_COMMENT = "comment";
-
- String PARAM_RENDITIONS = "renditions";
}
diff --git a/source/java/org/alfresco/rest/api/Renditions.java b/source/java/org/alfresco/rest/api/Renditions.java
index 1f33e517fd..57f49d523b 100644
--- a/source/java/org/alfresco/rest/api/Renditions.java
+++ b/source/java/org/alfresco/rest/api/Renditions.java
@@ -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.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
+import org.alfresco.service.cmr.repository.NodeRef;
/**
* Renditions API
@@ -31,6 +32,8 @@ import org.alfresco.rest.framework.resource.parameters.Parameters;
*/
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.
*
@@ -70,5 +73,13 @@ public interface Renditions
*/
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);
}
diff --git a/source/java/org/alfresco/rest/api/impl/NodesImpl.java b/source/java/org/alfresco/rest/api/impl/NodesImpl.java
index 15127ae42e..af8ef3018e 100644
--- a/source/java/org/alfresco/rest/api/impl/NodesImpl.java
+++ b/source/java/org/alfresco/rest/api/impl/NodesImpl.java
@@ -1950,15 +1950,20 @@ public class NodesImpl implements Nodes
public BinaryResource getContent(String fileNodeId, Parameters parameters, boolean recordActivity)
{
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 nodeProps = nodeService.getProperties(nodeRef);
- ContentData cd = (ContentData)nodeProps.get(ContentModel.PROP_CONTENT);
- String name = (String)nodeProps.get(ContentModel.PROP_NAME);
+ ContentData cd = (ContentData) nodeProps.get(ContentModel.PROP_CONTENT);
+ String name = (String) nodeProps.get(ContentModel.PROP_NAME);
org.alfresco.rest.framework.resource.content.ContentInfo ci = null;
String mimeType = null;
@@ -1982,7 +1987,7 @@ public class NodesImpl implements Nodes
}
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)
{
- final ActivityInfo activityInfo = getActivityInfo(getParentNodeRef(nodeRef), nodeRef);
+ final ActivityInfo activityInfo = getActivityInfo(getParentNodeRef(nodeRef), nodeRef);
postActivity(Activity_Type.DOWNLOADED, activityInfo, true);
}
diff --git a/source/java/org/alfresco/rest/api/impl/QuickShareLinksImpl.java b/source/java/org/alfresco/rest/api/impl/QuickShareLinksImpl.java
index 9b6e6949fa..a941029bde 100644
--- a/source/java/org/alfresco/rest/api/impl/QuickShareLinksImpl.java
+++ b/source/java/org/alfresco/rest/api/impl/QuickShareLinksImpl.java
@@ -230,15 +230,13 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean
throw new InvalidNodeRefException(nodeRef);
}
- String nodeId = nodeRef.getId();
-
if (renditionId != null)
{
- return renditions.getContent(nodeId, renditionId, parameters);
+ return renditions.getContent(nodeRef, renditionId, parameters);
}
else
{
- return nodes.getContent(nodeId, parameters, false);
+ return nodes.getContent(nodeRef, parameters, false);
}
}
}, networkTenantDomain);
diff --git a/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java b/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java
index 4aa9b5117e..a8780e431e 100644
--- a/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java
+++ b/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java
@@ -21,6 +21,7 @@ package org.alfresco.rest.api.impl;
import org.alfresco.model.ContentModel;
import org.alfresco.query.PagingResults;
+import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.thumbnail.ThumbnailDefinition;
import org.alfresco.repo.thumbnail.ThumbnailHelper;
import org.alfresco.repo.thumbnail.ThumbnailRegistry;
@@ -96,6 +97,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
private NamespaceService namespaceService;
private ServiceRegistry serviceRegistry;
private ResourceLoader resourceLoader;
+ private TenantService tenantService;
public void setNodes(Nodes nodes)
{
@@ -123,12 +125,18 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
this.resourceLoader = resourceLoader;
}
+ public void setTenantService(TenantService tenantService)
+ {
+ this.tenantService = tenantService;
+ }
+
public void init()
{
PropertyCheck.mandatory(this, "nodes", nodes);
PropertyCheck.mandatory(this, "thumbnailService", thumbnailService);
PropertyCheck.mandatory(this, "scriptThumbnailService", scriptThumbnailService);
PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry);
+ PropertyCheck.mandatory(this, "tenantService", tenantService);
this.nodeService = serviceRegistry.getNodeService();
this.actionService = serviceRegistry.getActionService();
@@ -288,7 +296,13 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
public BinaryResource getContent(String nodeId, String renditionId, Parameters parameters)
{
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
boolean attach = true;
@@ -382,7 +396,8 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
{
return null;
}
- return nodeRefRendition.getChildRef();
+
+ return tenantService.getName(nodeRef, nodeRefRendition.getChildRef());
}
protected Rendition toApiRendition(NodeRef renditionNodeRef)
diff --git a/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java b/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java
index bae33d0bca..4b3888e49e 100644
--- a/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java
+++ b/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java
@@ -27,6 +27,9 @@ import org.alfresco.rest.api.impl.QuickShareLinksImpl;
import org.alfresco.rest.api.model.QuickShareLink;
import org.alfresco.rest.api.nodes.NodesEntityResource;
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.PublicApiClient.Paging;
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.util.MultiPartBuilder;
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.PersonService;
+import org.alfresco.service.cmr.site.SiteVisibility;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -68,6 +73,19 @@ public class SharedLinkApiTest extends AbstractBaseApiTest
{
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 user2;
private List users = new ArrayList<>();
@@ -91,6 +109,12 @@ public class SharedLinkApiTest extends AbstractBaseApiTest
// so the tests for the specific network would work.
users.add(user1);
users.add(user2);
+
+ networkOne = getTestFixture().getRandomNetwork();
+ userOneN1 = networkOne.createUser();
+ userTwoN1 = networkOne.createUser();
+
+ userOneN1Site = createSite(networkOne, userOneN1, SiteVisibility.PRIVATE);
}
@After
@@ -131,6 +155,7 @@ public class SharedLinkApiTest extends AbstractBaseApiTest
* {@literal :/alfresco/api//public/alfresco/versions/1/shared-links/}
* {@literal :/alfresco/api//public/alfresco/versions/1/shared-links//content}
* {@literal :/alfresco/api//public/alfresco/versions/1/shared-links//renditions}
+ * {@literal :/alfresco/api//public/alfresco/versions/1/shared-links//renditions//content}
*
*/
@Test
@@ -661,6 +686,166 @@ public class SharedLinkApiTest extends AbstractBaseApiTest
}
}
+ /**
+ * Tests shared links to file (content) in a multi-tenant system.
+ *
+ * POST:
+ * {@literal :/alfresco/api//public/alfresco/versions/1/shared-links}
+ *
+ * DELETE:
+ * {@literal :/alfresco/api//public/alfresco/versions/1/shared-links/}
+ *
+ * GET:
+ * The following do not require authentication
+ * {@literal :/alfresco/api//public/alfresco/versions/1/shared-links/}
+ * {@literal :/alfresco/api//public/alfresco/versions/1/shared-links//content}
+ * {@literal :/alfresco/api//public/alfresco/versions/1/shared-links//renditions}
+ * {@literal :/alfresco/api//public/alfresco/versions/1/shared-links//renditions//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 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 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 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 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 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
public String getScope()
{