diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/CIBATest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/CIBATest.java
index b27b64cf7e..8e6cea0997 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/CIBATest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/CIBATest.java
@@ -124,6 +124,7 @@ import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder;
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder;
import org.keycloak.testsuite.util.OAuthClient.AuthenticationRequestAcknowledgement;
import org.keycloak.util.JsonSerialization;
+import org.keycloak.testsuite.client.policies.AbstractClientPoliciesTest;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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
deleted file mode 100644
index 2300ce545b..0000000000
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java
+++ /dev/null
@@ -1,3756 +0,0 @@
-/*
- * 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 java.io.IOException;
-import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-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.Optional;
-
-import javax.ws.rs.BadRequestException;
-import javax.ws.rs.core.Response;
-
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.TreeNode;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.TextNode;
-
-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.arquillian.graphene.page.Page;
-import org.jboss.logging.Logger;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Test;
-import org.keycloak.OAuth2Constants;
-import org.keycloak.OAuthErrorException;
-import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.admin.client.resource.ProtocolMappersResource;
-import org.keycloak.admin.client.resource.RolesResource;
-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.common.util.Base64Url;
-import org.keycloak.common.util.Time;
-import org.keycloak.crypto.Algorithm;
-import org.keycloak.events.Details;
-import org.keycloak.events.Errors;
-import org.keycloak.events.EventType;
-import org.keycloak.jose.jws.JWSBuilder;
-import org.keycloak.jose.jws.JWSInput;
-import org.keycloak.models.AdminRoles;
-import org.keycloak.models.CibaConfig;
-import org.keycloak.models.Constants;
-import org.keycloak.models.OAuth2DeviceConfig;
-import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.models.utils.ModelToRepresentation;
-import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
-import org.keycloak.protocol.oidc.OIDCConfigAttributes;
-import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.protocol.oidc.mappers.ClaimsParameterWithValueIdTokenMapper;
-import org.keycloak.protocol.oidc.utils.OIDCResponseType;
-import org.keycloak.representations.AccessToken;
-import org.keycloak.representations.AuthorizationResponseToken;
-import org.keycloak.representations.ClaimsRepresentation;
-import org.keycloak.representations.IDToken;
-import org.keycloak.representations.RefreshToken;
-import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation;
-import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.representations.idm.EventRepresentation;
-import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
-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.ClientPolicyEvent;
-import org.keycloak.services.clientpolicy.ClientPolicyException;
-import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory;
-import org.keycloak.services.clientpolicy.condition.ClientAccessTypeCondition;
-import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory;
-import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory;
-import org.keycloak.services.clientpolicy.condition.ClientScopesConditionFactory;
-import org.keycloak.services.clientpolicy.condition.ClientUpdaterContextConditionFactory;
-import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceGroupsConditionFactory;
-import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceHostsConditionFactory;
-import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceRolesConditionFactory;
-import org.keycloak.services.clientpolicy.executor.ClientSecretRotationExecutor;
-import org.keycloak.services.clientpolicy.executor.ClientSecretRotationExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.ConfidentialClientAcceptExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.ConsentRequiredExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.FullScopeDisabledExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforcerExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.IntentClientBindCheckExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.PKCEEnforcerExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.RegistrationAccessTokenRotationDisabledExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.RejectRequestExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.RejectResourceOwnerPasswordCredentialsGrantExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.SecureClientAuthenticatorExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.SecureClientUrisExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.SecureLogoutExecutorFactory;
-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.SecureSigningAlgorithmExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtExecutorFactory;
-import org.keycloak.services.clientpolicy.executor.SuppressRefreshTokenRotationExecutorFactory;
-import org.keycloak.testsuite.admin.ApiUtil;
-import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
-import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
-import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
-import org.keycloak.testsuite.pages.ErrorPage;
-import org.keycloak.testsuite.pages.LogoutConfirmPage;
-import org.keycloak.testsuite.pages.OAuth2DeviceVerificationPage;
-import org.keycloak.testsuite.pages.OAuthGrantPage;
-import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject;
-import org.keycloak.testsuite.services.clientpolicy.condition.TestRaiseExceptionConditionFactory;
-import org.keycloak.testsuite.services.clientpolicy.executor.TestRaiseExceptionExecutorFactory;
-import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
-import org.keycloak.testsuite.util.ClientBuilder;
-import org.keycloak.testsuite.util.ClientPoliciesUtil;
-import org.keycloak.testsuite.util.MutualTLSUtils;
-import org.keycloak.testsuite.util.OAuthClient;
-import org.keycloak.testsuite.util.RoleBuilder;
-import org.keycloak.testsuite.util.ServerURLs;
-import org.keycloak.testsuite.util.UserBuilder;
-import org.keycloak.util.JsonSerialization;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
-import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId;
-import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPoliciesBuilder;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createAnyClientConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientAccessTypeConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientRolesConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientScopesConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateContextConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceGroupsConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceHostsConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceRolesConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createConsentRequiredExecutorConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createFullScopeDisabledExecutorConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createHolderOfKeyEnforceExecutorConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createIntentClientBindCheckExecutorConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createPKCEEnforceExecutorConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createRejectisResourceOwnerPasswordCredentialsGrantExecutorConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureClientAuthenticatorExecutorConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureRequestObjectExecutorConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureResponseTypeExecutor;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureSigningAlgorithmEnforceExecutorConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createTestRaiseExeptionConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createTestRaiseExeptionExecutorConfig;
-
-/**
- * @author Takashi Norimatsu
- */
-@EnableFeature(value = Profile.Feature.CLIENT_SECRET_ROTATION)
-public class ClientPoliciesTest extends AbstractClientPoliciesTest {
-
- private static final Logger logger = Logger.getLogger(ClientPoliciesTest.class);
-
- private static final String CLIENT_NAME = "Zahlungs-App";
- private static final String TEST_USER_NAME = "test-user@localhost";
- private static final String TEST_USER_PASSWORD = "password";
-
- public static final String DEVICE_APP = "test-device";
- public static final String DEVICE_APP_PUBLIC = "test-device-public";
- private static String userId;
-
- private static final String SECRET_ROTATION_PROFILE = "ClientSecretRotationProfile";
- private static final String SECRET_ROTATION_POLICY = "ClientSecretRotationPolicy";
-
- @Page
- protected OAuth2DeviceVerificationPage verificationPage;
-
- @Page
- protected OAuthGrantPage grantPage;
-
- @Page
- protected ErrorPage errorPage;
-
- @Page
- protected LogoutConfirmPage logoutConfirmPage;
-
- @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);
-
- List clients = realm.getClients();
-
- ClientRepresentation app = ClientBuilder.create()
- .id(KeycloakModelUtils.generateId())
- .clientId("test-device")
- .secret("secret")
- .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
- .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
- .build();
- clients.add(app);
-
- ClientRepresentation appPublic = ClientBuilder.create().id(KeycloakModelUtils.generateId()).publicClient()
- .clientId(DEVICE_APP_PUBLIC)
- .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
- .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
- .build();
- clients.add(appPublic);
-
- userId = KeycloakModelUtils.generateId();
- UserRepresentation deviceUser = UserBuilder.create()
- .id(userId)
- .username("device-login")
- .email("device-login@localhost")
- .password("password")
- .build();
- users.add(deviceUser);
-
- testRealms.add(realm);
- }
-
- @Test
- public void testAdminClientRegisterUnacceptableAuthType() throws Exception {
- setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME);
- try {
- createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
- clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID);
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
- }
- }
-
- @Test
- public void testAdminClientRegisterAcceptableAuthType() throws Exception {
- 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() throws Exception {
- setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME);
- try {
- createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
- }
- }
-
- @Test
- public void testAdminClientUpdateUnacceptableAuthType() throws Exception {
- 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 (ClientPolicyException cpe) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, cpe.getError());
- }
- assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType());
- }
-
- // KEYCLOAK-18108
- @Test
- public void testTwoProfilesWithDifferentConfigurationOfSameExecutorType() throws Exception {
- setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME);
-
- // register another profile with "SecureClientAuthEnforceExecutorFactory", but use different configuration of client authenticator.
- // This profile won't allow JWTClientSecretAuthenticator.PROVIDER_ID
- String profileName = "UnusedProfile";
- String json = (new ClientProfilesBuilder(getProfilesWithoutGlobals())).addProfile(
- (new ClientProfileBuilder()).createProfile(profileName, "Profile with SecureClientAuthEnforceExecutorFactory")
- .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
- createSecureClientAuthenticatorExecutorConfig(
- Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
- null))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // Make sure it is still possible to create client with JWTClientSecretAuthenticator. The "UnusedProfile" should not be used as it is not referenced from any client policy
- String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
- clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
- });
- assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType());
- }
-
- @Test
- public void testAdminClientUpdateAcceptableAuthType() throws Exception {
- 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 Exception {
- 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 testAdminClientAutoConfiguredClientAuthType() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Pershyy Profil")
- .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
- createSecureClientAuthenticatorExecutorConfig(
- Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
- X509ClientAuthenticator.PROVIDER_ID))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Persha Polityka", Boolean.TRUE)
- .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
- createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // Attempt to create client with set authenticator to ClientIdAndSecretAuthenticator. Should fail
- try {
- createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
- clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID);
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
- }
-
- // Attempt to create client without set authenticator. Default authenticator should be set
- String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
- });
-
- assertEquals(X509ClientAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType());
-
- // update profiles
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Pershyy Profil")
- .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
- createSecureClientAuthenticatorExecutorConfig(
- Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
- JWTClientAuthenticator.PROVIDER_ID))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // It is allowed to update authenticator to one of allowed client authenticators. Default client authenticator is not explicitly set in this case
- updateClientByAdmin(cId, (ClientRepresentation clientRep) -> {
- clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
- });
- assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType());
- }
-
- // Tests that secured client authenticator is enforced also during client authentication itself (during token request after successful login)
- @Test
- public void testSecureClientAuthenticatorDuringLogin() throws Exception {
- // register profile to NOT allow authentication with ClientIdAndSecret
- String profileName = "MyProfile";
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(profileName, "Primum Profile")
- .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
- createSecureClientAuthenticatorExecutorConfig(
- Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
- null))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register role policy
- String roleAlphaName = "sample-client-role-alpha";
- String roleZetaName = "sample-client-role-zeta";
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName)))
- .addProfile(profileName)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // create a client without client role. It should be successful (policy not applied)
- String clientId = generateSuffixedName(CLIENT_NAME);
- String cId = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret("secret");
- });
-
- // Login with clientIdAndSecret. It should be successful (policy not applied)
- successfulLoginAndLogout(clientId, "secret");
-
- // Add role to the client
- ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId);
- ClientRepresentation clientRep = clientResource.toRepresentation();
- Assert.assertEquals(ClientIdAndSecretAuthenticator.PROVIDER_ID, clientRep.getClientAuthenticatorType());
- clientResource.roles().create(RoleBuilder.create().name(roleAlphaName).build());
-
- // Not allowed to client authentication with clientIdAndSecret anymore. Client matches policy now
- oauth.clientId(clientId);
- oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
-
- String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
- OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, "secret");
- assertEquals(400, res.getStatusCode());
- assertEquals(OAuthErrorException.INVALID_GRANT, res.getError());
- assertEquals("Configured client authentication method not allowed for client", res.getErrorDescription());
- }
-
- @Test
- public void testDynamicClientRegisterAndUpdate() throws Exception {
- 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 Exception {
- 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 Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Eichte profil")
- .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
- createPKCEEnforceExecutorConfig(Boolean.TRUE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- 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);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Eischt Politik", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- failLoginByNotFollowingPKCE(clientId);
-
- // update policies
- updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Aktualiseiert Eischt Politik", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList("anothor-client-role")))
- .addProfile(PROFILE_NAME)
- .toRepresentation());
-
- successfulLoginAndLogout(clientId, clientSecret);
-
- // update policies
- updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Aktualiseiert Eischt Politik", Boolean.TRUE)
- .addProfile(PROFILE_NAME)
- .toRepresentation());
-
- successfulLoginAndLogout(clientId, clientSecret);
- }
-
- @Test
- public void testCreateUpdateDeleteExecutorRuntime() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Purofairu Sono Ichi")
- .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
- createPKCEEnforceExecutorConfig(Boolean.FALSE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Porishii Sono Ichi", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
- .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
- createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER)))
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- 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);
-
- // update policies
- updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Koushinsareta Porishii Sono Ichi", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
- .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
- createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER)))
- .addProfile(PROFILE_NAME)
- .toRepresentation());
-
- failLoginByNotFollowingPKCE(clientId);
-
- // update profiles
- updateProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Koushinsareta Purofairu Sono Ichi")
- .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
- createPKCEEnforceExecutorConfig(Boolean.TRUE))
- .toRepresentation());
-
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- clientRep.setServiceAccountsEnabled(Boolean.FALSE);
- });
- assertEquals(false, getClientByAdmin(cid).isServiceAccountsEnabled());
- assertEquals(OAuth2Constants.PKCE_METHOD_S256, OIDCAdvancedConfigWrapper.fromClientRepresentation(getClientByAdmin(cid)).getPkceCodeChallengeMethod());
-
- // update profiles
- updateProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Sarani Koushinsareta Purofairu Sono Ichi").toRepresentation());
-
- 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 Exception {
- String roleAlphaName = "sample-client-role-alpha";
- String roleBetaName = "sample-client-role-beta";
- String roleZetaName = "sample-client-role-zeta";
- String roleCommonName = "sample-client-role-common";
-
- // register profiles
- String profileAlphaName = "MyProfile-alpha";
- String profileBetaName = "MyProfile-beta";
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(profileAlphaName, "Pierwszy Profil")
- .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
- createSecureClientAuthenticatorExecutorConfig(Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID), ClientIdAndSecretAuthenticator.PROVIDER_ID))
- .toRepresentation()).addProfile(
- (new ClientProfileBuilder()).createProfile(profileBetaName, "Drugi Profil")
- .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
- createPKCEEnforceExecutorConfig(Boolean.TRUE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- String policyAlphaName = "MyPolicy-alpha";
- String policyBetaName = "MyPolicy-beta";
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(policyAlphaName, "Pierwsza Zasada", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName)))
- .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
- createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER)))
- .addProfile(profileAlphaName)
- .toRepresentation()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(policyBetaName, "Drugi Zasada", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(roleBetaName, roleZetaName)))
- .addProfile(profileBetaName)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- String clientAlphaId = generateSuffixedName("Alpha-App");
- String clientAlphaSecret = "secretAlpha";
-
- // Not allowed client authenticator should fail
- try {
- createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientAlphaSecret);
- clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
- }
-
- String cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientAlphaSecret);
- clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.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 Exception {
- // register policies
- String json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Fyrsta Stefnan", Boolean.TRUE)
- .addCondition(TestRaiseExceptionConditionFactory.PROVIDER_ID,
- createTestRaiseExeptionConditionConfig())
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- try {
- createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.SERVER_ERROR, e.getMessage());
- }
- }
-
- @Test
- public void testAnyClientCondition() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil")
- .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID,
- createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- 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 Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Die Erste Politik")
- .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientAccessTypeCondition", "Die Erste Politik", Boolean.TRUE)
- .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, null)
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).addPolicy(
- (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateSourceGroupsCondition", "Die Zweite Politik", Boolean.TRUE)
- .addCondition(ClientUpdaterSourceGroupsConditionFactory.PROVIDER_ID, null)
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).addPolicy(
- (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateSourceRolesCondition", "Die Dritte Politik", Boolean.TRUE)
- .addCondition(ClientUpdaterSourceRolesConditionFactory.PROVIDER_ID, null)
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).addPolicy(
- (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateContextCondition", "Die Vierte Politik", Boolean.TRUE)
- .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID, null)
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- 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);
- }
-
- @Test
- public void testClientUpdateSourceHostsCondition() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvni Profil")
- .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
- createSecureClientAuthenticatorExecutorConfig(
- Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
- null)
- )
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prvni Politika", Boolean.TRUE)
- .addCondition(ClientUpdaterSourceHostsConditionFactory.PROVIDER_ID,
- createClientUpdateSourceHostsConditionConfig(Arrays.asList("localhost", "127.0.0.1")))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- String clientId = generateSuffixedName(CLIENT_NAME);
- String clientSecret = "secret";
- try {
- createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
- }
-
- // update policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Aktualizovana Prvni Politika", Boolean.TRUE)
- .addCondition(ClientUpdaterSourceHostsConditionFactory.PROVIDER_ID,
- createClientUpdateSourceHostsConditionConfig(Arrays.asList("example.com")))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- try {
- createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- });
- } catch (Exception e) {
- fail();
- }
- }
-
- @Test
- public void testClientUpdateSourceGroupsCondition() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profil")
- .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
- createSecureClientAuthenticatorExecutorConfig(
- Arrays.asList(JWTClientAuthenticator.PROVIDER_ID),
- null)
- )
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politik", Boolean.TRUE)
- .addCondition(ClientUpdaterSourceGroupsConditionFactory.PROVIDER_ID,
- createClientUpdateSourceGroupsConditionConfig(Arrays.asList("topGroup")))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- 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 Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Il Primo Profilo")
- .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
- createSecureClientAuthenticatorExecutorConfig(
- Arrays.asList(JWTClientSecretAuthenticator.PROVIDER_ID),
- null)
- )
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Prima Politica", Boolean.TRUE)
- .addCondition(ClientUpdaterSourceRolesConditionFactory.PROVIDER_ID,
- createClientUpdateSourceRolesConditionConfig(Arrays.asList(Constants.REALM_MANAGEMENT_CLIENT_ID + "." + AdminRoles.CREATE_CLIENT)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- 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 Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Het Eerste Profiel")
- .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
- createPKCEEnforceExecutorConfig(Boolean.TRUE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Het Eerste Beleid", Boolean.TRUE)
- .addCondition(ClientScopesConditionFactory.PROVIDER_ID,
- createClientScopesConditionConfig(ClientScopesConditionFactory.OPTIONAL, Arrays.asList("offline_access", "microprofile-jwt")))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- 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 Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "El Primer Perfil")
- .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Primera Plitica", Boolean.TRUE)
- .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID,
- createClientAccessTypeConditionConfig(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // 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);
-
- // update profiles
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "El Primer Perfil")
- .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
- createPKCEEnforceExecutorConfig(Boolean.FALSE)) // check only
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // Attempt to create a confidential client without PKCE setting. Should fail
- try {
- createClientByAdmin(generateSuffixedName("Gamma-App"), (ClientRepresentation clientRep) -> {
- clientRep.setSecret("secretGamma");
- clientRep.setBearerOnly(Boolean.FALSE);
- clientRep.setPublicClient(Boolean.FALSE);
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
- assertEquals("Invalid client metadata: code_challenge_method", e.getErrorDetail());
- }
-
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "El Primer Perfil")
- .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
- createPKCEEnforceExecutorConfig(Boolean.TRUE)) // enforce
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- authCreateClients();
- String clientGammaId = createClientDynamically(generateSuffixedName("Gamma-App"), (OIDCClientRepresentation clientRep) -> {
- clientRep.setClientSecret("secretGamma");
- });
-
- ClientRepresentation clientRep = getClientByAdmin(clientGammaId);
- assertEquals(OAuth2Constants.PKCE_METHOD_S256, OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).getPkceCodeChallengeMethod());
-
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "El Primer Perfil")
- .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
- createPKCEEnforceExecutorConfig(Boolean.FALSE)) // check only
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // Attempt to update the confidential client with not allowed PKCE setting. Should fail
- try {
- updateClientByAdmin(clientGammaId, (ClientRepresentation updatingClientRep) -> {
- updatingClientRep.setAttributes(new HashMap<>());
- updatingClientRep.getAttributes().put(OIDCConfigAttributes.PKCE_CODE_CHALLENGE_METHOD, OAuth2Constants.PKCE_METHOD_PLAIN);
- });
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
- assertEquals("Invalid client metadata: code_challenge_method", e.getErrorDetail());
- }
- ClientRepresentation cRep = getClientByAdmin(clientGammaId);
- assertEquals(OAuth2Constants.PKCE_METHOD_S256, cRep.getAttributes().get(OIDCConfigAttributes.PKCE_CODE_CHALLENGE_METHOD));
-
- }
-
- @Test
- public void testSecureResponseTypeExecutor() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil")
- .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "A Primeira Politica", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- 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);
- oauth.nonce("vbwe566fsfffds");
- 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();
-
- // update profiles
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil")
- .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, createSecureResponseTypeExecutor(Boolean.FALSE, Boolean.TRUE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN + " " + OIDCResponseType.TOKEN); // token response type allowed
- oauth.nonce("cie8cjcwiw");
- 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();
-
- // shall allow code using response_mode jwt
- oauth.responseType(OIDCResponseType.CODE);
- oauth.responseMode("jwt");
- OAuthClient.AuthorizationEndpointResponse authzResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
- String jwsResponse = authzResponse.getResponse();
- AuthorizationResponseToken responseObject = oauth.verifyAuthorizationResponseToken(jwsResponse);
- code = (String) responseObject.getOtherClaims().get(OAuth2Constants.CODE);
- res = oauth.doAccessTokenRequest(code, clientSecret);
- assertEquals(200, res.getStatusCode());
-
- // update profiles
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil")
- .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, createSecureResponseTypeExecutor(Boolean.FALSE, Boolean.FALSE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- oauth.openLogout();
- oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN + " " + OIDCResponseType.TOKEN); // token response type allowed
- oauth.responseMode("jwt");
- oauth.openLoginForm();
- final JWSInput errorJws = new JWSInput(new OAuthClient.AuthorizationEndpointResponse(oauth).getResponse());
- JsonNode errorClaims = JsonSerialization.readValue(errorJws.getContent(), JsonNode.class);
- assertEquals(OAuthErrorException.INVALID_REQUEST, errorClaims.get("error").asText());
- }
-
- @Test
- public void testSecureResponseTypeExecutorAllowTokenResponseType() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil")
- .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, createSecureResponseTypeExecutor(null, Boolean.TRUE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forsta Policyn", Boolean.TRUE)
- .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
- createClientUpdateContextConditionConfig(Arrays.asList(
- ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER,
- ClientUpdaterContextConditionFactory.BY_INITIAL_ACCESS_TOKEN,
- ClientUpdaterContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)))
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // create by Admin REST API
- try {
- createClientByAdmin(generateSuffixedName("App-by-Admin"), (ClientRepresentation clientRep) -> {
- clientRep.setSecret("secret");
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
- }
-
- // update profiles
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil")
- .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, createSecureResponseTypeExecutor(Boolean.TRUE, null))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- String cId = null;
- String clientId = generateSuffixedName(CLIENT_NAME);
- String clientSecret = "secret";
- try {
- cId = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- clientRep.setStandardFlowEnabled(Boolean.TRUE);
- clientRep.setImplicitFlowEnabled(Boolean.TRUE);
- clientRep.setPublicClient(Boolean.FALSE);
- });
- } catch (ClientPolicyException e) {
- fail();
- }
- ClientRepresentation cRep = getClientByAdmin(cId);
- assertEquals(Boolean.TRUE.toString(), cRep.getAttributes().get(OIDCConfigAttributes.ID_TOKEN_AS_DETACHED_SIGNATURE));
-
- 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);
- oauth.nonce("LIVieviDie028f");
- 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();
-
- IDToken idToken = oauth.verifyIDToken(new OAuthClient.AuthorizationEndpointResponse(oauth).getIdToken());
- // confirm ID token as detached signature does not include authenticated user's claims
- Assert.assertNull(idToken.getEmailVerified());
- Assert.assertNull(idToken.getName());
- Assert.assertNull(idToken.getPreferredUsername());
- Assert.assertNull(idToken.getGivenName());
- Assert.assertNull(idToken.getFamilyName());
- Assert.assertNull(idToken.getEmail());
- assertEquals("LIVieviDie028f", idToken.getNonce());
- // confirm an access token not returned
- Assert.assertNull(new OAuthClient.AuthorizationEndpointResponse(oauth).getAccessToken());
-
- 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();
- }
-
- @Test
- public void testSecureRequestObjectExecutor() throws Exception {
- Integer availablePeriod = Integer.valueOf(SecureRequestObjectExecutor.DEFAULT_AVAILABLE_PERIOD + 400);
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil")
- .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID,
- createSecureRequestObjectExecutorConfig(availablePeriod, null))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prva Politika", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- 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("Missing parameter: 'request' or 'request_uri'", 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("Invalid parameter. Parameters in 'request' object not matching with request parameters", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
-
- // check whether client_id exists in both query parameter and request object
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.setClientId(null);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, true);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Invalid parameter. Parameters in 'request' object not matching with request parameters", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
-
- // check whether response_type exists in both query parameter and request object
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.setResponseType(null);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, true);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Invalid parameter. Parameters in 'request' object not matching with request parameters", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
-
- // Check scope required
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.setScope(null);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, true);
- oauth.scope(null);
- oauth.openid(false);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Parameter 'scope' missing in the request parameters or in 'request' object", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
- oauth.openid(true);
-
- // check whether "exp" claim exists
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.exp(null);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Missing parameter in the 'request' object: 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(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Request Expired", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
-
- // check whether "nbf" claim exists
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.nbf(null);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Missing parameter in the 'request' object: nbf", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
-
- // check whether request object not yet being processed
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.nbf(requestObject.getNbf() + 600);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Request not yet being processed", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
-
- // check whether request object's available period is short
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.exp(requestObject.getNbf() + availablePeriod.intValue() + 1);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Request's available period is long", 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(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Missing parameter in the 'request' object: 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(OAuthErrorException.INVALID_REQUEST_URI, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Invalid parameter in the 'request' object: 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. Parameters in 'request' object not matching with request parameters", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
-
- // valid request object
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, true);
-
- successfulLoginAndLogout(clientId, clientSecret);
-
- // update profile : no configuration - "nbf" check and available period is 3600 sec
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil")
- .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // check whether "nbf" claim exists
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.nbf(null);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Missing parameter in the 'request' object: nbf", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
-
- // check whether request object not yet being processed
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.nbf(requestObject.getNbf() + 600);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Request not yet being processed", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
-
- // check whether request object's available period is short
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.exp(requestObject.getNbf() + SecureRequestObjectExecutor.DEFAULT_AVAILABLE_PERIOD + 1);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Request's available period is long", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
-
- // update profile : not check "nbf"
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil")
- .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID,
- createSecureRequestObjectExecutorConfig(null, Boolean.FALSE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // not check whether "nbf" claim exists
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.nbf(null);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
- successfulLoginAndLogout(clientId, clientSecret);
-
- // not check whether request object not yet being processed
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.nbf(requestObject.getNbf() + 600);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
- successfulLoginAndLogout(clientId, clientSecret);
-
- // not check whether request object's available period is short
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.exp(requestObject.getNbf() + SecureRequestObjectExecutor.DEFAULT_AVAILABLE_PERIOD + 1);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
- successfulLoginAndLogout(clientId, clientSecret);
-
- // update profile : force request object encryption
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil")
- .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID, createSecureRequestObjectExecutorConfig(null, null, true))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("Request object not encrypted", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
- }
-
- @Test
- public void testParSecureRequestObjectExecutor() throws Exception {
- Integer availablePeriod = Integer.valueOf(SecureRequestObjectExecutor.DEFAULT_AVAILABLE_PERIOD + 400);
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil")
- .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID,
- createSecureRequestObjectExecutorConfig(availablePeriod, true))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prva Politika", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- 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()));
- });
-
- oauth.realm(REALM_NAME);
- oauth.clientId(clientId);
-
- adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build());
-
- AuthorizationEndpointRequestObject requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
-
- oauth.request(signRequestObject(requestObject));
- OAuthClient.ParResponse pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
- assertEquals(201, pResp.getStatusCode());
- String requestUri = pResp.getRequestUri();
-
- oauth.scope(null);
- oauth.responseType(null);
- oauth.request(null);
- oauth.requestUri(requestUri);
- OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
- assertNotNull(loginResponse.getCode());
- oauth.openLogout();
-
- requestObject.exp(null);
- oauth.requestUri(null);
- oauth.request(signRequestObject(requestObject));
- pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
- requestUri = pResp.getRequestUri();
- oauth.request(null);
- oauth.requestUri(requestUri);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST_URI, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
-
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.nbf(null);
- oauth.requestUri(null);
- oauth.request(signRequestObject(requestObject));
- pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
- requestUri = pResp.getRequestUri();
- oauth.request(null);
- oauth.requestUri(requestUri);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST_URI, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
-
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.audience("https://www.other1.example.com/");
- oauth.request(signRequestObject(requestObject));
- oauth.requestUri(null);
- pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
- requestUri = pResp.getRequestUri();
- oauth.request(null);
- oauth.requestUri(requestUri);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST_URI, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
-
- requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
- requestObject.setOtherClaims(OIDCLoginProtocol.REQUEST_URI_PARAM, "foo");
- oauth.request(signRequestObject(requestObject));
- oauth.requestUri(null);
- pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
- assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, pResp.getError());
- }
-
- private String signRequestObject(AuthorizationEndpointRequestObject requestObject) throws IOException {
- byte[] contentBytes = JsonSerialization.writeValueAsBytes(requestObject);
- String encodedRequestObject = Base64Url.encode(contentBytes);
- TestOIDCEndpointsApplicationResource client = testingClient.testApp().oidcClientEndpoints();
-
- // use and set jwks_url
- ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(oauth.getRealm()), oauth.getClientId());
- ClientRepresentation clientRep = clientResource.toRepresentation();
- OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true);
- OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(TestApplicationResourceUrls.clientJwksUri());
- clientResource.update(clientRep);
- client.generateKeys(Algorithm.PS256);
- client.registerOIDCRequest(encodedRequestObject, Algorithm.PS256);
-
- // do not send any other parameter but the request request parameter
- String oidcRequest = client.getOIDCRequest();
- return oidcRequest;
- }
-
- @Test
- public void testSecureSessionEnforceExecutor() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
- .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- String roleAlphaName = "sample-client-role-alpha";
- String roleBetaName = "sample-client-role-beta";
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(roleBetaName)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- 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 Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
- .addExecutor(SecureSigningAlgorithmExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forsta Policyn", Boolean.TRUE)
- .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
- createClientUpdateContextConditionConfig(Arrays.asList(
- ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER,
- ClientUpdaterContextConditionFactory.BY_INITIAL_ACCESS_TOKEN,
- ClientUpdaterContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // 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, "none");
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_REQUEST, 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, Algorithm.PS256);
- clientRep.getAttributes().put(OIDCConfigAttributes.REQUEST_OBJECT_SIGNATURE_ALG, Algorithm.ES256);
- clientRep.getAttributes().put(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG, Algorithm.ES256);
- clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, Algorithm.ES256);
- clientRep.getAttributes().put(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG, Algorithm.ES256);
- });
-
- // create by Admin REST API - success, PS256 enforced
- String cAppAdmin2Id = createClientByAdmin(generateSuffixedName("App-by-Admin2"), (ClientRepresentation client2Rep) -> {
- });
- ClientRepresentation cRep2 = getClientByAdmin(cAppAdmin2Id);
- assertEquals(Algorithm.PS256, cRep2.getAttributes().get(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG));
- assertEquals(Algorithm.PS256, cRep2.getAttributes().get(OIDCConfigAttributes.REQUEST_OBJECT_SIGNATURE_ALG));
- assertEquals(Algorithm.PS256, cRep2.getAttributes().get(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG));
- assertEquals(Algorithm.PS256, cRep2.getAttributes().get(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG));
- assertEquals(Algorithm.PS256, cRep2.getAttributes().get(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG));
-
- // 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);
- });
- } catch (ClientPolicyException cpe) {
- assertEquals(Errors.INVALID_REQUEST, cpe.getError());
- }
- ClientRepresentation cRep = getClientByAdmin(cAppAdminId);
- assertEquals(Algorithm.ES256, 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);
- });
- cRep = getClientByAdmin(cAppAdminId);
- assertEquals(Algorithm.PS384, cRep.getAttributes().get(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG));
-
- // update profiles, ES256 enforced
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
- .addExecutor(SecureSigningAlgorithmExecutorFactory.PROVIDER_ID,
- createSecureSigningAlgorithmEnforceExecutorConfig(Algorithm.ES256))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // update by Admin REST API - success
- updateClientByAdmin(cAppAdmin2Id, (ClientRepresentation client2Rep) -> {
- client2Rep.getAttributes().remove(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG);
- client2Rep.getAttributes().remove(OIDCConfigAttributes.REQUEST_OBJECT_SIGNATURE_ALG);
- client2Rep.getAttributes().remove(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG);
- client2Rep.getAttributes().remove(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG);
- client2Rep.getAttributes().remove(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG);
- });
- cRep2 = getClientByAdmin(cAppAdmin2Id);
- assertEquals(Algorithm.ES256, cRep2.getAttributes().get(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG));
- assertEquals(Algorithm.ES256, cRep2.getAttributes().get(OIDCConfigAttributes.REQUEST_OBJECT_SIGNATURE_ALG));
- assertEquals(Algorithm.ES256, cRep2.getAttributes().get(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG));
- assertEquals(Algorithm.ES256, cRep2.getAttributes().get(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG));
- assertEquals(Algorithm.ES256, cRep2.getAttributes().get(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG));
-
- // update profiles, fall back to PS256
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
- .addExecutor(SecureSigningAlgorithmExecutorFactory.PROVIDER_ID,
- createSecureSigningAlgorithmEnforceExecutorConfig(Algorithm.RS512))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // 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);
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_REQUEST, e.getMessage());
- }
-
- // create dynamically - success
- String cAppDynamicClientId = createClientDynamically(generateSuffixedName("App-in-Dynamic"), (OIDCClientRepresentation clientRep) -> {
- clientRep.setUserinfoSignedResponseAlg(Algorithm.ES256);
- clientRep.setRequestObjectSigningAlg(Algorithm.ES256);
- clientRep.setIdTokenSignedResponseAlg(Algorithm.PS256);
- clientRep.setTokenEndpointAuthSigningAlg(Algorithm.PS256);
- });
- events.expect(EventType.CLIENT_REGISTER).client(cAppDynamicClientId).user(Matchers.isEmptyOrNullString()).assertEvent();
-
- // update dynamically - fail
- try {
- updateClientDynamically(cAppDynamicClientId, (OIDCClientRepresentation clientRep) -> {
- clientRep.setIdTokenSignedResponseAlg(Algorithm.RS256);
- });
- fail();
- } catch (ClientRegistrationException e) {
- assertEquals(ERR_MSG_CLIENT_REG_FAIL, e.getMessage());
- }
- assertEquals(Algorithm.PS256, getClientDynamically(cAppDynamicClientId).getIdTokenSignedResponseAlg());
-
- // update dynamically - success
- updateClientDynamically(cAppDynamicClientId, (OIDCClientRepresentation clientRep) -> {
- clientRep.setIdTokenSignedResponseAlg(Algorithm.ES384);
- });
- assertEquals(Algorithm.ES384, getClientDynamically(cAppDynamicClientId).getIdTokenSignedResponseAlg());
-
- // create dynamically - success, PS256 enforced
- restartAuthenticatedClientRegistrationSetting();
- String cAppDynamicClient2Id = createClientDynamically(generateSuffixedName("App-in-Dynamic"), (OIDCClientRepresentation client2Rep) -> {
- });
- OIDCClientRepresentation cAppDynamicClient2Rep = getClientDynamically(cAppDynamicClient2Id);
- assertEquals(Algorithm.PS256, cAppDynamicClient2Rep.getUserinfoSignedResponseAlg());
- assertEquals(Algorithm.PS256, cAppDynamicClient2Rep.getRequestObjectSigningAlg());
- assertEquals(Algorithm.PS256, cAppDynamicClient2Rep.getIdTokenSignedResponseAlg());
- assertEquals(Algorithm.PS256, cAppDynamicClient2Rep.getTokenEndpointAuthSigningAlg());
-
- // update profiles, enforce ES256
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
- .addExecutor(SecureSigningAlgorithmExecutorFactory.PROVIDER_ID,
- createSecureSigningAlgorithmEnforceExecutorConfig(Algorithm.ES256))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // update dynamically - success, ES256 enforced
- updateClientDynamically(cAppDynamicClient2Id, (OIDCClientRepresentation client2Rep) -> {
- client2Rep.setUserinfoSignedResponseAlg(null);
- client2Rep.setRequestObjectSigningAlg(null);
- client2Rep.setIdTokenSignedResponseAlg(null);
- client2Rep.setTokenEndpointAuthSigningAlg(null);
- });
- cAppDynamicClient2Rep = getClientDynamically(cAppDynamicClient2Id);
- assertEquals(Algorithm.ES256, cAppDynamicClient2Rep.getUserinfoSignedResponseAlg());
- assertEquals(Algorithm.ES256, cAppDynamicClient2Rep.getRequestObjectSigningAlg());
- assertEquals(Algorithm.ES256, cAppDynamicClient2Rep.getIdTokenSignedResponseAlg());
- assertEquals(Algorithm.ES256, cAppDynamicClient2Rep.getTokenEndpointAuthSigningAlg());
- }
-
- @Test
- public void testSecureClientRegisteringUriEnforceExecutor() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili")
- .addExecutor(SecureClientUrisExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Ensimmainen Politiikka", Boolean.TRUE)
- .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
- createClientUpdateContextConditionConfig(Arrays.asList(
- ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER,
- ClientUpdaterContextConditionFactory.BY_INITIAL_ACCESS_TOKEN,
- ClientUpdaterContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- 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());
- }
-
- String cid = null;
- String clientId = generateSuffixedName(CLIENT_NAME);
- try {
- cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setServiceAccountsEnabled(Boolean.TRUE);
- clientRep.setRedirectUris(null);
- });
- } catch (Exception e) {
- fail();
- }
-
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- clientRep.setRedirectUris(null);
- clientRep.setServiceAccountsEnabled(Boolean.FALSE);
- });
- assertEquals(false, getClientByAdmin(cid).isServiceAccountsEnabled());
-
- // update policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Paivitetyn Ensimmaisen Politiikka", Boolean.TRUE)
- .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
- createClientUpdateContextConditionConfig(Arrays.asList(
- ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER,
- ClientUpdaterContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- try {
- updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> {
- clientRep.setRedirectUris(Collections.singletonList("https://newredirect/*"));
- });
- fail();
- } catch (ClientRegistrationException e) {
- assertEquals(ERR_MSG_CLIENT_REG_FAIL, e.getMessage());
- }
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- // rootUrl
- clientRep.setRootUrl("https://client.example.com/");
- // adminUrl
- clientRep.setAdminUrl("https://client.example.com/admin/");
- // baseUrl
- clientRep.setBaseUrl("https://client.example.com/base/");
- // web origins
- clientRep.setWebOrigins(Arrays.asList("https://valid.other.client.example.com/", "https://valid.another.client.example.com/"));
- // backchannel logout URL
- Map attributes = Optional.ofNullable(clientRep.getAttributes()).orElse(new HashMap<>());
- attributes.put(OIDCConfigAttributes.BACKCHANNEL_LOGOUT_URL, "https://client.example.com/logout/");
- clientRep.setAttributes(attributes);
- // OAuth2 : redirectUris
- clientRep.setRedirectUris(Arrays.asList("https://client.example.com/redirect/", "https://client.example.com/callback/"));
- // OAuth2 : jwks_uri
- attributes.put(OIDCConfigAttributes.JWKS_URL, "https://client.example.com/jwks/");
- clientRep.setAttributes(attributes);
- // OIDD : requestUris
- setAttributeMultivalued(clientRep, OIDCConfigAttributes.REQUEST_URIS, Arrays.asList("https://client.example.com/request/", "https://client.example.com/reqobj/"));
- // CIBA Client Notification Endpoint
- attributes.put(CibaConfig.CIBA_BACKCHANNEL_CLIENT_NOTIFICATION_ENDPOINT, "https://client.example.com/client-notification/");
- clientRep.setAttributes(attributes);
- });
- } catch (Exception e) {
- fail();
- }
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- // rootUrl
- clientRep.setRootUrl("http://client.example.com/*/");
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
- assertEquals("Invalid rootUrl", e.getErrorDetail());
- }
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- // adminUrl
- clientRep.setAdminUrl("http://client.example.com/admin/");
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
- assertEquals("Invalid adminUrl", e.getErrorDetail());
- }
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- // baseUrl
- clientRep.setBaseUrl("https://client.example.com/base/*");
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
- assertEquals("Invalid baseUrl", e.getErrorDetail());
- }
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- // web origins
- clientRep.setWebOrigins(Arrays.asList("http://valid.another.client.example.com/"));
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
- assertEquals("Invalid webOrigins", e.getErrorDetail());
- }
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- // backchannel logout URL
- Map attributes = Optional.ofNullable(clientRep.getAttributes()).orElse(new HashMap<>());
- attributes.put(OIDCConfigAttributes.BACKCHANNEL_LOGOUT_URL, "httpss://client.example.com/logout/");
- clientRep.setAttributes(attributes);
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
- assertEquals("Invalid logoutUrl", e.getErrorDetail());
- }
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- // OAuth2 : redirectUris
- clientRep.setRedirectUris(Arrays.asList("https://client.example.com/redirect/", "ftp://client.example.com/callback/"));
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
- assertEquals("Invalid redirectUris", e.getErrorDetail());
- }
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- // OAuth2 : jwks_uri
- Map attributes = Optional.ofNullable(clientRep.getAttributes()).orElse(new HashMap<>());
- attributes.put(OIDCConfigAttributes.JWKS_URL, "http s://client.example.com/jwks/");
- clientRep.setAttributes(attributes);
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
- assertEquals("Invalid jwksUri", e.getErrorDetail());
- }
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- // OIDD : requestUris
- setAttributeMultivalued(clientRep, OIDCConfigAttributes.REQUEST_URIS, Arrays.asList("https://client.example.com/request/*", "https://client.example.com/reqobj/"));
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
- assertEquals("Invalid requestUris", e.getErrorDetail());
- }
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
- // CIBA Client Notification Endpoint
- Map attributes = Optional.ofNullable(clientRep.getAttributes()).orElse(new HashMap<>());
- attributes.put(CibaConfig.CIBA_BACKCHANNEL_CLIENT_NOTIFICATION_ENDPOINT, "http://client.example.com/client-notification/");
- clientRep.setAttributes(attributes);
- });
- fail();
- } catch (ClientPolicyException e) {
- assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
- assertEquals("Invalid cibaClientNotificationEndpoint", e.getErrorDetail());
- }
- }
-
- @Test
- public void testClientPolicyTriggeredForServiceAccountRequest() throws Exception {
- String clientId = "service-account-app";
- String clientSecret = "app-secret";
- createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- clientRep.setStandardFlowEnabled(Boolean.FALSE);
- clientRep.setImplicitFlowEnabled(Boolean.FALSE);
- clientRep.setServiceAccountsEnabled(Boolean.TRUE);
- clientRep.setPublicClient(Boolean.FALSE);
- clientRep.setBearerOnly(Boolean.FALSE);
- });
-
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
- .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
- createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.SERVICE_ACCOUNT_TOKEN_REQUEST)))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- String origClientId = oauth.getClientId();
- oauth.clientId("service-account-app");
- try {
- OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest("app-secret");
- assertEquals(400, response.getStatusCode());
- assertEquals(ClientPolicyEvent.SERVICE_ACCOUNT_TOKEN_REQUEST.toString(), response.getError());
- assertEquals("Exception thrown intentionally", response.getErrorDescription());
- } finally {
- oauth.clientId(origClientId);
- }
- }
-
- private List getAttributeMultivalued(ClientRepresentation clientRep, String attrKey) {
- String attrValue = Optional.ofNullable(clientRep.getAttributes()).orElse(Collections.emptyMap()).get(attrKey);
- if (attrValue == null) return Collections.emptyList();
- return Arrays.asList(Constants.CFG_DELIMITER_PATTERN.split(attrValue));
- }
-
- private void setAttributeMultivalued(ClientRepresentation clientRep, String attrKey, List attrValues) {
- String attrValueFull = String.join(Constants.CFG_DELIMITER, attrValues);
- clientRep.getAttributes().put(attrKey, attrValueFull);
- }
-
- @Test
- public void testSecureSigningAlgorithmForSignedJwtEnforceExecutorWithSecureAlg() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili")
- .addExecutor(SecureSigningAlgorithmForSignedJwtExecutorFactory.PROVIDER_ID, createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig(Boolean.TRUE)
- ).toRepresentation()
- )
- .toString();
- updateProfiles(json);
-
- // register policies
- String roleAlphaName = "sample-client-role-alpha";
- String roleZetaName = "sample-client-role-zeta";
- String roleCommonName = "sample-client-role-common";
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // create a client with client role
- String clientId = generateSuffixedName(CLIENT_NAME);
- String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret("secret");
- clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
- clientRep.setAttributes(new HashMap<>());
- clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, Algorithm.ES256);
- });
- adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(roleAlphaName).build());
- adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(roleCommonName).build());
-
-
- ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId);
- ClientRepresentation clientRep = clientResource.toRepresentation();
-
- KeyPair keyPair = setupJwksUrl(Algorithm.ES256, clientRep, clientResource);
- PublicKey publicKey = keyPair.getPublic();
- PrivateKey privateKey = keyPair.getPrivate();
-
- String signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, 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, Algorithm.ES256);
- OAuthClient.AccessTokenResponse refreshedResponse = doRefreshTokenRequestWithSignedJWT(response.getRefreshToken(), signedJwt);
- assertEquals(200, refreshedResponse.getStatusCode());
-
- // introspect token
- signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, Algorithm.ES256);
- HttpResponse tokenIntrospectionResponse = doTokenIntrospectionWithSignedJWT("access_token", refreshedResponse.getAccessToken(), signedJwt);
- assertEquals(200, tokenIntrospectionResponse.getStatusLine().getStatusCode());
-
- // revoke token
- signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, Algorithm.ES256);
- HttpResponse revokeTokenResponse = doTokenRevokeWithSignedJWT("refresh_toke", refreshedResponse.getRefreshToken(), signedJwt);
- assertEquals(200, revokeTokenResponse.getStatusLine().getStatusCode());
-
- signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, 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, Algorithm.ES256);
- HttpResponse logoutResponse = doLogoutWithSignedJWT(refreshedResponse.getRefreshToken(), signedJwt);
- assertEquals(204, logoutResponse.getStatusLine().getStatusCode());
- }
-
- @Test
- public void testSecureSigningAlgorithmForSignedJwtEnforceExecutorWithNotSecureAlg() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili")
- .addExecutor(SecureSigningAlgorithmForSignedJwtExecutorFactory.PROVIDER_ID, createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig(Boolean.FALSE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- String roleAlphaName = "sample-client-role-alpha";
- String roleZetaName = "sample-client-role-zeta";
- String roleCommonName = "sample-client-role-common";
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // create a client with client role
- String clientId = generateSuffixedName(CLIENT_NAME);
- String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret("secret");
- clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
- clientRep.setAttributes(new HashMap<>());
- clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, Algorithm.RS256);
- });
- adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(roleAlphaName).build());
- adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(roleCommonName).build());
-
- ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId);
- ClientRepresentation clientRep = clientResource.toRepresentation();
-
- KeyPair keyPair = setupJwksUrl(Algorithm.RS256, clientRep, clientResource);
- PublicKey publicKey = keyPair.getPublic();
- PrivateKey privateKey = keyPair.getPrivate();
-
- String signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, Algorithm.RS256);
-
- 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(400, response.getStatusCode());
- assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
- assertEquals("not allowed signature algorithm.", response.getErrorDescription());
- }
-
- @Test
- public void testHolderOfKeyEnforceExecutor() throws Exception {
- Assume.assumeTrue("This test must be executed with enabled TLS.", ServerURLs.AUTH_SERVER_SSL_REQUIRED);
-
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Az Elso Profil")
- .addExecutor(HolderOfKeyEnforcerExecutorFactory.PROVIDER_ID,
- createHolderOfKeyEnforceExecutorConfig(Boolean.TRUE))
- .addExecutor(SecureSigningAlgorithmForSignedJwtExecutorFactory.PROVIDER_ID,
- createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig(Boolean.FALSE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Az Elso Politika", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID,
- createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- try (ClientAttributeUpdater cau = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, TEST_CLIENT)) {
- ClientRepresentation clientRep = cau.getResource().toRepresentation();
- Assert.assertNotNull(clientRep);
- OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseMtlsHoKToken(true);
- cau.update();
- checkMtlsFlow();
- }
- }
-
- @Test
- public void testNegativeLogicCondition() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
- .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- String clientId = generateSuffixedName(CLIENT_NAME);
- String clientSecret = "secretBeta";
- createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- });
-
- try {
- failLoginWithoutSecureSessionParameter(clientId, ERR_MSG_MISSING_NONCE);
-
- // update policies
- updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig(Boolean.TRUE))
- .addProfile(PROFILE_NAME)
- .toRepresentation());
-
- successfulLoginAndLogout(clientId, clientSecret);
-
- // update policies
- updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig(Boolean.FALSE))
- .addProfile(PROFILE_NAME)
- .toRepresentation());
-
- failLoginWithoutSecureSessionParameter(clientId, ERR_MSG_MISSING_NONCE);
- } catch (Exception e) {
- fail();
- }
- }
-
- @Test
- public void testExtendedClientPolicyIntefacesForClientRegistrationPolicyMigration() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
- .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
- createTestRaiseExeptionExecutorConfig(Arrays.asList(
- ClientPolicyEvent.REGISTERED, ClientPolicyEvent.UPDATED, ClientPolicyEvent.UNREGISTER)))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- String clientName = "ByAdmin-App" + KeycloakModelUtils.generateId().substring(0, 7);
- String clientId = null;
-
- try {
- createClientByAdmin(clientName, (ClientRepresentation clientRep) -> {
- });
- fail();
- } catch (ClientPolicyException cpe) {
- assertEquals(ClientPolicyEvent.REGISTERED.toString(), cpe.getError());
- }
-
- clientId = getClientByAdminWithName(clientName).getId();
- assertEquals(true, getClientByAdmin(clientId).isEnabled());
- try {
- updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setEnabled(false);
- });
- fail();
- } catch (ClientPolicyException cpe) {
- assertEquals(ClientPolicyEvent.UPDATED.toString(), cpe.getError());
- }
- assertEquals(false, getClientByAdmin(clientId).isEnabled());
-
- try {
- deleteClientByAdmin(clientId);
- fail();
- } catch (ClientPolicyException cpe) {
- assertEquals(ClientPolicyEvent.UNREGISTER.toString(), cpe.getError());
- }
-
- // TODO : For dynamic client registration, the existing test scheme can not distinguish when the exception happens on which event so that the migrated client policy executors test them afterwards.
- }
-
- @Test
- public void testUpdatePolicyWithoutNameNotAllowed() throws Exception {
- // register policies
- String json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(null, "La Premiere Politique", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- try {
- updatePolicies(json);
- fail();
- } catch (ClientPolicyException cpe) {
- assertEquals("update policies failed", cpe.getError());
- }
- }
-
- @Test
- public void testConfidentialClientAcceptExecutorExecutor() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Erstes Profil")
- .addExecutor(ConfidentialClientAcceptExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Erstes Politik", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- String clientConfidentialId = generateSuffixedName("confidential-app");
- String clientConfidentialSecret = "app-secret";
- String cidConfidential = createClientByAdmin(clientConfidentialId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientConfidentialSecret);
- clientRep.setStandardFlowEnabled(Boolean.TRUE);
- clientRep.setImplicitFlowEnabled(Boolean.TRUE);
- clientRep.setPublicClient(Boolean.FALSE);
- clientRep.setBearerOnly(Boolean.FALSE);
- });
- adminClient.realm(REALM_NAME).clients().get(cidConfidential).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build());
-
- successfulLoginAndLogout(clientConfidentialId, clientConfidentialSecret);
-
- String clientPublicId = generateSuffixedName("public-app");
- String cidPublic = createClientByAdmin(clientPublicId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientConfidentialSecret);
- clientRep.setStandardFlowEnabled(Boolean.TRUE);
- clientRep.setImplicitFlowEnabled(Boolean.TRUE);
- clientRep.setPublicClient(Boolean.TRUE);
- clientRep.setBearerOnly(Boolean.FALSE);
- });
- adminClient.realm(REALM_NAME).clients().get(cidPublic).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build());
-
- oauth.clientId(clientPublicId);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_CLIENT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals("invalid client access type", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
- }
-
- @Test
- public void testConsentRequiredExecutorExecutor() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Test Profile")
- .addExecutor(ConsentRequiredExecutorFactory.PROVIDER_ID, createConsentRequiredExecutorConfig(true))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Test Policy", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID,
- createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // Client will be auto-configured to enable consentRequired
- String clientId = generateSuffixedName("aaa-app");
- String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setImplicitFlowEnabled(Boolean.FALSE);
- clientRep.setConsentRequired(Boolean.FALSE);
- });
- ClientRepresentation clientRep = getClientByAdmin(cid);
- assertEquals(Boolean.TRUE, clientRep.isConsentRequired());
-
- // Client cannot be updated to disable consentRequired
- updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
- cRep.setConsentRequired(Boolean.FALSE);
- });
- clientRep = getClientByAdmin(cid);
- assertEquals(Boolean.TRUE, clientRep.isConsentRequired());
-
- // Switch auto-configure to false. Auto-configuration won't happen, but validation will still be here, so should not be possible to disable consentRequired
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Test Profile")
- .addExecutor(ConsentRequiredExecutorFactory.PROVIDER_ID, createConsentRequiredExecutorConfig(false))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // Not possible to register client with consentRequired due the validation
- try {
- createClientByAdmin(clientId, (ClientRepresentation clientRep2) -> {
- clientRep2.setConsentRequired(Boolean.FALSE);
- });
- fail();
- } catch (ClientPolicyException cpe) {
- assertEquals(Errors.INVALID_REGISTRATION, cpe.getError());
- }
-
- // Not possible to update existing client to consentRequired due the validation
- try {
- updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
- cRep.setConsentRequired(Boolean.FALSE);
- });
- fail();
- } catch (ClientPolicyException cpe) {
- assertEquals(Errors.INVALID_REGISTRATION, cpe.getError());
- }
- clientRep = getClientByAdmin(cid);
- assertEquals(Boolean.TRUE, clientRep.isConsentRequired());
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
- cRep.setImplicitFlowEnabled(Boolean.TRUE);
- });
- clientRep = getClientByAdmin(cid);
- assertEquals(Boolean.TRUE, clientRep.isImplicitFlowEnabled());
- assertEquals(Boolean.TRUE, clientRep.isConsentRequired());
- } catch (ClientPolicyException cpe) {
- fail();
- }
- }
-
- @Test
- public void testFullScopeDisabledExecutor() throws Exception {
- // register profiles - client autoConfigured to disable fullScopeAllowed
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Test Profile")
- .addExecutor(FullScopeDisabledExecutorFactory.PROVIDER_ID, createFullScopeDisabledExecutorConfig(true))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Test Policy", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID,
- createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // Client will be auto-configured to disable fullScopeAllowed
- String clientId = generateSuffixedName("aaa-app");
- String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setImplicitFlowEnabled(Boolean.FALSE);
- clientRep.setFullScopeAllowed(Boolean.TRUE);
- });
- ClientRepresentation clientRep = getClientByAdmin(cid);
- assertEquals(Boolean.FALSE, clientRep.isFullScopeAllowed());
-
- // Client cannot be updated to disable fullScopeAllowed
- updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
- cRep.setFullScopeAllowed(Boolean.TRUE);
- });
- clientRep = getClientByAdmin(cid);
- assertEquals(Boolean.FALSE, clientRep.isFullScopeAllowed());
-
- // Switch auto-configure to false. Auto-configuration won't happen, but validation will still be here, so should not be possible to enable fullScopeAllowed
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Test Profile")
- .addExecutor(FullScopeDisabledExecutorFactory.PROVIDER_ID, createFullScopeDisabledExecutorConfig(false))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // Not possible to register client with fullScopeAllowed due the validation
- try {
- createClientByAdmin(clientId, (ClientRepresentation clientRep2) -> {
- clientRep2.setFullScopeAllowed(Boolean.TRUE);
- });
- fail();
- } catch (ClientPolicyException cpe) {
- assertEquals(Errors.INVALID_REGISTRATION, cpe.getError());
- }
-
- // Not possible to update existing client to fullScopeAllowed due the validation
- try {
- updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
- cRep.setFullScopeAllowed(Boolean.TRUE);
- });
- fail();
- } catch (ClientPolicyException cpe) {
- assertEquals(Errors.INVALID_REGISTRATION, cpe.getError());
- }
- clientRep = getClientByAdmin(cid);
- assertEquals(Boolean.FALSE, clientRep.isFullScopeAllowed());
-
- try {
- updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
- cRep.setImplicitFlowEnabled(Boolean.TRUE);
- });
- clientRep = getClientByAdmin(cid);
- assertEquals(Boolean.TRUE, clientRep.isImplicitFlowEnabled());
- assertEquals(Boolean.FALSE, clientRep.isFullScopeAllowed());
- } catch (ClientPolicyException cpe) {
- fail();
- }
- }
-
- @Test
- public void testExtendedClientPolicyIntefacesForDeviceAuthorizationRequest() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
- .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
- createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.DEVICE_AUTHORIZATION_REQUEST)))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // Device Authorization Request from device
- oauth.realm(REALM_NAME);
- oauth.clientId(DEVICE_APP);
- OAuthClient.DeviceAuthorizationResponse response = oauth.doDeviceAuthorizationRequest(DEVICE_APP, "secret");
- assertEquals(400, response.getStatusCode());
- assertEquals(ClientPolicyEvent.DEVICE_AUTHORIZATION_REQUEST.toString(), response.getError());
- assertEquals("Exception thrown intentionally", response.getErrorDescription());
- }
-
- @Test
- public void testExtendedClientPolicyIntefacesForDeviceTokenRequest() throws Exception {
- // Device Authorization Request from device
- oauth.realm(REALM_NAME);
- oauth.clientId(DEVICE_APP);
- OAuthClient.DeviceAuthorizationResponse response = oauth.doDeviceAuthorizationRequest(DEVICE_APP, "secret");
-
- Assert.assertEquals(200, response.getStatusCode());
- assertNotNull(response.getDeviceCode());
- assertNotNull(response.getUserCode());
- assertNotNull(response.getVerificationUri());
- assertNotNull(response.getVerificationUriComplete());
-
- // Verify user code from verification page using browser
- openVerificationPage(response.getVerificationUri());
- verificationPage.assertCurrent();
- verificationPage.submit(response.getUserCode());
-
- loginPage.assertCurrent();
-
- // Do Login
- oauth.fillLoginForm("device-login", "password");
-
- // Consent
- grantPage.assertCurrent();
- grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.ROLES_CONSENT_TEXT);
- grantPage.accept();
-
- verificationPage.assertApprovedPage();
-
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
- .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
- createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.DEVICE_TOKEN_REQUEST)))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // Token request from device
- OAuthClient.AccessTokenResponse tokenResponse = oauth.doDeviceTokenRequest(DEVICE_APP, "secret", response.getDeviceCode());
- assertEquals(400, tokenResponse.getStatusCode());
- assertEquals(OAuthErrorException.INVALID_GRANT, tokenResponse.getError());
- assertEquals("Exception thrown intentionally", tokenResponse.getErrorDescription());
- }
-
- @Test
- public void testExtendedClientPolicyIntefacesForDeviceTokenResponse() throws Exception {
- // Device Authorization Request from device
- oauth.realm(REALM_NAME);
- oauth.clientId(DEVICE_APP);
- OAuthClient.DeviceAuthorizationResponse response = oauth.doDeviceAuthorizationRequest(DEVICE_APP, "secret");
-
- Assert.assertEquals(200, response.getStatusCode());
- assertNotNull(response.getDeviceCode());
- assertNotNull(response.getUserCode());
- assertNotNull(response.getVerificationUri());
- assertNotNull(response.getVerificationUriComplete());
-
- // Verify user code from verification page using browser
- openVerificationPage(response.getVerificationUri());
- verificationPage.assertCurrent();
- verificationPage.submit(response.getUserCode());
-
- loginPage.assertCurrent();
-
- // Do Login
- oauth.fillLoginForm("device-login", "password");
-
- // Consent
- grantPage.assertCurrent();
- grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.ROLES_CONSENT_TEXT);
- grantPage.accept();
-
- verificationPage.assertApprovedPage();
-
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
- .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
- createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.DEVICE_TOKEN_RESPONSE)))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // Token request from device
- OAuthClient.AccessTokenResponse tokenResponse = oauth.doDeviceTokenRequest(DEVICE_APP, "secret", response.getDeviceCode());
- assertEquals(400, tokenResponse.getStatusCode());
- assertEquals(ClientPolicyEvent.DEVICE_TOKEN_RESPONSE.toString(), tokenResponse.getError());
- assertEquals("Exception thrown intentionally", tokenResponse.getErrorDescription());
- }
-
- @Test
- public void testExtendedClientPolicyIntefacesForTokenResponse() throws Exception {
- // register a confidential client
- String clientId = generateSuffixedName(CLIENT_NAME);
- String clientSecret = "secret";
- createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- clientRep.setPublicClient(Boolean.FALSE);
- clientRep.setBearerOnly(Boolean.FALSE);
- });
-
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
- .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
- createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.TOKEN_RESPONSE)))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Primera Plitica", Boolean.TRUE)
- .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID,
- createClientAccessTypeConditionConfig(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- oauth.clientId(clientId);
- oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
-
- events.expectLogin().client(clientId).assertEvent();
- String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
- OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, clientSecret);
- assertEquals(400, response.getStatusCode());
- assertEquals(ClientPolicyEvent.TOKEN_RESPONSE.toString(), response.getError());
- assertEquals("Exception thrown intentionally", response.getErrorDescription());
- }
-
- @Test
- public void testExtendedClientPolicyIntefacesForTokenRefreshResponse() throws Exception {
- 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.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();
-
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil")
- .addExecutor(SuppressRefreshTokenRotationExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- String refreshTokenString = res.getRefreshToken();
- OAuthClient.AccessTokenResponse accessTokenResponseRefreshed = oauth.doRefreshTokenRequest(refreshTokenString, clientSecret);
- assertEquals(200, accessTokenResponseRefreshed.getStatusCode());
- assertEquals(null, accessTokenResponseRefreshed.getRefreshToken());
-
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList("other" + SAMPLE_CLIENT_ROLE)))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- accessTokenResponseRefreshed = oauth.doRefreshTokenRequest(refreshTokenString, clientSecret);
- assertEquals(200, accessTokenResponseRefreshed.getStatusCode());
- RefreshToken refreshedRefreshToken = oauth.parseRefreshToken(accessTokenResponseRefreshed.getRefreshToken());
- assertEquals(sessionId, refreshedRefreshToken.getSessionState());
- assertEquals(sessionId, refreshedRefreshToken.getSessionState());
- assertEquals(findUserByUsername(adminClient.realm(REALM_NAME), TEST_USER_NAME).getId(), refreshedRefreshToken.getSubject());
- }
-
- @Test
- public void testExtendedClientPolicyIntefacesForServiceAccountTokenRequeponse() throws Exception {
- String clientId = "service-account-app";
- String clientSecret = "app-secret";
- createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- clientRep.setStandardFlowEnabled(Boolean.FALSE);
- clientRep.setImplicitFlowEnabled(Boolean.FALSE);
- clientRep.setServiceAccountsEnabled(Boolean.TRUE);
- clientRep.setPublicClient(Boolean.FALSE);
- clientRep.setBearerOnly(Boolean.FALSE);
- });
-
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
- .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
- createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.SERVICE_ACCOUNT_TOKEN_RESPONSE)))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Het Eerste Beleid", Boolean.TRUE)
- .addCondition(ClientScopesConditionFactory.PROVIDER_ID,
- createClientScopesConditionConfig(ClientScopesConditionFactory.OPTIONAL, Arrays.asList("offline_access", "microprofile-jwt")))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
-
- String origClientId = oauth.getClientId();
- oauth.clientId("service-account-app");
- oauth.scope("offline_access");
- try {
- OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest("app-secret");
- assertEquals(400, response.getStatusCode());
- assertEquals(ClientPolicyEvent.SERVICE_ACCOUNT_TOKEN_RESPONSE.toString(), response.getError());
- assertEquals("Exception thrown intentionally", response.getErrorDescription());
- } finally {
- oauth.clientId(origClientId);
- }
- }
-
- @Test
- public void testExtendedClientPolicyIntefacesForResourceOwnerPasswordCredentialsResponse() throws Exception {
-
- String clientId = generateSuffixedName(CLIENT_NAME);
- String clientSecret = "secret";
-
- createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- clientRep.setStandardFlowEnabled(Boolean.TRUE);
- clientRep.setDirectAccessGrantsEnabled(Boolean.TRUE);
- clientRep.setPublicClient(Boolean.FALSE);
- });
-
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
- .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
- createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.RESOURCE_OWNER_PASSWORD_CREDENTIALS_RESPONSE)))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Porisii desu", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID,
- createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- oauth.clientId(clientId);
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(clientSecret, TEST_USER_NAME, TEST_USER_PASSWORD, null);
-
- assertEquals(400, response.getStatusCode());
- assertEquals(ClientPolicyEvent.RESOURCE_OWNER_PASSWORD_CREDENTIALS_RESPONSE.toString(), response.getError());
- assertEquals("Exception thrown intentionally", response.getErrorDescription());
- }
-
- @Test
- public void testSecureLogoutExecutor() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Logout Test")
- .addExecutor(SecureLogoutExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Logout Policy", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID,
- createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- String clientId = generateSuffixedName(CLIENT_NAME);
- String clientSecret = "secret";
- try {
- createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- clientRep.setStandardFlowEnabled(Boolean.TRUE);
- clientRep.setImplicitFlowEnabled(Boolean.TRUE);
- clientRep.setPublicClient(Boolean.FALSE);
- clientRep.setFrontchannelLogout(true);
- });
- } catch (ClientPolicyException cpe) {
- assertEquals("Front-channel logout is not allowed for this client", cpe.getErrorDetail());
- }
-
- String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- clientRep.setStandardFlowEnabled(Boolean.TRUE);
- clientRep.setImplicitFlowEnabled(Boolean.TRUE);
- clientRep.setPublicClient(Boolean.FALSE);
- });
-
- ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(cid);
- ClientRepresentation clientRep = clientResource.toRepresentation();
-
- clientRep.setFrontchannelLogout(true);
-
- try {
- clientResource.update(clientRep);
- } catch (BadRequestException bre) {
- assertEquals("Front-channel logout is not allowed for this client", bre.getResponse().readEntity(OAuth2ErrorRepresentation.class).getErrorDescription());
- }
-
- ClientPolicyExecutorConfigurationRepresentation config = new ClientPolicyExecutorConfigurationRepresentation();
-
- config.setConfigAsMap(SecureLogoutExecutorFactory.ALLOW_FRONT_CHANNEL_LOGOUT, Boolean.TRUE.booleanValue());
-
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Logout Test")
- .addExecutor(SecureLogoutExecutorFactory.PROVIDER_ID, config)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setFrontChannelLogoutUrl(oauth.getRedirectUri());
- clientResource.update(clientRep);
-
- config.setConfigAsMap(SecureLogoutExecutorFactory.ALLOW_FRONT_CHANNEL_LOGOUT, Boolean.FALSE.toString());
-
- json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Logout Test")
- .addExecutor(SecureLogoutExecutorFactory.PROVIDER_ID, config)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- OAuthClient.AccessTokenResponse response = successfulLogin(clientId, clientSecret);
-
- oauth.idTokenHint(response.getIdToken()).openLogout();
-
- assertTrue(driver.getPageSource().contains("Front-channel logout is not allowed for this client"));
- }
-
- @Test
- public void testRejectResourceOwnerCredentialsGrantExecutor() throws Exception {
-
- String clientId = generateSuffixedName(CLIENT_NAME);
- String clientSecret = "secret";
-
- createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- clientRep.setStandardFlowEnabled(Boolean.TRUE);
- clientRep.setDirectAccessGrantsEnabled(Boolean.TRUE);
- clientRep.setPublicClient(Boolean.FALSE);
- });
-
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Purofairu desu")
- .addExecutor(RejectResourceOwnerPasswordCredentialsGrantExecutorFactory.PROVIDER_ID,
- createRejectisResourceOwnerPasswordCredentialsGrantExecutorConfig(Boolean.TRUE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Porisii desu", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID,
- createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- oauth.clientId(clientId);
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(clientSecret, TEST_USER_NAME, TEST_USER_PASSWORD, null);
-
- assertEquals(400, response.getStatusCode());
- assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
- assertEquals("resource owner password credentials grant is prohibited.", response.getErrorDescription());
-
- }
-
- @Test
- public void testRejectRequestExecutor() throws Exception {
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil")
- .addExecutor(RejectRequestExecutorFactory.PROVIDER_ID, null)
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- String clientBetaId = generateSuffixedName("Beta-App");
- createClientByAdmin(clientBetaId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret("secretBeta");
- });
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID,
- createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- try {
- oauth.clientId(clientBetaId);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- assertEquals(ERR_MSG_REQ_NOT_ALLOWED, oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
- revertToBuiltinProfiles();
- successfulLoginAndLogout(clientBetaId, "secretBeta");
- } catch (Exception e) {
- fail();
- }
- }
-
- /**
- * When creating a dynamic client the secret expiration date must be defined
- *
- * @throws Exception
- */
- @Test
- public void whenCreateDynamicClientSecretExpirationDateMustExist() throws Exception {
-
- //enable policy
- configureCustomProfileAndPolicy(60, 30, 20);
-
- String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {
- });
- OIDCClientRepresentation response = getClientDynamically(clientId);
- assertThat(response.getClientSecret(), notNullValue());
- assertThat(response.getClientSecretExpiresAt().intValue(), greaterThan(0));
-
- }
-
- /**
- * When update a dynamic client the secret expiration date must be defined and the rotation process must obey the policy configuration
- *
- * @throws Exception
- */
- @Test
- public void whenUpdateDynamicClientRotationMustFollowConfiguration() throws Exception {
-
- //enable policy
- configureCustomProfileAndPolicy(60, 30, 20);
-
- String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {
- });
- OIDCClientRepresentation response = getClientDynamically(clientId);
-
- String firstSecret = response.getClientSecret();
- Integer firstSecretExpiration = response.getClientSecretExpiresAt();
-
- updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> {
- clientRep.setContacts(Collections.singletonList("keycloak@keycloak.org"));
- });
-
- OIDCClientRepresentation updated = getClientDynamically(clientId);
-
- //secret rotation must NOT occur
- assertThat(updated.getClientSecret(), equalTo(firstSecret));
- assertThat(updated.getClientSecretExpiresAt(), equalTo(firstSecretExpiration));
-
- //force secret expiration
- setTimeOffset(61);
-
- updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> {
- clientRep.setClientName(generateSuffixedName(CLIENT_NAME));
- });
-
- updated = getClientDynamically(clientId);
- String updatedSecret = updated.getClientSecret();
-
- //secret rotation must occur
- assertThat(updatedSecret, not(equalTo(firstSecret)));
- assertThat(updated.getClientSecretExpiresAt(), not(equalTo(firstSecretExpiration)));
-
- //login with updated secret
- assertLoginAndLogoutStatus(clientId, updatedSecret, Response.Status.OK);
-
- //login with rotated secret
- assertLoginAndLogoutStatus(clientId, firstSecret, Response.Status.OK);
-
- //force rotated secret expiration
- setTimeOffset(100);
-
- //login with updated secret (remains valid)
- assertLoginAndLogoutStatus(clientId, updatedSecret, Response.Status.OK);
-
- //try to log in with rotated secret (must fail)
- assertLoginAndLogoutStatus(clientId, firstSecret, Response.Status.UNAUTHORIZED);
-
- }
-
- /**
- * When updating a dynamic client within the "time remaining to expiration" period the client secret must be rotated and
- * the new secret must be sent along with the new expiration date.
- * Even though the client secret is still valid, the time remaining setting should force rotation
- *
- * @throws Exception
- */
- @Test
- public void whenUpdateDynamicClientDuringRemainingExpirationPeriodMustRotateSecret() throws Exception {
-
- //enable policy
- configureCustomProfileAndPolicy(60, 30, 20);
-
- String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {
- });
- OIDCClientRepresentation response = getClientDynamically(clientId);
-
- String firstSecret = response.getClientSecret();
- Integer firstSecretExpiration = response.getClientSecretExpiresAt();
-
- assertThat(firstSecretExpiration, is(greaterThan(Time.currentTime())));
-
- //Enter in Remaining expiration window
- setTimeOffset(41);
-
- //update client to force rotation (due to remaining expiration)
- updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> {
- clientRep.setContacts(Collections.singletonList("keycloak@keycloak.org"));
- });
-
- OIDCClientRepresentation updated = getClientDynamically(clientId);
-
- //secret rotation must occur
- assertThat(updated.getClientSecret(), not(equalTo(firstSecret)));
- assertThat(updated.getClientSecretExpiresAt(), not(equalTo(firstSecretExpiration)));
-
- }
-
- @Test
- public void testIntentClientBindCheck() throws Exception {
- final String intentName = "openbanking_intent_id";
-
- // register profiles
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Het Eerste Profiel")
- .addExecutor(IntentClientBindCheckExecutorFactory.PROVIDER_ID,
- createIntentClientBindCheckExecutorConfig(intentName, TestApplicationResourceUrls.checkIntentClientBoundUri()))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Het Eerste Beleid", Boolean.TRUE)
- .addCondition(ClientScopesConditionFactory.PROVIDER_ID,
- createClientScopesConditionConfig(ClientScopesConditionFactory.OPTIONAL, Arrays.asList("microprofile-jwt")))
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- // create a client
- String clientId = generateSuffixedName(CLIENT_NAME);
- String clientSecret = "secret";
- createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
- clientRep.setSecret(clientSecret);
- clientRep.setStandardFlowEnabled(Boolean.TRUE);
- clientRep.setImplicitFlowEnabled(Boolean.TRUE);
- });
- ClientResource app = findClientResourceByClientId(adminClient.realm("test"), clientId);
- ProtocolMappersResource res = app.getProtocolMappers();
- res.createMapper(ModelToRepresentation.toRepresentation(ClaimsParameterWithValueIdTokenMapper.createMapper("claimsParameterWithValueIdTokenMapper", "openbanking_intent_id", true))).close();
-
- // register a binding of an intent with different client
- String intentId = "123abc456xyz";
- String differentClientId = "test-app";
- Response r = testingClient.testApp().oidcClientEndpoints().bindIntentWithClient(intentId, differentClientId);
- assertEquals(204, r.getStatus());
-
- // create a request object with claims
- String nonce = "naodfejawi37d";
-
- ClaimsRepresentation claimsRep = new ClaimsRepresentation();
- ClaimsRepresentation.ClaimValue claimValue = new ClaimsRepresentation.ClaimValue<>();
- claimValue.setEssential(Boolean.TRUE);
- claimValue.setValue(intentId);
- claimsRep.setIdTokenClaims(Collections.singletonMap(intentName, claimValue));
-
- Map oidcRequest = new HashMap<>();
- oidcRequest.put(OIDCLoginProtocol.CLIENT_ID_PARAM, clientId);
- oidcRequest.put(OIDCLoginProtocol.NONCE_PARAM, nonce);
- oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN);
- oidcRequest.put(OIDCLoginProtocol.REDIRECT_URI_PARAM, oauth.getRedirectUri());
- oidcRequest.put(OIDCLoginProtocol.CLAIMS_PARAM, claimsRep);
- oidcRequest.put(OIDCLoginProtocol.SCOPE_PARAM, "openid" + " " + "microprofile-jwt");
- String request = new JWSBuilder().jsonContent(oidcRequest).none();
-
- // send an authorization request
- oauth.scope("openid" + " " + "microprofile-jwt");
- oauth.request(request);
- oauth.clientId(clientId);
- oauth.nonce(nonce);
- oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentFragment().get(OAuth2Constants.ERROR));
- assertEquals("The intent is not bound with the client", oauth.getCurrentFragment().get(OAuth2Constants.ERROR_DESCRIPTION));
-
- // register a binding of an intent with a valid client
- r = testingClient.testApp().oidcClientEndpoints().bindIntentWithClient(intentId, clientId);
- assertEquals(204, r.getStatus());
-
- // send an authorization request
- oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
-
- // check an authorization response
- EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent();
- String sessionId = loginEvent.getSessionId();
- String codeId = loginEvent.getDetails().get(Details.CODE_ID);
- String code = oauth.getCurrentFragment().get(OAuth2Constants.CODE);
- OAuthClient.AuthorizationEndpointResponse authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth, true);
- JWSInput idToken = new JWSInput(authzResponse.getIdToken());
- ObjectMapper mapper = JsonSerialization.mapper;
- JsonParser parser = mapper.getFactory().createParser(idToken.readContentAsString());
- TreeNode treeNode = mapper.readTree(parser);
- String clientBoundIntentId = ((TextNode) treeNode.get(intentName)).asText();
- assertEquals(intentId, clientBoundIntentId);
-
- // send a token request
- OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, clientSecret);
-
- // check a token response
- assertEquals(200, response.getStatusCode());
- events.expectCodeToToken(codeId, sessionId).client(clientId).assertEvent();
- idToken = new JWSInput(response.getIdToken());
- mapper = JsonSerialization.mapper;
- parser = mapper.getFactory().createParser(idToken.readContentAsString());
- treeNode = mapper.readTree(parser);
- clientBoundIntentId = ((TextNode) treeNode.get(intentName)).asText();
- assertEquals(intentId, clientBoundIntentId);
-
- // logout
- oauth.doLogout(response.getRefreshToken(), clientSecret);
- events.expectLogout(response.getSessionState()).client(clientId).clearDetails().assertEvent();
-
- // create a request object with invalid claims
- claimsRep = new ClaimsRepresentation();
- claimValue = new ClaimsRepresentation.ClaimValue<>();
- claimValue.setEssential(Boolean.TRUE);
- claimValue.setValue(intentId);
- claimsRep.setIdTokenClaims(Collections.singletonMap("other_intent_id", claimValue));
- oidcRequest.put(OIDCLoginProtocol.CLAIMS_PARAM, claimsRep);
- request = new JWSBuilder().jsonContent(oidcRequest).none();
-
- // send an authorization request
- oauth.request(request);
- oauth.openLoginForm();
- assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentFragment().get(OAuth2Constants.ERROR));
- assertEquals("no claim for an intent value for ID token" , oauth.getCurrentFragment().get(OAuth2Constants.ERROR_DESCRIPTION));
- }
-
- @Test
- public void testRegistrationAccessTokenRotationDisabledExecutor() throws Exception {
- // register profiles - client autoConfigured to disable registration access token rotation
- String json = new ClientProfilesBuilder().addProfile(
- new ClientProfileBuilder().createProfile(PROFILE_NAME, "Test Profile")
- .addExecutor(
- RegistrationAccessTokenRotationDisabledExecutorFactory.PROVIDER_ID,
- new ClientPolicyExecutorConfigurationRepresentation()
- )
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = new ClientPoliciesBuilder().addPolicy(
- new ClientPolicyBuilder().createPolicy(POLICY_NAME, "Test Policy", Boolean.TRUE)
- .addCondition(AnyClientConditionFactory.PROVIDER_ID,
- createAnyClientConditionConfig())
- .addProfile(PROFILE_NAME)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
-
- String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), r -> {});
- OIDCClientRepresentation createdClient = getClientDynamically(clientId);
-
- updateClientDynamically(clientId, clientRep ->
- clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.CLIENT_SECRET_BASIC));
-
- assertEquals(createdClient.getRegistrationAccessToken(), getClientDynamically(clientId).getRegistrationAccessToken());
- }
-
- private void openVerificationPage(String verificationUri) {
- driver.navigate().to(verificationUri);
- }
-
- 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.
- driver.navigate().to(oauth.getLogoutUrl().build());
- logoutConfirmPage.assertCurrent();
- logoutConfirmPage.confirmLogout();
- 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(400, accessTokenResponseRefreshed.getStatusCode());
- assertEquals(OAuthErrorException.INVALID_GRANT, 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) throws Exception {
- // register profiles
- String profileName = "MyProfile";
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(profileName, "Primum Profile")
- .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
- createSecureClientAuthenticatorExecutorConfig(
- Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
- null))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(policyName, "Primum Consilium", Boolean.TRUE)
- .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
- createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER)))
- .addProfile(profileName)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
- }
-
- private void setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(String policyName) throws Exception {
- // register profiles
- String profileName = "MyProfile";
- String json = (new ClientProfilesBuilder()).addProfile(
- (new ClientProfileBuilder()).createProfile(profileName, "Primul Profil")
- .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
- createSecureClientAuthenticatorExecutorConfig(
- Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID, JWTClientAuthenticator.PROVIDER_ID),
- ClientIdAndSecretAuthenticator.PROVIDER_ID))
- .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
- createPKCEEnforceExecutorConfig(Boolean.TRUE))
- .toRepresentation()
- ).toString();
- updateProfiles(json);
-
- // register policies
- json = (new ClientPoliciesBuilder()).addPolicy(
- (new ClientPolicyBuilder()).createPolicy(policyName, "Prima Politica", Boolean.TRUE)
- .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
- createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
- .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
- createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_INITIAL_ACCESS_TOKEN)))
- .addProfile(profileName)
- .toRepresentation()
- ).toString();
- updatePolicies(json);
- }
-
- private void successfulLoginAndLogout(String clientId, String clientSecret) {
- OAuthClient.AccessTokenResponse res = successfulLogin(clientId, clientSecret);
- oauth.doLogout(res.getRefreshToken(), clientSecret);
- events.expectLogout(res.getSessionState()).client(clientId).clearDetails().assertEvent();
- }
-
- private OAuthClient.AccessTokenResponse successfulLogin(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();
-
- return res;
- }
-
- 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.idTokenHint(res.getIdToken()).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));
- }
-
- private void doConfigProfileAndPolicy(ClientPoliciesUtil.ClientProfileBuilder profileBuilder,
- ClientSecretRotationExecutor.Configuration profileConfig) throws Exception {
- String json = (new ClientPoliciesUtil.ClientProfilesBuilder()).addProfile(
- profileBuilder.createProfile(SECRET_ROTATION_PROFILE, "Enable Client Secret Rotation")
- .addExecutor(ClientSecretRotationExecutorFactory.PROVIDER_ID, profileConfig)
- .toRepresentation()).toString();
- updateProfiles(json);
-
- // register policies
- ClientAccessTypeCondition.Configuration config = new ClientAccessTypeCondition.Configuration();
- config.setType(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL));
- json = (new ClientPoliciesUtil.ClientPoliciesBuilder()).addPolicy(
- (new ClientPoliciesUtil.ClientPolicyBuilder()).createPolicy(SECRET_ROTATION_POLICY,
- "Policy for Client Secret Rotation",
- Boolean.TRUE).addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, config)
- .addProfile(SECRET_ROTATION_PROFILE).toRepresentation()).toString();
- updatePolicies(json);
- }
-
- private void configureCustomProfileAndPolicy(int secretExpiration, int rotatedExpiration,
- int remainingExpiration) throws Exception {
- ClientPoliciesUtil.ClientProfileBuilder profileBuilder = new ClientPoliciesUtil.ClientProfileBuilder();
- ClientSecretRotationExecutor.Configuration profileConfig = getClientProfileConfiguration(
- secretExpiration, rotatedExpiration, remainingExpiration);
-
- doConfigProfileAndPolicy(profileBuilder, profileConfig);
- }
-
- @NotNull
- private ClientSecretRotationExecutor.Configuration getClientProfileConfiguration(
- int expirationPeriod, int rotatedExpirationPeriod, int remainExpirationPeriod) {
- ClientSecretRotationExecutor.Configuration profileConfig = new ClientSecretRotationExecutor.Configuration();
- profileConfig.setExpirationPeriod(expirationPeriod);
- profileConfig.setRotatedExpirationPeriod(rotatedExpirationPeriod);
- profileConfig.setRemainExpirationPeriod(remainExpirationPeriod);
- return profileConfig;
- }
-
- private void assertLoginAndLogoutStatus(String clientId, String secret, Response.Status status) {
- oauth.clientId(clientId);
- OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME,
- TEST_USER_PASSWORD);
- String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
- OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, secret);
- assertThat(res.getStatusCode(), equalTo(status.getStatusCode()));
- oauth.doLogout(res.getRefreshToken(), secret);
- }
-}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/FAPI1Test.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/FAPI1Test.java
index f196ffe70e..3f12bb4ad4 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/FAPI1Test.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/FAPI1Test.java
@@ -73,6 +73,7 @@ import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResou
import org.keycloak.testsuite.util.MutualTLSUtils;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.ServerURLs;
+import org.keycloak.testsuite.client.policies.AbstractClientPoliciesTest;
import java.security.KeyPair;
import java.security.PrivateKey;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/FAPICIBATest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/FAPICIBATest.java
index b695e76f75..a466822276 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/FAPICIBATest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/FAPICIBATest.java
@@ -99,6 +99,7 @@ import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPoliciesBuilder;
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder;
import org.keycloak.testsuite.util.OAuthClient.AuthenticationRequestAcknowledgement;
import org.keycloak.util.JsonSerialization;
+import org.keycloak.testsuite.client.policies.AbstractClientPoliciesTest;
/**
* Test for the FAPI CIBA specifications (still implementer's draft):
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/policies/AbstractClientPoliciesTest.java
similarity index 74%
rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java
rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/AbstractClientPoliciesTest.java
index 1bbaf3de6c..890ec315f5 100644
--- 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/policies/AbstractClientPoliciesTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * Copyright 2023 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");
@@ -15,7 +15,25 @@
* limitations under the License.
*/
-package org.keycloak.testsuite.client;
+package org.keycloak.testsuite.client.policies;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientAccessTypeConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientRolesConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientScopesConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateContextConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceGroupsConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceHostsConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceRolesConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createHolderOfKeyEnforceExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createPKCEEnforceExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureClientAuthenticatorExecutorConfig;
import java.io.IOException;
import java.net.URI;
@@ -37,6 +55,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
@@ -46,9 +65,6 @@ import java.util.stream.Collectors;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.core.Response;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
@@ -58,7 +74,9 @@ 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.arquillian.graphene.page.Page;
import org.jboss.logging.Logger;
+import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -66,7 +84,10 @@ 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;
@@ -81,13 +102,17 @@ import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.crypto.Algorithm;
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.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.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.ClientPoliciesRepresentation;
@@ -98,6 +123,7 @@ import org.keycloak.representations.idm.ClientPolicyRepresentation;
import org.keycloak.representations.idm.ClientProfileRepresentation;
import org.keycloak.representations.idm.ClientProfilesRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
import org.keycloak.services.Urls;
@@ -117,6 +143,8 @@ import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceHostsCond
import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceHostsConditionFactory;
import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceRolesCondition;
import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceRolesConditionFactory;
+import org.keycloak.services.clientpolicy.executor.ClientSecretRotationExecutor;
+import org.keycloak.services.clientpolicy.executor.ClientSecretRotationExecutorFactory;
import org.keycloak.services.clientpolicy.executor.ConsentRequiredExecutorFactory;
import org.keycloak.services.clientpolicy.executor.FullScopeDisabledExecutorFactory;
import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforcerExecutorFactory;
@@ -134,30 +162,25 @@ 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.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LogoutConfirmPage;
+import org.keycloak.testsuite.pages.OAuth2DeviceVerificationPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject;
+import org.keycloak.testsuite.util.ClientPoliciesUtil;
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPoliciesBuilder;
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder;
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder;
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder;
+import org.keycloak.testsuite.util.MutualTLSUtils;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.ServerURLs;
import org.keycloak.util.JsonSerialization;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientAccessTypeConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientRolesConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientScopesConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateContextConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceGroupsConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceHostsConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceRolesConditionConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createHolderOfKeyEnforceExecutorConfig;
-import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureClientAuthenticatorExecutorConfig;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Takashi Norimatsu
@@ -187,6 +210,29 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
private static final ObjectMapper objectMapper = new ObjectMapper();
+ protected static final String CLIENT_NAME = "Zahlungs-App";
+ protected static final String TEST_USER_NAME = "test-user@localhost";
+ protected static final String TEST_USER_PASSWORD = "password";
+
+ protected static final String DEVICE_APP = "test-device";
+ protected static final String DEVICE_APP_PUBLIC = "test-device-public";
+ protected static String userId;
+
+ protected static final String SECRET_ROTATION_PROFILE = "ClientSecretRotationProfile";
+ protected static final String SECRET_ROTATION_POLICY = "ClientSecretRotationPolicy";
+
+ @Page
+ protected OAuth2DeviceVerificationPage verificationPage;
+
+ @Page
+ protected OAuthGrantPage grantPage;
+
+ @Page
+ protected ErrorPage errorPage;
+
+ @Page
+ protected LogoutConfirmPage logoutConfirmPage;
+
@Rule
public AssertEvents events = new AssertEvents(this);
@@ -1207,4 +1253,363 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
Assert.assertTrue("Expected empty configuration for provider " + executorProviderId, config.isEmpty());
}
+ protected String signRequestObject(AuthorizationEndpointRequestObject requestObject) throws IOException {
+ byte[] contentBytes = JsonSerialization.writeValueAsBytes(requestObject);
+ String encodedRequestObject = Base64Url.encode(contentBytes);
+ TestOIDCEndpointsApplicationResource client = testingClient.testApp().oidcClientEndpoints();
+
+ // use and set jwks_url
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(oauth.getRealm()), oauth.getClientId());
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(TestApplicationResourceUrls.clientJwksUri());
+ clientResource.update(clientRep);
+ client.generateKeys(Algorithm.PS256);
+ client.registerOIDCRequest(encodedRequestObject, Algorithm.PS256);
+
+ // do not send any other parameter but the request request parameter
+ String oidcRequest = client.getOIDCRequest();
+ return oidcRequest;
+ }
+
+ protected List getAttributeMultivalued(ClientRepresentation clientRep, String attrKey) {
+ String attrValue = Optional.ofNullable(clientRep.getAttributes()).orElse(Collections.emptyMap()).get(attrKey);
+ if (attrValue == null) return Collections.emptyList();
+ return Arrays.asList(Constants.CFG_DELIMITER_PATTERN.split(attrValue));
+ }
+
+ protected void setAttributeMultivalued(ClientRepresentation clientRep, String attrKey, List attrValues) {
+ String attrValueFull = String.join(Constants.CFG_DELIMITER, attrValues);
+ clientRep.getAttributes().put(attrKey, attrValueFull);
+ }
+
+ protected void openVerificationPage(String verificationUri) {
+ driver.navigate().to(verificationUri);
+ }
+
+ protected 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.
+ driver.navigate().to(oauth.getLogoutUrl().build());
+ logoutConfirmPage.assertCurrent();
+ logoutConfirmPage.confirmLogout();
+ 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(400, accessTokenResponseRefreshed.getStatusCode());
+ assertEquals(OAuthErrorException.INVALID_GRANT, 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);
+ }
+ }
+
+ protected void setupPolicyClientIdAndSecretNotAcceptableAuthType(String policyName) throws Exception {
+ // register profiles
+ String profileName = "MyProfile";
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(profileName, "Primum Profile")
+ .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
+ createSecureClientAuthenticatorExecutorConfig(
+ Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
+ null))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(policyName, "Primum Consilium", Boolean.TRUE)
+ .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
+ createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER)))
+ .addProfile(profileName)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+ }
+
+ protected void setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(String policyName) throws Exception {
+ // register profiles
+ String profileName = "MyProfile";
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(profileName, "Primul Profil")
+ .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
+ createSecureClientAuthenticatorExecutorConfig(
+ Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID, JWTClientAuthenticator.PROVIDER_ID),
+ ClientIdAndSecretAuthenticator.PROVIDER_ID))
+ .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
+ createPKCEEnforceExecutorConfig(Boolean.TRUE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(policyName, "Prima Politica", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
+ .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
+ createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_INITIAL_ACCESS_TOKEN)))
+ .addProfile(profileName)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+ }
+
+ protected void successfulLoginAndLogout(String clientId, String clientSecret) {
+ OAuthClient.AccessTokenResponse res = successfulLogin(clientId, clientSecret);
+ oauth.doLogout(res.getRefreshToken(), clientSecret);
+ events.expectLogout(res.getSessionState()).client(clientId).clearDetails().assertEvent();
+ }
+
+ protected OAuthClient.AccessTokenResponse successfulLogin(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();
+
+ return res;
+ }
+
+ protected 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);
+ }
+
+ protected 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));
+ }
+
+ protected 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.idTokenHint(res.getIdToken()).openLogout();
+ events.expectLogout(sessionId).clearDetails().assertEvent();
+ }
+
+ protected 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));
+ }
+
+ protected 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));
+ }
+
+ protected void doConfigProfileAndPolicy(ClientPoliciesUtil.ClientProfileBuilder profileBuilder,
+ ClientSecretRotationExecutor.Configuration profileConfig) throws Exception {
+ String json = (new ClientPoliciesUtil.ClientProfilesBuilder()).addProfile(
+ profileBuilder.createProfile(SECRET_ROTATION_PROFILE, "Enable Client Secret Rotation")
+ .addExecutor(ClientSecretRotationExecutorFactory.PROVIDER_ID, profileConfig)
+ .toRepresentation()).toString();
+ updateProfiles(json);
+
+ // register policies
+ ClientAccessTypeCondition.Configuration config = new ClientAccessTypeCondition.Configuration();
+ config.setType(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL));
+ json = (new ClientPoliciesUtil.ClientPoliciesBuilder()).addPolicy(
+ (new ClientPoliciesUtil.ClientPolicyBuilder()).createPolicy(SECRET_ROTATION_POLICY,
+ "Policy for Client Secret Rotation",
+ Boolean.TRUE).addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, config)
+ .addProfile(SECRET_ROTATION_PROFILE).toRepresentation()).toString();
+ updatePolicies(json);
+ }
+
+ protected void configureCustomProfileAndPolicy(int secretExpiration, int rotatedExpiration,
+ int remainingExpiration) throws Exception {
+ ClientPoliciesUtil.ClientProfileBuilder profileBuilder = new ClientPoliciesUtil.ClientProfileBuilder();
+ ClientSecretRotationExecutor.Configuration profileConfig = getClientProfileConfiguration(
+ secretExpiration, rotatedExpiration, remainingExpiration);
+
+ doConfigProfileAndPolicy(profileBuilder, profileConfig);
+ }
+
+ @NotNull
+ protected ClientSecretRotationExecutor.Configuration getClientProfileConfiguration(
+ int expirationPeriod, int rotatedExpirationPeriod, int remainExpirationPeriod) {
+ ClientSecretRotationExecutor.Configuration profileConfig = new ClientSecretRotationExecutor.Configuration();
+ profileConfig.setExpirationPeriod(expirationPeriod);
+ profileConfig.setRotatedExpirationPeriod(rotatedExpirationPeriod);
+ profileConfig.setRemainExpirationPeriod(remainExpirationPeriod);
+ return profileConfig;
+ }
+
+ protected void assertLoginAndLogoutStatus(String clientId, String secret, Response.Status status) {
+ oauth.clientId(clientId);
+ OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME,
+ TEST_USER_PASSWORD);
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, secret);
+ assertThat(res.getStatusCode(), equalTo(status.getStatusCode()));
+ oauth.doLogout(res.getRefreshToken(), secret);
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesAdminTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesAdminTest.java
new file mode 100644
index 0000000000..090173b7b6
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesAdminTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2023 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.policies;
+
+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.util.ClientPoliciesUtil.createClientUpdateContextConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureClientAuthenticatorExecutorConfig;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Test;
+import org.keycloak.OAuthErrorException;
+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.common.Profile;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.Constants;
+import org.keycloak.models.OAuth2DeviceConfig;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCConfigAttributes;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.services.clientpolicy.ClientPolicyException;
+import org.keycloak.services.clientpolicy.condition.ClientUpdaterContextConditionFactory;
+import org.keycloak.services.clientpolicy.executor.SecureClientAuthenticatorExecutorFactory;
+import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LogoutConfirmPage;
+import org.keycloak.testsuite.pages.OAuth2DeviceVerificationPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPoliciesBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+
+/**
+ * This test class is for testing client policies' related actions done through an admin console, admin CLI, and admin REST API.
+ *
+ * @author Takashi Norimatsu
+ */
+@EnableFeature(value = Profile.Feature.CLIENT_SECRET_ROTATION)
+public class ClientPoliciesAdminTest extends AbstractClientPoliciesTest {
+
+ @Page
+ protected OAuth2DeviceVerificationPage verificationPage;
+
+ @Page
+ protected OAuthGrantPage grantPage;
+
+ @Page
+ protected ErrorPage errorPage;
+
+ @Page
+ protected LogoutConfirmPage logoutConfirmPage;
+
+ @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);
+
+ List clients = realm.getClients();
+
+ ClientRepresentation app = ClientBuilder.create()
+ .id(KeycloakModelUtils.generateId())
+ .clientId("test-device")
+ .secret("secret")
+ .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
+ .build();
+ clients.add(app);
+
+ ClientRepresentation appPublic = ClientBuilder.create().id(KeycloakModelUtils.generateId()).publicClient()
+ .clientId(DEVICE_APP_PUBLIC)
+ .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
+ .build();
+ clients.add(appPublic);
+
+ userId = KeycloakModelUtils.generateId();
+ UserRepresentation deviceUser = UserBuilder.create()
+ .id(userId)
+ .username("device-login")
+ .email("device-login@localhost")
+ .password("password")
+ .build();
+ users.add(deviceUser);
+
+ testRealms.add(realm);
+ }
+
+ @Test
+ public void testAdminClientRegisterUnacceptableAuthType() throws Exception {
+ setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME);
+ try {
+ createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
+ clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID);
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
+ }
+ }
+
+ @Test
+ public void testAdminClientRegisterAcceptableAuthType() throws Exception {
+ 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() throws Exception {
+ setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME);
+ try {
+ createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
+ }
+ }
+
+ @Test
+ public void testAdminClientUpdateUnacceptableAuthType() throws Exception {
+ 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 (ClientPolicyException cpe) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, cpe.getError());
+ }
+ assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType());
+ }
+
+ @Test
+ public void testAdminClientUpdateAcceptableAuthType() throws Exception {
+ 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 Exception {
+ 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 testAdminClientAutoConfiguredClientAuthType() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Pershyy Profil")
+ .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
+ createSecureClientAuthenticatorExecutorConfig(
+ Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
+ X509ClientAuthenticator.PROVIDER_ID))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Persha Polityka", Boolean.TRUE)
+ .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
+ createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // Attempt to create client with set authenticator to ClientIdAndSecretAuthenticator. Should fail
+ try {
+ createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
+ clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID);
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
+ }
+
+ // Attempt to create client without set authenticator. Default authenticator should be set
+ String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
+ });
+
+ assertEquals(X509ClientAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType());
+
+ // update profiles
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Pershyy Profil")
+ .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
+ createSecureClientAuthenticatorExecutorConfig(
+ Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
+ JWTClientAuthenticator.PROVIDER_ID))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // It is allowed to update authenticator to one of allowed client authenticators. Default client authenticator is not explicitly set in this case
+ updateClientByAdmin(cId, (ClientRepresentation clientRep) -> {
+ clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
+ });
+ assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType());
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesConditionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesConditionTest.java
new file mode 100644
index 0000000000..3120171e63
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesConditionTest.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright 2023 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.policies;
+
+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.util.ClientPoliciesUtil.createAnyClientConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientAccessTypeConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientScopesConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceGroupsConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceHostsConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateSourceRolesConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createPKCEEnforceExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureClientAuthenticatorExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createTestRaiseExeptionExecutorConfig;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.OAuthErrorException;
+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.models.AdminRoles;
+import org.keycloak.models.Constants;
+import org.keycloak.models.OAuth2DeviceConfig;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+import org.keycloak.protocol.oidc.OIDCConfigAttributes;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+import org.keycloak.services.clientpolicy.ClientPolicyEvent;
+import org.keycloak.services.clientpolicy.ClientPolicyException;
+import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientScopesConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceGroupsConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceHostsConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceRolesConditionFactory;
+import org.keycloak.services.clientpolicy.executor.PKCEEnforcerExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.SecureClientAuthenticatorExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutorFactory;
+import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LogoutConfirmPage;
+import org.keycloak.testsuite.pages.OAuth2DeviceVerificationPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.services.clientpolicy.executor.TestRaiseExceptionExecutorFactory;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPoliciesBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder;
+import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.UserBuilder;
+
+/**
+ * This test class is for testing a condition of client policies.
+ *
+ * @author Takashi Norimatsu
+ */
+@EnableFeature(value = Profile.Feature.CLIENT_SECRET_ROTATION)
+public class ClientPoliciesConditionTest extends AbstractClientPoliciesTest {
+
+ @Page
+ protected OAuth2DeviceVerificationPage verificationPage;
+
+ @Page
+ protected OAuthGrantPage grantPage;
+
+ @Page
+ protected ErrorPage errorPage;
+
+ @Page
+ protected LogoutConfirmPage logoutConfirmPage;
+
+ @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);
+
+ List clients = realm.getClients();
+
+ ClientRepresentation app = ClientBuilder.create()
+ .id(KeycloakModelUtils.generateId())
+ .clientId("test-device")
+ .secret("secret")
+ .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
+ .build();
+ clients.add(app);
+
+ ClientRepresentation appPublic = ClientBuilder.create().id(KeycloakModelUtils.generateId()).publicClient()
+ .clientId(DEVICE_APP_PUBLIC)
+ .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
+ .build();
+ clients.add(appPublic);
+
+ userId = KeycloakModelUtils.generateId();
+ UserRepresentation deviceUser = UserBuilder.create()
+ .id(userId)
+ .username("device-login")
+ .email("device-login@localhost")
+ .password("password")
+ .build();
+ users.add(deviceUser);
+
+ testRealms.add(realm);
+ }
+
+ @Test
+ public void testAnyClientCondition() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil")
+ .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID,
+ createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ 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 testClientUpdateSourceHostsCondition() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvni Profil")
+ .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
+ createSecureClientAuthenticatorExecutorConfig(
+ Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
+ null)
+ )
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prvni Politika", Boolean.TRUE)
+ .addCondition(ClientUpdaterSourceHostsConditionFactory.PROVIDER_ID,
+ createClientUpdateSourceHostsConditionConfig(Arrays.asList("localhost", "127.0.0.1")))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ String clientSecret = "secret";
+ try {
+ createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
+ }
+
+ // update policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Aktualizovana Prvni Politika", Boolean.TRUE)
+ .addCondition(ClientUpdaterSourceHostsConditionFactory.PROVIDER_ID,
+ createClientUpdateSourceHostsConditionConfig(Arrays.asList("example.com")))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ try {
+ createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ });
+ } catch (Exception e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void testClientUpdateSourceGroupsCondition() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profil")
+ .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
+ createSecureClientAuthenticatorExecutorConfig(
+ Arrays.asList(JWTClientAuthenticator.PROVIDER_ID),
+ null)
+ )
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politik", Boolean.TRUE)
+ .addCondition(ClientUpdaterSourceGroupsConditionFactory.PROVIDER_ID,
+ createClientUpdateSourceGroupsConditionConfig(Arrays.asList("topGroup")))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ 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 Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Il Primo Profilo")
+ .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
+ createSecureClientAuthenticatorExecutorConfig(
+ Arrays.asList(JWTClientSecretAuthenticator.PROVIDER_ID),
+ null)
+ )
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Prima Politica", Boolean.TRUE)
+ .addCondition(ClientUpdaterSourceRolesConditionFactory.PROVIDER_ID,
+ createClientUpdateSourceRolesConditionConfig(Arrays.asList(Constants.REALM_MANAGEMENT_CLIENT_ID + "." + AdminRoles.CREATE_CLIENT)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ 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 Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Het Eerste Profiel")
+ .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
+ createPKCEEnforceExecutorConfig(Boolean.TRUE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Het Eerste Beleid", Boolean.TRUE)
+ .addCondition(ClientScopesConditionFactory.PROVIDER_ID,
+ createClientScopesConditionConfig(ClientScopesConditionFactory.OPTIONAL, Arrays.asList("offline_access", "microprofile-jwt")))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ 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 Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "El Primer Perfil")
+ .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Primera Plitica", Boolean.TRUE)
+ .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID,
+ createClientAccessTypeConditionConfig(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // 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);
+
+ // update profiles
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "El Primer Perfil")
+ .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
+ createPKCEEnforceExecutorConfig(Boolean.FALSE)) // check only
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // Attempt to create a confidential client without PKCE setting. Should fail
+ try {
+ createClientByAdmin(generateSuffixedName("Gamma-App"), (ClientRepresentation clientRep) -> {
+ clientRep.setSecret("secretGamma");
+ clientRep.setBearerOnly(Boolean.FALSE);
+ clientRep.setPublicClient(Boolean.FALSE);
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
+ assertEquals("Invalid client metadata: code_challenge_method", e.getErrorDetail());
+ }
+
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "El Primer Perfil")
+ .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
+ createPKCEEnforceExecutorConfig(Boolean.TRUE)) // enforce
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ authCreateClients();
+ String clientGammaId = createClientDynamically(generateSuffixedName("Gamma-App"), (OIDCClientRepresentation clientRep) -> {
+ clientRep.setClientSecret("secretGamma");
+ });
+
+ ClientRepresentation clientRep = getClientByAdmin(clientGammaId);
+ assertEquals(OAuth2Constants.PKCE_METHOD_S256, OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).getPkceCodeChallengeMethod());
+
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "El Primer Perfil")
+ .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
+ createPKCEEnforceExecutorConfig(Boolean.FALSE)) // check only
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // Attempt to update the confidential client with not allowed PKCE setting. Should fail
+ try {
+ updateClientByAdmin(clientGammaId, (ClientRepresentation updatingClientRep) -> {
+ updatingClientRep.setAttributes(new HashMap<>());
+ updatingClientRep.getAttributes().put(OIDCConfigAttributes.PKCE_CODE_CHALLENGE_METHOD, OAuth2Constants.PKCE_METHOD_PLAIN);
+ });
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
+ assertEquals("Invalid client metadata: code_challenge_method", e.getErrorDetail());
+ }
+ ClientRepresentation cRep = getClientByAdmin(clientGammaId);
+ assertEquals(OAuth2Constants.PKCE_METHOD_S256, cRep.getAttributes().get(OIDCConfigAttributes.PKCE_CODE_CHALLENGE_METHOD));
+
+ }
+
+ @Test
+ public void testClientPolicyTriggeredForServiceAccountRequest() throws Exception {
+ String clientId = "service-account-app";
+ String clientSecret = "app-secret";
+ createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ clientRep.setStandardFlowEnabled(Boolean.FALSE);
+ clientRep.setImplicitFlowEnabled(Boolean.FALSE);
+ clientRep.setServiceAccountsEnabled(Boolean.TRUE);
+ clientRep.setPublicClient(Boolean.FALSE);
+ clientRep.setBearerOnly(Boolean.FALSE);
+ });
+
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
+ .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
+ createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.SERVICE_ACCOUNT_TOKEN_REQUEST)))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ String origClientId = oauth.getClientId();
+ oauth.clientId("service-account-app");
+ try {
+ OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest("app-secret");
+ assertEquals(400, response.getStatusCode());
+ assertEquals(ClientPolicyEvent.SERVICE_ACCOUNT_TOKEN_REQUEST.toString(), response.getError());
+ assertEquals("Exception thrown intentionally", response.getErrorDescription());
+ } finally {
+ oauth.clientId(origClientId);
+ }
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExecutorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExecutorTest.java
new file mode 100644
index 0000000000..7a49fba289
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExecutorTest.java
@@ -0,0 +1,1437 @@
+/*
+ * Copyright 2023 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.policies;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createAnyClientConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientRolesConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateContextConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureClientAuthenticatorExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureRequestObjectExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureResponseTypeExecutor;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureSigningAlgorithmEnforceExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+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.Optional;
+
+import javax.ws.rs.BadRequestException;
+
+import org.apache.http.HttpResponse;
+import org.hamcrest.Matchers;
+import org.jboss.arquillian.graphene.page.Page;
+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.crypto.Algorithm;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventType;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.CibaConfig;
+import org.keycloak.models.Constants;
+import org.keycloak.models.OAuth2DeviceConfig;
+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.AuthorizationResponseToken;
+import org.keycloak.representations.IDToken;
+import org.keycloak.representations.RefreshToken;
+import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.EventRepresentation;
+import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+import org.keycloak.services.clientpolicy.ClientPolicyException;
+import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientUpdaterContextConditionFactory;
+import org.keycloak.services.clientpolicy.executor.SecureClientAuthenticatorExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.SecureClientUrisExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.SecureLogoutExecutorFactory;
+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.SecureSigningAlgorithmExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtExecutorFactory;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
+import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LogoutConfirmPage;
+import org.keycloak.testsuite.pages.OAuth2DeviceVerificationPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPoliciesBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder;
+import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.keycloak.util.JsonSerialization;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * This test class is for testing an executor of client policies.
+ *
+ * @author Takashi Norimatsu
+ */
+@EnableFeature(value = Profile.Feature.CLIENT_SECRET_ROTATION)
+public class ClientPoliciesExecutorTest extends AbstractClientPoliciesTest {
+
+ @Page
+ protected OAuth2DeviceVerificationPage verificationPage;
+
+ @Page
+ protected OAuthGrantPage grantPage;
+
+ @Page
+ protected ErrorPage errorPage;
+
+ @Page
+ protected LogoutConfirmPage logoutConfirmPage;
+
+ @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);
+
+ List clients = realm.getClients();
+
+ ClientRepresentation app = ClientBuilder.create()
+ .id(KeycloakModelUtils.generateId())
+ .clientId("test-device")
+ .secret("secret")
+ .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
+ .build();
+ clients.add(app);
+
+ ClientRepresentation appPublic = ClientBuilder.create().id(KeycloakModelUtils.generateId()).publicClient()
+ .clientId(DEVICE_APP_PUBLIC)
+ .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
+ .build();
+ clients.add(appPublic);
+
+ userId = KeycloakModelUtils.generateId();
+ UserRepresentation deviceUser = UserBuilder.create()
+ .id(userId)
+ .username("device-login")
+ .email("device-login@localhost")
+ .password("password")
+ .build();
+ users.add(deviceUser);
+
+ testRealms.add(realm);
+ }
+
+ // Tests that secured client authenticator is enforced also during client authentication itself (during token request after successful login)
+ @Test
+ public void testSecureClientAuthenticatorDuringLogin() throws Exception {
+ // register profile to NOT allow authentication with ClientIdAndSecret
+ String profileName = "MyProfile";
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(profileName, "Primum Profile")
+ .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
+ createSecureClientAuthenticatorExecutorConfig(
+ Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
+ null))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register role policy
+ String roleAlphaName = "sample-client-role-alpha";
+ String roleZetaName = "sample-client-role-zeta";
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName)))
+ .addProfile(profileName)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // create a client without client role. It should be successful (policy not applied)
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ String cId = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret("secret");
+ });
+
+ // Login with clientIdAndSecret. It should be successful (policy not applied)
+ successfulLoginAndLogout(clientId, "secret");
+
+ // Add role to the client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId);
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ Assert.assertEquals(ClientIdAndSecretAuthenticator.PROVIDER_ID, clientRep.getClientAuthenticatorType());
+ clientResource.roles().create(RoleBuilder.create().name(roleAlphaName).build());
+
+ // Not allowed to client authentication with clientIdAndSecret anymore. Client matches policy now
+ oauth.clientId(clientId);
+ oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, "secret");
+ assertEquals(400, res.getStatusCode());
+ assertEquals(OAuthErrorException.INVALID_GRANT, res.getError());
+ assertEquals("Configured client authentication method not allowed for client", res.getErrorDescription());
+ }
+
+ @Test
+ public void testSecureResponseTypeExecutor() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil")
+ .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "A Primeira Politica", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ 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);
+ oauth.nonce("vbwe566fsfffds");
+ 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();
+
+ // update profiles
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil")
+ .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, createSecureResponseTypeExecutor(Boolean.FALSE, Boolean.TRUE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN + " " + OIDCResponseType.TOKEN); // token response type allowed
+ oauth.nonce("cie8cjcwiw");
+ 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();
+
+ // shall allow code using response_mode jwt
+ oauth.responseType(OIDCResponseType.CODE);
+ oauth.responseMode("jwt");
+ OAuthClient.AuthorizationEndpointResponse authzResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
+ String jwsResponse = authzResponse.getResponse();
+ AuthorizationResponseToken responseObject = oauth.verifyAuthorizationResponseToken(jwsResponse);
+ code = (String) responseObject.getOtherClaims().get(OAuth2Constants.CODE);
+ res = oauth.doAccessTokenRequest(code, clientSecret);
+ assertEquals(200, res.getStatusCode());
+
+ // update profiles
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil")
+ .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, createSecureResponseTypeExecutor(Boolean.FALSE, Boolean.FALSE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ oauth.openLogout();
+ oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN + " " + OIDCResponseType.TOKEN); // token response type allowed
+ oauth.responseMode("jwt");
+ oauth.openLoginForm();
+ final JWSInput errorJws = new JWSInput(new OAuthClient.AuthorizationEndpointResponse(oauth).getResponse());
+ JsonNode errorClaims = JsonSerialization.readValue(errorJws.getContent(), JsonNode.class);
+ assertEquals(OAuthErrorException.INVALID_REQUEST, errorClaims.get("error").asText());
+ }
+
+ @Test
+ public void testSecureResponseTypeExecutorAllowTokenResponseType() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil")
+ .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, createSecureResponseTypeExecutor(null, Boolean.TRUE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forsta Policyn", Boolean.TRUE)
+ .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
+ createClientUpdateContextConditionConfig(Arrays.asList(
+ ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER,
+ ClientUpdaterContextConditionFactory.BY_INITIAL_ACCESS_TOKEN,
+ ClientUpdaterContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)))
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // create by Admin REST API
+ try {
+ createClientByAdmin(generateSuffixedName("App-by-Admin"), (ClientRepresentation clientRep) -> {
+ clientRep.setSecret("secret");
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
+ }
+
+ // update profiles
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil")
+ .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, createSecureResponseTypeExecutor(Boolean.TRUE, null))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ String cId = null;
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ String clientSecret = "secret";
+ try {
+ cId = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ clientRep.setStandardFlowEnabled(Boolean.TRUE);
+ clientRep.setImplicitFlowEnabled(Boolean.TRUE);
+ clientRep.setPublicClient(Boolean.FALSE);
+ });
+ } catch (ClientPolicyException e) {
+ fail();
+ }
+ ClientRepresentation cRep = getClientByAdmin(cId);
+ assertEquals(Boolean.TRUE.toString(), cRep.getAttributes().get(OIDCConfigAttributes.ID_TOKEN_AS_DETACHED_SIGNATURE));
+
+ 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);
+ oauth.nonce("LIVieviDie028f");
+ 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();
+
+ IDToken idToken = oauth.verifyIDToken(new OAuthClient.AuthorizationEndpointResponse(oauth).getIdToken());
+ // confirm ID token as detached signature does not include authenticated user's claims
+ Assert.assertNull(idToken.getEmailVerified());
+ Assert.assertNull(idToken.getName());
+ Assert.assertNull(idToken.getPreferredUsername());
+ Assert.assertNull(idToken.getGivenName());
+ Assert.assertNull(idToken.getFamilyName());
+ Assert.assertNull(idToken.getEmail());
+ assertEquals("LIVieviDie028f", idToken.getNonce());
+ // confirm an access token not returned
+ Assert.assertNull(new OAuthClient.AuthorizationEndpointResponse(oauth).getAccessToken());
+
+ 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();
+ }
+
+ @Test
+ public void testSecureRequestObjectExecutor() throws Exception {
+ Integer availablePeriod = Integer.valueOf(SecureRequestObjectExecutor.DEFAULT_AVAILABLE_PERIOD + 400);
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil")
+ .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID,
+ createSecureRequestObjectExecutorConfig(availablePeriod, null))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prva Politika", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ 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("Missing parameter: 'request' or 'request_uri'", 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("Invalid parameter. Parameters in 'request' object not matching with request parameters", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+
+ // check whether client_id exists in both query parameter and request object
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.setClientId(null);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, true);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Invalid parameter. Parameters in 'request' object not matching with request parameters", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+
+ // check whether response_type exists in both query parameter and request object
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.setResponseType(null);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, true);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Invalid parameter. Parameters in 'request' object not matching with request parameters", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+
+ // Check scope required
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.setScope(null);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, true);
+ oauth.scope(null);
+ oauth.openid(false);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Parameter 'scope' missing in the request parameters or in 'request' object", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+ oauth.openid(true);
+
+ // check whether "exp" claim exists
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.exp(null);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Missing parameter in the 'request' object: 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(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Request Expired", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+
+ // check whether "nbf" claim exists
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.nbf(null);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Missing parameter in the 'request' object: nbf", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+
+ // check whether request object not yet being processed
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.nbf(requestObject.getNbf() + 600);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Request not yet being processed", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+
+ // check whether request object's available period is short
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.exp(requestObject.getNbf() + availablePeriod.intValue() + 1);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Request's available period is long", 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(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Missing parameter in the 'request' object: 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(OAuthErrorException.INVALID_REQUEST_URI, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Invalid parameter in the 'request' object: 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. Parameters in 'request' object not matching with request parameters", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+
+ // valid request object
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, true);
+
+ successfulLoginAndLogout(clientId, clientSecret);
+
+ // update profile : no configuration - "nbf" check and available period is 3600 sec
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil")
+ .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // check whether "nbf" claim exists
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.nbf(null);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Missing parameter in the 'request' object: nbf", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+
+ // check whether request object not yet being processed
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.nbf(requestObject.getNbf() + 600);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Request not yet being processed", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+
+ // check whether request object's available period is short
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.exp(requestObject.getNbf() + SecureRequestObjectExecutor.DEFAULT_AVAILABLE_PERIOD + 1);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Request's available period is long", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+
+ // update profile : not check "nbf"
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil")
+ .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID,
+ createSecureRequestObjectExecutorConfig(null, Boolean.FALSE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // not check whether "nbf" claim exists
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.nbf(null);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
+ successfulLoginAndLogout(clientId, clientSecret);
+
+ // not check whether request object not yet being processed
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.nbf(requestObject.getNbf() + 600);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
+ successfulLoginAndLogout(clientId, clientSecret);
+
+ // not check whether request object's available period is short
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.exp(requestObject.getNbf() + SecureRequestObjectExecutor.DEFAULT_AVAILABLE_PERIOD + 1);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
+ successfulLoginAndLogout(clientId, clientSecret);
+
+ // update profile : force request object encryption
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil")
+ .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID, createSecureRequestObjectExecutorConfig(null, null, true))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ registerRequestObject(requestObject, clientId, Algorithm.ES256, false);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("Request object not encrypted", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+ }
+
+ @Test
+ public void testParSecureRequestObjectExecutor() throws Exception {
+ Integer availablePeriod = Integer.valueOf(SecureRequestObjectExecutor.DEFAULT_AVAILABLE_PERIOD + 400);
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil")
+ .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID,
+ createSecureRequestObjectExecutorConfig(availablePeriod, true))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prva Politika", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ 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()));
+ });
+
+ oauth.realm(REALM_NAME);
+ oauth.clientId(clientId);
+
+ adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build());
+
+ AuthorizationEndpointRequestObject requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+
+ oauth.request(signRequestObject(requestObject));
+ OAuthClient.ParResponse pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
+ assertEquals(201, pResp.getStatusCode());
+ String requestUri = pResp.getRequestUri();
+
+ oauth.scope(null);
+ oauth.responseType(null);
+ oauth.request(null);
+ oauth.requestUri(requestUri);
+ OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
+ assertNotNull(loginResponse.getCode());
+ oauth.openLogout();
+
+ requestObject.exp(null);
+ oauth.requestUri(null);
+ oauth.request(signRequestObject(requestObject));
+ pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
+ requestUri = pResp.getRequestUri();
+ oauth.request(null);
+ oauth.requestUri(requestUri);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST_URI, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.nbf(null);
+ oauth.requestUri(null);
+ oauth.request(signRequestObject(requestObject));
+ pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
+ requestUri = pResp.getRequestUri();
+ oauth.request(null);
+ oauth.requestUri(requestUri);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST_URI, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.audience("https://www.other1.example.com/");
+ oauth.request(signRequestObject(requestObject));
+ oauth.requestUri(null);
+ pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
+ requestUri = pResp.getRequestUri();
+ oauth.request(null);
+ oauth.requestUri(requestUri);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST_URI, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+
+ requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
+ requestObject.setOtherClaims(OIDCLoginProtocol.REQUEST_URI_PARAM, "foo");
+ oauth.request(signRequestObject(requestObject));
+ oauth.requestUri(null);
+ pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
+ assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, pResp.getError());
+ }
+
+ @Test
+ public void testSecureSessionEnforceExecutor() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
+ .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ String roleAlphaName = "sample-client-role-alpha";
+ String roleBetaName = "sample-client-role-beta";
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(roleBetaName)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ 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 Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
+ .addExecutor(SecureSigningAlgorithmExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forsta Policyn", Boolean.TRUE)
+ .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
+ createClientUpdateContextConditionConfig(Arrays.asList(
+ ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER,
+ ClientUpdaterContextConditionFactory.BY_INITIAL_ACCESS_TOKEN,
+ ClientUpdaterContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // 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, "none");
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_REQUEST, 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, Algorithm.PS256);
+ clientRep.getAttributes().put(OIDCConfigAttributes.REQUEST_OBJECT_SIGNATURE_ALG, Algorithm.ES256);
+ clientRep.getAttributes().put(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG, Algorithm.ES256);
+ clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, Algorithm.ES256);
+ clientRep.getAttributes().put(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG, Algorithm.ES256);
+ });
+
+ // create by Admin REST API - success, PS256 enforced
+ String cAppAdmin2Id = createClientByAdmin(generateSuffixedName("App-by-Admin2"), (ClientRepresentation client2Rep) -> {
+ });
+ ClientRepresentation cRep2 = getClientByAdmin(cAppAdmin2Id);
+ assertEquals(Algorithm.PS256, cRep2.getAttributes().get(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG));
+ assertEquals(Algorithm.PS256, cRep2.getAttributes().get(OIDCConfigAttributes.REQUEST_OBJECT_SIGNATURE_ALG));
+ assertEquals(Algorithm.PS256, cRep2.getAttributes().get(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG));
+ assertEquals(Algorithm.PS256, cRep2.getAttributes().get(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG));
+ assertEquals(Algorithm.PS256, cRep2.getAttributes().get(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG));
+
+ // 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);
+ });
+ } catch (ClientPolicyException cpe) {
+ assertEquals(Errors.INVALID_REQUEST, cpe.getError());
+ }
+ ClientRepresentation cRep = getClientByAdmin(cAppAdminId);
+ assertEquals(Algorithm.ES256, 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);
+ });
+ cRep = getClientByAdmin(cAppAdminId);
+ assertEquals(Algorithm.PS384, cRep.getAttributes().get(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG));
+
+ // update profiles, ES256 enforced
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
+ .addExecutor(SecureSigningAlgorithmExecutorFactory.PROVIDER_ID,
+ createSecureSigningAlgorithmEnforceExecutorConfig(Algorithm.ES256))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // update by Admin REST API - success
+ updateClientByAdmin(cAppAdmin2Id, (ClientRepresentation client2Rep) -> {
+ client2Rep.getAttributes().remove(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG);
+ client2Rep.getAttributes().remove(OIDCConfigAttributes.REQUEST_OBJECT_SIGNATURE_ALG);
+ client2Rep.getAttributes().remove(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG);
+ client2Rep.getAttributes().remove(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG);
+ client2Rep.getAttributes().remove(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG);
+ });
+ cRep2 = getClientByAdmin(cAppAdmin2Id);
+ assertEquals(Algorithm.ES256, cRep2.getAttributes().get(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG));
+ assertEquals(Algorithm.ES256, cRep2.getAttributes().get(OIDCConfigAttributes.REQUEST_OBJECT_SIGNATURE_ALG));
+ assertEquals(Algorithm.ES256, cRep2.getAttributes().get(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG));
+ assertEquals(Algorithm.ES256, cRep2.getAttributes().get(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG));
+ assertEquals(Algorithm.ES256, cRep2.getAttributes().get(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG));
+
+ // update profiles, fall back to PS256
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
+ .addExecutor(SecureSigningAlgorithmExecutorFactory.PROVIDER_ID,
+ createSecureSigningAlgorithmEnforceExecutorConfig(Algorithm.RS512))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // 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);
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_REQUEST, e.getMessage());
+ }
+
+ // create dynamically - success
+ String cAppDynamicClientId = createClientDynamically(generateSuffixedName("App-in-Dynamic"), (OIDCClientRepresentation clientRep) -> {
+ clientRep.setUserinfoSignedResponseAlg(Algorithm.ES256);
+ clientRep.setRequestObjectSigningAlg(Algorithm.ES256);
+ clientRep.setIdTokenSignedResponseAlg(Algorithm.PS256);
+ clientRep.setTokenEndpointAuthSigningAlg(Algorithm.PS256);
+ });
+ events.expect(EventType.CLIENT_REGISTER).client(cAppDynamicClientId).user(Matchers.isEmptyOrNullString()).assertEvent();
+
+ // update dynamically - fail
+ try {
+ updateClientDynamically(cAppDynamicClientId, (OIDCClientRepresentation clientRep) -> {
+ clientRep.setIdTokenSignedResponseAlg(Algorithm.RS256);
+ });
+ fail();
+ } catch (ClientRegistrationException e) {
+ assertEquals(ERR_MSG_CLIENT_REG_FAIL, e.getMessage());
+ }
+ assertEquals(Algorithm.PS256, getClientDynamically(cAppDynamicClientId).getIdTokenSignedResponseAlg());
+
+ // update dynamically - success
+ updateClientDynamically(cAppDynamicClientId, (OIDCClientRepresentation clientRep) -> {
+ clientRep.setIdTokenSignedResponseAlg(Algorithm.ES384);
+ });
+ assertEquals(Algorithm.ES384, getClientDynamically(cAppDynamicClientId).getIdTokenSignedResponseAlg());
+
+ // create dynamically - success, PS256 enforced
+ restartAuthenticatedClientRegistrationSetting();
+ String cAppDynamicClient2Id = createClientDynamically(generateSuffixedName("App-in-Dynamic"), (OIDCClientRepresentation client2Rep) -> {
+ });
+ OIDCClientRepresentation cAppDynamicClient2Rep = getClientDynamically(cAppDynamicClient2Id);
+ assertEquals(Algorithm.PS256, cAppDynamicClient2Rep.getUserinfoSignedResponseAlg());
+ assertEquals(Algorithm.PS256, cAppDynamicClient2Rep.getRequestObjectSigningAlg());
+ assertEquals(Algorithm.PS256, cAppDynamicClient2Rep.getIdTokenSignedResponseAlg());
+ assertEquals(Algorithm.PS256, cAppDynamicClient2Rep.getTokenEndpointAuthSigningAlg());
+
+ // update profiles, enforce ES256
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
+ .addExecutor(SecureSigningAlgorithmExecutorFactory.PROVIDER_ID,
+ createSecureSigningAlgorithmEnforceExecutorConfig(Algorithm.ES256))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // update dynamically - success, ES256 enforced
+ updateClientDynamically(cAppDynamicClient2Id, (OIDCClientRepresentation client2Rep) -> {
+ client2Rep.setUserinfoSignedResponseAlg(null);
+ client2Rep.setRequestObjectSigningAlg(null);
+ client2Rep.setIdTokenSignedResponseAlg(null);
+ client2Rep.setTokenEndpointAuthSigningAlg(null);
+ });
+ cAppDynamicClient2Rep = getClientDynamically(cAppDynamicClient2Id);
+ assertEquals(Algorithm.ES256, cAppDynamicClient2Rep.getUserinfoSignedResponseAlg());
+ assertEquals(Algorithm.ES256, cAppDynamicClient2Rep.getRequestObjectSigningAlg());
+ assertEquals(Algorithm.ES256, cAppDynamicClient2Rep.getIdTokenSignedResponseAlg());
+ assertEquals(Algorithm.ES256, cAppDynamicClient2Rep.getTokenEndpointAuthSigningAlg());
+ }
+
+ @Test
+ public void testSecureClientRegisteringUriEnforceExecutor() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili")
+ .addExecutor(SecureClientUrisExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Ensimmainen Politiikka", Boolean.TRUE)
+ .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
+ createClientUpdateContextConditionConfig(Arrays.asList(
+ ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER,
+ ClientUpdaterContextConditionFactory.BY_INITIAL_ACCESS_TOKEN,
+ ClientUpdaterContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ 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());
+ }
+
+ String cid = null;
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ try {
+ cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setServiceAccountsEnabled(Boolean.TRUE);
+ clientRep.setRedirectUris(null);
+ });
+ } catch (Exception e) {
+ fail();
+ }
+
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ clientRep.setRedirectUris(null);
+ clientRep.setServiceAccountsEnabled(Boolean.FALSE);
+ });
+ assertEquals(false, getClientByAdmin(cid).isServiceAccountsEnabled());
+
+ // update policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Paivitetyn Ensimmaisen Politiikka", Boolean.TRUE)
+ .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
+ createClientUpdateContextConditionConfig(Arrays.asList(
+ ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER,
+ ClientUpdaterContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ try {
+ updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> {
+ clientRep.setRedirectUris(Collections.singletonList("https://newredirect/*"));
+ });
+ fail();
+ } catch (ClientRegistrationException e) {
+ assertEquals(ERR_MSG_CLIENT_REG_FAIL, e.getMessage());
+ }
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ // rootUrl
+ clientRep.setRootUrl("https://client.example.com/");
+ // adminUrl
+ clientRep.setAdminUrl("https://client.example.com/admin/");
+ // baseUrl
+ clientRep.setBaseUrl("https://client.example.com/base/");
+ // web origins
+ clientRep.setWebOrigins(Arrays.asList("https://valid.other.client.example.com/", "https://valid.another.client.example.com/"));
+ // backchannel logout URL
+ Map attributes = Optional.ofNullable(clientRep.getAttributes()).orElse(new HashMap<>());
+ attributes.put(OIDCConfigAttributes.BACKCHANNEL_LOGOUT_URL, "https://client.example.com/logout/");
+ clientRep.setAttributes(attributes);
+ // OAuth2 : redirectUris
+ clientRep.setRedirectUris(Arrays.asList("https://client.example.com/redirect/", "https://client.example.com/callback/"));
+ // OAuth2 : jwks_uri
+ attributes.put(OIDCConfigAttributes.JWKS_URL, "https://client.example.com/jwks/");
+ clientRep.setAttributes(attributes);
+ // OIDD : requestUris
+ setAttributeMultivalued(clientRep, OIDCConfigAttributes.REQUEST_URIS, Arrays.asList("https://client.example.com/request/", "https://client.example.com/reqobj/"));
+ // CIBA Client Notification Endpoint
+ attributes.put(CibaConfig.CIBA_BACKCHANNEL_CLIENT_NOTIFICATION_ENDPOINT, "https://client.example.com/client-notification/");
+ clientRep.setAttributes(attributes);
+ });
+ } catch (Exception e) {
+ fail();
+ }
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ // rootUrl
+ clientRep.setRootUrl("http://client.example.com/*/");
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
+ assertEquals("Invalid rootUrl", e.getErrorDetail());
+ }
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ // adminUrl
+ clientRep.setAdminUrl("http://client.example.com/admin/");
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
+ assertEquals("Invalid adminUrl", e.getErrorDetail());
+ }
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ // baseUrl
+ clientRep.setBaseUrl("https://client.example.com/base/*");
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
+ assertEquals("Invalid baseUrl", e.getErrorDetail());
+ }
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ // web origins
+ clientRep.setWebOrigins(Arrays.asList("http://valid.another.client.example.com/"));
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
+ assertEquals("Invalid webOrigins", e.getErrorDetail());
+ }
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ // backchannel logout URL
+ Map attributes = Optional.ofNullable(clientRep.getAttributes()).orElse(new HashMap<>());
+ attributes.put(OIDCConfigAttributes.BACKCHANNEL_LOGOUT_URL, "httpss://client.example.com/logout/");
+ clientRep.setAttributes(attributes);
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
+ assertEquals("Invalid logoutUrl", e.getErrorDetail());
+ }
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ // OAuth2 : redirectUris
+ clientRep.setRedirectUris(Arrays.asList("https://client.example.com/redirect/", "ftp://client.example.com/callback/"));
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
+ assertEquals("Invalid redirectUris", e.getErrorDetail());
+ }
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ // OAuth2 : jwks_uri
+ Map attributes = Optional.ofNullable(clientRep.getAttributes()).orElse(new HashMap<>());
+ attributes.put(OIDCConfigAttributes.JWKS_URL, "http s://client.example.com/jwks/");
+ clientRep.setAttributes(attributes);
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
+ assertEquals("Invalid jwksUri", e.getErrorDetail());
+ }
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ // OIDD : requestUris
+ setAttributeMultivalued(clientRep, OIDCConfigAttributes.REQUEST_URIS, Arrays.asList("https://client.example.com/request/*", "https://client.example.com/reqobj/"));
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
+ assertEquals("Invalid requestUris", e.getErrorDetail());
+ }
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ // CIBA Client Notification Endpoint
+ Map attributes = Optional.ofNullable(clientRep.getAttributes()).orElse(new HashMap<>());
+ attributes.put(CibaConfig.CIBA_BACKCHANNEL_CLIENT_NOTIFICATION_ENDPOINT, "http://client.example.com/client-notification/");
+ clientRep.setAttributes(attributes);
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getError());
+ assertEquals("Invalid cibaClientNotificationEndpoint", e.getErrorDetail());
+ }
+ }
+
+ @Test
+ public void testSecureSigningAlgorithmForSignedJwtEnforceExecutorWithSecureAlg() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili")
+ .addExecutor(SecureSigningAlgorithmForSignedJwtExecutorFactory.PROVIDER_ID, createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig(Boolean.TRUE)
+ ).toRepresentation()
+ )
+ .toString();
+ updateProfiles(json);
+
+ // register policies
+ String roleAlphaName = "sample-client-role-alpha";
+ String roleZetaName = "sample-client-role-zeta";
+ String roleCommonName = "sample-client-role-common";
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // create a client with client role
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret("secret");
+ clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
+ clientRep.setAttributes(new HashMap<>());
+ clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, Algorithm.ES256);
+ });
+ adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(roleAlphaName).build());
+ adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(roleCommonName).build());
+
+
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId);
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+
+ KeyPair keyPair = setupJwksUrl(Algorithm.ES256, clientRep, clientResource);
+ PublicKey publicKey = keyPair.getPublic();
+ PrivateKey privateKey = keyPair.getPrivate();
+
+ String signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, 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, Algorithm.ES256);
+ OAuthClient.AccessTokenResponse refreshedResponse = doRefreshTokenRequestWithSignedJWT(response.getRefreshToken(), signedJwt);
+ assertEquals(200, refreshedResponse.getStatusCode());
+
+ // introspect token
+ signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, Algorithm.ES256);
+ HttpResponse tokenIntrospectionResponse = doTokenIntrospectionWithSignedJWT("access_token", refreshedResponse.getAccessToken(), signedJwt);
+ assertEquals(200, tokenIntrospectionResponse.getStatusLine().getStatusCode());
+
+ // revoke token
+ signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, Algorithm.ES256);
+ HttpResponse revokeTokenResponse = doTokenRevokeWithSignedJWT("refresh_toke", refreshedResponse.getRefreshToken(), signedJwt);
+ assertEquals(200, revokeTokenResponse.getStatusLine().getStatusCode());
+
+ signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, 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, Algorithm.ES256);
+ HttpResponse logoutResponse = doLogoutWithSignedJWT(refreshedResponse.getRefreshToken(), signedJwt);
+ assertEquals(204, logoutResponse.getStatusLine().getStatusCode());
+ }
+
+ @Test
+ public void testSecureSigningAlgorithmForSignedJwtEnforceExecutorWithNotSecureAlg() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili")
+ .addExecutor(SecureSigningAlgorithmForSignedJwtExecutorFactory.PROVIDER_ID, createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig(Boolean.FALSE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ String roleAlphaName = "sample-client-role-alpha";
+ String roleZetaName = "sample-client-role-zeta";
+ String roleCommonName = "sample-client-role-common";
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // create a client with client role
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret("secret");
+ clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
+ clientRep.setAttributes(new HashMap<>());
+ clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, Algorithm.RS256);
+ });
+ adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(roleAlphaName).build());
+ adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(roleCommonName).build());
+
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId);
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+
+ KeyPair keyPair = setupJwksUrl(Algorithm.RS256, clientRep, clientResource);
+ PublicKey publicKey = keyPair.getPublic();
+ PrivateKey privateKey = keyPair.getPrivate();
+
+ String signedJwt = createSignedRequestToken(clientId, privateKey, publicKey, Algorithm.RS256);
+
+ 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(400, response.getStatusCode());
+ assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
+ assertEquals("not allowed signature algorithm.", response.getErrorDescription());
+ }
+
+ @Test
+ public void testSecureLogoutExecutor() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Logout Test")
+ .addExecutor(SecureLogoutExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Logout Policy", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID,
+ createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ String clientSecret = "secret";
+ try {
+ createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ clientRep.setStandardFlowEnabled(Boolean.TRUE);
+ clientRep.setImplicitFlowEnabled(Boolean.TRUE);
+ clientRep.setPublicClient(Boolean.FALSE);
+ clientRep.setFrontchannelLogout(true);
+ });
+ } catch (ClientPolicyException cpe) {
+ assertEquals("Front-channel logout is not allowed for this client", cpe.getErrorDetail());
+ }
+
+ String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ clientRep.setStandardFlowEnabled(Boolean.TRUE);
+ clientRep.setImplicitFlowEnabled(Boolean.TRUE);
+ clientRep.setPublicClient(Boolean.FALSE);
+ });
+
+ ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(cid);
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+
+ clientRep.setFrontchannelLogout(true);
+
+ try {
+ clientResource.update(clientRep);
+ } catch (BadRequestException bre) {
+ assertEquals("Front-channel logout is not allowed for this client", bre.getResponse().readEntity(OAuth2ErrorRepresentation.class).getErrorDescription());
+ }
+
+ ClientPolicyExecutorConfigurationRepresentation config = new ClientPolicyExecutorConfigurationRepresentation();
+
+ config.setConfigAsMap(SecureLogoutExecutorFactory.ALLOW_FRONT_CHANNEL_LOGOUT, Boolean.TRUE.booleanValue());
+
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Logout Test")
+ .addExecutor(SecureLogoutExecutorFactory.PROVIDER_ID, config)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setFrontChannelLogoutUrl(oauth.getRedirectUri());
+ clientResource.update(clientRep);
+
+ config.setConfigAsMap(SecureLogoutExecutorFactory.ALLOW_FRONT_CHANNEL_LOGOUT, Boolean.FALSE.toString());
+
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Logout Test")
+ .addExecutor(SecureLogoutExecutorFactory.PROVIDER_ID, config)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ OAuthClient.AccessTokenResponse response = successfulLogin(clientId, clientSecret);
+
+ oauth.idTokenHint(response.getIdToken()).openLogout();
+
+ assertTrue(driver.getPageSource().contains("Front-channel logout is not allowed for this client"));
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExtendedEventTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExtendedEventTest.java
new file mode 100644
index 0000000000..e4c3c5dc58
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesExtendedEventTest.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright 2023 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.policies;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
+import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createAnyClientConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientAccessTypeConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientRolesConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientScopesConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createTestRaiseExeptionExecutorConfig;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.OAuthErrorException;
+import org.keycloak.common.Profile;
+import org.keycloak.events.Details;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.Constants;
+import org.keycloak.models.OAuth2DeviceConfig;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCConfigAttributes;
+import org.keycloak.representations.RefreshToken;
+import org.keycloak.representations.idm.ClientRepresentation;
+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.services.clientpolicy.ClientPolicyEvent;
+import org.keycloak.services.clientpolicy.ClientPolicyException;
+import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientScopesConditionFactory;
+import org.keycloak.services.clientpolicy.executor.SuppressRefreshTokenRotationExecutorFactory;
+import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LogoutConfirmPage;
+import org.keycloak.testsuite.pages.OAuth2DeviceVerificationPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.services.clientpolicy.executor.TestRaiseExceptionExecutorFactory;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPoliciesBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder;
+import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+
+/**
+ * This test class is for testing a newly supported event for client policies.
+ *
+ * @author Takashi Norimatsu
+ */
+@EnableFeature(value = Profile.Feature.CLIENT_SECRET_ROTATION)
+public class ClientPoliciesExtendedEventTest extends AbstractClientPoliciesTest {
+
+ @Page
+ protected OAuth2DeviceVerificationPage verificationPage;
+
+ @Page
+ protected OAuthGrantPage grantPage;
+
+ @Page
+ protected ErrorPage errorPage;
+
+ @Page
+ protected LogoutConfirmPage logoutConfirmPage;
+
+ @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);
+
+ List clients = realm.getClients();
+
+ ClientRepresentation app = ClientBuilder.create()
+ .id(KeycloakModelUtils.generateId())
+ .clientId("test-device")
+ .secret("secret")
+ .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
+ .build();
+ clients.add(app);
+
+ ClientRepresentation appPublic = ClientBuilder.create().id(KeycloakModelUtils.generateId()).publicClient()
+ .clientId(DEVICE_APP_PUBLIC)
+ .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
+ .build();
+ clients.add(appPublic);
+
+ userId = KeycloakModelUtils.generateId();
+ UserRepresentation deviceUser = UserBuilder.create()
+ .id(userId)
+ .username("device-login")
+ .email("device-login@localhost")
+ .password("password")
+ .build();
+ users.add(deviceUser);
+
+ testRealms.add(realm);
+ }
+
+ @Test
+ public void testExtendedClientPolicyIntefacesForClientRegistrationPolicyMigration() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
+ .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
+ createTestRaiseExeptionExecutorConfig(Arrays.asList(
+ ClientPolicyEvent.REGISTERED, ClientPolicyEvent.UPDATED, ClientPolicyEvent.UNREGISTER)))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ String clientName = "ByAdmin-App" + KeycloakModelUtils.generateId().substring(0, 7);
+ String clientId = null;
+
+ try {
+ createClientByAdmin(clientName, (ClientRepresentation clientRep) -> {
+ });
+ fail();
+ } catch (ClientPolicyException cpe) {
+ assertEquals(ClientPolicyEvent.REGISTERED.toString(), cpe.getError());
+ }
+
+ clientId = getClientByAdminWithName(clientName).getId();
+ assertEquals(true, getClientByAdmin(clientId).isEnabled());
+ try {
+ updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setEnabled(false);
+ });
+ fail();
+ } catch (ClientPolicyException cpe) {
+ assertEquals(ClientPolicyEvent.UPDATED.toString(), cpe.getError());
+ }
+ assertEquals(false, getClientByAdmin(clientId).isEnabled());
+
+ try {
+ deleteClientByAdmin(clientId);
+ fail();
+ } catch (ClientPolicyException cpe) {
+ assertEquals(ClientPolicyEvent.UNREGISTER.toString(), cpe.getError());
+ }
+
+ // TODO : For dynamic client registration, the existing test scheme can not distinguish when the exception happens on which event so that the migrated client policy executors test them afterwards.
+ }
+
+ @Test
+ public void testExtendedClientPolicyIntefacesForDeviceAuthorizationRequest() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
+ .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
+ createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.DEVICE_AUTHORIZATION_REQUEST)))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // Device Authorization Request from device
+ oauth.realm(REALM_NAME);
+ oauth.clientId(DEVICE_APP);
+ OAuthClient.DeviceAuthorizationResponse response = oauth.doDeviceAuthorizationRequest(DEVICE_APP, "secret");
+ assertEquals(400, response.getStatusCode());
+ assertEquals(ClientPolicyEvent.DEVICE_AUTHORIZATION_REQUEST.toString(), response.getError());
+ assertEquals("Exception thrown intentionally", response.getErrorDescription());
+ }
+
+ @Test
+ public void testExtendedClientPolicyIntefacesForDeviceTokenRequest() throws Exception {
+ // Device Authorization Request from device
+ oauth.realm(REALM_NAME);
+ oauth.clientId(DEVICE_APP);
+ OAuthClient.DeviceAuthorizationResponse response = oauth.doDeviceAuthorizationRequest(DEVICE_APP, "secret");
+
+ Assert.assertEquals(200, response.getStatusCode());
+ assertNotNull(response.getDeviceCode());
+ assertNotNull(response.getUserCode());
+ assertNotNull(response.getVerificationUri());
+ assertNotNull(response.getVerificationUriComplete());
+
+ // Verify user code from verification page using browser
+ openVerificationPage(response.getVerificationUri());
+ verificationPage.assertCurrent();
+ verificationPage.submit(response.getUserCode());
+
+ loginPage.assertCurrent();
+
+ // Do Login
+ oauth.fillLoginForm("device-login", "password");
+
+ // Consent
+ grantPage.assertCurrent();
+ grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.ROLES_CONSENT_TEXT);
+ grantPage.accept();
+
+ verificationPage.assertApprovedPage();
+
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
+ .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
+ createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.DEVICE_TOKEN_REQUEST)))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // Token request from device
+ OAuthClient.AccessTokenResponse tokenResponse = oauth.doDeviceTokenRequest(DEVICE_APP, "secret", response.getDeviceCode());
+ assertEquals(400, tokenResponse.getStatusCode());
+ assertEquals(OAuthErrorException.INVALID_GRANT, tokenResponse.getError());
+ assertEquals("Exception thrown intentionally", tokenResponse.getErrorDescription());
+ }
+
+ @Test
+ public void testExtendedClientPolicyIntefacesForDeviceTokenResponse() throws Exception {
+ // Device Authorization Request from device
+ oauth.realm(REALM_NAME);
+ oauth.clientId(DEVICE_APP);
+ OAuthClient.DeviceAuthorizationResponse response = oauth.doDeviceAuthorizationRequest(DEVICE_APP, "secret");
+
+ Assert.assertEquals(200, response.getStatusCode());
+ assertNotNull(response.getDeviceCode());
+ assertNotNull(response.getUserCode());
+ assertNotNull(response.getVerificationUri());
+ assertNotNull(response.getVerificationUriComplete());
+
+ // Verify user code from verification page using browser
+ openVerificationPage(response.getVerificationUri());
+ verificationPage.assertCurrent();
+ verificationPage.submit(response.getUserCode());
+
+ loginPage.assertCurrent();
+
+ // Do Login
+ oauth.fillLoginForm("device-login", "password");
+
+ // Consent
+ grantPage.assertCurrent();
+ grantPage.assertGrants(OAuthGrantPage.PROFILE_CONSENT_TEXT, OAuthGrantPage.EMAIL_CONSENT_TEXT, OAuthGrantPage.ROLES_CONSENT_TEXT);
+ grantPage.accept();
+
+ verificationPage.assertApprovedPage();
+
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
+ .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
+ createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.DEVICE_TOKEN_RESPONSE)))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // Token request from device
+ OAuthClient.AccessTokenResponse tokenResponse = oauth.doDeviceTokenRequest(DEVICE_APP, "secret", response.getDeviceCode());
+ assertEquals(400, tokenResponse.getStatusCode());
+ assertEquals(ClientPolicyEvent.DEVICE_TOKEN_RESPONSE.toString(), tokenResponse.getError());
+ assertEquals("Exception thrown intentionally", tokenResponse.getErrorDescription());
+ }
+
+ @Test
+ public void testExtendedClientPolicyIntefacesForTokenResponse() throws Exception {
+ // register a confidential client
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ String clientSecret = "secret";
+ createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ clientRep.setPublicClient(Boolean.FALSE);
+ clientRep.setBearerOnly(Boolean.FALSE);
+ });
+
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
+ .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
+ createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.TOKEN_RESPONSE)))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Primera Plitica", Boolean.TRUE)
+ .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID,
+ createClientAccessTypeConditionConfig(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ oauth.clientId(clientId);
+ oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
+
+ events.expectLogin().client(clientId).assertEvent();
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, clientSecret);
+ assertEquals(400, response.getStatusCode());
+ assertEquals(ClientPolicyEvent.TOKEN_RESPONSE.toString(), response.getError());
+ assertEquals("Exception thrown intentionally", response.getErrorDescription());
+ }
+
+ @Test
+ public void testExtendedClientPolicyIntefacesForTokenRefreshResponse() throws Exception {
+ 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.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();
+
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil")
+ .addExecutor(SuppressRefreshTokenRotationExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ String refreshTokenString = res.getRefreshToken();
+ OAuthClient.AccessTokenResponse accessTokenResponseRefreshed = oauth.doRefreshTokenRequest(refreshTokenString, clientSecret);
+ assertEquals(200, accessTokenResponseRefreshed.getStatusCode());
+ assertEquals(null, accessTokenResponseRefreshed.getRefreshToken());
+
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList("other" + SAMPLE_CLIENT_ROLE)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ accessTokenResponseRefreshed = oauth.doRefreshTokenRequest(refreshTokenString, clientSecret);
+ assertEquals(200, accessTokenResponseRefreshed.getStatusCode());
+ RefreshToken refreshedRefreshToken = oauth.parseRefreshToken(accessTokenResponseRefreshed.getRefreshToken());
+ assertEquals(sessionId, refreshedRefreshToken.getSessionState());
+ assertEquals(sessionId, refreshedRefreshToken.getSessionState());
+ assertEquals(findUserByUsername(adminClient.realm(REALM_NAME), TEST_USER_NAME).getId(), refreshedRefreshToken.getSubject());
+ }
+
+ @Test
+ public void testExtendedClientPolicyIntefacesForServiceAccountTokenRequeponse() throws Exception {
+ String clientId = "service-account-app";
+ String clientSecret = "app-secret";
+ createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ clientRep.setStandardFlowEnabled(Boolean.FALSE);
+ clientRep.setImplicitFlowEnabled(Boolean.FALSE);
+ clientRep.setServiceAccountsEnabled(Boolean.TRUE);
+ clientRep.setPublicClient(Boolean.FALSE);
+ clientRep.setBearerOnly(Boolean.FALSE);
+ });
+
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
+ .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
+ createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.SERVICE_ACCOUNT_TOKEN_RESPONSE)))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Het Eerste Beleid", Boolean.TRUE)
+ .addCondition(ClientScopesConditionFactory.PROVIDER_ID,
+ createClientScopesConditionConfig(ClientScopesConditionFactory.OPTIONAL, Arrays.asList("offline_access", "microprofile-jwt")))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+
+ String origClientId = oauth.getClientId();
+ oauth.clientId("service-account-app");
+ oauth.scope("offline_access");
+ try {
+ OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest("app-secret");
+ assertEquals(400, response.getStatusCode());
+ assertEquals(ClientPolicyEvent.SERVICE_ACCOUNT_TOKEN_RESPONSE.toString(), response.getError());
+ assertEquals("Exception thrown intentionally", response.getErrorDescription());
+ } finally {
+ oauth.clientId(origClientId);
+ }
+ }
+
+ @Test
+ public void testExtendedClientPolicyIntefacesForResourceOwnerPasswordCredentialsResponse() throws Exception {
+
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ String clientSecret = "secret";
+
+ createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ clientRep.setStandardFlowEnabled(Boolean.TRUE);
+ clientRep.setDirectAccessGrantsEnabled(Boolean.TRUE);
+ clientRep.setPublicClient(Boolean.FALSE);
+ });
+
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
+ .addExecutor(TestRaiseExceptionExecutorFactory.PROVIDER_ID,
+ createTestRaiseExeptionExecutorConfig(Arrays.asList(ClientPolicyEvent.RESOURCE_OWNER_PASSWORD_CREDENTIALS_RESPONSE)))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Porisii desu", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID,
+ createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ oauth.clientId(clientId);
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(clientSecret, TEST_USER_NAME, TEST_USER_PASSWORD, null);
+
+ assertEquals(400, response.getStatusCode());
+ assertEquals(ClientPolicyEvent.RESOURCE_OWNER_PASSWORD_CREDENTIALS_RESPONSE.toString(), response.getError());
+ assertEquals("Exception thrown intentionally", response.getErrorDescription());
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesFeatureTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesFeatureTest.java
similarity index 94%
rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesFeatureTest.java
rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesFeatureTest.java
index 92fabfcb76..54d265ac88 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesFeatureTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesFeatureTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * Copyright 2023 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");
@@ -16,7 +16,7 @@
*
*/
-package org.keycloak.testsuite.client;
+package org.keycloak.testsuite.client.policies;
import java.util.Set;
@@ -37,6 +37,8 @@ import static org.junit.Assert.fail;
import static org.keycloak.common.Profile.Feature.CLIENT_POLICIES;
/**
+ * This test class is for enabling and disabling client policies by feature mechanism.
+ *
* @author Marek Posolda
*/
public class ClientPoliciesFeatureTest extends AbstractTestRealmKeycloakTest {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesImportExportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesImportExportTest.java
similarity index 94%
rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesImportExportTest.java
rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesImportExportTest.java
index dfaf0cf6af..de7af86fc4 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesImportExportTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesImportExportTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * Copyright 2023 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");
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.keycloak.testsuite.client;
+package org.keycloak.testsuite.client.policies;
import org.junit.After;
import org.junit.Test;
@@ -35,6 +35,8 @@ import java.util.List;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
/**
+ * This test class is for testing client policies that are applied when importing and exporting a realm setting file.
+ *
* @author Takashi Norimatsu
*/
public class ClientPoliciesImportExportTest extends AbstractClientPoliciesTest {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesLoadUpdateTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesLoadUpdateTest.java
similarity index 99%
rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesLoadUpdateTest.java
rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesLoadUpdateTest.java
index 3247d5d73c..06456fa466 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesLoadUpdateTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesLoadUpdateTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * Copyright 2023 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");
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.keycloak.testsuite.client;
+package org.keycloak.testsuite.client.policies;
import org.hamcrest.Matchers;
import org.junit.Test;
@@ -59,6 +59,8 @@ import static org.keycloak.testsuite.util.ClientPoliciesUtil.createPKCEEnforceEx
import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureClientAuthenticatorExecutorConfig;
/**
+ * This test class is for testing loading and updating profiles and policies file of client policies.
+ *
* @author Takashi Norimatsu
*/
public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesTest.java
new file mode 100644
index 0000000000..6e1468afe0
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/ClientPoliciesTest.java
@@ -0,0 +1,1158 @@
+/*
+ * Copyright 2023 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.policies;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+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.findClientResourceByClientId;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createAnyClientConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientRolesConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientScopesConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateContextConditionConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createConsentRequiredExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createFullScopeDisabledExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createHolderOfKeyEnforceExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createIntentClientBindCheckExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createPKCEEnforceExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createRejectisResourceOwnerPasswordCredentialsGrantExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureClientAuthenticatorExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig;
+import static org.keycloak.testsuite.util.ClientPoliciesUtil.createTestRaiseExeptionConditionConfig;
+
+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 javax.ws.rs.core.Response;
+
+import org.hamcrest.Matchers;
+import org.jboss.logging.Logger;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.OAuthErrorException;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ProtocolMappersResource;
+import org.keycloak.admin.client.resource.RolesResource;
+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.common.Profile;
+import org.keycloak.common.util.Time;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventType;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.Constants;
+import org.keycloak.models.OAuth2DeviceConfig;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+import org.keycloak.protocol.oidc.OIDCConfigAttributes;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.mappers.ClaimsParameterWithValueIdTokenMapper;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
+import org.keycloak.representations.ClaimsRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+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.services.clientpolicy.ClientPolicyException;
+import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientScopesConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientUpdaterContextConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceGroupsConditionFactory;
+import org.keycloak.services.clientpolicy.condition.ClientUpdaterSourceRolesConditionFactory;
+import org.keycloak.services.clientpolicy.executor.ConfidentialClientAcceptExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.ConsentRequiredExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.FullScopeDisabledExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforcerExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.IntentClientBindCheckExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.PKCEEnforcerExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.RejectRequestExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.RejectResourceOwnerPasswordCredentialsGrantExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.SecureClientAuthenticatorExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutorFactory;
+import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtExecutorFactory;
+import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
+import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
+import org.keycloak.testsuite.services.clientpolicy.condition.TestRaiseExceptionConditionFactory;
+import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPoliciesBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder;
+import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder;
+import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.RoleBuilder;
+import org.keycloak.testsuite.util.ServerURLs;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.keycloak.util.JsonSerialization;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.TreeNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.TextNode;
+
+/**
+ * @author Takashi Norimatsu
+ */
+@EnableFeature(value = Profile.Feature.CLIENT_SECRET_ROTATION)
+public class ClientPoliciesTest extends AbstractClientPoliciesTest {
+
+ private static final Logger logger = Logger.getLogger(ClientPoliciesTest.class);
+
+ @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);
+
+ List clients = realm.getClients();
+
+ ClientRepresentation app = ClientBuilder.create()
+ .id(KeycloakModelUtils.generateId())
+ .clientId("test-device")
+ .secret("secret")
+ .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
+ .build();
+ clients.add(app);
+
+ ClientRepresentation appPublic = ClientBuilder.create().id(KeycloakModelUtils.generateId()).publicClient()
+ .clientId(DEVICE_APP_PUBLIC)
+ .attribute(OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED, "true")
+ .attribute(OIDCConfigAttributes.POST_LOGOUT_REDIRECT_URIS, "+")
+ .build();
+ clients.add(appPublic);
+
+ userId = KeycloakModelUtils.generateId();
+ UserRepresentation deviceUser = UserBuilder.create()
+ .id(userId)
+ .username("device-login")
+ .email("device-login@localhost")
+ .password("password")
+ .build();
+ users.add(deviceUser);
+
+ testRealms.add(realm);
+ }
+
+ // KEYCLOAK-18108
+ @Test
+ public void testTwoProfilesWithDifferentConfigurationOfSameExecutorType() throws Exception {
+ setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME);
+
+ // register another profile with "SecureClientAuthEnforceExecutorFactory", but use different configuration of client authenticator.
+ // This profile won't allow JWTClientSecretAuthenticator.PROVIDER_ID
+ String profileName = "UnusedProfile";
+ String json = (new ClientProfilesBuilder(getProfilesWithoutGlobals())).addProfile(
+ (new ClientProfileBuilder()).createProfile(profileName, "Profile with SecureClientAuthEnforceExecutorFactory")
+ .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
+ createSecureClientAuthenticatorExecutorConfig(
+ Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID),
+ null))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // Make sure it is still possible to create client with JWTClientSecretAuthenticator. The "UnusedProfile" should not be used as it is not referenced from any client policy
+ String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
+ clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
+ });
+ assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType());
+ }
+
+ @Test
+ public void testDynamicClientRegisterAndUpdate() throws Exception {
+ 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 Exception {
+ 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 Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Eichte profil")
+ .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
+ createPKCEEnforceExecutorConfig(Boolean.TRUE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ 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);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Eischt Politik", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ failLoginByNotFollowingPKCE(clientId);
+
+ // update policies
+ updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Aktualiseiert Eischt Politik", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList("anothor-client-role")))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation());
+
+ successfulLoginAndLogout(clientId, clientSecret);
+
+ // update policies
+ updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Aktualiseiert Eischt Politik", Boolean.TRUE)
+ .addProfile(PROFILE_NAME)
+ .toRepresentation());
+
+ successfulLoginAndLogout(clientId, clientSecret);
+ }
+
+ @Test
+ public void testCreateUpdateDeleteExecutorRuntime() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Purofairu Sono Ichi")
+ .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
+ createPKCEEnforceExecutorConfig(Boolean.FALSE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Porishii Sono Ichi", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
+ .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
+ createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER)))
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ 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);
+
+ // update policies
+ updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Koushinsareta Porishii Sono Ichi", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
+ .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
+ createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation());
+
+ failLoginByNotFollowingPKCE(clientId);
+
+ // update profiles
+ updateProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Koushinsareta Purofairu Sono Ichi")
+ .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
+ createPKCEEnforceExecutorConfig(Boolean.TRUE))
+ .toRepresentation());
+
+ updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
+ clientRep.setServiceAccountsEnabled(Boolean.FALSE);
+ });
+ assertEquals(false, getClientByAdmin(cid).isServiceAccountsEnabled());
+ assertEquals(OAuth2Constants.PKCE_METHOD_S256, OIDCAdvancedConfigWrapper.fromClientRepresentation(getClientByAdmin(cid)).getPkceCodeChallengeMethod());
+
+ // update profiles
+ updateProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Sarani Koushinsareta Purofairu Sono Ichi").toRepresentation());
+
+ 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 Exception {
+ String roleAlphaName = "sample-client-role-alpha";
+ String roleBetaName = "sample-client-role-beta";
+ String roleZetaName = "sample-client-role-zeta";
+ String roleCommonName = "sample-client-role-common";
+
+ // register profiles
+ String profileAlphaName = "MyProfile-alpha";
+ String profileBetaName = "MyProfile-beta";
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(profileAlphaName, "Pierwszy Profil")
+ .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID,
+ createSecureClientAuthenticatorExecutorConfig(Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID), ClientIdAndSecretAuthenticator.PROVIDER_ID))
+ .toRepresentation()).addProfile(
+ (new ClientProfileBuilder()).createProfile(profileBetaName, "Drugi Profil")
+ .addExecutor(PKCEEnforcerExecutorFactory.PROVIDER_ID,
+ createPKCEEnforceExecutorConfig(Boolean.TRUE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ String policyAlphaName = "MyPolicy-alpha";
+ String policyBetaName = "MyPolicy-beta";
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(policyAlphaName, "Pierwsza Zasada", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName)))
+ .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
+ createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER)))
+ .addProfile(profileAlphaName)
+ .toRepresentation()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(policyBetaName, "Drugi Zasada", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(roleBetaName, roleZetaName)))
+ .addProfile(profileBetaName)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ String clientAlphaId = generateSuffixedName("Alpha-App");
+ String clientAlphaSecret = "secretAlpha";
+
+ // Not allowed client authenticator should fail
+ try {
+ createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientAlphaSecret);
+ clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
+ }
+
+ String cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientAlphaSecret);
+ clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.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 Exception {
+ // register policies
+ String json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Fyrsta Stefnan", Boolean.TRUE)
+ .addCondition(TestRaiseExceptionConditionFactory.PROVIDER_ID,
+ createTestRaiseExeptionConditionConfig())
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ try {
+ createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {
+ });
+ fail();
+ } catch (ClientPolicyException e) {
+ assertEquals(OAuthErrorException.SERVER_ERROR, e.getMessage());
+ }
+ }
+
+ @Test
+ public void testConditionWithoutNoConfiguration() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Die Erste Politik")
+ .addExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientAccessTypeCondition", "Die Erste Politik", Boolean.TRUE)
+ .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, null)
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateSourceGroupsCondition", "Die Zweite Politik", Boolean.TRUE)
+ .addCondition(ClientUpdaterSourceGroupsConditionFactory.PROVIDER_ID, null)
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateSourceRolesCondition", "Die Dritte Politik", Boolean.TRUE)
+ .addCondition(ClientUpdaterSourceRolesConditionFactory.PROVIDER_ID, null)
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateContextCondition", "Die Vierte Politik", Boolean.TRUE)
+ .addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID, null)
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ 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);
+ }
+
+ @Test
+ public void testHolderOfKeyEnforceExecutor() throws Exception {
+ Assume.assumeTrue("This test must be executed with enabled TLS.", ServerURLs.AUTH_SERVER_SSL_REQUIRED);
+
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Az Elso Profil")
+ .addExecutor(HolderOfKeyEnforcerExecutorFactory.PROVIDER_ID,
+ createHolderOfKeyEnforceExecutorConfig(Boolean.TRUE))
+ .addExecutor(SecureSigningAlgorithmForSignedJwtExecutorFactory.PROVIDER_ID,
+ createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig(Boolean.FALSE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Az Elso Politika", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID,
+ createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ try (ClientAttributeUpdater cau = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, TEST_CLIENT)) {
+ ClientRepresentation clientRep = cau.getResource().toRepresentation();
+ Assert.assertNotNull(clientRep);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseMtlsHoKToken(true);
+ cau.update();
+ checkMtlsFlow();
+ }
+ }
+
+ @Test
+ public void testNegativeLogicCondition() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen")
+ .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ String clientSecret = "secretBeta";
+ createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ });
+
+ try {
+ failLoginWithoutSecureSessionParameter(clientId, ERR_MSG_MISSING_NONCE);
+
+ // update policies
+ updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig(Boolean.TRUE))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation());
+
+ successfulLoginAndLogout(clientId, clientSecret);
+
+ // update policies
+ updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig(Boolean.FALSE))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation());
+
+ failLoginWithoutSecureSessionParameter(clientId, ERR_MSG_MISSING_NONCE);
+ } catch (Exception e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void testUpdatePolicyWithoutNameNotAllowed() throws Exception {
+ // register policies
+ String json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(null, "La Premiere Politique", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ try {
+ updatePolicies(json);
+ fail();
+ } catch (ClientPolicyException cpe) {
+ assertEquals("update policies failed", cpe.getError());
+ }
+ }
+
+ @Test
+ public void testConfidentialClientAcceptExecutorExecutor() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Erstes Profil")
+ .addExecutor(ConfidentialClientAcceptExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Erstes Politik", Boolean.TRUE)
+ .addCondition(ClientRolesConditionFactory.PROVIDER_ID,
+ createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ String clientConfidentialId = generateSuffixedName("confidential-app");
+ String clientConfidentialSecret = "app-secret";
+ String cidConfidential = createClientByAdmin(clientConfidentialId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientConfidentialSecret);
+ clientRep.setStandardFlowEnabled(Boolean.TRUE);
+ clientRep.setImplicitFlowEnabled(Boolean.TRUE);
+ clientRep.setPublicClient(Boolean.FALSE);
+ clientRep.setBearerOnly(Boolean.FALSE);
+ });
+ adminClient.realm(REALM_NAME).clients().get(cidConfidential).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build());
+
+ successfulLoginAndLogout(clientConfidentialId, clientConfidentialSecret);
+
+ String clientPublicId = generateSuffixedName("public-app");
+ String cidPublic = createClientByAdmin(clientPublicId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientConfidentialSecret);
+ clientRep.setStandardFlowEnabled(Boolean.TRUE);
+ clientRep.setImplicitFlowEnabled(Boolean.TRUE);
+ clientRep.setPublicClient(Boolean.TRUE);
+ clientRep.setBearerOnly(Boolean.FALSE);
+ });
+ adminClient.realm(REALM_NAME).clients().get(cidPublic).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build());
+
+ oauth.clientId(clientPublicId);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_CLIENT, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals("invalid client access type", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+ }
+
+ @Test
+ public void testConsentRequiredExecutorExecutor() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Test Profile")
+ .addExecutor(ConsentRequiredExecutorFactory.PROVIDER_ID, createConsentRequiredExecutorConfig(true))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Test Policy", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID,
+ createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // Client will be auto-configured to enable consentRequired
+ String clientId = generateSuffixedName("aaa-app");
+ String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setImplicitFlowEnabled(Boolean.FALSE);
+ clientRep.setConsentRequired(Boolean.FALSE);
+ });
+ ClientRepresentation clientRep = getClientByAdmin(cid);
+ assertEquals(Boolean.TRUE, clientRep.isConsentRequired());
+
+ // Client cannot be updated to disable consentRequired
+ updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
+ cRep.setConsentRequired(Boolean.FALSE);
+ });
+ clientRep = getClientByAdmin(cid);
+ assertEquals(Boolean.TRUE, clientRep.isConsentRequired());
+
+ // Switch auto-configure to false. Auto-configuration won't happen, but validation will still be here, so should not be possible to disable consentRequired
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Test Profile")
+ .addExecutor(ConsentRequiredExecutorFactory.PROVIDER_ID, createConsentRequiredExecutorConfig(false))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // Not possible to register client with consentRequired due the validation
+ try {
+ createClientByAdmin(clientId, (ClientRepresentation clientRep2) -> {
+ clientRep2.setConsentRequired(Boolean.FALSE);
+ });
+ fail();
+ } catch (ClientPolicyException cpe) {
+ assertEquals(Errors.INVALID_REGISTRATION, cpe.getError());
+ }
+
+ // Not possible to update existing client to consentRequired due the validation
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
+ cRep.setConsentRequired(Boolean.FALSE);
+ });
+ fail();
+ } catch (ClientPolicyException cpe) {
+ assertEquals(Errors.INVALID_REGISTRATION, cpe.getError());
+ }
+ clientRep = getClientByAdmin(cid);
+ assertEquals(Boolean.TRUE, clientRep.isConsentRequired());
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
+ cRep.setImplicitFlowEnabled(Boolean.TRUE);
+ });
+ clientRep = getClientByAdmin(cid);
+ assertEquals(Boolean.TRUE, clientRep.isImplicitFlowEnabled());
+ assertEquals(Boolean.TRUE, clientRep.isConsentRequired());
+ } catch (ClientPolicyException cpe) {
+ fail();
+ }
+ }
+
+ @Test
+ public void testFullScopeDisabledExecutor() throws Exception {
+ // register profiles - client autoConfigured to disable fullScopeAllowed
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Test Profile")
+ .addExecutor(FullScopeDisabledExecutorFactory.PROVIDER_ID, createFullScopeDisabledExecutorConfig(true))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Test Policy", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID,
+ createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // Client will be auto-configured to disable fullScopeAllowed
+ String clientId = generateSuffixedName("aaa-app");
+ String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setImplicitFlowEnabled(Boolean.FALSE);
+ clientRep.setFullScopeAllowed(Boolean.TRUE);
+ });
+ ClientRepresentation clientRep = getClientByAdmin(cid);
+ assertEquals(Boolean.FALSE, clientRep.isFullScopeAllowed());
+
+ // Client cannot be updated to disable fullScopeAllowed
+ updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
+ cRep.setFullScopeAllowed(Boolean.TRUE);
+ });
+ clientRep = getClientByAdmin(cid);
+ assertEquals(Boolean.FALSE, clientRep.isFullScopeAllowed());
+
+ // Switch auto-configure to false. Auto-configuration won't happen, but validation will still be here, so should not be possible to enable fullScopeAllowed
+ json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Test Profile")
+ .addExecutor(FullScopeDisabledExecutorFactory.PROVIDER_ID, createFullScopeDisabledExecutorConfig(false))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // Not possible to register client with fullScopeAllowed due the validation
+ try {
+ createClientByAdmin(clientId, (ClientRepresentation clientRep2) -> {
+ clientRep2.setFullScopeAllowed(Boolean.TRUE);
+ });
+ fail();
+ } catch (ClientPolicyException cpe) {
+ assertEquals(Errors.INVALID_REGISTRATION, cpe.getError());
+ }
+
+ // Not possible to update existing client to fullScopeAllowed due the validation
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
+ cRep.setFullScopeAllowed(Boolean.TRUE);
+ });
+ fail();
+ } catch (ClientPolicyException cpe) {
+ assertEquals(Errors.INVALID_REGISTRATION, cpe.getError());
+ }
+ clientRep = getClientByAdmin(cid);
+ assertEquals(Boolean.FALSE, clientRep.isFullScopeAllowed());
+
+ try {
+ updateClientByAdmin(cid, (ClientRepresentation cRep) -> {
+ cRep.setImplicitFlowEnabled(Boolean.TRUE);
+ });
+ clientRep = getClientByAdmin(cid);
+ assertEquals(Boolean.TRUE, clientRep.isImplicitFlowEnabled());
+ assertEquals(Boolean.FALSE, clientRep.isFullScopeAllowed());
+ } catch (ClientPolicyException cpe) {
+ fail();
+ }
+ }
+
+ @Test
+ public void testRejectResourceOwnerCredentialsGrantExecutor() throws Exception {
+
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ String clientSecret = "secret";
+
+ createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ clientRep.setStandardFlowEnabled(Boolean.TRUE);
+ clientRep.setDirectAccessGrantsEnabled(Boolean.TRUE);
+ clientRep.setPublicClient(Boolean.FALSE);
+ });
+
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Purofairu desu")
+ .addExecutor(RejectResourceOwnerPasswordCredentialsGrantExecutorFactory.PROVIDER_ID,
+ createRejectisResourceOwnerPasswordCredentialsGrantExecutorConfig(Boolean.TRUE))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Porisii desu", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID,
+ createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ oauth.clientId(clientId);
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(clientSecret, TEST_USER_NAME, TEST_USER_PASSWORD, null);
+
+ assertEquals(400, response.getStatusCode());
+ assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
+ assertEquals("resource owner password credentials grant is prohibited.", response.getErrorDescription());
+
+ }
+
+ @Test
+ public void testRejectRequestExecutor() throws Exception {
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil")
+ .addExecutor(RejectRequestExecutorFactory.PROVIDER_ID, null)
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ String clientBetaId = generateSuffixedName("Beta-App");
+ createClientByAdmin(clientBetaId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret("secretBeta");
+ });
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
+ .addCondition(AnyClientConditionFactory.PROVIDER_ID,
+ createAnyClientConditionConfig())
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ try {
+ oauth.clientId(clientBetaId);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+ assertEquals(ERR_MSG_REQ_NOT_ALLOWED, oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
+ revertToBuiltinProfiles();
+ successfulLoginAndLogout(clientBetaId, "secretBeta");
+ } catch (Exception e) {
+ fail();
+ }
+ }
+
+ /**
+ * When creating a dynamic client the secret expiration date must be defined
+ *
+ * @throws Exception
+ */
+ @Test
+ public void whenCreateDynamicClientSecretExpirationDateMustExist() throws Exception {
+
+ //enable policy
+ configureCustomProfileAndPolicy(60, 30, 20);
+
+ String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {
+ });
+ OIDCClientRepresentation response = getClientDynamically(clientId);
+ assertThat(response.getClientSecret(), notNullValue());
+ assertThat(response.getClientSecretExpiresAt().intValue(), greaterThan(0));
+
+ }
+
+ /**
+ * When update a dynamic client the secret expiration date must be defined and the rotation process must obey the policy configuration
+ *
+ * @throws Exception
+ */
+ @Test
+ public void whenUpdateDynamicClientRotationMustFollowConfiguration() throws Exception {
+
+ //enable policy
+ configureCustomProfileAndPolicy(60, 30, 20);
+
+ String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {
+ });
+ OIDCClientRepresentation response = getClientDynamically(clientId);
+
+ String firstSecret = response.getClientSecret();
+ Integer firstSecretExpiration = response.getClientSecretExpiresAt();
+
+ updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> {
+ clientRep.setContacts(Collections.singletonList("keycloak@keycloak.org"));
+ });
+
+ OIDCClientRepresentation updated = getClientDynamically(clientId);
+
+ //secret rotation must NOT occur
+ assertThat(updated.getClientSecret(), equalTo(firstSecret));
+ assertThat(updated.getClientSecretExpiresAt(), equalTo(firstSecretExpiration));
+
+ //force secret expiration
+ setTimeOffset(61);
+
+ updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> {
+ clientRep.setClientName(generateSuffixedName(CLIENT_NAME));
+ });
+
+ updated = getClientDynamically(clientId);
+ String updatedSecret = updated.getClientSecret();
+
+ //secret rotation must occur
+ assertThat(updatedSecret, not(equalTo(firstSecret)));
+ assertThat(updated.getClientSecretExpiresAt(), not(equalTo(firstSecretExpiration)));
+
+ //login with updated secret
+ assertLoginAndLogoutStatus(clientId, updatedSecret, Response.Status.OK);
+
+ //login with rotated secret
+ assertLoginAndLogoutStatus(clientId, firstSecret, Response.Status.OK);
+
+ //force rotated secret expiration
+ setTimeOffset(100);
+
+ //login with updated secret (remains valid)
+ assertLoginAndLogoutStatus(clientId, updatedSecret, Response.Status.OK);
+
+ //try to log in with rotated secret (must fail)
+ assertLoginAndLogoutStatus(clientId, firstSecret, Response.Status.UNAUTHORIZED);
+
+ }
+
+ /**
+ * When updating a dynamic client within the "time remaining to expiration" period the client secret must be rotated and
+ * the new secret must be sent along with the new expiration date.
+ * Even though the client secret is still valid, the time remaining setting should force rotation
+ *
+ * @throws Exception
+ */
+ @Test
+ public void whenUpdateDynamicClientDuringRemainingExpirationPeriodMustRotateSecret() throws Exception {
+
+ //enable policy
+ configureCustomProfileAndPolicy(60, 30, 20);
+
+ String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {
+ });
+ OIDCClientRepresentation response = getClientDynamically(clientId);
+
+ String firstSecret = response.getClientSecret();
+ Integer firstSecretExpiration = response.getClientSecretExpiresAt();
+
+ assertThat(firstSecretExpiration, is(greaterThan(Time.currentTime())));
+
+ //Enter in Remaining expiration window
+ setTimeOffset(41);
+
+ //update client to force rotation (due to remaining expiration)
+ updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> {
+ clientRep.setContacts(Collections.singletonList("keycloak@keycloak.org"));
+ });
+
+ OIDCClientRepresentation updated = getClientDynamically(clientId);
+
+ //secret rotation must occur
+ assertThat(updated.getClientSecret(), not(equalTo(firstSecret)));
+ assertThat(updated.getClientSecretExpiresAt(), not(equalTo(firstSecretExpiration)));
+
+ }
+
+ @Test
+ public void testIntentClientBindCheck() throws Exception {
+ final String intentName = "openbanking_intent_id";
+
+ // register profiles
+ String json = (new ClientProfilesBuilder()).addProfile(
+ (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Het Eerste Profiel")
+ .addExecutor(IntentClientBindCheckExecutorFactory.PROVIDER_ID,
+ createIntentClientBindCheckExecutorConfig(intentName, TestApplicationResourceUrls.checkIntentClientBoundUri()))
+ .toRepresentation()
+ ).toString();
+ updateProfiles(json);
+
+ // register policies
+ json = (new ClientPoliciesBuilder()).addPolicy(
+ (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Het Eerste Beleid", Boolean.TRUE)
+ .addCondition(ClientScopesConditionFactory.PROVIDER_ID,
+ createClientScopesConditionConfig(ClientScopesConditionFactory.OPTIONAL, Arrays.asList("microprofile-jwt")))
+ .addProfile(PROFILE_NAME)
+ .toRepresentation()
+ ).toString();
+ updatePolicies(json);
+
+ // create a client
+ String clientId = generateSuffixedName(CLIENT_NAME);
+ String clientSecret = "secret";
+ createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
+ clientRep.setSecret(clientSecret);
+ clientRep.setStandardFlowEnabled(Boolean.TRUE);
+ clientRep.setImplicitFlowEnabled(Boolean.TRUE);
+ });
+ ClientResource app = findClientResourceByClientId(adminClient.realm("test"), clientId);
+ ProtocolMappersResource res = app.getProtocolMappers();
+ res.createMapper(ModelToRepresentation.toRepresentation(ClaimsParameterWithValueIdTokenMapper.createMapper("claimsParameterWithValueIdTokenMapper", "openbanking_intent_id", true))).close();
+
+ // register a binding of an intent with different client
+ String intentId = "123abc456xyz";
+ String differentClientId = "test-app";
+ Response r = testingClient.testApp().oidcClientEndpoints().bindIntentWithClient(intentId, differentClientId);
+ assertEquals(204, r.getStatus());
+
+ // create a request object with claims
+ String nonce = "naodfejawi37d";
+
+ ClaimsRepresentation claimsRep = new ClaimsRepresentation();
+ ClaimsRepresentation.ClaimValue claimValue = new ClaimsRepresentation.ClaimValue<>();
+ claimValue.setEssential(Boolean.TRUE);
+ claimValue.setValue(intentId);
+ claimsRep.setIdTokenClaims(Collections.singletonMap(intentName, claimValue));
+
+ Map oidcRequest = new HashMap<>();
+ oidcRequest.put(OIDCLoginProtocol.CLIENT_ID_PARAM, clientId);
+ oidcRequest.put(OIDCLoginProtocol.NONCE_PARAM, nonce);
+ oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN);
+ oidcRequest.put(OIDCLoginProtocol.REDIRECT_URI_PARAM, oauth.getRedirectUri());
+ oidcRequest.put(OIDCLoginProtocol.CLAIMS_PARAM, claimsRep);
+ oidcRequest.put(OIDCLoginProtocol.SCOPE_PARAM, "openid" + " " + "microprofile-jwt");
+ String request = new JWSBuilder().jsonContent(oidcRequest).none();
+
+ // send an authorization request
+ oauth.scope("openid" + " " + "microprofile-jwt");
+ oauth.request(request);
+ oauth.clientId(clientId);
+ oauth.nonce(nonce);
+ oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentFragment().get(OAuth2Constants.ERROR));
+ assertEquals("The intent is not bound with the client", oauth.getCurrentFragment().get(OAuth2Constants.ERROR_DESCRIPTION));
+
+ // register a binding of an intent with a valid client
+ r = testingClient.testApp().oidcClientEndpoints().bindIntentWithClient(intentId, clientId);
+ assertEquals(204, r.getStatus());
+
+ // send an authorization request
+ oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
+
+ // check an authorization response
+ EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent();
+ String sessionId = loginEvent.getSessionId();
+ String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+ String code = oauth.getCurrentFragment().get(OAuth2Constants.CODE);
+ OAuthClient.AuthorizationEndpointResponse authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth, true);
+ JWSInput idToken = new JWSInput(authzResponse.getIdToken());
+ ObjectMapper mapper = JsonSerialization.mapper;
+ JsonParser parser = mapper.getFactory().createParser(idToken.readContentAsString());
+ TreeNode treeNode = mapper.readTree(parser);
+ String clientBoundIntentId = ((TextNode) treeNode.get(intentName)).asText();
+ assertEquals(intentId, clientBoundIntentId);
+
+ // send a token request
+ OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, clientSecret);
+
+ // check a token response
+ assertEquals(200, response.getStatusCode());
+ events.expectCodeToToken(codeId, sessionId).client(clientId).assertEvent();
+ idToken = new JWSInput(response.getIdToken());
+ mapper = JsonSerialization.mapper;
+ parser = mapper.getFactory().createParser(idToken.readContentAsString());
+ treeNode = mapper.readTree(parser);
+ clientBoundIntentId = ((TextNode) treeNode.get(intentName)).asText();
+ assertEquals(intentId, clientBoundIntentId);
+
+ // logout
+ oauth.doLogout(response.getRefreshToken(), clientSecret);
+ events.expectLogout(response.getSessionState()).client(clientId).clearDetails().assertEvent();
+
+ // create a request object with invalid claims
+ claimsRep = new ClaimsRepresentation();
+ claimValue = new ClaimsRepresentation.ClaimValue<>();
+ claimValue.setEssential(Boolean.TRUE);
+ claimValue.setValue(intentId);
+ claimsRep.setIdTokenClaims(Collections.singletonMap("other_intent_id", claimValue));
+ oidcRequest.put(OIDCLoginProtocol.CLAIMS_PARAM, claimsRep);
+ request = new JWSBuilder().jsonContent(oidcRequest).none();
+
+ // send an authorization request
+ oauth.request(request);
+ oauth.openLoginForm();
+ assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentFragment().get(OAuth2Constants.ERROR));
+ assertEquals("no claim for an intent value for ID token" , oauth.getCurrentFragment().get(OAuth2Constants.ERROR_DESCRIPTION));
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/par/ParTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/par/ParTest.java
index 355d062793..0e97a25ea0 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/par/ParTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/par/ParTest.java
@@ -63,7 +63,7 @@ import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory;
import org.keycloak.testsuite.admin.ApiUtil;
-import org.keycloak.testsuite.client.AbstractClientPoliciesTest;
+import org.keycloak.testsuite.client.policies.AbstractClientPoliciesTest;
import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;