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

121844 jvonka: Quick Share Link API - initial commit (work-in-progress)
   - demonstrate WebApiNoAuth
   - auth required to create &/or delete quick share link
   - no auth required to get &/or download content for a quick share link
   - TODO review detailed api & impl (+ add tests)
   RA-775, RA-773, RA-750, RA-708, RA-776


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@126427 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Jamal Kaabi-Mofrad
2016-05-10 10:52:51 +00:00
parent ee39a9192e
commit 06f639f03d
9 changed files with 948 additions and 53 deletions

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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
{
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<ResourceOperation> 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<Class<? extends ResourceAction>> apiDeleted)
{
super(uniqueId, RESOURCE_TYPE.ACTION, null, api, apiDeleted, null);
super(uniqueId, RESOURCE_TYPE.ACTION, null, api, apiDeleted, null, null);
this.actionMethod = null;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Class<? extends ResourceAction>> 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<ResourceMetadata> metainfo)
{
final Map<String,List<ResourceOperation>> operationGroupedByProperty = new HashMap<String,List<ResourceOperation>>();
MetaHelperCallback helperForAddressProps = new MetaHelperCallback(resource) {
@Override
public void whenNewOperation(ResourceOperation operation, Method aMethod)
{
Annotation addressableProps = AnnotationUtils.findAnnotation(aMethod, BinaryProperties.class);
if (addressableProps != null)
{
Map<String, Object> 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<ResourceOperation> ops = new ArrayList<ResourceOperation>();
operationGroupedByProperty.put(propKey, ops);
}
List<ResourceOperation> 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<Class<? extends ResourceAction>> 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<String, List<ResourceOperation>> 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<Class<? extends ResourceAction>> 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<Class<? extends ResourceAction>> apiNoAuth = new HashSet<Class<? extends ResourceAction>>();
private String entityPath;
private Map<String,List<ResourceOperation>> operationGroupedByProperty;
public MetaHelperAddressable(Class<?> resource, String entityPath, Map<String,List<ResourceOperation>> 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<String, Object> 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<ResourceOperation> ops = new ArrayList<ResourceOperation>();
operationGroupedByProperty.put(propKey, ops);
}
List<ResourceOperation> 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<ResourceOperation> operations = new ArrayList<ResourceOperation>();
private Set<Class<? extends ResourceAction>> apiDeleted = new HashSet<Class<? extends ResourceAction>>();
private Set<Class<? extends ResourceAction>> apiNoAuth = new HashSet<Class<? extends ResourceAction>>();
@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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Class<? extends ResourceAction>> apiDeleted;
private Set<Class<? extends ResourceAction>> apiNoAuth;
@SuppressWarnings("unchecked")
public ResourceMetadata(String uniqueId, RESOURCE_TYPE type, List<ResourceOperation> operations, Api api, Set<Class<? extends ResourceAction>> apiDeleted, String parentResource)
public ResourceMetadata(String uniqueId, RESOURCE_TYPE type, List<ResourceOperation> operations, Api api,
Set<Class<? extends ResourceAction>> apiDeleted,
Set<Class<? extends ResourceAction>> apiNoAuth,
String parentResource)
{
super();
this.uniqueId = uniqueId;
@@ -36,6 +60,7 @@ public class ResourceMetadata
this.operations = (List<ResourceOperation>) (operations==null?Collections.emptyList():operations);
this.api = api;
this.apiDeleted = (Set<Class<? extends ResourceAction>>) (apiDeleted==null?Collections.emptySet():apiDeleted);
this.apiNoAuth = (Set<Class<? extends ResourceAction>>) (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();
}

View File

@@ -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<Void>()
{
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;
}
}