Issue 29627: Expose Authorization Server Metadata Endpoint under /.well-known/oauth-authorization-server to comply with rfc8414 (#29628)
closes #29627 Signed-off-by: Francis Pouatcha <francis.pouatcha@adorsys.com> Signed-off-by: Takashi Norimatsu <takashi.norimatsu.ws@hitachi.com> Co-authored-by: Takashi Norimatsu <takashi.norimatsu.ws@hitachi.com>
This commit is contained in:
parent
f7044ba5c2
commit
542fc65923
5 changed files with 485 additions and 387 deletions
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/package org.keycloak.protocol.oauth2;
|
||||
|
||||
import org.keycloak.protocol.oidc.OIDCWellKnownProviderFactory;
|
||||
import org.keycloak.wellknown.WellKnownProviderFactory;
|
||||
|
||||
/**
|
||||
* {@link OAuth2WellKnownProviderFactory} implementation for the OAuth2 auto discovery
|
||||
* <p>
|
||||
* {@see https://www.rfc-editor.org/rfc/rfc8414.html#section-3}
|
||||
*
|
||||
* @author <a href="mailto:francis.pouatcha@adorsys.com">Francis Pouatcha</a>
|
||||
*/
|
||||
public class OAuth2WellKnownProviderFactory extends OIDCWellKnownProviderFactory {
|
||||
public static final String PROVIDER_ID = "oauth-authorization-server";
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
}
|
|
@ -18,3 +18,4 @@
|
|||
org.keycloak.protocol.oidc.OIDCWellKnownProviderFactory
|
||||
org.keycloak.authorization.config.UmaWellKnownProviderFactory
|
||||
org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProviderFactory
|
||||
org.keycloak.protocol.oauth2.OAuth2WellKnownProviderFactory
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.oauth;
|
||||
|
||||
import org.keycloak.protocol.oauth2.OAuth2WellKnownProviderFactory;
|
||||
import org.keycloak.testsuite.oidc.AbstractWellKnownProviderTest;
|
||||
|
||||
public class OAuth2WellKnownProviderTest extends AbstractWellKnownProviderTest {
|
||||
protected String getWellKnownProviderId() {
|
||||
return OAuth2WellKnownProviderFactory.PROVIDER_ID;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,418 @@
|
|||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.oidc;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import jakarta.ws.rs.client.Client;
|
||||
import jakarta.ws.rs.client.Invocation;
|
||||
import jakarta.ws.rs.client.WebTarget;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.UriBuilder;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.jose.jwe.JWEConstants;
|
||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.protocol.oidc.representations.MTLSEndpointAliases;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationService;
|
||||
import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.admin.AbstractAdminTest;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.broker.util.SimpleHttpDefault;
|
||||
import org.keycloak.testsuite.forms.BrowserFlowTest;
|
||||
import org.keycloak.testsuite.forms.LevelOfAssuranceFlowTest;
|
||||
import org.keycloak.testsuite.util.AdminClientUtil;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.TokenSignatureUtil;
|
||||
import org.keycloak.testsuite.wellknown.CustomOIDCWellKnownProviderFactory;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public abstract class AbstractWellKnownProviderTest extends AbstractKeycloakTest {
|
||||
|
||||
private CloseableHttpClient client;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
client = HttpClientBuilder.create().build();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
try {
|
||||
client.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = AbstractAdminTest.loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
|
||||
testRealms.add(realm);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void clientConfiguration() {
|
||||
ClientManager.realm(adminClient.realm("test")).clientId("test-app").directAccessGrant(true);
|
||||
/*
|
||||
* Configure the default client ID. Seems like OAuthClient is keeping the state of clientID
|
||||
* For example: If some test case configure oauth.clientId("sample-public-client"), other tests
|
||||
* will faile and the clientID will always be "sample-public-client
|
||||
* @see AccessTokenTest#testAuthorizationNegotiateHeaderIgnored()
|
||||
*/
|
||||
oauth.clientId("test-app");
|
||||
}
|
||||
|
||||
abstract protected String getWellKnownProviderId();
|
||||
|
||||
@Test
|
||||
public void testDiscovery() {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
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());
|
||||
assertEquals(oidcConfig.getTokenEndpoint(), oauth.getAccessTokenUrl());
|
||||
assertEquals(oidcConfig.getUserinfoEndpoint(), OIDCLoginProtocolService.userInfoUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT)).build("test").toString());
|
||||
assertEquals(oidcConfig.getJwksUri(), oauth.getCertsUrl("test"));
|
||||
|
||||
|
||||
String registrationUri = UriBuilder
|
||||
.fromUri(OAuthClient.AUTH_SERVER_ROOT)
|
||||
.path(RealmsResource.class)
|
||||
.path(RealmsResource.class, "getClientsService")
|
||||
.path(ClientRegistrationService.class, "provider")
|
||||
.build("test", OIDCClientRegistrationProviderFactory.ID)
|
||||
.toString();
|
||||
assertEquals(oidcConfig.getRegistrationEndpoint(), registrationUri);
|
||||
|
||||
// Support standard + implicit + hybrid flow
|
||||
assertContains(oidcConfig.getResponseTypesSupported(), OAuth2Constants.CODE, OIDCResponseType.ID_TOKEN, "id_token token", "code id_token", "code token", "code id_token token");
|
||||
assertEquals(7, oidcConfig.getGrantTypesSupported().size());
|
||||
assertContains(oidcConfig.getGrantTypesSupported(), OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT,
|
||||
OAuth2Constants.DEVICE_CODE_GRANT_TYPE);
|
||||
assertContains(oidcConfig.getResponseModesSupported(), "query", "fragment", "form_post", "jwt", "query.jwt", "fragment.jwt", "form_post.jwt");
|
||||
|
||||
Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "pairwise", "public");
|
||||
|
||||
// Signature algorithms
|
||||
Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
Assert.assertNames(oidcConfig.getAuthorizationSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
|
||||
// request object encryption algorithms
|
||||
Assert.assertNames(oidcConfig.getRequestObjectEncryptionAlgValuesSupported(), JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256, JWEConstants.RSA1_5);
|
||||
Assert.assertNames(oidcConfig.getRequestObjectEncryptionEncValuesSupported(), JWEConstants.A256GCM, JWEConstants.A192GCM, JWEConstants.A128GCM, JWEConstants.A128CBC_HS256, JWEConstants.A192CBC_HS384, JWEConstants.A256CBC_HS512);
|
||||
|
||||
// Encryption algorithms
|
||||
Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
|
||||
Assert.assertNames(oidcConfig.getIdTokenEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
|
||||
Assert.assertNames(oidcConfig.getAuthorizationEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
|
||||
Assert.assertNames(oidcConfig.getAuthorizationEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
|
||||
Assert.assertNames(oidcConfig.getUserInfoEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
|
||||
Assert.assertNames(oidcConfig.getUserInfoEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
|
||||
|
||||
// Client authentication
|
||||
Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt", "client_secret_jwt", "tls_client_auth");
|
||||
Assert.assertNames(oidcConfig.getTokenEndpointAuthSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
// NOTE: Those are overriden in "oidc-well-known-config-override.json" and they are tested in testDefaultProviderCustomizations
|
||||
//Assert.assertNames(oidcConfig.getIntrospectionEndpointAuthMethodsSupported(), "private_key_jwt", "client_secret_jwt", "tls_client_auth", "custom_nonexisting_authenticator");
|
||||
Assert.assertNames(oidcConfig.getIntrospectionEndpointAuthSigningAlgValuesSupported(), Algorithm.PS256,
|
||||
Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256,
|
||||
Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
|
||||
// Claims
|
||||
assertContains(oidcConfig.getClaimsSupported(), IDToken.NAME, IDToken.EMAIL, IDToken.PREFERRED_USERNAME, IDToken.FAMILY_NAME, IDToken.ACR);
|
||||
Assert.assertNames(oidcConfig.getClaimTypesSupported(), "normal");
|
||||
Assert.assertTrue(oidcConfig.getClaimsParameterSupported());
|
||||
|
||||
// Scopes supported
|
||||
assertScopesSupportedMatchesWithRealm(oidcConfig);
|
||||
|
||||
// Request and Request_Uri
|
||||
Assert.assertTrue(oidcConfig.getRequestParameterSupported());
|
||||
Assert.assertTrue(oidcConfig.getRequestUriParameterSupported());
|
||||
Assert.assertTrue(oidcConfig.getRequireRequestUriRegistration());
|
||||
|
||||
// KEYCLOAK-7451 OAuth Authorization Server Metadata for Proof Key for Code Exchange
|
||||
// PKCE support
|
||||
Assert.assertNames(oidcConfig.getCodeChallengeMethodsSupported(), OAuth2Constants.PKCE_METHOD_PLAIN, OAuth2Constants.PKCE_METHOD_S256);
|
||||
|
||||
// KEYCLOAK-6771 Certificate Bound Token
|
||||
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.2
|
||||
Assert.assertTrue(oidcConfig.getTlsClientCertificateBoundAccessTokens());
|
||||
MTLSEndpointAliases mtlsEndpointAliases = oidcConfig.getMtlsEndpointAliases();
|
||||
Assert.assertEquals(oidcConfig.getTokenEndpoint(), mtlsEndpointAliases.getTokenEndpoint());
|
||||
Assert.assertEquals(oidcConfig.getRevocationEndpoint(), mtlsEndpointAliases.getRevocationEndpoint());
|
||||
|
||||
// CIBA
|
||||
assertEquals(oidcConfig.getBackchannelAuthenticationEndpoint(), oauth.getBackchannelAuthenticationUrl());
|
||||
assertContains(oidcConfig.getGrantTypesSupported(), OAuth2Constants.CIBA_GRANT_TYPE);
|
||||
Assert.assertNames(oidcConfig.getBackchannelTokenDeliveryModesSupported(), "poll", "ping");
|
||||
Assert.assertNames(oidcConfig.getBackchannelAuthenticationRequestSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.EdDSA);
|
||||
|
||||
Assert.assertTrue(oidcConfig.getBackchannelLogoutSupported());
|
||||
Assert.assertTrue(oidcConfig.getBackchannelLogoutSessionSupported());
|
||||
|
||||
// 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, Algorithm.EdDSA);
|
||||
|
||||
assertEquals(oidcConfig.getDeviceAuthorizationEndpoint(), oauth.getDeviceAuthorizationUrl());
|
||||
|
||||
// Pushed Authorization Request (PAR)
|
||||
assertEquals(oauth.getParEndpointUrl(), oidcConfig.getPushedAuthorizationRequestEndpoint());
|
||||
assertEquals(Boolean.FALSE, oidcConfig.getRequirePushedAuthorizationRequests());
|
||||
|
||||
// frontchannel logout
|
||||
assertTrue(oidcConfig.getFrontChannelLogoutSessionSupported());
|
||||
assertTrue(oidcConfig.getFrontChannelLogoutSupported());
|
||||
|
||||
// DPoP - negative test for preview profile - see testDpopSigningAlgValuesSupportedWithDpop for actual test
|
||||
assertNull("dpop_signing_alg_values_supported should not be present unless DPoP feature is enabled",
|
||||
oidcConfig.getDpopSigningAlgValuesSupported());
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpDiscovery() {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, "http://localhost:8180/auth");
|
||||
|
||||
Assert.assertNotNull(oidcConfig.getJwksUri());
|
||||
|
||||
// Token Revocation
|
||||
Assert.assertNotNull(oidcConfig.getRevocationEndpoint());
|
||||
Assert.assertNotNull(oidcConfig.getRevocationEndpointAuthMethodsSupported());
|
||||
Assert.assertNotNull(oidcConfig.getRevocationEndpointAuthSigningAlgValuesSupported());
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIssuerMatches() throws Exception {
|
||||
OAuthClient.AuthorizationEndpointResponse authzResp = oauth.doLogin("test-user@localhost", "password");
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(authzResp.getCode(), "password");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
|
||||
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
|
||||
// assert issuer matches
|
||||
assertEquals(idToken.getIssuer(), oidcConfig.getIssuer());
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void corsTest() {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
UriBuilder builder = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT);
|
||||
URI oidcDiscoveryUri = RealmsResource.wellKnownProviderUrl(builder).build("test", getWellKnownProviderId());
|
||||
WebTarget oidcDiscoveryTarget = client.target(oidcDiscoveryUri);
|
||||
|
||||
|
||||
Invocation.Builder request = oidcDiscoveryTarget.request();
|
||||
request.header(Cors.ORIGIN_HEADER, "http://somehost");
|
||||
Response response = request.get();
|
||||
|
||||
assertEquals("http://somehost", response.getHeaders().getFirst(Cors.ACCESS_CONTROL_ALLOW_ORIGIN));
|
||||
|
||||
|
||||
Invocation.Builder nullRequest = oidcDiscoveryTarget.request();
|
||||
nullRequest.header(Cors.ORIGIN_HEADER, "null");
|
||||
Response nullResponse = nullRequest.get();
|
||||
|
||||
assertEquals("null", nullResponse.getHeaders().getFirst(Cors.ACCESS_CONTROL_ALLOW_ORIGIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void certs() throws IOException {
|
||||
TokenSignatureUtil.registerKeyProvider(Algorithm.ES256, adminClient, testContext);
|
||||
|
||||
OIDCConfigurationRepresentation representation = SimpleHttpDefault.doGet(getAuthServerRoot().toString() + "realms/test/.well-known/openid-configuration", client).asJson(OIDCConfigurationRepresentation.class);
|
||||
String jwksUri = representation.getJwksUri();
|
||||
|
||||
JSONWebKeySet jsonWebKeySet = SimpleHttpDefault.doGet(jwksUri, client).asJson(JSONWebKeySet.class);
|
||||
assertEquals(3, jsonWebKeySet.getKeys().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntrospectionEndpointClaim() throws IOException {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
public void testAcrValuesSupported() throws IOException {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
// Default values when no "acr-to-loa" mapping and no authentication flow configured
|
||||
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
Assert.assertNames(oidcConfig.getAcrValuesSupported(), "0", "1");
|
||||
|
||||
// Update authentication flow and see it uses "acr" values from it
|
||||
LevelOfAssuranceFlowTest.configureStepUpFlow(testingClient);
|
||||
oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
Assert.assertNames(oidcConfig.getAcrValuesSupported(), "0", "1", "2", "3");
|
||||
|
||||
// Configure "ACR-To-Loa" mapping and check it has both configured values and numbers from authentication flow
|
||||
RealmResource testRealm = adminClient.realm("test");
|
||||
RealmRepresentation realmRep = testRealm.toRepresentation();
|
||||
Map<String, Integer> acrToLoa = new HashMap<>();
|
||||
acrToLoa.put("poor", 0);
|
||||
acrToLoa.put("silver", 1);
|
||||
acrToLoa.put("gold", 2);
|
||||
String acrToLoaAttr = JsonSerialization.writeValueAsString(acrToLoa);
|
||||
realmRep.getAttributes().put(Constants.ACR_LOA_MAP, acrToLoaAttr);
|
||||
testRealm.update(realmRep);
|
||||
|
||||
oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
Assert.assertNames(oidcConfig.getAcrValuesSupported(), "poor", "silver", "gold", "0", "1", "2", "3");
|
||||
|
||||
// Use mappings even with values not included in the authentication flow
|
||||
acrToLoa = new HashMap<>();
|
||||
acrToLoa.put("poor", 0);
|
||||
acrToLoa.put("silver", 1);
|
||||
acrToLoa.put("gold", 2);
|
||||
acrToLoa.put("platinum", 3);
|
||||
acrToLoa.put("diamond", 4);
|
||||
acrToLoaAttr = JsonSerialization.writeValueAsString(acrToLoa);
|
||||
realmRep.getAttributes().put(Constants.ACR_LOA_MAP, acrToLoaAttr);
|
||||
testRealm.update(realmRep);
|
||||
|
||||
oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
Assert.assertNames(oidcConfig.getAcrValuesSupported(), "poor", "silver", "gold", "platinum", "diamond", "0", "1", "2", "3");
|
||||
|
||||
// Revert realm and flow
|
||||
realmRep.getAttributes().remove(Constants.ACR_LOA_MAP);
|
||||
testRealm.update(realmRep);
|
||||
BrowserFlowTest.revertFlows(testRealm, "browser - Level of Authentication FLow");
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFeature(value = Profile.Feature.TOKEN_EXCHANGE, skipRestart = true)
|
||||
public void testGrantTypesSupportedWithTokenExchange() throws IOException {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
assertEquals(oidcConfig.getGrantTypesSupported().size(),8);
|
||||
assertContains(oidcConfig.getGrantTypesSupported(), OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFeature(value = Profile.Feature.DPOP, skipRestart = true)
|
||||
public void testDpopSigningAlgValuesSupportedWithDpop() throws IOException {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
|
||||
try {
|
||||
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
|
||||
// DPoP
|
||||
Assert.assertNames(oidcConfig.getDpopSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512,
|
||||
Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertScopesSupportedMatchesWithRealm(OIDCConfigurationRepresentation oidcConfig) {
|
||||
Assert.assertNames(oidcConfig.getScopesSupported(), OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS,
|
||||
OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_PHONE, OAuth2Constants.SCOPE_ADDRESS, OIDCLoginProtocolFactory.ACR_SCOPE, OIDCLoginProtocolFactory.BASIC_SCOPE,
|
||||
OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE);
|
||||
}
|
||||
|
||||
protected OIDCConfigurationRepresentation getOIDCDiscoveryRepresentation(Client client, String uriTemplate) {
|
||||
try {
|
||||
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, String uriTemplate) {
|
||||
UriBuilder builder = UriBuilder.fromUri(uriTemplate);
|
||||
URI oidcDiscoveryUri = RealmsResource.wellKnownProviderUrl(builder).build("test", this.getWellKnownProviderId());
|
||||
WebTarget oidcDiscoveryTarget = client.target(oidcDiscoveryUri);
|
||||
|
||||
Response response = oidcDiscoveryTarget.request().get();
|
||||
|
||||
assertEquals("no-cache, must-revalidate, no-transform, no-store", response.getHeaders().getFirst("Cache-Control"));
|
||||
|
||||
return response.readEntity(String.class);
|
||||
}
|
||||
|
||||
private void assertContains(List<String> actual, String... expected) {
|
||||
for (String exp : expected) {
|
||||
Assert.assertTrue(actual.contains(exp));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,351 +17,29 @@
|
|||
|
||||
package org.keycloak.testsuite.oidc;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import jakarta.ws.rs.client.Client;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.jose.jwe.JWEConstants;
|
||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.protocol.oidc.OIDCWellKnownProviderFactory;
|
||||
import org.keycloak.protocol.oidc.representations.MTLSEndpointAliases;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationService;
|
||||
import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.admin.AbstractAdminTest;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.broker.util.SimpleHttpDefault;
|
||||
import org.keycloak.testsuite.forms.BrowserFlowTest;
|
||||
import org.keycloak.testsuite.forms.LevelOfAssuranceFlowTest;
|
||||
import org.keycloak.testsuite.util.AdminClientUtil;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.TokenSignatureUtil;
|
||||
import org.keycloak.testsuite.wellknown.CustomOIDCWellKnownProviderFactory;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import jakarta.ws.rs.client.Client;
|
||||
import jakarta.ws.rs.client.Invocation;
|
||||
import jakarta.ws.rs.client.WebTarget;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.UriBuilder;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
|
||||
public class OIDCWellKnownProviderTest extends AbstractWellKnownProviderTest {
|
||||
|
||||
private CloseableHttpClient client;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
client = HttpClientBuilder.create().build();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
try {
|
||||
client.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = AbstractAdminTest.loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
|
||||
testRealms.add(realm);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void clientConfiguration() {
|
||||
ClientManager.realm(adminClient.realm("test")).clientId("test-app").directAccessGrant(true);
|
||||
/*
|
||||
* Configure the default client ID. Seems like OAuthClient is keeping the state of clientID
|
||||
* For example: If some test case configure oauth.clientId("sample-public-client"), other tests
|
||||
* will faile and the clientID will always be "sample-public-client
|
||||
* @see AccessTokenTest#testAuthorizationNegotiateHeaderIgnored()
|
||||
*/
|
||||
oauth.clientId("test-app");
|
||||
protected String getWellKnownProviderId() {
|
||||
return OIDCWellKnownProviderFactory.PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiscovery() {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
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());
|
||||
assertEquals(oidcConfig.getTokenEndpoint(), oauth.getAccessTokenUrl());
|
||||
assertEquals(oidcConfig.getUserinfoEndpoint(), OIDCLoginProtocolService.userInfoUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT)).build("test").toString());
|
||||
assertEquals(oidcConfig.getJwksUri(), oauth.getCertsUrl("test"));
|
||||
|
||||
|
||||
String registrationUri = UriBuilder
|
||||
.fromUri(OAuthClient.AUTH_SERVER_ROOT)
|
||||
.path(RealmsResource.class)
|
||||
.path(RealmsResource.class, "getClientsService")
|
||||
.path(ClientRegistrationService.class, "provider")
|
||||
.build("test", OIDCClientRegistrationProviderFactory.ID)
|
||||
.toString();
|
||||
assertEquals(oidcConfig.getRegistrationEndpoint(), registrationUri);
|
||||
|
||||
// Support standard + implicit + hybrid flow
|
||||
assertContains(oidcConfig.getResponseTypesSupported(), OAuth2Constants.CODE, OIDCResponseType.ID_TOKEN, "id_token token", "code id_token", "code token", "code id_token token");
|
||||
assertEquals(oidcConfig.getGrantTypesSupported().size(),7);
|
||||
assertContains(oidcConfig.getGrantTypesSupported(), OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT,
|
||||
OAuth2Constants.DEVICE_CODE_GRANT_TYPE);
|
||||
assertContains(oidcConfig.getResponseModesSupported(), "query", "fragment", "form_post", "jwt", "query.jwt", "fragment.jwt", "form_post.jwt");
|
||||
|
||||
Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "pairwise", "public");
|
||||
|
||||
// Signature algorithms
|
||||
Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
Assert.assertNames(oidcConfig.getAuthorizationSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
|
||||
// request object encryption algorithms
|
||||
Assert.assertNames(oidcConfig.getRequestObjectEncryptionAlgValuesSupported(), JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256, JWEConstants.RSA1_5);
|
||||
Assert.assertNames(oidcConfig.getRequestObjectEncryptionEncValuesSupported(), JWEConstants.A256GCM, JWEConstants.A192GCM, JWEConstants.A128GCM, JWEConstants.A128CBC_HS256, JWEConstants.A192CBC_HS384, JWEConstants.A256CBC_HS512);
|
||||
|
||||
// Encryption algorithms
|
||||
Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
|
||||
Assert.assertNames(oidcConfig.getIdTokenEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
|
||||
Assert.assertNames(oidcConfig.getAuthorizationEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
|
||||
Assert.assertNames(oidcConfig.getAuthorizationEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
|
||||
Assert.assertNames(oidcConfig.getUserInfoEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
|
||||
Assert.assertNames(oidcConfig.getUserInfoEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
|
||||
|
||||
// Client authentication
|
||||
Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt", "client_secret_jwt", "tls_client_auth");
|
||||
Assert.assertNames(oidcConfig.getTokenEndpointAuthSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
// NOTE: Those are overriden in "oidc-well-known-config-override.json" and they are tested in testDefaultProviderCustomizations
|
||||
//Assert.assertNames(oidcConfig.getIntrospectionEndpointAuthMethodsSupported(), "private_key_jwt", "client_secret_jwt", "tls_client_auth", "custom_nonexisting_authenticator");
|
||||
Assert.assertNames(oidcConfig.getIntrospectionEndpointAuthSigningAlgValuesSupported(), Algorithm.PS256,
|
||||
Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256,
|
||||
Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
|
||||
|
||||
// Claims
|
||||
assertContains(oidcConfig.getClaimsSupported(), IDToken.NAME, IDToken.EMAIL, IDToken.PREFERRED_USERNAME, IDToken.FAMILY_NAME, IDToken.ACR);
|
||||
Assert.assertNames(oidcConfig.getClaimTypesSupported(), "normal");
|
||||
Assert.assertTrue(oidcConfig.getClaimsParameterSupported());
|
||||
|
||||
// Scopes supported
|
||||
assertScopesSupportedMatchesWithRealm(oidcConfig);
|
||||
|
||||
// Request and Request_Uri
|
||||
Assert.assertTrue(oidcConfig.getRequestParameterSupported());
|
||||
Assert.assertTrue(oidcConfig.getRequestUriParameterSupported());
|
||||
Assert.assertTrue(oidcConfig.getRequireRequestUriRegistration());
|
||||
|
||||
// KEYCLOAK-7451 OAuth Authorization Server Metadata for Proof Key for Code Exchange
|
||||
// PKCE support
|
||||
Assert.assertNames(oidcConfig.getCodeChallengeMethodsSupported(), OAuth2Constants.PKCE_METHOD_PLAIN, OAuth2Constants.PKCE_METHOD_S256);
|
||||
|
||||
// KEYCLOAK-6771 Certificate Bound Token
|
||||
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.2
|
||||
Assert.assertTrue(oidcConfig.getTlsClientCertificateBoundAccessTokens());
|
||||
MTLSEndpointAliases mtlsEndpointAliases = oidcConfig.getMtlsEndpointAliases();
|
||||
Assert.assertEquals(oidcConfig.getTokenEndpoint(), mtlsEndpointAliases.getTokenEndpoint());
|
||||
Assert.assertEquals(oidcConfig.getRevocationEndpoint(), mtlsEndpointAliases.getRevocationEndpoint());
|
||||
|
||||
// CIBA
|
||||
assertEquals(oidcConfig.getBackchannelAuthenticationEndpoint(), oauth.getBackchannelAuthenticationUrl());
|
||||
assertContains(oidcConfig.getGrantTypesSupported(), OAuth2Constants.CIBA_GRANT_TYPE);
|
||||
Assert.assertNames(oidcConfig.getBackchannelTokenDeliveryModesSupported(), "poll", "ping");
|
||||
Assert.assertNames(oidcConfig.getBackchannelAuthenticationRequestSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.EdDSA);
|
||||
|
||||
Assert.assertTrue(oidcConfig.getBackchannelLogoutSupported());
|
||||
Assert.assertTrue(oidcConfig.getBackchannelLogoutSessionSupported());
|
||||
|
||||
// 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, Algorithm.EdDSA);
|
||||
|
||||
assertEquals(oidcConfig.getDeviceAuthorizationEndpoint(), oauth.getDeviceAuthorizationUrl());
|
||||
|
||||
// Pushed Authorization Request (PAR)
|
||||
assertEquals(oauth.getParEndpointUrl(), oidcConfig.getPushedAuthorizationRequestEndpoint());
|
||||
assertEquals(Boolean.FALSE, oidcConfig.getRequirePushedAuthorizationRequests());
|
||||
|
||||
// frontchannel logout
|
||||
assertTrue(oidcConfig.getFrontChannelLogoutSessionSupported());
|
||||
assertTrue(oidcConfig.getFrontChannelLogoutSupported());
|
||||
|
||||
// DPoP - negative test for preview profile - see testDpopSigningAlgValuesSupportedWithDpop for actual test
|
||||
assertNull("dpop_signing_alg_values_supported should not be present unless DPoP feature is enabled",
|
||||
oidcConfig.getDpopSigningAlgValuesSupported());
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpDiscovery() {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, "http://localhost:8180/auth");
|
||||
|
||||
Assert.assertNotNull(oidcConfig.getJwksUri());
|
||||
|
||||
// Token Revocation
|
||||
Assert.assertNotNull(oidcConfig.getRevocationEndpoint());
|
||||
Assert.assertNotNull(oidcConfig.getRevocationEndpointAuthMethodsSupported());
|
||||
Assert.assertNotNull(oidcConfig.getRevocationEndpointAuthSigningAlgValuesSupported());
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIssuerMatches() throws Exception {
|
||||
OAuthClient.AuthorizationEndpointResponse authzResp = oauth.doLogin("test-user@localhost", "password");
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(authzResp.getCode(), "password");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
|
||||
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
|
||||
// assert issuer matches
|
||||
assertEquals(idToken.getIssuer(), oidcConfig.getIssuer());
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void corsTest() {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
UriBuilder builder = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT);
|
||||
URI oidcDiscoveryUri = RealmsResource.wellKnownProviderUrl(builder).build("test", OIDCWellKnownProviderFactory.PROVIDER_ID);
|
||||
WebTarget oidcDiscoveryTarget = client.target(oidcDiscoveryUri);
|
||||
|
||||
|
||||
Invocation.Builder request = oidcDiscoveryTarget.request();
|
||||
request.header(Cors.ORIGIN_HEADER, "http://somehost");
|
||||
Response response = request.get();
|
||||
|
||||
assertEquals("http://somehost", response.getHeaders().getFirst(Cors.ACCESS_CONTROL_ALLOW_ORIGIN));
|
||||
|
||||
|
||||
Invocation.Builder nullRequest = oidcDiscoveryTarget.request();
|
||||
nullRequest.header(Cors.ORIGIN_HEADER, "null");
|
||||
Response nullResponse = nullRequest.get();
|
||||
|
||||
assertEquals("null", nullResponse.getHeaders().getFirst(Cors.ACCESS_CONTROL_ALLOW_ORIGIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void certs() throws IOException {
|
||||
TokenSignatureUtil.registerKeyProvider(Algorithm.ES256, adminClient, testContext);
|
||||
|
||||
OIDCConfigurationRepresentation representation = SimpleHttpDefault.doGet(getAuthServerRoot().toString() + "realms/test/.well-known/openid-configuration", client).asJson(OIDCConfigurationRepresentation.class);
|
||||
String jwksUri = representation.getJwksUri();
|
||||
|
||||
JSONWebKeySet jsonWebKeySet = SimpleHttpDefault.doGet(jwksUri, client).asJson(JSONWebKeySet.class);
|
||||
assertEquals(3, jsonWebKeySet.getKeys().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntrospectionEndpointClaim() throws IOException {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
public void testAcrValuesSupported() throws IOException {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
// Default values when no "acr-to-loa" mapping and no authentication flow configured
|
||||
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
Assert.assertNames(oidcConfig.getAcrValuesSupported(), "0", "1");
|
||||
|
||||
// Update authentication flow and see it uses "acr" values from it
|
||||
LevelOfAssuranceFlowTest.configureStepUpFlow(testingClient);
|
||||
oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
Assert.assertNames(oidcConfig.getAcrValuesSupported(), "0", "1", "2", "3");
|
||||
|
||||
// Configure "ACR-To-Loa" mapping and check it has both configured values and numbers from authentication flow
|
||||
RealmResource testRealm = adminClient.realm("test");
|
||||
RealmRepresentation realmRep = testRealm.toRepresentation();
|
||||
Map<String, Integer> acrToLoa = new HashMap<>();
|
||||
acrToLoa.put("poor", 0);
|
||||
acrToLoa.put("silver", 1);
|
||||
acrToLoa.put("gold", 2);
|
||||
String acrToLoaAttr = JsonSerialization.writeValueAsString(acrToLoa);
|
||||
realmRep.getAttributes().put(Constants.ACR_LOA_MAP, acrToLoaAttr);
|
||||
testRealm.update(realmRep);
|
||||
|
||||
oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
Assert.assertNames(oidcConfig.getAcrValuesSupported(), "poor", "silver", "gold", "0", "1", "2", "3");
|
||||
|
||||
// Use mappings even with values not included in the authentication flow
|
||||
acrToLoa = new HashMap<>();
|
||||
acrToLoa.put("poor", 0);
|
||||
acrToLoa.put("silver", 1);
|
||||
acrToLoa.put("gold", 2);
|
||||
acrToLoa.put("platinum", 3);
|
||||
acrToLoa.put("diamond", 4);
|
||||
acrToLoaAttr = JsonSerialization.writeValueAsString(acrToLoa);
|
||||
realmRep.getAttributes().put(Constants.ACR_LOA_MAP, acrToLoaAttr);
|
||||
testRealm.update(realmRep);
|
||||
|
||||
oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
Assert.assertNames(oidcConfig.getAcrValuesSupported(), "poor", "silver", "gold", "platinum", "diamond", "0", "1", "2", "3");
|
||||
|
||||
// Revert realm and flow
|
||||
realmRep.getAttributes().remove(Constants.ACR_LOA_MAP);
|
||||
testRealm.update(realmRep);
|
||||
BrowserFlowTest.revertFlows(testRealm, "browser - Level of Authentication FLow");
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
public void testDefaultProviderCustomizations() throws IOException {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
|
@ -390,64 +68,4 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFeature(value = Profile.Feature.TOKEN_EXCHANGE, skipRestart = true)
|
||||
public void testGrantTypesSupportedWithTokenExchange() throws IOException {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
try {
|
||||
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
assertEquals(oidcConfig.getGrantTypesSupported().size(),8);
|
||||
assertContains(oidcConfig.getGrantTypesSupported(), OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFeature(value = Profile.Feature.DPOP, skipRestart = true)
|
||||
public void testDpopSigningAlgValuesSupportedWithDpop() throws IOException {
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
|
||||
try {
|
||||
OIDCConfigurationRepresentation oidcConfig = getOIDCDiscoveryRepresentation(client, OAuthClient.AUTH_SERVER_ROOT);
|
||||
|
||||
// DPoP
|
||||
Assert.assertNames(oidcConfig.getDpopSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512,
|
||||
Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void assertScopesSupportedMatchesWithRealm(OIDCConfigurationRepresentation oidcConfig) {
|
||||
Assert.assertNames(oidcConfig.getScopesSupported(), OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS,
|
||||
OAuth2Constants.SCOPE_PROFILE, OAuth2Constants.SCOPE_EMAIL, OAuth2Constants.SCOPE_PHONE, OAuth2Constants.SCOPE_ADDRESS, OIDCLoginProtocolFactory.ACR_SCOPE, OIDCLoginProtocolFactory.BASIC_SCOPE,
|
||||
OIDCLoginProtocolFactory.ROLES_SCOPE, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE, OIDCLoginProtocolFactory.MICROPROFILE_JWT_SCOPE);
|
||||
}
|
||||
|
||||
private OIDCConfigurationRepresentation getOIDCDiscoveryRepresentation(Client client, String uriTemplate) {
|
||||
try {
|
||||
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, String uriTemplate) {
|
||||
UriBuilder builder = UriBuilder.fromUri(uriTemplate);
|
||||
URI oidcDiscoveryUri = RealmsResource.wellKnownProviderUrl(builder).build("test", OIDCWellKnownProviderFactory.PROVIDER_ID);
|
||||
WebTarget oidcDiscoveryTarget = client.target(oidcDiscoveryUri);
|
||||
|
||||
Response response = oidcDiscoveryTarget.request().get();
|
||||
|
||||
assertEquals("no-cache, must-revalidate, no-transform, no-store", response.getHeaders().getFirst("Cache-Control"));
|
||||
|
||||
return response.readEntity(String.class);
|
||||
}
|
||||
|
||||
private void assertContains(List<String> actual, String... expected) {
|
||||
for (String exp : expected) {
|
||||
Assert.assertTrue(actual.contains(exp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue