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

View file

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

View file

@ -22,6 +22,10 @@ import org.junit.Test;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.adapters.config.AdapterConfig; 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.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
@ -30,6 +34,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -185,5 +190,26 @@ public class JsonParserTest {
return JsonSerialization.readValue(repp, Map.class); 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 org.keycloak.vault.VaultTranscriber;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @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); <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> * @param <T>

View file

@ -65,6 +65,7 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -337,8 +338,19 @@ public class DefaultKeycloakSession implements KeycloakSession {
} }
@Override @Override
@SuppressWarnings("unchecked")
public <T extends Provider> T getComponentProvider(Class<T> clazz, String componentId) { 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(); Integer hash = clazz.hashCode() + componentId.hashCode();
T provider = (T) providers.get(hash); T provider = (T) providers.get(hash);
final RealmModel realm = getContext().getRealm(); 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+ // allowed on JDK 1.8, attempt of such a modification throws ConcurrentModificationException with JDK 9+
if (provider == null) { if (provider == null) {
final String realmId = realm.getId(); 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) { if (providerFactory != null) {
provider = providerFactory.create(this); provider = providerFactory.create(this);
providers.put(hash, provider); providers.put(hash, provider);

View file

@ -22,16 +22,17 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.JsonNode;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.JsonConfigComponentModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -71,75 +72,57 @@ public class ClientPoliciesUtil {
} }
/** /**
* gets existing client profiles in a realm as model. * Gets existing client profile of given name with resolved executor providers. It can be profile from realm or from global client profiles.
* not return null.
*/ */
static Map<String, ClientProfileModel> getClientProfilesModel(KeycloakSession session, RealmModel realm, List<ClientProfileRepresentation> globalClientProfiles) { static ClientProfile getClientProfileModel(KeycloakSession session, RealmModel realm, ClientProfilesRepresentation profilesRep, List<ClientProfileRepresentation> globalClientProfiles, String profileName) throws ClientPolicyException {
// get existing profiles as json // Obtain profiles from realm
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();
}
List<ClientProfileRepresentation> profiles = profilesRep.getProfiles(); List<ClientProfileRepresentation> profiles = profilesRep.getProfiles();
if (profiles == null) {
profiles = new ArrayList<>();
}
// Add global profiles as well // Add global profiles as well
profiles.addAll(globalClientProfiles); profiles.addAll(globalClientProfiles);
// constructing existing profiles (representation -> model) ClientProfileRepresentation profileRep = profiles.stream()
Map<String, ClientProfileModel> profileMap = new HashMap<>(); .filter(clientProfile -> profileName.equals(clientProfile.getName()))
for (ClientProfileRepresentation profileRep : profilesRep.getProfiles()) { .findFirst().orElse(null);
// ignore profile without name if (profileRep == null) {
if (profileRep.getName() == null) { return null;
continue;
} }
ClientProfileModel profileModel = new ClientProfileModel(); ClientProfile profileModel = new ClientProfile();
profileModel.setName(profileRep.getName()); profileModel.setName(profileRep.getName());
profileModel.setDescription(profileRep.getDescription()); profileModel.setDescription(profileRep.getDescription());
if (profileRep.getExecutors() == null) { if (profileRep.getExecutors() == null) {
profileModel.setExecutors(new ArrayList<>()); profileModel.setExecutors(new ArrayList<>());
profileMap.put(profileRep.getName(), profileModel); return profileModel;
continue;
} }
List<ClientPolicyExecutorProvider> executors = new ArrayList<>(); List<ClientPolicyExecutorProvider> executors = new ArrayList<>();
if (profileRep.getExecutors() != null) { if (profileRep.getExecutors() != null) {
for (ClientPolicyExecutorRepresentation executorRep : profileRep.getExecutors()) { for (ClientPolicyExecutorRepresentation executorRep : profileRep.getExecutors()) {
ClientPolicyExecutorProvider provider = session.getProvider(ClientPolicyExecutorProvider.class, executorRep.getExecutorProviderId()); ClientPolicyExecutorProvider provider = getExecutorProvider(session, realm, executorRep.getExecutorProviderId(), executorRep.getConfiguration());
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); 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); profileModel.setExecutors(executors);
profileMap.put(profileRep.getName(), profileModel); return profileModel;
} }
return profileMap; 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. * not return null.
*/ */
static List<ClientPolicyModel> getEnabledClientPoliciesModel(KeycloakSession session, RealmModel realm) { static List<ClientPolicy> getEnabledClientPolicies(KeycloakSession session, RealmModel realm) {
// get existing profiles as json // get existing profiles as json
String policiesJson = getClientPoliciesJsonString(realm); String policiesJson = getClientPoliciesJsonString(realm);
if (policiesJson == null) { if (policiesJson == null) {
@ -329,7 +312,7 @@ public class ClientPoliciesUtil {
} }
// constructing existing policies (representation -> model) // constructing existing policies (representation -> model)
List<ClientPolicyModel> policyList = new ArrayList<>(); List<ClientPolicy> policyList = new ArrayList<>();
for (ClientPolicyRepresentation policyRep: policiesRep.getPolicies()) { for (ClientPolicyRepresentation policyRep: policiesRep.getPolicies()) {
// ignore policy without name // ignore policy without name
if (policyRep.getName() == null) { if (policyRep.getName() == null) {
@ -341,7 +324,7 @@ public class ClientPoliciesUtil {
continue; continue;
} }
ClientPolicyModel policyModel = new ClientPolicyModel(); ClientPolicy policyModel = new ClientPolicy();
policyModel.setName(policyRep.getName()); policyModel.setName(policyRep.getName());
policyModel.setDescription(policyRep.getDescription()); policyModel.setDescription(policyRep.getDescription());
policyModel.setEnable(true); policyModel.setEnable(true);
@ -349,20 +332,8 @@ public class ClientPoliciesUtil {
List<ClientPolicyConditionProvider> conditions = new ArrayList<>(); List<ClientPolicyConditionProvider> conditions = new ArrayList<>();
if (policyRep.getConditions() != null) { if (policyRep.getConditions() != null) {
for (ClientPolicyConditionRepresentation conditionRep : policyRep.getConditions()) { for (ClientPolicyConditionRepresentation conditionRep : policyRep.getConditions()) {
ClientPolicyConditionProvider provider = session.getProvider(ClientPolicyConditionProvider.class, conditionRep.getConditionProviderId()); ClientPolicyConditionProvider provider = getConditionProvider(session, realm, conditionRep.getConditionProviderId(), conditionRep.getConfiguration());
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); conditions.add(provider);
} catch (IllegalArgumentException iae) {
logger.warnv("failed for Configuration Setup :: error = {0}", iae.getMessage());
}
} }
} }
policyModel.setConditions(conditions); policyModel.setConditions(conditions);
@ -377,6 +348,19 @@ public class ClientPoliciesUtil {
return policyList; 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. * convert client policies as representation to json.
* can return null. * can return null.

View file

@ -13,6 +13,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.keycloak.services.clientpolicy; 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> * @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 name;
protected String description; 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> * @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 name;
protected String description; protected String description;

View file

@ -20,9 +20,7 @@ package org.keycloak.services.clientpolicy;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.common.Profile; 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 { private void doPolicyOperation(ClientConditionOperation condition, ClientExecutorOperation executor, RealmModel realm) throws ClientPolicyException {
Map<String, ClientProfileModel> map = ClientPoliciesUtil.getClientProfilesModel(session, realm, globalClientProfilesSupplier.get()); List<ClientPolicy> list = ClientPoliciesUtil.getEnabledClientPolicies(session, realm);
List<ClientPolicyModel> list = ClientPoliciesUtil.getEnabledClientPoliciesModel(session, realm).stream().collect(Collectors.toList());
if (list == null || list.isEmpty()) { if (list == null || list.isEmpty()) {
logger.trace("POLICY OPERATION :: No enabled policy."); logger.trace("POLICY OPERATION :: No enabled policy.");
return; return;
} }
for (ClientPolicyModel policy: list) { for (ClientPolicy policy: list) {
logger.tracev("POLICY OPERATION :: policy name = {0}", policy.getName()); logger.tracev("POLICY OPERATION :: policy name = {0}", policy.getName());
if (!isSatisfied(policy, condition)) { if (!isSatisfied(policy, condition)) {
logger.tracev("POLICY UNSATISFIED :: policy name = {0}", policy.getName()); 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()); logger.tracev("POLICY APPLIED :: policy name = {0}", policy.getName());
execute(policy, executor, map); execute(policy, executor, realm);
} }
} }
private boolean isSatisfied( private boolean isSatisfied(
ClientPolicyModel policy, ClientPolicy policy,
ClientConditionOperation op) throws ClientPolicyException { ClientConditionOperation op) throws ClientPolicyException {
if (policy.getConditions() == null || policy.getConditions().isEmpty()) { if (policy.getConditions() == null || policy.getConditions().isEmpty()) {
@ -133,16 +130,20 @@ public class DefaultClientPolicyManager implements ClientPolicyManager {
} }
private void execute( private void execute(
ClientPolicyModel policy, ClientPolicy policy,
ClientExecutorOperation op, ClientExecutorOperation op,
Map<String, ClientProfileModel> map) throws ClientPolicyException { RealmModel realm) throws ClientPolicyException {
if (policy.getProfiles() == null || policy.getProfiles().isEmpty()) { if (policy.getProfiles() == null || policy.getProfiles().isEmpty()) {
logger.tracev("NO PROFILE :: policy name = {0}", policy.getName()); 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()) { for (String profileName : policy.getProfiles()) {
ClientProfileModel profile = map.get(profileName); ClientProfile profile = ClientPoliciesUtil.getClientProfileModel(session, realm, clientProfiles, globalClientProfilesSupplier.get(), profileName);
if (profile == null) { if (profile == null) {
logger.tracev("PROFILE NOT FOUND :: policy name = {0}, profile name = {1}", policy.getName(), profileName); logger.tracev("PROFILE NOT FOUND :: policy name = {0}, profile name = {1}", policy.getName(), profileName);
continue; 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 // retrieve loaded builtin profiles
ClientProfilesRepresentation actualProfilesRep = getProfilesWithGlobals(); ClientProfilesRepresentation actualProfilesRep = getProfilesWithGlobals();
@ -768,6 +768,11 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
profilesRep.setProfiles(new ArrayList<>()); profilesRep.setProfiles(new ArrayList<>());
} }
// Create client profile from existing representation
public ClientProfilesBuilder(ClientProfilesRepresentation existingRep) {
this.profilesRep = existingRep;
}
public ClientProfilesBuilder addProfile(ClientProfileRepresentation rep) { public ClientProfilesBuilder addProfile(ClientProfileRepresentation rep) {
profilesRep.getProfiles().add(rep); profilesRep.getProfiles().add(rep);
return this; return this;
@ -809,11 +814,14 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
return this; return this;
} }
public ClientProfileBuilder addExecutor(String providerId, ClientPolicyExecutorConfigurationRepresentation config) { public ClientProfileBuilder addExecutor(String providerId, ClientPolicyExecutorConfigurationRepresentation config) throws Exception {
if (config == null) { if (config == null) {
config = new ClientPolicyExecutorConfigurationRepresentation(); 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; return this;
} }
@ -930,8 +938,11 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
return this; return this;
} }
public ClientPolicyBuilder addCondition(String providerId, ClientPolicyConditionConfigurationRepresentation config) { public ClientPolicyBuilder addCondition(String providerId, ClientPolicyConditionConfigurationRepresentation config) throws Exception {
policyRep.getConditions().add(new ClientPolicyConditionRepresentation(providerId, config)); ClientPolicyConditionRepresentation condition = new ClientPolicyConditionRepresentation();
condition.setConditionProviderId(providerId);
condition.setConfiguration(JsonSerialization.mapper.readValue(JsonSerialization.mapper.writeValueAsBytes(config), JsonNode.class));
policyRep.getConditions().add(condition);
return this; return this;
} }
@ -1278,16 +1289,15 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
assertExpectedAugmenedExecutor(isAugment, PKCEEnforcerExecutorFactory.PROVIDER_ID, profileRep); 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); assertExpectedAugmenedExecutor(isAugment, SecureClientAuthenticatorExecutorFactory.PROVIDER_ID, profileRep);
assertNotNull(profileRep); assertNotNull(profileRep);
Map<String, Object> actualExecutorConfig = getConfigOfExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID, profileRep); JsonNode actualExecutorConfig = getConfigOfExecutor(SecureClientAuthenticatorExecutorFactory.PROVIDER_ID, profileRep);
assertNotNull(actualExecutorConfig); assertNotNull(actualExecutorConfig);
Set<String> actualClientAuthns = new HashSet<>((Collection<String>) JsonSerialization.readValue(actualExecutorConfig.get("client-authns").toString(), List.class));
Set<String> actualClientAuthns = new HashSet<>((Collection<String>) actualExecutorConfig.get("client-authns"));
assertEquals(new HashSet<>(clientAuthns), actualClientAuthns); assertEquals(new HashSet<>(clientAuthns), actualClientAuthns);
String actualClientAuthnAugment = actualExecutorConfig.get("client-authns-augment").toString(); String actualClientAuthnAugment = actualExecutorConfig.get("client-authns-augment").textValue();
assertEquals(clientAuthnsAugment, actualClientAuthnAugment); assertEquals(clientAuthnsAugment, actualClientAuthnAugment);
} }
@ -1317,17 +1327,17 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
protected void assertExpectedAugmenedExecutor(boolean isAugment, String providerId, ClientProfileRepresentation profileRep) { protected void assertExpectedAugmenedExecutor(boolean isAugment, String providerId, ClientProfileRepresentation profileRep) {
assertNotNull(profileRep); assertNotNull(profileRep);
Map<String, Object> actualExecutorConfig = getConfigOfExecutor(providerId, profileRep); JsonNode actualExecutorConfig = getConfigOfExecutor(providerId, profileRep);
assertNotNull(actualExecutorConfig); 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); assertEquals(isAugment, actualIsAugment);
} }
private Map<String, Object> getConfigOfExecutor(String providerId, ClientProfileRepresentation profileRep) { private JsonNode getConfigOfExecutor(String providerId, ClientProfileRepresentation profileRep) {
ClientPolicyExecutorRepresentation executorRep = profileRep.getExecutors().stream() ClientPolicyExecutorRepresentation executorRep = profileRep.getExecutors().stream()
.filter(profileRepp -> providerId.equals(profileRepp.getExecutorProviderId())) .filter(profileRepp -> providerId.equals(profileRepp.getExecutorProviderId()))
.findFirst().orElse(null); .findFirst().orElse(null);
return executorRep == null ? null : executorRep.getConfiguration().getConfigAsMap(); return executorRep == null ? null : executorRep.getConfiguration();
} }
// Assertions about policies // Assertions about policies
@ -1455,7 +1465,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
} }
private void assertExpectedEmptyConfig(String executorProviderId, ClientProfileRepresentation profileRep) { 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()); Assert.assertTrue("Expected empty configuration for provider " + executorProviderId, config.isEmpty());
} }

View file

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

View file

@ -207,6 +207,31 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); 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 @Test
public void testAdminClientUpdateAcceptableAuthType() throws Exception { public void testAdminClientUpdateAcceptableAuthType() throws Exception {
setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); 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 // register profiles
String profileName = "MyProfile"; String profileName = "MyProfile";
String json = (new ClientProfilesBuilder()).addProfile( String json = (new ClientProfilesBuilder()).addProfile(
@ -2075,7 +2100,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
updatePolicies(json); updatePolicies(json);
} }
private void setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(String policyName) throws ClientPolicyException { private void setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(String policyName) throws Exception {
// register profiles // register profiles
String profileName = "MyProfile"; String profileName = "MyProfile";
String json = (new ClientProfilesBuilder()).addProfile( String json = (new ClientProfilesBuilder()).addProfile(