package org.gcube.smartgears.handlers.application.request; import static org.gcube.smartgears.Constants.token_header; 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.invalid_request_error; import java.util.Set; import org.gcube.common.keycloak.KeycloakClient; import org.gcube.common.keycloak.KeycloakClientException; import org.gcube.common.keycloak.KeycloakClientFactory; import org.gcube.common.security.ContextBean; import org.gcube.common.security.ContextBean.Type; import org.gcube.common.security.providers.SecretManagerProvider; import org.gcube.common.security.secrets.GCubeSecret; import org.gcube.common.security.secrets.Secret; import org.gcube.common.security.secrets.UmaTokenSecret; import org.gcube.smartgears.Constants; import org.gcube.smartgears.configuration.Mode; 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.security.SimpleCredentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RequestValidator extends RequestHandler { private static Logger log = LoggerFactory.getLogger(RequestValidator.class); private static final String BEARER_AUTH_PREFIX ="Bearer"; private ApplicationContext appContext; @Override public String getName() { return Constants.request_validation; } @Override public void handleRequest(RequestEvent call) { log.trace("executing request validator ON REQUEST"); appContext = call.context(); SecretManagerProvider.set(getSecret(call)); validateAgainstLifecycle(call); rejectUnauthorizedCalls(call); if (appContext.container().configuration().mode()!=Mode.offline) { validateScopeCall(); validatePolicy(call); } } @Override public void handleResponse(ResponseEvent e) { log.debug("resetting all the Thread local for this call."); SecretManagerProvider.reset(); } private void validateAgainstLifecycle(RequestEvent call) { switch(appContext.lifecycle().state()) { case stopped : application_unavailable_error.fire(); break; case failed: application_failed_error.fire(); break; default: //nothing to do, but avoids warnings } } private void validateScopeCall() { String context = SecretManagerProvider.get().getContext(); if (context == null) { log.warn("rejecting unscoped call to {}",appContext.name()); invalid_request_error.fire("call is unscoped"); } ContextBean bean = new ContextBean(context); ContainerConfiguration conf = appContext.container().configuration(); Set allowedContexts =appContext.authorizationProvider().getContexts(); if (!allowedContexts.contains(context) && !(conf.authorizeChildrenContext() && bean.is(Type.VRE) && allowedContexts.contains(bean.enclosingScope().toString()) ) ) { log.warn("rejecting call to {} in invalid context {}, allowed context are {}",appContext.name(),context,allowedContexts); invalid_request_error.fire(appContext.name()+" cannot be called in scope "+context); } } private void rejectUnauthorizedCalls(RequestEvent call){ Secret secret = SecretManagerProvider.get(); if (secret == null){ log.warn("rejecting call to {}, authorization required",appContext.name()); RequestError.request_not_authorized_error.fire(appContext.name()+": authorization required"); } } @Override public String toString() { return getName(); } private void validatePolicy(RequestEvent call){ //TODO: must be re-thought } private Secret getSecret(RequestEvent call){ String token = call.request().getParameter(token_header)==null? call.request().getHeader(token_header):call.request().getParameter(token_header); String authHeader = call.request().getHeader(Constants.authorization_header); log.trace("authorization header is {}",authHeader); log.trace("token header is {}", token); log.info("d4s-user set to {} ", call.request().getHeader("d4s-user")); String umaToken = null; if (authHeader!=null && !authHeader.isEmpty()) if (authHeader.startsWith(BEARER_AUTH_PREFIX)) umaToken = authHeader.substring(BEARER_AUTH_PREFIX.length()).trim(); Secret secret = null; if (umaToken!=null) { secret = new UmaTokenSecret(umaToken); SimpleCredentials credentials = (SimpleCredentials)appContext.authorizationProvider().getCredentials(); KeycloakClient client = KeycloakClientFactory.newInstance(); try { if(!client.isAccessTokenVerified(secret.getContext(), credentials.getClientID(), credentials.getSecret(), umaToken)) RequestError.request_not_authorized_error.fire("access token verification error"); }catch (KeycloakClientException e) { RequestError.internal_server_error.fire("error contacting keycloak client", e); } } else if (token!=null && !token.isEmpty()) try { secret = new GCubeSecret(token); }catch(Throwable t) { RequestError.request_not_authorized_error.fire("gcube token verification error ("+t.getMessage()+")"); } return secret; } }