diff --git a/.classpath b/.classpath index 4918979..5bbc871 100644 --- a/.classpath +++ b/.classpath @@ -1,5 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + @@ -13,22 +36,5 @@ - - - - - - - - - - - - - - - - - diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component index 4413d77..4a2bb6d 100644 --- a/.settings/org.eclipse.wst.common.component +++ b/.settings/org.eclipse.wst.common.component @@ -1,21 +1,50 @@ - + + + + + + + - + + + + - + + + + + - + + + + uses + - + + + + - + + + + - - + + + + + + + + diff --git a/.settings/org.eclipse.wst.common.project.facet.core.xml b/.settings/org.eclipse.wst.common.project.facet.core.xml index 71d64dd..c82abf3 100644 --- a/.settings/org.eclipse.wst.common.project.facet.core.xml +++ b/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -3,5 +3,5 @@ - + diff --git a/gcube/extra-resources/WEB-INF/web.xml b/gcube/extra-resources/WEB-INF/web.xml index 9fd650f..2b62579 100644 --- a/gcube/extra-resources/WEB-INF/web.xml +++ b/gcube/extra-resources/WEB-INF/web.xml @@ -1,5 +1,9 @@ - + org.gcube.resourcemanagement.whnmanager.WHNManager diff --git a/pom.xml b/pom.xml index 97ea121..a2b12f9 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ org.gcube.distribution gcube-smartgears-bom - 3.0.0-SNAPSHOT + 3.0.1-SNAPSHOT pom import @@ -39,30 +39,31 @@ org.gcube.core common-smartgears-app + + + + org.glassfish.jersey.inject + jersey-cdi2-se + - - javax.ws.rs - javax.ws.rs-api - org.glassfish.jersey.containers jersey-container-servlet - - - javax.servlet - javax.servlet-api - 3.0.1 - - - org.glassfish.jersey.media - jersey-media-json-jackson - + + org.glassfish.jersey.media + jersey-media-json-jackson + + + jakarta.servlet + jakarta.servlet-api + + org.slf4j slf4j-api @@ -80,6 +81,27 @@ test + + + junit + junit + 4.13.2 + test + + + ch.qos.logback + logback-classic + test + + + org.gcube.common + gxHTTP + + + org.gcube.common + event-publisher-library + [1.0.0-SNAPSHOT, 2.0.0-SNAPSHOT) + diff --git a/src/main/java/org/gcube/resourcemanagement/whnmanager/ContextManager.java b/src/main/java/org/gcube/resourcemanagement/whnmanager/ContextManager.java index 839f488..ec255d2 100644 --- a/src/main/java/org/gcube/resourcemanagement/whnmanager/ContextManager.java +++ b/src/main/java/org/gcube/resourcemanagement/whnmanager/ContextManager.java @@ -1,135 +1,182 @@ package org.gcube.resourcemanagement.whnmanager; -import java.net.HttpURLConnection; -import java.util.Map.Entry; +import java.net.URL; +import java.security.InvalidParameterException; +import java.util.HashMap; +import java.util.Map; import java.util.Set; +import java.util.function.Predicate; -import javax.ws.rs.DELETE; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - -import org.gcube.common.gxhttp.request.GXHTTPStringRequest; -import org.gcube.common.gxhttp.util.ContentUtils; +import org.gcube.common.keycloak.KeycloakClient; +import org.gcube.common.keycloak.KeycloakClientFactory; +import org.gcube.common.keycloak.model.ModelUtils; +import org.gcube.common.keycloak.model.TokenResponse; import org.gcube.common.security.providers.SecretManagerProvider; import org.gcube.common.security.secrets.Secret; +import org.gcube.event.publisher.AbstractHTTPWithJWTTokenAuthEventSender; +import org.gcube.event.publisher.Event; +import org.gcube.event.publisher.EventStatus.Status; +import org.gcube.oidc.rest.JWTToken; +import org.gcube.oidc.rest.OpenIdConnectRESTHelperException; import org.gcube.resourcemanagement.whnmanager.utils.ValidationUtils; import org.gcube.smartgears.ContextProvider; import org.gcube.smartgears.context.application.ApplicationContext; import org.gcube.smartgears.managers.ContextEvents; +import org.gcube.smartgears.security.AuthorizationProvider; import org.gcube.smartgears.security.SimpleCredentials; -import org.gcube.smartgears.security.defaults.DefaultAuthorizationProvider; import org.gcube.smartgears.utils.InnerMethodName; +import org.json.simple.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + + @Path("/contexts") public class ContextManager { - - private static Logger logger=LoggerFactory.getLogger(ContextManager.class); - + + private static Logger logger = LoggerFactory.getLogger(ContextManager.class); + + private static final String CONDUCTOR_URI = "https://conductor.cloud-dev.d4science.org/api/workflow/"; + // private static final String REQUEST_PATH ="workflow"; + + private static final String ADD_CONTEXT_TASK = "ghn_client_add_to_context"; + private static final String REMOVE_CONTEXT_TASK = "ghn_client_remove_from_context"; + + private enum TaskType { + ADD(ADD_CONTEXT_TASK), REMOVE(REMOVE_CONTEXT_TASK); + + private String taskName; + + TaskType(String contextName) { + this.taskName = contextName; + + } + } + + private static final int READ_TIMEOUT = 40000; + private static final int CONNECTION_TIMEOUT = 40000; + @PUT @Path("") - public Response add(@FormParam("context") String context){ - InnerMethodName.instance.set("addContext"); - Secret secret = SecretManagerProvider.instance.get(); - logger.trace("WHNManager: addToContext method invokation with parameters context :{} and caller: {} curentContext: {}",context, secret.getOwner().getId(), secret.getContext() ); - ValidationUtils.valid("context", context); - ApplicationContext appContext = ContextProvider.get(); - if(context!=null){ - - GXHTTPStringRequest request = GXHTTPStringRequest.newRequest("https://conductor.dev.d4science.org/api"); - try { - - request = request.path("workflow"); - for(Entry entry : secret.getHTTPAuthorizationHeaders().entrySet()) - request = request.header(entry.getKey(), entry.getValue()); - - - SimpleCredentials credentials = ((DefaultAuthorizationProvider) appContext.container().authorizationProvider()).getCredentials(); - - HttpURLConnection response = request.post(String.format(" \"name\": \"ghn_client_add_to_contexts\",\n" + - " \"input\" : {\n" + - " \"client_id\" : \"%s\",\n" + - " \"context_list\" : [\"%s\"] }", credentials.getClientID(), context)) ; - - if(response.getResponseCode() == Status.CREATED.getStatusCode()) { - String body = ContentUtils.toString(ContentUtils.toByteArray(response.getInputStream())); - logger.info("Returned response for remove scope {} ",body); - } - - appContext.container().events().fire(context, ContextEvents.ADD_CONTEXT_TO_CONTAINER); - } catch (Exception e) { - logger.error("error adding context {}", context, e); - return Response.serverError().build(); - } - - }else{ - logger.error("context is null"); - return Response.status(Status.BAD_REQUEST).build(); + public Response add(final @FormParam("context") String context) { + InnerMethodName.set("addContext"); + Secret secret = SecretManagerProvider.get(); + logger.debug( + "WHNManager: addToContext method invokation with parameters context :{} and caller: {} curentContext: {}", + context, secret.getOwner().getId(), secret.getContext()); + try { + executeTask(l -> l.contains(context), TaskType.ADD, context); + ApplicationContext appContext = ContextProvider.get(); + appContext.container().events().fire(context, ContextEvents.ADD_CONTEXT_TO_CONTAINER); + return Response.ok().build(); + } catch (InvalidParameterException ip) { + logger.warn("clientId already contains {}", context); + return Response.noContent().build(); + } catch (IllegalArgumentException ia) { + logger.warn("null context passed",ia); + return Response.status(400, "context parameter is null").build(); + } catch (Throwable t) { + logger.error("error adding context", t); + throw new WebApplicationException(t); } - return Response.ok().build(); } - - + @DELETE @Path("") - public Response remove(@FormParam("context") String context){ - InnerMethodName.instance.set("removeContext"); - Secret secret = SecretManagerProvider.instance.get(); - logger.trace("WHNManager: removeFromContext method invokation with parameters context :{} and caller: {} curentContext: {}",context, secret.getOwner().getId(), secret.getContext()); - ValidationUtils.valid("context", context); - ApplicationContext appContext = ContextProvider.get(); - if(context!=null){ - logger.trace("allowed container in context are {} ",appContext.container().authorizationProvider().getContexts()); - - GXHTTPStringRequest request = GXHTTPStringRequest.newRequest("https://conductor.dev.d4science.org/api"); - try { - - request = request.path("workflow"); - for(Entry entry : secret.getHTTPAuthorizationHeaders().entrySet()) - request = request.header(entry.getKey(), entry.getValue()); - - - SimpleCredentials credentials = ((DefaultAuthorizationProvider) appContext.container().authorizationProvider()).getCredentials(); - - HttpURLConnection response = request.post(String.format(" \"name\": \"ghn_client_remove_from_contexts\",\n" + - " \"input\" : {\n" + - " \"client_id\" : \"%s\",\n" + - " \"context_list\" : [\"%s\"] }", credentials.getClientID(), context)) ; - - if(response.getResponseCode() == Status.CREATED.getStatusCode()) { - String body = ContentUtils.toString(ContentUtils.toByteArray(response.getInputStream())); - logger.info("Returned response for remove scope {} ",body); - } - - appContext.container().events().fire(context, ContextEvents.REMOVE_CONTEXT_FROM_CONTAINER); - } catch (Exception e) { - logger.error("error removing context {}", context, e); - return Response.serverError().build(); - } - - - }else{ - logger.error("context is null"); - return Response.status(Status.BAD_REQUEST).build(); + public Response remove(final @FormParam("context") String context) { + InnerMethodName.set("removeContext"); + Secret secret = SecretManagerProvider.get(); + logger.debug( + "WHNManager: removeFromContext method invokation with parameters context :{} and caller: {} curentContext: {}", + context, secret.getOwner().getId(), secret.getContext()); + try { + executeTask(l -> !l.contains(context), TaskType.REMOVE, context); + ApplicationContext appContext = ContextProvider.get(); + appContext.container().events().fire(context, ContextEvents.REMOVE_CONTEXT_FROM_CONTAINER); + return Response.ok().build(); + } catch (Throwable t) { + logger.error("error removing context", t); + throw new WebApplicationException(t); } - return Response.ok().build(); } - + + private Status executeTask(Predicate> notModifiablePredicate, TaskType type, String inputContext ) throws Throwable { + ValidationUtils.valid("context", inputContext); + ApplicationContext appContext = ContextProvider.get(); + AuthorizationProvider authProvider = appContext.container().authorizationProvider(); + + SimpleCredentials credentials = (SimpleCredentials) authProvider.getCredentials(); + + logger.debug("contexts already present in the container are {} executing task {}",authProvider.getContexts(), type.taskName); + + if (notModifiablePredicate.test(authProvider.getContexts())) + throw new InvalidParameterException(); + + Map inputs = new HashMap(); + inputs.put("client_id", credentials.getClientID()); + inputs.put("context", inputContext); + + Event event = new Event(type.taskName, type.taskName, appContext.name(), inputs); + + Status result = checkEventResult(credentials, event); + + logger.debug("{} exectured with result {}", type.taskName, result); + if (result != Status.COMPLETED) { + throw new Exception("error executing the workflow retuned with status " + result); + } + return result; + } + + private Status checkEventResult(SimpleCredentials credentials, Event event) throws Throwable { + Secret secret = SecretManagerProvider.get(); + AbstractHTTPWithJWTTokenAuthEventSender eventRequest = getAuthEventSender(credentials, secret); + + String result = eventRequest.sendAndGetResult(event); + + JSONObject eventResult; + do { + Thread.sleep(5000); + eventResult = eventRequest.retrive(result); + } while (Status.RUNNING == Status.valueOf((String) eventResult.get("status"))); + + return Status.valueOf((String) eventResult.get("status")); + } + @GET @Path("") @Produces(MediaType.APPLICATION_JSON) - public String[] get(){ - InnerMethodName.instance.set("addContext"); + public String[] get() { + InnerMethodName.set("getContext"); ApplicationContext appContext = ContextProvider.get(); Set contexts = appContext.authorizationProvider().getContexts(); return contexts.stream().toArray(String[]::new); } - + + private AbstractHTTPWithJWTTokenAuthEventSender getAuthEventSender(SimpleCredentials credentials, Secret secret) + throws Throwable { + KeycloakClient client = KeycloakClientFactory.newInstance(); + TokenResponse tokenResponse = client.queryOIDCToken(secret.getContext(), credentials.getClientID(), + credentials.getSecret()); + AbstractHTTPWithJWTTokenAuthEventSender eventSender = new AbstractHTTPWithJWTTokenAuthEventSender( + new URL(CONDUCTOR_URI), credentials.getClientID(), credentials.getSecret(), null) { + protected JWTToken getAuthorizationToken() throws OpenIdConnectRESTHelperException { + return JWTToken.fromString(ModelUtils.toJSONString(tokenResponse)); + } + }; + + eventSender.setConnectionTimeout(CONNECTION_TIMEOUT); + eventSender.setReadTimeout(READ_TIMEOUT); + + return eventSender; + } + } diff --git a/src/main/java/org/gcube/resourcemanagement/whnmanager/WHNManager.java b/src/main/java/org/gcube/resourcemanagement/whnmanager/WHNManager.java index cb83098..bdb26f7 100644 --- a/src/main/java/org/gcube/resourcemanagement/whnmanager/WHNManager.java +++ b/src/main/java/org/gcube/resourcemanagement/whnmanager/WHNManager.java @@ -3,8 +3,8 @@ package org.gcube.resourcemanagement.whnmanager; import java.util.HashSet; import java.util.Set; -import javax.ws.rs.Path; -import javax.ws.rs.core.Application; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Application; @Path("/") public class WHNManager extends Application{ @@ -17,6 +17,4 @@ public class WHNManager extends Application{ return classes; } - - } diff --git a/src/test/java/org/gcube/resourcemanagement/whnmanager/ContextManagerIntegrationTest.java b/src/test/java/org/gcube/resourcemanagement/whnmanager/ContextManagerIntegrationTest.java index 5bc2d0a..2d10c9c 100644 --- a/src/test/java/org/gcube/resourcemanagement/whnmanager/ContextManagerIntegrationTest.java +++ b/src/test/java/org/gcube/resourcemanagement/whnmanager/ContextManagerIntegrationTest.java @@ -6,17 +6,16 @@ import static org.junit.Assert.assertEquals; import java.util.Collections; import java.util.Set; -import javax.servlet.ServletContext; -import javax.ws.rs.core.Application; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; +import jakarta.servlet.ServletContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; import org.gcube.common.events.Hub; import org.gcube.common.security.credentials.Credentials; import org.gcube.common.security.secrets.Secret; -import org.gcube.resourcemanagement.whnmanager.ContextManager; import org.gcube.smartgears.ContextProvider; import org.gcube.smartgears.configuration.application.ApplicationConfiguration; import org.gcube.smartgears.context.Properties;