diff --git a/distro/changelog.xml b/distro/changelog.xml index eb6b036..cf5b236 100644 --- a/distro/changelog.xml +++ b/distro/changelog.xml @@ -1,4 +1,17 @@ + + Added ThreadLocal InnerMethodName to set method name from application + + + Validation handler for application split in 2 different handlers: + - ContextRetriever that set Token and Scope + - RequestValidation that does all the required checks + + + + Added gcube bom dependency + Search for handlers in the root classpath + Modified the Authorization filter to accept also children scope when authorizeChildrenContext is enabled on ContianerConfiguration diff --git a/endpoint.xml b/endpoint.xml new file mode 100644 index 0000000..70d5b58 Binary files /dev/null and b/endpoint.xml differ diff --git a/pom.xml b/pom.xml index 6e16686..c9aec40 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.gcube.core common-smartgears - 2.1.3-SNAPSHOT + 2.1.5-SNAPSHOT SmartGears diff --git a/src/main/java/org/gcube/smartgears/Constants.java b/src/main/java/org/gcube/smartgears/Constants.java index 32f3007..acb233b 100644 --- a/src/main/java/org/gcube/smartgears/Constants.java +++ b/src/main/java/org/gcube/smartgears/Constants.java @@ -117,6 +117,11 @@ public class Constants { */ public static final String request_validation = "request-validation"; + /** + * The configuration name of {@link RequestValidator}s. + */ + public static final String request_context_retriever = "context-retriever"; + /** * The configuration name of {@link AccountingManager}s. diff --git a/src/main/java/org/gcube/smartgears/handlers/application/lifecycle/ProfilePublisher.java b/src/main/java/org/gcube/smartgears/handlers/application/lifecycle/ProfilePublisher.java index 6c58cbd..03f60ad 100644 --- a/src/main/java/org/gcube/smartgears/handlers/application/lifecycle/ProfilePublisher.java +++ b/src/main/java/org/gcube/smartgears/handlers/application/lifecycle/ProfilePublisher.java @@ -96,6 +96,7 @@ public class ProfilePublisher { } sharePublished(profile); + log.debug("shared profile with scopes {}", profile.scopes().asCollection()); } public void addToAll() { diff --git a/src/main/java/org/gcube/smartgears/handlers/application/request/RequestAccounting.java b/src/main/java/org/gcube/smartgears/handlers/application/request/RequestAccounting.java index ce67c53..b9910a5 100644 --- a/src/main/java/org/gcube/smartgears/handlers/application/request/RequestAccounting.java +++ b/src/main/java/org/gcube/smartgears/handlers/application/request/RequestAccounting.java @@ -9,7 +9,6 @@ import org.gcube.accounting.datamodel.usagerecords.ServiceUsageRecord; import org.gcube.accounting.persistence.AccountingPersistence; import org.gcube.accounting.persistence.AccountingPersistenceFactory; import org.gcube.common.authorization.library.provider.AuthorizationProvider; -import org.gcube.common.authorization.library.provider.CalledMethodProvider; import org.gcube.common.authorization.library.provider.SecurityTokenProvider; import org.gcube.common.scope.api.ScopeProvider; import org.gcube.smartgears.Constants; @@ -17,6 +16,7 @@ import org.gcube.smartgears.context.application.ApplicationContext; import org.gcube.smartgears.handlers.application.RequestEvent; import org.gcube.smartgears.handlers.application.RequestHandler; import org.gcube.smartgears.handlers.application.ResponseEvent; +import org.gcube.smartgears.utils.InnerMethodName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,11 +43,11 @@ public class RequestAccounting extends RequestHandler { if (calledMethod.isEmpty()) calledMethod = "/"; } - CalledMethodProvider.instance.set(calledMethod); + InnerMethodName.instance.set(calledMethod); String caller = AuthorizationProvider.instance.get()!=null? AuthorizationProvider.instance.get().getClient().getId(): "UNKNOWN"; startCallThreadLocal.set(System.currentTimeMillis()); log.info("REQUEST START ON {}:{}({}) CALLED FROM {}@{} IN SCOPE {} ", - context.configuration().name(),context.configuration().serviceClass(), CalledMethodProvider.instance.get(), + context.configuration().name(),context.configuration().serviceClass(), InnerMethodName.instance.get(), caller, e.request().getRemoteHost(), ScopeProvider.instance.get()); } @@ -73,10 +73,10 @@ public class RequestAccounting extends RequestHandler { generateAccounting(caller,callerQualifier,callerIp==null?"UNKNOWN":callerIp , context); log.info("REQUEST SERVED ON {}:{}({}) CALLED FROM {}@{} IN SCOPE {} FINISHED IN {} millis", - context.configuration().name(),context.configuration().serviceClass(), CalledMethodProvider.instance.get(), + context.configuration().name(),context.configuration().serviceClass(), InnerMethodName.instance.get(), caller, callerIp, ScopeProvider.instance.get(), System.currentTimeMillis()-startCallThreadLocal.get()); startCallThreadLocal.remove(); - CalledMethodProvider.instance.reset(); + InnerMethodName.instance.reset(); if (resetScope) ScopeProvider.instance.reset(); } @@ -94,7 +94,7 @@ public class RequestAccounting extends RequestHandler { serviceUsageRecord.setServiceName(context.configuration().name()); serviceUsageRecord.setHost(context.container().configuration().hostname()+":"+context.container().configuration().port()); - serviceUsageRecord.setCalledMethod(CalledMethodProvider.instance.get()); + serviceUsageRecord.setCalledMethod(InnerMethodName.instance.get()); serviceUsageRecord.setCallerHost(remoteHost); serviceUsageRecord.setOperationResult(OperationResult.SUCCESS); serviceUsageRecord.setDuration(System.currentTimeMillis()-startCallThreadLocal.get()); diff --git a/src/main/java/org/gcube/smartgears/handlers/application/request/RequestContextRetriever.java b/src/main/java/org/gcube/smartgears/handlers/application/request/RequestContextRetriever.java new file mode 100644 index 0000000..b61d861 --- /dev/null +++ b/src/main/java/org/gcube/smartgears/handlers/application/request/RequestContextRetriever.java @@ -0,0 +1,88 @@ +package org.gcube.smartgears.handlers.application.request; + +import static org.gcube.common.authorization.client.Constants.authorizationService; +import static org.gcube.smartgears.Constants.oauth_secret; +import static org.gcube.smartgears.Constants.scope_header; +import static org.gcube.smartgears.Constants.token_header; +import static org.gcube.smartgears.handlers.application.request.RequestError.internal_server_error; +import static org.gcube.smartgears.handlers.application.request.RequestError.invalid_request_error; + +import javax.xml.bind.DatatypeConverter; +import javax.xml.bind.annotation.XmlRootElement; + +import org.gcube.common.authorization.client.exceptions.ObjectNotFound; +import org.gcube.common.authorization.library.AuthorizationEntry; +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.smartgears.Constants; +import org.gcube.smartgears.handlers.application.RequestEvent; +import org.gcube.smartgears.handlers.application.RequestHandler; +import org.gcube.smartgears.handlers.application.ResponseEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@XmlRootElement(name = Constants.request_context_retriever) +public class RequestContextRetriever extends RequestHandler { + + private static Logger log = LoggerFactory.getLogger(RequestContextRetriever.class); + + + @Override + public String getName() { + return Constants.request_context_retriever; + } + + @Override + public void handleRequest(RequestEvent call) { + String token = call.request().getParameter(token_header)==null? call.request().getHeader(token_header):call.request().getParameter(token_header); + String scope = call.request().getParameter(scope_header)==null? call.request().getHeader(scope_header):call.request().getParameter(scope_header); + + if (token==null && call.request().getHeader(Constants.authorization_header)!=null){ + String basicAuthorization = call.request().getHeader(Constants.authorization_header); + String base64Credentials = basicAuthorization.substring("Basic".length()).trim(); + String credentials = new String(DatatypeConverter.parseBase64Binary(base64Credentials)); + // credentials = username:password + final String[] values = credentials.split(":",2); + token = values[1]; + } + + if (token==null && scope==null && call.request().getParameter(oauth_secret)!=null) + token = call.request().getParameter(oauth_secret); + + //Gives priority to the token + if (token!=null) + this.retreiveAndSetInfo(token, call); + else if (scope!=null) + ScopeProvider.instance.set(scope); + + } + + @Override + public void handleResponse(ResponseEvent e) { + SecurityTokenProvider.instance.reset(); + AuthorizationProvider.instance.reset(); + ScopeProvider.instance.reset(); + log.debug("resetting all the Thread local for this call."); + } + + private void retreiveAndSetInfo(String token, RequestEvent call){ + log.info("retrieving context using token {} ", token); + AuthorizationEntry authEntry = null; + try{ + authEntry = authorizationService().get(token); + }catch(ObjectNotFound onf){ + log.warn("rejecting call to {}, invalid token {}",call.context().name(),token); + invalid_request_error.fire(call.context().name()+" invalid token : "+token); + }catch(Exception e){ + log.error("error contacting authorization service",e); + internal_server_error.fire("error contacting authorization service"); + } + + AuthorizationProvider.instance.set(new Caller(authEntry.getClientInfo(), authEntry.getQualifier())); + SecurityTokenProvider.instance.set(token); + ScopeProvider.instance.set(authEntry.getContext()); + log.info("retrieved request authorization info {} in scope {} ", AuthorizationProvider.instance.get(), authEntry.getContext()); + } +} diff --git a/src/main/java/org/gcube/smartgears/handlers/application/request/RequestValidator.java b/src/main/java/org/gcube/smartgears/handlers/application/request/RequestValidator.java index 520f28d..115eff0 100644 --- a/src/main/java/org/gcube/smartgears/handlers/application/request/RequestValidator.java +++ b/src/main/java/org/gcube/smartgears/handlers/application/request/RequestValidator.java @@ -1,9 +1,6 @@ package org.gcube.smartgears.handlers.application.request; import static org.gcube.common.authorization.client.Constants.authorizationService; -import static org.gcube.smartgears.Constants.scope_header; -import static org.gcube.smartgears.Constants.token_header; -import static org.gcube.smartgears.Constants.oauth_secret; import static org.gcube.smartgears.handlers.application.request.RequestError.application_failed_error; import static org.gcube.smartgears.handlers.application.request.RequestError.application_unavailable_error; import static org.gcube.smartgears.handlers.application.request.RequestError.internal_server_error; @@ -11,7 +8,6 @@ import static org.gcube.smartgears.handlers.application.request.RequestError.inv import java.io.IOException; -import javax.xml.bind.DatatypeConverter; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; @@ -19,12 +15,8 @@ import org.gcube.common.authorization.client.exceptions.ObjectNotFound; import org.gcube.common.authorization.library.AuthorizationEntry; import org.gcube.common.authorization.library.PolicyUtils; import org.gcube.common.authorization.library.policies.Policy; -import org.gcube.common.authorization.library.provider.AuthorizationProvider; -import org.gcube.common.authorization.library.provider.ClientInfo; import org.gcube.common.authorization.library.provider.SecurityTokenProvider; import org.gcube.common.authorization.library.provider.ServiceIdentifier; -import org.gcube.common.authorization.library.provider.UserInfo; -import org.gcube.common.authorization.library.utils.Caller; import org.gcube.common.scope.api.ScopeProvider; import org.gcube.common.scope.impl.ScopeBean; import org.gcube.common.scope.impl.ScopeBean.Type; @@ -33,7 +25,6 @@ import org.gcube.smartgears.configuration.container.ContainerConfiguration; import org.gcube.smartgears.context.application.ApplicationContext; import org.gcube.smartgears.handlers.application.RequestEvent; import org.gcube.smartgears.handlers.application.RequestHandler; -import org.gcube.smartgears.handlers.application.ResponseEvent; import org.gcube.smartgears.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,6 +33,7 @@ import org.slf4j.LoggerFactory; public class RequestValidator extends RequestHandler { @XmlAttribute(required=false, name="oauth") + @Deprecated boolean oauthCompatibility = false; private static Logger log = LoggerFactory.getLogger(RequestValidator.class); @@ -62,22 +54,15 @@ public class RequestValidator extends RequestHandler { validateAgainstLifecycle(call); - if (!validateToken(call)){ - String scope = call.request().getParameter(scope_header)==null? call.request().getHeader(scope_header):call.request().getParameter(scope_header); - validateScope(scope); - log.info("received call to {} in context {}",call.uri(),scope); - } - - } + rejectUnauthorizedCalls(call); + + validateScopeCall(); + + if (SecurityTokenProvider.instance.get()!=null) + validatePolicy(SecurityTokenProvider.instance.get(), call); - @Override - public void handleResponse(ResponseEvent e){ - SecurityTokenProvider.instance.reset(); - AuthorizationProvider.instance.reset(); - ScopeProvider.instance.reset(); - log.debug("resetting all the Thread local for this call."); } - + private void validateAgainstLifecycle(RequestEvent call) { switch(context.lifecycle().state()) { @@ -95,8 +80,10 @@ public class RequestValidator extends RequestHandler { } - private void validateScope(String scope) { - + private void validateScopeCall() { + + String scope = ScopeProvider.instance.get(); + if (scope == null) { log.warn("rejecting unscoped call to {}",context.name()); invalid_request_error.fire("call is unscoped"); @@ -110,41 +97,18 @@ public class RequestValidator extends RequestHandler { log.warn("rejecting call to {} in invalid context {}, allowed context are {}",context.name(),scope,context.container().configuration().allowedContexts()); invalid_request_error.fire(context.name()+" cannot be called in scope "+scope); } - - ScopeProvider.instance.set(scope); } - private boolean validateToken(RequestEvent call){ - - - String token = call.request().getParameter(token_header)==null? call.request().getHeader(token_header):call.request().getParameter(token_header); - - String scope = call.request().getParameter(scope_header)==null? call.request().getHeader(scope_header):call.request().getParameter(scope_header); - - if(token==null && scope==null && oauthCompatibility) - token = call.request().getParameter(oauth_secret); + private void rejectUnauthorizedCalls(RequestEvent call){ + String token = SecurityTokenProvider.instance.get(); + String scope = ScopeProvider.instance.get(); if (token == null && scope==null){ log.warn("rejecting call to {}, authorization required",context.name(),token); if (call.context().container().configuration().authenticationEnpoint()==null){ - if (call.request().getHeader(Constants.authorization_header)!=null){ - String basicAuthorization = call.request().getHeader(Constants.authorization_header); - String base64Credentials = basicAuthorization.substring("Basic".length()).trim(); - String credentials = new String(DatatypeConverter.parseBase64Binary(base64Credentials)); - // credentials = username:password - final String[] values = credentials.split(":",2); - token = values[1]; - ClientInfo info = retreiveAndSetInfo(token, call); - if (!(info instanceof UserInfo) || !info.getId().equals(values[0])) { - log.warn("rejecting call to {}, username {} not valid for token {}",context.name(),values[0],token); - RequestError.request_not_authorized_error.fire(context.name()+": username "+values[0]+" not valid for token "+token); - } - return true; - }else { log.warn("rejecting call to {}, authorization required",context.name(),token); RequestError.request_not_authorized_error.fire(context.name()+": authorization required"); - } }else { log.info("authorization enpoint found on configuration, redirecting the call"); String recallLocation = String.format("http://%s:%d%s", call.context().container().configuration().hostname(), call.context().container().configuration().port(), call.uri()); @@ -154,19 +118,9 @@ public class RequestValidator extends RequestHandler { } catch (IOException e) { log.error("errror redirecting call",e ); } - return false; } } - - log.trace("token is "+token); - - if (token!=null){ - retreiveAndSetInfo(token, call); - return true; - } - log.info("invalid token, checking the context"); - return false; } @Override @@ -174,8 +128,8 @@ public class RequestValidator extends RequestHandler { return getName(); } - private ClientInfo retreiveAndSetInfo(String token, RequestEvent call){ - log.info("accessing validator with token {} ", token); + private void validatePolicy(String token, RequestEvent call){ + log.info("accessing policy validator with token {} ", token); AuthorizationEntry authEntry = null; try{ authEntry = authorizationService().get(token); @@ -195,11 +149,6 @@ public class RequestValidator extends RequestHandler { invalid_request_error.fire("rejecting call to "+context.name()+": "+authEntry.getClientInfo().getId()+" is not allowed to contact the service"); } - AuthorizationProvider.instance.set(new Caller(authEntry.getClientInfo(), authEntry.getQualifier())); - validateScope(authEntry.getContext()); - log.info("retrieved request authorization info {} in scope {} ", AuthorizationProvider.instance.get(), authEntry.getContext()); - SecurityTokenProvider.instance.set(token); - return authEntry.getClientInfo(); } diff --git a/src/main/java/org/gcube/smartgears/utils/InnerMethodName.java b/src/main/java/org/gcube/smartgears/utils/InnerMethodName.java new file mode 100644 index 0000000..9c81c21 --- /dev/null +++ b/src/main/java/org/gcube/smartgears/utils/InnerMethodName.java @@ -0,0 +1,40 @@ +package org.gcube.smartgears.utils; + +import org.gcube.common.authorization.library.provider.CalledMethodProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class InnerMethodName { + + public static InnerMethodName instance = new InnerMethodName(); + + private static Logger logger = LoggerFactory.getLogger(CalledMethodProvider.class); + + // Thread local variable containing each thread's ID + private static final InheritableThreadLocal threadMethod = + new InheritableThreadLocal() { + + @Override protected String initialValue() { + return "UNKNOWN"; + } + + }; + + private InnerMethodName(){} + + public String get(){ + String calledMethod = threadMethod.get(); + logger.trace("getting InnerMethodName as "+calledMethod+" in thread "+Thread.currentThread().getId() ); + return calledMethod; + } + + public void set(String calledMethod){ + if (calledMethod==null) return; + threadMethod.set(calledMethod); + logger.trace("setting InnerMethodName as "+calledMethod+" in thread "+Thread.currentThread().getId() ); + } + + public void reset(){ + threadMethod.remove(); + } +} diff --git a/src/main/resources/META-INF/default-handlers.xml b/src/main/resources/META-INF/default-handlers.xml index d6a1181..39ecdba 100644 --- a/src/main/resources/META-INF/default-handlers.xml +++ b/src/main/resources/META-INF/default-handlers.xml @@ -4,6 +4,7 @@ + diff --git a/src/main/resources/META-INF/services/org.gcube.smartgears.handlers.application.ApplicationHandler b/src/main/resources/META-INF/services/org.gcube.smartgears.handlers.application.ApplicationHandler index 5c7dfb1..977cf56 100644 --- a/src/main/resources/META-INF/services/org.gcube.smartgears.handlers.application.ApplicationHandler +++ b/src/main/resources/META-INF/services/org.gcube.smartgears.handlers.application.ApplicationHandler @@ -1,3 +1,4 @@ org.gcube.smartgears.handlers.application.lifecycle.ProfileManager org.gcube.smartgears.handlers.application.request.RequestValidator -org.gcube.smartgears.handlers.application.request.RequestAccounting \ No newline at end of file +org.gcube.smartgears.handlers.application.request.RequestAccounting +org.gcube.smartgears.handlers.application.request.RequestContextRetriever \ No newline at end of file