From b89edabcfcb5dd323be04b1833b254a83fd70f7c Mon Sep 17 00:00:00 2001 From: Takashi Norimatsu Date: Fri, 22 Jan 2021 19:43:35 +0900 Subject: [PATCH] KEYCLOAK-16889 Client Policy : Refactor Test Class --- .../client/AbstractClientPoliciesTest.java | 709 ++++++ .../testsuite/client/ClientPoliciesTest.java | 1552 ++++++++++++ .../client/ClientPolicyBasicsTest.java | 2231 ----------------- 3 files changed, 2261 insertions(+), 2231 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPolicyBasicsTest.java diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java new file mode 100644 index 0000000000..52b342ff6d --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java @@ -0,0 +1,709 @@ +/* + * Copyright 2021 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.client; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; + +import javax.ws.rs.core.Response; + +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicNameValuePair; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.keycloak.OAuth2Constants; +import org.keycloak.OAuthErrorException; +import org.keycloak.adapters.AdapterUtils; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.client.registration.Auth; +import org.keycloak.client.registration.ClientRegistration; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.common.util.Base64; +import org.keycloak.common.util.Base64Url; +import org.keycloak.common.util.KeyUtils; +import org.keycloak.common.util.KeycloakUriBuilder; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.common.util.Time; +import org.keycloak.common.util.UriUtils; +import org.keycloak.constants.ServiceUrlConstants; +import org.keycloak.crypto.KeyType; +import org.keycloak.crypto.SignatureSignerContext; +import org.keycloak.events.Errors; +import org.keycloak.events.EventType; +import org.keycloak.jose.jws.Algorithm; +import org.keycloak.jose.jws.JWSBuilder; +import org.keycloak.models.Constants; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; +import org.keycloak.representations.JsonWebToken; +import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; +import org.keycloak.representations.idm.ClientInitialAccessPresentation; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; +import org.keycloak.representations.oidc.TokenMetadataRepresentation; +import org.keycloak.services.Urls; +import org.keycloak.services.clientpolicy.ClientPolicyException; +import org.keycloak.services.clientpolicy.ClientPolicyProvider; +import org.keycloak.services.clientpolicy.DefaultClientPolicyProviderFactory; +import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; +import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientScopesConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateContextConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceGroupsConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceHostsConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceRolesConditionFactory; +import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; +import org.keycloak.services.clientpolicy.executor.SecureClientAuthEnforceExecutorFactory; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls; +import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource; +import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource; +import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject; +import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.ServerURLs; +import org.keycloak.util.JsonSerialization; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { + + protected static final String REALM_NAME = "test"; + protected static final String TEST_CLIENT = "test-app"; + protected static final String TEST_CLIENT_SECRET = "password"; + + protected static final String ERR_MSG_MISSING_NONCE = "Missing parameter: nonce"; + protected static final String ERR_MSG_MISSING_STATE = "Missing parameter: state"; + protected static final String ERR_MSG_CLIENT_REG_FAIL = "Failed to send request"; + + protected static final String AnyClientCondition_NAME = "AnyClientCondition"; + protected static final String ClientAccessTypeCondition_NAME = "ClientAccessTypeCondition"; + protected static final String ClientRolesCondition_NAME = "ClientRolesCondition"; + protected static final String ClientScopesCondition_NAME = "ClientScopesCondition"; + protected static final String ClientUpdateContextCondition_NAME = "ClientUpdateContextCondition"; + protected static final String ClientUpdateSourceGroupsCondition_NAME = "ClientUpdateSourceGroupsCondition"; + protected static final String ClientUpdateSourceRolesCondition_NAME = "ClientUpdateSourceRolesCondition"; + protected static final String ClientUpdateSourceHostsCondition_NAME = "ClientUpdateSourceHostsCondition"; + protected static final String TestRaiseExeptionCondition_NAME = "TestRaiseExeptionCondition"; + + protected static final String HolderOfKeyEnforceExecutor_NAME = "HolderOfKeyEnforceExecutor"; + protected static final String PKCEEnforceExecutor_NAME = "PKCEEnforceExecutor"; + protected static final String SecureClientAuthEnforceExecutor_NAME = "SecureClientAuthEnforceExecutor"; + protected static final String SecureRedirectUriEnforceExecutor_NAME = "SecureRedirectUriEnforceExecutor"; + protected static final String SecureResponseTypeExecutor_NAME = "SecureResponseTypeExecutor"; + protected static final String SecureRequestObjectExecutor_NAME = "SecureRequestObjectExecutor"; + protected static final String SecureSessionEnforceExecutor_NAME = "SecureSessionEnforceExecutor"; + protected static final String SecureSigningAlgorithmEnforceExecutor_NAME = "SecureSigningAlgorithmEnforceExecutor"; + protected static final String SecureSigningAlgorithmForSignedJwtEnforceExecutor_NAME = "SecureSigningAlgorithmForSignedJwtEnforceExecutor"; + + protected ClientRegistration reg; + + @Rule + public AssertEvents events = new AssertEvents(this); + + @Before + public void before() throws Exception { + // get initial access token for Dynamic Client Registration with authentication + reg = ClientRegistration.create().url(suiteContext.getAuthServerInfo().getContextRoot() + "/auth", REALM_NAME).build(); + ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10)); + reg.auth(Auth.token(token)); + } + + @After + public void after() throws Exception { + reg.close(); + } + + protected String generateSuffixedName(String name) { + return name + "-" + UUID.randomUUID().toString().subSequence(0, 7); + } + + // Utilities for Request Object retrieved by reference from jwks_uri + + protected KeyPair setupJwks(String algorithm, ClientRepresentation clientRepresentation, ClientResource clientResource) throws Exception { + // generate and register client keypair + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); + oidcClientEndpointsResource.generateKeys(algorithm); + Map generatedKeys = oidcClientEndpointsResource.getKeysAsBase64(); + KeyPair keyPair = getKeyPairFromGeneratedBase64(generatedKeys, algorithm); + + // use and set jwks_url + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setUseJwksUrl(true); + String jwksUrl = TestApplicationResourceUrls.clientJwksUri(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setJwksUrl(jwksUrl); + clientResource.update(clientRepresentation); + + // set time offset, so that new keys are downloaded + setTimeOffset(20); + + return keyPair; + } + + private KeyPair getKeyPairFromGeneratedBase64(Map generatedKeys, String algorithm) throws Exception { + // It seems that PemUtils.decodePrivateKey, decodePublicKey can only treat RSA type keys, not EC type keys. Therefore, these are not used. + String privateKeyBase64 = generatedKeys.get(TestingOIDCEndpointsApplicationResource.PRIVATE_KEY); + String publicKeyBase64 = generatedKeys.get(TestingOIDCEndpointsApplicationResource.PUBLIC_KEY); + PrivateKey privateKey = decodePrivateKey(Base64.decode(privateKeyBase64), algorithm); + PublicKey publicKey = decodePublicKey(Base64.decode(publicKeyBase64), algorithm); + return new KeyPair(publicKey, privateKey); + } + + private PrivateKey decodePrivateKey(byte[] der, String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der); + String keyAlg = getKeyAlgorithmFromJwaAlgorithm(algorithm); + KeyFactory kf = KeyFactory.getInstance(keyAlg, "BC"); + return kf.generatePrivate(spec); + } + + private PublicKey decodePublicKey(byte[] der, String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { + X509EncodedKeySpec spec = new X509EncodedKeySpec(der); + String keyAlg = getKeyAlgorithmFromJwaAlgorithm(algorithm); + KeyFactory kf = KeyFactory.getInstance(keyAlg, "BC"); + return kf.generatePublic(spec); + } + + private String getKeyAlgorithmFromJwaAlgorithm(String jwaAlgorithm) { + String keyAlg = null; + switch (jwaAlgorithm) { + case org.keycloak.crypto.Algorithm.RS256: + case org.keycloak.crypto.Algorithm.RS384: + case org.keycloak.crypto.Algorithm.RS512: + case org.keycloak.crypto.Algorithm.PS256: + case org.keycloak.crypto.Algorithm.PS384: + case org.keycloak.crypto.Algorithm.PS512: + keyAlg = KeyType.RSA; + break; + case org.keycloak.crypto.Algorithm.ES256: + case org.keycloak.crypto.Algorithm.ES384: + case org.keycloak.crypto.Algorithm.ES512: + keyAlg = KeyType.EC; + break; + default : + throw new RuntimeException("Unsupported signature algorithm"); + } + return keyAlg; + } + + // Signed JWT for client authentication utility + + protected String createSignedRequestToken(String clientId, PrivateKey privateKey, PublicKey publicKey, String algorithm) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + JsonWebToken jwt = createRequestToken(clientId, getRealmInfoUrl()); + String kid = KeyUtils.createKeyId(publicKey); + SignatureSignerContext signer = oauth.createSigner(privateKey, kid, algorithm); + return new JWSBuilder().kid(kid).jsonContent(jwt).sign(signer); + } + + private String getRealmInfoUrl() { + String authServerBaseUrl = UriUtils.getOrigin(oauth.getRedirectUri()) + "/auth"; + return KeycloakUriBuilder.fromUri(authServerBaseUrl).path(ServiceUrlConstants.REALM_INFO_PATH).build(REALM_NAME).toString(); + } + + private JsonWebToken createRequestToken(String clientId, String realmInfoUrl) { + JsonWebToken reqToken = new JsonWebToken(); + reqToken.id(AdapterUtils.generateId()); + reqToken.issuer(clientId); + reqToken.subject(clientId); + reqToken.audience(realmInfoUrl); + + int now = Time.currentTime(); + reqToken.iat(Long.valueOf(now)); + reqToken.exp(Long.valueOf(now + 10)); + reqToken.nbf(Long.valueOf(now)); + + return reqToken; + } + + // OAuth2 protocol operation with signed JWT for client authentication + + protected OAuthClient.AccessTokenResponse doAccessTokenRequestWithSignedJWT(String code, String signedJwt) throws Exception { + List parameters = new LinkedList<>(); + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE)); + parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, code)); + parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, oauth.getRedirectUri())); + parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)); + parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt)); + + CloseableHttpResponse response = sendRequest(oauth.getAccessTokenUrl(), parameters); + return new OAuthClient.AccessTokenResponse(response); + } + + protected OAuthClient.AccessTokenResponse doRefreshTokenRequestWithSignedJWT(String refreshToken, String signedJwt) throws Exception { + List parameters = new LinkedList<>(); + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN)); + parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken)); + parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)); + parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt)); + + CloseableHttpResponse response = sendRequest(oauth.getRefreshTokenUrl(), parameters); + return new OAuthClient.AccessTokenResponse(response); + } + + protected HttpResponse doTokenIntrospectionWithSignedJWT(String tokenType, String tokenToIntrospect, String signedJwt) throws Exception { + List parameters = new LinkedList<>(); + parameters.add(new BasicNameValuePair("token", tokenToIntrospect)); + parameters.add(new BasicNameValuePair("token_type_hint", tokenType)); + parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)); + parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt)); + + return sendRequest(oauth.getTokenIntrospectionUrl(), parameters); + } + + protected HttpResponse doTokenRevokeWithSignedJWT(String tokenType, String tokenToIntrospect, String signedJwt) throws Exception { + List parameters = new LinkedList<>(); + parameters.add(new BasicNameValuePair("token", tokenToIntrospect)); + parameters.add(new BasicNameValuePair("token_type_hint", tokenType)); + parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)); + parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt)); + + return sendRequest(oauth.getTokenRevocationUrl(), parameters); + } + + protected HttpResponse doLogoutWithSignedJWT(String refreshToken, String signedJwt) throws Exception { + List parameters = new LinkedList<>(); + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN)); + parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken)); + parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)); + parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt)); + + return sendRequest(oauth.getLogoutUrl().build(), parameters); + } + + private CloseableHttpResponse sendRequest(String requestUrl, List parameters) throws Exception { + CloseableHttpClient client = new DefaultHttpClient(); + try { + HttpPost post = new HttpPost(requestUrl); + UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, "UTF-8"); + post.setEntity(formEntity); + return client.execute(post); + } finally { + oauth.closeClient(client); + } + } + + // Request Object utility + + protected AuthorizationEndpointRequestObject createValidRequestObjectForSecureRequestObjectExecutor(String clientId) throws URISyntaxException { + AuthorizationEndpointRequestObject requestObject = new AuthorizationEndpointRequestObject(); + requestObject.id(KeycloakModelUtils.generateId()); + requestObject.iat(Long.valueOf(Time.currentTime())); + requestObject.exp(requestObject.getIat() + Long.valueOf(300)); + requestObject.nbf(Long.valueOf(0)); + requestObject.setClientId(clientId); + requestObject.setResponseType("code"); + requestObject.setRedirectUriParam(oauth.getRedirectUri()); + requestObject.setScope("openid"); + String scope = KeycloakModelUtils.generateId(); + oauth.stateParamHardcoded(scope); + requestObject.setState(scope); + requestObject.setMax_age(Integer.valueOf(600)); + requestObject.setOtherClaims("custom_claim_ein", "rot"); + requestObject.audience(Urls.realmIssuer(new URI(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth"), REALM_NAME), "https://example.com"); + return requestObject; + } + + protected void registerRequestObject(AuthorizationEndpointRequestObject requestObject, String clientId, Algorithm sigAlg, boolean isUseRequestUri) throws URISyntaxException, IOException { + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); + + // Set required signature for request_uri + // use and set jwks_url + ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId); + ClientRepresentation clientRep = clientResource.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(sigAlg); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true); + String jwksUrl = TestApplicationResourceUrls.clientJwksUri(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(jwksUrl); + clientResource.update(clientRep); + + oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); + + // generate and register client keypair + oidcClientEndpointsResource.generateKeys(sigAlg.name()); + + // register request object + byte[] contentBytes = JsonSerialization.writeValueAsBytes(requestObject); + String encodedRequestObject = Base64Url.encode(contentBytes); + oidcClientEndpointsResource.registerOIDCRequest(encodedRequestObject, sigAlg.name()); + + if (isUseRequestUri) { + oauth.request(null); + oauth.requestUri(TestApplicationResourceUrls.clientRequestUri()); + } else { + oauth.requestUri(null); + oauth.request(oidcClientEndpointsResource.getOIDCRequest()); + } + } + + // PKCE utility + + protected String generateS256CodeChallenge(String codeVerifier) throws Exception { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(codeVerifier.getBytes("ISO_8859_1")); + byte[] digestBytes = md.digest(); + String codeChallenge = Base64Url.encode(digestBytes); + return codeChallenge; + } + + // OAuth2 protocol operation + + protected void doIntrospectAccessToken(OAuthClient.AccessTokenResponse tokenRes, String username, String clientId, String clientSecret) throws IOException { + String tokenResponse = oauth.introspectAccessTokenWithClientCredential(clientId, clientSecret, tokenRes.getAccessToken()); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(tokenResponse); + assertEquals(true, jsonNode.get("active").asBoolean()); + assertEquals(username, jsonNode.get("username").asText()); + assertEquals(clientId, jsonNode.get("client_id").asText()); + TokenMetadataRepresentation rep = objectMapper.readValue(tokenResponse, TokenMetadataRepresentation.class); + assertEquals(true, rep.isActive()); + assertEquals(clientId, rep.getClientId()); + assertEquals(clientId, rep.getIssuedFor()); + events.expect(EventType.INTROSPECT_TOKEN).client(clientId).user((String)null).clearDetails().assertEvent(); + } + + protected void doTokenRevoke(String refreshToken, String clientId, String clientSecret, String userId, boolean isOfflineAccess) throws IOException { + oauth.clientId(clientId); + oauth.doTokenRevoke(refreshToken, "refresh_token", clientSecret); + + // confirm revocation + OAuthClient.AccessTokenResponse tokenRes = oauth.doRefreshTokenRequest(refreshToken, clientSecret); + assertEquals(400, tokenRes.getStatusCode()); + assertEquals(OAuthErrorException.INVALID_GRANT, tokenRes.getError()); + if (isOfflineAccess) assertEquals("Offline user session not found", tokenRes.getErrorDescription()); + else assertEquals("Session not active", tokenRes.getErrorDescription()); + + events.expect(EventType.REVOKE_GRANT).clearDetails().client(clientId).user(userId).assertEvent(); + } + + // Client CRUD operation by Admin REST API primitives + + protected String createClientByAdmin(String clientId, Consumer op) throws ClientPolicyException { + ClientRepresentation clientRep = new ClientRepresentation(); + clientRep.setClientId(clientId); + clientRep.setName(clientId); + clientRep.setProtocol("openid-connect"); + clientRep.setBearerOnly(Boolean.FALSE); + clientRep.setPublicClient(Boolean.FALSE); + clientRep.setServiceAccountsEnabled(Boolean.TRUE); + clientRep.setRedirectUris(Collections.singletonList(ServerURLs.getAuthServerContextRoot() + "/auth/realms/master/app/auth")); + op.accept(clientRep); + Response resp = adminClient.realm(REALM_NAME).clients().create(clientRep); + if (resp.getStatus() == Response.Status.BAD_REQUEST.getStatusCode()) { + throw new ClientPolicyException(Errors.INVALID_REGISTRATION, "registration error by admin"); + } + resp.close(); + assertEquals(Response.Status.CREATED.getStatusCode(), resp.getStatus()); + // registered components will be removed automatically when a test method finishes regardless of its success or failure. + String cId = ApiUtil.getCreatedId(resp); + testContext.getOrCreateCleanup(REALM_NAME).addClientUuid(cId); + return cId; + } + + protected ClientRepresentation getClientByAdmin(String cId) { + ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(cId); + return clientResource.toRepresentation(); + } + + protected void updateClientByAdmin(String cId, Consumer op) { + ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(cId); + ClientRepresentation clientRep = clientResource.toRepresentation(); + op.accept(clientRep); + clientResource.update(clientRep); + } + + protected void deleteClientByAdmin(String cId) { + ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(cId); + clientResource.remove(); + } + + // Registration/Initial Access Token acquisition for Dynamic Client Registration + + protected void authCreateClients() { + reg.auth(Auth.token(getToken("create-clients", "password"))); + } + + protected void authManageClients() { + reg.auth(Auth.token(getToken("manage-clients", "password"))); + } + + protected void authNoAccess() { + reg.auth(Auth.token(getToken("no-access", "password"))); + } + + private String getToken(String username, String password) { + try { + return oauth.doGrantAccessTokenRequest(REALM_NAME, username, password, null, Constants.ADMIN_CLI_CLIENT_ID, null).getAccessToken(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // Client CRUD operation by Dynamic Client Registration primitives + + protected String createClientDynamically(String clientName, Consumer op) throws ClientRegistrationException { + OIDCClientRepresentation clientRep = new OIDCClientRepresentation(); + clientRep.setClientName(clientName); + clientRep.setClientUri(ServerURLs.getAuthServerContextRoot()); + clientRep.setRedirectUris(Collections.singletonList(ServerURLs.getAuthServerContextRoot() + "/auth/realms/master/app/auth")); + op.accept(clientRep); + OIDCClientRepresentation response = reg.oidc().create(clientRep); + reg.auth(Auth.token(response)); + // registered components will be removed automatically when a test method finishes regardless of its success or failure. + String clientId = response.getClientId(); + testContext.getOrCreateCleanup(REALM_NAME).addClientUuid(clientId); + return clientId; + } + + protected OIDCClientRepresentation getClientDynamically(String clientId) throws ClientRegistrationException { + return reg.oidc().get(clientId); + } + + protected void updateClientDynamically(String clientId, Consumer op) throws ClientRegistrationException { + OIDCClientRepresentation clientRep = reg.oidc().get(clientId); + op.accept(clientRep); + OIDCClientRepresentation response = reg.oidc().update(clientRep); + reg.auth(Auth.token(response)); + } + + protected void deleteClientDynamically(String clientId) throws ClientRegistrationException { + reg.oidc().delete(clientId); + } + + // Policy CRUD operation primitives + + protected String createPolicy(String name, String providerId, String subType, List conditions, List executors) { + ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyProvider.class.getName(), subType); + component.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditions); + component.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executors); + return createComponent(component); + } + + protected ComponentRepresentation getPolicy(String name) { + return getComponent(name, ClientPolicyProvider.class.getName()); + } + + protected void updatePolicy(ComponentRepresentation policy) { + updateComponent(policy); + } + + protected void deletePolicy(String policyName) { + ComponentRepresentation policy = getPolicy(policyName); + List conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS); + List executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS); + conditionIds.stream().forEach(i->adminClient.realm(REALM_NAME).components().component(i).remove()); + executorIds.stream().forEach(i->adminClient.realm(REALM_NAME).components().component(i).remove()); + adminClient.realm(REALM_NAME).components().component(policy.getId()).remove(); + } + + // Condition CRUD operation primitives + + // only create the component + protected String createCondition(String name, String providerId, String subType, Consumer op) { + ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyConditionProvider.class.getName(), subType); + op.accept(component); + return createComponent(component); + } + + // register created component to the policy + protected void registerCondition(String conditionName, String policyName) { + ComponentRepresentation policy = getPolicy(policyName); + List conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS); + if (conditionIds == null) conditionIds = new ArrayList(); + ComponentRepresentation condition = getCondition(conditionName); + conditionIds.add(condition.getId()); + policy.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditionIds); + updatePolicy(policy); + } + + protected ComponentRepresentation getCondition(String name) { + return getComponent(name, ClientPolicyConditionProvider.class.getName()); + } + + protected void updateCondition(String name, Consumer op) { + ComponentRepresentation condition = getCondition(name); + op.accept(condition); + updateComponent(condition); + } + + protected void deleteCondition(String conditionName, String policyName) { + ComponentRepresentation policy = getPolicy(policyName); + List conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS); + ComponentRepresentation condition = getCondition(conditionName); + String conditionId = condition.getId(); + adminClient.realm(REALM_NAME).components().component(conditionId).remove(); + conditionIds.remove(conditionId); + policy.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditionIds); + updatePolicy(policy); + } + + // Condition configuration primitives + + protected void setConditionRegistrationMethods(ComponentRepresentation provider, List registrationMethods) { + provider.getConfig().put(ClientUpdateContextConditionFactory.UPDATE_CLIENT_SOURCE, registrationMethods); + } + + protected void setConditionClientRoles(ComponentRepresentation provider, List clientRoles) { + provider.getConfig().put(ClientRolesConditionFactory.ROLES, clientRoles); + } + + protected void setConditionClientScopes(ComponentRepresentation provider, List clientScopes) { + provider.getConfig().put(ClientScopesConditionFactory.SCOPES, clientScopes); + } + + protected void setConditionClientAccessType(ComponentRepresentation provider, List clientAccessTypes) { + provider.getConfig().put(ClientAccessTypeConditionFactory.TYPE, clientAccessTypes); + } + + protected void setConditionClientUpdateSourceHosts(ComponentRepresentation provider, List hosts) { + provider.getConfig().putSingle(ClientUpdateSourceHostsConditionFactory.HOST_SENDING_REQUEST_MUST_MATCH, "true"); + provider.getConfig().put(ClientUpdateSourceHostsConditionFactory.TRUSTED_HOSTS, hosts); + } + + protected void setConditionClientUpdateSourceGroups(ComponentRepresentation provider, List groups) { + provider.getConfig().put(ClientUpdateSourceGroupsConditionFactory.GROUPS, groups); + } + + protected void setConditionUpdatingClientSourceRoles(ComponentRepresentation provider, List groups) { + provider.getConfig().put(ClientUpdateSourceRolesConditionFactory.ROLES, groups); + } + + // Executor CRUD operation primitives + + // only create the component + protected String createExecutor(String name, String providerId, String subType, Consumer op) { + ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyExecutorProvider.class.getName(), subType); + op.accept(component); + return createComponent(component); + } + + // register created component to the policy + protected void registerExecutor(String executorName, String policyName) { + ComponentRepresentation policy = getPolicy(policyName); + List executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS); + if (executorIds == null) executorIds = new ArrayList(); + ComponentRepresentation executor = getExecutor(executorName); + executorIds.add(executor.getId()); + policy.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executorIds); + updatePolicy(policy); + } + + protected ComponentRepresentation getExecutor(String name) { + return getComponent(name, ClientPolicyExecutorProvider.class.getName()); + } + + protected void updateExecutor(String name, Consumer op) { + ComponentRepresentation executor = getExecutor(name); + op.accept(executor); + updateComponent(executor); + } + + protected void deleteExecutor(String executorName, String policyName) { + ComponentRepresentation policy = getPolicy(policyName); + List executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS); + ComponentRepresentation executor = getExecutor(executorName); + String executorId = executor.getId(); + adminClient.realm(REALM_NAME).components().component(executorId).remove(); + executorIds.remove(executorId); + policy.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executorIds); + updatePolicy(policy); + } + + // Executor configuration primitives + + protected void setExecutorAugmentActivate(ComponentRepresentation provider) { + provider.getConfig().putSingle("is-augment", Boolean.TRUE.toString()); + } + + protected void setExecutorAugmentDeactivate(ComponentRepresentation provider) { + provider.getConfig().putSingle("is-augment", Boolean.FALSE.toString()); + } + + protected void setExecutorAcceptedClientAuthMethods(ComponentRepresentation provider, List acceptedClientAuthMethods) { + provider.getConfig().put(SecureClientAuthEnforceExecutorFactory.CLIENT_AUTHNS, acceptedClientAuthMethods); + } + + protected void setExecutorAugmentedClientAuthMethod(ComponentRepresentation provider, String augmentedClientAuthMethod) { + provider.getConfig().putSingle(SecureClientAuthEnforceExecutorFactory.CLIENT_AUTHNS_AUGMENT, augmentedClientAuthMethod); + } + + // Component on which condition/executor/policy based CRUD operation primitives + + private ComponentRepresentation createComponentInstance(String name, String providerId, String providerType, String subType) { + ComponentRepresentation rep = new ComponentRepresentation(); + rep.setId(org.keycloak.models.utils.KeycloakModelUtils.generateId()); + rep.setName(name); + rep.setParentId(REALM_NAME); + rep.setProviderId(providerId); + rep.setProviderType(providerType); + rep.setSubType(subType); + rep.setConfig(new MultivaluedHashMap<>()); + return rep; + } + + private String createComponent(ComponentRepresentation cr) { + Response resp = adminClient.realm(REALM_NAME).components().add(cr); + String id = ApiUtil.getCreatedId(resp); + resp.close(); + // registered components will be removed automatically when a test method finishes regardless of its success or failure. + testContext.getOrCreateCleanup(REALM_NAME).addComponentId(id); + return id; + } + + private ComponentRepresentation getComponent(String name, String providerType) { + return adminClient.realm(REALM_NAME).components().query(null, providerType, name).get(0); + } + + private void updateComponent(ComponentRepresentation cr) { + adminClient.realm(REALM_NAME).components().component(cr.getId()).update(cr); + } + + private void deleteComponent(String id) { + adminClient.realm(REALM_NAME).components().component(id).remove(); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java new file mode 100644 index 0000000000..9908cb3d59 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java @@ -0,0 +1,1552 @@ +/* + * Copyright 2020 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.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; +import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import javax.ws.rs.BadRequestException; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.hamcrest.Matchers; +import org.jboss.logging.Logger; +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.OAuth2Constants; +import org.keycloak.OAuthErrorException; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; +import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; +import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator; +import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.common.Profile; +import org.keycloak.events.Details; +import org.keycloak.events.Errors; +import org.keycloak.events.EventType; +import org.keycloak.jose.jws.Algorithm; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.Constants; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; +import org.keycloak.protocol.oidc.OIDCConfigAttributes; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.utils.OIDCResponseType; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.RefreshToken; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.EventRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; +import org.keycloak.representations.oidc.TokenMetadataRepresentation; +import org.keycloak.services.clientpolicy.ClientPolicyException; +import org.keycloak.services.clientpolicy.DefaultClientPolicyProviderFactory; +import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateContextConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceGroupsConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceHostsConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceRolesConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientScopesConditionFactory; +import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureClientAuthEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureRedirectUriEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureRequestObjectExecutor; +import org.keycloak.services.clientpolicy.executor.SecureRequestObjectExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureResponseTypeExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; +import org.keycloak.testsuite.arquillian.annotation.EnableFeature; +import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer; +import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls; +import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject; +import org.keycloak.testsuite.services.clientpolicy.condition.TestRaiseExeptionConditionFactory; +import org.keycloak.testsuite.util.MutualTLSUtils; +import org.keycloak.testsuite.util.OAuthClient; + +import org.keycloak.admin.client.resource.RolesResource; +import org.keycloak.testsuite.util.RoleBuilder; + +import org.keycloak.util.JsonSerialization; + +@EnableFeature(value = Profile.Feature.CLIENT_POLICIES, skipRestart = true) +public class ClientPoliciesTest extends AbstractClientPoliciesTest { + + private static final Logger logger = Logger.getLogger(ClientPoliciesTest.class); + + private static final String POLICY_NAME = "MyPolicy"; + private static final String CLIENT_NAME = "Zahlungs-App"; + private static final String SAMPLE_CLIENT_ROLE = "sample-client-role"; + private static final String TEST_USER_NAME = "test-user@localhost"; + private static final String TEST_USER_PASSWORD = "password"; + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + + List users = realm.getUsers(); + + LinkedList credentials = new LinkedList<>(); + CredentialRepresentation password = new CredentialRepresentation(); + password.setType(CredentialRepresentation.PASSWORD); + password.setValue("password"); + credentials.add(password); + + UserRepresentation user = new UserRepresentation(); + user.setEnabled(true); + user.setUsername("manage-clients"); + user.setCredentials(credentials); + user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.MANAGE_CLIENTS))); + + users.add(user); + + user = new UserRepresentation(); + user.setEnabled(true); + user.setUsername("create-clients"); + user.setCredentials(credentials); + user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.CREATE_CLIENT))); + user.setGroups(Arrays.asList("topGroup")); // defined in testrealm.json + + users.add(user); + + realm.setUsers(users); + + testRealms.add(realm); + } + + @Test + public void testAdminClientRegisterUnacceptableAuthType() { + setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); + try { + createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { + clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID); + }); + fail(); + } catch (ClientPolicyException e) { + assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); + } + } + + @Test + public void testAdminClientRegisterAcceptableAuthType() throws ClientPolicyException { + setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); + String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { + clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); + }); + assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); + } + + @Test + public void testAdminClientRegisterDefaultAuthType() { + setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); + try { + createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {}); + fail(); + } catch (ClientPolicyException e) { + assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); + } + } + + @Test + public void testAdminClientUpdateUnacceptableAuthType() throws ClientPolicyException { + setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); + String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { + clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); + }); + assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); + try { + updateClientByAdmin(cId, (ClientRepresentation clientRep) -> { + clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID); + }); + fail(); + } catch (BadRequestException bre) { + assertEquals("HTTP 400 Bad Request", bre.getMessage()); + } + assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); + } + + @Test + public void testAdminClientUpdateAcceptableAuthType() throws ClientPolicyException { + setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); + + String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { + clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); + }); + + assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); + + updateClientByAdmin(cId, (ClientRepresentation clientRep) -> { + clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID); + }); + assertEquals(JWTClientAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); + } + + @Test + public void testAdminClientUpdateDefaultAuthType() throws ClientPolicyException { + setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); + + String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { + clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); + }); + + assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); + + updateClientByAdmin(cId, (ClientRepresentation clientRep) -> { + clientRep.setServiceAccountsEnabled(Boolean.FALSE); + }); + assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); + assertEquals(Boolean.FALSE, getClientByAdmin(cId).isServiceAccountsEnabled()); + } + + @Test + public void testAdminClientAugmentedAuthType() throws ClientPolicyException { + setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); + + updateExecutor(SecureClientAuthEnforceExecutor_NAME, (ComponentRepresentation provider) -> { + setExecutorAugmentActivate(provider); + setExecutorAugmentedClientAuthMethod(provider, X509ClientAuthenticator.PROVIDER_ID); + }); + + String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { + clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID); + }); + + assertEquals(X509ClientAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); + + updateExecutor(SecureClientAuthEnforceExecutor_NAME, (ComponentRepresentation provider) -> { + setExecutorAugmentedClientAuthMethod(provider, JWTClientAuthenticator.PROVIDER_ID); + }); + + updateClientByAdmin(cId, (ClientRepresentation clientRep) -> { + clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); + }); + assertEquals(JWTClientAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); + } + + @Test + public void testDynamicClientRegisterAndUpdate() throws ClientRegistrationException { + setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); + + String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {}); + assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, getClientDynamically(clientId).getTokenEndpointAuthMethod()); + assertEquals(Boolean.FALSE, getClientDynamically(clientId).getTlsClientCertificateBoundAccessTokens()); + + updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> { + clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.CLIENT_SECRET_BASIC); + clientRep.setTlsClientCertificateBoundAccessTokens(Boolean.TRUE); + }); + assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, getClientDynamically(clientId).getTokenEndpointAuthMethod()); + assertEquals(Boolean.TRUE, getClientDynamically(clientId).getTlsClientCertificateBoundAccessTokens()); + } + + @Test + public void testCreateDeletePolicyRuntime() throws ClientRegistrationException { + String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {}); + OIDCClientRepresentation clientRep = getClientDynamically(clientId); + assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, clientRep.getTokenEndpointAuthMethod()); + events.expect(EventType.CLIENT_REGISTER).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent(); + events.expect(EventType.CLIENT_INFO).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent(); + adminClient.realm(REALM_NAME).clients().get(clientId).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build()); + + successfulLoginAndLogout(clientId, clientRep.getClientSecret()); + + setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(POLICY_NAME); + + failLoginByNotFollowingPKCE(clientId); + + deletePolicy(POLICY_NAME); + logger.info("... Deleted Policy : " + POLICY_NAME); + + successfulLoginAndLogout(clientId, clientRep.getClientSecret()); + } + + @Test + public void testCreateUpdateDeleteConditionRuntime() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String executorName = PKCEEnforceExecutor_NAME; + createExecutor(executorName, PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAugmentActivate(provider); + }); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + String clientId = generateSuffixedName(CLIENT_NAME); + String clientSecret = "secret"; + String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientSecret); + }); + adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build()); + + successfulLoginAndLogout(clientId, clientSecret); + + String conditionName = ClientRolesCondition_NAME; + createCondition(conditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientRoles(provider, new ArrayList<>(Arrays.asList(SAMPLE_CLIENT_ROLE))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + failLoginByNotFollowingPKCE(clientId); + + updateCondition(conditionName, (ComponentRepresentation provider) -> { + setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("anothor-client-role"))); + }); + + successfulLoginAndLogout(clientId, clientSecret); + + deleteCondition(conditionName, policyName); + + successfulLoginAndLogout(clientId, clientSecret); + } + + @Test + public void testCreateUpdateDeleteExecutorRuntime() throws ClientRegistrationException, ClientPolicyException { + String policyName = generateSuffixedName(CLIENT_NAME); + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String roleConditionName = ClientRolesCondition_NAME; + createCondition(roleConditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientRoles(provider, new ArrayList<>(Arrays.asList(SAMPLE_CLIENT_ROLE))); + }); + registerCondition(roleConditionName, policyName); + logger.info("... Registered Condition : " + roleConditionName); + + String updateConditionName = ClientUpdateContextCondition_NAME; + createCondition(updateConditionName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))); + }); + registerCondition(updateConditionName, policyName); + logger.info("... Registered Condition : " + updateConditionName); + + String clientId = generateSuffixedName(CLIENT_NAME); + String clientSecret = "secret"; + String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientSecret); + }); + adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build()); + + successfulLoginAndLogout(clientId, clientSecret); + + String executorName = PKCEEnforceExecutor_NAME; + createExecutor(executorName, PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAugmentDeactivate(provider); + }); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + failLoginByNotFollowingPKCE(clientId); + + updateExecutor(executorName, (ComponentRepresentation provider) -> { + setExecutorAugmentActivate(provider); + }); + + updateClientByAdmin(cid, (ClientRepresentation clientRep) -> { + clientRep.setServiceAccountsEnabled(Boolean.FALSE); + }); + assertEquals(false, getClientByAdmin(cid).isServiceAccountsEnabled()); + assertEquals(OAuth2Constants.PKCE_METHOD_S256, OIDCAdvancedConfigWrapper.fromClientRepresentation(getClientByAdmin(cid)).getPkceCodeChallengeMethod()); + + deleteExecutor(executorName, policyName); + logger.info("... Deleted Executor : " + executorName); + + updateClientByAdmin(cid, (ClientRepresentation clientRep) -> { + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setPkceCodeChallengeMethod(null); + }); + assertEquals(null, OIDCAdvancedConfigWrapper.fromClientRepresentation(getClientByAdmin(cid)).getPkceCodeChallengeMethod()); + + successfulLoginAndLogout(clientId, clientSecret); + } + + @Test + public void testAuthzCodeFlowUnderMultiPhasePolicy() throws Exception { + setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(POLICY_NAME); + + String clientName = generateSuffixedName(CLIENT_NAME); + String clientId = createClientDynamically(clientName, (OIDCClientRepresentation clientRep) -> {}); + events.expect(EventType.CLIENT_REGISTER).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent(); + OIDCClientRepresentation response = getClientDynamically(clientId); + String clientSecret = response.getClientSecret(); + assertEquals(clientName, response.getClientName()); + assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, response.getTokenEndpointAuthMethod()); + events.expect(EventType.CLIENT_INFO).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent(); + + adminClient.realm(REALM_NAME).clients().get(clientId).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build()); + + successfulLoginAndLogoutWithPKCE(response.getClientId(), clientSecret, TEST_USER_NAME, TEST_USER_PASSWORD); + } + + @Test + public void testMultiplePolicies() throws ClientRegistrationException, ClientPolicyException { + String roleAlphaName = "sample-client-role-alpha"; + String roleBetaName = "sample-client-role-beta"; + String roleZetaName = "sample-client-role-zeta"; + String roleCommonName = "sample-client-role-common"; + + String policyAlphaName = "MyPolicy-alpha"; + createPolicy(policyAlphaName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyAlphaName); + + String roleConditionAlphaName = generateSuffixedName(ClientRolesCondition_NAME); + createCondition(roleConditionAlphaName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientRoles(provider, new ArrayList<>(Arrays.asList(roleAlphaName, roleZetaName))); + }); + registerCondition(roleConditionAlphaName, policyAlphaName); + logger.info("... Registered Condition : " + roleConditionAlphaName); + + String updateConditionAlphaName = generateSuffixedName(ClientUpdateContextCondition_NAME); + createCondition(updateConditionAlphaName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))); + }); + registerCondition(updateConditionAlphaName, policyAlphaName); + logger.info("... Registered Condition : " + updateConditionAlphaName); + + String clientAuthExecutorAlphaName = generateSuffixedName(SecureClientAuthEnforceExecutor_NAME); + createExecutor(clientAuthExecutorAlphaName, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID))); + setExecutorAugmentActivate(provider); + setExecutorAugmentedClientAuthMethod(provider, ClientIdAndSecretAuthenticator.PROVIDER_ID); + }); + registerExecutor(clientAuthExecutorAlphaName, policyAlphaName); + logger.info("... Registered Executor : " + clientAuthExecutorAlphaName); + + String policyBetaName = "MyPolicy-beta"; + createPolicy(policyBetaName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyBetaName); + + String roleConditionBetaName = generateSuffixedName(ClientRolesCondition_NAME); + createCondition(roleConditionBetaName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientRoles(provider, new ArrayList<>(Arrays.asList(roleBetaName, roleZetaName))); + }); + registerCondition(roleConditionBetaName, policyBetaName); + logger.info("... Registered Condition : " + roleConditionBetaName); + + String pkceExecutorBetaName = generateSuffixedName(PKCEEnforceExecutor_NAME); + createExecutor(pkceExecutorBetaName, PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAugmentActivate(provider); + }); + registerExecutor(pkceExecutorBetaName, policyBetaName); + logger.info("... Registered Executor : " + pkceExecutorBetaName); + + String clientAlphaId = generateSuffixedName("Alpha-App"); + String clientAlphaSecret = "secretAlpha"; + String cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientAlphaSecret); + clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); + }); + RolesResource rolesResourceAlpha = adminClient.realm(REALM_NAME).clients().get(cAlphaId).roles(); + rolesResourceAlpha.create(RoleBuilder.create().name(roleAlphaName).build()); + rolesResourceAlpha.create(RoleBuilder.create().name(roleCommonName).build()); + + String clientBetaId = generateSuffixedName("Beta-App"); + String cBetaId = createClientByAdmin(clientBetaId, (ClientRepresentation clientRep) -> { + clientRep.setSecret("secretBeta"); + }); + RolesResource rolesResourceBeta = adminClient.realm(REALM_NAME).clients().get(cBetaId).roles(); + rolesResourceBeta.create(RoleBuilder.create().name(roleBetaName).build()); + rolesResourceBeta.create(RoleBuilder.create().name(roleCommonName).build()); + + assertEquals(ClientIdAndSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cAlphaId).getClientAuthenticatorType()); + successfulLoginAndLogout(clientAlphaId, clientAlphaSecret); + failLoginByNotFollowingPKCE(clientBetaId); + } + + @Test + public void testIntentionalExceptionOnCondition() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = TestRaiseExeptionCondition_NAME; + createCondition(conditionName, TestRaiseExeptionConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + try { + createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {}); + fail(); + } catch (ClientPolicyException e) { + assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); + } + } + + @Test + public void testAnyClientCondition() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = AnyClientCondition_NAME; + createCondition(conditionName, AnyClientConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executorName = SecureSessionEnforceExecutor_NAME; + createExecutor(executorName, SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + String clientAlphaId = generateSuffixedName("Alpha-App"); + String clientAlphaSecret = "secretAlpha"; + createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { + clientRep.setDefaultRoles((String[]) Arrays.asList("sample-client-role-alpha").toArray(new String[1])); + clientRep.setSecret(clientAlphaSecret); + }); + + String clientBetaId = generateSuffixedName("Beta-App"); + createClientByAdmin(clientBetaId, (ClientRepresentation clientRep) -> { + clientRep.setSecret("secretBeta"); + }); + + try { + failLoginWithoutSecureSessionParameter(clientBetaId, ERR_MSG_MISSING_NONCE); + oauth.nonce("yesitisnonce"); + successfulLoginAndLogout(clientAlphaId, clientAlphaSecret); + } catch (Exception e) { + fail(); + } + } + + @Test + public void testConditionWithoutNoConfiguration() throws ClientRegistrationException, ClientPolicyException { + String policyName = "MyPolicy-ClientAccessTypeCondition"; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + String conditionName = ClientAccessTypeCondition_NAME; + createCondition(conditionName, ClientAccessTypeConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + policyName = "MyPolicy-ClientUpdateSourceGroupsCondition"; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + conditionName = ClientUpdateSourceGroupsCondition_NAME; + createCondition(conditionName, ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + policyName = "MyPolicy-ClientUpdateSourceRolesCondition"; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + conditionName = ClientUpdateSourceRolesCondition_NAME; + createCondition(conditionName, ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + policyName = "MyPolicy-ClientUpdateContextCondition"; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + conditionName = ClientUpdateContextCondition_NAME; + createCondition(conditionName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + policyName = "MyPolicy-SecureSessionEnforceExecutor"; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + String executorName = SecureSessionEnforceExecutor_NAME; + createExecutor(executorName, SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + String clientId = generateSuffixedName(CLIENT_NAME); + String clientSecret = "secret"; + createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientSecret); + clientRep.setBearerOnly(Boolean.FALSE); + clientRep.setPublicClient(Boolean.FALSE); + }); + + successfulLoginAndLogout(clientId, clientSecret); + } + + @AuthServerContainerExclude(AuthServer.REMOTE) + @Test + public void testClientUpdateSourceHostsCondition() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = ClientUpdateSourceHostsCondition_NAME; + createCondition(conditionName, ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientUpdateSourceHosts(provider, new ArrayList<>(Arrays.asList("localhost", "127.0.0.1"))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executorName = SecureClientAuthEnforceExecutor_NAME; + createExecutor(executorName, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList( + JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID))); + }); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + String clientId = generateSuffixedName(CLIENT_NAME); + String clientSecret = "secret"; + try { + createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientSecret); + }); + fail(); + } catch (ClientPolicyException e) { + assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); + } + + updateCondition(conditionName, (ComponentRepresentation provider) -> { + setConditionClientUpdateSourceHosts(provider, new ArrayList<>(Arrays.asList("example.com"))); + }); + try { + createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientSecret); + }); + } catch (Exception e) { + fail(); + } + } + + @Test + public void testClientUpdateSourceGroupsCondition() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = ClientUpdateSourceGroupsCondition_NAME; + createCondition(conditionName, ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientUpdateSourceGroups(provider, new ArrayList<>(Arrays.asList("topGroup"))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executorName = SecureClientAuthEnforceExecutor_NAME; + createExecutor(executorName, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList(JWTClientAuthenticator.PROVIDER_ID))); + }); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + try { + authCreateClients(); + createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {}); + fail(); + } catch (ClientRegistrationException e) { + assertEquals(ERR_MSG_CLIENT_REG_FAIL, e.getMessage()); + } + authManageClients(); + try { + createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {}); + } catch (Exception e) { + fail(); + } + } + + @Test + public void testClientUpdateSourceRolesCondition() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = ClientUpdateSourceRolesCondition_NAME; + createCondition(conditionName, ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionUpdatingClientSourceRoles(provider, new ArrayList<>(Arrays.asList(AdminRoles.CREATE_CLIENT))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executorName = SecureClientAuthEnforceExecutor_NAME; + createExecutor(executorName, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList(JWTClientAuthenticator.PROVIDER_ID))); + }); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + try { + authCreateClients(); + createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {}); + fail(); + } catch (ClientRegistrationException e) { + assertEquals(ERR_MSG_CLIENT_REG_FAIL, e.getMessage()); + } + authManageClients(); + try { + createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {}); + } catch (Exception e) { + fail(); + } + } + + @Test + public void testClientScopesCondition() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = ClientScopesCondition_NAME; + createCondition(conditionName, ClientScopesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientScopes(provider, new ArrayList<>(Arrays.asList("offline_access", "microprofile-jwt"))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executorName = PKCEEnforceExecutor_NAME; + createExecutor(executorName, PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAugmentActivate(provider); + }); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + String clientId = generateSuffixedName(CLIENT_NAME); + String clientSecret = "secret"; + createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientSecret); + }); + + try { + oauth.scope("address" + " " + "phone"); + successfulLoginAndLogout(clientId, clientSecret); + + oauth.scope("microprofile-jwt" + " " + "profile"); + failLoginByNotFollowingPKCE(clientId); + + oauth.scope("microprofile-jwt" + " " + "profile"); + failLoginByNotFollowingPKCE(clientId); + + successfulLoginAndLogoutWithPKCE(clientId, clientSecret, TEST_USER_NAME, TEST_USER_PASSWORD); + } catch (Exception e) { + fail(); + } + } + + + @Test + public void testClientAccessTypeCondition() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = ClientAccessTypeCondition_NAME; + createCondition(conditionName, ClientAccessTypeConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientAccessType(provider, new ArrayList<>(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executorName = SecureSessionEnforceExecutor_NAME; + createExecutor(executorName, SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + // confidential client + String clientAlphaId = generateSuffixedName("Alpha-App"); + createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { + clientRep.setSecret("secretAlpha"); + clientRep.setBearerOnly(Boolean.FALSE); + clientRep.setPublicClient(Boolean.FALSE); + }); + + // public client + String clientBetaId = generateSuffixedName("Beta-App"); + createClientByAdmin(clientBetaId, (ClientRepresentation clientRep) -> { + clientRep.setBearerOnly(Boolean.FALSE); + clientRep.setPublicClient(Boolean.TRUE); + }); + + successfulLoginAndLogout(clientBetaId, null); + failLoginWithoutNonce(clientAlphaId); + } + + @Test + public void testSecureResponseTypeExecutor() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = ClientRolesCondition_NAME; + createCondition(conditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientRoles(provider, new ArrayList<>(Arrays.asList(SAMPLE_CLIENT_ROLE))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executorName = SecureResponseTypeExecutor_NAME; + createExecutor(executorName, SecureResponseTypeExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + String clientId = generateSuffixedName(CLIENT_NAME); + String clientSecret = "secret"; + String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientSecret); + clientRep.setStandardFlowEnabled(Boolean.TRUE); + clientRep.setImplicitFlowEnabled(Boolean.TRUE); + clientRep.setPublicClient(Boolean.FALSE); + }); + adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build()); + + oauth.clientId(clientId); + oauth.openLoginForm(); + assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + assertEquals("invalid response_type", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); + + oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN + " " + OIDCResponseType.TOKEN); + oauth.nonce("cie8cjcwiw"); + oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD); + + EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent(); + String sessionId = loginEvent.getSessionId(); + String codeId = loginEvent.getDetails().get(Details.CODE_ID); + String code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode(); + OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, clientSecret); + assertEquals(200, res.getStatusCode()); + events.expectCodeToToken(codeId, sessionId).client(clientId).assertEvent(); + + oauth.doLogout(res.getRefreshToken(), clientSecret); + events.expectLogout(sessionId).client(clientId).clearDetails().assertEvent(); + + oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN); + oauth.nonce("vbwe566fsfffds"); + oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD); + + loginEvent = events.expectLogin().client(clientId).assertEvent(); + sessionId = loginEvent.getSessionId(); + codeId = loginEvent.getDetails().get(Details.CODE_ID); + code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode(); + res = oauth.doAccessTokenRequest(code, clientSecret); + assertEquals(200, res.getStatusCode()); + events.expectCodeToToken(codeId, sessionId).client(clientId).assertEvent(); + + oauth.doLogout(res.getRefreshToken(), clientSecret); + events.expectLogout(sessionId).client(clientId).clearDetails().assertEvent(); + } + + @Test + public void testSecureRequestObjectExecutor() throws ClientRegistrationException, ClientPolicyException, URISyntaxException, IOException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = ClientRolesCondition_NAME; + createCondition(conditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientRoles(provider, new ArrayList<>(Arrays.asList(SAMPLE_CLIENT_ROLE))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executor = SecureRequestObjectExecutor_NAME; + createExecutor(executor, SecureRequestObjectExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerExecutor(executor, policyName); + logger.info("... Registered Executor : " + executor); + + String clientId = generateSuffixedName(CLIENT_NAME); + String clientSecret = "secret"; + String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientSecret); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestUris(Arrays.asList(TestApplicationResourceUrls.clientRequestUri())); + }); + adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build()); + + oauth.clientId(clientId); + AuthorizationEndpointRequestObject requestObject; + + // check whether whether request object exists + oauth.request(null); + oauth.requestUri(null); + oauth.openLoginForm(); + assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + assertEquals("Invalid parameter", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); + + // check whether request_uri is https scheme + // cannot test because existing AuthorizationEndpoint check and return error before executing client policy + + // check whether request object can be retrieved from request_uri + // cannot test because existing AuthorizationEndpoint check and return error before executing client policy + + // check whether request object can be parsed successfully + // cannot test because existing AuthorizationEndpoint check and return error before executing client policy + + // check whether scope exists in both query parameter and request object + requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); + requestObject.setScope(null); + registerRequestObject(requestObject, clientId, Algorithm.ES256, true); + oauth.openLoginForm(); + assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + assertEquals("Missing parameter : scope", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); + + // check whether "exp" claim exists + requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); + requestObject.exp(null); + registerRequestObject(requestObject, clientId, Algorithm.ES256, false); + oauth.openLoginForm(); + assertEquals(SecureRequestObjectExecutor.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + assertEquals("Missing parameter : exp", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); + + // check whether request object not expired + requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); + requestObject.exp(Long.valueOf(0)); + registerRequestObject(requestObject, clientId, Algorithm.ES256, true); + oauth.openLoginForm(); + assertEquals(SecureRequestObjectExecutor.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + assertEquals("Request Expired", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); + + // check whether "aud" claim exists + requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); + requestObject.audience((String)null); + registerRequestObject(requestObject, clientId, Algorithm.ES256, false); + oauth.openLoginForm(); + assertEquals(SecureRequestObjectExecutor.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + assertEquals("Missing parameter : aud", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); + + // check whether "aud" claim points to this keycloak as authz server + requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); + requestObject.audience(suiteContext.getAuthServerInfo().getContextRoot().toString()); + registerRequestObject(requestObject, clientId, Algorithm.ES256, true); + oauth.openLoginForm(); + assertEquals(SecureRequestObjectExecutor.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + assertEquals("Invalid parameter : aud", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); + + // confirm whether all parameters in query string are included in the request object, and have the same values + // argument "request" are parameters overridden by parameters in request object + requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); + requestObject.setState("notmatchstate"); + registerRequestObject(requestObject, clientId, Algorithm.ES256, false); + oauth.openLoginForm(); + assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + assertEquals("Invalid parameter", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); + + // valid request object + requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); + registerRequestObject(requestObject, clientId, Algorithm.ES256, true); + + successfulLoginAndLogout(clientId, clientSecret); + } + + @Test + public void testSecureSessionEnforceExecutor() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String roleAlphaName = "sample-client-role-alpha"; + String roleBetaName = "sample-client-role-beta"; + String conditionName = ClientRolesCondition_NAME; + createCondition(conditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientRoles(provider, new ArrayList<>(Arrays.asList(roleBetaName))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executorName = SecureSessionEnforceExecutor_NAME; + createExecutor(executorName, SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + String clientAlphaId = generateSuffixedName("Alpha-App"); + String clientAlphaSecret = "secretAlpha"; + String cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientAlphaSecret); + }); + adminClient.realm(REALM_NAME).clients().get(cAlphaId).roles().create(RoleBuilder.create().name(roleAlphaName).build()); + + String clientBetaId = generateSuffixedName("Beta-App"); + String clientBetaSecret = "secretBeta"; + String cBetaId = createClientByAdmin(clientBetaId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientBetaSecret); + }); + adminClient.realm(REALM_NAME).clients().get(cBetaId).roles().create(RoleBuilder.create().name(roleBetaName).build()); + + successfulLoginAndLogout(clientAlphaId, clientAlphaSecret); + + oauth.openid(false); + successfulLoginAndLogout(clientAlphaId, clientAlphaSecret); + + oauth.openid(true); + failLoginWithoutSecureSessionParameter(clientBetaId, ERR_MSG_MISSING_NONCE); + + oauth.nonce("yesitisnonce"); + successfulLoginAndLogout(clientBetaId, clientBetaSecret); + + oauth.openid(false); + oauth.stateParamHardcoded(null); + failLoginWithoutSecureSessionParameter(clientBetaId, ERR_MSG_MISSING_STATE); + + oauth.stateParamRandom(); + successfulLoginAndLogout(clientBetaId, clientBetaSecret); + } + + @Test + public void testSecureSigningAlgorithmEnforceExecutor() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = ClientUpdateContextCondition_NAME; + createCondition(conditionName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList( + ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, + ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN, + ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String execuorName = SecureSigningAlgorithmEnforceExecutor_NAME; + createExecutor(execuorName, SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerExecutor(execuorName, policyName); + logger.info("... Registered Executor : " + execuorName); + + // create by Admin REST API - fail + try { + createClientByAdmin(generateSuffixedName("App-by-Admin"), (ClientRepresentation clientRep) -> { + clientRep.setSecret("secret"); + clientRep.setAttributes(new HashMap<>()); + clientRep.getAttributes().put(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, Algorithm.none.name()); + }); + fail(); + } catch (ClientPolicyException e) { + assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); + } + + // create by Admin REST API - success + String cAppAdminId = createClientByAdmin(generateSuffixedName("App-by-Admin"), (ClientRepresentation clientRep) -> { + clientRep.setAttributes(new HashMap<>()); + clientRep.getAttributes().put(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, org.keycloak.crypto.Algorithm.PS256); + clientRep.getAttributes().put(OIDCConfigAttributes.REQUEST_OBJECT_SIGNATURE_ALG, org.keycloak.crypto.Algorithm.ES256); + clientRep.getAttributes().put(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG, org.keycloak.crypto.Algorithm.ES256); + clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, org.keycloak.crypto.Algorithm.ES256); + clientRep.getAttributes().put(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG, org.keycloak.crypto.Algorithm.ES256); + }); + + // update by Admin REST API - fail + try { + updateClientByAdmin(cAppAdminId, (ClientRepresentation clientRep) -> { + clientRep.setAttributes(new HashMap<>()); + clientRep.getAttributes().put(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG, Algorithm.RS512.name()); + }); + } catch (BadRequestException bre) { + assertEquals("HTTP 400 Bad Request", bre.getMessage()); + } + ClientRepresentation cRep = getClientByAdmin(cAppAdminId); + assertEquals(Algorithm.ES256.name(), cRep.getAttributes().get(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG)); + + // update by Admin REST API - success + updateClientByAdmin(cAppAdminId, (ClientRepresentation clientRep) -> { + clientRep.setAttributes(new HashMap<>()); + clientRep.getAttributes().put(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG, Algorithm.PS384.name()); + }); + cRep = getClientByAdmin(cAppAdminId); + assertEquals(Algorithm.PS384.name(), cRep.getAttributes().get(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG)); + + // create dynamically - fail + try { + createClientByAdmin(generateSuffixedName("App-in-Dynamic"), (ClientRepresentation clientRep) -> { + clientRep.setSecret("secret"); + clientRep.setAttributes(new HashMap<>()); + clientRep.getAttributes().put(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, Algorithm.RS384.name()); + }); + fail(); + } catch (ClientPolicyException e) { + assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); + } + + // create dynamically - success + String cAppDynamicClientId = createClientDynamically(generateSuffixedName("App-in-Dynamic"), (OIDCClientRepresentation clientRep) -> { + clientRep.setUserinfoSignedResponseAlg(org.keycloak.crypto.Algorithm.ES256); + clientRep.setRequestObjectSigningAlg(org.keycloak.crypto.Algorithm.ES256); + clientRep.setIdTokenSignedResponseAlg(org.keycloak.crypto.Algorithm.PS256); + clientRep.setTokenEndpointAuthSigningAlg(org.keycloak.crypto.Algorithm.PS256); + }); + events.expect(EventType.CLIENT_REGISTER).client(cAppDynamicClientId).user(Matchers.isEmptyOrNullString()).assertEvent(); + getClientDynamically(cAppDynamicClientId); + + // update dynamically - fail + try { + updateClientDynamically(cAppDynamicClientId, (OIDCClientRepresentation clientRep) -> { + clientRep.setIdTokenSignedResponseAlg(Algorithm.RS256.name()); + }); + fail(); + } catch (ClientRegistrationException e) { + assertEquals(ERR_MSG_CLIENT_REG_FAIL, e.getMessage()); + } + assertEquals(org.keycloak.crypto.Algorithm.PS256, getClientDynamically(cAppDynamicClientId).getIdTokenSignedResponseAlg()); + + // update dynamically - success + updateClientDynamically(cAppDynamicClientId, (OIDCClientRepresentation clientRep) -> { + clientRep.setIdTokenSignedResponseAlg(org.keycloak.crypto.Algorithm.ES384); + }); + assertEquals(org.keycloak.crypto.Algorithm.ES384, getClientDynamically(cAppDynamicClientId).getIdTokenSignedResponseAlg()); + } + + @Test + public void testSecureRedirectUriEnforceExecutor() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = ClientUpdateContextCondition_NAME; + createCondition(conditionName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList( + ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, + ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN, + ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executorName = SecureRedirectUriEnforceExecutor_NAME; + createExecutor(executorName, SecureRedirectUriEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + try { + createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> { + clientRep.setRedirectUris(Collections.singletonList("http://newredirect")); + }); + fail(); + } catch (ClientRegistrationException e) { + assertEquals(ERR_MSG_CLIENT_REG_FAIL, e.getMessage()); + } + updateCondition(conditionName, (ComponentRepresentation provider) -> { + setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList( + ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, + ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN))); + }); + try { + createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {}); + } catch (Exception e) { + fail(); + } + } + + @Test + public void testSecureSigningAlgorithmForSignedJwtEnforceExecutor() throws Exception { + String roleAlphaName = "sample-client-role-alpha"; + String roleZetaName = "sample-client-role-zeta"; + String roleCommonName = "sample-client-role-common"; + + // policy including client role condition + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = ClientRolesCondition_NAME; + createCondition(conditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientRoles(provider, new ArrayList<>(Arrays.asList(roleAlphaName, roleZetaName))); + }); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executorName = SecureSigningAlgorithmForSignedJwtEnforceExecutor_NAME; + createExecutor(executorName, SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + // create a client with client role + String clientId = generateSuffixedName(CLIENT_NAME); + createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setDefaultRoles(Arrays.asList(roleAlphaName, roleCommonName).toArray(new String[2])); + clientRep.setSecret("secret"); + clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID); + clientRep.setAttributes(new HashMap<>()); + clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, org.keycloak.crypto.Algorithm.ES256); + }); + + ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId); + ClientRepresentation clientRep = clientResource.toRepresentation(); + + KeyPair keyPair = setupJwks(org.keycloak.crypto.Algorithm.ES256, clientRep, clientResource); + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + + String signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); + + oauth.clientId(clientId); + oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD); + EventRepresentation loginEvent = events.expectLogin() + .client(clientId) + .assertEvent(); + String sessionId = loginEvent.getSessionId(); + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + + // obtain access token + OAuthClient.AccessTokenResponse response = doAccessTokenRequestWithSignedJWT(code, signedJwt); + + assertEquals(200, response.getStatusCode()); + oauth.verifyToken(response.getAccessToken()); + RefreshToken refreshToken = oauth.parseRefreshToken(response.getRefreshToken()); + assertEquals(sessionId, refreshToken.getSessionState()); + assertEquals(sessionId, refreshToken.getSessionState()); + events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), loginEvent.getSessionId()) + .client(clientId) + .detail(Details.CLIENT_AUTH_METHOD, JWTClientAuthenticator.PROVIDER_ID) + .assertEvent(); + + // refresh token + signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); + OAuthClient.AccessTokenResponse refreshedResponse = doRefreshTokenRequestWithSignedJWT(response.getRefreshToken(), signedJwt); + assertEquals(200, refreshedResponse.getStatusCode()); + + // introspect token + signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); + HttpResponse tokenIntrospectionResponse = doTokenIntrospectionWithSignedJWT("access_token", refreshedResponse.getAccessToken(), signedJwt); + assertEquals(200, tokenIntrospectionResponse.getStatusLine().getStatusCode()); + + // revoke token + signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); + HttpResponse revokeTokenResponse = doTokenRevokeWithSignedJWT("refresh_toke", refreshedResponse.getRefreshToken(), signedJwt); + assertEquals(200, revokeTokenResponse.getStatusLine().getStatusCode()); + + signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); + OAuthClient.AccessTokenResponse tokenRes = doRefreshTokenRequestWithSignedJWT(refreshedResponse.getRefreshToken(), signedJwt); + assertEquals(400, tokenRes.getStatusCode()); + assertEquals(OAuthErrorException.INVALID_GRANT, tokenRes.getError()); + + // logout + signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); + HttpResponse logoutResponse = doLogoutWithSignedJWT(refreshedResponse.getRefreshToken(), signedJwt); + assertEquals(204, logoutResponse.getStatusLine().getStatusCode()); + } + + @Test + public void testHolderOfKeyEnforceExecutor() throws Exception { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + String conditionName = AnyClientCondition_NAME; + createCondition(conditionName, AnyClientConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); + registerCondition(conditionName, policyName); + logger.info("... Registered Condition : " + conditionName); + + String executorName = HolderOfKeyEnforceExecutor_NAME; + createExecutor(executorName, HolderOfKeyEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAugmentActivate(provider); + }); + registerExecutor(executorName, policyName); + logger.info("... Registered Executor : " + executorName); + + ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), TEST_CLIENT); + ClientRepresentation clientRep = clientResource.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseMtlsHoKToken(true); + clientResource.update(clientRep); + try { + checkMtlsFlow(); + } finally { + clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), TEST_CLIENT); + clientRep = clientResource.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseMtlsHoKToken(false); + clientResource.update(clientRep); + } + } + + private void checkMtlsFlow() throws IOException { + // Check login. + OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD); + Assert.assertNull(loginResponse.getError()); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + + // Check token obtaining. + OAuthClient.AccessTokenResponse accessTokenResponse; + try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { + accessTokenResponse = oauth.doAccessTokenRequest(code, TEST_CLIENT_SECRET, client); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + assertEquals(200, accessTokenResponse.getStatusCode()); + + // Check token refresh. + OAuthClient.AccessTokenResponse accessTokenResponseRefreshed; + try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { + accessTokenResponseRefreshed = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), TEST_CLIENT_SECRET, client); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + assertEquals(200, accessTokenResponseRefreshed.getStatusCode()); + + // Check token introspection. + String tokenResponse; + try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { + tokenResponse = oauth.introspectTokenWithClientCredential(TEST_CLIENT, TEST_CLIENT_SECRET, "access_token", accessTokenResponse.getAccessToken(), client); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + Assert.assertNotNull(tokenResponse); + TokenMetadataRepresentation tokenMetadataRepresentation = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class); + Assert.assertTrue(tokenMetadataRepresentation.isActive()); + + // Check token revoke. + CloseableHttpResponse tokenRevokeResponse; + try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { + tokenRevokeResponse = oauth.doTokenRevoke(accessTokenResponse.getRefreshToken(), "refresh_token", TEST_CLIENT_SECRET, client); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + assertEquals(200, tokenRevokeResponse.getStatusLine().getStatusCode()); + + // Check logout. + CloseableHttpResponse logoutResponse; + try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { + logoutResponse = oauth.doLogout(accessTokenResponse.getRefreshToken(), TEST_CLIENT_SECRET, client); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + assertEquals(204, logoutResponse.getStatusLine().getStatusCode()); + + // Check login. + loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD); + Assert.assertNull(loginResponse.getError()); + + code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + + // Check token obtaining without certificate + try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) { + accessTokenResponse = oauth.doAccessTokenRequest(code, TEST_CLIENT_SECRET, client); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + assertEquals(400, accessTokenResponse.getStatusCode()); + assertEquals(OAuthErrorException.INVALID_GRANT, accessTokenResponse.getError()); + + // Check frontchannel logout and login. + oauth.openLogout(); + loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD); + Assert.assertNull(loginResponse.getError()); + + code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + + // Check token obtaining. + try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { + accessTokenResponse = oauth.doAccessTokenRequest(code, TEST_CLIENT_SECRET, client); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + assertEquals(200, accessTokenResponse.getStatusCode()); + + // Check token refresh with other certificate + try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) { + accessTokenResponseRefreshed = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), TEST_CLIENT_SECRET, client); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + assertEquals(401, accessTokenResponseRefreshed.getStatusCode()); + assertEquals(Errors.NOT_ALLOWED, accessTokenResponseRefreshed.getError()); + + // Check token revoke with other certificate + try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) { + tokenRevokeResponse = oauth.doTokenRevoke(accessTokenResponse.getRefreshToken(), "refresh_token", TEST_CLIENT_SECRET, client); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + assertEquals(401, tokenRevokeResponse.getStatusLine().getStatusCode()); + + // Check logout without certificate + try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) { + logoutResponse = oauth.doLogout(accessTokenResponse.getRefreshToken(), TEST_CLIENT_SECRET, client); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + assertEquals(401, logoutResponse.getStatusLine().getStatusCode()); + + // Check logout. + try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { + logoutResponse = oauth.doLogout(accessTokenResponse.getRefreshToken(), TEST_CLIENT_SECRET, client); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + private void setupPolicyClientIdAndSecretNotAcceptableAuthType(String policyName) { + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + createCondition(ClientUpdateContextCondition_NAME, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))); + }); + registerCondition(ClientUpdateContextCondition_NAME, policyName); + logger.info("... Registered Condition : " + ClientUpdateContextCondition_NAME); + + createExecutor(SecureClientAuthEnforceExecutor_NAME, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList( + JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID))); + }); + registerExecutor(SecureClientAuthEnforceExecutor_NAME, policyName); + logger.info("... Registered Executor : " + SecureClientAuthEnforceExecutor_NAME); + + } + + private void setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(String policyName) { + logger.info("Setup Policy"); + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + createCondition(ClientUpdateContextCondition_NAME, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN))); + }); + registerCondition(ClientUpdateContextCondition_NAME, policyName); + logger.info("... Registered Condition : " + ClientUpdateContextCondition_NAME); + + createCondition(ClientRolesCondition_NAME, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setConditionClientRoles(provider, new ArrayList<>(Arrays.asList(SAMPLE_CLIENT_ROLE))); + }); + registerCondition(ClientRolesCondition_NAME, policyName); + logger.info("... Registered Condition : " + ClientRolesCondition_NAME); + + createExecutor(SecureClientAuthEnforceExecutor_NAME, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID, JWTClientAuthenticator.PROVIDER_ID))); + setExecutorAugmentedClientAuthMethod(provider, ClientIdAndSecretAuthenticator.PROVIDER_ID); + setExecutorAugmentActivate(provider); + }); + registerExecutor(SecureClientAuthEnforceExecutor_NAME, policyName); + logger.info("... Registered Executor : " + SecureClientAuthEnforceExecutor_NAME); + + createExecutor(PKCEEnforceExecutor_NAME, PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { + setExecutorAugmentActivate(provider); + }); + registerExecutor(PKCEEnforceExecutor_NAME, policyName); + logger.info("... Registered Executor : " + PKCEEnforceExecutor_NAME); + } + + private void successfulLoginAndLogout(String clientId, String clientSecret) { + oauth.clientId(clientId); + oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD); + + EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent(); + String sessionId = loginEvent.getSessionId(); + String codeId = loginEvent.getDetails().get(Details.CODE_ID); + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, clientSecret); + assertEquals(200, res.getStatusCode()); + events.expectCodeToToken(codeId, sessionId).client(clientId).assertEvent(); + + oauth.doLogout(res.getRefreshToken(), clientSecret); + events.expectLogout(sessionId).client(clientId).clearDetails().assertEvent(); + } + + private void successfulLoginAndLogoutWithPKCE(String clientId, String clientSecret, String userName, String userPassword) throws Exception { + oauth.clientId(clientId); + String codeVerifier = "1a345A7890123456r8901c3456789012b45K7890l23"; // 43 + String codeChallenge = generateS256CodeChallenge(codeVerifier); + oauth.codeChallenge(codeChallenge); + oauth.codeChallengeMethod(OAuth2Constants.PKCE_METHOD_S256); + oauth.nonce("bjapewiziIE083d"); + + oauth.doLogin(userName, userPassword); + + EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent(); + String sessionId = loginEvent.getSessionId(); + String codeId = loginEvent.getDetails().get(Details.CODE_ID); + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + + oauth.codeVerifier(codeVerifier); + OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, clientSecret); + assertEquals(200, res.getStatusCode()); + events.expectCodeToToken(codeId, sessionId).client(clientId).assertEvent(); + + AccessToken token = oauth.verifyToken(res.getAccessToken()); + String userId = findUserByUsername(adminClient.realm(REALM_NAME), userName).getId(); + assertEquals(userId, token.getSubject()); + Assert.assertNotEquals(userName, token.getSubject()); + assertEquals(sessionId, token.getSessionState()); + assertEquals(clientId, token.getIssuedFor()); + + String refreshTokenString = res.getRefreshToken(); + RefreshToken refreshToken = oauth.parseRefreshToken(refreshTokenString); + assertEquals(sessionId, refreshToken.getSessionState()); + assertEquals(clientId, refreshToken.getIssuedFor()); + + OAuthClient.AccessTokenResponse refreshResponse = oauth.doRefreshTokenRequest(refreshTokenString, clientSecret); + assertEquals(200, refreshResponse.getStatusCode()); + events.expectRefresh(refreshToken.getId(), sessionId).client(clientId).assertEvent(); + + AccessToken refreshedToken = oauth.verifyToken(refreshResponse.getAccessToken()); + RefreshToken refreshedRefreshToken = oauth.parseRefreshToken(refreshResponse.getRefreshToken()); + assertEquals(sessionId, refreshedToken.getSessionState()); + assertEquals(sessionId, refreshedRefreshToken.getSessionState()); + assertEquals(findUserByUsername(adminClient.realm(REALM_NAME), userName).getId(), refreshedToken.getSubject()); + + doIntrospectAccessToken(refreshResponse, userName, clientId, clientSecret); + + doTokenRevoke(refreshResponse.getRefreshToken(), clientId, clientSecret, userId, false); + } + + private void failLoginByNotFollowingPKCE(String clientId) { + oauth.clientId(clientId); + oauth.openLoginForm(); + assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + assertEquals("Missing parameter: code_challenge_method", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); + } + + private void failTokenRequestByNotFollowingPKCE(String clientId, String clientSecret) { + oauth.clientId(clientId); + oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD); + + EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent(); + String sessionId = loginEvent.getSessionId(); + String codeId = loginEvent.getDetails().get(Details.CODE_ID); + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, clientSecret); + + assertEquals(OAuthErrorException.INVALID_GRANT, res.getError()); + assertEquals("PKCE code verifier not specified", res.getErrorDescription()); + events.expect(EventType.CODE_TO_TOKEN_ERROR).client(clientId).session(sessionId).clearDetails().error(Errors.CODE_VERIFIER_MISSING).assertEvent(); + + oauth.openLogout(); + events.expectLogout(sessionId).clearDetails().assertEvent(); + } + + private void failLoginWithoutSecureSessionParameter(String clientId, String errorDescription) { + oauth.clientId(clientId); + oauth.openLoginForm(); + assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + assertEquals(errorDescription, oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); + } + + private void failLoginWithoutNonce(String clientId) { + oauth.clientId(clientId); + oauth.openLoginForm(); + assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + assertEquals(ERR_MSG_MISSING_NONCE, oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); + } + +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPolicyBasicsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPolicyBasicsTest.java deleted file mode 100644 index b0a220d869..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPolicyBasicsTest.java +++ /dev/null @@ -1,2231 +0,0 @@ -/* - * Copyright 2020 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.client; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; -import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import javax.ws.rs.BadRequestException; -import javax.ws.rs.core.Response; - -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; -import org.hamcrest.Matchers; -import org.jboss.logging.Logger; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.keycloak.OAuth2Constants; -import org.keycloak.OAuthErrorException; -import org.keycloak.adapters.AdapterUtils; -import org.keycloak.admin.client.resource.ClientResource; -import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; -import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; -import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator; -import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator; -import org.keycloak.client.registration.Auth; -import org.keycloak.client.registration.ClientRegistration; -import org.keycloak.client.registration.ClientRegistrationException; -import org.keycloak.common.Profile; -import org.keycloak.common.util.Base64; -import org.keycloak.common.util.Base64Url; -import org.keycloak.common.util.KeyUtils; -import org.keycloak.common.util.KeycloakUriBuilder; -import org.keycloak.common.util.MultivaluedHashMap; -import org.keycloak.common.util.Time; -import org.keycloak.common.util.UriUtils; -import org.keycloak.constants.ServiceUrlConstants; -import org.keycloak.crypto.KeyType; -import org.keycloak.crypto.SignatureSignerContext; -import org.keycloak.events.Details; -import org.keycloak.events.Errors; -import org.keycloak.events.EventType; -import org.keycloak.jose.jws.Algorithm; -import org.keycloak.jose.jws.JWSBuilder; -import org.keycloak.models.AdminRoles; -import org.keycloak.models.Constants; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; -import org.keycloak.protocol.oidc.OIDCConfigAttributes; -import org.keycloak.protocol.oidc.OIDCLoginProtocol; -import org.keycloak.protocol.oidc.utils.OIDCResponseType; -import org.keycloak.representations.AccessToken; -import org.keycloak.representations.JsonWebToken; -import org.keycloak.representations.RefreshToken; -import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; -import org.keycloak.representations.idm.ClientInitialAccessPresentation; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.ComponentRepresentation; -import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.representations.idm.EventRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.representations.oidc.OIDCClientRepresentation; -import org.keycloak.representations.oidc.TokenMetadataRepresentation; -import org.keycloak.services.Urls; -import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyProvider; -import org.keycloak.services.clientpolicy.DefaultClientPolicyProviderFactory; -import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory; -import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory; -import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; -import org.keycloak.services.clientpolicy.condition.ClientUpdateContextConditionFactory; -import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceGroupsConditionFactory; -import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceHostsConditionFactory; -import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceRolesConditionFactory; -import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory; -import org.keycloak.services.clientpolicy.condition.ClientScopesConditionFactory; -import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; -import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutorFactory; -import org.keycloak.services.clientpolicy.executor.SecureClientAuthEnforceExecutorFactory; -import org.keycloak.services.clientpolicy.executor.SecureRedirectUriEnforceExecutorFactory; -import org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutorFactory; -import org.keycloak.services.clientpolicy.executor.SecureRequestObjectExecutor; -import org.keycloak.services.clientpolicy.executor.SecureRequestObjectExecutorFactory; -import org.keycloak.services.clientpolicy.executor.SecureResponseTypeExecutorFactory; -import org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutorFactory; -import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmEnforceExecutorFactory; -import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory; -import org.keycloak.testsuite.AbstractKeycloakTest; -import org.keycloak.testsuite.AssertEvents; -import org.keycloak.testsuite.admin.ApiUtil; -import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; -import org.keycloak.testsuite.arquillian.annotation.EnableFeature; -import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer; -import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls; -import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource; -import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource; -import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject; -import org.keycloak.testsuite.services.clientpolicy.condition.TestRaiseExeptionConditionFactory; -import org.keycloak.testsuite.util.MutualTLSUtils; -import org.keycloak.testsuite.util.OAuthClient; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.keycloak.admin.client.resource.RolesResource; -import org.keycloak.testsuite.util.RoleBuilder; - -import org.keycloak.testsuite.util.ServerURLs; -import org.keycloak.util.JsonSerialization; - -@EnableFeature(value = Profile.Feature.CLIENT_POLICIES, skipRestart = true) -public class ClientPolicyBasicsTest extends AbstractKeycloakTest { - - private static final Logger logger = Logger.getLogger(ClientPolicyBasicsTest.class); - - static final String REALM_NAME = "test"; - static final String TEST_CLIENT = "test-app"; - - static final String CLIENTUPDATECONTEXT_CONDITION_NAME = "ClientUpdateContextCondition"; - static final String CLIENTUPDATECONTEXT_CONDITION_ALPHA_NAME = "ClientUpdateContextCondition-alpha"; - - static final String CLIENTROLES_CONDITION_NAME = "ClientRolesCondition"; - static final String CLIENTROLES_CONDITION_ALPHA_NAME = "ClientRolesCondition-alpha"; - static final String CLIENTROLES_CONDITION_BETA_NAME = "ClientRolesCondition-beta"; - - static final String SECURERESPONSETYPE_EXECUTOR_NAME = "SecureResponseTypeExecutor"; - - static final String SECUREREQUESTOBJECT_EXECUTOR_NAME = "SecureRequestObjectExecutor"; - - ClientRegistration reg; - - @Rule - public AssertEvents events = new AssertEvents(this); - - @Before - public void before() throws Exception { - // get initial access token for Dynamic Client Registration with authentication - reg = ClientRegistration.create().url(suiteContext.getAuthServerInfo().getContextRoot() + "/auth", REALM_NAME).build(); - ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10)); - reg.auth(Auth.token(token)); - - } - - @After - public void after() throws Exception { - reg.close(); - - } - - @Override - public void addTestRealms(List testRealms) { - RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); - - List users = realm.getUsers(); - - LinkedList credentials = new LinkedList<>(); - CredentialRepresentation password = new CredentialRepresentation(); - password.setType(CredentialRepresentation.PASSWORD); - password.setValue("password"); - credentials.add(password); - - UserRepresentation user = new UserRepresentation(); - user.setEnabled(true); - user.setUsername("manage-clients"); - user.setCredentials(credentials); - user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.MANAGE_CLIENTS))); - - users.add(user); - - user = new UserRepresentation(); - user.setEnabled(true); - user.setUsername("create-clients"); - user.setCredentials(credentials); - user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.CREATE_CLIENT))); - user.setGroups(Arrays.asList("topGroup")); // defined in testrealm.json - - users.add(user); - - realm.setUsers(users); - - testRealms.add(realm); - } - - @Test - public void testAdminClientRegisterUnacceptableAuthType() { - setupPolicyAcceptableAuthType("MyPolicy"); - - try { - createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> { - clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID); - }); - fail(); - } catch (ClientPolicyException e) { - assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); - } - } - - @Test - public void testAdminClientRegisterAcceptableAuthType() throws ClientPolicyException { - setupPolicyAcceptableAuthType("MyPolicy"); - - String clientId = createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> { - clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); - }); - try { - assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType()); - } finally { - deleteClientByAdmin(clientId); - } - } - - @Test - public void testAdminClientUpdateUnacceptableAuthType() throws ClientPolicyException { - setupPolicyAcceptableAuthType("MyPolicy"); - - String clientId = createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> { - clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); - }); - - try { - assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType()); - - try { - updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> { - clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID); - }); - fail(); - } catch (BadRequestException bre) {} - assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType()); - - } finally { - deleteClientByAdmin(clientId); - } - } - - @Test - public void testAdminClientUpdateAcceptableAuthType() throws ClientPolicyException { - setupPolicyAcceptableAuthType("MyPolicy"); - - String clientId = createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> { - clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); - }); - - try { - assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType()); - - updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> { - clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID); - }); - assertEquals(JWTClientAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType()); - - } finally { - deleteClientByAdmin(clientId); - } - } - - @Test - public void testAdminClientRegisterDefaultAuthType() { - setupPolicyAcceptableAuthType("MyPolicy"); - - try { - createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> {}); - fail(); - } catch (ClientPolicyException e) { - assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); - } - } - - @Test - public void testAdminClientUpdateDefaultAuthType() throws ClientPolicyException { - setupPolicyAcceptableAuthType("MyPolicy"); - - String clientId = createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> { - clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); - }); - - try { - assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType()); - - updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> { - clientRep.setServiceAccountsEnabled(Boolean.FALSE); - }); - assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType()); - assertEquals(Boolean.FALSE, getClientByAdmin(clientId).isServiceAccountsEnabled()); - } finally { - deleteClientByAdmin(clientId); - } - } - - @Test - public void testAdminClientAugmentedAuthType() throws ClientPolicyException { - setupPolicyAcceptableAuthType("MyPolicy"); - - updateExecutor("SecureClientAuthEnforceExecutor", (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - setExecutorAugmentedClientAuthMethod(provider, X509ClientAuthenticator.PROVIDER_ID); - }); - - String clientId = createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> { - clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID); - }); - - try { - assertEquals(X509ClientAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType()); - - updateExecutor("SecureClientAuthEnforceExecutor", (ComponentRepresentation provider) -> { - setExecutorAugmentedClientAuthMethod(provider, JWTClientAuthenticator.PROVIDER_ID); - }); - - updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> { - clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); - }); - assertEquals(JWTClientAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType()); - - } finally { - deleteClientByAdmin(clientId); - } - } - - @Test - public void testDynamicClientRegisterAndUpdate() throws ClientRegistrationException { - setupPolicyAcceptableAuthType("MyPolicy"); - - String clientId = createClientDynamically("Gourmet-App", (OIDCClientRepresentation clientRep) -> {}); - try { - assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, getClientDynamically(clientId).getTokenEndpointAuthMethod()); - assertEquals(Boolean.FALSE, getClientDynamically(clientId).getTlsClientCertificateBoundAccessTokens()); - - updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> { - clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.CLIENT_SECRET_BASIC); - clientRep.setTlsClientCertificateBoundAccessTokens(Boolean.TRUE); - }); - assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, getClientDynamically(clientId).getTokenEndpointAuthMethod()); - assertEquals(Boolean.TRUE, getClientDynamically(clientId).getTlsClientCertificateBoundAccessTokens()); - - } finally { - deleteClientDynamically(clientId); - } - } - - @Test - public void testAuthzCodeFlowUnderMultiPhasePolicy() throws Exception { - setupPolicyAuthzCodeFlowUnderMultiPhasePolicy("MultiPhasePolicy"); - - String userName = "test-user@localhost"; - String userPassword = "password"; - String clientName = "Flughafen-App"; - String clientId = createClientDynamically(clientName, (OIDCClientRepresentation clientRep) -> {}); - events.expect(EventType.CLIENT_REGISTER).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent(); - OIDCClientRepresentation response = getClientDynamically(clientId); - String clientSecret = response.getClientSecret(); - assertEquals(clientName, response.getClientName()); - assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, response.getTokenEndpointAuthMethod()); - events.expect(EventType.CLIENT_INFO).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent(); - - adminClient.realm(REALM_NAME).clients().get(clientId).roles().create(RoleBuilder.create().name("sample-client-role").build()); - - successfulLoginAndLogoutWithPKCE(response.getClientId(), clientSecret, userName, userPassword); - } - - @Test - public void testCreateDeletePolicyRuntime() throws ClientRegistrationException { - String clientId = createClientDynamically("Gourmet-App", (OIDCClientRepresentation clientRep) -> {}); - try { - OIDCClientRepresentation clientRep = getClientDynamically(clientId); - assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, clientRep.getTokenEndpointAuthMethod()); - events.expect(EventType.CLIENT_REGISTER).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent(); - events.expect(EventType.CLIENT_INFO).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent(); - adminClient.realm(REALM_NAME).clients().get(clientId).roles().create(RoleBuilder.create().name("sample-client-role").build()); - - successfulLoginAndLogout(clientId, clientRep.getClientSecret()); - - setupPolicyAuthzCodeFlowUnderMultiPhasePolicy("MyPolicy"); - - failLoginByNotFollowingPKCE(clientId); - - deletePolicy("MyPolicy"); - logger.info("... Deleted Policy : MyPolicy"); - - successfulLoginAndLogout(clientId, clientRep.getClientSecret()); - - } finally { - deleteClientDynamically(clientId); - } - } - - @Test - public void testCreateUpdateDeleteConditionRuntime() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createExecutor("PKCEEnforceExecutor", PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - }); - registerExecutor("PKCEEnforceExecutor", policyName); - logger.info("... Registered Executor : PKCEEnforceExecutor"); - - String clientId = "Zahlungs-App"; - String clientSecret = "secret"; - String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientSecret); - }); - adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name("sample-client-role").build()); - - try { - successfulLoginAndLogout(clientId, clientSecret); - - createCondition(CLIENTROLES_CONDITION_NAME, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role"))); - }); - registerCondition(CLIENTROLES_CONDITION_NAME, policyName); - logger.info("... Registered Condition : " + CLIENTROLES_CONDITION_NAME); - - failLoginByNotFollowingPKCE(clientId); - - updateCondition(CLIENTROLES_CONDITION_NAME, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("anothor-client-role"))); - }); - - successfulLoginAndLogout(clientId, clientSecret); - - deleteCondition(CLIENTROLES_CONDITION_NAME, policyName); - - successfulLoginAndLogout(clientId, clientSecret); - - } finally { - deleteClientByAdmin(cid); - } - } - - @Test - public void testCreateUpdateDeleteExecutorRuntime() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition(CLIENTROLES_CONDITION_NAME, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role"))); - }); - registerCondition(CLIENTROLES_CONDITION_NAME, policyName); - logger.info("... Registered Condition : " + CLIENTROLES_CONDITION_NAME); - - createCondition(CLIENTUPDATECONTEXT_CONDITION_NAME, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))); - }); - registerCondition(CLIENTUPDATECONTEXT_CONDITION_NAME, policyName); - logger.info("... Registered Condition : " + CLIENTUPDATECONTEXT_CONDITION_NAME); - - String clientId = "Zahlungs-App"; - String clientSecret = "secret"; - String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientSecret); - }); - adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name("sample-client-role").build()); - - try { - successfulLoginAndLogout(clientId, clientSecret); - - createExecutor("PKCEEnforceExecutor", PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAugmentDeactivate(provider); - }); - registerExecutor("PKCEEnforceExecutor", policyName); - logger.info("... Registered Executor : PKCEEnforceExecutor"); - - failLoginByNotFollowingPKCE(clientId); - - updateExecutor("PKCEEnforceExecutor", (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - }); - - updateClientByAdmin(cid, (ClientRepresentation clientRep) -> { - clientRep.setServiceAccountsEnabled(Boolean.FALSE); - }); - assertEquals(false, getClientByAdmin(cid).isServiceAccountsEnabled()); - assertEquals(OAuth2Constants.PKCE_METHOD_S256, OIDCAdvancedConfigWrapper.fromClientRepresentation(getClientByAdmin(cid)).getPkceCodeChallengeMethod()); - - deleteExecutor("PKCEEnforceExecutor", policyName); - logger.info("... Deleted Executor : PKCEEnforceExecutor"); - - updateClientByAdmin(cid, (ClientRepresentation clientRep) -> { - OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setPkceCodeChallengeMethod(null); - }); - assertEquals(null, OIDCAdvancedConfigWrapper.fromClientRepresentation(getClientByAdmin(cid)).getPkceCodeChallengeMethod()); - - successfulLoginAndLogout(clientId, clientSecret); - - } finally { - deleteClientByAdmin(cid); - } - - } - - @Test - public void testMultiplePolicies() throws ClientRegistrationException, ClientPolicyException { - String policyAlphaName = "MyPolicy-alpha"; - createPolicy(policyAlphaName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyAlphaName); - - createCondition(CLIENTROLES_CONDITION_ALPHA_NAME, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role-alpha", "sample-client-role-zeta"))); - }); - registerCondition(CLIENTROLES_CONDITION_ALPHA_NAME, policyAlphaName); - logger.info("... Registered Condition : " + CLIENTROLES_CONDITION_ALPHA_NAME); - - createCondition(CLIENTUPDATECONTEXT_CONDITION_ALPHA_NAME, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))); - }); - registerCondition(CLIENTUPDATECONTEXT_CONDITION_ALPHA_NAME, policyAlphaName); - logger.info("... Registered Condition : " + CLIENTUPDATECONTEXT_CONDITION_ALPHA_NAME); - - createExecutor("SecureClientAuthEnforceExecutor-alpha", SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID))); - setExecutorAugmentActivate(provider); - setExecutorAugmentedClientAuthMethod(provider, ClientIdAndSecretAuthenticator.PROVIDER_ID); - }); - registerExecutor("SecureClientAuthEnforceExecutor-alpha", policyAlphaName); - logger.info("... Registered Executor : SecureClientAuthEnforceExecutor-alpha"); - - String policyBetaName = "MyPolicy-beta"; - createPolicy(policyBetaName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyBetaName); - - createCondition(CLIENTROLES_CONDITION_BETA_NAME, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role-beta", "sample-client-role-zeta"))); - }); - registerCondition(CLIENTROLES_CONDITION_BETA_NAME, policyBetaName); - logger.info("... Registered Condition : " + CLIENTROLES_CONDITION_BETA_NAME); - - createExecutor("PKCEEnforceExecutor-beta", PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - }); - registerExecutor("PKCEEnforceExecutor-beta", policyBetaName); - logger.info("... Registered Executor : PKCEEnforceExecutor-beta"); - - String clientAlphaId = "Alpha-App"; - String clientAlphaSecret = "secretAlpha"; - String cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientAlphaSecret); - clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); - }); - RolesResource rolesResourceAlpha = adminClient.realm(REALM_NAME).clients().get(cAlphaId).roles(); - rolesResourceAlpha.create(RoleBuilder.create().name("sample-client-role-alpha").build()); - rolesResourceAlpha.create(RoleBuilder.create().name("sample-client-role-common").build()); - - String clientBetaId = "Beta-App"; - String clientBetaSecret = "secretBeta"; - String cBetaId = createClientByAdmin(clientBetaId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientBetaSecret); - }); - RolesResource rolesResourceBeta = adminClient.realm(REALM_NAME).clients().get(cBetaId).roles(); - rolesResourceBeta.create(RoleBuilder.create().name("sample-client-role-beta").build()); - rolesResourceBeta.create(RoleBuilder.create().name("sample-client-role-common").build()); - - try { - assertEquals(ClientIdAndSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cAlphaId).getClientAuthenticatorType()); - - successfulLoginAndLogout(clientAlphaId, clientAlphaSecret); - - failLoginByNotFollowingPKCE(clientBetaId); - - } finally { - deleteClientByAdmin(cAlphaId); - deleteClientByAdmin(cBetaId); - } - } - - @Test - public void testIntentionalExceptionOnCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition("TestRaiseExeptionCondition", TestRaiseExeptionConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerCondition("TestRaiseExeptionCondition", policyName); - logger.info("... Registered Condition : TestRaiseExeptionCondition-beta"); - - try { - createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> { - }); - fail(); - } catch (ClientPolicyException e) { - assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); - } - } - - @Test - public void testSecureResponseTypeExecutor() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition(CLIENTROLES_CONDITION_NAME, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role"))); - }); - registerCondition(CLIENTROLES_CONDITION_NAME, policyName); - logger.info("... Registered Condition : " + CLIENTROLES_CONDITION_NAME); - - createExecutor(SECURERESPONSETYPE_EXECUTOR_NAME, SecureResponseTypeExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerExecutor(SECURERESPONSETYPE_EXECUTOR_NAME, policyName); - logger.info("... Registered Executor : " + SECURERESPONSETYPE_EXECUTOR_NAME); - - String clientId = "Zahlungs-App"; - String clientSecret = "secret"; - String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientSecret); - clientRep.setStandardFlowEnabled(Boolean.TRUE); - clientRep.setImplicitFlowEnabled(Boolean.TRUE); - clientRep.setPublicClient(Boolean.FALSE); - }); - adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name("sample-client-role").build()); - - try { - oauth.clientId(clientId); - oauth.openLoginForm(); - assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - assertEquals("invalid response_type", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); - - oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN + " " + OIDCResponseType.TOKEN); - oauth.nonce("cie8cjcwiw"); - oauth.doLogin("test-user@localhost", "password"); - - EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent(); - String sessionId = loginEvent.getSessionId(); - String codeId = loginEvent.getDetails().get(Details.CODE_ID); - String code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode(); - OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, clientSecret); - assertEquals(200, res.getStatusCode()); - events.expectCodeToToken(codeId, sessionId).client(clientId).assertEvent(); - - oauth.doLogout(res.getRefreshToken(), clientSecret); - events.expectLogout(sessionId).client(clientId).clearDetails().assertEvent(); - - oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN); - oauth.nonce("vbwe566fsfffds"); - oauth.doLogin("test-user@localhost", "password"); - - loginEvent = events.expectLogin().client(clientId).assertEvent(); - sessionId = loginEvent.getSessionId(); - codeId = loginEvent.getDetails().get(Details.CODE_ID); - code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode(); - res = oauth.doAccessTokenRequest(code, clientSecret); - assertEquals(200, res.getStatusCode()); - events.expectCodeToToken(codeId, sessionId).client(clientId).assertEvent(); - - oauth.doLogout(res.getRefreshToken(), clientSecret); - events.expectLogout(sessionId).client(clientId).clearDetails().assertEvent(); - } finally { - deleteClientByAdmin(cid); - } - } - - @Test - public void testSecureRequestObjectExecutor() throws ClientRegistrationException, ClientPolicyException, URISyntaxException, IOException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition(CLIENTROLES_CONDITION_NAME, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role"))); - }); - registerCondition(CLIENTROLES_CONDITION_NAME, policyName); - logger.info("... Registered Condition : " + CLIENTROLES_CONDITION_NAME); - - createExecutor(SECUREREQUESTOBJECT_EXECUTOR_NAME, SecureRequestObjectExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerExecutor(SECUREREQUESTOBJECT_EXECUTOR_NAME, policyName); - logger.info("... Registered Executor : " + SECUREREQUESTOBJECT_EXECUTOR_NAME); - - String clientId = "Zahlungs-App"; - String clientSecret = "secret"; - String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientSecret); - OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestUris(Arrays.asList(TestApplicationResourceUrls.clientRequestUri())); - }); - adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name("sample-client-role").build()); - - try { - oauth.clientId(clientId); - AuthorizationEndpointRequestObject requestObject; - - // check whether whether request object exists - oauth.request(null); - oauth.requestUri(null); - oauth.openLoginForm(); - assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - assertEquals("Invalid parameter", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); - - // check whether request_uri is https scheme - // cannot test because existing AuthorizationEndpoint check and return error before executing client policy - - // check whether request object can be retrieved from request_uri - // cannot test because existing AuthorizationEndpoint check and return error before executing client policy - - // check whether request object can be parsed successfully - // cannot test because existing AuthorizationEndpoint check and return error before executing client policy - - // check whether scope exists in both query parameter and request object - requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); - requestObject.setScope(null); - registerRequestObject(requestObject, clientId, Algorithm.ES256, true); - oauth.openLoginForm(); - assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - assertEquals("Missing parameter : scope", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); - - // check whether "exp" claim exists - requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); - requestObject.exp(null); - registerRequestObject(requestObject, clientId, Algorithm.ES256, false); - oauth.openLoginForm(); - assertEquals(SecureRequestObjectExecutor.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - assertEquals("Missing parameter : exp", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); - - // check whether request object not expired - requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); - requestObject.exp(Long.valueOf(0)); - registerRequestObject(requestObject, clientId, Algorithm.ES256, true); - oauth.openLoginForm(); - assertEquals(SecureRequestObjectExecutor.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - assertEquals("Request Expired", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); - - // check whether "aud" claim exists - requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); - requestObject.audience((String)null); - registerRequestObject(requestObject, clientId, Algorithm.ES256, false); - oauth.openLoginForm(); - assertEquals(SecureRequestObjectExecutor.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - assertEquals("Missing parameter : aud", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); - - // check whether "aud" claim points to this keycloak as authz server - requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); - requestObject.audience(suiteContext.getAuthServerInfo().getContextRoot().toString()); - registerRequestObject(requestObject, clientId, Algorithm.ES256, true); - oauth.openLoginForm(); - assertEquals(SecureRequestObjectExecutor.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - assertEquals("Invalid parameter : aud", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); - - // confirm whether all parameters in query string are included in the request object, and have the same values - // argument "request" are parameters overridden by parameters in request object - requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); - requestObject.setState("notmatchstate"); - registerRequestObject(requestObject, clientId, Algorithm.ES256, false); - oauth.openLoginForm(); - assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - assertEquals("Invalid parameter", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); - - // valid request object - requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId); - registerRequestObject(requestObject, clientId, Algorithm.ES256, true); - - successfulLoginAndLogout(clientId, clientSecret); - } finally { - deleteClientByAdmin(cid); - } - - } - - @Test - public void testSecureSessionEnforceExecutor() throws ClientRegistrationException, ClientPolicyException { - String policyBetaName = "MyPolicy-beta"; - createPolicy(policyBetaName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyBetaName); - - createCondition("ClientRolesCondition-beta", ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role-beta"))); - }); - registerCondition("ClientRolesCondition-beta", policyBetaName); - logger.info("... Registered Condition : ClientRolesCondition-beta"); - - createExecutor("SecureSessionEnforceExecutor-beta", SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerExecutor("SecureSessionEnforceExecutor-beta", policyBetaName); - logger.info("... Registered Executor : SecureSessionEnforceExecutor-beta"); - - String clientAlphaId = "Alpha-App"; - String clientAlphaSecret = "secretAlpha"; - String cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientAlphaSecret); - }); - adminClient.realm(REALM_NAME).clients().get(cAlphaId).roles().create(RoleBuilder.create().name("sample-client-role-alpha").build()); - - String clientBetaId = "Beta-App"; - String clientBetaSecret = "secretBeta"; - String cBetaId = createClientByAdmin(clientBetaId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientBetaSecret); - }); - adminClient.realm(REALM_NAME).clients().get(cBetaId).roles().create(RoleBuilder.create().name("sample-client-role-beta").build()); - - try { - successfulLoginAndLogout(clientAlphaId, clientAlphaSecret); - - oauth.openid(false); - successfulLoginAndLogout(clientAlphaId, clientAlphaSecret); - - oauth.openid(true); - failLoginWithoutSecureSessionParameter(clientBetaId, "Missing parameter: nonce"); - - oauth.nonce("yesitisnonce"); - successfulLoginAndLogout(clientBetaId, clientBetaSecret); - - oauth.openid(false); - oauth.stateParamHardcoded(null); - failLoginWithoutSecureSessionParameter(clientBetaId, "Missing parameter: state"); - - oauth.stateParamRandom(); - successfulLoginAndLogout(clientBetaId, clientBetaSecret); - } finally { - deleteClientByAdmin(cAlphaId); - deleteClientByAdmin(cBetaId); - } - } - - @Test - public void testClientScopesCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition("ClientScopesCondition", ClientScopesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientScopes(provider, new ArrayList<>(Arrays.asList("offline_access", "microprofile-jwt"))); - }); - registerCondition("ClientScopesCondition", policyName); - logger.info("... Registered Condition : ClientScopesCondition"); - - createExecutor("PKCEEnforceExecutor", PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - }); - registerExecutor("PKCEEnforceExecutor", policyName); - logger.info("... Registered Executor : PKCEEnforceExecutor"); - - String clientAlphaId = "Alpha-App"; - String clientAlphaSecret = "secretAlpha"; - String cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientAlphaSecret); - }); - - try { - oauth.scope("address" + " " + "phone"); - successfulLoginAndLogout(clientAlphaId, clientAlphaSecret); - - oauth.scope("microprofile-jwt" + " " + "profile"); - failLoginByNotFollowingPKCE(clientAlphaId); - - oauth.scope("microprofile-jwt" + " " + "profile"); - failLoginByNotFollowingPKCE(clientAlphaId); - - successfulLoginAndLogoutWithPKCE(clientAlphaId, clientAlphaSecret, "test-user@localhost", "password"); - } catch (Exception e) { - fail(); - } finally { - deleteClientByAdmin(cAlphaId); - } - } - - @Test - public void testSecureSigningAlgorithmEnforceExecutor() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition("ClientUpdateContextCondition", ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList( - ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, - ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN, - ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN))); - }); - registerCondition("ClientUpdateContextCondition", policyName); - logger.info("... Registered Condition : ClientUpdateContextConditionFactory"); - - createExecutor("SecureSigningAlgorithmEnforceExecutor", SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerExecutor("SecureSigningAlgorithmEnforceExecutor", policyName); - logger.info("... Registered Executor : SecureSigningAlgorithmEnforceExecutor"); - - String cAppAdminId = null; - String cAppDynamicId = null; - try { - - // create by Admin REST API - fail - try { - createClientByAdmin("App-by-Admin", (ClientRepresentation clientRep) -> { - clientRep.setSecret("secretBeta"); - clientRep.setAttributes(new HashMap<>()); - clientRep.getAttributes().put(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, Algorithm.none.name()); - }); - fail(); - } catch (ClientPolicyException e) { - assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); - } - - // create by Admin REST API - success - cAppAdminId = createClientByAdmin("App-by-Admin", (ClientRepresentation clientRep) -> { - clientRep.setAttributes(new HashMap<>()); - clientRep.getAttributes().put(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, Algorithm.PS256.name()); - clientRep.getAttributes().put(OIDCConfigAttributes.REQUEST_OBJECT_SIGNATURE_ALG, Algorithm.ES256.name()); - clientRep.getAttributes().put(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG, Algorithm.ES256.name()); - clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, Algorithm.ES256.name()); - clientRep.getAttributes().put(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG, Algorithm.ES256.name()); - }); - - // update by Admin REST API - fail - try { - updateClientByAdmin(cAppAdminId, (ClientRepresentation clientRep) -> { - clientRep.setAttributes(new HashMap<>()); - clientRep.getAttributes().put(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG, Algorithm.RS512.name()); - }); - } catch (Exception e) { - assertEquals("HTTP 400 Bad Request", e.getMessage()); - } - ClientRepresentation cRep = getClientByAdmin(cAppAdminId); - assertEquals(Algorithm.ES256.name(), cRep.getAttributes().get(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG)); - - // update by Admin REST API - success - updateClientByAdmin(cAppAdminId, (ClientRepresentation clientRep) -> { - clientRep.setAttributes(new HashMap<>()); - clientRep.getAttributes().put(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG, Algorithm.PS384.name()); - }); - cRep = getClientByAdmin(cAppAdminId); - assertEquals(Algorithm.PS384.name(), cRep.getAttributes().get(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG)); - - // create dynamically - fail - try { - createClientByAdmin("App-in-Dynamic", (ClientRepresentation clientRep) -> { - clientRep.setSecret("secretBeta"); - clientRep.setAttributes(new HashMap<>()); - clientRep.getAttributes().put(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, Algorithm.RS384.name()); - }); - fail(); - } catch (ClientPolicyException e) { - assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); - } - - // create dynamically - success - cAppDynamicId = createClientDynamically("App-in-Dynamic", (OIDCClientRepresentation clientRep) -> { - clientRep.setUserinfoSignedResponseAlg(Algorithm.ES256.name()); - clientRep.setRequestObjectSigningAlg(Algorithm.ES256.name()); - clientRep.setIdTokenSignedResponseAlg(Algorithm.PS256.name()); - clientRep.setTokenEndpointAuthSigningAlg(Algorithm.PS256.name()); - }); - events.expect(EventType.CLIENT_REGISTER).client(cAppDynamicId).user(Matchers.isEmptyOrNullString()).assertEvent(); - getClientDynamically(cAppDynamicId); - - // update dynamically - fail - try { - updateClientDynamically(cAppDynamicId, (OIDCClientRepresentation clientRep) -> { - clientRep.setIdTokenSignedResponseAlg(Algorithm.RS256.name()); - }); - fail(); - } catch (ClientRegistrationException e) { - assertEquals("Failed to send request", e.getMessage()); - } - OIDCClientRepresentation oidcCRep = getClientDynamically(cAppDynamicId); - assertEquals(Algorithm.PS256.name(), oidcCRep.getIdTokenSignedResponseAlg()); - - // update dynamically - success - updateClientDynamically(cAppDynamicId, (OIDCClientRepresentation clientRep) -> { - clientRep.setIdTokenSignedResponseAlg(Algorithm.ES384.name()); - }); - oidcCRep = getClientDynamically(cAppDynamicId); - assertEquals(Algorithm.ES384.name(), oidcCRep.getIdTokenSignedResponseAlg()); - - } finally { - deleteClientByAdmin(cAppAdminId); - deleteClientDynamically(cAppDynamicId); - } - } - - @Test - public void testClientAccessTypeCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition("ClientAccessTypeCondition", ClientAccessTypeConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientAccessType(provider, new ArrayList<>(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL))); - }); - registerCondition("ClientAccessTypeCondition", policyName); - logger.info("... Registered Condition : ClientAccessTypeCondition"); - - createExecutor("SecureSessionEnforceExecutor", SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerExecutor("SecureSessionEnforceExecutor", policyName); - logger.info("... Registered Executor : SecureSessionEnforceExecutor"); - - // confidential client - String clientAlphaId = "Alpha-App"; - String clientAlphaSecret = "secretAlpha"; - String cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientAlphaSecret); - clientRep.setBearerOnly(Boolean.FALSE); - clientRep.setPublicClient(Boolean.FALSE); - }); - - // public client - String clientBetaId = "Beta-App"; - String cBetaId = createClientByAdmin(clientBetaId, (ClientRepresentation clientRep) -> { - clientRep.setBearerOnly(Boolean.FALSE); - clientRep.setPublicClient(Boolean.TRUE); - }); - - try { - successfulLoginAndLogout(clientBetaId, null); - failLoginWithoutNonce(clientAlphaId); - } finally { - deleteClientByAdmin(cAlphaId); - deleteClientByAdmin(cBetaId); - } - } - - @Test - public void testConditionWithoutNoConfiguration() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy-ClientAccessTypeCondition"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - createCondition("ClientAccessTypeCondition", ClientAccessTypeConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerCondition("ClientAccessTypeCondition", policyName); - logger.info("... Registered Condition : ClientAccessTypeCondition"); - - policyName = "MyPolicy-ClientUpdateSourceGroupsCondition"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - createCondition("ClientUpdateSourceGroupsCondition", ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerCondition("ClientUpdateSourceGroupsCondition", policyName); - logger.info("... Registered Condition : ClientUpdateSourceGroupsCondition"); - - policyName = "MyPolicy-ClientUpdateSourceRolesCondition"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - createCondition("ClientUpdateSourceRolesCondition", ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerCondition("ClientUpdateSourceRolesCondition", policyName); - logger.info("... Registered Condition : ClientUpdateSourceRolesCondition"); - - policyName = "MyPolicy-ClientUpdateContextCondition"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - createCondition("ClientUpdateContextCondition", ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerCondition("ClientUpdateContextCondition", policyName); - logger.info("... Registered Condition : ClientUpdateContextCondition"); - - policyName = "MyPolicy-SecureSessionEnforceExecutor"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - createExecutor("SecureSessionEnforceExecutor", SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerExecutor("SecureSessionEnforceExecutor", policyName); - logger.info("... Registered Executor : SecureSessionEnforceExecutor"); - - String clientAlphaId = "Alpha-App"; - String clientAlphaSecret = "secretAlpha"; - String cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientAlphaSecret); - clientRep.setBearerOnly(Boolean.FALSE); - clientRep.setPublicClient(Boolean.FALSE); - }); - - try { - successfulLoginAndLogout(clientAlphaId, clientAlphaSecret); - } finally { - deleteClientByAdmin(cAlphaId); - } - } - - @AuthServerContainerExclude(AuthServer.REMOTE) - @Test - public void testClientUpdateSourceHostsCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition("ClientUpdateSourceHostsCondition", ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientUpdateSourceHosts(provider, new ArrayList<>(Arrays.asList("localhost", "127.0.0.1"))); - }); - registerCondition("ClientUpdateSourceHostsCondition", policyName); - logger.info("... Registered Condition : ClientUpdateSourceHostsCondition"); - - createExecutor("SecureClientAuthEnforceExecutor", SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList( - JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID))); - }); - registerExecutor("SecureClientAuthEnforceExecutor", policyName); - logger.info("... Registered Executor : SecureClientAuthEnforceExecutor"); - - String clientAlphaId = "Alpha-App"; - String clientAlphaSecret = "secretAlpha"; - try { - createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientAlphaSecret); - }); - fail(); - } catch (ClientPolicyException e) { - assertEquals(Errors.INVALID_REGISTRATION, e.getMessage()); - } - - String cAlphaId = null; - try { - updateCondition("ClientUpdateSourceHostsCondition", (ComponentRepresentation provider) -> { - setConditionClientUpdateSourceHosts(provider, new ArrayList<>(Arrays.asList("example.com"))); - }); - cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientAlphaSecret); - }); - } finally { - deleteClientByAdmin(cAlphaId); - } - } - - @Test - public void testClientUpdateSourceGroupsCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition("ClientUpdateSourceGroupsCondition", ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientUpdateSourceGroups(provider, new ArrayList<>(Arrays.asList("topGroup"))); - }); - registerCondition("ClientUpdateSourceGroupsCondition", policyName); - logger.info("... Registered Condition : ClientUpdateSourceGroupsCondition"); - - createExecutor("SecureClientAuthEnforceExecutor", SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList(JWTClientAuthenticator.PROVIDER_ID))); - }); - registerExecutor("SecureClientAuthEnforceExecutor", policyName); - logger.info("... Registered Executor : SecureClientAuthEnforceExecutor"); - - String cid = null; - try { - try { - authCreateClients(); - createClientDynamically("Gourmet-App", (OIDCClientRepresentation clientRep) -> {}); - fail(); - } catch (ClientRegistrationException e) { - assertEquals("Failed to send request", e.getMessage()); - } - authManageClients(); - cid = createClientDynamically("Gourmet-App", (OIDCClientRepresentation clientRep) -> {}); - } finally { - deleteClientByAdmin(cid); - - } - } - - @Test - public void testUpdatingClientSourceRolesCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition("ClientUpdateSourceRolesCondition", ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionUpdatingClientSourceRoles(provider, new ArrayList<>(Arrays.asList(AdminRoles.CREATE_CLIENT))); - }); - registerCondition("ClientUpdateSourceRolesCondition", policyName); - logger.info("... Registered Condition : ClientUpdateSourceRolesCondition"); - - createExecutor("SecureClientAuthEnforceExecutor", SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList(JWTClientAuthenticator.PROVIDER_ID))); - }); - registerExecutor("SecureClientAuthEnforceExecutor", policyName); - logger.info("... Registered Executor : SecureClientAuthEnforceExecutor"); - - String cid = null; - try { - try { - authCreateClients(); - createClientDynamically("Gourmet-App", (OIDCClientRepresentation clientRep) -> {}); - fail(); - } catch (ClientRegistrationException e) { - assertEquals("Failed to send request", e.getMessage()); - } - authManageClients(); - cid = createClientDynamically("Gourmet-App", (OIDCClientRepresentation clientRep) -> { - }); - } finally { - deleteClientByAdmin(cid); - - } - } - - @Test - public void testSecureRedirectUriEnforceExecutor() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition("ClientUpdateContextCondition", ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList( - ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, - ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN, - ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN))); - }); - registerCondition("ClientUpdateContextCondition", policyName); - logger.info("... Registered Condition : UpdatingClientSourceCondition"); - - createExecutor("SecureRedirectUriEnforceExecutor", SecureRedirectUriEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerExecutor("SecureRedirectUriEnforceExecutor", policyName); - logger.info("... Registered Executor : SecureRedirectUriEnforceExecutor"); - - String cid = null; - try { - try { - createClientDynamically("Gourmet-App", (OIDCClientRepresentation clientRep) -> { - clientRep.setRedirectUris(Collections.singletonList("http://newredirect")); - }); - fail(); - } catch (ClientRegistrationException e) { - assertEquals("Failed to send request", e.getMessage()); - } - updateCondition("ClientUpdateContextCondition", (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList( - ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, - ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN))); - }); - cid = createClientDynamically("Gourmet-App", (OIDCClientRepresentation clientRep) -> { - }); - } finally { - deleteClientByAdmin(cid); - } - } - - @Test - public void testSecureSigningAlgorithmForSignedJwtEnforceExecutor() throws Exception { - // policy including client role condition - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition("ClientRolesCondition", ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role-alpha", "sample-client-role-zeta"))); - }); - registerCondition("ClientRolesCondition", policyName); - logger.info("... Registered Condition : " + "ClientRolesCondition"); - - createExecutor("SecureSigningAlgorithmForSignedJwtEnforceExecutor", SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, null, - (ComponentRepresentation provider) -> { - }); - - registerExecutor("SecureSigningAlgorithmForSignedJwtEnforceExecutor", policyName); - logger.info("... Registered Executor : SecureSigningAlgorithmForSignedJwtEnforceExecutor"); - - // crate a client with client role - String clientAlphaId = "Alpha-App"; - String clientAlphaSecret = "secretAlpha"; - String cAlphaId = null; - - cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { - clientRep.setDefaultRoles(Arrays.asList("sample-client-role-alpha", "sample-client-role-common").toArray(new String[2])); - clientRep.setSecret(clientAlphaSecret); - clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID); - clientRep.setAttributes(new HashMap<>()); - clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, org.keycloak.crypto.Algorithm.ES256); - }); - - try { - ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientAlphaId); - ClientRepresentation clientRep = clientResource.toRepresentation(); - - KeyPair keyPair = setupJwks(org.keycloak.crypto.Algorithm.ES256, clientRep, clientResource); - PublicKey publicKey = keyPair.getPublic(); - PrivateKey privateKey = keyPair.getPrivate(); - - successfulLoginAndLogoutWithSignedJWT(clientAlphaId, privateKey, publicKey); - } finally { - deleteClientByAdmin(cAlphaId); - } - } - - @Test - public void testAnyClientCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - createCondition("AnyClientCondition", AnyClientConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerCondition("AnyClientCondition", policyName); - logger.info("... Registered Condition : " + "AnyClientCondition"); - - createExecutor("SecureSessionEnforceExecutor", SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerExecutor("SecureSessionEnforceExecutor", policyName); - logger.info("... Registered Executor : SecureSessionEnforceExecutor-beta"); - - String clientAlphaId = "Alpha-App"; - String clientAlphaSecret = "secretAlpha"; - String cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> { - clientRep.setDefaultRoles((String[]) Arrays.asList("sample-client-role-alpha").toArray(new String[1])); - clientRep.setSecret(clientAlphaSecret); - }); - - String clientBetaId = "Beta-App"; - String clientBetaSecret = "secretBeta"; - String cBetaId = createClientByAdmin(clientBetaId, (ClientRepresentation clientRep) -> { - clientRep.setSecret(clientBetaSecret); - }); - - try { - failLoginWithoutSecureSessionParameter(clientBetaId, "Missing parameter: nonce"); - oauth.nonce("yesitisnonce"); - successfulLoginAndLogout(clientAlphaId, clientAlphaSecret); - } catch (Exception e) { - fail(); - } finally { - deleteClientByAdmin(cAlphaId); - deleteClientByAdmin(cBetaId); - } - } - - @Test - public void testHolderOfKeyEnforceExecutor() throws Exception { - String policyName = "MyPolicy"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition("ClientRolesCondition", ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, Collections.singletonList("sample-client-role")); - }); - registerCondition("ClientRolesCondition", policyName); - logger.info("... Registered Condition : ClientRolesCondition"); - - createExecutor("HolderOfKeyEnforceExecutor", HolderOfKeyEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - }); - registerExecutor("HolderOfKeyEnforceExecutor", policyName); - logger.info("... Registered Executor : HolderOfKeyEnforceExecutor"); - - String clientName = "Zahlungs-App"; - String userPassword = "password"; - String clientId = createClientDynamically(clientName, (OIDCClientRepresentation clientRep) -> { - clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.TLS_CLIENT_AUTH); - }); - - try { - checkMtlsFlow(userPassword); - } finally { - deleteClientByAdmin(clientId); - } - } - - private void checkMtlsFlow(String password) throws IOException { - ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), "test-app"); - ClientRepresentation clientRep = clientResource.toRepresentation(); - clientRep.setDefaultRoles(new String[]{"sample-client-role"}); - OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseMtlsHoKToken(true); - - clientResource.update(clientRep); - - // Check login. - OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin("test-user@localhost", password); - Assert.assertNull(loginResponse.getError()); - - String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); - - // Check token obtaining. - OAuthClient.AccessTokenResponse accessTokenResponse; - try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { - accessTokenResponse = oauth.doAccessTokenRequest(code, password, client); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - assertEquals(200, accessTokenResponse.getStatusCode()); - - // Check token refresh. - OAuthClient.AccessTokenResponse accessTokenResponseRefreshed; - try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { - accessTokenResponseRefreshed = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), password, client); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - assertEquals(200, accessTokenResponseRefreshed.getStatusCode()); - - // Check token introspection. - String tokenResponse; - try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { - tokenResponse = oauth.introspectTokenWithClientCredential(TEST_CLIENT, password, "access_token", accessTokenResponse.getAccessToken(), client); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - Assert.assertNotNull(tokenResponse); - TokenMetadataRepresentation tokenMetadataRepresentation = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class); - Assert.assertTrue(tokenMetadataRepresentation.isActive()); - - // Check token revoke. - CloseableHttpResponse tokenRevokeResponse; - try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { - tokenRevokeResponse = oauth.doTokenRevoke(accessTokenResponse.getRefreshToken(), "refresh_token", password, client); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - assertEquals(200, tokenRevokeResponse.getStatusLine().getStatusCode()); - - // Check logout. - CloseableHttpResponse logoutResponse; - try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { - logoutResponse = oauth.doLogout(accessTokenResponse.getRefreshToken(), password, client); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - - assertEquals(204, logoutResponse.getStatusLine().getStatusCode()); - - // Check login. - loginResponse = oauth.doLogin("test-user@localhost", password); - Assert.assertNull(loginResponse.getError()); - - code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); - - // Check token obtaining without certificate - try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) { - accessTokenResponse = oauth.doAccessTokenRequest(code, password, client); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - assertEquals(400, accessTokenResponse.getStatusCode()); - assertEquals(OAuthErrorException.INVALID_GRANT, accessTokenResponse.getError()); - - // Check frontchannel logout and login. - oauth.openLogout(); - loginResponse = oauth.doLogin("test-user@localhost", password); - Assert.assertNull(loginResponse.getError()); - - code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); - - // Check token obtaining. - try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { - accessTokenResponse = oauth.doAccessTokenRequest(code, password, client); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - assertEquals(200, accessTokenResponse.getStatusCode()); - - // Check token refresh with other certificate - try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) { - accessTokenResponseRefreshed = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), password, client); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - assertEquals(401, accessTokenResponseRefreshed.getStatusCode()); - assertEquals(Errors.NOT_ALLOWED, accessTokenResponseRefreshed.getError()); - - // Check token revoke with other certificate - try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) { - tokenRevokeResponse = oauth.doTokenRevoke(accessTokenResponse.getRefreshToken(), "refresh_token", password, client); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - assertEquals(401, tokenRevokeResponse.getStatusLine().getStatusCode()); - - // Check logout without certificate - try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) { - logoutResponse = oauth.doLogout(accessTokenResponse.getRefreshToken(), password, client); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - assertEquals(401, logoutResponse.getStatusLine().getStatusCode()); - - // Check logout. - try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) { - logoutResponse = oauth.doLogout(accessTokenResponse.getRefreshToken(), password, client); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - } - - private CloseableHttpResponse sendRequest(String requestUrl, List parameters) throws Exception { - CloseableHttpClient client = new DefaultHttpClient(); - try { - HttpPost post = new HttpPost(requestUrl); - UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, "UTF-8"); - post.setEntity(formEntity); - return client.execute(post); - } finally { - oauth.closeClient(client); - } - } - - private void successfulLoginAndLogoutWithSignedJWT(String clientId, PrivateKey privateKey, PublicKey publicKey) throws Exception { - String signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); - - oauth.clientId(clientId); - oauth.doLogin("test-user@localhost", "password"); - EventRepresentation loginEvent = events.expectLogin() - .client(clientId) - .assertEvent(); - String sessionId = loginEvent.getSessionId(); - String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); - - //obtain access token - OAuthClient.AccessTokenResponse response = doAccessTokenRequestWithSignedJWT(code, signedJwt); - - assertEquals(200, response.getStatusCode()); - oauth.verifyToken(response.getAccessToken()); - RefreshToken refreshToken = oauth.parseRefreshToken(response.getRefreshToken()); - assertEquals(sessionId, refreshToken.getSessionState()); - assertEquals(sessionId, refreshToken.getSessionState()); - events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), loginEvent.getSessionId()) - .client(clientId) - .detail(Details.CLIENT_AUTH_METHOD, JWTClientAuthenticator.PROVIDER_ID) - .assertEvent(); - - //refresh token - signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); - OAuthClient.AccessTokenResponse refreshedResponse = doRefreshTokenRequestWithSignedJWT(response.getRefreshToken(), signedJwt); - assertEquals(200, refreshedResponse.getStatusCode()); - - //introspect token - signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); - HttpResponse tokenIntrospectionResponse = doTokenIntrospectionWithSignedJWT("access_token", refreshedResponse.getAccessToken(), signedJwt); - assertEquals(200, tokenIntrospectionResponse.getStatusLine().getStatusCode()); - - //revoke token - signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); - HttpResponse revokeTokenResponse = doTokenRevokeWithSignedJWT("refresh_toke", refreshedResponse.getRefreshToken(), signedJwt); - assertEquals(200, revokeTokenResponse.getStatusLine().getStatusCode()); - - signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); - OAuthClient.AccessTokenResponse tokenRes = doRefreshTokenRequestWithSignedJWT(refreshedResponse.getRefreshToken(), signedJwt); - assertEquals(400, tokenRes.getStatusCode()); - assertEquals(OAuthErrorException.INVALID_GRANT, tokenRes.getError()); - - //logout - signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256); - HttpResponse logoutResponse = doLogoutWithSignedJWT(refreshedResponse.getRefreshToken(), signedJwt); - assertEquals(204, logoutResponse.getStatusLine().getStatusCode()); - - } - - private KeyPair setupJwks(String algorithm, ClientRepresentation clientRepresentation, ClientResource clientResource) throws Exception { - // generate and register client keypair - TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); - oidcClientEndpointsResource.generateKeys(algorithm); - Map generatedKeys = oidcClientEndpointsResource.getKeysAsBase64(); - KeyPair keyPair = getKeyPairFromGeneratedBase64(generatedKeys, algorithm); - - // use and set jwks_url - OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setUseJwksUrl(true); - String jwksUrl = TestApplicationResourceUrls.clientJwksUri(); - OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setJwksUrl(jwksUrl); - clientResource.update(clientRepresentation); - - // set time offset, so that new keys are downloaded - setTimeOffset(20); - - return keyPair; - } - - private KeyPair getKeyPairFromGeneratedBase64(Map generatedKeys, String algorithm) throws Exception { - // It seems that PemUtils.decodePrivateKey, decodePublicKey can only treat RSA type keys, not EC type keys. Therefore, these are not used. - String privateKeyBase64 = generatedKeys.get(TestingOIDCEndpointsApplicationResource.PRIVATE_KEY); - String publicKeyBase64 = generatedKeys.get(TestingOIDCEndpointsApplicationResource.PUBLIC_KEY); - PrivateKey privateKey = decodePrivateKey(Base64.decode(privateKeyBase64), algorithm); - PublicKey publicKey = decodePublicKey(Base64.decode(publicKeyBase64), algorithm); - return new KeyPair(publicKey, privateKey); - } - - private static PrivateKey decodePrivateKey(byte[] der, String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { - PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der); - String keyAlg = getKeyAlgorithmFromJwaAlgorithm(algorithm); - KeyFactory kf = KeyFactory.getInstance(keyAlg, "BC"); - return kf.generatePrivate(spec); - } - - private static PublicKey decodePublicKey(byte[] der, String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { - X509EncodedKeySpec spec = new X509EncodedKeySpec(der); - String keyAlg = getKeyAlgorithmFromJwaAlgorithm(algorithm); - KeyFactory kf = KeyFactory.getInstance(keyAlg, "BC"); - return kf.generatePublic(spec); - } - - private String createSignedRequestToken(String clientId, String realmInfoUrl, PrivateKey privateKey, PublicKey publicKey, String algorithm) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { - JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl); - String kid = KeyUtils.createKeyId(publicKey); - SignatureSignerContext signer = oauth.createSigner(privateKey, kid, algorithm); - return new JWSBuilder().kid(kid).jsonContent(jwt).sign(signer); - } - - private OAuthClient.AccessTokenResponse doAccessTokenRequestWithSignedJWT(String code, String signedJwt) throws Exception { - List parameters = new LinkedList<>(); - parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE)); - parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, code)); - parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, oauth.getRedirectUri())); - parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)); - parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt)); - - CloseableHttpResponse response = sendRequest(oauth.getAccessTokenUrl(), parameters); - return new OAuthClient.AccessTokenResponse(response); - } - - private OAuthClient.AccessTokenResponse doRefreshTokenRequestWithSignedJWT(String refreshToken, String signedJwt) throws Exception { - List parameters = new LinkedList<>(); - parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN)); - parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken)); - parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)); - parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt)); - - CloseableHttpResponse response = sendRequest(oauth.getRefreshTokenUrl(), parameters); - return new OAuthClient.AccessTokenResponse(response); - } - - private HttpResponse doTokenIntrospectionWithSignedJWT(String tokenType, String tokenToIntrospect, String signedJwt) throws Exception { - List parameters = new LinkedList<>(); - parameters.add(new BasicNameValuePair("token", tokenToIntrospect)); - parameters.add(new BasicNameValuePair("token_type_hint", tokenType)); - parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)); - parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt)); - - return sendRequest(oauth.getTokenIntrospectionUrl(), parameters); - } - - private HttpResponse doTokenRevokeWithSignedJWT(String tokenType, String tokenToIntrospect, String signedJwt) throws Exception { - List parameters = new LinkedList<>(); - parameters.add(new BasicNameValuePair("token", tokenToIntrospect)); - parameters.add(new BasicNameValuePair("token_type_hint", tokenType)); - parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)); - parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt)); - - return sendRequest(oauth.getTokenRevocationUrl(), parameters); - } - - private HttpResponse doLogoutWithSignedJWT(String refreshToken, String signedJwt) throws Exception { - List parameters = new LinkedList<>(); - parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN)); - parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken)); - parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)); - parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt)); - - return sendRequest(oauth.getLogoutUrl().build(), parameters); - } - - private JsonWebToken createRequestToken(String clientId, String realmInfoUrl) { - JsonWebToken reqToken = new JsonWebToken(); - reqToken.id(AdapterUtils.generateId()); - reqToken.issuer(clientId); - reqToken.subject(clientId); - reqToken.audience(realmInfoUrl); - - int now = Time.currentTime(); - reqToken.issuedAt(now); - reqToken.expiration(now + 10); - reqToken.notBefore(now); - - return reqToken; - } - - private static String getKeyAlgorithmFromJwaAlgorithm(String jwaAlgorithm) { - String keyAlg = null; - switch (jwaAlgorithm) { - case org.keycloak.crypto.Algorithm.RS256: - case org.keycloak.crypto.Algorithm.RS384: - case org.keycloak.crypto.Algorithm.RS512: - case org.keycloak.crypto.Algorithm.PS256: - case org.keycloak.crypto.Algorithm.PS384: - case org.keycloak.crypto.Algorithm.PS512: - keyAlg = KeyType.RSA; - break; - case org.keycloak.crypto.Algorithm.ES256: - case org.keycloak.crypto.Algorithm.ES384: - case org.keycloak.crypto.Algorithm.ES512: - keyAlg = KeyType.EC; - break; - default : - throw new RuntimeException("Unsupported signature algorithm"); - } - return keyAlg; - } - - private String getRealmInfoUrl() { - String authServerBaseUrl = UriUtils.getOrigin(oauth.getRedirectUri()) + "/auth"; - return KeycloakUriBuilder.fromUri(authServerBaseUrl).path(ServiceUrlConstants.REALM_INFO_PATH).build(REALM_NAME).toString(); - } - - private AuthorizationEndpointRequestObject createValidRequestObjectForSecureRequestObjectExecutor(String clientId) throws URISyntaxException { - AuthorizationEndpointRequestObject requestObject = new AuthorizationEndpointRequestObject(); - requestObject.id(KeycloakModelUtils.generateId()); - requestObject.iat(Long.valueOf(Time.currentTime())); - requestObject.exp(requestObject.getIat() + Long.valueOf(300)); - requestObject.nbf(Long.valueOf(0)); - requestObject.setClientId(clientId); - requestObject.setResponseType("code"); - requestObject.setRedirectUriParam(oauth.getRedirectUri()); - requestObject.setScope("openid"); - String scope = KeycloakModelUtils.generateId(); - oauth.stateParamHardcoded(scope); - requestObject.setState(scope); - requestObject.setMax_age(Integer.valueOf(600)); - requestObject.setOtherClaims("custom_claim_ein", "rot"); - requestObject.audience(Urls.realmIssuer(new URI(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth"), REALM_NAME), "https://example.com"); - return requestObject; - } - - private void registerRequestObject(AuthorizationEndpointRequestObject requestObject, String clientId, Algorithm sigAlg, boolean isUseRequestUri) throws URISyntaxException, IOException { - TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); - - // Set required signature for request_uri - // use and set jwks_url - ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId); - ClientRepresentation clientRep = clientResource.toRepresentation(); - OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(sigAlg); - OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true); - String jwksUrl = TestApplicationResourceUrls.clientJwksUri(); - OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(jwksUrl); - clientResource.update(clientRep); - - oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); - - // generate and register client keypair - oidcClientEndpointsResource.generateKeys(sigAlg.name()); - - // register request object - byte[] contentBytes = JsonSerialization.writeValueAsBytes(requestObject); - String encodedRequestObject = Base64Url.encode(contentBytes); - oidcClientEndpointsResource.registerOIDCRequest(encodedRequestObject, sigAlg.name()); - - if (isUseRequestUri) { - oauth.request(null); - oauth.requestUri(TestApplicationResourceUrls.clientRequestUri()); - } else { - oauth.requestUri(null); - oauth.request(oidcClientEndpointsResource.getOIDCRequest()); - } - } - - private void setupPolicyAcceptableAuthType(String policyName) { - - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition(CLIENTUPDATECONTEXT_CONDITION_NAME, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))); - }); - registerCondition(CLIENTUPDATECONTEXT_CONDITION_NAME, policyName); - logger.info("... Registered Condition : " + CLIENTUPDATECONTEXT_CONDITION_NAME); - - createExecutor("SecureClientAuthEnforceExecutor", SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList( - JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID))); - }); - registerExecutor("SecureClientAuthEnforceExecutor", policyName); - logger.info("... Registered Executor : SecureClientAuthEnforceExecutor"); - - } - - private void setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(String policyName) { - - logger.info("Setup Policy"); - - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition(CLIENTUPDATECONTEXT_CONDITION_NAME, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN))); - }); - registerCondition(CLIENTUPDATECONTEXT_CONDITION_NAME, policyName); - logger.info("... Registered Condition : " + CLIENTUPDATECONTEXT_CONDITION_NAME); - - createCondition(CLIENTROLES_CONDITION_NAME, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role"))); - }); - registerCondition(CLIENTROLES_CONDITION_NAME, policyName); - logger.info("... Registered Condition : " + CLIENTROLES_CONDITION_NAME); - - createExecutor("SecureClientAuthEnforceExecutor", SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID, JWTClientAuthenticator.PROVIDER_ID))); - setExecutorAugmentedClientAuthMethod(provider, ClientIdAndSecretAuthenticator.PROVIDER_ID); - setExecutorAugmentActivate(provider); - }); - registerExecutor("SecureClientAuthEnforceExecutor", policyName); - logger.info("... Registered Executor : SecureClientAuthEnforceExecutor"); - - createExecutor("PKCEEnforceExecutor", PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - }); - registerExecutor("PKCEEnforceExecutor", policyName); - logger.info("... Registered Executor : PKCEEnforceExecutor"); - - } - - private void successfulLoginAndLogout(String clientId, String clientSecret) { - oauth.clientId(clientId); - oauth.doLogin("test-user@localhost", "password"); - - EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent(); - String sessionId = loginEvent.getSessionId(); - String codeId = loginEvent.getDetails().get(Details.CODE_ID); - String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); - OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, clientSecret); - assertEquals(200, res.getStatusCode()); - events.expectCodeToToken(codeId, sessionId).client(clientId).assertEvent(); - - oauth.doLogout(res.getRefreshToken(), clientSecret); - events.expectLogout(sessionId).client(clientId).clearDetails().assertEvent(); - } - - private void successfulLoginAndLogoutWithPKCE(String clientId, String clientSecret, String userName, String userPassword) throws Exception { - oauth.clientId(clientId); - String codeVerifier = "1a345A7890123456r8901c3456789012b45K7890l23"; // 43 - String codeChallenge = generateS256CodeChallenge(codeVerifier); - oauth.codeChallenge(codeChallenge); - oauth.codeChallengeMethod(OAuth2Constants.PKCE_METHOD_S256); - oauth.nonce("bjapewiziIE083d"); - - oauth.doLogin(userName, userPassword); - - EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent(); - String sessionId = loginEvent.getSessionId(); - String codeId = loginEvent.getDetails().get(Details.CODE_ID); - String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); - - oauth.codeVerifier(codeVerifier); - - OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, clientSecret); - - assertEquals(200, res.getStatusCode()); - events.expectCodeToToken(codeId, sessionId).client(clientId).assertEvent(); - - AccessToken token = oauth.verifyToken(res.getAccessToken()); - - String userId = findUserByUsername(adminClient.realm(REALM_NAME), userName).getId(); - assertEquals(userId, token.getSubject()); - Assert.assertNotEquals(userName, token.getSubject()); - assertEquals(sessionId, token.getSessionState()); - assertEquals(clientId, token.getIssuedFor()); - - String refreshTokenString = res.getRefreshToken(); - RefreshToken refreshToken = oauth.parseRefreshToken(refreshTokenString); - assertEquals(sessionId, refreshToken.getSessionState()); - assertEquals(clientId, refreshToken.getIssuedFor()); - - OAuthClient.AccessTokenResponse refreshResponse = oauth.doRefreshTokenRequest(refreshTokenString, clientSecret); - assertEquals(200, refreshResponse.getStatusCode()); - - AccessToken refreshedToken = oauth.verifyToken(refreshResponse.getAccessToken()); - RefreshToken refreshedRefreshToken = oauth.parseRefreshToken(refreshResponse.getRefreshToken()); - assertEquals(sessionId, refreshedToken.getSessionState()); - assertEquals(sessionId, refreshedRefreshToken.getSessionState()); - - assertEquals(findUserByUsername(adminClient.realm(REALM_NAME), userName).getId(), refreshedToken.getSubject()); - - events.expectRefresh(refreshToken.getId(), sessionId).client(clientId).assertEvent(); - - doIntrospectAccessToken(refreshResponse, userName, clientId, clientSecret); - - doTokenRevoke(refreshResponse.getRefreshToken(), clientId, clientSecret, userId, false); - } - - private void failLoginByNotFollowingPKCE(String clientId) { - oauth.clientId(clientId); - oauth.openLoginForm(); - assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - assertEquals("Missing parameter: code_challenge_method", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); - } - - private void failTokenRequestByNotFollowingPKCE(String clientId, String clientSecret) { - oauth.clientId(clientId); - oauth.doLogin("test-user@localhost", "password"); - - EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent(); - String sessionId = loginEvent.getSessionId(); - String codeId = loginEvent.getDetails().get(Details.CODE_ID); - String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); - OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, clientSecret); - - assertEquals(OAuthErrorException.INVALID_GRANT, res.getError()); - assertEquals("PKCE code verifier not specified", res.getErrorDescription()); - events.expect(EventType.CODE_TO_TOKEN_ERROR).client(clientId).session(sessionId).clearDetails().error(Errors.CODE_VERIFIER_MISSING).assertEvent(); - - oauth.openLogout(); - - events.expectLogout(sessionId).clearDetails().assertEvent(); - } - - private void failLoginWithoutSecureSessionParameter(String clientId, String errorDescription) { - oauth.clientId(clientId); - oauth.openLoginForm(); - assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - assertEquals(errorDescription, oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); - } - - private void failLoginWithoutNonce(String clientId) { - oauth.clientId(clientId); - oauth.openLoginForm(); - assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - assertEquals("Missing parameter: nonce", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); - } - - private String generateS256CodeChallenge(String codeVerifier) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(codeVerifier.getBytes("ISO_8859_1")); - byte[] digestBytes = md.digest(); - String codeChallenge = Base64Url.encode(digestBytes); - return codeChallenge; - } - - private void doIntrospectAccessToken(OAuthClient.AccessTokenResponse tokenRes, String username, String clientId, String clientSecret) throws IOException { - String tokenResponse = oauth.introspectAccessTokenWithClientCredential(clientId, clientSecret, tokenRes.getAccessToken()); - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode jsonNode = objectMapper.readTree(tokenResponse); - assertEquals(true, jsonNode.get("active").asBoolean()); - assertEquals(username, jsonNode.get("username").asText()); - assertEquals(clientId, jsonNode.get("client_id").asText()); - TokenMetadataRepresentation rep = objectMapper.readValue(tokenResponse, TokenMetadataRepresentation.class); - assertEquals(true, rep.isActive()); - assertEquals(clientId, rep.getClientId()); - assertEquals(clientId, rep.getIssuedFor()); - events.expect(EventType.INTROSPECT_TOKEN).client(clientId).user((String)null).clearDetails().assertEvent(); - } - - private void doTokenRevoke(String refreshToken, String clientId, String clientSecret, String userId, boolean isOfflineAccess) throws IOException { - oauth.clientId(clientId); - oauth.doTokenRevoke(refreshToken, "refresh_token", clientSecret); - - // confirm revocation - OAuthClient.AccessTokenResponse tokenRes = oauth.doRefreshTokenRequest(refreshToken, clientSecret); - assertEquals(400, tokenRes.getStatusCode()); - assertEquals(OAuthErrorException.INVALID_GRANT, tokenRes.getError()); - if (isOfflineAccess) assertEquals("Offline user session not found", tokenRes.getErrorDescription()); - else assertEquals("Session not active", tokenRes.getErrorDescription()); - - events.expect(EventType.REVOKE_GRANT).clearDetails().client(clientId).user(userId).assertEvent(); - } - - private void authCreateClients() { - reg.auth(Auth.token(getToken("create-clients", "password"))); - } - - private void authManageClients() { - reg.auth(Auth.token(getToken("manage-clients", "password"))); - } - - private void authNoAccess() { - reg.auth(Auth.token(getToken("no-access", "password"))); - } - - private String getToken(String username, String password) { - try { - return oauth.doGrantAccessTokenRequest(REALM_NAME, username, password, null, Constants.ADMIN_CLI_CLIENT_ID, null).getAccessToken(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private ComponentRepresentation createComponentInstance(String name, String providerId, String providerType, String subType) { - ComponentRepresentation rep = new ComponentRepresentation(); - rep.setId(org.keycloak.models.utils.KeycloakModelUtils.generateId()); - rep.setName(name); - rep.setParentId(REALM_NAME); - rep.setProviderId(providerId); - rep.setProviderType(providerType); - rep.setSubType(subType); - rep.setConfig(new MultivaluedHashMap<>()); - return rep; - } - - private String createComponent(ComponentRepresentation cr) { - Response resp = adminClient.realm(REALM_NAME).components().add(cr); - String id = ApiUtil.getCreatedId(resp); - resp.close(); - // registered components will be removed automatically - testContext.getOrCreateCleanup(REALM_NAME).addComponentId(id); - return id; - } - - private ComponentRepresentation getComponent(String name, String providerType) { - return adminClient.realm(REALM_NAME).components().query(null, providerType, name).get(0); - } - - private void updateComponent(ComponentRepresentation cr) { - adminClient.realm(REALM_NAME).components().component(cr.getId()).update(cr); - } - - private void deleteComponent(String id) { - adminClient.realm(REALM_NAME).components().component(id).remove(); - } - - private String createCondition(String name, String providerId, String subType, Consumer op) { - ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyConditionProvider.class.getName(), subType); - op.accept(component); - return createComponent(component); - } - - private void registerCondition(String conditionName, String policyName) { - ComponentRepresentation policy = getPolicy(policyName); - List conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS); - if (conditionIds == null) conditionIds = new ArrayList(); - ComponentRepresentation condition = getCondition(conditionName); - conditionIds.add(condition.getId()); - policy.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditionIds); - updatePolicy(policy); - } - - private ComponentRepresentation getCondition(String name) { - return getComponent(name, ClientPolicyConditionProvider.class.getName()); - } - - private void updateCondition(String name, Consumer op) { - ComponentRepresentation condition = getCondition(name); - op.accept(condition); - updateComponent(condition); - } - - private void deleteCondition(String conditionName, String policyName) { - ComponentRepresentation policy = getPolicy(policyName); - List conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS); - ComponentRepresentation condition = getCondition(conditionName); - String conditionId = condition.getId(); - adminClient.realm(REALM_NAME).components().component(conditionId).remove(); - conditionIds.remove(conditionId); - policy.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditionIds); - updatePolicy(policy); - } - - private String createExecutor(String name, String providerId, String subType, Consumer op) { - ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyExecutorProvider.class.getName(), subType); - op.accept(component); - return createComponent(component); - } - - private void registerExecutor(String executorName, String policyName) { - ComponentRepresentation policy = getPolicy(policyName); - List executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS); - if (executorIds == null) executorIds = new ArrayList(); - ComponentRepresentation executor = getExecutor(executorName); - executorIds.add(executor.getId()); - policy.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executorIds); - updatePolicy(policy); - } - - private ComponentRepresentation getExecutor(String name) { - return getComponent(name, ClientPolicyExecutorProvider.class.getName()); - } - - private void updateExecutor(String name, Consumer op) { - ComponentRepresentation executor = getExecutor(name); - op.accept(executor); - updateComponent(executor); - } - - private void deleteExecutor(String executorName, String policyName) { - ComponentRepresentation policy = getPolicy(policyName); - List executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS); - ComponentRepresentation executor = getExecutor(executorName); - String executorId = executor.getId(); - adminClient.realm(REALM_NAME).components().component(executorId).remove(); - executorIds.remove(executorId); - policy.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executorIds); - updatePolicy(policy); - } - - private String createPolicy(String name, String providerId, String subType, List conditions, List executors) { - ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyProvider.class.getName(), subType); - component.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditions); - component.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executors); - return createComponent(component); - } - - private ComponentRepresentation getPolicy(String name) { - return getComponent(name, ClientPolicyProvider.class.getName()); - } - - private void updatePolicy(ComponentRepresentation policy) { - updateComponent(policy); - } - - private void deletePolicy(String policyName) { - ComponentRepresentation policy = getPolicy(policyName); - List conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS); - List executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS); - conditionIds.stream().forEach(i->adminClient.realm(REALM_NAME).components().component(i).remove()); - executorIds.stream().forEach(i->adminClient.realm(REALM_NAME).components().component(i).remove()); - adminClient.realm(REALM_NAME).components().component(policy.getId()).remove(); - } - - private String createClientByAdmin(String clientName, Consumer op) throws ClientPolicyException { - ClientRepresentation clientRep = new ClientRepresentation(); - clientRep.setClientId(clientName); - clientRep.setName(clientName); - clientRep.setProtocol("openid-connect"); - clientRep.setBearerOnly(Boolean.FALSE); - clientRep.setPublicClient(Boolean.FALSE); - clientRep.setServiceAccountsEnabled(Boolean.TRUE); - clientRep.setRedirectUris(Collections.singletonList(ServerURLs.getAuthServerContextRoot() + "/auth/realms/master/app/auth")); - op.accept(clientRep); - Response resp = adminClient.realm(REALM_NAME).clients().create(clientRep); - if (resp.getStatus() == Response.Status.BAD_REQUEST.getStatusCode()) { - throw new ClientPolicyException(Errors.INVALID_REGISTRATION, "registration error by admin"); - } - resp.close(); - assertEquals(Response.Status.CREATED.getStatusCode(), resp.getStatus()); - return ApiUtil.getCreatedId(resp); - } - - private ClientRepresentation getClientByAdmin(String clientId) { - ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientId); - return clientResource.toRepresentation(); - } - - private void updateClientByAdmin(String clientId, Consumer op) { - ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientId); - ClientRepresentation clientRep = clientResource.toRepresentation(); - op.accept(clientRep); - clientResource.update(clientRep); - } - - private void deleteClientByAdmin(String clientId) { - ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientId); - clientResource.remove(); - } - - private String createClientDynamically(String clientName, Consumer op) throws ClientRegistrationException { - OIDCClientRepresentation clientRep = new OIDCClientRepresentation(); - clientRep.setClientName(clientName); - clientRep.setClientUri(ServerURLs.getAuthServerContextRoot()); - clientRep.setRedirectUris(Collections.singletonList(ServerURLs.getAuthServerContextRoot() + "/auth/realms/master/app/auth")); - op.accept(clientRep); - OIDCClientRepresentation response = reg.oidc().create(clientRep); - reg.auth(Auth.token(response)); - return response.getClientId(); - } - - private OIDCClientRepresentation getClientDynamically(String clientId) throws ClientRegistrationException { - return reg.oidc().get(clientId); - } - - private void updateClientDynamically(String clientId, Consumer op) throws ClientRegistrationException { - OIDCClientRepresentation clientRep = reg.oidc().get(clientId); - op.accept(clientRep); - OIDCClientRepresentation response = reg.oidc().update(clientRep); - reg.auth(Auth.token(response)); - } - - private void deleteClientDynamically(String clientId) throws ClientRegistrationException { - reg.oidc().delete(clientId); - } - - private void setConditionRegistrationMethods(ComponentRepresentation provider, List registrationMethods) { - provider.getConfig().put(ClientUpdateContextConditionFactory.UPDATE_CLIENT_SOURCE, registrationMethods); - } - - private void setConditionClientRoles(ComponentRepresentation provider, List clientRoles) { - provider.getConfig().put(ClientRolesConditionFactory.ROLES, clientRoles); - } - - private void setConditionClientScopes(ComponentRepresentation provider, List clientScopes) { - provider.getConfig().put(ClientScopesConditionFactory.SCOPES, clientScopes); - } - - private void setConditionClientAccessType(ComponentRepresentation provider, List clientAccessTypes) { - provider.getConfig().put(ClientAccessTypeConditionFactory.TYPE, clientAccessTypes); - } - - private void setConditionClientUpdateSourceHosts(ComponentRepresentation provider, List hosts) { - provider.getConfig().putSingle(ClientUpdateSourceHostsConditionFactory.HOST_SENDING_REQUEST_MUST_MATCH, "true"); - provider.getConfig().put(ClientUpdateSourceHostsConditionFactory.TRUSTED_HOSTS, hosts); - } - - private void setConditionClientUpdateSourceGroups(ComponentRepresentation provider, List groups) { - provider.getConfig().put(ClientUpdateSourceGroupsConditionFactory.GROUPS, groups); - } - - private void setConditionUpdatingClientSourceRoles(ComponentRepresentation provider, List groups) { - provider.getConfig().put(ClientUpdateSourceRolesConditionFactory.ROLES, groups); - } - - private void setExecutorAugmentActivate(ComponentRepresentation provider) { - provider.getConfig().putSingle("is-augment", Boolean.TRUE.toString()); - } - - private void setExecutorAugmentDeactivate(ComponentRepresentation provider) { - provider.getConfig().putSingle("is-augment", Boolean.FALSE.toString()); - } - - private void setExecutorAcceptedClientAuthMethods(ComponentRepresentation provider, List acceptedClientAuthMethods) { - provider.getConfig().put(SecureClientAuthEnforceExecutorFactory.CLIENT_AUTHNS, acceptedClientAuthMethods); - } - - private void setExecutorAugmentedClientAuthMethod(ComponentRepresentation provider, String augmentedClientAuthMethod) { - provider.getConfig().putSingle(SecureClientAuthEnforceExecutorFactory.CLIENT_AUTHNS_AUGMENT, augmentedClientAuthMethod); - } -} \ No newline at end of file