package org.gcube.resourcemanagement.whnmanager; 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 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.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 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(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); } } @DELETE @Path("") 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); } } 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.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; } }