diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml
index 6117ba3c4f..43565b0fa7 100644
--- a/config/alfresco/public-rest-context.xml
+++ b/config/alfresco/public-rest-context.xml
@@ -29,6 +29,10 @@
webscript.default
+
+
+
+
@@ -696,6 +700,13 @@
+
+
+
+
+
+
+
diff --git a/source/java/org/alfresco/rest/api/PublicApiDeclarativeRegistry.java b/source/java/org/alfresco/rest/api/PublicApiDeclarativeRegistry.java
index a7132e8b40..2a550f723e 100644
--- a/source/java/org/alfresco/rest/api/PublicApiDeclarativeRegistry.java
+++ b/source/java/org/alfresco/rest/api/PublicApiDeclarativeRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2012 Alfresco Software Limited.
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -18,19 +18,45 @@
*/
package org.alfresco.rest.api;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import org.alfresco.rest.framework.Api;
+import org.alfresco.rest.framework.core.ResourceLocator;
+import org.alfresco.rest.framework.core.ResourceWithMetadata;
+import org.alfresco.rest.framework.core.exceptions.DeletedResourceException;
+import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
+import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction;
+import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
+import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
+import org.alfresco.rest.framework.resource.actions.interfaces.ResourceAction;
+import org.alfresco.rest.framework.resource.content.BinaryResource;
+import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.extensions.webscripts.ArgumentTypeDescription;
import org.springframework.extensions.webscripts.Container;
import org.springframework.extensions.webscripts.DeclarativeRegistry;
+import org.springframework.extensions.webscripts.Description;
import org.springframework.extensions.webscripts.Description.FormatStyle;
import org.springframework.extensions.webscripts.Description.RequiredAuthentication;
import org.springframework.extensions.webscripts.Description.RequiredTransaction;
import org.springframework.extensions.webscripts.Description.TransactionCapability;
import org.springframework.extensions.webscripts.DescriptionImpl;
import org.springframework.extensions.webscripts.Match;
+import org.springframework.extensions.webscripts.NegotiatedFormat;
+import org.springframework.extensions.webscripts.Path;
import org.springframework.extensions.webscripts.TransactionParameters;
+import org.springframework.extensions.webscripts.TypeDescription;
+import org.springframework.extensions.webscripts.URLModelFactory;
import org.springframework.extensions.webscripts.WebScript;
+import org.springframework.extensions.webscripts.WebScriptRequest;
+import org.springframework.extensions.webscripts.WebScriptResponse;
+import org.springframework.http.HttpMethod;
public class PublicApiDeclarativeRegistry extends DeclarativeRegistry
{
@@ -38,6 +64,13 @@ public class PublicApiDeclarativeRegistry extends DeclarativeRegistry
private WebScript getNetworkWebScript;
private Container container;
+ private ResourceLocator locator;
+
+ public void setLocator(ResourceLocator locator)
+ {
+ this.locator = locator;
+ }
+
public void setGetNetworksWebScript(WebScript getNetworksWebScript)
{
this.getNetworksWebScript = getNetworksWebScript;
@@ -79,10 +112,265 @@ public class PublicApiDeclarativeRegistry extends DeclarativeRegistry
}
else
{
- return super.findWebScript(method, uri);
+ Match match = super.findWebScript(method, uri);
+
+ HttpMethod httpMethod = HttpMethod.valueOf(method);
+
+ if (httpMethod.equals(HttpMethod.GET))
+ {
+ // TODO - review (experimental)
+
+ // noAuth currently only exposed for GET
+ Map templateVars = match.getTemplateVars();
+ Api api = determineApi(templateVars);
+
+ // TODO can we avoid locating resource more than once ?
+ ResourceWithMetadata rwm = locator.locateResource(api, templateVars, HttpMethod.valueOf(method));
+
+ Class resAction = null;
+
+ switch (rwm.getMetaData().getType())
+ {
+ case ENTITY:
+ // TODO check params for entity id (for now - assume there is)
+ if (EntityResourceAction.ReadById.class.isAssignableFrom(rwm.getResource().getClass()))
+ {
+ resAction = EntityResourceAction.ReadById.class;
+ }
+ break;
+ case PROPERTY:
+ // TODO check params for entity id (for now - assume there is)
+ if (BinaryResourceAction.Read.class.isAssignableFrom(rwm.getResource().getClass()))
+ {
+ resAction = BinaryResourceAction.Read.class;
+ }
+ break;
+ default:
+ break;
+ }
+
+ final boolean noAuth = (resAction != null && rwm.getMetaData().isNoAuth(resAction));
+
+ if (noAuth)
+ {
+ final WebScript webScript = match.getWebScript();
+
+ // hack ! - is there a better way (to dynamically override "requiredAuthentication") or handle noAuth check earlier ?
+ WebScript noAuthWebScriptWrapper = new WebScript()
+ {
+ @Override
+ public void init(Container container, Description description)
+ {
+ webScript.init(container, description);
+ }
+
+ @Override
+ public Description getDescription()
+ {
+ final Description d = webScript.getDescription();
+ return new Description()
+ {
+ @Override
+ public String getStorePath()
+ {
+ return d.getStorePath();
+ }
+
+ @Override
+ public String getScriptPath()
+ {
+ return d.getScriptPath();
+ }
+
+ @Override
+ public Path getPackage()
+ {
+ return d.getPackage();
+ }
+
+ @Override
+ public String getDescPath()
+ {
+ return d.getDescPath();
+ }
+
+ @Override
+ public InputStream getDescDocument() throws IOException
+ {
+ return d.getDescDocument();
+ }
+
+ @Override
+ public String getKind()
+ {
+ return d.getKind();
+ }
+
+ @Override
+ public Set getFamilys()
+ {
+ return d.getFamilys();
+ }
+
+ @Override
+ public RequiredAuthentication getRequiredAuthentication()
+ {
+ return RequiredAuthentication.none;
+ }
+
+ @Override
+ public String getRunAs()
+ {
+ return d.getRunAs();
+ }
+
+ @Override
+ public RequiredTransaction getRequiredTransaction()
+ {
+ return d.getRequiredTransaction();
+ }
+
+ @Override
+ public RequiredTransactionParameters getRequiredTransactionParameters()
+ {
+ return d.getRequiredTransactionParameters();
+ }
+
+ @Override
+ public RequiredCache getRequiredCache()
+ {
+ return d.getRequiredCache();
+ }
+
+ @Override
+ public String getMethod()
+ {
+ return d.getMethod();
+ }
+
+ @Override
+ public String[] getURIs()
+ {
+ return d.getURIs();
+ }
+
+ @Override
+ public FormatStyle getFormatStyle()
+ {
+ return d.getFormatStyle();
+ }
+
+ @Override
+ public String getDefaultFormat()
+ {
+ return d.getDefaultFormat();
+ }
+
+ @Override
+ public NegotiatedFormat[] getNegotiatedFormats()
+ {
+ return d.getNegotiatedFormats();
+ }
+
+ @Override
+ public Map getExtensions()
+ {
+ return d.getExtensions();
+ }
+
+ @Override
+ public Lifecycle getLifecycle()
+ {
+ return d.getLifecycle();
+ }
+
+ @Override
+ public boolean getMultipartProcessing()
+ {
+ return d.getMultipartProcessing();
+ }
+
+ @Override
+ public void setMultipartProcessing(boolean b)
+ {
+ d.setMultipartProcessing(b);
+ }
+
+ @Override
+ public ArgumentTypeDescription[] getArguments()
+ {
+ return d.getArguments();
+ }
+
+ @Override
+ public TypeDescription[] getRequestTypes()
+ {
+ return d.getRequestTypes();
+ }
+
+ @Override
+ public TypeDescription[] getResponseTypes()
+ {
+ return d.getResponseTypes();
+ }
+
+ @Override
+ public String getId()
+ {
+ return d.getId();
+ }
+
+ @Override
+ public String getShortName()
+ {
+ return d.getShortName();
+ }
+
+ @Override
+ public String getDescription()
+ {
+ return d.getDescription();
+ }
+ };
+ }
+
+ @Override
+ public ResourceBundle getResources()
+ {
+ return webScript.getResources();
+ }
+
+ @Override
+ public void execute(WebScriptRequest webScriptRequest, WebScriptResponse webScriptResponse) throws IOException
+ {
+ webScript.execute(webScriptRequest, webScriptResponse);
+ }
+
+ @Override
+ public void setURLModelFactory(URLModelFactory urlModelFactory)
+ {
+ webScript.setURLModelFactory(urlModelFactory);
+ }
+ };
+
+ match = new Match(match.getTemplate(), match.getTemplateVars(), match.getPath(), noAuthWebScriptWrapper);
+ }
+
+ }
+
+ return match;
}
}
-
+
+ // note: same as ApiWebscript
+ private Api determineApi(Map templateVars)
+ {
+ String apiScope = templateVars.get("apiScope");
+ String apiVersion = templateVars.get("apiVersion");
+ String apiName = templateVars.get("apiName");
+ return Api.valueOf(apiName,apiScope,apiVersion);
+ }
+
private void initWebScript(WebScript webScript, String name)
{
DescriptionImpl serviceDesc = new DescriptionImpl(name, name, name, name);
diff --git a/source/java/org/alfresco/rest/api/model/QuickShareLink.java b/source/java/org/alfresco/rest/api/model/QuickShareLink.java
new file mode 100644
index 0000000000..6f2b52b632
--- /dev/null
+++ b/source/java/org/alfresco/rest/api/model/QuickShareLink.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.rest.api.model;
+
+import java.util.Date;
+
+/**
+ * Representation of quick share link
+ *
+ * The "sharedId" provides a short link/url that is easy to copy/paste/send (via email or other).
+ * As of now, these links are public in that they provide unauthenticated access to the
+ * node's content and limited metadata info, such as file name and last modifer/modification.
+ *
+ * In the future, the QuickShareService *could* be enhanced to provide additional features,
+ * such as link expiry &/or "password" protection, etc.
+ *
+ * @author janv
+ *
+ */
+public class QuickShareLink
+{
+ // unique "short" link (ie. shorter than a guid, 22 vs 36 chars)
+ private String sharedId;
+
+ private String nodeId;
+
+ private String name;
+ private ContentInfo content;
+
+ protected Date modifiedAt;
+ protected UserInfo modifiedByUser;
+
+ public QuickShareLink()
+ {
+ }
+
+ public QuickShareLink(String sharedId, String nodeId)
+ {
+ this.sharedId = sharedId;
+ this.nodeId = nodeId;
+ }
+
+ public String getSharedId() {
+ return sharedId;
+ }
+
+ public void setSharedId(String sharedId) {
+ this.sharedId = sharedId;
+ }
+
+ public String getNodeId() {
+ return nodeId;
+ }
+
+ public void setNodeId(String nodeId) {
+ this.nodeId = nodeId;
+ }
+
+ public ContentInfo getContent()
+ {
+ return content;
+ }
+
+ public void setContent(ContentInfo content)
+ {
+ this.content = content;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public Date getModifiedAt()
+ {
+ return modifiedAt;
+ }
+
+ public void setModifiedAt(Date modifiedAt)
+ {
+ this.modifiedAt = modifiedAt;
+ }
+
+ public UserInfo getModifiedByUser()
+ {
+ return modifiedByUser;
+ }
+
+ public void setModifiedByUser(UserInfo modifiedByUser)
+ {
+ this.modifiedByUser = modifiedByUser;
+ }
+
+ // eg. for debug logging etc
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("QuickShareLink [sharedId=").append(getSharedId());
+ sb.append(", nodeId=").append(getNodeId());
+ sb.append(", name=").append(getName());
+ sb.append(", modifiedAt=").append(getModifiedAt());
+ sb.append(", modifiedByUser=").append(getModifiedByUser());
+ sb.append(", content=").append(getContent());
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/source/java/org/alfresco/rest/api/quicksharelinks/QuickShareLinkEntityResource.java b/source/java/org/alfresco/rest/api/quicksharelinks/QuickShareLinkEntityResource.java
new file mode 100644
index 0000000000..03fc49be3d
--- /dev/null
+++ b/source/java/org/alfresco/rest/api/quicksharelinks/QuickShareLinkEntityResource.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.rest.api.quicksharelinks;
+
+import org.alfresco.model.QuickShareModel;
+import org.alfresco.repo.tenant.TenantUtil;
+import org.alfresco.rest.api.Nodes;
+import org.alfresco.rest.api.model.ContentInfo;
+import org.alfresco.rest.api.model.QuickShareLink;
+import org.alfresco.rest.api.model.UserInfo;
+import org.alfresco.rest.framework.BinaryProperties;
+import org.alfresco.rest.framework.WebApiDescription;
+import org.alfresco.rest.framework.WebApiNoAuth;
+import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
+import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
+import org.alfresco.rest.framework.resource.EntityResource;
+import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction;
+import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
+import org.alfresco.rest.framework.resource.content.BinaryResource;
+import org.alfresco.rest.framework.resource.parameters.Parameters;
+import org.alfresco.service.cmr.quickshare.InvalidSharedIdException;
+import org.alfresco.service.cmr.quickshare.QuickShareDTO;
+import org.alfresco.service.cmr.quickshare.QuickShareService;
+import org.alfresco.service.cmr.repository.InvalidNodeRefException;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.util.Pair;
+import org.alfresco.util.ParameterCheck;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An implementation of an Entity Resource for a QuickShareLink (name TBC !!)
+ *
+ * @author janv
+ */
+@EntityResource(name="quicksharelinks", title = "QuickShareLinks")
+public class QuickShareLinkEntityResource implements EntityResourceAction.ReadById,
+ BinaryResourceAction.Read, EntityResourceAction.Delete,
+ EntityResourceAction.Create, InitializingBean
+{
+ // TODO move impl into QuickShare REST service (especially if & when we need to span more than one resource) ....
+
+ private static final Log logger = LogFactory.getLog(QuickShareLinkEntityResource.class);
+
+ private final static String DISABLED = "QuickShare is disabled system-wide";
+ private boolean enabled = true;
+
+ private QuickShareService quickShareService;
+ private Nodes nodes;
+ private NodeService nodeService;
+
+ public void setQuickShareService(QuickShareService quickShareService)
+ {
+ this.quickShareService = quickShareService;
+ }
+
+ public void setNodes(Nodes nodes)
+ {
+ this.nodes = nodes;
+ }
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ this.enabled = enabled;
+ }
+
+ @Override
+ public void afterPropertiesSet()
+ {
+ ParameterCheck.mandatory("quickShareService", this.quickShareService);
+ ParameterCheck.mandatory("nodes", this.nodes);
+ ParameterCheck.mandatory("nodeService", this.nodeService);
+ }
+
+ /**
+ * Returns limited metadata regarding the sharedId.
+ *
+ * Note: does not require authenticated access !
+ */
+ @Override
+ @WebApiDescription(title="Returns quick share information for given sharedId.")
+ @WebApiNoAuth
+ public QuickShareLink readById(String sharedId, Parameters parameters)
+ {
+ if (! enabled)
+ {
+ throw new PermissionDeniedException(DISABLED);
+ }
+
+ return getQuickShareInfo(sharedId);
+ }
+
+ /**
+ * Download content via sharedId.
+ *
+ * Note: does not require authenticated access !
+ *
+ * @param sharedId
+ * @param parameters {@link Parameters}
+ * @return
+ * @throws EntityNotFoundException
+ */
+ @Override
+ @WebApiDescription(title = "Download content", description = "Download content")
+ @WebApiNoAuth
+ @BinaryProperties({"content"})
+ public BinaryResource readProperty(String sharedId, final Parameters parameters) throws EntityNotFoundException
+ {
+ if (! enabled)
+ {
+ throw new PermissionDeniedException(DISABLED);
+ }
+
+ try
+ {
+ Pair pair = quickShareService.getTenantNodeRefFromSharedId(sharedId);
+
+ String networkTenantDomain = pair.getFirst();
+ final NodeRef nodeRef = pair.getSecond();
+
+ return TenantUtil.runAsSystemTenant(new TenantUtil.TenantRunAsWork()
+ {
+ public BinaryResource doWork() throws Exception
+ {
+ // belt-and-braces (similar to QuickSjareContentGet)
+ if (! nodeService.hasAspect(nodeRef, QuickShareModel.ASPECT_QSHARE))
+ {
+ throw new InvalidNodeRefException(nodeRef);
+ }
+
+ return nodes.getContent(nodeRef.getId(), parameters);
+ }
+ }, networkTenantDomain);
+ }
+ catch (InvalidSharedIdException ex)
+ {
+ logger.warn("Unable to find: "+sharedId);
+ throw new EntityNotFoundException("Unable to find: "+sharedId);
+ }
+ catch (InvalidNodeRefException inre){
+ logger.warn("Unable to find: "+sharedId+" ["+inre.getNodeRef()+"]");
+ throw new EntityNotFoundException("Unable to find: "+sharedId);
+ }
+ }
+
+ /**
+ * Delete the specified quick share.
+ *
+ * Requires authenticated access.
+ *
+ * @param sharedId String id of the quick share
+ */
+ @Override
+ @WebApiDescription(title = "Delete quick share", description="Delete the quick share reference")
+ public void delete(String sharedId, Parameters parameters)
+ {
+ if (! enabled)
+ {
+ throw new PermissionDeniedException(DISABLED);
+ }
+
+ try
+ {
+ quickShareService.unshareContent(sharedId);
+ }
+ catch (InvalidSharedIdException ex)
+ {
+ logger.warn("Unable to find: "+sharedId);
+ throw new EntityNotFoundException("Unable to find: "+sharedId);
+ }
+ catch (InvalidNodeRefException inre){
+ logger.warn("Unable to find: "+sharedId+" ["+inre.getNodeRef()+"]");
+ throw new EntityNotFoundException("Unable to find: "+sharedId);
+ }
+ }
+
+ /**
+ * Create quick share.
+ *
+ * Requires authenticated access.
+ *
+ * @param nodeIds
+ * @param parameters
+ * @return
+ */
+ @Override
+ @WebApiDescription(title="Create quick share")
+ public List create(List nodeIds, Parameters parameters)
+ {
+ List result = new ArrayList<>(nodeIds.size());
+
+ for (QuickShareLink qs : nodeIds)
+ {
+ String nodeId = qs.getNodeId();
+ QuickShareDTO qsDto = quickShareService.shareContent(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, nodeId));
+
+ // TODO should we skip errors (eg. broken share) ?
+ result.add(getQuickShareInfo(qsDto.getId()));
+ }
+
+ return result;
+ }
+
+ private QuickShareLink getQuickShareInfo(String sharedId)
+ {
+ try
+ {
+ Map map = (Map)quickShareService.getMetaData(sharedId).get("item");
+
+ String nodeId = new NodeRef((String)map.get("nodeRef")).getId();
+
+ ContentInfo contentInfo = new ContentInfo((String)map.get("mimetype"), null, (Long)map.get("size"), null);
+
+ // note: we do not return modifier user id (to be consistent with v0 internal - limited disclosure)
+ UserInfo modifier = new UserInfo(null,(String)map.get("modifierFirstName"), (String)map.get("modifierLastName"));
+
+ // TODO other "properties" (if needed) - eg. cm:title, cm:lastThumbnailModificationData, ... thumbnail info ...
+
+ QuickShareLink qs = new QuickShareLink(sharedId, nodeId);
+ qs.setName((String)map.get("name"));
+ qs.setContent(contentInfo);
+ qs.setModifiedAt((Date)map.get("modified"));
+ qs.setModifiedByUser(modifier);
+
+ return qs;
+ }
+ catch (InvalidSharedIdException ex)
+ {
+ logger.warn("Unable to find: "+sharedId);
+ throw new EntityNotFoundException("Unable to find: "+sharedId);
+ }
+ catch (InvalidNodeRefException inre){
+ logger.warn("Unable to find: "+sharedId+" ["+inre.getNodeRef()+"]");
+ throw new EntityNotFoundException("Unable to find: "+sharedId);
+ }
+ }
+}
diff --git a/source/java/org/alfresco/rest/framework/WebApiNoAuth.java b/source/java/org/alfresco/rest/framework/WebApiNoAuth.java
new file mode 100644
index 0000000000..baa06e5b0d
--- /dev/null
+++ b/source/java/org/alfresco/rest/framework/WebApiNoAuth.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.rest.framework;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates this Web api is does *not* require authentication !
+ *
+ * @author janv
+ */
+@Target({ElementType.TYPE,ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface WebApiNoAuth
+{
+
+}
diff --git a/source/java/org/alfresco/rest/framework/core/ActionResourceMetaData.java b/source/java/org/alfresco/rest/framework/core/ActionResourceMetaData.java
index e461cd057a..7d41897f8d 100644
--- a/source/java/org/alfresco/rest/framework/core/ActionResourceMetaData.java
+++ b/source/java/org/alfresco/rest/framework/core/ActionResourceMetaData.java
@@ -1,3 +1,21 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
package org.alfresco.rest.framework.core;
import org.alfresco.rest.framework.Api;
@@ -26,7 +44,7 @@ public class ActionResourceMetaData extends ResourceMetadata
*/
public ActionResourceMetaData(String uniqueId, List operations, Api api, Method actionMethod)
{
- super(uniqueId, RESOURCE_TYPE.ACTION, operations, api, null, null);
+ super(uniqueId, RESOURCE_TYPE.ACTION, operations, api, null, null, null);
if (operations.size()!= 1)
{
throw new IllegalArgumentException("Only 1 action per url is supported for an entity");
@@ -42,7 +60,7 @@ public class ActionResourceMetaData extends ResourceMetadata
*/
public ActionResourceMetaData(String uniqueId, Api api, Set> apiDeleted)
{
- super(uniqueId, RESOURCE_TYPE.ACTION, null, api, apiDeleted, null);
+ super(uniqueId, RESOURCE_TYPE.ACTION, null, api, apiDeleted, null, null);
this.actionMethod = null;
}
diff --git a/source/java/org/alfresco/rest/framework/core/ResourceInspector.java b/source/java/org/alfresco/rest/framework/core/ResourceInspector.java
index 138d74f91b..2b33c05a14 100644
--- a/source/java/org/alfresco/rest/framework/core/ResourceInspector.java
+++ b/source/java/org/alfresco/rest/framework/core/ResourceInspector.java
@@ -1,4 +1,21 @@
-
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
package org.alfresco.rest.framework.core;
import java.lang.annotation.Annotation;
@@ -18,6 +35,7 @@ import org.alfresco.rest.framework.BinaryProperties;
import org.alfresco.rest.framework.WebApi;
import org.alfresco.rest.framework.WebApiDeleted;
import org.alfresco.rest.framework.WebApiDescription;
+import org.alfresco.rest.framework.WebApiNoAuth;
import org.alfresco.rest.framework.WebApiParam;
import org.alfresco.rest.framework.WebApiParameters;
import org.alfresco.rest.framework.core.ResourceMetadata.RESOURCE_TYPE;
@@ -32,6 +50,7 @@ import org.alfresco.rest.framework.resource.actions.interfaces.MultiPartResource
import org.alfresco.rest.framework.resource.actions.interfaces.MultiPartRelationshipResourceAction;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
import org.alfresco.rest.framework.resource.actions.interfaces.ResourceAction;
+import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -97,17 +116,20 @@ public class ResourceInspector
findOperation(EntityResourceAction.Delete.class, HttpMethod.DELETE, helper);
findOperation(MultiPartResourceAction.Create.class, HttpMethod.POST, helper);
+ boolean noAuth = resource.isAnnotationPresent(WebApiNoAuth.class);
+ Set> apiNoAuth = (noAuth ? ALL_ENTITY_RESOURCE_INTERFACES : helper.apiNoAuth);
+
if (resource.isAnnotationPresent(WebApiDeleted.class))
{
metainfo.add(new ResourceMetadata(ResourceDictionary.resourceKey(urlPath,null), RESOURCE_TYPE.ENTITY,
- null, api, ALL_ENTITY_RESOURCE_INTERFACES, null));
+ null, api, ALL_ENTITY_RESOURCE_INTERFACES, apiNoAuth, null));
}
else
{
if (!helper.apiDeleted.isEmpty() || !helper.operations.isEmpty())
{
metainfo.add(new ResourceMetadata(ResourceDictionary.resourceKey(urlPath,null), RESOURCE_TYPE.ENTITY,
- helper.operations, api, helper.apiDeleted, null));
+ helper.operations, api, helper.apiDeleted, apiNoAuth, null));
}
}
@@ -126,56 +148,29 @@ public class ResourceInspector
public static void inspectAddressedProperties(Api api, Class> resource, final String entityPath, List metainfo)
{
final Map> operationGroupedByProperty = new HashMap>();
- MetaHelperCallback helperForAddressProps = new MetaHelperCallback(resource) {
- @Override
- public void whenNewOperation(ResourceOperation operation, Method aMethod)
- {
- Annotation addressableProps = AnnotationUtils.findAnnotation(aMethod, BinaryProperties.class);
- if (addressableProps != null)
- {
- Map annotAttribs = AnnotationUtils.getAnnotationAttributes(addressableProps);
- String[] props = (String[]) annotAttribs.get("value");
- for (String property : props)
- {
- String propKey = ResourceDictionary.propertyResourceKey(entityPath,property);
- if (!operationGroupedByProperty.containsKey(propKey))
- {
- List ops = new ArrayList();
- operationGroupedByProperty.put(propKey, ops);
- }
- List operations = operationGroupedByProperty.get(propKey);
- operations.add(operation);
- }
-
- }
- else
- {
- logger.warn("Resource "+resource.getCanonicalName()+" should declare a @BinaryProperties annotation.");
- }
- }
+ MetaHelperAddressable helperForAddressProps = new MetaHelperAddressable(resource, entityPath, operationGroupedByProperty);
- @Override
- public void whenOperationDeleted(Class extends ResourceAction> deleted, Method aMethod)
- {
- }
- };
findOperation(BinaryResourceAction.Read.class, HttpMethod.GET, helperForAddressProps);
findOperation(BinaryResourceAction.Delete.class, HttpMethod.DELETE, helperForAddressProps);
findOperation(BinaryResourceAction.Update.class, HttpMethod.PUT, helperForAddressProps);
-
+
+ boolean noAuth = resource.isAnnotationPresent(WebApiNoAuth.class);
+ Set> apiNoAuth = (noAuth ? ALL_PROPERTY_RESOURCE_INTERFACES : helperForAddressProps.apiNoAuth);
+
if (resource.isAnnotationPresent(WebApiDeleted.class))
{
metainfo.add(new ResourceMetadata(ResourceDictionary.propertyResourceKey(entityPath,"FIX_ME"), RESOURCE_TYPE.PROPERTY,
- null, inspectApi(resource), ALL_PROPERTY_RESOURCE_INTERFACES, null));
+ null, inspectApi(resource), ALL_PROPERTY_RESOURCE_INTERFACES, apiNoAuth, null));
}
else
{
for (Entry> groupedOps : operationGroupedByProperty.entrySet())
{
- metainfo.add(new ResourceMetadata(groupedOps.getKey(), RESOURCE_TYPE.PROPERTY, groupedOps.getValue(), api, null, null));
- }
+ metainfo.add(new ResourceMetadata(groupedOps.getKey(), RESOURCE_TYPE.PROPERTY, groupedOps.getValue(), api, null, apiNoAuth, null));
+ }
}
+
}
/**
@@ -197,14 +192,17 @@ public class ResourceInspector
findOperation(RelationshipResourceAction.Update.class, HttpMethod.PUT, helper);
findOperation(RelationshipResourceAction.Delete.class, HttpMethod.DELETE, helper);
findOperation(MultiPartRelationshipResourceAction.Create.class, HttpMethod.POST, helper);
-
+
+ boolean noAuth = resource.isAnnotationPresent(WebApiNoAuth.class);
+ Set> apiNoAuth = (noAuth ? ALL_RELATIONSHIP_RESOURCE_INTERFACES : helper.apiNoAuth);
+
if (resource.isAnnotationPresent(WebApiDeleted.class))
{
- return Arrays.asList(new ResourceMetadata(ResourceDictionary.resourceKey(entityPath,urlPath), RESOURCE_TYPE.RELATIONSHIP, null, inspectApi(resource), ALL_RELATIONSHIP_RESOURCE_INTERFACES, entityPath));
+ return Arrays.asList(new ResourceMetadata(ResourceDictionary.resourceKey(entityPath,urlPath), RESOURCE_TYPE.RELATIONSHIP, null, inspectApi(resource), ALL_RELATIONSHIP_RESOURCE_INTERFACES, apiNoAuth, entityPath));
}
else
{
- return Arrays.asList(new ResourceMetadata(ResourceDictionary.resourceKey(entityPath,urlPath), RESOURCE_TYPE.RELATIONSHIP, helper.operations, inspectApi(resource), helper.apiDeleted, entityPath));
+ return Arrays.asList(new ResourceMetadata(ResourceDictionary.resourceKey(entityPath,urlPath), RESOURCE_TYPE.RELATIONSHIP, helper.operations, inspectApi(resource), helper.apiDeleted, apiNoAuth, entityPath));
}
}
@@ -221,6 +219,7 @@ public class ResourceInspector
{
Method aMethod = findMethod(resourceInterfaceWithOneMethod, helper.resource);
ResourceOperation operation = inspectOperation(helper.resource, aMethod, httpMethod);
+
if (isDeleted(aMethod))
{
helper.whenOperationDeleted(resourceInterfaceWithOneMethod, aMethod);
@@ -229,6 +228,11 @@ public class ResourceInspector
{
helper.whenNewOperation(operation, aMethod);
}
+
+ if (isNoAuth(aMethod))
+ {
+ helper.whenOperationNoAuth(resourceInterfaceWithOneMethod, aMethod);
+ }
}
}
@@ -423,6 +427,17 @@ public class ResourceInspector
WebApiDeleted deleted = AnnotationUtils.getAnnotation(method, WebApiDeleted.class);
return (deleted!=null);
}
+
+ /**
+ * Returns true if the method has been marked as no auth required.
+ * @param method the method
+ * @return true - if is is marked as no auth required.
+ */
+ public static boolean isNoAuth(Method method)
+ {
+ WebApiNoAuth noAuth = AnnotationUtils.getAnnotation(method, WebApiNoAuth.class);
+ return (noAuth!=null);
+ }
/**
* Returns the method for the interface
@@ -630,6 +645,10 @@ public class ResourceInspector
Object id = ResourceInspectorUtil.invokeMethod(annotatedMethod, obj);
if (id != null)
{
+ if (id instanceof NodeRef)
+ {
+ return ((NodeRef)id).getId();
+ }
return String.valueOf(id);
}
else
@@ -679,6 +698,66 @@ public class ResourceInspector
}
return UniqueId.UNIQUE_NAME;
}
+
+ private static class MetaHelperAddressable extends MetaHelperCallback {
+
+ private Set> apiNoAuth = new HashSet>();
+
+ private String entityPath;
+ private Map> operationGroupedByProperty;
+
+ public MetaHelperAddressable(Class> resource, String entityPath, Map> operationGroupedByProperty)
+ {
+ super(resource);
+
+ this.entityPath = entityPath;
+ this.operationGroupedByProperty = operationGroupedByProperty;
+ }
+
+ public MetaHelperAddressable(Class> resource)
+ {
+ super(resource);
+ }
+
+ @Override
+ public void whenNewOperation(ResourceOperation operation, Method aMethod)
+ {
+ Annotation addressableProps = AnnotationUtils.findAnnotation(aMethod, BinaryProperties.class);
+ if (addressableProps != null)
+ {
+ Map annotAttribs = AnnotationUtils.getAnnotationAttributes(addressableProps);
+ String[] props = (String[]) annotAttribs.get("value");
+ for (String property : props)
+ {
+ String propKey = ResourceDictionary.propertyResourceKey(entityPath,property);
+ if (!operationGroupedByProperty.containsKey(propKey))
+ {
+ List ops = new ArrayList();
+ operationGroupedByProperty.put(propKey, ops);
+ }
+ List operations = operationGroupedByProperty.get(propKey);
+ operations.add(operation);
+ }
+
+ }
+ else
+ {
+ logger.warn("Resource "+resource.getCanonicalName()+" should declare a @BinaryProperties annotation.");
+ }
+ }
+
+ @Override
+ public void whenOperationDeleted(Class extends ResourceAction> deleted, Method aMethod)
+ {
+ }
+
+ @Override
+ public void whenOperationNoAuth(Class extends ResourceAction> noAuth, Method aMethod)
+ {
+ // TODO review - is this right ?
+ apiNoAuth.add(noAuth);
+ }
+ }
/**
* Little container of a subset of metadata
@@ -693,7 +772,9 @@ public class ResourceInspector
}
private List operations = new ArrayList();
+
private Set> apiDeleted = new HashSet>();
+ private Set> apiNoAuth = new HashSet>();
@Override
public void whenNewOperation(ResourceOperation operation, Method aMethod)
@@ -706,6 +787,12 @@ public class ResourceInspector
{
apiDeleted.add(deleted);
}
+
+ @Override
+ public void whenOperationNoAuth(Class extends ResourceAction> noAuth, Method aMethod)
+ {
+ apiNoAuth.add(noAuth);
+ }
}
/**
@@ -725,6 +812,7 @@ public class ResourceInspector
public abstract void whenNewOperation(ResourceOperation operation, Method aMethod);
public abstract void whenOperationDeleted(Class extends ResourceAction> deleted, Method aMethod);
+ public abstract void whenOperationNoAuth(Class extends ResourceAction> noAuth, Method aMethod);
}
}
diff --git a/source/java/org/alfresco/rest/framework/core/ResourceMetadata.java b/source/java/org/alfresco/rest/framework/core/ResourceMetadata.java
index 79a23e747c..0b313fbde1 100644
--- a/source/java/org/alfresco/rest/framework/core/ResourceMetadata.java
+++ b/source/java/org/alfresco/rest/framework/core/ResourceMetadata.java
@@ -1,3 +1,21 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
package org.alfresco.rest.framework.core;
import java.util.Collections;
@@ -14,6 +32,7 @@ import org.springframework.http.HttpMethod;
* the resource can perform and what properties it has.
*
* @author Gethin James
+ * @author janv
*/
public class ResourceMetadata
{
@@ -25,10 +44,15 @@ public class ResourceMetadata
@JsonIgnore
private final Api api;
+
private final Set> apiDeleted;
+ private Set> apiNoAuth;
@SuppressWarnings("unchecked")
- public ResourceMetadata(String uniqueId, RESOURCE_TYPE type, List operations, Api api, Set> apiDeleted, String parentResource)
+ public ResourceMetadata(String uniqueId, RESOURCE_TYPE type, List operations, Api api,
+ Set> apiDeleted,
+ Set> apiNoAuth,
+ String parentResource)
{
super();
this.uniqueId = uniqueId;
@@ -36,6 +60,7 @@ public class ResourceMetadata
this.operations = (List) (operations==null?Collections.emptyList():operations);
this.api = api;
this.apiDeleted = (Set>) (apiDeleted==null?Collections.emptySet():apiDeleted);
+ this.apiNoAuth = (Set>) (apiNoAuth==null?Collections.emptySet():apiNoAuth);
this.parentResource = parentResource!=null?(parentResource.startsWith("/")?parentResource:"/"+parentResource):null;
}
@@ -85,7 +110,17 @@ public class ResourceMetadata
{
return apiDeleted.contains(resourceAction);
}
-
+
+ /**
+ * Indicates if this resource action supports unauthenticated access.
+ * @param resourceAction
+ * @return
+ */
+ public boolean isNoAuth(Class extends ResourceAction> resourceAction)
+ {
+ return apiNoAuth.contains(resourceAction);
+ }
+
/**
* URL uniqueId to the resource
*
@@ -133,6 +168,8 @@ public class ResourceMetadata
builder.append(this.operations);
builder.append(", apiDeleted=");
builder.append(this.apiDeleted);
+ builder.append(", apiNoAuth=");
+ builder.append(this.apiNoAuth);
builder.append("]");
return builder.toString();
}
diff --git a/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java b/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java
index 1613790812..61ee63e699 100644
--- a/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java
+++ b/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2015 Alfresco Software Limited.
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -22,6 +22,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
+import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.web.scripts.content.ContentStreamer;
import org.alfresco.rest.framework.Api;
import org.alfresco.rest.framework.core.HttpMethodSupport;
@@ -30,6 +31,7 @@ import org.alfresco.rest.framework.core.ResourceWithMetadata;
import org.alfresco.rest.framework.core.exceptions.ApiException;
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper;
import org.alfresco.rest.framework.resource.actions.ActionExecutor;
+import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction;
import org.alfresco.rest.framework.resource.content.BinaryResource;
import org.alfresco.rest.framework.resource.content.ContentInfo;
import org.alfresco.rest.framework.resource.content.FileBinaryResource;
@@ -42,6 +44,7 @@ import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.extensions.surf.util.URLEncoder;
+import org.springframework.extensions.webscripts.Description;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
@@ -117,7 +120,7 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements
});
//Outside the transaction.
- Object toSerialize = respons.get("toSerialize");
+ final Object toSerialize = respons.get("toSerialize");
ContentInfo contentInfo = (ContentInfo) respons.get("contentInfo");
// set caching (MNT-13938)
@@ -130,7 +133,25 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements
{
if (toSerialize instanceof BinaryResource)
{
- streamResponse(req, res, (BinaryResource) toSerialize);
+ // TODO review (experimental) - can we move earlier & wrap complete execute ? Also for QuickShare (in MT/Cloud) needs to be tenant for the nodeRef (TBC).
+ boolean noAuth = resource.getMetaData().isNoAuth(BinaryResourceAction.Read.class);
+ if (noAuth)
+ {
+ String networkTenantDomain = TenantUtil.getCurrentDomain();
+
+ TenantUtil.runAsSystemTenant(new TenantUtil.TenantRunAsWork()
+ {
+ public Void doWork() throws Exception
+ {
+ streamResponse(req, res, (BinaryResource) toSerialize);
+ return null;
+ }
+ }, networkTenantDomain);
+ }
+ else
+ {
+ streamResponse(req, res, (BinaryResource) toSerialize);
+ }
}
else
{
@@ -243,5 +264,4 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements
{
this.streamer = streamer;
}
-
}