diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..e43402f --- /dev/null +++ b/.classpath @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..9b31a84 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + authorization-common-library + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/pom.xml b/pom.xml index effb698..f731812 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.gcube.common - authorization-library + common-authorization 1.0.0-SNAPSHOT authorization service common library @@ -16,6 +16,19 @@ distro + + + org.gcube.core + common-scope + [1.0.0-SNAPSHOT, 2.0.0-SNAPSHOT) + + + org.slf4j + slf4j-api + 1.7.5 + + + diff --git a/src/main/java/org/gcube/common/authorization/library/AuthorizationEntry.java b/src/main/java/org/gcube/common/authorization/library/AuthorizationEntry.java index 4dda367..602074e 100644 --- a/src/main/java/org/gcube/common/authorization/library/AuthorizationEntry.java +++ b/src/main/java/org/gcube/common/authorization/library/AuthorizationEntry.java @@ -1,5 +1,7 @@ package org.gcube.common.authorization.library; +import java.util.List; + import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; @@ -9,15 +11,15 @@ import javax.xml.bind.annotation.XmlRootElement; public class AuthorizationEntry { private String userName; - private String role; + private List roles; private String scope; protected AuthorizationEntry(){} - public AuthorizationEntry(String userName, String role, String scope) { + public AuthorizationEntry(String userName, List roles, String scope) { super(); this.userName = userName; - this.role = role; + this.roles = roles; this.scope = scope; } @@ -25,8 +27,8 @@ public class AuthorizationEntry { return userName; } - public String getRole() { - return role; + public List getRoles() { + return roles; } public String getScope() { @@ -35,7 +37,7 @@ public class AuthorizationEntry { @Override public String toString() { - return "AuthorizationEntry [userName=" + userName + ", role=" + role + return "AuthorizationEntry [userName=" + userName + ", roles=" + roles + ", scope=" + scope + "]"; } diff --git a/src/main/java/org/gcube/common/authorization/library/AuthorizationInvocationHandler.java b/src/main/java/org/gcube/common/authorization/library/AuthorizationInvocationHandler.java new file mode 100644 index 0000000..70b01f2 --- /dev/null +++ b/src/main/java/org/gcube/common/authorization/library/AuthorizationInvocationHandler.java @@ -0,0 +1,62 @@ +package org.gcube.common.authorization.library; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +import org.gcube.common.authorization.library.annotations.IsAllowedFor; +import org.gcube.common.authorization.library.annotations.SubjectToQuota; +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.provider.Service; +import org.gcube.common.authorization.library.provider.UserInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AuthorizationInvocationHandler implements InvocationHandler{ + + public static Logger log = LoggerFactory.getLogger(AuthorizationInvocationHandler.class); + + private String handledClass; + + private Object obj; + + ResourceAuthorizationProxy resourceAuthorizationProxy; + + protected AuthorizationInvocationHandler(I obj, String className, ResourceAuthorizationProxy resourceAuthorizationProxy) { + handledClass = className; + this.obj = obj; + this.resourceAuthorizationProxy = resourceAuthorizationProxy; + } + + public Object invoke(Object proxy, Method method, + Object[] args) throws Throwable { + log.trace("calling proxed method "+method.getName()+" on "+handledClass); + UserInfo info = AuthorizationProvider.instance.get(); + if(method.isAnnotationPresent(IsAllowedFor.class)){ + IsAllowedFor allowed = method.getAnnotation(IsAllowedFor.class); + if (allowed.roles().length>0 && isOneElementContainedinRoles(info.getRoles(), allowed.roles())){ + String message = "blocking method "+method.getName()+" for user "+info.getUserName()+": only roles "+Arrays.toString(allowed.roles()) +" can access"; + log.warn(message); + throw new SecurityException(message); + } + } + if(method.isAnnotationPresent(SubjectToQuota.class)){ + Service service = new Service(resourceAuthorizationProxy.getServiceClass(), resourceAuthorizationProxy.getServiceName()); + if (info.getBannedServices().contains(service)){ + String message = "blocking method "+method.getName()+" for user "+info.getUserName()+": overquota reached"; + log.warn(message); + throw new SecurityException(message); + } + } + return method.invoke(obj, args); + } + + private static boolean isOneElementContainedinRoles(List elements, String[] allowedRoles){ + for (String role: allowedRoles ) + if (elements.contains(role)) + return true; + return false; + } + +} diff --git a/src/main/java/org/gcube/common/authorization/library/AuthorizedTasks.java b/src/main/java/org/gcube/common/authorization/library/AuthorizedTasks.java new file mode 100644 index 0000000..494aaf2 --- /dev/null +++ b/src/main/java/org/gcube/common/authorization/library/AuthorizedTasks.java @@ -0,0 +1,75 @@ +package org.gcube.common.authorization.library; + +import java.util.concurrent.Callable; + +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.provider.UserInfo; +import org.gcube.common.scope.api.ScopeProvider; + + +public class AuthorizedTasks { + + /** + * Binds a {@link Callable} task to the current scope and user. + * @param task the task + * @return an equivalent {@link Callable} task bound to the current scope and user + */ + static public Callable bind(final Callable task) { + + final String callScope = ScopeProvider.instance.get(); + + final UserInfo userCall = AuthorizationProvider.instance.get(); + + return new Callable() { + @Override + public V call() throws Exception { + + //bind underlying thread to callscope + ScopeProvider.instance.set(callScope); + //bind underlying thread to call user + AuthorizationProvider.instance.set(userCall); + try { + return task.call(); + } + finally { + ScopeProvider.instance.reset(); + AuthorizationProvider.instance.reset(); + } + + } + }; + } + + /** + * Binds a {@link Runnable} task to the current scope and user. + * @param task the task + * @return an equivalent {@link Runnable} task bound to the current scope and user + */ + static public Runnable bind(final Runnable task) { + + final String callScope = ScopeProvider.instance.get(); + + final UserInfo userCall = AuthorizationProvider.instance.get(); + + return new Runnable() { + @Override + public void run() { + + //bind underlying thread to callscope + ScopeProvider.instance.set(callScope); + //bind underlying thread to call user + AuthorizationProvider.instance.set(userCall); + + try { + task.run(); + } + finally { + ScopeProvider.instance.reset(); + AuthorizationProvider.instance.reset(); + } + + } + }; + } + +} diff --git a/src/main/java/org/gcube/common/authorization/library/GenericProxyFactory.java b/src/main/java/org/gcube/common/authorization/library/GenericProxyFactory.java new file mode 100644 index 0000000..2b80274 --- /dev/null +++ b/src/main/java/org/gcube/common/authorization/library/GenericProxyFactory.java @@ -0,0 +1,16 @@ +package org.gcube.common.authorization.library; + +import java.lang.reflect.Proxy; + +public class GenericProxyFactory { + + + @SuppressWarnings("unchecked") + public static T getProxy(Class intf, + final I obj, ResourceAuthorizationProxy resourceAuthorizationProxy) { + return (T) + Proxy.newProxyInstance(obj.getClass().getClassLoader(), + new Class[] { intf }, + new AuthorizationInvocationHandler(obj, intf.getSimpleName(), resourceAuthorizationProxy)); + } +} \ No newline at end of file diff --git a/src/main/java/org/gcube/common/authorization/library/ResourceAuthorizationProxy.java b/src/main/java/org/gcube/common/authorization/library/ResourceAuthorizationProxy.java new file mode 100644 index 0000000..07d958e --- /dev/null +++ b/src/main/java/org/gcube/common/authorization/library/ResourceAuthorizationProxy.java @@ -0,0 +1,19 @@ +package org.gcube.common.authorization.library; + +public abstract class ResourceAuthorizationProxy { + + private I delegate; + + public ResourceAuthorizationProxy(Class classIntrface, T wrapped){ + delegate = GenericProxyFactory.getProxy(classIntrface,wrapped, this ); + } + + public I getDelegate() { + return delegate; + } + + public abstract String getServiceClass(); + + public abstract String getServiceName(); + +} diff --git a/src/main/java/org/gcube/common/authorization/library/annotations/IsAllowedFor.java b/src/main/java/org/gcube/common/authorization/library/annotations/IsAllowedFor.java new file mode 100644 index 0000000..242192c --- /dev/null +++ b/src/main/java/org/gcube/common/authorization/library/annotations/IsAllowedFor.java @@ -0,0 +1,15 @@ +package org.gcube.common.authorization.library.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface IsAllowedFor { + + String[] roles(); +} diff --git a/src/main/java/org/gcube/common/authorization/library/annotations/SubjectToQuota.java b/src/main/java/org/gcube/common/authorization/library/annotations/SubjectToQuota.java new file mode 100644 index 0000000..02f12f7 --- /dev/null +++ b/src/main/java/org/gcube/common/authorization/library/annotations/SubjectToQuota.java @@ -0,0 +1,14 @@ +package org.gcube.common.authorization.library.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface SubjectToQuota { + +} diff --git a/src/main/java/org/gcube/common/authorization/library/provider/AuthorizationProvider.java b/src/main/java/org/gcube/common/authorization/library/provider/AuthorizationProvider.java index 35e4eb3..d24e1b4 100644 --- a/src/main/java/org/gcube/common/authorization/library/provider/AuthorizationProvider.java +++ b/src/main/java/org/gcube/common/authorization/library/provider/AuthorizationProvider.java @@ -1,12 +1,18 @@ package org.gcube.common.authorization.library.provider; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class AuthorizationProvider { public static AuthorizationProvider instance = new AuthorizationProvider(); + private static Logger logger = LoggerFactory.getLogger(AuthorizationProvider.class); + // Thread local variable containing each thread's ID - private static final ThreadLocal threadAuth = - new ThreadLocal() { + private static final InheritableThreadLocal threadAuth = + new InheritableThreadLocal() { @Override protected UserInfo initialValue() { return null; @@ -17,11 +23,18 @@ public class AuthorizationProvider { private AuthorizationProvider(){} public UserInfo get(){ - return threadAuth.get(); + UserInfo info = threadAuth.get(); + logger.info("getting "+info+" in thread "+Thread.currentThread().getId() ); + return info; } public void set(UserInfo authorizationToken){ threadAuth.set(authorizationToken); + logger.info("setting "+authorizationToken+" in thread "+Thread.currentThread().getId() ); + } + + public void reset(){ + threadAuth.remove(); } } diff --git a/src/main/java/org/gcube/common/authorization/library/provider/Service.java b/src/main/java/org/gcube/common/authorization/library/provider/Service.java new file mode 100644 index 0000000..cc2209b --- /dev/null +++ b/src/main/java/org/gcube/common/authorization/library/provider/Service.java @@ -0,0 +1,61 @@ +package org.gcube.common.authorization.library.provider; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class Service { + + private String serviceClass; + private String serviceName; + + public Service(String serviceClass, String serviceName) { + super(); + this.serviceClass = serviceClass; + this.serviceName = serviceName; + } + + public String getServiceClass() { + return serviceClass; + } + public String getServiceName() { + return serviceName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((serviceClass == null) ? 0 : serviceClass.hashCode()); + result = prime * result + + ((serviceName == null) ? 0 : serviceName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Service other = (Service) obj; + if (serviceClass == null) { + if (other.serviceClass != null) + return false; + } else if (!serviceClass.equals(other.serviceClass)) + return false; + if (serviceName == null) { + if (other.serviceName != null) + return false; + } else if (!serviceName.equals(other.serviceName)) + return false; + return true; + } + + +} diff --git a/src/main/java/org/gcube/common/authorization/library/provider/UserInfo.java b/src/main/java/org/gcube/common/authorization/library/provider/UserInfo.java index 8d39216..7c251fc 100644 --- a/src/main/java/org/gcube/common/authorization/library/provider/UserInfo.java +++ b/src/main/java/org/gcube/common/authorization/library/provider/UserInfo.java @@ -1,5 +1,7 @@ package org.gcube.common.authorization.library.provider; +import java.util.List; + import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; @@ -10,30 +12,34 @@ import javax.xml.bind.annotation.XmlRootElement; public class UserInfo { private String userName; - private String role; - + private List roles; + private List bannedServices; + protected UserInfo(){} - public UserInfo(String userName, String role) { + public UserInfo(String userName, List roles, List bannedServices) { super(); this.userName = userName; - this.role = role; + this.roles = roles; + this.bannedServices = bannedServices; } public String getUserName() { return userName; } - public String getRole() { - return role; + public List getRoles() { + return roles; + } + + public List getBannedServices() { + return bannedServices; } - - @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((role == null) ? 0 : role.hashCode()); + result = prime * result + ((roles == null) ? 0 : roles.hashCode()); result = prime * result + ((userName == null) ? 0 : userName.hashCode()); return result; @@ -48,10 +54,10 @@ public class UserInfo { if (getClass() != obj.getClass()) return false; UserInfo other = (UserInfo) obj; - if (role == null) { - if (other.role != null) + if (roles == null) { + if (other.roles != null) return false; - } else if (!role.equals(other.role)) + } else if (!roles.equals(other.roles)) return false; if (userName == null) { if (other.userName != null) @@ -63,7 +69,7 @@ public class UserInfo { @Override public String toString() { - return "UserInfo [userName=" + userName + ", role=" + role + "]"; + return "UserInfo [userName=" + userName + ", roles=" + roles + "]"; }