diff --git a/README.md b/README.md index 75b0841..852f870 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ the created users will be tenantless and asked to create a new tenant. To address this issue, this extension introduces the concept of `tenant-specific IDPs` and an additional authenticator that facilitates the creation of required memberships. To configure an IDP as tenant-specific, tenants' IDs should be added to the `multi-tenancy.tenants` configuration attribute of the IDP as a **comma-separated list**. -This can be achieved using the standard [Keycloak REST API](https://www.keycloak.org/docs-api/22.0.5/rest-api/index.html#_identity_providers). +This can be achieved using the standard [Keycloak REST API](https://www.keycloak.org/docs-api/23.0.1/rest-api/index.html#_identity_providers). > **_Note_** > - _With tenant-specific IDP configuration, the IDP limits access to only the tenants listed in the configuration. diff --git a/pom.xml b/pom.xml index bfa9bbd..1d4e640 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ - 22.0.5 + 23.0.1 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 5df7f92..b057e1f 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/AbstractAdminResource.java @@ -4,14 +4,11 @@ import jakarta.persistence.EntityManager; import jakarta.ws.rs.NotAuthorizedException; import jakarta.ws.rs.NotFoundException; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.HttpHeaders; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import org.keycloak.Config; -import org.keycloak.common.ClientConnection; import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInputException; @@ -29,13 +26,6 @@ public abstract class AbstractAdminResource { - @Context - protected ClientConnection clientConnection; - - @Context - private HttpHeaders headers; - - @Context protected KeycloakSession session; protected final RealmModel realm; @@ -47,18 +37,20 @@ public abstract class AbstractAdminResource { protected TenantProvider tenantProvider; - public AbstractAdminResource(RealmModel realm) { - this.realm = realm; + public AbstractAdminResource(KeycloakSession session) { + this.session = session; + this.realm = session.getContext().getRealm(); + setup(); } - public void setup() { + private void setup() { setupAuth(); setupEvents(); setupProvider(); } private void setupAuth() { - String tokenString = AppAuthManager.extractAuthorizationHeaderToken(headers); + var tokenString = AppAuthManager.extractAuthorizationHeaderToken(session.getContext().getRequestHeaders()); if (tokenString == null) { throw new NotAuthorizedException("Bearer"); @@ -73,9 +65,9 @@ private void setupAuth() { throw new NotAuthorizedException("Bearer token format error"); } - String realmName = token.getIssuer().substring(token.getIssuer().lastIndexOf('/') + 1); - RealmManager realmManager = new RealmManager(session); - RealmModel realm = realmManager.getRealmByName(realmName); + var realmName = token.getIssuer().substring(token.getIssuer().lastIndexOf('/') + 1); + var realmManager = new RealmManager(session); + var realm = realmManager.getRealmByName(realmName); if (realm == null) { throw new NotAuthorizedException("Unknown realm in token"); @@ -84,8 +76,8 @@ private void setupAuth() { var bearerTokenAuthenticator = new BearerTokenAuthenticator(session); bearerTokenAuthenticator.setRealm(realm); bearerTokenAuthenticator.setUriInfo(session.getContext().getUri()); - bearerTokenAuthenticator.setConnection(clientConnection); - bearerTokenAuthenticator.setHeaders(headers); + bearerTokenAuthenticator.setConnection(session.getContext().getConnection()); + bearerTokenAuthenticator.setHeaders(session.getContext().getRequestHeaders()); AuthenticationManager.AuthResult authResult = bearerTokenAuthenticator.authenticate(); if (authResult == null) { throw new NotAuthorizedException("Bearer"); 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 0f64b40..ffa7147 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantInvitationsResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantInvitationsResource.java @@ -35,7 +35,7 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.keycloak.events.admin.OperationType; import org.keycloak.models.Constants; -import org.keycloak.models.RealmModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; @@ -43,8 +43,8 @@ public class TenantInvitationsResource extends AbstractAdminResource { private final TenantModel tenant; - public TenantMembershipsResource(RealmModel realm, TenantModel tenant) { - super(realm); + public TenantMembershipsResource(KeycloakSession session, TenantModel tenant) { + super(session); 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 80a6a9b..8f22111 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantResource.java @@ -8,16 +8,15 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.openapi.annotations.Operation; -import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.events.admin.OperationType; -import org.keycloak.models.RealmModel; +import org.keycloak.models.KeycloakSession; public class TenantResource extends AbstractAdminResource { private final TenantModel tenant; - public TenantResource(RealmModel realm, TenantModel tenant) { - super(realm); + public TenantResource(KeycloakSession session, TenantModel tenant) { + super(session); this.tenant = tenant; } @@ -40,17 +39,11 @@ public void deleteTenant() { @Path("invitations") public TenantInvitationsResource invitations() { - TenantInvitationsResource resource = new TenantInvitationsResource(realm, tenant); - ResteasyProviderFactory.getInstance().injectProperties(resource); - resource.setup(); - return resource; + return new TenantInvitationsResource(session, tenant); } @Path("memberships") public TenantMembershipsResource memberships() { - TenantMembershipsResource resource = new TenantMembershipsResource(realm, tenant); - ResteasyProviderFactory.getInstance().injectProperties(resource); - resource.setup(); - return resource; + return new TenantMembershipsResource(session, 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 a8c1e9e..8226360 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResource.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResource.java @@ -22,15 +22,14 @@ import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; -import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.events.admin.OperationType; import org.keycloak.models.Constants; -import org.keycloak.models.RealmModel; +import org.keycloak.models.KeycloakSession; public class TenantsResource extends AbstractAdminResource { - public TenantsResource(RealmModel realm) { - super(realm); + public TenantsResource(KeycloakSession session) { + super(session); } @POST @@ -77,10 +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 { - TenantResource resource = new TenantResource(realm, model); - ResteasyProviderFactory.getInstance().injectProperties(resource); - resource.setup(); - return resource; + return new TenantResource(session, 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 10c71a5..84cdcb7 100644 --- a/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java +++ b/src/main/java/dev/sultanov/keycloak/multitenancy/resource/TenantsResourceProvider.java @@ -1,8 +1,6 @@ package dev.sultanov.keycloak.multitenancy.resource; -import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; import org.keycloak.services.resource.RealmResourceProvider; public class TenantsResourceProvider implements RealmResourceProvider { @@ -15,11 +13,7 @@ public TenantsResourceProvider(KeycloakSession session) { @Override public Object getResource() { - RealmModel realm = session.getContext().getRealm(); - TenantsResource resource = new TenantsResource(realm); - ResteasyProviderFactory.getInstance().injectProperties(resource); - resource.setup(); - return resource; + return new TenantsResource(session); } @Override diff --git a/src/test/java/dev/sultanov/keycloak/multitenancy/support/BaseIntegrationTest.java b/src/test/java/dev/sultanov/keycloak/multitenancy/support/BaseIntegrationTest.java index fc4f439..4f51ee8 100644 --- a/src/test/java/dev/sultanov/keycloak/multitenancy/support/BaseIntegrationTest.java +++ b/src/test/java/dev/sultanov/keycloak/multitenancy/support/BaseIntegrationTest.java @@ -18,7 +18,7 @@ public class BaseIntegrationTest { private static final Integer MAILHOG_HTTP_PORT = 8025; private static final Network network = Network.newNetwork(); - private static final KeycloakContainer keycloak = new KeycloakContainer("quay.io/keycloak/keycloak:22.0.5") + private static final KeycloakContainer keycloak = new KeycloakContainer("quay.io/keycloak/keycloak:23.0.1") .withRealmImportFiles("/realm-export.json", "/idp-realm-export.json") .withProviderClassesFrom("target/classes") .withNetwork(network) diff --git a/src/test/java/dev/sultanov/keycloak/multitenancy/support/browser/SignInPage.java b/src/test/java/dev/sultanov/keycloak/multitenancy/support/browser/SignInPage.java index ffabbdf..bc3dcd3 100644 --- a/src/test/java/dev/sultanov/keycloak/multitenancy/support/browser/SignInPage.java +++ b/src/test/java/dev/sultanov/keycloak/multitenancy/support/browser/SignInPage.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.microsoft.playwright.Page; +import com.microsoft.playwright.Page.GetByLabelOptions; import com.microsoft.playwright.options.AriaRole; public class SignInPage extends AbstractPage { @@ -24,7 +25,7 @@ public SelectLoginMethodPage tryAnotherWay() { public SignInPage fillCredentials(String email, String password) { page.getByLabel("Email").fill(email); - page.getByLabel("Password").fill(password); + page.getByLabel("Password", new GetByLabelOptions().setExact(true)).fill(password); return this; } diff --git a/src/test/resources/idp-realm-export.json b/src/test/resources/idp-realm-export.json index 6a7202d..4af5c1d 100644 --- a/src/test/resources/idp-realm-export.json +++ b/src/test/resources/idp-realm-export.json @@ -1794,7 +1794,6 @@ "cibaInterval" : "5", "realmReusableOtpCode" : "false" }, - "keycloakVersion" : "22.0.5", "userManagedAccessAllowed" : false, "clientProfiles" : { "profiles" : [ ]