diff --git a/core/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java b/core/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java index 450a4ec7ab..3b2cc9c11a 100755 --- a/core/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java +++ b/core/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java @@ -118,6 +118,15 @@ public class OIDCConfigurationRepresentation { @JsonProperty("tls_client_certificate_bound_access_tokens") private Boolean tlsClientCertificateBoundAccessTokens; + @JsonProperty("revocation_endpoint") + private String revocationEndpoint; + + @JsonProperty("revocation_endpoint_auth_methods_supported") + private List revocationEndpointAuthMethodsSupported; + + @JsonProperty("revocation_endpoint_auth_signing_alg_values_supported") + private List revocationEndpointAuthSigningAlgValuesSupported; + protected Map otherClaims = new HashMap(); public String getIssuer() { @@ -347,6 +356,30 @@ public class OIDCConfigurationRepresentation { this.tlsClientCertificateBoundAccessTokens = tlsClientCertificateBoundAccessTokens; } + public String getRevocationEndpoint() { + return revocationEndpoint; + } + + public void setRevocationEndpoint(String revocationEndpoint) { + this.revocationEndpoint = revocationEndpoint; + } + + public List getRevocationEndpointAuthMethodsSupported() { + return revocationEndpointAuthMethodsSupported; + } + + public void setRevocationEndpointAuthMethodsSupported(List revocationEndpointAuthMethodsSupported) { + this.revocationEndpointAuthMethodsSupported = revocationEndpointAuthMethodsSupported; + } + + public List getRevocationEndpointAuthSigningAlgValuesSupported() { + return revocationEndpointAuthSigningAlgValuesSupported; + } + + public void setRevocationEndpointAuthSigningAlgValuesSupported(List revocationEndpointAuthSigningAlgValuesSupported) { + this.revocationEndpointAuthSigningAlgValuesSupported = revocationEndpointAuthSigningAlgValuesSupported; + } + @JsonAnyGetter public Map getOtherClaims() { return otherClaims; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java index 9ab18a79d5..66168507e6 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java @@ -43,6 +43,7 @@ import org.keycloak.wellknown.WellKnownProvider; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; +import java.net.URI; import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -134,6 +135,14 @@ public class OIDCWellKnownProvider implements WellKnownProvider { // https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.2 config.setTlsClientCertificateBoundAccessTokens(true); + URI revocationEndpoint = frontendUriBuilder.clone().path(OIDCLoginProtocolService.class, "revoke") + .build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL); + if (isHttps(revocationEndpoint)) { + config.setRevocationEndpoint(revocationEndpoint.toString()); + config.setRevocationEndpointAuthMethodsSupported(getClientAuthMethodsSupported()); + config.setRevocationEndpointAuthSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(false)); + } + return config; } @@ -200,4 +209,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider { } return result; } + + private boolean isHttps(URI uri) { + return uri.getScheme().equals("https"); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java index 2c763d80b7..36b7aebba9 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java @@ -58,6 +58,7 @@ import java.net.URI; import java.util.List; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; /** * @author Marek Posolda @@ -102,7 +103,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { public void testDiscovery() { Client client = ClientBuilder.newClient(); try { - OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client); + OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT); // URIs are filled assertEquals(oidcConfig.getAuthorizationEndpoint(), OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT)).build("test").toString()); @@ -161,6 +162,28 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { // https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.2 Assert.assertTrue(oidcConfig.getTlsClientCertificateBoundAccessTokens()); + // Token Revocation + assertEquals(oidcConfig.getRevocationEndpoint(), oauth.getTokenRevocationUrl()); + Assert.assertNames(oidcConfig.getRevocationEndpointAuthMethodsSupported(), "client_secret_basic", + "client_secret_post", "private_key_jwt", "client_secret_jwt", "tls_client_auth"); + Assert.assertNames(oidcConfig.getRevocationEndpointAuthSigningAlgValuesSupported(), Algorithm.PS256, + Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, + Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512); + } finally { + client.close(); + } + } + + @Test + public void testHttpDiscovery() { + Client client = ClientBuilder.newClient(); + try { + OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, "http://localhost:8180/auth"); + + // Token Revocation + assertNull(oidcConfig.getRevocationEndpoint()); + Assert.assertNull(oidcConfig.getRevocationEndpointAuthMethodsSupported()); + Assert.assertNull(oidcConfig.getRevocationEndpointAuthSigningAlgValuesSupported()); } finally { client.close(); } @@ -175,7 +198,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { Client client = ClientBuilder.newClient(); try { - OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client); + OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT); // assert issuer matches assertEquals(idToken.getIssuer(), oidcConfig.getIssuer()); @@ -214,23 +237,25 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { public void testIntrospectionEndpointClaim() throws IOException { Client client = ClientBuilder.newClient(); try { - ObjectNode oidcConfig = JsonSerialization.readValue(getOIDCDiscoveryConfiguration(client), ObjectNode.class); - assertEquals(oidcConfig.get("introspection_endpoint").asText(), getOIDCDiscoveryRepresentation(client).getIntrospectionEndpoint()); + ObjectNode oidcConfig = JsonSerialization + .readValue(getOIDCDiscoveryConfiguration(client, OAuthClient.AUTH_SERVER_ROOT), ObjectNode.class); + assertEquals(oidcConfig.get("introspection_endpoint").asText(), + getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT).getIntrospectionEndpoint()); } finally { client.close(); } } - private OIDCConfigurationRepresentation getOIDCDiscoveryRepresentation(Client client) { + private OIDCConfigurationRepresentation getOIDCDiscoveryRepresentation(Client client, String uriTemplate) { try { - return JsonSerialization.readValue(getOIDCDiscoveryConfiguration(client), OIDCConfigurationRepresentation.class); + return JsonSerialization.readValue(getOIDCDiscoveryConfiguration(client, uriTemplate), OIDCConfigurationRepresentation.class); } catch (IOException cause) { throw new RuntimeException("Failed to parse OIDC configuration", cause); } } - private String getOIDCDiscoveryConfiguration(Client client) { - UriBuilder builder = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT); + private String getOIDCDiscoveryConfiguration(Client client, String uriTemplate) { + UriBuilder builder = UriBuilder.fromUri(uriTemplate); URI oidcDiscoveryUri = RealmsResource.wellKnownProviderUrl(builder).build("test", OIDCWellKnownProviderFactory.PROVIDER_ID); WebTarget oidcDiscoveryTarget = client.target(oidcDiscoveryUri);