From efea30e5e59d94eee73c5997a65c40903e09ac12 Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Mon, 9 Oct 2023 17:13:56 +0200 Subject: [PATCH] Moved from `javax.ws.rs.*` to `jakarta.ws.rs.*` classes and added support for CORS to HEAD and GET --- .../avatar/AbstractAvatarResource.java | 8 +- .../keycloak/avatar/AvatarAdminResource.java | 26 +++--- .../gcube/keycloak/avatar/AvatarResource.java | 80 ++++++++++++------- .../account/DeleteAccountResource.java | 18 ++--- .../mapper/D4ScienceContextMapperTest.java | 4 +- 5 files changed, 75 insertions(+), 61 deletions(-) diff --git a/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AbstractAvatarResource.java b/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AbstractAvatarResource.java index c6860f4..a90219f 100644 --- a/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AbstractAvatarResource.java +++ b/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AbstractAvatarResource.java @@ -3,10 +3,6 @@ package org.gcube.keycloak.avatar; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; -import javax.ws.rs.NotFoundException; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.StreamingOutput; - import org.apache.commons.io.IOUtils; import org.gcube.keycloak.avatar.storage.AvatarStorageProvider; import org.gcube.keycloak.avatar.storage.file.FileAvatarStorageProvider; @@ -18,6 +14,10 @@ import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager.AuthResult; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.StreamingOutput; + public abstract class AbstractAvatarResource { protected static final Logger logger = Logger.getLogger(AbstractAvatarResource.class); diff --git a/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AvatarAdminResource.java b/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AvatarAdminResource.java index 378764a..a6c66ff 100644 --- a/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AvatarAdminResource.java +++ b/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AvatarAdminResource.java @@ -2,19 +2,6 @@ package org.gcube.keycloak.avatar; import java.io.InputStream; -import javax.ws.rs.Consumes; -import javax.ws.rs.ForbiddenException; -import javax.ws.rs.GET; -import javax.ws.rs.NotAuthorizedException; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - import org.gcube.keycloak.avatar.storage.AvatarStorageProvider; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput; @@ -23,6 +10,19 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.ForbiddenException; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.NotAuthorizedException; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + public class AvatarAdminResource extends AbstractAvatarResource { @Context diff --git a/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AvatarResource.java b/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AvatarResource.java index 692bf7e..4bb907c 100644 --- a/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AvatarResource.java +++ b/avatar-realm-resource/src/main/java/org/gcube/keycloak/avatar/AvatarResource.java @@ -3,28 +3,31 @@ package org.gcube.keycloak.avatar; import java.io.InputStream; import java.util.List; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.NotAuthorizedException; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; - import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput; import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.keycloak.http.HttpRequest; import org.keycloak.models.KeycloakSession; +import org.keycloak.services.resources.Cors; import org.keycloak.services.resources.RealmsResource; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.NotAuthorizedException; +import jakarta.ws.rs.OPTIONS; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; +import jakarta.ws.rs.core.UriInfo; + public class AvatarResource extends AbstractAvatarResource { -// public static final String STATE_CHECKER_ATTRIBUTE = "state_checker"; -// public static final String STATE_CHECKER_PARAMETER = "stateChecker"; + // public static final String STATE_CHECKER_ATTRIBUTE = "state_checker"; + // public static final String STATE_CHECKER_PARAMETER = "stateChecker"; public AvatarResource(KeycloakSession session) { super(session); @@ -38,15 +41,34 @@ public class AvatarResource extends AbstractAvatarResource { return service; } + @OPTIONS + public Response handleCorsPreflight(@Context final HttpRequest request) { + logger.info("Received CORS preflight request for ext/user; request is " + request); + return Cors.add(request, Response.ok()) + .preflight() + .allowAllOrigins() + .allowedMethods("GET", "POST", "DELETE") + .exposedHeaders("Location") + .auth() + .build(); + } + @GET @Produces({ "image/png", "image/jpeg", "image/gif" }) - public Response downloadCurrentUserAvatarImage() { + public Response downloadCurrentUserAvatarImage(@Context final HttpRequest request) { if (auth == null) { logger.debug("Unhautorized call to get avatar"); throw new NotAuthorizedException("Bearer"); } logger.debugf("Getting avatar for user %s in realm %s", auth.getUser(), auth.getSession().getRealm()); - return fetchAndCreateResponse(auth.getSession().getRealm(), auth.getUser()); + return Cors + .add(request, + Response.fromResponse(fetchAndCreateResponse(auth.getSession().getRealm(), auth.getUser()))) + .allowAllOrigins() + .allowedMethods("GET", "POST", "DELETE") + .exposedHeaders("Location") + .auth() + .build(); } @POST @@ -57,9 +79,9 @@ public class AvatarResource extends AbstractAvatarResource { throw new NotAuthorizedException("Bearer"); } -// if (!isValidStateChecker(input)) { -// throw new ForbiddenException("State"); -// } + // if (!isValidStateChecker(input)) { + // throw new ForbiddenException("State"); + // } logger.debugf("Uploading new avatar for user %s in realm %s", auth.getUser(), auth.getSession().getRealm()); Response response = null; @@ -86,15 +108,15 @@ public class AvatarResource extends AbstractAvatarResource { return response; } -// private boolean isValidStateChecker(MultipartFormDataInput input) { -// try { -// String actualStateChecker = input.getFormDataPart(STATE_CHECKER_PARAMETER, String.class, null); -// String requiredStateChecker = (String) session.getAttribute(STATE_CHECKER_ATTRIBUTE); -// -// return Objects.equals(requiredStateChecker, actualStateChecker); -// } catch (Exception ex) { -// return false; -// } -// } + // private boolean isValidStateChecker(MultipartFormDataInput input) { + // try { + // String actualStateChecker = input.getFormDataPart(STATE_CHECKER_PARAMETER, String.class, null); + // String requiredStateChecker = (String) session.getAttribute(STATE_CHECKER_ATTRIBUTE); + // + // return Objects.equals(requiredStateChecker, actualStateChecker); + // } catch (Exception ex) { + // return false; + // } + // } } \ No newline at end of file diff --git a/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountResource.java b/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountResource.java index a0a7f44..e7a9b62 100644 --- a/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountResource.java +++ b/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountResource.java @@ -2,11 +2,6 @@ package org.gcube.keycloak.account; import java.net.URI; -import javax.ws.rs.NotAuthorizedException; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.core.Response; - import org.gcube.keycloak.avatar.storage.AvatarStorageProvider; import org.gcube.keycloak.event.OrchestratorEventPublisherProviderFactory; import org.jboss.logging.Logger; @@ -18,13 +13,15 @@ import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.resources.RealmsResource; +import jakarta.ws.rs.NotAuthorizedException; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + public class DeleteAccountResource { protected static final Logger logger = Logger.getLogger(DeleteAccountResource.class); -// public static final String STATE_CHECKER_ATTRIBUTE = "state_checker"; -// public static final String STATE_CHECKER_PARAMETER = "stateChecker"; - private final KeycloakSession session; private final AuthenticationManager.AuthResult auth; @@ -37,16 +34,11 @@ public class DeleteAccountResource { @NoCache @POST() @Path("request-delete") -// public Response performDeleteAccount(@FormParam(STATE_CHECKER_PARAMETER) String stateChecker) { public Response performDeleteAccount() { if (auth == null) { logger.debug("Invoked DELETE without authorization"); throw new NotAuthorizedException("Cookie"); } -// String requiredStateChecker = session.getAttribute(STATE_CHECKER_ATTRIBUTE, String.class); -// if (!requiredStateChecker.equals(stateChecker)) { -// throw new ForbiddenException("State"); -// } logger.info("Invoked perform delete account"); logger.debug("Getting realm model from auth session"); diff --git a/protocol-mapper/src/test/java/org/gcube/keycloak/protocol/oidc/mapper/D4ScienceContextMapperTest.java b/protocol-mapper/src/test/java/org/gcube/keycloak/protocol/oidc/mapper/D4ScienceContextMapperTest.java index 0968079..3759022 100644 --- a/protocol-mapper/src/test/java/org/gcube/keycloak/protocol/oidc/mapper/D4ScienceContextMapperTest.java +++ b/protocol-mapper/src/test/java/org/gcube/keycloak/protocol/oidc/mapper/D4ScienceContextMapperTest.java @@ -9,8 +9,6 @@ import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; -import javax.ws.rs.core.HttpHeaders; - import org.junit.Test; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; @@ -26,6 +24,8 @@ import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.AccessToken; import org.mockito.Mockito; +import jakarta.ws.rs.core.HttpHeaders; + /** * Original code repo: https://github.com/mschwartau/keycloak-custom-protocol-mapper-example */