From 3a497a03c195db1ce123e665faba807c969a2da0 Mon Sep 17 00:00:00 2001 From: Geoffrey Fourmis Date: Fri, 19 Jan 2024 14:34:14 +0100 Subject: [PATCH 1/5] #9 HTTP 401 with preflight requests --- .../resource/AbstractAdminResource.java | 17 ++++++++++- .../multitenancy/resource/CorsResource.java | 29 +++++++++++++++++++ .../resource/TenantsResourceProvider.java | 9 +++++- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/main/java/dev/sultanov/keycloak/multitenancy/resource/CorsResource.java diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java index b057e1f..ae60242 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java @@ -10,6 +10,8 @@ import java.lang.reflect.Type; import org.keycloak.Config; import org.keycloak.connections.jpa.JpaConnectionProvider; +import org.keycloak.http.HttpRequest; +import org.keycloak.http.HttpResponse; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInputException; import org.keycloak.models.ClientModel; @@ -21,6 +23,7 @@ import org.keycloak.services.managers.AppAuthManager.BearerTokenAuthenticator; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.RealmManager; +import org.keycloak.services.resources.Cors; import org.keycloak.services.resources.admin.AdminAuth; import org.keycloak.services.resources.admin.AdminEventBuilder; @@ -47,6 +50,18 @@ private void setup() { setupAuth(); setupEvents(); setupProvider(); + setupCors(); + } + + private void setupCors() { + HttpRequest request = session.getContext().getHttpRequest(); + HttpResponse response = session.getContext().getHttpResponse(); + Cors.add(request) + .allowedOrigins(auth.getToken()) + .allowedMethods(CorsResource.METHODS) + .exposedHeaders("Location") + .auth() + .build(response); } private void setupAuth() { @@ -120,4 +135,4 @@ protected final void setupProvider() { this.tenantProvider = session.getProvider(TenantProvider.class); } -} \ No newline at end of file +} diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/CorsResource.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/CorsResource.java new file mode 100644 index 0000000..91bc63c --- /dev/null +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/CorsResource.java @@ -0,0 +1,29 @@ +package dev.sultanov.keycloak.multitenancy.resource; + +import jakarta.ws.rs.OPTIONS; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; +import org.keycloak.http.HttpRequest; +import org.keycloak.models.KeycloakSession; +import org.keycloak.services.resources.Cors; + +public class CorsResource { + + private final KeycloakSession session; + private final HttpRequest request; + + public CorsResource(KeycloakSession session, HttpRequest request) { + this.session = session; + this.request = request; + } + + public static final String[] METHODS = { + "GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS" + }; + + @OPTIONS + @Path("{any:.*}") + public Response preflight() { + return Cors.add(request, Response.ok()).auth().allowedMethods(METHODS).preflight().build(); + } +} diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java index 84cdcb7..408e403 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java @@ -1,5 +1,6 @@ package dev.sultanov.keycloak.multitenancy.resource; +import org.keycloak.http.HttpRequest; import org.keycloak.models.KeycloakSession; import org.keycloak.services.resource.RealmResourceProvider; @@ -11,9 +12,15 @@ public TenantsResourceProvider(KeycloakSession session) { this.session = session; } + @Override public Object getResource() { - return new TenantsResource(session); + HttpRequest request = session.getContext().getHttpRequest(); + if (request != null && "OPTIONS".equals(request.getHttpMethod())) { + return new CorsResource(session, request); + } else { + return new TenantsResource(session); + } } @Override From 2a12c8f9d23ed7d89127f06fdc3f78eb082b6023 Mon Sep 17 00:00:00 2001 From: Geoffrey Fourmis Date: Fri, 19 Jan 2024 22:05:19 +0100 Subject: [PATCH 2/5] feedback --- .../multitenancy/resource/CorsResource.java | 13 +++++-------- .../resource/TenantsResourceProvider.java | 3 +-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/CorsResource.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/CorsResource.java index 91bc63c..a05d8fe 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/CorsResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/CorsResource.java @@ -4,23 +4,20 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.core.Response; import org.keycloak.http.HttpRequest; -import org.keycloak.models.KeycloakSession; import org.keycloak.services.resources.Cors; public class CorsResource { - private final KeycloakSession session; + public static final String[] METHODS = { + "GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS" + }; + private final HttpRequest request; - public CorsResource(KeycloakSession session, HttpRequest request) { - this.session = session; + public CorsResource(HttpRequest request) { this.request = request; } - public static final String[] METHODS = { - "GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS" - }; - @OPTIONS @Path("{any:.*}") public Response preflight() { diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java index 408e403..1273c2a 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java @@ -12,12 +12,11 @@ public TenantsResourceProvider(KeycloakSession session) { this.session = session; } - @Override public Object getResource() { HttpRequest request = session.getContext().getHttpRequest(); if (request != null && "OPTIONS".equals(request.getHttpMethod())) { - return new CorsResource(session, request); + return new CorsResource(request); } else { return new TenantsResource(session); } From b417501a363ad4337ee2fa53164bd7bf5e8c567a Mon Sep 17 00:00:00 2001 From: Geoffrey Fourmis Date: Sat, 20 Jan 2024 03:54:02 +0100 Subject: [PATCH 3/5] setup should run only once for a request to avoid duplicate headers --- .../keycloak/multitenancy/resource/AbstractAdminResource.java | 3 +-- .../multitenancy/resource/TenantsResourceProvider.java | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java index ae60242..d9d2526 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java @@ -43,10 +43,9 @@ public abstract class AbstractAdminResource { public AbstractAdminResource(KeycloakSession session) { this.session = session; this.realm = session.getContext().getRealm(); - setup(); } - private void setup() { + public final void setup() { setupAuth(); setupEvents(); setupProvider(); diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java index 1273c2a..adfac15 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java @@ -18,7 +18,9 @@ public Object getResource() { if (request != null && "OPTIONS".equals(request.getHttpMethod())) { return new CorsResource(request); } else { - return new TenantsResource(session); + TenantsResource resource = new TenantsResource(session); + resource.setup(); + return resource; } } From d6363c6e31335559033ceb03ae5ba393fc97f963 Mon Sep 17 00:00:00 2001 From: Geoffrey Fourmis Date: Sat, 20 Jan 2024 05:34:23 +0100 Subject: [PATCH 4/5] setup should run only once for a request --- .../multitenancy/resource/AbstractAdminResource.java | 11 ++++++++++- .../resource/TenantInvitationsResource.java | 4 ++-- .../resource/TenantMembershipsResource.java | 4 ++-- .../multitenancy/resource/TenantResource.java | 8 ++++---- .../multitenancy/resource/TenantsResource.java | 2 +- .../resource/TenantsResourceProvider.java | 4 +--- 6 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java index d9d2526..cd41a72 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java @@ -43,9 +43,18 @@ public abstract class AbstractAdminResource { public AbstractAdminResource(KeycloakSession session) { this.session = session; this.realm = session.getContext().getRealm(); + this.setup(); } - public final void setup() { + protected AbstractAdminResource(AbstractAdminResource parent) { + this.session = parent.session; + this.realm = parent.realm; + this.auth = parent.auth; + this.adminEvent = parent.adminEvent; + this.user = parent.user; + } + + private void setup() { setupAuth(); setupEvents(); setupProvider(); diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantInvitationsResource.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantInvitationsResource.java index ffa7147..a1fa4cb 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantInvitationsResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantInvitationsResource.java @@ -43,8 +43,8 @@ public class TenantInvitationsResource extends AbstractAdminResource parent, TenantModel tenant) { + super(parent); this.tenant = tenant; } diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantMembershipsResource.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantMembershipsResource.java index c9c98ad..9b77640 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantMembershipsResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantMembershipsResource.java @@ -30,8 +30,8 @@ public class TenantMembershipsResource extends AbstractAdminResource parent, TenantModel tenant) { + super(parent); this.tenant = tenant; } diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantResource.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantResource.java index 8f22111..a063649 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantResource.java @@ -15,8 +15,8 @@ public class TenantResource extends AbstractAdminResource { private final TenantModel tenant; - public TenantResource(KeycloakSession session, TenantModel tenant) { - super(session); + public TenantResource(AbstractAdminResource parent, TenantModel tenant) { + super(parent); this.tenant = tenant; } @@ -39,11 +39,11 @@ public void deleteTenant() { @Path("invitations") public TenantInvitationsResource invitations() { - return new TenantInvitationsResource(session, tenant); + return new TenantInvitationsResource(this, tenant); } @Path("memberships") public TenantMembershipsResource memberships() { - return new TenantMembershipsResource(session, tenant); + return new TenantMembershipsResource(this, tenant); } } diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResource.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResource.java index 8226360..c2eaf8a 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResource.java @@ -76,7 +76,7 @@ public TenantResource getTenantResource(@PathParam("tenantId") String tenantId) if (!auth.isTenantAdmin(model)) { throw new NotAuthorizedException(String.format("Insufficient permission to access %s", tenantId)); } else { - return new TenantResource(session, model); + return new TenantResource(this, model); } } } diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java index adfac15..1273c2a 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java @@ -18,9 +18,7 @@ public Object getResource() { if (request != null && "OPTIONS".equals(request.getHttpMethod())) { return new CorsResource(request); } else { - TenantsResource resource = new TenantsResource(session); - resource.setup(); - return resource; + return new TenantsResource(session); } } From 1b5c4b4a600124170f96792a0c815a61e9ead7ab Mon Sep 17 00:00:00 2001 From: Geoffrey Fourmis Date: Mon, 22 Jan 2024 16:50:58 +0100 Subject: [PATCH 5/5] missing copy --- .../keycloak/multitenancy/resource/AbstractAdminResource.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java index cd41a72..82364ca 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java @@ -52,6 +52,8 @@ protected AbstractAdminResource(AbstractAdminResource parent) { this.auth = parent.auth; this.adminEvent = parent.adminEvent; this.user = parent.user; + this.entityManager = parent.entityManager; + this.tenantProvider = parent.tenantProvider; } private void setup() {