KEYCLOAK-18108 Refactoring retrieve of condition/executor providers. Make sure correct configuration of executor/condition is used for particular provider

This commit is contained in:
mposolda 2021-05-14 14:11:36 +02:00 committed by Marek Posolda
parent c2e2cbe180
commit 71dcbec642
14 changed files with 329 additions and 136 deletions

View file

@ -19,6 +19,7 @@
package org.keycloak.representations.idm;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -28,15 +29,8 @@ public class ClientPolicyConditionRepresentation {
@JsonProperty("condition")
private String conditionProviderId;
private ClientPolicyConditionConfigurationRepresentation configuration;
public ClientPolicyConditionRepresentation() {
}
public ClientPolicyConditionRepresentation(String conditionProviderId, ClientPolicyConditionConfigurationRepresentation configuration) {
this.conditionProviderId = conditionProviderId;
this.configuration = configuration;
}
@JsonProperty("configuration")
private JsonNode configuration;
public String getConditionProviderId() {
return conditionProviderId;
@ -46,11 +40,11 @@ public class ClientPolicyConditionRepresentation {
this.conditionProviderId = conditionProviderId;
}
public ClientPolicyConditionConfigurationRepresentation getConfiguration() {
public JsonNode getConfiguration() {
return configuration;
}
public void setConfiguration(ClientPolicyConditionConfigurationRepresentation configuration) {
public void setConfiguration(JsonNode configuration) {
this.configuration = configuration;
}
}

View file

@ -19,6 +19,7 @@
package org.keycloak.representations.idm;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -28,15 +29,8 @@ public class ClientPolicyExecutorRepresentation {
@JsonProperty("executor")
private String executorProviderId;
private ClientPolicyExecutorConfigurationRepresentation configuration;
public ClientPolicyExecutorRepresentation() {
}
public ClientPolicyExecutorRepresentation(String executorProviderId, ClientPolicyExecutorConfigurationRepresentation configuration) {
this.executorProviderId = executorProviderId;
this.configuration = configuration;
}
@JsonProperty("configuration")
private JsonNode configuration;
public String getExecutorProviderId() {
return executorProviderId;
@ -46,11 +40,11 @@ public class ClientPolicyExecutorRepresentation {
this.executorProviderId = providerId;
}
public ClientPolicyExecutorConfigurationRepresentation getConfiguration() {
public JsonNode getConfiguration() {
return configuration;
}
public void setConfiguration(ClientPolicyExecutorConfigurationRepresentation configuration) {
public void setConfiguration(JsonNode configuration) {
this.configuration = configuration;
}
}

View file

@ -22,6 +22,10 @@ import org.junit.Test;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.ClientPoliciesRepresentation;
import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation;
import org.keycloak.representations.idm.ClientPolicyConditionRepresentation;
import org.keycloak.representations.idm.ClientPolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.util.JsonSerialization;
@ -30,6 +34,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -185,5 +190,26 @@ public class JsonParserTest {
return JsonSerialization.readValue(repp, Map.class);
}
@Test
public void testReadClientPolicy() throws Exception {
InputStream is = getClass().getClassLoader().getResourceAsStream("sample-client-policy.json");
ClientPoliciesRepresentation clientPolicies = JsonSerialization.readValue(is, ClientPoliciesRepresentation.class);
}
Assert.assertEquals(clientPolicies.getPolicies().size(), 1);
ClientPolicyRepresentation clientPolicy = clientPolicies.getPolicies().get(0);
Assert.assertEquals("some-policy", clientPolicy.getName());
List<ClientPolicyConditionRepresentation> conditions = clientPolicy.getConditions();
Assert.assertEquals(conditions.size(), 1);
ClientPolicyConditionRepresentation condition = conditions.get(0);
Assert.assertEquals("some-condition", condition.getConditionProviderId());
ClientPolicyConditionConfigurationRepresentation configRep = JsonSerialization.mapper.convertValue(condition.getConfiguration(), ClientPolicyConditionConfigurationRepresentation.class);
Assert.assertEquals(true, configRep.isNegativeLogic());
Assert.assertEquals("val1", configRep.getConfigAsMap().get("string-option"));
Assert.assertEquals(14, configRep.getConfigAsMap().get("int-option"));
Assert.assertEquals(true, configRep.getConfigAsMap().get("bool-option"));
Assert.assertNull(configRep.getConfigAsMap().get("not-existing-option"));
}
}

View file

@ -0,0 +1,20 @@
{
"policies": [
{
"name": "some-policy",
"description": "This is some client policy.",
"enabled": true,
"conditions": [
{
"condition": "some-condition",
"configuration": {
"is-negative-logic": true,
"string-option": "val1",
"int-option": 14,
"bool-option": true
}
}
]
}
]
}

View file

@ -0,0 +1,113 @@
/*
* 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.component;
import com.fasterxml.jackson.databind.JsonNode;
import org.keycloak.provider.Provider;
/**
* Component model backed by JSON configuration. Useful for providers, which rely on JSON configuration rather than on ComponentModel, which is directly
* persisted as entity in the DB (store).
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JsonConfigComponentModel extends ComponentModel {
private final String providerType;
private final String providerId;
private final String componentId;
private final JsonNode configNode;
/**
* @param providerType
* @param realmId
* @param providerId
* @param configNode JSON configuration of this provider. For example if node corresponds to JSON like "{\"foo\":\"bar\"}", then
* component configuration is supposed to have one configuration option "foo" with value "bar"
*/
public JsonConfigComponentModel(Class<? extends Provider> providerType, String realmId, String providerId, JsonNode configNode) {
checkNotNull(providerType, "providerType must be not null");
checkNotNull(realmId, "realmId must be not null");
checkNotNull(providerId, "providerId must be not null");
checkNotNull(configNode, "configNode must be not null for provider " + providerId);
this.providerType = providerType.getName();
this.providerId = providerId;
this.configNode = configNode;
// We don't have realm model ID of the component, so componentId based on the realmId, providerType, providerId and hashCode of configurations.
this.componentId = realmId + "::" + providerType + "::" + this.providerId + "::" + configNode.hashCode();
}
private void checkNotNull(Object value, String message) {
if (value == null) {
throw new IllegalArgumentException(message);
}
}
@Override
public String getProviderType() {
return providerType;
}
@Override
public String getProviderId() {
return providerId;
}
@Override
public String getName() {
return componentId + "-config";
}
@Override
public String getId() {
return componentId;
}
@Override
public boolean get(String key, boolean defaultValue) {
JsonNode sub = configNode.get(key);
return sub == null ? defaultValue : sub.asBoolean();
}
@Override
public long get(String key, long defaultValue) {
JsonNode sub = configNode.get(key);
return sub == null ? defaultValue : sub.asLong();
}
@Override
public int get(String key, int defaultValue) {
JsonNode sub = configNode.get(key);
return sub == null ? defaultValue : sub.asInt();
}
@Override
public String get(String key, String defaultValue) {
JsonNode sub = configNode.get(key);
return sub == null ? defaultValue : sub.asText();
}
@Override
public String get(String key) {
return get(key, null);
}
}

View file

@ -27,6 +27,7 @@ import org.keycloak.storage.federated.UserFederatedStorageProvider;
import org.keycloak.vault.VaultTranscriber;
import java.util.Set;
import java.util.function.Function;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -74,6 +75,18 @@ public interface KeycloakSession extends InvalidationHandler {
*/
<T extends Provider> T getComponentProvider(Class<T> clazz, String componentId);
/**
* Returns a component provider for a component from the realm that is relevant to this session.
* The relevant realm must be set prior to calling this method in the context, see {@link KeycloakContext#getRealm()}.
* @param <T>
* @param clazz
* @param componentId Component configuration
* @param modelGetter Getter to retrieve componentModel
* @throws IllegalArgumentException If the realm is not set in the context.
* @return Provider configured according to the {@link componentId}, {@code null} if it cannot be instantiated.
*/
<T extends Provider> T getComponentProvider(Class<T> clazz, String componentId, Function<KeycloakSessionFactory, ComponentModel> modelGetter);
/**
*
* @param <T>

View file

@ -65,6 +65,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
@ -337,8 +338,19 @@ public class DefaultKeycloakSession implements KeycloakSession {
}
@Override
@SuppressWarnings("unchecked")
public <T extends Provider> T getComponentProvider(Class<T> clazz, String componentId) {
final RealmModel realm = getContext().getRealm();
if (realm == null) {
throw new IllegalArgumentException("Realm not set in the context.");
}
// Loads componentModel from the realm
return this.getComponentProvider(clazz, componentId, KeycloakModelUtils.componentModelGetter(realm.getId(), componentId));
}
@Override
@SuppressWarnings("unchecked")
public <T extends Provider> T getComponentProvider(Class<T> clazz, String componentId, Function<KeycloakSessionFactory, ComponentModel> modelGetter) {
Integer hash = clazz.hashCode() + componentId.hashCode();
T provider = (T) providers.get(hash);
final RealmModel realm = getContext().getRealm();
@ -351,7 +363,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
// allowed on JDK 1.8, attempt of such a modification throws ConcurrentModificationException with JDK 9+
if (provider == null) {
final String realmId = realm.getId();
ProviderFactory<T> providerFactory = factory.getProviderFactory(clazz, realmId, componentId, KeycloakModelUtils.componentModelGetter(realmId, componentId));
ProviderFactory<T> providerFactory = factory.getProviderFactory(clazz, realmId, componentId, modelGetter);
if (providerFactory != null) {
provider = providerFactory.create(this);
providers.put(hash, provider);

View file

@ -22,16 +22,17 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.JsonNode;
import org.jboss.logging.Logger;
import org.keycloak.common.Profile;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.JsonConfigComponentModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@ -71,75 +72,57 @@ public class ClientPoliciesUtil {
}
/**
* gets existing client profiles in a realm as model.
* not return null.
* Gets existing client profile of given name with resolved executor providers. It can be profile from realm or from global client profiles.
*/
static Map<String, ClientProfileModel> getClientProfilesModel(KeycloakSession session, RealmModel realm, List<ClientProfileRepresentation> globalClientProfiles) {
// get existing profiles as json
String profilesJson = getClientProfilesJsonString(realm);
if (profilesJson == null) {
return Collections.emptyMap();
}
// deserialize existing profiles (json -> representation)
ClientProfilesRepresentation profilesRep = null;
try {
profilesRep = convertClientProfilesJsonToRepresentation(profilesJson);
} catch (ClientPolicyException e) {
logger.warnv("Failed to serialize client profiles json string. err={0}, errDetail={1}", e.getError(), e.getErrorDetail());
return Collections.emptyMap();
}
if (profilesRep == null || profilesRep.getProfiles() == null) {
return Collections.emptyMap();
}
static ClientProfile getClientProfileModel(KeycloakSession session, RealmModel realm, ClientProfilesRepresentation profilesRep, List<ClientProfileRepresentation> globalClientProfiles, String profileName) throws ClientPolicyException {
// Obtain profiles from realm
List<ClientProfileRepresentation> profiles = profilesRep.getProfiles();
if (profiles == null) {
profiles = new ArrayList<>();
}
// Add global profiles as well
profiles.addAll(globalClientProfiles);
// constructing existing profiles (representation -> model)
Map<String, ClientProfileModel> profileMap = new HashMap<>();
for (ClientProfileRepresentation profileRep : profilesRep.getProfiles()) {
// ignore profile without name
if (profileRep.getName() == null) {
continue;
}
ClientProfileModel profileModel = new ClientProfileModel();
profileModel.setName(profileRep.getName());
profileModel.setDescription(profileRep.getDescription());
if (profileRep.getExecutors() == null) {
profileModel.setExecutors(new ArrayList<>());
profileMap.put(profileRep.getName(), profileModel);
continue;
}
List<ClientPolicyExecutorProvider> executors = new ArrayList<>();
if (profileRep.getExecutors() != null) {
for (ClientPolicyExecutorRepresentation executorRep : profileRep.getExecutors()) {
ClientPolicyExecutorProvider provider = session.getProvider(ClientPolicyExecutorProvider.class, executorRep.getExecutorProviderId());
if (provider == null) {
// executor's provider not found. just skip it.
logger.warnf("Executor with provider ID %s not found", executorRep.getExecutorProviderId());
continue;
}
try {
ClientPolicyExecutorConfigurationRepresentation configuration = (ClientPolicyExecutorConfigurationRepresentation) JsonSerialization.mapper.convertValue(executorRep.getConfiguration(), provider.getExecutorConfigurationClass());
provider.setupConfiguration(configuration);
executors.add(provider);
} catch (IllegalArgumentException iae) {
logger.warnv("failed for Configuration Setup during setup provider {0} :: error = {1}", executorRep.getExecutorProviderId(), iae.getMessage());
}
}
}
profileModel.setExecutors(executors);
profileMap.put(profileRep.getName(), profileModel);
ClientProfileRepresentation profileRep = profiles.stream()
.filter(clientProfile -> profileName.equals(clientProfile.getName()))
.findFirst().orElse(null);
if (profileRep == null) {
return null;
}
return profileMap;
ClientProfile profileModel = new ClientProfile();
profileModel.setName(profileRep.getName());
profileModel.setDescription(profileRep.getDescription());
if (profileRep.getExecutors() == null) {
profileModel.setExecutors(new ArrayList<>());
return profileModel;
}
List<ClientPolicyExecutorProvider> executors = new ArrayList<>();
if (profileRep.getExecutors() != null) {
for (ClientPolicyExecutorRepresentation executorRep : profileRep.getExecutors()) {
ClientPolicyExecutorProvider provider = getExecutorProvider(session, realm, executorRep.getExecutorProviderId(), executorRep.getConfiguration());
executors.add(provider);
}
}
profileModel.setExecutors(executors);
return profileModel;
}
private static ClientPolicyExecutorProvider getExecutorProvider(KeycloakSession session, RealmModel realm, String providerId, JsonNode config) {
ComponentModel componentModel = new JsonConfigComponentModel(ClientPolicyExecutorProvider.class, realm.getId(), providerId, config);
ClientPolicyExecutorProvider executorProvider = session.getComponentProvider(ClientPolicyExecutorProvider.class, componentModel.getId(), sessionFactory -> componentModel);
if (executorProvider == null) {
// condition's provider not found. just skip it.
throw new IllegalStateException("Executor with provider ID " + providerId + " not found");
}
ClientPolicyExecutorConfigurationRepresentation configuration = (ClientPolicyExecutorConfigurationRepresentation) JsonSerialization.mapper.convertValue(config, executorProvider.getExecutorConfigurationClass());
executorProvider.setupConfiguration(configuration);
return executorProvider;
}
/**
@ -306,10 +289,10 @@ public class ClientPoliciesUtil {
}
/**
* get existing enabled client policies in a realm as model.
* Gets existing enabled client policies in a realm.
* not return null.
*/
static List<ClientPolicyModel> getEnabledClientPoliciesModel(KeycloakSession session, RealmModel realm) {
static List<ClientPolicy> getEnabledClientPolicies(KeycloakSession session, RealmModel realm) {
// get existing profiles as json
String policiesJson = getClientPoliciesJsonString(realm);
if (policiesJson == null) {
@ -329,7 +312,7 @@ public class ClientPoliciesUtil {
}
// constructing existing policies (representation -> model)
List<ClientPolicyModel> policyList = new ArrayList<>();
List<ClientPolicy> policyList = new ArrayList<>();
for (ClientPolicyRepresentation policyRep: policiesRep.getPolicies()) {
// ignore policy without name
if (policyRep.getName() == null) {
@ -341,7 +324,7 @@ public class ClientPoliciesUtil {
continue;
}
ClientPolicyModel policyModel = new ClientPolicyModel();
ClientPolicy policyModel = new ClientPolicy();
policyModel.setName(policyRep.getName());
policyModel.setDescription(policyRep.getDescription());
policyModel.setEnable(true);
@ -349,20 +332,8 @@ public class ClientPoliciesUtil {
List<ClientPolicyConditionProvider> conditions = new ArrayList<>();
if (policyRep.getConditions() != null) {
for (ClientPolicyConditionRepresentation conditionRep : policyRep.getConditions()) {
ClientPolicyConditionProvider provider = session.getProvider(ClientPolicyConditionProvider.class, conditionRep.getConditionProviderId());
if (provider == null) {
// condition's provider not found. just skip it.
logger.warnf("Condition with provider ID %s not found", conditionRep.getConditionProviderId());
continue;
}
try {
ClientPolicyConditionConfigurationRepresentation configuration = (ClientPolicyConditionConfigurationRepresentation) JsonSerialization.mapper.convertValue(conditionRep.getConfiguration(), provider.getConditionConfigurationClass());
provider.setupConfiguration(configuration);
conditions.add(provider);
} catch (IllegalArgumentException iae) {
logger.warnv("failed for Configuration Setup :: error = {0}", iae.getMessage());
}
ClientPolicyConditionProvider provider = getConditionProvider(session, realm, conditionRep.getConditionProviderId(), conditionRep.getConfiguration());
conditions.add(provider);
}
}
policyModel.setConditions(conditions);
@ -377,6 +348,19 @@ public class ClientPoliciesUtil {
return policyList;
}
private static ClientPolicyConditionProvider getConditionProvider(KeycloakSession session, RealmModel realm, String providerId, JsonNode config) {
ComponentModel componentModel = new JsonConfigComponentModel(ClientPolicyConditionProvider.class, realm.getId(), providerId, config);
ClientPolicyConditionProvider conditionProvider = session.getComponentProvider(ClientPolicyConditionProvider.class, componentModel.getId(), sessionFactory -> componentModel);
if (conditionProvider == null) {
// condition's provider not found. just skip it.
throw new IllegalStateException("Condition with provider ID " + providerId + " not found");
}
ClientPolicyConditionConfigurationRepresentation configuration = (ClientPolicyConditionConfigurationRepresentation) JsonSerialization.mapper.convertValue(config, conditionProvider.getConditionConfigurationClass());
conditionProvider.setupConfiguration(configuration);
return conditionProvider;
}
/**
* convert client policies as representation to json.
* can return null.

View file

@ -13,6 +13,7 @@
* 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.services.clientpolicy;
@ -25,7 +26,7 @@ import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvide
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientPolicyModel implements Serializable {
class ClientPolicy implements Serializable {
protected String name;
protected String description;

View file

@ -26,7 +26,7 @@ import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientProfileModel implements Serializable {
class ClientProfile implements Serializable {
protected String name;
protected String description;

View file

@ -20,9 +20,7 @@ package org.keycloak.services.clientpolicy;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
import org.keycloak.common.Profile;
@ -68,15 +66,14 @@ public class DefaultClientPolicyManager implements ClientPolicyManager {
}
private void doPolicyOperation(ClientConditionOperation condition, ClientExecutorOperation executor, RealmModel realm) throws ClientPolicyException {
Map<String, ClientProfileModel> map = ClientPoliciesUtil.getClientProfilesModel(session, realm, globalClientProfilesSupplier.get());
List<ClientPolicyModel> list = ClientPoliciesUtil.getEnabledClientPoliciesModel(session, realm).stream().collect(Collectors.toList());
List<ClientPolicy> list = ClientPoliciesUtil.getEnabledClientPolicies(session, realm);
if (list == null || list.isEmpty()) {
logger.trace("POLICY OPERATION :: No enabled policy.");
return;
}
for (ClientPolicyModel policy: list) {
for (ClientPolicy policy: list) {
logger.tracev("POLICY OPERATION :: policy name = {0}", policy.getName());
if (!isSatisfied(policy, condition)) {
logger.tracev("POLICY UNSATISFIED :: policy name = {0}", policy.getName());
@ -84,12 +81,12 @@ public class DefaultClientPolicyManager implements ClientPolicyManager {
}
logger.tracev("POLICY APPLIED :: policy name = {0}", policy.getName());
execute(policy, executor, map);
execute(policy, executor, realm);
}
}
private boolean isSatisfied(
ClientPolicyModel policy,
ClientPolicy policy,
ClientConditionOperation op) throws ClientPolicyException {
if (policy.getConditions() == null || policy.getConditions().isEmpty()) {
@ -133,16 +130,20 @@ public class DefaultClientPolicyManager implements ClientPolicyManager {
}
private void execute(
ClientPolicyModel policy,
ClientPolicy policy,
ClientExecutorOperation op,
Map<String, ClientProfileModel> map) throws ClientPolicyException {
RealmModel realm) throws ClientPolicyException {
if (policy.getProfiles() == null || policy.getProfiles().isEmpty()) {
logger.tracev("NO PROFILE :: policy name = {0}", policy.getName());
return;
}
// Get profiles from realm
ClientProfilesRepresentation clientProfiles = ClientPoliciesUtil.getClientProfilesRepresentation(session, realm);
for (String profileName : policy.getProfiles()) {
ClientProfileModel profile = map.get(profileName);
ClientProfile profile = ClientPoliciesUtil.getClientProfileModel(session, realm, clientProfiles, globalClientProfilesSupplier.get(), profileName);
if (profile == null) {
logger.tracev("PROFILE NOT FOUND :: policy name = {0}, profile name = {1}", policy.getName(), profileName);
continue;

View file

@ -279,7 +279,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
}
protected void assertExpectedLoadedProfiles(Consumer<ClientProfilesRepresentation> modifiedAssertion) {
protected void assertExpectedLoadedProfiles(Consumer<ClientProfilesRepresentation> modifiedAssertion) throws Exception {
// retrieve loaded builtin profiles
ClientProfilesRepresentation actualProfilesRep = getProfilesWithGlobals();
@ -768,6 +768,11 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
profilesRep.setProfiles(new ArrayList<>());
}
// Create client profile from existing representation
public ClientProfilesBuilder(ClientProfilesRepresentation existingRep) {
this.profilesRep = existingRep;
}
public ClientProfilesBuilder addProfile(ClientProfileRepresentation rep) {
profilesRep.getProfiles().add(rep);
return this;
@ -809,11 +814,14 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
return this;
}
public ClientProfileBuilder addExecutor(String providerId, ClientPolicyExecutorConfigurationRepresentation config) {
public ClientProfileBuilder addExecutor(String providerId, ClientPolicyExecutorConfigurationRepresentation config) throws Exception {
if (config == null) {
config = new ClientPolicyExecutorConfigurationRepresentation();
}
profileRep.getExecutors().add(new ClientPolicyExecutorRepresentation(providerId, config));
ClientPolicyExecutorRepresentation executor = new ClientPolicyExecutorRepresentation();
executor.setExecutorProviderId(providerId);
executor.setConfiguration(JsonSerialization.mapper.readValue(JsonSerialization.mapper.writeValueAsBytes(config), JsonNode.class));
profileRep.getExecutors().add(executor);
return this;
}
@ -930,8 +938,11 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
return this;
}
public ClientPolicyBuilder addCondition(String providerId, ClientPolicyConditionConfigurationRepresentation config) {
policyRep.getConditions().add(new ClientPolicyConditionRepresentation(providerId, config));
public ClientPolicyBuilder addCondition(String providerId, ClientPolicyConditionConfigurationRepresentation config) throws Exception {
ClientPolicyConditionRepresentation condition = new ClientPolicyConditionRepresentation();
condition.setConditionProviderId(providerId);
condition.setConfiguration(JsonSerialization.mapper.readValue(JsonSerialization.mapper.writeValueAsBytes(config), JsonNode.class));
policyRep.getConditions().add(condition);
return this;
}
@ -1278,16 +1289,15 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
assertExpectedAugmenedExecutor(isAugment, PKCEEnforcerExecutorFactory.PROVIDER_ID, profileRep);
}
protected void assertExpectedSecureClientAuthEnforceExecutor(List<String> clientAuthns, boolean isAugment, String clientAuthnsAugment, ClientProfileRepresentation profileRep) {
protected void assertExpectedSecureClientAuthEnforceExecutor(List<String> clientAuthns, boolean isAugment, String clientAuthnsAugment, ClientProfileRepresentation profileRep) throws Exception {
assertExpectedAugmenedExecutor(isAugment, SecureClientAuthenticatorExecutorFactory.PROVIDER_ID, profileRep);
assertNotNull(profileRep);
Map<String, Object> actualExecutorConfig = getConfigOfExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID, profileRep);
JsonNode actualExecutorConfig = getConfigOfExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID, profileRep);
assertNotNull(actualExecutorConfig);
Set<String> actualClientAuthns = new HashSet<>((Collection<String>) actualExecutorConfig.get("client-authns"));
Set<String> actualClientAuthns = new HashSet<>((Collection<String>) JsonSerialization.readValue(actualExecutorConfig.get("client-authns").toString(), List.class));
assertEquals(new HashSet<>(clientAuthns), actualClientAuthns);
String actualClientAuthnAugment = actualExecutorConfig.get("client-authns-augment").toString();
String actualClientAuthnAugment = actualExecutorConfig.get("client-authns-augment").textValue();
assertEquals(clientAuthnsAugment, actualClientAuthnAugment);
}
@ -1317,17 +1327,17 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
protected void assertExpectedAugmenedExecutor(boolean isAugment, String providerId, ClientProfileRepresentation profileRep) {
assertNotNull(profileRep);
Map<String, Object> actualExecutorConfig = getConfigOfExecutor(providerId, profileRep);
JsonNode actualExecutorConfig = getConfigOfExecutor(providerId, profileRep);
assertNotNull(actualExecutorConfig);
boolean actualIsAugment = actualExecutorConfig.get("is-augment") == null ? false : (Boolean) actualExecutorConfig.get("is-augment");
boolean actualIsAugment = actualExecutorConfig.get("is-augment") == null ? false : actualExecutorConfig.get("is-augment").asBoolean();
assertEquals(isAugment, actualIsAugment);
}
private Map<String, Object> getConfigOfExecutor(String providerId, ClientProfileRepresentation profileRep) {
private JsonNode getConfigOfExecutor(String providerId, ClientProfileRepresentation profileRep) {
ClientPolicyExecutorRepresentation executorRep = profileRep.getExecutors().stream()
.filter(profileRepp -> providerId.equals(profileRepp.getExecutorProviderId()))
.findFirst().orElse(null);
return executorRep == null ? null : executorRep.getConfiguration().getConfigAsMap();
return executorRep == null ? null : executorRep.getConfiguration();
}
// Assertions about policies
@ -1455,7 +1465,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
}
private void assertExpectedEmptyConfig(String executorProviderId, ClientProfileRepresentation profileRep) {
Map<String, Object> config = getConfigOfExecutor(executorProviderId, profileRep);
JsonNode config = getConfigOfExecutor(executorProviderId, profileRep);
Assert.assertTrue("Expected empty configuration for provider " + executorProviderId, config.isEmpty());
}

View file

@ -72,7 +72,7 @@ public class ClientPoliciesImportExportTest extends AbstractClientPoliciesTest {
testRealmExportImport();
}
private void testRealmExportImport() throws LifecycleException {
private void testRealmExportImport() throws Exception {
testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT);
testingClient.testing().exportImport().setRealmName("test");

View file

@ -207,6 +207,31 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
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,
createSecureClientAuthEnforceExecutorConfig(Boolean.FALSE,
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);
@ -2051,7 +2076,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
}
}
private void setupPolicyClientIdAndSecretNotAcceptableAuthType(String policyName) throws ClientPolicyException {
private void setupPolicyClientIdAndSecretNotAcceptableAuthType(String policyName) throws Exception {
// register profiles
String profileName = "MyProfile";
String json = (new ClientProfilesBuilder()).addProfile(
@ -2075,7 +2100,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
updatePolicies(json);
}
private void setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(String policyName) throws ClientPolicyException {
private void setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(String policyName) throws Exception {
// register profiles
String profileName = "MyProfile";
String json = (new ClientProfilesBuilder()).addProfile(