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:
parent
c2e2cbe180
commit
71dcbec642
14 changed files with 329 additions and 136 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
||||
}
|
20
core/src/test/resources/sample-client-policy.json
Normal file
20
core/src/test/resources/sample-client-policy.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
ClientProfileRepresentation profileRep = profiles.stream()
|
||||
.filter(clientProfile -> profileName.equals(clientProfile.getName()))
|
||||
.findFirst().orElse(null);
|
||||
if (profileRep == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClientProfileModel profileModel = new ClientProfileModel();
|
||||
ClientProfile profileModel = new ClientProfile();
|
||||
profileModel.setName(profileRep.getName());
|
||||
profileModel.setDescription(profileRep.getDescription());
|
||||
|
||||
if (profileRep.getExecutors() == null) {
|
||||
profileModel.setExecutors(new ArrayList<>());
|
||||
profileMap.put(profileRep.getName(), profileModel);
|
||||
continue;
|
||||
return profileModel;
|
||||
}
|
||||
|
||||
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);
|
||||
ClientPolicyExecutorProvider provider = getExecutorProvider(session, realm, executorRep.getExecutorProviderId(), executorRep.getConfiguration());
|
||||
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);
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
ClientPolicyConditionProvider provider = getConditionProvider(session, realm, conditionRep.getConditionProviderId(), conditionRep.getConfiguration());
|
||||
conditions.add(provider);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
logger.warnv("failed for Configuration Setup :: error = {0}", iae.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue