diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientPoliciesRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientPoliciesRepresentation.java index b7127581b1..fdb5fa493d 100644 --- a/core/src/main/java/org/keycloak/representations/idm/ClientPoliciesRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientPoliciesRepresentation.java @@ -17,18 +17,19 @@ package org.keycloak.representations.idm; +import java.util.ArrayList; import java.util.List; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.JsonNode; +import org.keycloak.util.JsonSerialization; /** * Client Policies' (the set of all Client Policy) external representation class * * @author Takashi Norimatsu */ -@JsonIgnoreProperties(ignoreUnknown = true) public class ClientPoliciesRepresentation { - protected List policies; + protected List policies = new ArrayList<>(); public List getPolicies() { return policies; @@ -38,4 +39,17 @@ public class ClientPoliciesRepresentation { this.policies = policies; } + @Override + public int hashCode() { + return JsonSerialization.mapper.convertValue(this, JsonNode.class).hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ClientPoliciesRepresentation)) return false; + JsonNode jsonNode = JsonSerialization.mapper.convertValue(this, JsonNode.class); + JsonNode jsonNodeThat = JsonSerialization.mapper.convertValue(obj, JsonNode.class); + return jsonNode.equals(jsonNodeThat); + } + } diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientPolicyConditionConfigurationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyConditionConfigurationRepresentation.java new file mode 100644 index 0000000000..c99817728c --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyConditionConfigurationRepresentation.java @@ -0,0 +1,57 @@ +/* + * 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.representations.idm; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Just adds some type-safety to the ClientPolicyConditionConfiguration + * + * @author Takashi Norimatsu + */ +public class ClientPolicyConditionConfigurationRepresentation { + + private Map configAsMap = new HashMap<>(); + + @JsonProperty("is-negative-logic") + private Boolean negativeLogic; + + public Boolean isNegativeLogic() { + return negativeLogic; + } + + public void setNegativeLogic(Boolean negativeLogic) { + this.negativeLogic = negativeLogic; + } + + @JsonAnyGetter + public Map getConfigAsMap() { + return configAsMap; + } + + @JsonAnySetter + public void setConfigAsMap(String name, Object value) { + this.configAsMap.put(name, value); + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientPolicyConditionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyConditionRepresentation.java new file mode 100644 index 0000000000..b6a9c33c20 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyConditionRepresentation.java @@ -0,0 +1,56 @@ +/* + * 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.representations.idm; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Marek Posolda + */ +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; + } + + public String getConditionProviderId() { + return conditionProviderId; + } + + public void setConditionProviderId(String conditionProviderId) { + this.conditionProviderId = conditionProviderId; + } + + public ClientPolicyConditionConfigurationRepresentation getConfiguration() { + return configuration; + } + + public void setConfiguration(ClientPolicyConditionConfigurationRepresentation configuration) { + this.configuration = configuration; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientPolicyExecutorConfigurationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyExecutorConfigurationRepresentation.java new file mode 100644 index 0000000000..e38a900f03 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyExecutorConfigurationRepresentation.java @@ -0,0 +1,45 @@ +/* + * 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.representations.idm; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; + +/** + * Just adds some type-safety to the ClientPolicyExecutorConfiguration + * + * @author Marek Posolda + */ +public class ClientPolicyExecutorConfigurationRepresentation { + + private Map configAsMap = new HashMap<>(); + + @JsonAnyGetter + public Map getConfigAsMap() { + return configAsMap; + } + + @JsonAnySetter + public void setConfigAsMap(String name, Object value) { + this.configAsMap.put(name, value); + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientPolicyExecutorRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyExecutorRepresentation.java new file mode 100644 index 0000000000..ab0f96d084 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyExecutorRepresentation.java @@ -0,0 +1,56 @@ +/* + * 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.representations.idm; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Marek Posolda + */ +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; + } + + public String getExecutorProviderId() { + return executorProviderId; + } + + public void setExecutorProviderId(String providerId) { + this.executorProviderId = providerId; + } + + public ClientPolicyExecutorConfigurationRepresentation getConfiguration() { + return configuration; + } + + public void setConfiguration(ClientPolicyExecutorConfigurationRepresentation configuration) { + this.configuration = configuration; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientPolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyRepresentation.java index 8b623d213d..3674d8c5b7 100644 --- a/core/src/main/java/org/keycloak/representations/idm/ClientPolicyRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyRepresentation.java @@ -19,21 +19,17 @@ package org.keycloak.representations.idm; import java.util.List; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - /** * Client Policy's external representation class * * @author Takashi Norimatsu */ -@JsonIgnoreProperties(ignoreUnknown = true) public class ClientPolicyRepresentation { protected String name; protected String description; - protected Boolean builtin; - protected Boolean enable; - protected List conditions; + protected Boolean enabled; + protected List conditions; protected List profiles; public String getName() { @@ -52,27 +48,19 @@ public class ClientPolicyRepresentation { this.description = description; } - public Boolean isBuiltin() { - return builtin; + public Boolean isEnabled() { + return enabled; } - public void setBuiltin(Boolean builtin) { - this.builtin = builtin; + public void setEnabled(Boolean enabled) { + this.enabled = enabled; } - public Boolean isEnable() { - return enable; - } - - public void setEnable(Boolean enable) { - this.enable = enable; - } - - public List getConditions() { + public List getConditions() { return conditions; } - public void setConditions(List conditions) { + public void setConditions(List conditions) { this.conditions = conditions; } diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientProfileRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientProfileRepresentation.java index 7c88fd2478..12b05c3af4 100644 --- a/core/src/main/java/org/keycloak/representations/idm/ClientProfileRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientProfileRepresentation.java @@ -19,20 +19,16 @@ package org.keycloak.representations.idm; import java.util.List; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - /** * Client Profile's external representation class * * @author Takashi Norimatsu */ -@JsonIgnoreProperties(ignoreUnknown = true) public class ClientProfileRepresentation { protected String name; protected String description; - protected Boolean builtin; - protected List executors; + protected List executors; public String getName() { return name; @@ -50,19 +46,11 @@ public class ClientProfileRepresentation { this.description = description; } - public Boolean isBuiltin() { - return builtin; - } - - public void setBuiltin(Boolean builtin) { - this.builtin = builtin; - } - - public List getExecutors() { + public List getExecutors() { return executors; } - public void setExecutors(List executors) { + public void setExecutors(List executors) { this.executors = executors; } } diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientProfilesRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientProfilesRepresentation.java index 1581e21a72..f3261814f8 100644 --- a/core/src/main/java/org/keycloak/representations/idm/ClientProfilesRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientProfilesRepresentation.java @@ -17,18 +17,25 @@ package org.keycloak.representations.idm; +import java.util.ArrayList; import java.util.List; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import org.keycloak.util.JsonSerialization; /** * Client Profiles' (the set of all Client Profile) external representation class * * @author Takashi Norimatsu */ -@JsonIgnoreProperties(ignoreUnknown = true) public class ClientProfilesRepresentation { - protected List profiles; + + private List profiles = new ArrayList<>(); + + // Global profiles, which are builtin in Keycloak. + @JsonProperty("globalProfiles") + private List globalProfiles; public List getProfiles() { return profiles; @@ -38,4 +45,24 @@ public class ClientProfilesRepresentation { this.profiles = profiles; } + public List getGlobalProfiles() { + return globalProfiles; + } + + public void setGlobalProfiles(List globalProfiles) { + this.globalProfiles = globalProfiles; + } + + @Override + public int hashCode() { + return JsonSerialization.mapper.convertValue(this, JsonNode.class).hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ClientProfilesRepresentation)) return false; + JsonNode jsonNode = JsonSerialization.mapper.convertValue(this, JsonNode.class); + JsonNode jsonNodeThat = JsonSerialization.mapper.convertValue(obj, JsonNode.class); + return jsonNode.equals(jsonNodeThat); + } } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesPoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesPoliciesResource.java index 68545b17ec..ffb626ceb1 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesPoliciesResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesPoliciesResource.java @@ -5,9 +5,9 @@ import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.ClientPoliciesRepresentation; /** * @author Takashi Norimatsu @@ -17,10 +17,10 @@ public interface ClientPoliciesPoliciesResource { @GET @NoCache @Produces(MediaType.APPLICATION_JSON) - String getPolicies(); + ClientPoliciesRepresentation getPolicies(); @PUT @Consumes(MediaType.APPLICATION_JSON) - Response updatePolicies(final String json); + void updatePolicies(final ClientPoliciesRepresentation clientPolicies); } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesProfilesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesProfilesResource.java index 93b9517d59..c4922c6b15 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesProfilesResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesProfilesResource.java @@ -4,10 +4,11 @@ import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.ClientProfilesRepresentation; /** * @author Takashi Norimatsu @@ -17,9 +18,14 @@ public interface ClientPoliciesProfilesResource { @GET @NoCache @Produces(MediaType.APPLICATION_JSON) - String getProfiles(); + ClientProfilesRepresentation getProfiles(@QueryParam("include-global-profiles") Boolean includeGlobalProfiles); + /** + * Update client profiles in the realm. The "globalProfiles" field of clientProfiles is ignored as it is not possible to update global profiles + * + * @param clientProfiles + */ @PUT @Consumes(MediaType.APPLICATION_JSON) - Response updateProfiles(final String json); + void updateProfiles(final ClientProfilesRepresentation clientProfiles); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java b/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java index 280a3ca608..9e0e7e2284 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java +++ b/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java @@ -50,7 +50,6 @@ public class QuarkusKeycloakApplication extends KeycloakApplication { QuarkusKeycloakSessionFactory instance = QuarkusKeycloakSessionFactory.getInstance(); sessionFactory = instance; instance.init(); - instance.create().clientPolicy().setupClientPoliciesOnKeycloakApp("/keycloak-default-client-profiles.json", "/keycloak-default-client-policies.json"); sessionFactory.publish(new PostMigrationEvent()); } diff --git a/server-spi-private/src/main/java/org/keycloak/models/Constants.java b/server-spi-private/src/main/java/org/keycloak/models/Constants.java index 0799c8198f..4e149d333e 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/Constants.java +++ b/server-spi-private/src/main/java/org/keycloak/models/Constants.java @@ -120,4 +120,8 @@ public final class Constants { */ public static final String STORAGE_BATCH_SIZE = "org.keycloak.storage.batch_size"; + // Client Polices Realm Attributes Keys + public static final String CLIENT_PROFILES = "client-policies.profiles"; + public static final String CLIENT_POLICIES = "client-policies.policies"; + } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 74b92d425b..4b61790e33 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -95,8 +95,8 @@ public class ModelToRepresentation { REALM_EXCLUDED_ATTRIBUTES.add("webAuthnPolicyAvoidSameAuthenticatorRegisterPasswordless"); REALM_EXCLUDED_ATTRIBUTES.add("webAuthnPolicyAcceptableAaguidsPasswordless"); - REALM_EXCLUDED_ATTRIBUTES.add("client-policies.profiles"); - REALM_EXCLUDED_ATTRIBUTES.add("client-policies.policies"); + REALM_EXCLUDED_ATTRIBUTES.add(Constants.CLIENT_POLICIES); + REALM_EXCLUDED_ATTRIBUTES.add(Constants.CLIENT_PROFILES); } @@ -295,7 +295,7 @@ public class ModelToRepresentation { return rep; } - public static RealmRepresentation toRepresentation(RealmModel realm, boolean internal) { + public static RealmRepresentation toRepresentation(KeycloakSession session, RealmModel realm, boolean internal) { RealmRepresentation rep = new RealmRepresentation(); rep.setId(realm.getId()); rep.setRealm(realm.getName()); @@ -447,6 +447,8 @@ public class ModelToRepresentation { exportGroups(realm, rep); } + session.clientPolicy().updateRealmRepresentationFromModel(realm, rep); + rep.setAttributes(stripRealmAttributesIncludedAsFields(realm.getAttributes())); if (!internal) { diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index e1de98f301..29af8510e2 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -1181,6 +1181,7 @@ public class RepresentationToModel { realm.setWebAuthnPolicyPasswordless(webAuthnPolicy); updateCibaSettings(rep, realm); + session.clientPolicy().updateRealmModelFromRepresentation(realm, rep); if (rep.getSmtpServer() != null) { Map config = new HashMap(rep.getSmtpServer()); diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPoliciesUtil.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPoliciesUtil.java deleted file mode 100644 index 69553ce346..0000000000 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPoliciesUtil.java +++ /dev/null @@ -1,762 +0,0 @@ -/* - * Copyright 2021 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.keycloak.services.clientpolicy; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import org.jboss.logging.Logger; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.representations.idm.ClientPoliciesRepresentation; -import org.keycloak.representations.idm.ClientPolicyRepresentation; -import org.keycloak.representations.idm.ClientProfileRepresentation; -import org.keycloak.representations.idm.ClientProfilesRepresentation; -import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionConfiguration; -import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; -import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorConfiguration; -import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; -import org.keycloak.util.JsonSerialization; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Utilities for treating client policies/profiles - * - * @author Takashi Norimatsu - */ -public class ClientPoliciesUtil { - - private static final Logger logger = Logger.getLogger(ClientPoliciesUtil.class); - - private static final ObjectMapper objectMapper = new ObjectMapper(); - - /** - * gets existing client profiles in a realm as representation. - * not return null. - */ - public static ClientProfilesRepresentation getClientProfilesRepresentation(KeycloakSession session, RealmModel realm) throws ClientPolicyException { - ClientProfilesRepresentation profilesRep = null; - String profilesJson = null; - - // get existing profiles json - if (realm != null) { - profilesJson = session.clientPolicy().getClientProfilesJsonString(realm); - } else { - // if realm not specified, use builtin profiles set in keycloak's binary. - profilesJson = session.clientPolicy().getClientProfilesOnKeycloakApp(); - } - - // deserialize existing profiles (json -> representation) - if (profilesJson == null) { - return new ClientProfilesRepresentation(); - } - profilesRep = convertClientProfilesJsonToRepresentation(profilesJson); - if (profilesRep == null) { - return new ClientProfilesRepresentation(); - } - - return profilesRep; - } - - /** - * gets existing client profiles in a realm as model. - * not return null. - */ - public static Map getClientProfilesModel(KeycloakSession session, RealmModel realm) { - // get existing profiles as json - String profilesJson = session.clientPolicy().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(); - } - - // constructing existing profiles (representation -> model) - Map 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.isBuiltin() != null) { - profileModel.setBuiltin(profileRep.isBuiltin().booleanValue()); - } else { - profileModel.setBuiltin(false); - } - - if (profileRep.getExecutors() == null) { - profileModel.setExecutors(new ArrayList<>()); - profileMap.put(profileRep.getName(), profileModel); - continue; - } - - List executors = new ArrayList<>(); - if (profileRep.getExecutors() != null) { - profileRep.getExecutors().stream().forEach(obj->{ - JsonNode node = objectMapper.convertValue(obj, JsonNode.class); - node.fields().forEachRemaining(executor->{ - ClientPolicyExecutorProvider provider = session.getProvider(ClientPolicyExecutorProvider.class, executor.getKey()); - if (provider == null) { - // executor's provider not found. just skip it. - return; - } - - try { - ClientPolicyExecutorConfiguration configuration = (ClientPolicyExecutorConfiguration) JsonSerialization.mapper.convertValue(executor.getValue(), provider.getExecutorConfigurationClass()); - provider.setupConfiguration(configuration); - executors.add(provider); - } catch (IllegalArgumentException iae) { - logger.warnv("failed for Configuration Setup :: error = {0}", iae.getMessage()); - } - }); - }); - } - profileModel.setExecutors(executors); - - profileMap.put(profileRep.getName(), profileModel); - } - - return profileMap; - } - - /** - * get validated and modified builtin client profiles set on keycloak app as representation. - * it is loaded from json file enclosed in keycloak's binary. - * not return null. - */ - public static ClientProfilesRepresentation getValidatedBuiltinClientProfilesRepresentation(KeycloakSession session, InputStream is) throws ClientPolicyException { - // load builtin client profiles representation - ClientProfilesRepresentation proposedProfilesRep = null; - try { - proposedProfilesRep = JsonSerialization.readValue(is, ClientProfilesRepresentation.class); - } catch (Exception e) { - throw new ClientPolicyException("failed to deserialize builtin proposed client profiles json string.", e.getMessage()); - } - if (proposedProfilesRep == null) { - return new ClientProfilesRepresentation(); - } - - // no profile contained (it is valid) - List proposedProfileRepList = proposedProfilesRep.getProfiles(); - if (proposedProfileRepList == null || proposedProfileRepList.isEmpty()) { - return new ClientProfilesRepresentation(); - } - - // duplicated profile name is not allowed. - if (proposedProfileRepList.size() != proposedProfileRepList.stream().map(i->i.getName()).distinct().count()) { - throw new ClientPolicyException("proposed builtin client profile name duplicated."); - } - - // construct validated and modified profiles from builtin profiles in JSON file enclosed in keycloak binary. - ClientProfilesRepresentation updatingProfilesRep = new ClientProfilesRepresentation(); - updatingProfilesRep.setProfiles(new ArrayList<>()); - List updatingProfileList = updatingProfilesRep.getProfiles(); - - for (ClientProfileRepresentation proposedProfileRep : proposedProfilesRep.getProfiles()) { - if (proposedProfileRep.getName() == null) { - throw new ClientPolicyException("client profile without its name not allowed."); - } - - // ignore proposed ordinal profile because builtin profile can only be added. - if (proposedProfileRep.isBuiltin() == null || !proposedProfileRep.isBuiltin()) { - throw new ClientPolicyException("ordinal client profile not allowed."); - } - - ClientProfileRepresentation profileRep = new ClientProfileRepresentation(); - profileRep.setName(proposedProfileRep.getName()); - profileRep.setDescription(proposedProfileRep.getDescription()); - profileRep.setBuiltin(Boolean.TRUE); - - profileRep.setExecutors(new ArrayList<>()); // to prevent returning null - if (proposedProfileRep.getExecutors() != null) { - for (Object executor : proposedProfileRep.getExecutors()) { - if (isValidExecutor(session, executor) == false) { - throw new ClientPolicyException("proposed client profile contains the executor with its invalid configuration."); - } - profileRep.getExecutors().add(executor); - } - } - - updatingProfileList.add(profileRep); - } - - return updatingProfilesRep; - } - - /** - * convert client profiles as representation to json. - * can return null. - */ - public static String convertClientProfilesRepresentationToJson(ClientProfilesRepresentation reps) throws ClientPolicyException { - return convertRepresentationToJson(reps); - } - - /** - * convert client profiles as json to representation. - * not return null. - */ - private static ClientProfilesRepresentation convertClientProfilesJsonToRepresentation(String json) throws ClientPolicyException { - return convertJsonToRepresentation(json, ClientProfilesRepresentation.class); - } - - /** - * get validated and modified client profiles as json. - * it can be constructed by merging proposed client profiles with existing client profiles. - * can return null. - */ - public static String getValidatedClientProfilesJson(KeycloakSession session, RealmModel realm, ClientProfilesRepresentation proposedProfilesRep) throws ClientPolicyException { - return convertClientProfilesRepresentationToJson(getValidatedClientProfilesRepresentation(session, realm, proposedProfilesRep)); - } - - /** - * get validated and modified client profiles as representation. - * it can be constructed by merging proposed client profiles with existing client profiles. - * not return null. - */ - private static ClientProfilesRepresentation getValidatedClientProfilesRepresentation(KeycloakSession session, RealmModel realm, ClientProfilesRepresentation proposedProfilesRep) throws ClientPolicyException { - if (proposedProfilesRep == null) { - proposedProfilesRep = new ClientProfilesRepresentation(); - } - if (realm == null) { - throw new ClientPolicyException("realm not specified."); - } - - // deserialize existing profiles (json -> representation) - ClientProfilesRepresentation existingProfilesRep = null; - String existingProfilesJson = session.clientPolicy().getClientProfilesJsonString(realm); - if (existingProfilesJson != null) { - existingProfilesRep = convertClientProfilesJsonToRepresentation(existingProfilesJson); - if (existingProfilesRep == null) { - existingProfilesRep = new ClientProfilesRepresentation(); - } - } else { - existingProfilesRep = new ClientProfilesRepresentation(); - } - - // no profile contained (it is valid) - // back to initial builtin profiles - List proposedProfileRepList = proposedProfilesRep.getProfiles(); - if (proposedProfileRepList == null || proposedProfileRepList.isEmpty()) { - proposedProfileRepList = new ArrayList<>(); - proposedProfilesRep.setProfiles(new ArrayList<>()); - } - - // duplicated profile name is not allowed. - if (proposedProfileRepList.size() != proposedProfileRepList.stream().map(i->i.getName()).distinct().count()) { - throw new ClientPolicyException("proposed client profile name duplicated."); - } - - // construct updating profiles from existing profiles and proposed profiles - ClientProfilesRepresentation updatingProfilesRep = new ClientProfilesRepresentation(); - updatingProfilesRep.setProfiles(new ArrayList<>()); - List updatingProfileList = updatingProfilesRep.getProfiles(); - - // add existing builtin profiles to updating profiles - List existingProfileList = existingProfilesRep.getProfiles(); - if (existingProfileList != null && !existingProfileList.isEmpty()) { - existingProfileList.stream().filter(i->i.isBuiltin()).forEach(i->updatingProfileList.add(i)); - } - - for (ClientProfileRepresentation proposedProfileRep : proposedProfilesRep.getProfiles()) { - if (proposedProfileRep.getName() == null) { - throw new ClientPolicyException("client profile without its name not allowed."); - } - - // newly proposed builtin profile not allowed because builtin profile cannot added/deleted/modified. - if (proposedProfileRep.isBuiltin() != null && proposedProfileRep.isBuiltin()) { - throw new ClientPolicyException("newly builtin proposed client profile not allowed."); - } - - // not allow to overwrite builtin profiles - if (updatingProfileList.stream().anyMatch(i->proposedProfileRep.getName().equals(i.getName()))) { - throw new ClientPolicyException("proposed client profile name is the same one of the builtin profile."); - } - - // basically, proposed profile totally overrides existing profile - ClientProfileRepresentation profileRep = new ClientProfileRepresentation(); - profileRep.setName(proposedProfileRep.getName()); - profileRep.setDescription(proposedProfileRep.getDescription()); - profileRep.setBuiltin(Boolean.FALSE); - profileRep.setExecutors(new ArrayList<>()); - if (proposedProfileRep.getExecutors() != null) { - for (Object executor : proposedProfileRep.getExecutors()) { - if (isValidExecutor(session, executor) == false) { - throw new ClientPolicyException("proposed client profile contains the executor with its invalid configuration."); - } - profileRep.getExecutors().add(executor); - } - } - - updatingProfileList.add(profileRep); - } - - return updatingProfilesRep; - } - - /** - * get validated and modified builtin client profiles in a realm as representation. - * it can be constructed by merging proposed client profiles with existing client profiles. - * not return null. - */ - public static ClientProfilesRepresentation getValidatedClientProfilesRepresentation(KeycloakSession session, RealmModel realm, String profilesJson) throws ClientPolicyException { - if (profilesJson == null) { - throw new ClientPolicyException("no client profiles json."); - } - - // deserialize existing profiles (json -> representation) - ClientProfilesRepresentation proposedProfilesRep = convertClientProfilesJsonToRepresentation(profilesJson); - - return getValidatedClientProfilesRepresentation(session, realm, proposedProfilesRep); - } - - /** - * check whether the proposed executor's provider can be found in keycloak's ClientPolicyExecutorProvider list. - * not return null. - */ - private static boolean isValidExecutor(KeycloakSession session, Object executor) { - return isValidComponent(session, executor, "executor", (String providerId) -> { - Set providerSet = session.listProviderIds(ClientPolicyExecutorProvider.class); - if (providerSet != null && providerSet.contains(providerId)) { - return true; - } - logger.warnv("no executor provider found. providerId = {0}", providerId); - return false; - }); - } - - - /** - * get existing client policies in a realm as representation. - * not return null. - */ - public static ClientPoliciesRepresentation getClientPoliciesRepresentation(KeycloakSession session, RealmModel realm) throws ClientPolicyException { - ClientPoliciesRepresentation policiesRep = null; - String policiesJson = null; - - // get existing policies json - if (realm != null) { - policiesJson = session.clientPolicy().getClientPoliciesJsonString(realm); - } else { - // if realm not specified, use builtin policies set in keycloak's binary. - policiesJson = session.clientPolicy().getClientPoliciesOnKeycloakApp(); - } - - // deserialize existing policies (json -> representation) - if (policiesJson == null) { - return new ClientPoliciesRepresentation(); - } - policiesRep = convertClientPoliciesJsonToRepresentation(policiesJson); - if (policiesRep == null) { - return new ClientPoliciesRepresentation(); - } - - return policiesRep; - } - - /** - * get existing enabled client policies in a realm as model. - * not return null. - */ - public static List getEnabledClientProfilesModel(KeycloakSession session, RealmModel realm) { - // get existing profiles as json - String policiesJson = session.clientPolicy().getClientPoliciesJsonString(realm); - if (policiesJson == null) { - return Collections.emptyList(); - } - - // deserialize existing policies (json -> representation) - ClientPoliciesRepresentation policiesRep = null; - try { - policiesRep = convertClientPoliciesJsonToRepresentation(policiesJson); - } catch (ClientPolicyException e) { - logger.warnv("Failed to serialize client policies json string. err={0}, errDetail={1}", e.getError(), e.getErrorDetail()); - return Collections.emptyList(); - } - if (policiesRep == null || policiesRep.getPolicies() == null) { - return Collections.emptyList(); - } - - // constructing existing policies (representation -> model) - List policyList = new ArrayList<>(); - for (ClientPolicyRepresentation policyRep: policiesRep.getPolicies()) { - // ignore policy without name - if (policyRep.getName() == null) { - continue; - } - // pick up only enabled policy - if (policyRep.isEnable() == null || policyRep.isEnable() == false) { - continue; - } - - ClientPolicyModel policyModel = new ClientPolicyModel(); - policyModel.setName(policyRep.getName()); - policyModel.setDescription(policyRep.getDescription()); - policyModel.setEnable(true); - if (policyRep.isBuiltin() != null) { - policyModel.setBuiltin(policyRep.isBuiltin().booleanValue()); - } else { - policyModel.setBuiltin(false); - } - - List conditions = new ArrayList<>(); - if (policyRep.getConditions() != null) { - policyRep.getConditions().stream().forEach(obj->{ - JsonNode node = objectMapper.convertValue(obj, JsonNode.class); - node.fields().forEachRemaining(condition->{ - ClientPolicyConditionProvider provider = session.getProvider(ClientPolicyConditionProvider.class, condition.getKey()); - if (provider == null) { - // condition's provider not found. just skip it. - return; - } - - try { - ClientPolicyConditionConfiguration configuration = (ClientPolicyConditionConfiguration) JsonSerialization.mapper.convertValue(condition.getValue(), provider.getConditionConfigurationClass()); - provider.setupConfiguration(configuration); - conditions.add(provider); - } catch (IllegalArgumentException iae) { - logger.warnv("failed for Configuration Setup :: error = {0}", iae.getMessage()); - } - }); - }); - } - policyModel.setConditions(conditions); - - if (policyRep.getProfiles() != null) { - policyModel.setProfiles(policyRep.getProfiles().stream().collect(Collectors.toList())); - } - - policyList.add(policyModel); - } - - return policyList; - } - - /** - * get validated and modified builtin client policies set on keycloak app as representation. - * it is loaded from json file enclosed in keycloak's binary. - * not return null. - */ - public static ClientPoliciesRepresentation getValidatedBuiltinClientPoliciesRepresentation(KeycloakSession session, InputStream is) throws ClientPolicyException { - // load builtin client policies representation - ClientPoliciesRepresentation proposedPoliciesRep = null; - try { - proposedPoliciesRep = JsonSerialization.readValue(is, ClientPoliciesRepresentation.class); - } catch (Exception e) { - throw new ClientPolicyException("failed to deserialize builtin proposed client policies json string.", e.getMessage()); - } - if (proposedPoliciesRep == null) { - proposedPoliciesRep = new ClientPoliciesRepresentation(); - } - - // no policy contained (it is valid) - List proposedPolicyRepList = proposedPoliciesRep.getPolicies(); - if (proposedPolicyRepList == null || proposedPolicyRepList.isEmpty()) { - return new ClientPoliciesRepresentation(); - } - - // duplicated policy name is not allowed. - if (proposedPolicyRepList.size() != proposedPolicyRepList.stream().map(i->i.getName()).distinct().count()) { - throw new ClientPolicyException("proposed builtin client policy name duplicated."); - } - - // construct validated and modified policies from builtin profiles in JSON file enclosed in keycloak binary. - ClientPoliciesRepresentation updatingPoliciesRep = new ClientPoliciesRepresentation(); - updatingPoliciesRep.setPolicies(new ArrayList<>()); - List updatingPoliciesList = updatingPoliciesRep.getPolicies(); - - for (ClientPolicyRepresentation proposedPolicyRep : proposedPoliciesRep.getPolicies()) { - if (proposedPolicyRep.getName() == null) { - throw new ClientPolicyException("proposed client policy name missing."); - } - - // ignore proposed ordinal policy because builtin policy can only be added. - if (proposedPolicyRep.isBuiltin() == null || !proposedPolicyRep.isBuiltin()) { - throw new ClientPolicyException("ordinal client policy not allowed."); - } - - ClientPolicyRepresentation policyRep = new ClientPolicyRepresentation(); - policyRep.setName(proposedPolicyRep.getName()); - policyRep.setDescription(proposedPolicyRep.getDescription()); - policyRep.setBuiltin(Boolean.TRUE); - Boolean enabled = (proposedPolicyRep.isEnable() != null) ? proposedPolicyRep.isEnable() : Boolean.FALSE; - policyRep.setEnable(enabled); - - policyRep.setConditions(new ArrayList<>()); - if (proposedPolicyRep.getConditions() != null) { - for (Object condition : proposedPolicyRep.getConditions()) { - if (isValidCondition(session, condition) == false) { - throw new ClientPolicyException("the proposed client policy contains the condition with its invalid configuration."); - } - policyRep.getConditions().add(condition); - } - } - - Set existingProfileNames = new HashSet<>(); - ClientProfilesRepresentation reps = getClientProfilesRepresentation(session, null); - reps.getProfiles().stream().map(profile->profile.getName()).forEach(profileName->existingProfileNames.add(profileName)); - policyRep.setProfiles(new ArrayList<>()); - if (proposedPolicyRep.getProfiles() != null) { - for (String profileName : proposedPolicyRep.getProfiles()) { - if (existingProfileNames.contains(profileName) == false) { - throw new ClientPolicyException("referring not existing client profile not allowed."); - } - } - proposedPolicyRep.getProfiles().stream().distinct().forEach(profileName->policyRep.getProfiles().add(profileName)); - } - - updatingPoliciesList.add(policyRep); - } - - return updatingPoliciesRep; - } - - /** - * convert client policies as representation to json. - * can return null. - */ - public static String convertClientPoliciesRepresentationToJson(ClientPoliciesRepresentation reps) throws ClientPolicyException { - return convertRepresentationToJson(reps); - } - - /** - * convert client policies as json to representation. - * not return null. - */ - private static ClientPoliciesRepresentation convertClientPoliciesJsonToRepresentation(String json) throws ClientPolicyException { - return convertJsonToRepresentation(json, ClientPoliciesRepresentation.class); - } - - /** - * get validated and modified client policies as json. - * it can be constructed by merging proposed client policies with existing client policies. - * can return null. - */ - public static String getValidatedClientPoliciesJson(KeycloakSession session, RealmModel realm, ClientPoliciesRepresentation proposedPoliciesRep) throws ClientPolicyException { - return convertClientPoliciesRepresentationToJson(getValidatedClientPoliciesRepresentation(session, realm, proposedPoliciesRep)); - } - - /** - * get validated and modified client policies as representation. - * it can be constructed by merging proposed client policies with existing client policies. - * not return null. - */ - private static ClientPoliciesRepresentation getValidatedClientPoliciesRepresentation(KeycloakSession session, RealmModel realm, ClientPoliciesRepresentation proposedPoliciesRep) throws ClientPolicyException { - if (proposedPoliciesRep == null) { - proposedPoliciesRep = new ClientPoliciesRepresentation(); - } - if (realm == null) { - throw new ClientPolicyException("realm not specified."); - } - - // deserialize existing profiles (json -> represetation) - ClientPoliciesRepresentation existingPoliciesRep = null; - String existingPoliciesJson = session.clientPolicy().getClientPoliciesJsonString(realm); - if (existingPoliciesJson != null) { - existingPoliciesRep = convertClientPoliciesJsonToRepresentation(existingPoliciesJson); - if (existingPoliciesRep == null) { - existingPoliciesRep = new ClientPoliciesRepresentation(); - } - } else { - existingPoliciesRep = new ClientPoliciesRepresentation(); - } - - // no policy contained (it is valid) - // back to initial builtin policies - List proposedPolicyRepList = proposedPoliciesRep.getPolicies(); - if (proposedPolicyRepList == null || proposedPolicyRepList.isEmpty()) { - proposedPolicyRepList = new ArrayList<>(); - proposedPoliciesRep.setPolicies(new ArrayList<>()); - } - - // duplicated policy name is not allowed. - if (proposedPolicyRepList.size() != proposedPolicyRepList.stream().map(i->i.getName()).distinct().count()) { - throw new ClientPolicyException("proposed client policy name duplicated."); - } - - // construct updating policies from existing policies and proposed policies - ClientPoliciesRepresentation updatingPoliciesRep = new ClientPoliciesRepresentation(); - updatingPoliciesRep.setPolicies(new ArrayList<>()); - List updatingPoliciesList = updatingPoliciesRep.getPolicies(); - - // add existing builtin policies to updating policies - List existingPoliciesList = existingPoliciesRep.getPolicies(); - if (existingPoliciesList != null && !existingPoliciesList.isEmpty()) { - existingPoliciesList.stream().filter(i->i.isBuiltin()).forEach(i->updatingPoliciesList.add(i)); - } - - for (ClientPolicyRepresentation proposedPolicyRep : proposedPoliciesRep.getPolicies()) { - if (proposedPolicyRep.getName() == null) { - throw new ClientPolicyException("proposed client policy name missing."); - } - - // newly proposed builtin policy not allowed because builtin policy cannot added/deleted/modified. - Boolean enabled = (proposedPolicyRep.isEnable() != null) ? proposedPolicyRep.isEnable() : Boolean.FALSE; - if (proposedPolicyRep.isBuiltin() != null && proposedPolicyRep.isBuiltin()) { - // only enable field of the existing builtin policy can be overridden. - if (updatingPoliciesList.stream().anyMatch(i->i.getName().equals(proposedPolicyRep.getName()))) { - updatingPoliciesList.stream().filter(i->i.getName().equals(proposedPolicyRep.getName())).forEach(i->i.setEnable(enabled)); - continue; - } - throw new ClientPolicyException("newly builtin proposed client policy not allowed."); - } - - // basically, proposed policy totally overrides existing policy except for enabled field.. - ClientPolicyRepresentation policyRep = new ClientPolicyRepresentation(); - policyRep.setName(proposedPolicyRep.getName()); - policyRep.setDescription(proposedPolicyRep.getDescription()); - policyRep.setBuiltin(Boolean.FALSE); - policyRep.setEnable(enabled); - - policyRep.setConditions(new ArrayList<>()); - if (proposedPolicyRep.getConditions() != null) { - for (Object condition : proposedPolicyRep.getConditions()) { - if (isValidCondition(session, condition) == false) { - throw new ClientPolicyException("the proposed client policy contains the condition with its invalid configuration."); - } - policyRep.getConditions().add(condition); - } - } - - Set existingProfileNames = new HashSet<>(); - ClientProfilesRepresentation reps = getClientProfilesRepresentation(session, realm); - if (reps.getProfiles() != null) { - reps.getProfiles().stream().map(profile->profile.getName()).forEach(profileName->existingProfileNames.add(profileName)); - } - policyRep.setProfiles(new ArrayList<>()); - if (proposedPolicyRep.getProfiles() != null) { - for (String profileName : proposedPolicyRep.getProfiles()) { - if (existingProfileNames.contains(profileName) == false) { - throw new ClientPolicyException("referring not existing client profile not allowed."); - } - } - proposedPolicyRep.getProfiles().stream().distinct().forEach(profileName->policyRep.getProfiles().add(profileName)); - } - - updatingPoliciesList.add(policyRep); - } - - return updatingPoliciesRep; - } - - /** - * get validated and modified builtin client policies in a realm as representation. - * it can be constructed by merging proposed client policies with existing client policies. - * not return null. - */ - public static ClientPoliciesRepresentation getValidatedClientPoliciesRepresentation(KeycloakSession session, RealmModel realm, String policiesJson) throws ClientPolicyException { - if (policiesJson == null) { - throw new ClientPolicyException("no client policies json."); - } - // deserialize existing policies (json -> representation) - ClientPoliciesRepresentation proposedPoliciesRep = convertClientPoliciesJsonToRepresentation(policiesJson); - return getValidatedClientPoliciesRepresentation(session, realm, proposedPoliciesRep); - } - - /** - * check whether the proposed condition's provider can be found in keycloak's ClientPolicyConditionProvider list. - * not return null. - */ - private static boolean isValidCondition(KeycloakSession session, Object condition) { - return isValidComponent(session, condition, "condition", (String providerId) -> { - Set providerSet = session.listProviderIds(ClientPolicyConditionProvider.class); - if (providerSet != null && providerSet.contains(providerId)) { - return true; - } - logger.warnv("no executor provider found. providerId = {0}", providerId); - return false; - }); - } - - - private static boolean isValidComponent(KeycloakSession session, Object obj, String type, Predicate f) { - JsonNode node = null; - - try { - node = objectMapper.convertValue(obj, JsonNode.class); - } catch (IllegalArgumentException iae) { - logger.warnv("invalid json string representating {0}. err={1}", type, iae.getMessage()); - return false; - } - - Iterator> it = node.fields(); - while (it.hasNext()) { - Entry entry = it.next(); - // whether find provider - if(!f.test(entry.getKey())) return false; - } - return true; - } - - private static String convertRepresentationToJson(Object reps) throws ClientPolicyException { - if (reps == null) return null; - - String json = null; - try { - json = objectMapper.writeValueAsString(reps); - } catch (JsonProcessingException jpe) { - throw new ClientPolicyException(jpe.getMessage()); - } - - return json; - } - - private static T convertJsonToRepresentation(String json, Class type) throws ClientPolicyException { - if (json == null) { - throw new ClientPolicyException("no json."); - } - - T rep = null; - try { - rep = JsonSerialization.readValue(json, type); - } catch (IOException ioe) { - throw new ClientPolicyException("failed to deserialize.", ioe.getMessage()); - } - - return rep; - } - -} diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorConfiguration.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManagerFactory.java similarity index 67% rename from server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorConfiguration.java rename to server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManagerFactory.java index 7c8f95a4c6..43dd044773 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorConfiguration.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManagerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * 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"); @@ -16,15 +16,12 @@ * */ -package org.keycloak.services.clientpolicy.executor; +package org.keycloak.services.clientpolicy; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.keycloak.provider.ProviderFactory; /** - * Just adds some type-safety to the ClientPolicyExecutorConfiguration - * * @author Marek Posolda */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class ClientPolicyExecutorConfiguration { +public interface ClientPolicyManagerFactory extends ProviderFactory { } diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManagerSpi.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManagerSpi.java new file mode 100644 index 0000000000..d15b4ce137 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManagerSpi.java @@ -0,0 +1,49 @@ +/* + * 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.services.clientpolicy; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Marek Posolda + */ +public class ClientPolicyManagerSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "client-policy-manager"; + } + + @Override + public Class getProviderClass() { + return ClientPolicyManager.class; + } + + @Override + public Class getProviderFactoryClass() { + return ClientPolicyManagerFactory.class; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProvider.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProvider.java new file mode 100644 index 0000000000..f9a6ddec28 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProvider.java @@ -0,0 +1,56 @@ +/* + * 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.services.clientpolicy.condition; + +import java.util.Optional; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; +import org.keycloak.services.clientpolicy.ClientPolicyException; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public abstract class AbstractClientPolicyConditionProvider implements ClientPolicyConditionProvider { + + protected final KeycloakSession session; + protected CONFIG configuration; + + public AbstractClientPolicyConditionProvider(KeycloakSession session) { + this.session = session; + } + + @Override + public void setupConfiguration(CONFIG config) { + if (config == null) { + // Fallback for the case that null configuration is passed as an argument + this.configuration = JsonSerialization.mapper.convertValue(new ClientPolicyConditionConfigurationRepresentation(), getConditionConfigurationClass()); + } else { + this.configuration = config; + } + } + + public boolean isNegativeLogic() throws ClientPolicyException { + if (configuration == null) { + throw new ClientPolicyException("Not allowed to call this when configuration is not set"); + } + return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue(); + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionConfiguration.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionConfiguration.java deleted file mode 100644 index b1b5ce34d7..0000000000 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionConfiguration.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2021 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.keycloak.services.clientpolicy.condition; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -/** - * Just adds some type-safety to the ClientPolicyConditionConfiguration - * - * @author Takashi Norimatsu - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class ClientPolicyConditionConfiguration { -} diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProvider.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProvider.java index 4673af9cf6..e63a3f46e6 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProvider.java @@ -18,6 +18,7 @@ package org.keycloak.services.clientpolicy.condition; import org.keycloak.provider.Provider; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyException; @@ -31,7 +32,7 @@ import org.keycloak.services.clientpolicy.ClientPolicyVote; * * @author Takashi Norimatsu */ -public interface ClientPolicyConditionProvider extends Provider { +public interface ClientPolicyConditionProvider extends Provider { @Override default void close() { @@ -42,14 +43,13 @@ public interface ClientPolicyConditionProvider getConditionConfigurationClass() { - return (Class) ClientPolicyConditionConfiguration.class; + return (Class) ClientPolicyConditionConfigurationRepresentation.class; } /** @@ -73,9 +73,7 @@ public interface ClientPolicyConditionProviderTakashi Norimatsu */ -public interface ClientPolicyConditionProviderFactory extends ProviderFactory, ConfiguredProvider { +public interface ClientPolicyConditionProviderFactory extends ProviderFactory, ConfiguredProvider, EnvironmentDependentProviderFactory { + + @Override + default boolean isSupported() { + return Profile.isFeatureEnabled(Profile.Feature.CLIENT_POLICIES); + } } diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProvider.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProvider.java index aee6ea344b..42f2b588ca 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProvider.java @@ -18,6 +18,7 @@ package org.keycloak.services.clientpolicy.executor; import org.keycloak.provider.Provider; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyEvent; @@ -30,7 +31,7 @@ import org.keycloak.services.clientpolicy.ClientPolicyEvent; * * @author Takashi Norimatsu */ -public interface ClientPolicyExecutorProvider extends Provider { +public interface ClientPolicyExecutorProvider extends Provider { @Override default void close() { @@ -45,10 +46,10 @@ public interface ClientPolicyExecutorProvider getExecutorConfigurationClass() { - return (Class) ClientPolicyExecutorConfiguration.class; + return (Class) ClientPolicyExecutorConfigurationRepresentation.class; } /** diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProviderFactory.java index c94ccc11a6..801444e190 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProviderFactory.java @@ -17,11 +17,18 @@ package org.keycloak.services.clientpolicy.executor; +import org.keycloak.common.Profile; import org.keycloak.provider.ConfiguredProvider; +import org.keycloak.provider.EnvironmentDependentProviderFactory; import org.keycloak.provider.ProviderFactory; /** * @author Takashi Norimatsu */ -public interface ClientPolicyExecutorProviderFactory extends ProviderFactory, ConfiguredProvider { +public interface ClientPolicyExecutorProviderFactory extends ProviderFactory, ConfiguredProvider, EnvironmentDependentProviderFactory { + + @Override + default boolean isSupported() { + return Profile.isFeatureEnabled(Profile.Feature.CLIENT_POLICIES); + } } diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 5fc115c315..b90ce42b1a 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -97,4 +97,5 @@ org.keycloak.validation.ClientValidationSPI org.keycloak.headers.SecurityHeadersSpi org.keycloak.services.clientpolicy.condition.ClientPolicyConditionSpi org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorSpi +org.keycloak.services.clientpolicy.ClientPolicyManagerSpi org.keycloak.userprofile.UserProfileSpi diff --git a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManager.java b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManager.java index 2ebb8b316e..479305f682 100644 --- a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManager.java +++ b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManager.java @@ -18,6 +18,9 @@ package org.keycloak.services.clientpolicy; import org.keycloak.models.RealmModel; +import org.keycloak.provider.Provider; +import org.keycloak.representations.idm.ClientPoliciesRepresentation; +import org.keycloak.representations.idm.ClientProfilesRepresentation; import org.keycloak.representations.idm.RealmRepresentation; /** @@ -26,7 +29,7 @@ import org.keycloak.representations.idm.RealmRepresentation; * * @author Takashi Norimatsu */ -public interface ClientPolicyManager { +public interface ClientPolicyManager extends Provider { /** * execute a method for handling an event defined in {@link ClientPolicyEvent}. @@ -37,64 +40,54 @@ public interface ClientPolicyManager { void triggerOnEvent(ClientPolicyContext context) throws ClientPolicyException; /** - * when booting keycloak, reads json representations of the builtin client profiles and policies from files - * enclosed in keycloak-services jar file and put them onto the keycloak application. + * when creating a realm, adds the default client policies, which should be available on the realm and put them onto the realm as its attribute. * if these operation fails, put null. - * - * @param profilesFilePath - the file path for the builtin client profiles - * @param policiesFilePath - the file path for the builtin client policies - */ - void setupClientPoliciesOnKeycloakApp(String profilesFilePath, String policiesFilePath); - - /** - * when creating a realm, reads the builtin client profiles and policies - * that have already been set on keycloak application on booting keycloak and put them onto the realm as its attribute. - * if these operation fails, put null. - * + * * @param realm - the newly created realm */ void setupClientPoliciesOnCreatedRealm(RealmModel realm); /** - * when importing a realm, reads the builtin client profiles and policies - * that have already been set on keycloak application on booting keycloak and override them - * with ones loaded from the imported realm json file. - * if these operation fails, rolls them back to the builtin client profiles and policies set on keycloak application. - * + * when importing a realm, or updating a realm, update model from the representation object + * * @param realm - the newly created realm to be overriden by imported realm's representation * @param rep - imported realm's representation */ - void setupClientPoliciesOnImportedRealm(RealmModel realm, RealmRepresentation rep); + void updateRealmModelFromRepresentation(RealmModel realm, RealmRepresentation rep); /** * when updating client profiles via Admin REST API, reads the json representation of the client profiles * and overrides the existing client profiles set on the realm with them. * if these operation fails, rolls them back to the existing client profiles and throw an exception. + * + * If the "clientProfiles" parameter contains the global client profiles, they won't be updated on the realm at all * * @param realm - the realm whose client profiles is to be overriden by the new client profiles - * @param json - the json representation of the new client profiles that overrides the existing client profiles set on the realm + * @param clientProfiles - the json representation of the new client profiles that overrides the existing client profiles set on the realm. With + * the exception of global profiles, which are not overriden as mentioned above. * @throws {@link ClientPolicyException} */ - void updateClientProfiles(RealmModel realm, String json) throws ClientPolicyException; + void updateClientProfiles(RealmModel realm, ClientProfilesRepresentation clientProfiles) throws ClientPolicyException; /** * when getting client profiles via Admin REST API, returns the existing client profiles set on the realm. * * @param realm - the realm whose client profiles is to be returned + * @param includeGlobalProfiles - If true, method will return realm profiles and global profiles as well. If false, then "globalProfiles" field would be null * @return the json representation of the client profiles set on the realm */ - String getClientProfiles(RealmModel realm); + ClientProfilesRepresentation getClientProfiles(RealmModel realm, boolean includeGlobalProfiles) throws ClientPolicyException; /** * when updating client policies via Admin REST API, reads the json representation of the client policies * and overrides the existing client policies set on the realm with them. * if these operation fails, rolls them back to the existing client policies and throw an exception. - * + * * @param realm - the realm whose client policies is to be overriden by the new client policies - * @param json - the json representation of the new client policies that overrides the existing client policies set on the realm + * @param clientPolicies - the json representation of the new client policies that overrides the existing client policies set on the realm * @throws {@link ClientPolicyException} */ - void updateClientPolicies(RealmModel realm, String json) throws ClientPolicyException; + void updateClientPolicies(RealmModel realm, ClientPoliciesRepresentation clientPolicies) throws ClientPolicyException; /** * when getting client policies via Admin REST API, returns the existing client policies set on the realm. @@ -102,45 +95,15 @@ public interface ClientPolicyManager { * @param realm - the realm whose client policies is to be returned * @return the json representation of the client policies set on the realm */ - String getClientPolicies(RealmModel realm); + ClientPoliciesRepresentation getClientPolicies(RealmModel realm) throws ClientPolicyException; /** - * when exporting realm the realm, prepares the exported representation of the client profiles and policies. - * E.g. the builtin client profiles and policies are filtered out and not exported. - * + * when exporting realm, or retrieve the realm for admin REST API, prepares the exported representation of the client profiles and policies. + * Global client profiles and policies are filtered out and not exported. + * * @param realm - the realm to be exported * @param rep - the realm's representation to be exported actually */ - void setupClientPoliciesOnExportingRealm(RealmModel realm, RealmRepresentation rep); - - /** - * returns the json representation of the builtin client profiles set on keycloak application. - * - * @return the json representation of the builtin client profiles set on keycloak application - */ - String getClientProfilesOnKeycloakApp(); - - /** - * returns the json representation of the builtin client policies set on keycloak application. - * - * @return the json representation of the builtin client policies set on keycloak application - */ - String getClientPoliciesOnKeycloakApp(); - - /** - * returns the json representation of the client profiles set on the realm. - * - * @param realm - the realm whose client profiles is to be returned - * @return the json representation of the client profiles set on the realm - */ - String getClientProfilesJsonString(RealmModel realm); - - /** - * returns the json representation of the client policies set on the realm. - * - * @param realm - the realm whose client policies is to be returned - * @return the json representation of the client policies set on the realm - */ - String getClientPoliciesJsonString(RealmModel realm); + void updateRealmRepresentationFromModel(RealmModel realm, RealmRepresentation rep); } diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index 820c49686d..d57056b5cd 100755 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -87,7 +87,7 @@ public class ExportUtils { } public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, ExportOptions options, boolean internal) { - RealmRepresentation rep = ModelToRepresentation.toRepresentation(realm, internal); + RealmRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, internal); ModelToRepresentation.exportAuthenticationFlows(realm, rep); ModelToRepresentation.exportRequiredActions(realm, rep); @@ -260,9 +260,6 @@ public class ExportUtils { MultivaluedHashMap components = exportComponents(realm, realm.getId()); rep.setComponents(components); - // client policies - session.clientPolicy().setupClientPoliciesOnExportingRealm(realm, rep); - return rep; } diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 09c84e4d3f..da01849d9d 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -499,7 +499,7 @@ public class DefaultKeycloakSession implements KeycloakSession { @Override public ClientPolicyManager clientPolicy() { if (clientPolicyManager == null) { - clientPolicyManager = new DefaultClientPolicyManager(this); + clientPolicyManager = getProvider(ClientPolicyManager.class); } return clientPolicyManager; } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/ClientPoliciesUtil.java b/services/src/main/java/org/keycloak/services/clientpolicy/ClientPoliciesUtil.java new file mode 100644 index 0000000000..7035c08760 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientpolicy/ClientPoliciesUtil.java @@ -0,0 +1,514 @@ +/* + * 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.services.clientpolicy; + +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 org.jboss.logging.Logger; + +import org.keycloak.common.Profile; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.ClientPoliciesRepresentation; +import org.keycloak.representations.idm.ClientPolicyConditionRepresentation; +import org.keycloak.representations.idm.ClientPolicyExecutorRepresentation; +import org.keycloak.representations.idm.ClientPolicyRepresentation; +import org.keycloak.representations.idm.ClientProfileRepresentation; +import org.keycloak.representations.idm.ClientProfilesRepresentation; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; +import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; +import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; +import org.keycloak.util.JsonSerialization; + +/** + * Utilities for treating client policies/profiles + * + * @author Takashi Norimatsu + */ +public class ClientPoliciesUtil { + + private static final Logger logger = Logger.getLogger(ClientPoliciesUtil.class); + + /** + * gets existing client profiles in a realm as representation. + * not return null. + */ + static ClientProfilesRepresentation getClientProfilesRepresentation(KeycloakSession session, RealmModel realm) throws ClientPolicyException { + String profilesJson = getClientProfilesJsonString(realm); + + // deserialize existing profiles (json -> representation) + if (profilesJson == null) { + return new ClientProfilesRepresentation(); + } + return convertClientProfilesJsonToRepresentation(profilesJson); + } + + /** + * gets existing client profiles in a realm as model. + * not return null. + */ + static Map getClientProfilesModel(KeycloakSession session, RealmModel realm, List 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(); + } + List profiles = profilesRep.getProfiles(); + + // Add global profiles as well + profiles.addAll(globalClientProfiles); + + // constructing existing profiles (representation -> model) + Map 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 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); + } + + return profileMap; + } + + /** + * get validated and modified global (built-in) client profiles set on keycloak app as representation. + * it is loaded from json file enclosed in keycloak's binary. + * not return null. + */ + static List getValidatedGlobalClientProfilesRepresentation(KeycloakSession session, InputStream is) throws ClientPolicyException { + // load builtin client profiles representation + ClientProfilesRepresentation proposedProfilesRep = null; + try { + proposedProfilesRep = JsonSerialization.readValue(is, ClientProfilesRepresentation.class); + } catch (Exception e) { + throw new ClientPolicyException("failed to deserialize global proposed client profiles json string.", e.getMessage()); + } + if (proposedProfilesRep == null) { + return Collections.emptyList(); + } + + // no profile contained (it is valid) + List proposedProfileRepList = proposedProfilesRep.getProfiles(); + if (proposedProfileRepList == null || proposedProfileRepList.isEmpty()) { + return Collections.emptyList(); + } + + // duplicated profile name is not allowed. + if (proposedProfileRepList.size() != proposedProfileRepList.stream().map(i->i.getName()).distinct().count()) { + throw new ClientPolicyException("proposed global client profile name duplicated."); + } + + // construct validated and modified profiles from builtin profiles in JSON file enclosed in keycloak binary. + List updatingProfileList = new LinkedList<>(); + + for (ClientProfileRepresentation proposedProfileRep : proposedProfilesRep.getProfiles()) { + if (proposedProfileRep.getName() == null) { + throw new ClientPolicyException("client profile without its name not allowed."); + } + + ClientProfileRepresentation profileRep = new ClientProfileRepresentation(); + profileRep.setName(proposedProfileRep.getName()); + profileRep.setDescription(proposedProfileRep.getDescription()); + + profileRep.setExecutors(new ArrayList<>()); // to prevent returning null + if (proposedProfileRep.getExecutors() != null) { + for (ClientPolicyExecutorRepresentation executorRep : proposedProfileRep.getExecutors()) { + // Skip the check if feature is disabled as then the executor implementations are disabled + if (Profile.isFeatureEnabled(Profile.Feature.CLIENT_POLICIES) && !isValidExecutor(session, executorRep.getExecutorProviderId())) { + throw new ClientPolicyException("proposed client profile contains the executor with its invalid configuration."); + } + profileRep.getExecutors().add(executorRep); + } + } + + updatingProfileList.add(profileRep); + } + + return updatingProfileList; + } + + /** + * convert client profiles as representation to json. + * can return null. + */ + public static String convertClientProfilesRepresentationToJson(ClientProfilesRepresentation reps) throws ClientPolicyException { + try { + return JsonSerialization.writeValueAsString(reps); + } catch (IOException ioe) { + throw new ClientPolicyException(ioe.getMessage()); + } + } + + /** + * convert client profiles as json to representation. + * not return null. + */ + private static ClientProfilesRepresentation convertClientProfilesJsonToRepresentation(String json) throws ClientPolicyException { + try { + return JsonSerialization.readValue(json, ClientProfilesRepresentation.class); + } catch (IOException ioe) { + throw new ClientPolicyException(ioe.getMessage()); + } + } + + /** + * get validated and modified client profiles as representation. + * it can be constructed by merging proposed client profiles with existing client profiles. + * not return null. + */ + static ClientProfilesRepresentation getValidatedClientProfilesForUpdate(KeycloakSession session, RealmModel realm, + ClientProfilesRepresentation proposedProfilesRep, List globalClientProfiles) throws ClientPolicyException { + if (realm == null) { + throw new ClientPolicyException("realm not specified."); + } + + // no profile contained (it is valid) + List proposedProfileRepList = proposedProfilesRep.getProfiles(); + if (proposedProfileRepList == null || proposedProfileRepList.isEmpty()) { + proposedProfileRepList = new ArrayList<>(); + proposedProfilesRep.setProfiles(new ArrayList<>()); + } + + // Profile without name not allowed + if (proposedProfileRepList.stream().anyMatch(clientProfile -> clientProfile.getName() == null || clientProfile.getName().isEmpty())) { + throw new ClientPolicyException("client profile without its name not allowed."); + } + + // duplicated profile name is not allowed. + if (proposedProfileRepList.size() != proposedProfileRepList.stream().map(i->i.getName()).distinct().count()) { + throw new ClientPolicyException("proposed client profile name duplicated."); + } + + // Conflict with any global profile is not allowed + Set globalProfileNames = globalClientProfiles.stream().map(ClientProfileRepresentation::getName).collect(Collectors.toSet()); + for (ClientProfileRepresentation clientProfile : proposedProfileRepList) { + if (globalProfileNames.contains(clientProfile.getName())) { + throw new ClientPolicyException("Proposed profile name duplicated as the name of some global profile"); + } + } + + // Validate executor + for (ClientProfileRepresentation proposedProfileRep : proposedProfilesRep.getProfiles()) { + if (proposedProfileRep.getExecutors() != null) { + for (ClientPolicyExecutorRepresentation executorRep : proposedProfileRep.getExecutors()) { + if (!isValidExecutor(session, executorRep.getExecutorProviderId())) { + throw new ClientPolicyException("proposed client profile contains the executor, which does not have valid provider, or has invalid configuration."); + } + } + } + } + + // Make sure to not save built-in inside realm attribute + proposedProfilesRep.setGlobalProfiles(null); + + return proposedProfilesRep; + } + + /** + * check whether the proposed executor's provider can be found in keycloak's ClientPolicyExecutorProvider list. + * not return null. + */ + private static boolean isValidExecutor(KeycloakSession session, String executorProviderId) { + Set providerSet = session.listProviderIds(ClientPolicyExecutorProvider.class); + if (providerSet != null && providerSet.contains(executorProviderId)) { + return true; + } + logger.warnv("no executor provider found. providerId = {0}", executorProviderId); + return false; + } + + + /** + * get existing client policies in a realm as representation. + * not return null. + */ + static ClientPoliciesRepresentation getClientPoliciesRepresentation(KeycloakSession session, RealmModel realm) throws ClientPolicyException { + // get existing policies json + String policiesJson = getClientPoliciesJsonString(realm); + + // deserialize existing policies (json -> representation) + if (policiesJson == null) { + return new ClientPoliciesRepresentation(); + } + return convertClientPoliciesJsonToRepresentation(policiesJson); + } + + /** + * get existing enabled client policies in a realm as model. + * not return null. + */ + static List getEnabledClientPoliciesModel(KeycloakSession session, RealmModel realm) { + // get existing profiles as json + String policiesJson = getClientPoliciesJsonString(realm); + if (policiesJson == null) { + return Collections.emptyList(); + } + + // deserialize existing policies (json -> representation) + ClientPoliciesRepresentation policiesRep = null; + try { + policiesRep = convertClientPoliciesJsonToRepresentation(policiesJson); + } catch (ClientPolicyException e) { + logger.warnv("Failed to serialize client policies json string. err={0}, errDetail={1}", e.getError(), e.getErrorDetail()); + return Collections.emptyList(); + } + if (policiesRep == null || policiesRep.getPolicies() == null) { + return Collections.emptyList(); + } + + // constructing existing policies (representation -> model) + List policyList = new ArrayList<>(); + for (ClientPolicyRepresentation policyRep: policiesRep.getPolicies()) { + // ignore policy without name + if (policyRep.getName() == null) { + logger.warnf("Ignored client policy without name in the realm %s", realm.getName()); + continue; + } + // pick up only enabled policy + if (policyRep.isEnabled() == null || policyRep.isEnabled() == false) { + continue; + } + + ClientPolicyModel policyModel = new ClientPolicyModel(); + policyModel.setName(policyRep.getName()); + policyModel.setDescription(policyRep.getDescription()); + policyModel.setEnable(true); + + List 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()); + } + } + } + policyModel.setConditions(conditions); + + if (policyRep.getProfiles() != null) { + policyModel.setProfiles(policyRep.getProfiles().stream().collect(Collectors.toList())); + } + + policyList.add(policyModel); + } + + return policyList; + } + + /** + * convert client policies as representation to json. + * can return null. + */ + public static String convertClientPoliciesRepresentationToJson(ClientPoliciesRepresentation reps) throws ClientPolicyException { + try { + return JsonSerialization.writeValueAsString(reps); + } catch (IOException ioe) { + throw new ClientPolicyException(ioe.getMessage()); + } + } + + /** + * convert client policies as json to representation. + * not return null. + */ + private static ClientPoliciesRepresentation convertClientPoliciesJsonToRepresentation(String json) throws ClientPolicyException { + try { + return JsonSerialization.readValue(json, ClientPoliciesRepresentation.class); + } catch (IOException ioe) { + throw new ClientPolicyException(ioe.getMessage()); + } + } + + /** + * get validated and modified client policies as representation. + * it can be constructed by merging proposed client policies with existing client policies. + * not return null. + * + * @param session + * @param realm + * @param proposedPoliciesRep + */ + static ClientPoliciesRepresentation getValidatedClientPoliciesForUpdate(KeycloakSession session, RealmModel realm, + ClientPoliciesRepresentation proposedPoliciesRep, List existingGlobalProfiles) throws ClientPolicyException { + if (realm == null) { + throw new ClientPolicyException("realm not specified."); + } + + // no policy contained (it is valid) + List proposedPolicyRepList = proposedPoliciesRep.getPolicies(); + if (proposedPolicyRepList == null || proposedPolicyRepList.isEmpty()) { + proposedPolicyRepList = new ArrayList<>(); + proposedPoliciesRep.setPolicies(new ArrayList<>()); + } + + // Policy without name not allowed + if (proposedPolicyRepList.stream().anyMatch(clientPolicy -> clientPolicy.getName() == null || clientPolicy.getName().isEmpty())) { + throw new ClientPolicyException("proposed client policy name missing."); + } + + // duplicated policy name is not allowed. + if (proposedPolicyRepList.size() != proposedPolicyRepList.stream().map(i->i.getName()).distinct().count()) { + throw new ClientPolicyException("proposed client policy name duplicated."); + } + + // construct updating policies from existing policies and proposed policies + ClientPoliciesRepresentation updatingPoliciesRep = new ClientPoliciesRepresentation(); + updatingPoliciesRep.setPolicies(new ArrayList<>()); + List updatingPoliciesList = updatingPoliciesRep.getPolicies(); + + for (ClientPolicyRepresentation proposedPolicyRep : proposedPoliciesRep.getPolicies()) { + // newly proposed builtin policy not allowed because builtin policy cannot added/deleted/modified. + Boolean enabled = (proposedPolicyRep.isEnabled() != null) ? proposedPolicyRep.isEnabled() : Boolean.FALSE; + + // basically, proposed policy totally overrides existing policy except for enabled field.. + ClientPolicyRepresentation policyRep = new ClientPolicyRepresentation(); + policyRep.setName(proposedPolicyRep.getName()); + policyRep.setDescription(proposedPolicyRep.getDescription()); + policyRep.setEnabled(enabled); + + policyRep.setConditions(new ArrayList<>()); + if (proposedPolicyRep.getConditions() != null) { + for (ClientPolicyConditionRepresentation conditionRep : proposedPolicyRep.getConditions()) { + if (!isValidCondition(session, conditionRep.getConditionProviderId())) { + throw new ClientPolicyException("the proposed client policy contains the condition with its invalid configuration."); + } + policyRep.getConditions().add(conditionRep); + } + } + + Set existingProfileNames = existingGlobalProfiles.stream().map(ClientProfileRepresentation::getName).collect(Collectors.toSet()); + ClientProfilesRepresentation reps = getClientProfilesRepresentation(session, realm); + policyRep.setProfiles(new ArrayList<>()); + if (reps.getProfiles() != null) { + existingProfileNames.addAll(reps.getProfiles().stream() + .map(ClientProfileRepresentation::getName) + .collect(Collectors.toSet())); + } + if (proposedPolicyRep.getProfiles() != null) { + for (String profileName : proposedPolicyRep.getProfiles()) { + if (!existingProfileNames.contains(profileName)) { + logger.warnf("Client policy %s referred not existing profile %s"); + throw new ClientPolicyException("referring not existing client profile not allowed."); + } + } + proposedPolicyRep.getProfiles().stream().distinct().forEach(profileName->policyRep.getProfiles().add(profileName)); + } + + updatingPoliciesList.add(policyRep); + } + + return updatingPoliciesRep; + } + + /** + * check whether the proposed condition's provider can be found in keycloak's ClientPolicyConditionProvider list. + * not return null. + */ + private static boolean isValidCondition(KeycloakSession session, String conditionProviderId) { + Set providerSet = session.listProviderIds(ClientPolicyConditionProvider.class); + if (providerSet != null && providerSet.contains(conditionProviderId)) { + return true; + } + logger.warnv("no condition provider found. providerId = {0}", conditionProviderId); + return false; + } + + static String getClientProfilesJsonString(RealmModel realm) { + return realm.getAttribute(Constants.CLIENT_PROFILES); + } + + static String getClientPoliciesJsonString(RealmModel realm) { + return realm.getAttribute(Constants.CLIENT_POLICIES); + } + + static void setClientProfilesJsonString(RealmModel realm, String json) { + realm.setAttribute(Constants.CLIENT_PROFILES, json); + } + + static void setClientPoliciesJsonString(RealmModel realm, String json) { + realm.setAttribute(Constants.CLIENT_POLICIES, json); + } + +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyModel.java b/services/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyModel.java similarity index 81% rename from server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyModel.java rename to services/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyModel.java index 6934856d14..6859b1c81c 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyModel.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyModel.java @@ -20,6 +20,8 @@ package org.keycloak.services.clientpolicy; import java.io.Serializable; import java.util.List; +import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; + /** * @author Takashi Norimatsu */ @@ -27,9 +29,8 @@ public class ClientPolicyModel implements Serializable { protected String name; protected String description; - protected boolean builtin; protected boolean enable; - protected List conditions; // ClientPolicyConditionProvider is not visible so that use Object. + protected List conditions; protected List profiles; public String getName() { @@ -48,14 +49,6 @@ public class ClientPolicyModel implements Serializable { this.description = description; } - public boolean isBuiltin() { - return builtin; - } - - public void setBuiltin(boolean builtin) { - this.builtin = builtin; - } - public boolean isEnable() { return enable; } @@ -64,11 +57,11 @@ public class ClientPolicyModel implements Serializable { this.enable = enable; } - public List getConditions() { + public List getConditions() { return conditions; } - public void setConditions(List conditions) { + public void setConditions(List conditions) { this.conditions = conditions; } diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientProfileModel.java b/services/src/main/java/org/keycloak/services/clientpolicy/ClientProfileModel.java similarity index 78% rename from server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientProfileModel.java rename to services/src/main/java/org/keycloak/services/clientpolicy/ClientProfileModel.java index a4bc0c3bdd..5379b128b6 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientProfileModel.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/ClientProfileModel.java @@ -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; @@ -20,6 +21,8 @@ package org.keycloak.services.clientpolicy; import java.io.Serializable; import java.util.List; +import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; + /** * @author Takashi Norimatsu */ @@ -27,8 +30,7 @@ public class ClientProfileModel implements Serializable { protected String name; protected String description; - protected boolean builtin; - protected List executors; // ClientPolicyExecutorProvider is not visible so that use Object. + protected List executors; public String getName() { return name; @@ -46,19 +48,11 @@ public class ClientProfileModel implements Serializable { this.description = description; } - public boolean isBuiltin() { - return builtin; - } - - public void setBuiltin(boolean builtin) { - this.builtin = builtin; - } - - public List getExecutors() { + public List getExecutors() { return executors; } - public void setExecutors(List executors) { + public void setExecutors(List executors) { this.executors = executors; } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManager.java b/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManager.java index 110fa21fb1..33201f4b1c 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManager.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManager.java @@ -17,8 +17,11 @@ 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; @@ -31,6 +34,7 @@ import org.keycloak.representations.idm.ClientProfilesRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; +import org.keycloak.util.JsonSerialization; /** * @author Takashi Norimatsu @@ -40,9 +44,11 @@ public class DefaultClientPolicyManager implements ClientPolicyManager { private static final Logger logger = Logger.getLogger(DefaultClientPolicyManager.class); private final KeycloakSession session; + private final Supplier> globalClientProfilesSupplier; - public DefaultClientPolicyManager(KeycloakSession session) { + public DefaultClientPolicyManager(KeycloakSession session, Supplier> globalClientProfilesSupplier) { this.session = session; + this.globalClientProfilesSupplier = globalClientProfilesSupplier; } @Override @@ -62,8 +68,8 @@ public class DefaultClientPolicyManager implements ClientPolicyManager { } private void doPolicyOperation(ClientConditionOperation condition, ClientExecutorOperation executor, RealmModel realm) throws ClientPolicyException { - Map map = ClientPoliciesUtil.getClientProfilesModel(session, realm); - List list = ClientPoliciesUtil.getEnabledClientProfilesModel(session, realm).stream().collect(Collectors.toList()); + Map map = ClientPoliciesUtil.getClientProfilesModel(session, realm, globalClientProfilesSupplier.get()); + List list = ClientPoliciesUtil.getEnabledClientPoliciesModel(session, realm).stream().collect(Collectors.toList()); if (list == null || list.isEmpty()) { logger.trace("POLICY OPERATION :: No enabled policy."); @@ -71,13 +77,13 @@ public class DefaultClientPolicyManager implements ClientPolicyManager { } for (ClientPolicyModel policy: list) { - logger.tracev("POLICY OPERATION :: policy name = {0}, isBuiltin = {1}", policy.getName(), policy.isBuiltin()); + logger.tracev("POLICY OPERATION :: policy name = {0}", policy.getName()); if (!isSatisfied(policy, condition)) { - logger.tracev("POLICY UNSATISFIED :: policy name = {0}, isBuiltin = {1}", policy.getName(), policy.isBuiltin()); + logger.tracev("POLICY UNSATISFIED :: policy name = {0}", policy.getName()); continue; } - logger.tracev("POLICY APPLIED :: policy name = {0}, isBuiltin = {1}", policy.getName(), policy.isBuiltin()); + logger.tracev("POLICY APPLIED :: policy name = {0}", policy.getName()); execute(policy, executor, map); } } @@ -92,8 +98,7 @@ public class DefaultClientPolicyManager implements ClientPolicyManager { } boolean ret = false; - for (Object obj : policy.getConditions()) { - ClientPolicyConditionProvider condition = (ClientPolicyConditionProvider)obj; + for (ClientPolicyConditionProvider condition : policy.getConditions()) { logger.tracev("CONDITION OPERATION :: policy name = {0}, condition name = {1}, provider id = {2}", policy.getName(), condition.getName(), condition.getProviderId()); try { ClientPolicyVote vote = op.run(condition); @@ -148,8 +153,7 @@ public class DefaultClientPolicyManager implements ClientPolicyManager { continue; } - for (Object obj : profile.getExecutors()) { - ClientPolicyExecutorProvider executor = (ClientPolicyExecutorProvider)obj; + for (ClientPolicyExecutorProvider executor : profile.getExecutors()) { logger.tracev("EXECUTION :: policy name = {0}, profile name = {1}, executor name = {2}, provider id = {3}", policy.getName(), profileName, executor.getName(), executor.getProviderId()); try { op.run(executor); @@ -170,236 +174,121 @@ public class DefaultClientPolicyManager implements ClientPolicyManager { void run(ClientPolicyExecutorProvider executor) throws ClientPolicyException; } - - // Client Polices Realm Attributes Keys - public static final String CLIENT_PROFILES = "client-policies.profiles"; - public static final String CLIENT_POLICIES = "client-policies.policies"; - - // builtin profiles and policies are loaded on booting keycloak at once. - // therefore, their representations are kept and remain unchanged. - // these are shared among all realms. - - // those can be null to show that no profile/policy exist - private static String builtinClientProfilesJson; - private static String builtinClientPoliciesJson; - - @Override - public void setupClientPoliciesOnKeycloakApp(String profilesFilePath, String policiesFilePath) { - logger.trace("LOAD BUILTIN PROFILE POLICIES ON KEYCLOAK"); - - // client profile can be referred from client policy so that client profile needs to be loaded at first. - // load builtin profiles on keycloak app - ClientProfilesRepresentation validatedProfilesRep = null; - try { - validatedProfilesRep = ClientPoliciesUtil.getValidatedBuiltinClientProfilesRepresentation(session, getClass().getResourceAsStream(profilesFilePath)); - } catch (ClientPolicyException cpe) { - logger.warnv("LOAD BUILTIN PROFILES ON KEYCLOAK FAILED :: error = {0}, error detail = {1}", cpe.getError(), cpe.getErrorDetail()); - return; - } - - String validatedJson = null; - try { - validatedJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(validatedProfilesRep); - } catch (ClientPolicyException cpe) { - logger.warnv("VALIDATE SERIALIZE BUILTIN PROFILES ON KEYCLOAK FAILED :: error = {0}, error detail = {1}", cpe.getError(), cpe.getErrorDetail()); - return; - } - - builtinClientProfilesJson = validatedJson; - - // load builtin policies on keycloak app - ClientPoliciesRepresentation validatedPoliciesRep = null; - try { - validatedPoliciesRep = ClientPoliciesUtil.getValidatedBuiltinClientPoliciesRepresentation(session, getClass().getResourceAsStream(policiesFilePath)); - } catch (ClientPolicyException cpe) { - logger.warnv("LOAD BUILTIN POLICIES ON KEYCLOAK FAILED :: error = {0}, error detail = {1}", cpe.getError(), cpe.getErrorDetail()); - builtinClientProfilesJson = null; - return; - } - - validatedJson = null; - try { - validatedJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(validatedPoliciesRep); - } catch (ClientPolicyException cpe) { - logger.warnv("VALIDATE SERIALIZE BUILTIN POLICIES ON KEYCLOAK FAILED :: error = {0}, error detail = {1}", cpe.getError(), cpe.getErrorDetail()); - builtinClientProfilesJson = null; - return; - } - - builtinClientPoliciesJson = validatedJson; - } - @Override public void setupClientPoliciesOnCreatedRealm(RealmModel realm) { - logger.tracev("LOAD BUILTIN PROFILE POLICIES ON CREATED REALM :: realm = {0}", realm.getName()); - - // put already loaded builtin profiles/policies on keycloak app to newly created realm - setClientProfilesJsonString(realm, builtinClientProfilesJson); - setClientPoliciesJsonString(realm, builtinClientPoliciesJson); + // For now, not create any create policies on the new realms. Administrator is supposed to add the policies if needed } @Override - public void setupClientPoliciesOnImportedRealm(RealmModel realm, RealmRepresentation rep) { + public void updateRealmModelFromRepresentation(RealmModel realm, RealmRepresentation rep) { logger.tracev("LOAD PROFILE POLICIES ON IMPORTED REALM :: realm = {0}", realm.getName()); - // put already loaded builtin profiles/policies on keycloak app to newly created realm - setClientProfilesJsonString(realm, builtinClientProfilesJson); - setClientPoliciesJsonString(realm, builtinClientPoliciesJson); - - // merge imported polices/profiles with builtin policies/profiles - String validatedJson = null; - try { - validatedJson = ClientPoliciesUtil.getValidatedClientProfilesJson(session, realm, rep.getClientProfiles()); - } catch (ClientPolicyException e) { - logger.warnv("VALIDATE SERIALIZE IMPORTED REALM PROFILES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail()); - // revert to builtin profiles - validatedJson = builtinClientProfilesJson; + if (rep.getClientProfiles() != null) { + try { + updateClientProfiles(realm, rep.getClientProfiles()); + } catch (ClientPolicyException e) { + logger.warnv("VALIDATE SERIALIZE IMPORTED REALM PROFILES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail()); + throw new RuntimeException("Failed to update client profiles", e); + } } - setClientProfilesJsonString(realm, validatedJson); - try { - validatedJson = ClientPoliciesUtil.getValidatedClientPoliciesJson(session, realm, rep.getClientPolicies()); - } catch (ClientPolicyException e) { - logger.warnv("VALIDATE SERIALIZE IMPORTED REALM POLICIES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail()); - // revert to builtin profiles - validatedJson = builtinClientPoliciesJson; + if (rep.getClientPolicies() != null) { + try { + updateClientPolicies(realm, rep.getClientPolicies()); + } catch (ClientPolicyException e) { + logger.warnv("VALIDATE SERIALIZE IMPORTED REALM POLICIES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail()); + throw new RuntimeException("Failed to update client policies", e); + } + } else { + setupClientPoliciesOnCreatedRealm(realm); } - setClientPoliciesJsonString(realm, validatedJson); } @Override - public void updateClientProfiles(RealmModel realm, String json) throws ClientPolicyException { - logger.tracev("UPDATE PROFILES :: realm = {0}, PUT = {1}", realm.getName(), json); - String validatedJsonString = null; + public void updateClientProfiles(RealmModel realm, ClientProfilesRepresentation clientProfiles) throws ClientPolicyException { try { - validatedJsonString = getValidatedClientProfilesJson(realm, json); + if (clientProfiles == null) { + throw new ClientPolicyException("Passing null clientProfiles not allowed"); + } + ClientProfilesRepresentation validatedProfilesRep = ClientPoliciesUtil.getValidatedClientProfilesForUpdate(session, realm, clientProfiles, globalClientProfilesSupplier.get()); + String validatedJsonString = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(validatedProfilesRep); + ClientPoliciesUtil.setClientProfilesJsonString(realm, validatedJsonString); + logger.tracev("UPDATE PROFILES :: realm = {0}, validated and modified PUT = {1}", realm.getName(), validatedJsonString); } catch (ClientPolicyException e) { logger.warnv("VALIDATE SERIALIZE PROFILES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail()); throw e; } - setClientProfilesJsonString(realm, validatedJsonString); - logger.tracev("UPDATE PROFILES :: realm = {0}, validated and modified PUT = {1}", realm.getName(), validatedJsonString); } @Override - public String getClientProfiles(RealmModel realm) { - String json = getClientProfilesJsonString(realm); - logger.tracev("GET PROFILES :: realm = {0}, GET = {1}", realm.getName(), json); - return json; + public ClientProfilesRepresentation getClientProfiles(RealmModel realm, boolean includeGlobalProfiles) throws ClientPolicyException { + try { + ClientProfilesRepresentation clientProfiles = ClientPoliciesUtil.getClientProfilesRepresentation(session, realm); + if (includeGlobalProfiles) { + clientProfiles.setGlobalProfiles(new LinkedList<>(globalClientProfilesSupplier.get())); + } + + if (logger.isTraceEnabled()) { + logger.tracev("GET PROFILES :: realm = {0}, GET = {1}", realm.getName(), JsonSerialization.writeValueAsString(clientProfiles)); + } + + return clientProfiles; + } catch (ClientPolicyException e) { + logger.warnv("GET CLIENT PROFILES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail()); + throw e; + } catch (IOException ioe) { + throw new RuntimeException("Unexpected exception when converting JSON to String", ioe); + } } @Override - public void updateClientPolicies(RealmModel realm, String json) throws ClientPolicyException { - logger.tracev("UPDATE POLICIES :: realm = {0}, PUT = {1}", realm.getName(), json); + public void updateClientPolicies(RealmModel realm, ClientPoliciesRepresentation clientPolicies) throws ClientPolicyException { String validatedJsonString = null; try { - validatedJsonString = getValidatedClientPoliciesJson(realm, json); + if (clientPolicies == null) { + throw new ClientPolicyException("Passing null clientPolicies not allowed"); + } + ClientPoliciesRepresentation clientPoliciesRep = ClientPoliciesUtil.getValidatedClientPoliciesForUpdate(session, realm, clientPolicies, globalClientProfilesSupplier.get()); + validatedJsonString = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(clientPoliciesRep); } catch (ClientPolicyException e) { logger.warnv("VALIDATE SERIALIZE POLICIES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail()); throw e; } - setClientPoliciesJsonString(realm, validatedJsonString); + ClientPoliciesUtil.setClientPoliciesJsonString(realm, validatedJsonString); logger.tracev("UPDATE POLICIES :: realm = {0}, validated and modified PUT = {1}", realm.getName(), validatedJsonString); } @Override - public void setupClientPoliciesOnExportingRealm(RealmModel realm, RealmRepresentation rep) { - // client profiles that filter out builtin profiles.. - ClientProfilesRepresentation filteredOutProfiles = null; + public ClientPoliciesRepresentation getClientPolicies(RealmModel realm) throws ClientPolicyException { try { - filteredOutProfiles = getClientProfilesForExport(realm); - } catch (ClientPolicyException e) { - // set as null - } - rep.setClientProfiles(filteredOutProfiles); - - // client policies that filter out builtin and policies. - ClientPoliciesRepresentation filteredOutPolicies = null; - try { - filteredOutPolicies = getClientPoliciesForExport(realm); - } catch (ClientPolicyException e) { - // set as null - } - rep.setClientPolicies(filteredOutPolicies); - } - - @Override - public String getClientPolicies(RealmModel realm) { - String json = getClientPoliciesJsonString(realm); - logger.tracev("GET POLICIES :: realm = {0}, GET = {1}", realm.getName(), json); - return json; - } - - @Override - public String getClientProfilesOnKeycloakApp() { - return builtinClientProfilesJson; - } - - @Override - public String getClientPoliciesOnKeycloakApp() { - return builtinClientPoliciesJson; - } - - @Override - public String getClientProfilesJsonString(RealmModel realm) { - return realm.getAttribute(CLIENT_PROFILES); - } - - @Override - public String getClientPoliciesJsonString(RealmModel realm) { - return realm.getAttribute(CLIENT_POLICIES); - } - - private void setClientProfilesJsonString(RealmModel realm, String json) { - realm.setAttribute(CLIENT_PROFILES, json); - } - - private void setClientPoliciesJsonString(RealmModel realm, String json) { - realm.setAttribute(CLIENT_POLICIES, json); - } - - private String getValidatedClientProfilesJson(RealmModel realm, String profilesJson) throws ClientPolicyException { - ClientProfilesRepresentation validatedProfilesRep = ClientPoliciesUtil.getValidatedClientProfilesRepresentation(session, realm, profilesJson); - return ClientPoliciesUtil.convertClientProfilesRepresentationToJson(validatedProfilesRep); - } - - private String getValidatedClientPoliciesJson(RealmModel realm, String policiesJson) throws ClientPolicyException { - ClientPoliciesRepresentation validatedPoliciesRep = ClientPoliciesUtil.getValidatedClientPoliciesRepresentation(session, realm, policiesJson); - return ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(validatedPoliciesRep); - } - - /** - * not return null - */ - private ClientProfilesRepresentation getClientProfilesForExport(RealmModel realm) throws ClientPolicyException { - ClientProfilesRepresentation profilesRep = ClientPoliciesUtil.getClientProfilesRepresentation(session, realm); - if (profilesRep == null || profilesRep.getProfiles() == null) { - return new ClientProfilesRepresentation(); - } - - // not export builtin profiles - List filteredProfileRepList = profilesRep.getProfiles().stream().filter(profileRep->!profileRep.isBuiltin()).collect(Collectors.toList()); - profilesRep.setProfiles(filteredProfileRepList); - return profilesRep; - } - - /** - * not return null - */ - private ClientPoliciesRepresentation getClientPoliciesForExport(RealmModel realm) throws ClientPolicyException { - ClientPoliciesRepresentation policiesRep = ClientPoliciesUtil.getClientPoliciesRepresentation(session, realm); - if (policiesRep == null || policiesRep.getPolicies() == null) { - return new ClientPoliciesRepresentation(); - } - - policiesRep.getPolicies().stream().forEach(policyRep->{ - if (policyRep.isBuiltin()) { - // only keeps name, builtin and enabled fields. - policyRep.setDescription(null); - policyRep.setConditions(null); - policyRep.setProfiles(null); + ClientPoliciesRepresentation clientPolicies = ClientPoliciesUtil.getClientPoliciesRepresentation(session, realm); + if (logger.isTraceEnabled()) { + logger.tracev("GET POLICIES :: realm = {0}, GET = {1}", realm.getName(), JsonSerialization.writeValueAsString(clientPolicies)); } - }); - return policiesRep; + return clientPolicies; + } catch (ClientPolicyException e) { + logger.warnv("GET CLIENT POLICIES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail()); + throw e; + } catch (IOException ioe) { + throw new RuntimeException("Unexpected exception when converting JSON to String", ioe); + } + } + + @Override + public void updateRealmRepresentationFromModel(RealmModel realm, RealmRepresentation rep) { + try { + // client profiles that filter out global profiles.. + ClientProfilesRepresentation filteredOutProfiles = getClientProfiles(realm, false); + rep.setClientProfiles(filteredOutProfiles); + + ClientPoliciesRepresentation filteredOutPolicies = getClientPolicies(realm); + rep.setClientPolicies(filteredOutPolicies); + } catch (ClientPolicyException cpe) { + throw new IllegalStateException("Exception during export client profiles or client policies", cpe); + } + } + + @Override + public void close() { } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManagerFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManagerFactory.java new file mode 100644 index 0000000000..8ccfd01aa7 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManagerFactory.java @@ -0,0 +1,87 @@ +/* + * 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.services.clientpolicy; + +import java.util.List; + +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.representations.idm.ClientProfileRepresentation; + +/** + * @author Marek Posolda + */ +public class DefaultClientPolicyManagerFactory implements ClientPolicyManagerFactory { + + private static final Logger logger = Logger.getLogger(DefaultClientPolicyManagerFactory.class); + + // Global (builtin) profiles are loaded on booting keycloak at once. + // therefore, their representations are kept and remain unchanged. + // these are shared among all realms. + private volatile List globalClientProfiles; + + @Override + public ClientPolicyManager create(KeycloakSession session) { + return new DefaultClientPolicyManager(session, () -> getGlobalClientProfiles(session)); + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return "default"; + } + + /** + * When this method is called, assumption is that CLIENT_POLICIES feature is enabled + */ + protected List getGlobalClientProfiles(KeycloakSession session) { + if (globalClientProfiles == null) { + synchronized (this) { + if (globalClientProfiles == null) { + logger.trace("LOAD GLOBAL CLIENT PROFILES ON KEYCLOAK"); + + // load builtin profiles from keycloak-services + try { + this.globalClientProfiles = ClientPoliciesUtil.getValidatedGlobalClientProfilesRepresentation(session, getClass().getResourceAsStream("/keycloak-default-client-profiles.json")); + } catch (ClientPolicyException cpe) { + logger.warnv("LOAD GLOBAL PROFILES ON KEYCLOAK FAILED :: error = {0}, error detail = {1}", cpe.getError(), cpe.getErrorDetail()); + throw new IllegalStateException(cpe); + } + } + } + } + return globalClientProfiles; + } +} diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientCondition.java index eaf1244567..b54c6154cb 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientCondition.java @@ -17,54 +17,24 @@ package org.keycloak.services.clientpolicy.condition; -import java.util.Optional; - import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyVote; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * @author Takashi Norimatsu */ -public class AnyClientCondition implements ClientPolicyConditionProvider { - - // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. - private Configuration configuration = new Configuration(); +public class AnyClientCondition extends AbstractClientPolicyConditionProvider { public AnyClientCondition(KeycloakSession session) { + super(session); } @Override - public void setupConfiguration(Configuration config) { - this.configuration = config; - } - - @Override - public Class getConditionConfigurationClass() { - return Configuration.class; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Configuration extends ClientPolicyConditionConfiguration { - @JsonProperty("is-negative-logic") - protected Boolean negativeLogic; - - public Boolean isNegativeLogic() { - return negativeLogic; - } - - public void setNegativeLogic(Boolean negativeLogic) { - this.negativeLogic = negativeLogic; - } - } - - @Override - public boolean isNegativeLogic() { - return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue(); + public Class getConditionConfigurationClass() { + return ClientPolicyConditionConfigurationRepresentation.class; } @Override diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java index 4e62c43876..a58abb2b64 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java @@ -24,31 +24,20 @@ import java.util.Optional; import org.jboss.logging.Logger; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyVote; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * @author Takashi Norimatsu */ -public class ClientAccessTypeCondition implements ClientPolicyConditionProvider { +public class ClientAccessTypeCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientAccessTypeCondition.class); - // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. - private Configuration configuration = new Configuration(); - private final KeycloakSession session; - public ClientAccessTypeCondition(KeycloakSession session) { - this.session = session; - } - - @Override - public void setupConfiguration(Configuration config) { - this.configuration = config; + super(session); } @Override @@ -56,18 +45,7 @@ public class ClientAccessTypeCondition implements ClientPolicyConditionProvider< return Configuration.class; } - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Configuration extends ClientPolicyConditionConfiguration { - @JsonProperty("is-negative-logic") - protected Boolean negativeLogic; - - public Boolean isNegativeLogic() { - return negativeLogic; - } - - public void setNegativeLogic(Boolean negativeLogic) { - this.negativeLogic = negativeLogic; - } + public static class Configuration extends ClientPolicyConditionConfigurationRepresentation { protected List type; @@ -80,11 +58,6 @@ public class ClientAccessTypeCondition implements ClientPolicyConditionProvider< } } - @Override - public boolean isNegativeLogic() { - return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue(); - } - @Override public String getProviderId() { return ClientAccessTypeConditionFactory.PROVIDER_ID; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeConditionFactory.java index 31861064a8..6efb6e5940 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeConditionFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeConditionFactory.java @@ -73,7 +73,7 @@ public class ClientAccessTypeConditionFactory implements ClientPolicyConditionPr @Override public String getHelpText() { - return "It uses the client's access type (confidential, public, bearer-only) to determine whether the policy is applied."; + return "It uses the client's access type (confidential, public, bearer-only) to determine whether the policy is applied. Condition is checked during most of OpenID Connect requests (Authorization request, token requests, introspection endpoint request etc)."; } @Override diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesCondition.java index 3407fff081..f21ba33a38 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesCondition.java @@ -19,7 +19,6 @@ package org.keycloak.services.clientpolicy.condition; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -27,31 +26,20 @@ import org.jboss.logging.Logger; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RoleModel; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyVote; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * @author Takashi Norimatsu */ -public class ClientRolesCondition implements ClientPolicyConditionProvider { +public class ClientRolesCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientRolesCondition.class); - // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. - private Configuration configuration = new Configuration(); - private final KeycloakSession session; - public ClientRolesCondition(KeycloakSession session) { - this.session = session; - } - - @Override - public void setupConfiguration(Configuration config) { - this.configuration = config; + super(session); } @Override @@ -59,18 +47,7 @@ public class ClientRolesCondition implements ClientPolicyConditionProvider roles; @@ -83,11 +60,6 @@ public class ClientRolesCondition implements ClientPolicyConditionProviderTakashi Norimatsu */ -public class ClientScopesCondition implements ClientPolicyConditionProvider { +public class ClientScopesCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientScopesCondition.class); - // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. - private Configuration configuration = new Configuration(); - private final KeycloakSession session; - public ClientScopesCondition(KeycloakSession session) { - this.session = session; - } - - @Override - public void setupConfiguration(Configuration config) { - this.configuration = config; + super(session); } @Override @@ -64,18 +52,7 @@ public class ClientScopesCondition implements ClientPolicyConditionProvider scope; @@ -97,11 +74,6 @@ public class ClientScopesCondition implements ClientPolicyConditionProvider configProperties = new ArrayList(); static { - ProviderConfigProperty property; - property = new ProviderConfigProperty(SCOPES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "offline_access"); + ProviderConfigProperty property = new ProviderConfigProperty(SCOPES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, OAuth2Constants.OFFLINE_ACCESS); configProperties.add(property); - property = new ProviderConfigProperty(TYPE, "Scope Type", "Default or Optional", ProviderConfigProperty.LIST_TYPE, OPTIONAL); + property = new ProviderConfigProperty(TYPE, "Scope Type", + "If set to 'Default', condition evaluates to true if client has some default scopes of the values specified by the 'Expected Scopes' property. " + + "If set to 'Optional', condition evaluates to true if client has some optional scopes of the values specified by the 'Expected Scopes' property and at the same time, the scope were used as a value of 'scope' parameter in the request", + ProviderConfigProperty.LIST_TYPE, OPTIONAL); + property.setOptions(Arrays.asList(DEFAULT, OPTIONAL)); configProperties.add(property); } @@ -71,7 +76,7 @@ public class ClientScopesConditionFactory implements ClientPolicyConditionProvid @Override public String getHelpText() { - return "It uses the scopes requested or assigned in advance to the client to determine whether the policy is applied to this client."; + return "It uses the scopes requested or assigned in advance to the client to determine whether the policy is applied to this client. Condition is evaluated during OpenID Connect authorization request and/or token request."; } @Override diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextCondition.java index e6bd4e28a3..4195bf8771 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextCondition.java @@ -19,11 +19,11 @@ package org.keycloak.services.clientpolicy.condition; import java.util.Collections; import java.util.List; -import java.util.Optional; import org.jboss.logging.Logger; import org.keycloak.models.KeycloakSession; import org.keycloak.representations.JsonWebToken; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyVote; @@ -31,27 +31,17 @@ import org.keycloak.services.clientpolicy.context.ClientCRUDContext; import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils; import org.keycloak.util.TokenUtil; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** * @author Takashi Norimatsu */ -public class ClientUpdateContextCondition implements ClientPolicyConditionProvider { +public class ClientUpdateContextCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateContextCondition.class); - // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. - private Configuration configuration = new Configuration(); - private final KeycloakSession session; - public ClientUpdateContextCondition(KeycloakSession session) { - this.session = session; - } - - @Override - public void setupConfiguration(Configuration config) { - this.configuration = config; + super(session); } @Override @@ -59,18 +49,7 @@ public class ClientUpdateContextCondition implements ClientPolicyConditionProvid return Configuration.class; } - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Configuration extends ClientPolicyConditionConfiguration { - @JsonProperty("is-negative-logic") - protected Boolean negativeLogic; - - public Boolean isNegativeLogic() { - return negativeLogic; - } - - public void setNegativeLogic(Boolean negativeLogic) { - this.negativeLogic = negativeLogic; - } + public static class Configuration extends ClientPolicyConditionConfigurationRepresentation { @JsonProperty("update-client-source") protected List updateClientSource; @@ -84,11 +63,6 @@ public class ClientUpdateContextCondition implements ClientPolicyConditionProvid } } - @Override - public boolean isNegativeLogic() { - return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue(); - } - @Override public String getProviderId() { return ClientUpdateContextConditionFactory.PROVIDER_ID; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextConditionFactory.java index 676d0ed9b6..92d60093a9 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextConditionFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextConditionFactory.java @@ -44,7 +44,11 @@ public class ClientUpdateContextConditionFactory implements ClientPolicyConditio static { ProviderConfigProperty property; - property = new ProviderConfigProperty(UPDATE_CLIENT_SOURCE, null, null, ProviderConfigProperty.MULTIVALUED_LIST_TYPE, BY_AUTHENTICATED_USER); + property = new ProviderConfigProperty(UPDATE_CLIENT_SOURCE, "Update Client Context", "Specifies the context how is client created or updated. " + + "ByInitialAccessToken is usually OpenID Connect client registration with the initial access token. " + + "ByRegistrationAccessToken is usually OpenID Connect client update request with the registration access token. " + + "ByAuthenticatedUser is usually Admin REST request with the token on behalf of authenticated user or client (service account). ByAnonymous is usually anonymous OpenID Client registration request.", + ProviderConfigProperty.MULTIVALUED_LIST_TYPE, BY_AUTHENTICATED_USER); List updateProfileValues = Arrays.asList(BY_AUTHENTICATED_USER, BY_ANONYMOUS, BY_INITIAL_ACCESS_TOKEN, BY_REGISTRATION_ACCESS_TOKEN); property.setOptions(updateProfileValues); configProperties.add(property); diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsCondition.java index 90ea902d57..883867cb20 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsCondition.java @@ -19,7 +19,6 @@ package org.keycloak.services.clientpolicy.condition; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -29,6 +28,7 @@ import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; import org.keycloak.representations.JsonWebToken; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyVote; @@ -38,27 +38,15 @@ import org.keycloak.services.clientpolicy.context.ClientCRUDContext; import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext; import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * @author Takashi Norimatsu */ -public class ClientUpdateSourceGroupsCondition implements ClientPolicyConditionProvider { +public class ClientUpdateSourceGroupsCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateSourceGroupsCondition.class); - // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. - private Configuration configuration = new Configuration(); - private final KeycloakSession session; - public ClientUpdateSourceGroupsCondition(KeycloakSession session) { - this.session = session; - } - - @Override - public void setupConfiguration(Configuration config) { - this.configuration = config; + super(session); } @Override @@ -66,18 +54,7 @@ public class ClientUpdateSourceGroupsCondition implements ClientPolicyConditionP return Configuration.class; } - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Configuration extends ClientPolicyConditionConfiguration { - @JsonProperty("is-negative-logic") - protected Boolean negativeLogic; - - public Boolean isNegativeLogic() { - return negativeLogic; - } - - public void setNegativeLogic(Boolean negativeLogic) { - this.negativeLogic = negativeLogic; - } + public static class Configuration extends ClientPolicyConditionConfigurationRepresentation { protected List groups; @@ -90,11 +67,6 @@ public class ClientUpdateSourceGroupsCondition implements ClientPolicyConditionP } } - @Override - public boolean isNegativeLogic() { - return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue(); - } - @Override public String getProviderId() { return ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsCondition.java index cfcf3603f2..ebe448d85e 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsCondition.java @@ -21,36 +21,26 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.LinkedList; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyVote; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** * @author Takashi Norimatsu */ -public class ClientUpdateSourceHostsCondition implements ClientPolicyConditionProvider { +public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateSourceHostsCondition.class); - // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. - private Configuration configuration = new Configuration(); - private final KeycloakSession session; - public ClientUpdateSourceHostsCondition(KeycloakSession session) { - this.session = session; - } - - @Override - public void setupConfiguration(Configuration config) { - this.configuration = config; + super(session); } @Override @@ -59,18 +49,7 @@ public class ClientUpdateSourceHostsCondition implements ClientPolicyConditionPr } - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Configuration extends ClientPolicyConditionConfiguration { - @JsonProperty("is-negative-logic") - protected Boolean negativeLogic; - - public Boolean isNegativeLogic() { - return negativeLogic; - } - - public void setNegativeLogic(Boolean negativeLogic) { - this.negativeLogic = negativeLogic; - } + public static class Configuration extends ClientPolicyConditionConfigurationRepresentation { @JsonProperty("trusted-hosts") protected List trustedHosts; @@ -84,11 +63,6 @@ public class ClientUpdateSourceHostsCondition implements ClientPolicyConditionPr } } - @Override - public boolean isNegativeLogic() { - return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue(); - } - @Override public String getProviderId() { return ClientUpdateSourceHostsConditionFactory.PROVIDER_ID; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesCondition.java index 9f4a811125..9840c62ccd 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesCondition.java @@ -19,7 +19,6 @@ package org.keycloak.services.clientpolicy.condition; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -29,7 +28,9 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.JsonWebToken; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyVote; @@ -39,27 +40,16 @@ import org.keycloak.services.clientpolicy.context.ClientCRUDContext; import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext; import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; /** * @author Takashi Norimatsu */ -public class ClientUpdateSourceRolesCondition implements ClientPolicyConditionProvider { +public class ClientUpdateSourceRolesCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateSourceRolesCondition.class); - // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. - private Configuration configuration = new Configuration(); - private final KeycloakSession session; - public ClientUpdateSourceRolesCondition(KeycloakSession session) { - this.session = session; - } - - @Override - public void setupConfiguration(Configuration config) { - this.configuration = config; + super(session); } @Override @@ -67,18 +57,7 @@ public class ClientUpdateSourceRolesCondition implements ClientPolicyConditionPr return Configuration.class; } - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Configuration extends ClientPolicyConditionConfiguration { - @JsonProperty("is-negative-logic") - protected Boolean negativeLogic; - - public Boolean isNegativeLogic() { - return negativeLogic; - } - - public void setNegativeLogic(Boolean negativeLogic) { - this.negativeLogic = negativeLogic; - } + public static class Configuration extends ClientPolicyConditionConfigurationRepresentation { protected List roles; @@ -91,11 +70,6 @@ public class ClientUpdateSourceRolesCondition implements ClientPolicyConditionPr } } - @Override - public boolean isNegativeLogic() { - return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue(); - } - @Override public String getProviderId() { return ClientUpdateSourceRolesConditionFactory.PROVIDER_ID; @@ -147,28 +121,21 @@ public class ClientUpdateSourceRolesCondition implements ClientPolicyConditionPr Set expectedRoles = instantiateRolesForMatching(); if (expectedRoles == null) return false; - // user.getRoleMappingsStream() never returns null according to {@link UserModel.getRoleMappingsStream} - Set roles = user.getRoleMappingsStream().map(RoleModel::getName).collect(Collectors.toSet()); - if (logger.isTraceEnabled()) { + // user.getRoleMappingsStream() never returns null according to {@link UserModel.getRoleMappingsStream} + Set roles = user.getRoleMappingsStream().map(RoleModel::getName).collect(Collectors.toSet()); + roles.forEach(i -> logger.tracev("user role = {0}", i)); expectedRoles.forEach(i -> logger.tracev("roles expected = {0}", i)); } RealmModel realm = session.getContext().getRealm(); - boolean isMatched = expectedRoles.stream().anyMatch(i->{ - if (realm.getRole(i) != null && user.hasRole(realm.getRole(i))) { - return true; - } - return realm.getClientsStream().anyMatch(j->{ - if (j.getRole(i) != null && user.hasRole(j.getRole(i))) { - return true; - } - return false; - }); - }); - - return isMatched; + for (String roleName : expectedRoles) { + RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName); + if (role == null) continue; + if (user.hasRole(role)) return true; + } + return false; } private Set instantiateRolesForMatching() { diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConfidentialClientAcceptExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConfidentialClientAcceptExecutor.java index bbeae4436c..7bf6f2344e 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConfidentialClientAcceptExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConfidentialClientAcceptExecutor.java @@ -20,15 +20,14 @@ package org.keycloak.services.clientpolicy.executor; import org.keycloak.OAuthErrorException; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - /** * @author Takashi Norimatsu */ -public class ConfidentialClientAcceptExecutor implements ClientPolicyExecutorProvider { +public class ConfidentialClientAcceptExecutor implements ClientPolicyExecutorProvider { protected final KeycloakSession session; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConsentRequiredExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConsentRequiredExecutor.java index 4c6e1e2ca8..c3858d69fa 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConsentRequiredExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/ConsentRequiredExecutor.java @@ -19,6 +19,7 @@ package org.keycloak.services.clientpolicy.executor; import org.keycloak.events.Errors; import org.keycloak.models.ClientModel; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; @@ -27,7 +28,7 @@ import org.keycloak.services.clientpolicy.context.ClientCRUDContext; /** * @author Takashi Norimatsu */ -public class ConsentRequiredExecutor implements ClientPolicyExecutorProvider { +public class ConsentRequiredExecutor implements ClientPolicyExecutorProvider { @Override public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException { diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutor.java index 9dd65b86a1..213f108ef3 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutor.java @@ -26,6 +26,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.representations.AccessToken; import org.keycloak.representations.RefreshToken; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; @@ -36,7 +37,6 @@ import org.keycloak.services.clientpolicy.context.TokenRevokeContext; import org.keycloak.services.clientpolicy.context.UserInfoRequestContext; import org.keycloak.services.util.MtlsHoKTokenUtil; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import javax.ws.rs.core.MultivaluedMap; @@ -61,8 +61,7 @@ public class HolderOfKeyEnforceExecutor implements ClientPolicyExecutorProvider< return Configuration.class; } - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Configuration extends ClientPolicyExecutorConfiguration { + public static class Configuration extends ClientPolicyExecutorConfigurationRepresentation { @JsonProperty("is-augment") protected Boolean augment; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutorFactory.java index 5ded79e941..43f116ba9d 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutorFactory.java @@ -33,7 +33,7 @@ public class HolderOfKeyEnforceExecutorFactory implements ClientPolicyExecutorPr public static final String IS_AUGMENT = "is-augment"; private static final ProviderConfigProperty IS_AUGMENT_PROPERTY = new ProviderConfigProperty( - IS_AUGMENT, null, null, ProviderConfigProperty.BOOLEAN_TYPE, false); + IS_AUGMENT, "Augment Configuration", "If On, then the during client creation or update, the configuration of the client will be augmented to use MTLS HoK token", ProviderConfigProperty.BOOLEAN_TYPE, false); @Override public ClientPolicyExecutorProvider create(KeycloakSession session) { diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutor.java index 669fee05a1..76024737a0 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutor.java @@ -35,6 +35,7 @@ import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest import org.keycloak.protocol.oidc.utils.OAuth2Code; import org.keycloak.protocol.oidc.utils.OAuth2CodeParser; import org.keycloak.protocol.oidc.utils.OIDCResponseType; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; @@ -42,7 +43,6 @@ import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; import org.keycloak.services.clientpolicy.context.ClientCRUDContext; import org.keycloak.services.clientpolicy.context.TokenRequestContext; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -70,8 +70,7 @@ public class PKCEEnforceExecutor implements ClientPolicyExecutorProvider clientAuthns; @JsonProperty("client-authns-augment") diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutorFactory.java index 3c69fc3df6..52147643a1 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutorFactory.java @@ -20,12 +20,15 @@ package org.keycloak.services.clientpolicy.executor; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.keycloak.Config.Scope; +import org.keycloak.authentication.ClientAuthenticator; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderFactory; /** * @author Takashi Norimatsu @@ -38,12 +41,7 @@ public class SecureClientAuthEnforceExecutorFactory implements ClientPolicyExecu public static final String CLIENT_AUTHNS = "client-authns"; public static final String CLIENT_AUTHNS_AUGMENT = "client-authns-augment"; - private static final ProviderConfigProperty IS_AUGMENT_PROPERTY = new ProviderConfigProperty( - IS_AUGMENT, null, null, ProviderConfigProperty.BOOLEAN_TYPE, false); - private static final ProviderConfigProperty CLIENTAUTHNS_PROPERTY = new ProviderConfigProperty( - CLIENT_AUTHNS, null, null, ProviderConfigProperty.MULTIVALUED_STRING_TYPE, null); - private static final ProviderConfigProperty CLIENTAUTHNS_AUGMENT = new ProviderConfigProperty( - CLIENT_AUTHNS_AUGMENT, null, null, ProviderConfigProperty.STRING_TYPE, JWTClientAuthenticator.PROVIDER_ID); + private List configProperties = new ArrayList<>(); @Override public ClientPolicyExecutorProvider create(KeycloakSession session) { @@ -56,6 +54,25 @@ public class SecureClientAuthEnforceExecutorFactory implements ClientPolicyExecu @Override public void postInit(KeycloakSessionFactory factory) { + ProviderConfigProperty isAugmentProperty = new ProviderConfigProperty( + IS_AUGMENT, "Augment Configuration", "If On, then the during client creation or update, the configuration of the client will be augmented to enforce the authentication method to new clients", + ProviderConfigProperty.BOOLEAN_TYPE, false); + + List clientAuthProviders = factory.getProviderFactoriesStream(ClientAuthenticator.class) + .map(ProviderFactory::getId) + .collect(Collectors.toList()); + + ProviderConfigProperty clientAuthnsProperty = new ProviderConfigProperty( + CLIENT_AUTHNS, "Client Authentication Methods", "List of available client authentication methods, which are allowed for clients to use. Other client authentication methods will not be allowed.", + ProviderConfigProperty.MULTIVALUED_LIST_TYPE, null); + clientAuthnsProperty.setOptions(clientAuthProviders); + + ProviderConfigProperty clientAuthnsAugment = new ProviderConfigProperty( + CLIENT_AUTHNS_AUGMENT, "Augment Client Authentication Method", "If 'Augment Configuration' is ON, then this client authentication method will be set as the authentication method to new clients", + ProviderConfigProperty.LIST_TYPE, JWTClientAuthenticator.PROVIDER_ID); + clientAuthnsAugment.setOptions(clientAuthProviders); + + configProperties = Arrays.asList(isAugmentProperty, clientAuthnsProperty, clientAuthnsAugment); } @Override @@ -74,7 +91,7 @@ public class SecureClientAuthEnforceExecutorFactory implements ClientPolicyExecu @Override public List getConfigProperties() { - return new ArrayList<>(Arrays.asList(IS_AUGMENT_PROPERTY, CLIENTAUTHNS_PROPERTY, CLIENTAUTHNS_AUGMENT)); + return configProperties; } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientRegisteringUriEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientRegisteringUriEnforceExecutor.java index 678a0deff2..cb72bf7faf 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientRegisteringUriEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientRegisteringUriEnforceExecutor.java @@ -28,6 +28,7 @@ import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext; @@ -40,7 +41,7 @@ import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext; /** * @author Takashi Norimatsu */ -public class SecureClientRegisteringUriEnforceExecutor implements ClientPolicyExecutorProvider { +public class SecureClientRegisteringUriEnforceExecutor implements ClientPolicyExecutorProvider { private static final Logger logger = Logger.getLogger(SecureClientRegisteringUriEnforceExecutor.class); diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutor.java index 082b8b3fe2..01da41078a 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutor.java @@ -31,13 +31,12 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.endpoints.request.AuthzEndpointRequestParser; import org.keycloak.protocol.oidc.utils.OIDCResponseType; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.services.Urls; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; -import org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutor.Configuration; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; @@ -80,8 +79,7 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider return Configuration.class; } - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Configuration extends ClientPolicyExecutorConfiguration { + public static class Configuration extends ClientPolicyExecutorConfigurationRepresentation { @JsonProperty("available-period") protected Integer availablePeriod; @JsonProperty("verify-nbf") diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutorFactory.java index 50c4b351ac..2e4d697894 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutorFactory.java @@ -37,7 +37,14 @@ public class SecureRequestObjectExecutorFactory implements ClientPolicyExecutorP public static final String VERIFY_NBF = "verify-nbf"; private static final ProviderConfigProperty VERIFY_NBF_PROPERTY = new ProviderConfigProperty( - VERIFY_NBF, null, null, ProviderConfigProperty.BOOLEAN_TYPE, true); + VERIFY_NBF, "Verify Not-Before", "If ON, then it will be verified if 'request' object used in OIDC authorization request contains not-before " + + "claim and this claim will be validated", ProviderConfigProperty.BOOLEAN_TYPE, true); + + public static final String AVAILABLE_PERIOD = "available-period"; + + private static final ProviderConfigProperty AVAILABLE_PERIOD_PROPERTY = new ProviderConfigProperty( + AVAILABLE_PERIOD, "Available Period", "The maximum period in seconds for which the 'request' object used in OIDC authorization request is considered valid. " + + "It is used if 'Verify Not-Before' is ON.", ProviderConfigProperty.STRING_TYPE, "3600"); @Override public ClientPolicyExecutorProvider create(KeycloakSession session) { @@ -68,7 +75,7 @@ public class SecureRequestObjectExecutorFactory implements ClientPolicyExecutorP @Override public List getConfigProperties() { - return new ArrayList<>(Arrays.asList(VERIFY_NBF_PROPERTY)); + return new ArrayList<>(Arrays.asList(VERIFY_NBF_PROPERTY, AVAILABLE_PERIOD_PROPERTY)); } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutor.java index cfda6841c1..e161cfcfc5 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutor.java @@ -22,6 +22,7 @@ import org.keycloak.OAuthErrorException; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.utils.OIDCResponseType; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; @@ -29,7 +30,7 @@ import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; /** * @author Takashi Norimatsu */ -public class SecureResponseTypeExecutor implements ClientPolicyExecutorProvider { +public class SecureResponseTypeExecutor implements ClientPolicyExecutorProvider { private static final Logger logger = Logger.getLogger(SecureResponseTypeExecutor.class); diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutor.java index d5b97d9cce..da9a0b6cf9 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutor.java @@ -22,6 +22,7 @@ import org.keycloak.OAuthErrorException; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.utils.OIDCResponseType; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; @@ -30,7 +31,7 @@ import org.keycloak.util.TokenUtil; /** * @author Takashi Norimatsu */ -public class SecureSessionEnforceExecutor implements ClientPolicyExecutorProvider { +public class SecureSessionEnforceExecutor implements ClientPolicyExecutorProvider { private static final Logger logger = Logger.getLogger(SecureSessionEnforceExecutor.class); diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutor.java index 6b809461a3..5071d70b40 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutor.java @@ -19,9 +19,11 @@ package org.keycloak.services.clientpolicy.executor; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import org.jboss.logging.Logger; @@ -29,6 +31,7 @@ import org.keycloak.OAuthErrorException; import org.keycloak.crypto.Algorithm; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.OIDCConfigAttributes; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; @@ -37,7 +40,6 @@ import org.keycloak.services.clientpolicy.context.AdminClientUpdateContext; import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext; import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -61,6 +63,15 @@ public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecut private static final String DEFAULT_ALGORITHM_VALUE = Algorithm.PS256; + static final Set ALLOWED_ALGORITHMS = new LinkedHashSet<>(Arrays.asList( + Algorithm.PS256, + Algorithm.PS384, + Algorithm.PS512, + Algorithm.ES256, + Algorithm.ES384, + Algorithm.ES512 + )); + public SecureSigningAlgorithmEnforceExecutor(KeycloakSession session) { this.session = session; } @@ -81,8 +92,7 @@ public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecut return Configuration.class; } - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Configuration extends ClientPolicyExecutorConfiguration { + public static class Configuration extends ClientPolicyExecutorConfigurationRepresentation { @JsonProperty("default-algorithm") protected String defaultAlgorithm; @@ -165,16 +175,7 @@ public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecut } private static boolean isSecureAlgorithm(String sigAlg) { - switch (sigAlg) { - case Algorithm.PS256: - case Algorithm.PS384: - case Algorithm.PS512: - case Algorithm.ES256: - case Algorithm.ES384: - case Algorithm.ES512: - return true; - } - return false; + return ALLOWED_ALGORITHMS.contains(sigAlg); } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutorFactory.java index f8b6e15a2a..df2a96a460 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutorFactory.java @@ -19,6 +19,7 @@ package org.keycloak.services.clientpolicy.executor; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import org.keycloak.Config.Scope; @@ -37,7 +38,8 @@ public class SecureSigningAlgorithmEnforceExecutorFactory implements ClientPolic public static final String DEFAULT_ALGORITHM = "default-algorithm"; private static final ProviderConfigProperty DEFAULT_ALGORITHM_PROPERTY = new ProviderConfigProperty( - DEFAULT_ALGORITHM, null, null, ProviderConfigProperty.STRING_TYPE, Algorithm.PS256); + DEFAULT_ALGORITHM, "Default Algorithm", "Default signature algorithm, which will be set to clients during client registration/update in case that client does not specify any algorithm", + ProviderConfigProperty.LIST_TYPE, Algorithm.PS256, new LinkedList<>(SecureSigningAlgorithmEnforceExecutor.ALLOWED_ALGORITHMS).toArray(new String[] {})); @Override public ClientPolicyExecutorProvider create(KeycloakSession session) { @@ -63,7 +65,7 @@ public class SecureSigningAlgorithmEnforceExecutorFactory implements ClientPolic @Override public String getHelpText() { - return "It refuses the client whose signature algorithms are considered not to be secure. It accepts ES256, ES384, ES512, PS256, PS384 and PS512."; + return "It refuses the client whose signature algorithms are considered not to be secure. This is applied by server for signing ID Token, UserInfo and Access Token. Also it is used by client for Token Endpoint Authentication signature algorithm (for JWT client authenticators) and OIDC Request object. It accepts ES256, ES384, ES512, PS256, PS384 and PS512."; } @Override diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutor.java index c6e5be14b1..32df091fd2 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutor.java @@ -23,16 +23,15 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; -import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.common.util.ObjectUtil; import org.keycloak.crypto.Algorithm; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInputException; import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; public class SecureSigningAlgorithmForSignedJwtEnforceExecutor implements ClientPolicyExecutorProvider { @@ -61,8 +60,7 @@ public class SecureSigningAlgorithmForSignedJwtEnforceExecutor implements Client return SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID; } - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Configuration extends ClientPolicyExecutorConfiguration { + public static class Configuration extends ClientPolicyExecutorConfigurationRepresentation { @JsonProperty("require-client-assertion") protected Boolean requireClientAssertion; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.java index 6041595ab6..6a0f02520c 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.java @@ -34,7 +34,7 @@ public class SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory implements public static final String REQUIRE_CLIENT_ASSERTION = "require-client-assertion"; private static final ProviderConfigProperty REQUIRE_CLIENT_ASSERTION_PROPERTY = new ProviderConfigProperty( - REQUIRE_CLIENT_ASSERTION, null, null, ProviderConfigProperty.BOOLEAN_TYPE, false); + REQUIRE_CLIENT_ASSERTION, "Require Client Assertion", "If this is ON, then parameter 'client_assertion' will be required and request will fail if it is not present. If false, then parameter 'client_assertion' is not required. When 'client_assertion' parameter is present, then the algorithm on the JWT from specified client assertion is always checked regardless of the value of this switch", ProviderConfigProperty.BOOLEAN_TYPE, false); @Override public ClientPolicyExecutorProvider create(KeycloakSession session) { diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index b28375244c..73b3208a53 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -124,7 +124,7 @@ public class RealmManager { createDefaultClientScopes(realm); setupAuthorizationServices(realm); setupClientRegistrations(realm); - setupClientPolicies(realm); + session.clientPolicy().setupClientPoliciesOnCreatedRealm(realm); fireRealmPostCreate(realm); @@ -599,7 +599,7 @@ public class RealmManager { MigrationModelManager.migrateImport(session, realm, rep, skipUserDependent); } - setupClientPolicies(realm, rep); + session.clientPolicy().updateRealmModelFromRepresentation(realm, rep); fireRealmPostCreate(realm); @@ -714,14 +714,6 @@ public class RealmManager { DefaultClientRegistrationPolicies.addDefaultPolicies(realm); } - private void setupClientPolicies(RealmModel realm, RealmRepresentation rep) { - session.clientPolicy().setupClientPoliciesOnImportedRealm(realm, rep); - } - - private void setupClientPolicies(RealmModel realm) { - session.clientPolicy().setupClientPoliciesOnCreatedRealm(realm); - } - private void fireRealmPostCreate(RealmModel realm) { session.getKeycloakSessionFactory().publish(new RealmModel.RealmPostCreateEvent() { @Override diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 6bb6974f2f..03fd6b7b4e 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -172,8 +172,6 @@ public class KeycloakApplication extends Application { } // TODO up here ^^ - session.clientPolicy().setupClientPoliciesOnKeycloakApp("/keycloak-default-client-profiles.json", "/keycloak-default-client-policies.json"); - ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session); exportImportManager[0] = new ExportImportManager(session); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientPoliciesResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientPoliciesResource.java index a28a514a8c..26543172da 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientPoliciesResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientPoliciesResource.java @@ -17,6 +17,7 @@ package org.keycloak.services.resources.admin; +import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.PUT; @@ -32,6 +33,7 @@ import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpResponse; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.ClientPoliciesRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; @@ -58,19 +60,23 @@ public class ClientPoliciesResource { @GET @NoCache @Produces(MediaType.APPLICATION_JSON) - public String getPolicies() { + public ClientPoliciesRepresentation getPolicies() { auth.realm().requireViewRealm(); - return session.clientPolicy().getClientPolicies(realm); + try { + return session.clientPolicy().getClientPolicies(realm); + } catch (ClientPolicyException e) { + throw new BadRequestException(Response.status(Status.BAD_REQUEST).entity(e.getError()).build()); + } } @PUT @Consumes(MediaType.APPLICATION_JSON) - public Response updatePolicies(final String json) { + public Response updatePolicies(final ClientPoliciesRepresentation clientPolicies) { auth.realm().requireManageRealm(); try { - session.clientPolicy().updateClientPolicies(realm, json); + session.clientPolicy().updateClientPolicies(realm, clientPolicies); } catch (ClientPolicyException e) { return Response.status(Status.BAD_REQUEST).entity(e.getError()).build(); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientProfilesResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientProfilesResource.java index 2b39ea3965..14c3e1c199 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientProfilesResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientProfilesResource.java @@ -17,10 +17,12 @@ package org.keycloak.services.resources.admin; +import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -32,6 +34,7 @@ import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpResponse; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.ClientProfilesRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; @@ -58,19 +61,23 @@ public class ClientProfilesResource { @GET @NoCache @Produces(MediaType.APPLICATION_JSON) - public String getProfiles() { + public ClientProfilesRepresentation getProfiles(@QueryParam("include-global-profiles") boolean includeGlobalProfiles) { auth.realm().requireViewRealm(); - return session.clientPolicy().getClientProfiles(realm); + try { + return session.clientPolicy().getClientProfiles(realm, includeGlobalProfiles); + } catch (ClientPolicyException e) { + throw new BadRequestException(Response.status(Status.BAD_REQUEST).entity(e.getError()).build()); + } } @PUT @Consumes(MediaType.APPLICATION_JSON) - public Response updateProfiles(final String json) { + public Response updateProfiles(final ClientProfilesRepresentation clientProfiles) { auth.realm().requireManageRealm(); try { - session.clientPolicy().updateClientProfiles(realm, json); + session.clientPolicy().updateClientProfiles(realm, clientProfiles); } catch (ClientPolicyException e) { return Response.status(Status.BAD_REQUEST).entity(e.getError()).build(); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 684c6b07ff..8ab10e197a 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -57,6 +57,7 @@ import org.keycloak.KeyPairVerifier; import org.keycloak.authentication.CredentialRegistrator; import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.common.ClientConnection; +import org.keycloak.common.Profile; import org.keycloak.common.VerificationException; import org.keycloak.common.util.PemUtils; import org.keycloak.email.EmailTemplateProvider; @@ -113,6 +114,7 @@ import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluato import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.representations.idm.LDAPCapabilityRepresentation; +import org.keycloak.utils.ProfileHelper; import org.keycloak.utils.ReservedCharValidator; import com.fasterxml.jackson.core.type.TypeReference; @@ -371,7 +373,7 @@ public class RealmAdminResource { @Produces(MediaType.APPLICATION_JSON) public RealmRepresentation getRealm() { if (auth.realm().canViewRealm()) { - return ModelToRepresentation.toRepresentation(realm, false); + return ModelToRepresentation.toRepresentation(session, realm, false); } else { auth.realm().requireViewRealmNameList(); @@ -379,7 +381,7 @@ public class RealmAdminResource { rep.setRealm(realm.getName()); if (auth.realm().canViewIdentityProviders()) { - RealmRepresentation r = ModelToRepresentation.toRepresentation(realm, false); + RealmRepresentation r = ModelToRepresentation.toRepresentation(session, realm, false); rep.setIdentityProviders(r.getIdentityProviders()); rep.setIdentityProviderMappers(r.getIdentityProviderMappers()); } @@ -1209,6 +1211,7 @@ public class RealmAdminResource { @Path("client-policies/policies") public ClientPoliciesResource getClientPoliciesResource() { + ProfileHelper.requireFeature(Profile.Feature.CLIENT_POLICIES); ClientPoliciesResource resource = new ClientPoliciesResource(realm, auth); ResteasyProviderFactory.getInstance().injectProperties(resource); return resource; @@ -1216,6 +1219,7 @@ public class RealmAdminResource { @Path("client-policies/profiles") public ClientProfilesResource getClientProfilesResource() { + ProfileHelper.requireFeature(Profile.Feature.CLIENT_POLICIES); ClientProfilesResource resource = new ClientProfilesResource(realm, auth); ResteasyProviderFactory.getInstance().injectProperties(resource); return resource; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java index 0fc6ca528b..3ad678957c 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java @@ -102,7 +102,7 @@ public class RealmsAdminResource { protected RealmRepresentation toRealmRep(RealmModel realm) { if (AdminPermissions.realms(session, auth).canView(realm)) { - return ModelToRepresentation.toRepresentation(realm, false); + return ModelToRepresentation.toRepresentation(session, realm, false); } else if (AdminPermissions.realms(session, auth).isAdmin(realm)) { RealmRepresentation rep = new RealmRepresentation(); rep.setRealm(realm.getName()); diff --git a/services/src/main/resources/META-INF/services/org.keycloak.services.clientpolicy.ClientPolicyManagerFactory b/services/src/main/resources/META-INF/services/org.keycloak.services.clientpolicy.ClientPolicyManagerFactory new file mode 100644 index 0000000000..2a059969aa --- /dev/null +++ b/services/src/main/resources/META-INF/services/org.keycloak.services.clientpolicy.ClientPolicyManagerFactory @@ -0,0 +1,18 @@ +# +# 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. +# + +org.keycloak.services.clientpolicy.DefaultClientPolicyManagerFactory \ No newline at end of file diff --git a/services/src/main/resources/keycloak-default-client-policies.json b/services/src/main/resources/keycloak-default-client-policies.json deleted file mode 100644 index b10909c2e6..0000000000 --- a/services/src/main/resources/keycloak-default-client-policies.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "policies": [ - { - "name": "builtin-default-policy", - "description": "The built-in default policy applied to all clients.", - "builtin": true, - "enable": false, - "conditions": [ - { - "anyclient-condition": {} - } - ], - "profiles": [ - "builtin-default-profile" - ] - } - ] -} \ No newline at end of file diff --git a/services/src/main/resources/keycloak-default-client-profiles.json b/services/src/main/resources/keycloak-default-client-profiles.json index c193d73fce..154fd2de26 100644 --- a/services/src/main/resources/keycloak-default-client-profiles.json +++ b/services/src/main/resources/keycloak-default-client-profiles.json @@ -1,12 +1,12 @@ { "profiles": [ { - "name": "builtin-default-profile", - "description": "The built-in default profile for enforcing basic security level to clients.", - "builtin": true, + "name": "global-default-profile", + "description": "The global default profile for enforcing basic security level to clients.", "executors": [ { - "secure-session-enforce-executor": {} + "executor": "secure-session-enforce-executor", + "configuration": {} } ] } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/runonserver/RunHelpers.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/runonserver/RunHelpers.java index 4187a9768c..513e6ff439 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/runonserver/RunHelpers.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/runonserver/RunHelpers.java @@ -21,7 +21,7 @@ public class RunHelpers { @Override public FetchOnServer getRunOnServer() { - return (FetchOnServer) session -> ModelToRepresentation.toRepresentation(session.getContext().getRealm(), true); + return (FetchOnServer) session -> ModelToRepresentation.toRepresentation(session, session.getContext().getRealm(), true); } @Override diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionCondition.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionCondition.java index dc1e58f32b..fcc68d538a 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionCondition.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionCondition.java @@ -22,23 +22,16 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyVote; -import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionConfiguration; -import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; +import org.keycloak.services.clientpolicy.condition.AbstractClientPolicyConditionProvider; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; /** * @author Takashi Norimatsu */ -public class TestRaiseExeptionCondition implements ClientPolicyConditionProvider { - - // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. - private Configuration configuration = new Configuration(); +public class TestRaiseExeptionCondition extends AbstractClientPolicyConditionProvider { public TestRaiseExeptionCondition(KeycloakSession session) { - } - - @Override - public void setupConfiguration(Configuration config) { - this.configuration = config; + super(session); } @Override @@ -46,7 +39,7 @@ public class TestRaiseExeptionCondition implements ClientPolicyConditionProvider return Configuration.class; } - public static class Configuration extends ClientPolicyConditionConfiguration { + public static class Configuration extends ClientPolicyConditionConfigurationRepresentation { } @Override diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionConditionFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionConditionFactory.java index 9cd081f94b..afaab01f35 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionConditionFactory.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionConditionFactory.java @@ -66,4 +66,8 @@ public class TestRaiseExeptionConditionFactory implements ClientPolicyConditionP return Collections.emptyList(); } + @Override + public boolean isSupported() { + return true; + } } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java index 020cf8f287..f67d5ebb38 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java @@ -22,10 +22,10 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorConfiguration; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; -public class TestRaiseExeptionExecutor implements ClientPolicyExecutorProvider { +public class TestRaiseExeptionExecutor implements ClientPolicyExecutorProvider { private static final Logger logger = Logger.getLogger(TestRaiseExeptionExecutor.class); diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutorFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutorFactory.java index 45a3490a22..dffd57248e 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutorFactory.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutorFactory.java @@ -63,4 +63,8 @@ public class TestRaiseExeptionExecutorFactory implements ClientPolicyExecutorPro return Collections.emptyList(); } + @Override + public boolean isSupported() { + return true; + } } \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java index d3bbff23db..b7aaa60b83 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.client; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -38,6 +39,7 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; @@ -60,9 +62,11 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; +import org.hamcrest.Matchers; import org.jboss.logging.Logger; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Rule; import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; @@ -74,6 +78,7 @@ import org.keycloak.client.registration.ClientRegistration; import org.keycloak.client.registration.ClientRegistrationException; import org.keycloak.common.util.Base64; import org.keycloak.common.util.Base64Url; +import org.keycloak.common.util.BouncyIntegration; import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.Time; @@ -92,6 +97,10 @@ import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; import org.keycloak.representations.idm.ClientInitialAccessPresentation; import org.keycloak.representations.idm.ClientPoliciesRepresentation; +import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation; +import org.keycloak.representations.idm.ClientPolicyConditionRepresentation; +import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation; +import org.keycloak.representations.idm.ClientPolicyExecutorRepresentation; import org.keycloak.representations.idm.ClientPolicyRepresentation; import org.keycloak.representations.idm.ClientProfileRepresentation; import org.keycloak.representations.idm.ClientProfilesRepresentation; @@ -100,7 +109,6 @@ import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.representations.oidc.TokenMetadataRepresentation; import org.keycloak.services.Urls; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.condition.AnyClientCondition; import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory; import org.keycloak.services.clientpolicy.condition.ClientAccessTypeCondition; import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory; @@ -132,6 +140,7 @@ import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmEnforce import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutor; import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory; import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls; @@ -170,6 +179,11 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { private static final ObjectMapper objectMapper = new ObjectMapper(); + @BeforeClass + public static void beforeClientPoliciesTest() { + BouncyIntegration.init(); + } + @Rule public AssertEvents events = new AssertEvents(this); @@ -189,9 +203,9 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { revertToBuiltinPolicies(); } - protected void loadValidProfilesAndPolicies() throws Exception { + protected void setupValidProfilesAndPolicies() throws Exception { // load profiles - ClientProfileRepresentation loadedProfileRep = (new ClientProfileBuilder()).createProfile("ordinal-test-profile", "The profile that can be loaded.", Boolean.FALSE, null) + ClientProfileRepresentation loadedProfileRep = (new ClientProfileBuilder()).createProfile("ordinal-test-profile", "The profile that can be loaded.") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig( Boolean.TRUE, @@ -199,7 +213,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { JWTClientAuthenticator.PROVIDER_ID)) .toRepresentation(); - ClientProfileRepresentation loadedProfileRepWithoutBuiltinField = (new ClientProfileBuilder()).createProfile("lack-of-builtin-field-test-profile", "Without builtin field that is treated as builtin=false.", null, null) + ClientProfileRepresentation loadedProfileRepWithoutBuiltinField = (new ClientProfileBuilder()).createProfile("lack-of-builtin-field-test-profile", "Without builtin field that is treated as builtin=false.") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig( Boolean.TRUE, @@ -226,34 +240,34 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { (new ClientPolicyBuilder()).createPolicy( "new-policy", "duplicated profiles are ignored.", - Boolean.FALSE, - Boolean.TRUE, - null, - Arrays.asList("builtin-default-profile", "ordinal-test-profile", "lack-of-builtin-field-test-profile", "ordinal-test-profile")) + Boolean.TRUE) .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, createClientAccessTypeConditionConfig(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_PUBLIC, ClientAccessTypeConditionFactory.TYPE_BEARERONLY))) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) .addCondition(ClientScopesConditionFactory.PROVIDER_ID, createClientScopesConditionConfig(ClientScopesConditionFactory.OPTIONAL, Arrays.asList(SAMPLE_CLIENT_ROLE))) + .addProfile("global-default-profile") + .addProfile("ordinal-test-profile") + .addProfile("lack-of-builtin-field-test-profile") + .addProfile("ordinal-test-profile") + .toRepresentation(); ClientPolicyRepresentation loadedPolicyRepWithoutBuiltinField = (new ClientPolicyBuilder()).createPolicy( "lack-of-builtin-field-test-policy", "Without builtin field that is treated as builtin=false.", - null, - null, - null, - Arrays.asList("lack-of-builtin-field-test-profile")) + null) .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))) .addCondition(ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, createClientUpdateSourceGroupsConditionConfig(Arrays.asList("topGroup"))) - .addCondition(ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, + .addCondition(ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, createClientUpdateSourceHostsConditionConfig(Arrays.asList("localhost", "127.0.0.1"))) .addCondition(ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, createClientUpdateSourceRolesConditionConfig(Arrays.asList(AdminRoles.CREATE_CLIENT))) + .addProfile("lack-of-builtin-field-test-profile") .toRepresentation(); json = (new ClientPoliciesBuilder()) @@ -268,21 +282,21 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { protected void assertExpectedLoadedProfiles(Consumer modifiedAssertion) { // retrieve loaded builtin profiles - ClientProfilesRepresentation actualProfilesRep = getProfiles(); + ClientProfilesRepresentation actualProfilesRep = getProfilesWithGlobals(); // same profiles - assertExpectedProfiles(actualProfilesRep, Arrays.asList("builtin-default-profile", "ordinal-test-profile", "lack-of-builtin-field-test-profile")); + assertExpectedProfiles(actualProfilesRep, Arrays.asList("global-default-profile"), Arrays.asList("ordinal-test-profile", "lack-of-builtin-field-test-profile")); - // each profile - builtin-default-profile - ClientProfileRepresentation actualProfileRep = getProfileRepresentation(actualProfilesRep, "builtin-default-profile"); - assertExpectedProfile(actualProfileRep, "builtin-default-profile", "The built-in default profile for enforcing basic security level to clients.", true); + // each profile - global-default-profile + ClientProfileRepresentation actualProfileRep = getProfileRepresentation(actualProfilesRep, "global-default-profile", true); + assertExpectedProfile(actualProfileRep, "global-default-profile", "The global default profile for enforcing basic security level to clients."); // each executor assertExpectedExecutors(Arrays.asList(SecureSessionEnforceExecutorFactory.PROVIDER_ID), actualProfileRep); assertExpectedSecureSessionEnforceExecutor(actualProfileRep); // each profile - ordinal-test-profile - updated - actualProfileRep = getProfileRepresentation(actualProfilesRep, "ordinal-test-profile"); + actualProfileRep = getProfileRepresentation(actualProfilesRep, "ordinal-test-profile", false); modifiedAssertion.accept(actualProfilesRep); // each executor @@ -290,8 +304,8 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { assertExpectedSecureClientAuthEnforceExecutor(Arrays.asList(JWTClientAuthenticator.PROVIDER_ID), true, JWTClientAuthenticator.PROVIDER_ID, actualProfileRep); // each profile - lack-of-builtin-field-test-profile - actualProfileRep = getProfileRepresentation(actualProfilesRep, "lack-of-builtin-field-test-profile"); - assertExpectedProfile(actualProfileRep, "lack-of-builtin-field-test-profile", "Without builtin field that is treated as builtin=false.", false); + actualProfileRep = getProfileRepresentation(actualProfilesRep, "lack-of-builtin-field-test-profile", false); + assertExpectedProfile(actualProfileRep, "lack-of-builtin-field-test-profile", "Without builtin field that is treated as builtin=false."); // each executor assertExpectedExecutors(Arrays.asList( @@ -319,7 +333,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { ClientPoliciesRepresentation actualPoliciesRep = getPolicies(); // same policies - assertExpectedPolicies(Arrays.asList("builtin-default-policy", "new-policy", "lack-of-builtin-field-test-policy"), actualPoliciesRep); + assertExpectedPolicies(Arrays.asList("new-policy", "lack-of-builtin-field-test-policy"), actualPoliciesRep); // each policy - new-policy - updated ClientPolicyRepresentation actualPolicyRep = getPolicyRepresentation(actualPoliciesRep, "new-policy"); @@ -333,13 +347,13 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { // each policy - lack-of-builtin-field-test-policy actualPolicyRep = getPolicyRepresentation(actualPoliciesRep, "lack-of-builtin-field-test-policy"); - assertExpectedPolicy("lack-of-builtin-field-test-policy", "Without builtin field that is treated as builtin=false.", false, false, Arrays.asList("lack-of-builtin-field-test-profile"), actualPolicyRep); + assertExpectedPolicy("lack-of-builtin-field-test-policy", "Without builtin field that is treated as builtin=false.", false, Arrays.asList("lack-of-builtin-field-test-profile"), actualPolicyRep); // each condition assertExpectedConditions(Arrays.asList(ClientUpdateContextConditionFactory.PROVIDER_ID, ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, ClientUpdateSourceRolesConditionFactory.PROVIDER_ID), actualPolicyRep); assertExpectedClientUpdateContextCondition(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER), actualPolicyRep); assertExpectedClientUpdateSourceGroupsCondition(Arrays.asList("topGroup"), actualPolicyRep); - assertExpectedClientUpdateSourceHostsCondition(Arrays.asList("localhost", "127.0.0.1"), Arrays.asList(Boolean.TRUE, Boolean.TRUE), actualPolicyRep); + assertExpectedClientUpdateSourceHostsCondition(Arrays.asList("localhost", "127.0.0.1"), actualPolicyRep); assertExpectedClientUpdateSourceRolesCondition(Arrays.asList(AdminRoles.CREATE_CLIENT), actualPolicyRep); } @@ -783,51 +797,23 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { profileRep = new ClientProfileRepresentation(); } - public ClientProfileBuilder createProfile(String name, String description, Boolean isBuiltin, List executors) { + public ClientProfileBuilder createProfile(String name, String description) { if (name != null) { profileRep.setName(name); } if (description != null) { profileRep.setDescription(description); } - if (isBuiltin != null) { - profileRep.setBuiltin(isBuiltin); - } else { - profileRep.setBuiltin(Boolean.FALSE); - } - if (executors != null) { - profileRep.setExecutors(executors); - } else { - profileRep.setExecutors(new ArrayList<>()); - } + profileRep.setExecutors(new ArrayList<>()); + return this; } - public ClientProfileBuilder addExecutor(String providerId, Object config) { - String configString = null; + public ClientProfileBuilder addExecutor(String providerId, ClientPolicyExecutorConfigurationRepresentation config) { if (config == null) { - configString = "{}"; - } else { - try { - configString = objectMapper.writeValueAsString(config); - } catch (JsonProcessingException e) { - fail(); - } + config = new ClientPolicyExecutorConfigurationRepresentation(); } - String executorJson = (new StringBuilder()) - .append("{\"") - .append(providerId) - .append("\":") - .append(configString) - .append("}") - .toString(); - JsonNode node = null; - try { - node = objectMapper.readTree(executorJson); - } catch (JsonProcessingException e) { - fail(); - } - profileRep.getExecutors().add(node); + profileRep.getExecutors().add(new ClientPolicyExecutorRepresentation(providerId, config)); return this; } @@ -849,19 +835,19 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { // Client Profiles - Executor CRUD Operations - protected Object createHolderOfKeyEnforceExecutorConfig(Boolean isAugment) { + protected HolderOfKeyEnforceExecutor.Configuration createHolderOfKeyEnforceExecutorConfig(Boolean isAugment) { HolderOfKeyEnforceExecutor.Configuration config = new HolderOfKeyEnforceExecutor.Configuration(); config.setAugment(isAugment); return config; } - protected Object createPKCEEnforceExecutorConfig(Boolean isAugment) { + protected PKCEEnforceExecutor.Configuration createPKCEEnforceExecutorConfig(Boolean isAugment) { PKCEEnforceExecutor.Configuration config = new PKCEEnforceExecutor.Configuration(); config.setAugment(isAugment); return config; } - protected Object createSecureClientAuthEnforceExecutorConfig(Boolean isAugment, List clientAuthns, String clientAuthnsAugment) { + protected SecureClientAuthEnforceExecutor.Configuration createSecureClientAuthEnforceExecutorConfig(Boolean isAugment, List clientAuthns, String clientAuthnsAugment) { SecureClientAuthEnforceExecutor.Configuration config = new SecureClientAuthEnforceExecutor.Configuration(); config.setAugment(isAugment); config.setClientAuthns(clientAuthns); @@ -869,20 +855,20 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { return config; } - protected Object createSecureRequestObjectExecutorConfig(Integer availablePeriod, Boolean verifyNbf) { + protected SecureRequestObjectExecutor.Configuration createSecureRequestObjectExecutorConfig(Integer availablePeriod, Boolean verifyNbf) { SecureRequestObjectExecutor.Configuration config = new SecureRequestObjectExecutor.Configuration(); if (availablePeriod != null) config.setAvailablePeriod(availablePeriod); if (verifyNbf != null) config.setVerifyNbf(verifyNbf); return config; } - protected Object createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig(Boolean requireClientAssertion) { + protected SecureSigningAlgorithmForSignedJwtEnforceExecutor.Configuration createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig(Boolean requireClientAssertion) { SecureSigningAlgorithmForSignedJwtEnforceExecutor.Configuration config = new SecureSigningAlgorithmForSignedJwtEnforceExecutor.Configuration(); config.setRequireClientAssertion(requireClientAssertion); return config; } - protected Object createSecureSigningAlgorithmEnforceExecutorConfig(String defaultAlgorithm) { + protected SecureSigningAlgorithmEnforceExecutor.Configuration createSecureSigningAlgorithmEnforceExecutorConfig(String defaultAlgorithm) { SecureSigningAlgorithmEnforceExecutor.Configuration config = new SecureSigningAlgorithmEnforceExecutor.Configuration(); config.setDefaultAlgorithm(defaultAlgorithm); return config; @@ -927,59 +913,25 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { policyRep = new ClientPolicyRepresentation(); } - public ClientPolicyBuilder createPolicy(String name, String description, Boolean isBuiltin, Boolean isEnabled, List conditions, List profiles) { + public ClientPolicyBuilder createPolicy(String name, String description, Boolean isEnabled) { policyRep.setName(name); if (description != null) { policyRep.setDescription(description); } - if (isBuiltin != null) { - policyRep.setBuiltin(isBuiltin); - } else { - policyRep.setBuiltin(Boolean.FALSE); - } if (isEnabled != null) { - policyRep.setEnable(isEnabled); + policyRep.setEnabled(isEnabled); } else { - policyRep.setEnable(Boolean.FALSE); - } - if (conditions != null) { - policyRep.setConditions(conditions); - } else { - policyRep.setConditions(new ArrayList<>()); - } - if (profiles != null) { - policyRep.setProfiles(profiles); - } else { - policyRep.setProfiles(new ArrayList<>()); + policyRep.setEnabled(Boolean.FALSE); } + + policyRep.setConditions(new ArrayList<>()); + policyRep.setProfiles(new ArrayList<>()); + return this; } - public ClientPolicyBuilder addCondition(String providerId, Object config) { - String configString = null; - if (config == null) { - configString = "{}"; - } else { - try { - configString = objectMapper.writeValueAsString(config); - } catch (JsonProcessingException e) { - fail(); - } - } - String conditionJson = (new StringBuilder()) - .append("{\"") - .append(providerId) - .append("\":") - .append(configString) - .append("}") - .toString(); - JsonNode node = null; - try { - node = objectMapper.readTree(conditionJson); - } catch (JsonProcessingException e) { - fail(); - } - policyRep.getConditions().add(node); + public ClientPolicyBuilder addCondition(String providerId, ClientPolicyConditionConfigurationRepresentation config) { + policyRep.getConditions().add(new ClientPolicyConditionRepresentation(providerId, config)); return this; } @@ -1005,58 +957,58 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { // Client Policies - Condition CRUD Operations - protected Object createTestRaiseExeptionConditionConfig() { + protected TestRaiseExeptionCondition.Configuration createTestRaiseExeptionConditionConfig() { return new TestRaiseExeptionCondition.Configuration(); } - protected Object createAnyClientConditionConfig() { - return new AnyClientCondition.Configuration(); + protected ClientPolicyConditionConfigurationRepresentation createAnyClientConditionConfig() { + return new ClientPolicyConditionConfigurationRepresentation(); } - protected Object createAnyClientConditionConfig(Boolean isNegativeLogic) { - AnyClientCondition.Configuration config = new AnyClientCondition.Configuration(); + protected ClientPolicyConditionConfigurationRepresentation createAnyClientConditionConfig(Boolean isNegativeLogic) { + ClientPolicyConditionConfigurationRepresentation config = new ClientPolicyConditionConfigurationRepresentation(); config.setNegativeLogic(isNegativeLogic); return config; } - protected Object createClientAccessTypeConditionConfig(List types) { + protected ClientAccessTypeCondition.Configuration createClientAccessTypeConditionConfig(List types) { ClientAccessTypeCondition.Configuration config = new ClientAccessTypeCondition.Configuration(); config.setType(types); return config; } - protected Object createClientRolesConditionConfig(List roles) { + protected ClientRolesCondition.Configuration createClientRolesConditionConfig(List roles) { ClientRolesCondition.Configuration config = new ClientRolesCondition.Configuration(); config.setRoles(roles); return config; } - protected Object createClientScopesConditionConfig(String type, List scopes) { + protected ClientScopesCondition.Configuration createClientScopesConditionConfig(String type, List scopes) { ClientScopesCondition.Configuration config = new ClientScopesCondition.Configuration(); config.setType(type); config.setScope(scopes); return config; } - protected Object createClientUpdateContextConditionConfig(List updateClientSource) { + protected ClientUpdateContextCondition.Configuration createClientUpdateContextConditionConfig(List updateClientSource) { ClientUpdateContextCondition.Configuration config = new ClientUpdateContextCondition.Configuration(); config.setUpdateClientSource(updateClientSource); return config; } - protected Object createClientUpdateSourceGroupsConditionConfig(List groups) { + protected ClientUpdateSourceGroupsCondition.Configuration createClientUpdateSourceGroupsConditionConfig(List groups) { ClientUpdateSourceGroupsCondition.Configuration config = new ClientUpdateSourceGroupsCondition.Configuration(); config.setGroups(groups); return config; } - protected Object createClientUpdateSourceHostsConditionConfig(List trustedHosts) { + protected ClientUpdateSourceHostsCondition.Configuration createClientUpdateSourceHostsConditionConfig(List trustedHosts) { ClientUpdateSourceHostsCondition.Configuration config = new ClientUpdateSourceHostsCondition.Configuration(); config.setTrustedHosts(trustedHosts); return config; } - protected Object createClientUpdateSourceRolesConditionConfig(List roles) { + protected ClientUpdateSourceRolesCondition.Configuration createClientUpdateSourceRolesConditionConfig(List roles) { ClientUpdateSourceRolesCondition.Configuration config = new ClientUpdateSourceRolesCondition.Configuration(); config.setRoles(roles); return config; @@ -1074,24 +1026,15 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { return json; } - protected ClientProfilesRepresentation convertToProfiles(String json) { - ClientProfilesRepresentation reps = null; - try { - reps = JsonSerialization.readValue(json, ClientProfilesRepresentation.class); - } catch (IOException e) { - fail(); - } - return reps; - } - - protected String getProfilesJson() { - return adminClient.realm(REALM_NAME).clientPoliciesProfilesResource().getProfiles(); - } - + // TODO: Possibly change this to accept ClientProfilesRepresentation instead of String to have more type-safety. protected void updateProfiles(String json) throws ClientPolicyException { - Response resp = adminClient.realm(REALM_NAME).clientPoliciesProfilesResource().updateProfiles(json); - if (resp.getStatus() != 204) { - throw new ClientPolicyException("update profiles failed", resp.getStatusInfo().toString()); + try { + ClientProfilesRepresentation clientProfiles = JsonSerialization.readValue(json, ClientProfilesRepresentation.class); + adminClient.realm(REALM_NAME).clientPoliciesProfilesResource().updateProfiles(clientProfiles); + } catch (BadRequestException e) { + throw new ClientPolicyException("update profiles failed", e.getResponse().getStatusInfo().toString()); + } catch (Exception e) { + throw new ClientPolicyException("update profiles failed", e.getMessage()); } } @@ -1103,16 +1046,12 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { updateProfiles("{}"); } - protected ClientProfilesRepresentation getProfiles() { - return convertToProfiles(getProfilesJson()); + protected ClientProfilesRepresentation getProfilesWithGlobals() { + return adminClient.realm(REALM_NAME).clientPoliciesProfilesResource().getProfiles(true); } - protected ClientProfilesRepresentation getProfilesWithoutBuiltin() { - ClientProfilesRepresentation reps = new ClientProfilesRepresentation(); - reps.setProfiles(new ArrayList<>()); - ClientProfilesRepresentation repsWithBuiltin = getProfiles(); - repsWithBuiltin.getProfiles().stream().filter(i->!i.isBuiltin()).forEach(j->reps.getProfiles().add(j)); - return reps; + protected ClientProfilesRepresentation getProfilesWithoutGlobals() { + return adminClient.realm(REALM_NAME).clientPoliciesProfilesResource().getProfiles(false); } protected String convertToProfileJson(ClientProfileRepresentation rep) { @@ -1138,7 +1077,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { protected ClientProfileRepresentation getProfile(String name) { if (name == null) return null; - ClientProfilesRepresentation reps = getProfiles(); + ClientProfilesRepresentation reps = getProfilesWithGlobals(); if (reps == null || reps.getProfiles() == null) return null; if (reps.getProfiles().stream().anyMatch(i->name.equals(i.getName()))) { @@ -1153,7 +1092,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { } protected void addProfile(ClientProfileRepresentation profileRep) throws ClientPolicyException { - ClientProfilesRepresentation reps = getProfilesWithoutBuiltin(); + ClientProfilesRepresentation reps = getProfilesWithoutGlobals(); if (reps == null || reps.getProfiles() == null) return; reps.getProfiles().add(profileRep); updateProfiles(convertToProfilesJson(reps)); @@ -1164,7 +1103,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { if (profileRep == null || profileRep.getName() == null) return; String profileName = profileRep.getName(); - ClientProfilesRepresentation reps = getProfilesWithoutBuiltin(); + ClientProfilesRepresentation reps = getProfilesWithoutGlobals(); if (reps.getProfiles().stream().anyMatch(i->profileName.equals(i.getName()))) { ClientProfileRepresentation rep = reps.getProfiles().stream().filter(i->profileName.equals(i.getName())).collect(Collectors.toList()).get(0); @@ -1179,7 +1118,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { protected void deleteProfile(String profileName) throws ClientPolicyException { if (profileName == null) return; - ClientProfilesRepresentation reps = getProfilesWithoutBuiltin(); + ClientProfilesRepresentation reps = getProfilesWithoutGlobals(); if (reps.getProfiles().stream().anyMatch(i->profileName.equals(i.getName()))) { ClientProfileRepresentation rep = reps.getProfiles().stream().filter(i->profileName.equals(i.getName())).collect(Collectors.toList()).get(0); @@ -1202,45 +1141,24 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { return json; } - protected ClientPoliciesRepresentation convertToPolicies(String json) { - ClientPoliciesRepresentation reps = null; - try { - reps = JsonSerialization.readValue(json, ClientPoliciesRepresentation.class); - } catch (IOException e) { - fail(); - } - return reps; - } - - protected String getPoliciesJson() { - return adminClient.realm(REALM_NAME).clientPoliciesPoliciesResource().getPolicies(); - } - + // TODO: Possibly change this to accept ClientPoliciesRepresentation instead of String to have more type-safety. protected void updatePolicies(String json) throws ClientPolicyException { - Response resp = adminClient.realm(REALM_NAME).clientPoliciesPoliciesResource().updatePolicies(json); - if (resp.getStatus() != 204) { - throw new ClientPolicyException("update profiles failed", resp.getStatusInfo().toString()); + try { + ClientPoliciesRepresentation clientPolicies = json==null ? null : JsonSerialization.readValue(json, ClientPoliciesRepresentation.class); + adminClient.realm(REALM_NAME).clientPoliciesPoliciesResource().updatePolicies(clientPolicies); + } catch (BadRequestException e) { + throw new ClientPolicyException("update policies failed", e.getResponse().getStatusInfo().toString()); + } catch (IOException e) { + throw new ClientPolicyException("update policies failed", e.getMessage()); } } - protected void updatePolicies(ClientPoliciesRepresentation reps) throws ClientPolicyException { - updatePolicies(convertToPoliciesJson(reps)); - } - protected void revertToBuiltinPolicies() throws ClientPolicyException { updatePolicies("{}"); } protected ClientPoliciesRepresentation getPolicies() { - return convertToPolicies(getPoliciesJson()); - } - - protected ClientPoliciesRepresentation getPoliciesWithoutBuiltin() { - ClientPoliciesRepresentation reps = new ClientPoliciesRepresentation(); - reps.setPolicies(new ArrayList<>()); - ClientPoliciesRepresentation repsWithBuiltin = getPolicies(); - repsWithBuiltin.getPolicies().stream().filter(i->!i.isBuiltin()).forEach(j->reps.getPolicies().add(j)); - return reps; + return adminClient.realm(REALM_NAME).clientPoliciesPoliciesResource().getPolicies(); } protected String convertToPolicyJson(ClientPolicyRepresentation rep) { @@ -1281,7 +1199,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { } protected void addPolicy(ClientPolicyRepresentation policyRep) throws ClientPolicyException { - ClientPoliciesRepresentation reps = getPoliciesWithoutBuiltin(); + ClientPoliciesRepresentation reps = getPolicies(); if (reps == null || reps.getPolicies() == null) return; reps.getPolicies().add(policyRep); updatePolicies(convertToPoliciesJson(reps)); @@ -1292,7 +1210,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { if (policyRep == null || policyRep.getName() == null) return; String policyName = policyRep.getName(); - ClientPoliciesRepresentation reps = getPoliciesWithoutBuiltin(); + ClientPoliciesRepresentation reps = getPolicies(); if (reps.getPolicies().stream().anyMatch(i->policyName.equals(i.getName()))) { ClientPolicyRepresentation rep = reps.getPolicies().stream().filter(i->policyName.equals(i.getName())).collect(Collectors.toList()).get(0); @@ -1307,7 +1225,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { protected void deletePolicy(String policyName) throws ClientPolicyException { if (policyName == null) return; - ClientPoliciesRepresentation reps = getPoliciesWithoutBuiltin(); + ClientPoliciesRepresentation reps = getPolicies(); if (reps.getPolicies().stream().anyMatch(i->policyName.equals(i.getName()))) { ClientPolicyRepresentation rep = reps.getPolicies().stream().filter(i->policyName.equals(i.getName())).collect(Collectors.toList()).get(0); @@ -1328,24 +1246,28 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { // profile - protected ClientProfileRepresentation getProfileRepresentation(ClientProfilesRepresentation profilesRep, String name) { - return getCompoundRepresentation(profilesRep, name, (ClientProfilesRepresentation i)->i.getProfiles(), (ClientProfileRepresentation i)->i.getName()); + protected ClientProfileRepresentation getProfileRepresentation(ClientProfilesRepresentation profilesRep, String name, boolean global) { + Function> profilesListGetter = global ? ClientProfilesRepresentation::getGlobalProfiles : ClientProfilesRepresentation::getProfiles; + return getCompoundRepresentation(profilesRep, name, profilesListGetter, (ClientProfileRepresentation i)->i.getName()); } - protected void assertExpectedProfiles(ClientProfilesRepresentation profilesRep, List expectedProfiles) { - assertExpetedCompounds(expectedProfiles, profilesRep, (ClientProfilesRepresentation i)->i.getProfiles(), (ClientProfileRepresentation i)->i.getName()); + protected void assertExpectedProfiles(ClientProfilesRepresentation profilesRep, List expectedGlobalProfiles, List expectedRealmProfiles) { + assertExpectedCompounds(expectedGlobalProfiles, profilesRep, (ClientProfilesRepresentation i)->i.getGlobalProfiles(), (ClientProfileRepresentation i)->i.getName()); + assertExpectedCompounds(expectedRealmProfiles, profilesRep, (ClientProfilesRepresentation i)->i.getProfiles(), (ClientProfileRepresentation i)->i.getName()); } - protected void assertExpectedProfile(ClientProfileRepresentation actualProfileRep, String name, String description, boolean isBuiltin) { + protected void assertExpectedProfile(ClientProfileRepresentation actualProfileRep, String name, String description) { assertNotNull(actualProfileRep); assertEquals(description, actualProfileRep.getDescription()); - assertEquals(isBuiltin, actualProfileRep.isBuiltin()); } // executors protected void assertExpectedExecutors(List expectedExecutors, ClientProfileRepresentation profileRep) { - assertExpetedElement(expectedExecutors, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + List actualExecutorNames = profileRep.getExecutors().stream() + .map(ClientPolicyExecutorRepresentation::getExecutorProviderId) + .collect(Collectors.toList()); + assertThat(actualExecutorNames, Matchers.containsInAnyOrder(expectedExecutors.toArray())); } protected void assertExpectedHolderOfKeyEnforceExecutor(boolean isAugment, ClientProfileRepresentation profileRep) { @@ -1357,51 +1279,55 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { } protected void assertExpectedSecureClientAuthEnforceExecutor(List clientAuthns, boolean isAugment, String clientAuthnsAugment, ClientProfileRepresentation profileRep) { - JsonNode actualExecutorConfig = assertExpectedAugmenedExecutor(isAugment, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, profileRep); + assertExpectedAugmenedExecutor(isAugment, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, profileRep); + assertNotNull(profileRep); + Map actualExecutorConfig = getConfigOfExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, profileRep); + assertNotNull(actualExecutorConfig); - Set actualClientAuthns = new HashSet<>(); - if (actualExecutorConfig.findValue("client-authns") != null) actualExecutorConfig.findValue("client-authns").elements().forEachRemaining(i->actualClientAuthns.add(i.asText())); + Set actualClientAuthns = new HashSet<>((Collection) actualExecutorConfig.get("client-authns")); assertEquals(new HashSet<>(clientAuthns), actualClientAuthns); - String actualClientAuthnAugment = null; - if (actualExecutorConfig.findValue("client-authns-augment") != null) actualClientAuthnAugment = actualExecutorConfig.findValue("client-authns-augment").asText(); + String actualClientAuthnAugment = actualExecutorConfig.get("client-authns-augment").toString(); assertEquals(clientAuthnsAugment, actualClientAuthnAugment); } protected void assertExpectedSecureRedirectUriEnforceExecutor(ClientProfileRepresentation profileRep) { - assertExpectedNoConfigElement(SecureClientRegisteringUriEnforceExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + assertExpectedEmptyConfig(SecureClientRegisteringUriEnforceExecutorFactory.PROVIDER_ID, profileRep); } protected void assertExpectedSecureRequestObjectExecutor(ClientProfileRepresentation profileRep) { - assertExpectedNoConfigElement(SecureRequestObjectExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + assertExpectedEmptyConfig(SecureRequestObjectExecutorFactory.PROVIDER_ID, profileRep); } protected void assertExpectedSecureResponseTypeExecutor(ClientProfileRepresentation profileRep) { - assertExpectedNoConfigElement(SecureResponseTypeExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + assertExpectedEmptyConfig(SecureResponseTypeExecutorFactory.PROVIDER_ID, profileRep); } protected void assertExpectedSecureSessionEnforceExecutor(ClientProfileRepresentation profileRep) { - assertExpectedNoConfigElement(SecureSessionEnforceExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + assertExpectedEmptyConfig(SecureSessionEnforceExecutorFactory.PROVIDER_ID, profileRep); } protected void assertExpectedSecureSigningAlgorithmEnforceExecutor(ClientProfileRepresentation profileRep) { - assertExpectedNoConfigElement(SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + assertExpectedEmptyConfig(SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, profileRep); } protected void assertExpectedSecureSigningAlgorithmForSignedJwtEnforceExecutor(ClientProfileRepresentation profileRep) { - assertExpectedNoConfigElement(SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + assertExpectedEmptyConfig(SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, profileRep); } - protected JsonNode assertExpectedAugmenedExecutor(boolean isAugment, String providerId, ClientProfileRepresentation profileRep) { + protected void assertExpectedAugmenedExecutor(boolean isAugment, String providerId, ClientProfileRepresentation profileRep) { assertNotNull(profileRep); - JsonNode actualExecutorConfig = getConfig(profileRep.getExecutors(), providerId); + Map actualExecutorConfig = getConfigOfExecutor(providerId, profileRep); assertNotNull(actualExecutorConfig); - - boolean actualIsAugment = false; - if (actualExecutorConfig.findValue("is-augment") != null) actualIsAugment = actualExecutorConfig.findValue("is-augment").asBoolean(); + boolean actualIsAugment = actualExecutorConfig.get("is-augment") == null ? false : (Boolean) actualExecutorConfig.get("is-augment"); assertEquals(isAugment, actualIsAugment); + } - return actualExecutorConfig; + private Map 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(); } // Assertions about policies @@ -1429,94 +1355,69 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { assertEquals(new HashSet<>(expectedPolicies), actualPolicies); } - protected void assertExpectedPolicy(String name, String description, boolean isBuiltin, boolean isEnabled, List profiles, ClientPolicyRepresentation actualPolicyRep) { + protected void assertExpectedPolicy(String name, String description, boolean isEnabled, List profiles, ClientPolicyRepresentation actualPolicyRep) { assertNotNull(actualPolicyRep); assertEquals(description, actualPolicyRep.getDescription()); - assertEquals(isBuiltin, actualPolicyRep.isBuiltin()); - assertEquals(isEnabled, actualPolicyRep.isEnable()); + assertEquals(isEnabled, actualPolicyRep.isEnabled()); assertEquals(new HashSet<>(profiles), new HashSet<>(actualPolicyRep.getProfiles())); } // conditions protected void assertExpectedConditions(List expectedConditions, ClientPolicyRepresentation policyRep) { - assertExpetedElement(expectedConditions, policyRep, (ClientPolicyRepresentation i)->i.getConditions()); + List actualConditionNames = policyRep.getConditions().stream() + .map(ClientPolicyConditionRepresentation::getConditionProviderId) + .collect(Collectors.toList()); + assertThat(actualConditionNames, Matchers.containsInAnyOrder(expectedConditions.toArray())); } - protected void assertExpectedAnyClientCondition(ClientPolicyRepresentation profileRep) { - assertExpectedNoConfigElement(AnyClientConditionFactory.PROVIDER_ID, profileRep, (ClientPolicyRepresentation i)->i.getConditions()); + protected void assertExpectedAnyClientCondition(ClientPolicyRepresentation policyRep) { + ClientPolicyConditionConfigurationRepresentation config = getConfigAsExpectedType(policyRep, AnyClientConditionFactory.PROVIDER_ID, ClientPolicyConditionConfigurationRepresentation.class); + Assert.assertTrue("Expected empty configuration for provider " + AnyClientConditionFactory.PROVIDER_ID, config.getConfigAsMap().isEmpty()); } protected void assertExpectedClientAccessTypeCondition(List type, ClientPolicyRepresentation policyRep) { - JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientAccessTypeConditionFactory.PROVIDER_ID); - - Set actualTypes = new HashSet<>(); - if (actualConditionConfig.findValue("type") != null) - actualConditionConfig.findValue("type").elements().forEachRemaining(i->actualTypes.add(i.asText())); - assertEquals(new HashSet<>(type), actualTypes); + ClientAccessTypeCondition.Configuration cfg = getConfigAsExpectedType(policyRep, ClientAccessTypeConditionFactory.PROVIDER_ID, ClientAccessTypeCondition.Configuration.class); + Assert.assertEquals(cfg.getType(), type); } protected void assertExpectedClientRolesCondition(List roles, ClientPolicyRepresentation policyRep) { - JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientRolesConditionFactory.PROVIDER_ID); - - Set actualRoles = new HashSet<>(); - if (actualConditionConfig.findValue("roles") != null) - actualConditionConfig.findValue("roles").elements().forEachRemaining(i->actualRoles.add(i.asText())); - assertEquals(new HashSet<>(roles), actualRoles); + ClientRolesCondition.Configuration cfg = getConfigAsExpectedType(policyRep, ClientRolesConditionFactory.PROVIDER_ID, ClientRolesCondition.Configuration.class); + Assert.assertEquals(cfg.getRoles(), roles); } protected void assertExpectedClientScopesCondition(String type, List scopes, ClientPolicyRepresentation policyRep) { - JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientScopesConditionFactory.PROVIDER_ID); - - String actualType = null; - if (actualConditionConfig.findValue("type") != null) actualType = actualConditionConfig.findValue("type").asText(); - assertEquals(type, actualType); - - Set actualScopes = new HashSet<>(); - if (actualConditionConfig.findValue("scope") != null) - actualConditionConfig.findValue("scope").elements().forEachRemaining(i->actualScopes.add(i.asText())); - assertEquals(new HashSet<>(scopes), actualScopes); + ClientScopesCondition.Configuration cfg = getConfigAsExpectedType(policyRep, ClientScopesConditionFactory.PROVIDER_ID, ClientScopesCondition.Configuration.class); + Assert.assertEquals(cfg.getType(), type); + Assert.assertEquals(cfg.getScope(), scopes); } protected void assertExpectedClientUpdateContextCondition(List updateClientSources, ClientPolicyRepresentation policyRep) { - JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientUpdateContextConditionFactory.PROVIDER_ID); - - Set actualUpdateClientSources = new HashSet<>(); - if (actualConditionConfig.findValue("update-client-source") != null) - actualConditionConfig.findValue("update-client-source").elements().forEachRemaining(i->actualUpdateClientSources.add(i.asText())); - assertEquals(new HashSet<>(updateClientSources), actualUpdateClientSources); + ClientUpdateContextCondition.Configuration cfg = getConfigAsExpectedType(policyRep, ClientUpdateContextConditionFactory.PROVIDER_ID, ClientUpdateContextCondition.Configuration.class); + Assert.assertEquals(cfg.getUpdateClientSource(), updateClientSources); } protected void assertExpectedClientUpdateSourceGroupsCondition(List groups, ClientPolicyRepresentation policyRep) { - JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID); - - Set actualGroups = new HashSet<>(); - if (actualConditionConfig.findValue("groups") != null) - actualConditionConfig.findValue("groups").elements().forEachRemaining(i->actualGroups.add(i.asText())); - assertEquals(new HashSet<>(groups), actualGroups); + ClientUpdateSourceGroupsCondition.Configuration cfg = getConfigAsExpectedType(policyRep, ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, ClientUpdateSourceGroupsCondition.Configuration.class); + Assert.assertEquals(cfg.getGroups(), groups); } - protected void assertExpectedClientUpdateSourceHostsCondition(List trustedHosts, List hostSendingRequestMustMatch, ClientPolicyRepresentation policyRep) { - JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientUpdateSourceHostsConditionFactory.PROVIDER_ID); - - List actualTrustedHosts = new ArrayList<>(); - if (actualConditionConfig.findValue("trusted-hosts") != null) - actualConditionConfig.findValue("trusted-hosts").elements().forEachRemaining(i->actualTrustedHosts.add(i.asText())); - assertEquals(trustedHosts, actualTrustedHosts); - - List actualHostSendingRequestMustMatch = new ArrayList<>(); - if (actualConditionConfig.findValue("host-sending-request-must-match") != null) - actualConditionConfig.findValue("host-sending-request-must-match").elements().forEachRemaining(i->actualHostSendingRequestMustMatch.add(i.asBoolean())); - assertEquals(trustedHosts, actualTrustedHosts); + protected void assertExpectedClientUpdateSourceHostsCondition(List trustedHosts, ClientPolicyRepresentation policyRep) { + ClientUpdateSourceHostsCondition.Configuration cfg = getConfigAsExpectedType(policyRep, ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, ClientUpdateSourceHostsCondition.Configuration.class); + Assert.assertEquals(cfg.getTrustedHosts(), trustedHosts); } protected void assertExpectedClientUpdateSourceRolesCondition(List roles, ClientPolicyRepresentation policyRep) { - JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientUpdateSourceRolesConditionFactory.PROVIDER_ID); + ClientUpdateSourceRolesCondition.Configuration cfg = getConfigAsExpectedType(policyRep, ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, ClientUpdateSourceRolesCondition.Configuration.class); + Assert.assertEquals(cfg.getRoles(), roles); + } - Set actualRoles = new HashSet<>(); - if (actualConditionConfig.findValue("roles") != null) - actualConditionConfig.findValue("roles").elements().forEachRemaining(i->actualRoles.add(i.asText())); - assertEquals(new HashSet<>(roles), actualRoles); + private CFG getConfigAsExpectedType(ClientPolicyRepresentation policyRep, String conditionProviderId, Class configClass) { + ClientPolicyConditionRepresentation conditionRep = policyRep.getConditions().stream() + .filter(condition -> conditionProviderId.equals(condition.getConditionProviderId())) + .findFirst().orElseThrow(() -> new AssertionError("Expected to contain configuration for condition " + conditionProviderId)); + + return JsonSerialization.mapper.convertValue(conditionRep.getConfiguration(), configClass); } // profiles/policies common (compounds) @@ -1531,7 +1432,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { return rep; } - private void assertExpetedCompounds(List expected, R rep, Function> f, Function g) { + private void assertExpectedCompounds(List expected, R rep, Function> f, Function g) { assertNotNull(rep); List reps = f.apply(rep); if (reps == null) { @@ -1553,33 +1454,9 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { return reps.get(0); } - // condition/executor common (element) - - private void assertExpetedElement(List expected, T rep, Function> f) { - assertNotNull(rep); - List objs = f.apply(rep); - if (objs == null) { - assertNull(expected); - return; - } - Set actual = objs.stream().map(i->{ - JsonNode node = objectMapper.convertValue(i, JsonNode.class); - return node.fieldNames().next(); - }).collect(Collectors.toSet()); - assertEquals(new HashSet<>(expected), actual); - } - - private void assertExpectedNoConfigElement(String providerId, T rep, Function> f) { - assertNotNull(rep); - JsonNode actualConfig = getConfig(f.apply(rep), providerId); - assertEquals("", actualConfig.asText()); - } - - private JsonNode getConfig(List objs, String providerId) { - List nodes = objs.stream().map(i->objectMapper.convertValue(i, JsonNode.class)) - .filter(j->j.fieldNames().next().equals(providerId)).collect(Collectors.toList()); - if (nodes == null || nodes.size() != 1) return null; - return nodes.get(0); + private void assertExpectedEmptyConfig(String executorProviderId, ClientProfileRepresentation profileRep) { + Map config = getConfigOfExecutor(executorProviderId, profileRep); + Assert.assertTrue("Expected empty configuration for provider " + executorProviderId, config.isEmpty()); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesImportExportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesImportExportTest.java index 652c633234..3f22a80f83 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesImportExportTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesImportExportTest.java @@ -67,7 +67,7 @@ public class ClientPoliciesImportExportTest extends AbstractClientPoliciesTest { String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "client-policies-exported-realm.json"; testingClient.testing().exportImport().setFile(targetFilePath); - loadValidProfilesAndPolicies(); + setupValidProfilesAndPolicies(); testRealmExportImport(); } @@ -92,13 +92,13 @@ public class ClientPoliciesImportExportTest extends AbstractClientPoliciesTest { Assert.assertNames(adminClient.realms().findAll(), "master", "test"); assertExpectedLoadedProfiles((ClientProfilesRepresentation reps)->{ - ClientProfileRepresentation rep = getProfileRepresentation(reps, "ordinal-test-profile"); - assertExpectedProfile(rep, "ordinal-test-profile", "The profile that can be loaded.", false); + ClientProfileRepresentation rep = getProfileRepresentation(reps, "ordinal-test-profile", false); + assertExpectedProfile(rep, "ordinal-test-profile", "The profile that can be loaded."); }); assertExpectedLoadedPolicies((ClientPoliciesRepresentation reps)->{ ClientPolicyRepresentation rep = getPolicyRepresentation(reps, "new-policy"); - assertExpectedPolicy("new-policy", "duplicated profiles are ignored.", false, true, Arrays.asList("builtin-default-profile", "ordinal-test-profile", "lack-of-builtin-field-test-profile"), + assertExpectedPolicy("new-policy", "duplicated profiles are ignored.", true, Arrays.asList("global-default-profile", "ordinal-test-profile", "lack-of-builtin-field-test-profile"), rep); }); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesLoadUpdateTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesLoadUpdateTest.java index e69a2125ac..ab1fb54cd3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesLoadUpdateTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesLoadUpdateTest.java @@ -17,17 +17,23 @@ package org.keycloak.testsuite.client; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import org.hamcrest.Matchers; import org.junit.Test; +import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; +import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator; +import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator; import org.keycloak.common.Profile; import org.keycloak.representations.idm.ClientPoliciesRepresentation; import org.keycloak.representations.idm.ClientPolicyRepresentation; @@ -36,12 +42,12 @@ import org.keycloak.representations.idm.ClientProfilesRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPoliciesUtil; -import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory; import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory; import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory; import org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutorFactory; import org.keycloak.services.clientpolicy.executor.SecureClientAuthEnforceExecutorFactory; import org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutorFactory; +import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.EnableFeature; @@ -67,53 +73,51 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { @Test public void testLoadBuiltinProfilesAndPolicies() throws Exception { - // retrieve loaded builtin profiles - ClientProfilesRepresentation actualProfilesRep = getProfiles(); + // retrieve loaded global profiles + ClientProfilesRepresentation actualProfilesRep = getProfilesWithGlobals(); // same profiles - assertExpectedProfiles(actualProfilesRep, Arrays.asList("builtin-default-profile")); + assertExpectedProfiles(actualProfilesRep, Arrays.asList("global-default-profile"), Collections.emptyList()); // each profile - ClientProfileRepresentation actualProfileRep = getProfileRepresentation(actualProfilesRep, "builtin-default-profile"); - assertExpectedProfile(actualProfileRep, "builtin-default-profile", "The built-in default profile for enforcing basic security level to clients.", true); + ClientProfileRepresentation actualProfileRep = getProfileRepresentation(actualProfilesRep, "global-default-profile", true); + assertExpectedProfile(actualProfileRep, "global-default-profile", "The global default profile for enforcing basic security level to clients."); // each executor assertExpectedExecutors(Arrays.asList(SecureSessionEnforceExecutorFactory.PROVIDER_ID), actualProfileRep); + // Check the "get" request without globals. Assert nothing loaded + actualProfilesRep = getProfilesWithoutGlobals(); + assertExpectedProfiles(actualProfilesRep, null, Collections.emptyList()); + // retrieve loaded builtin policies ClientPoliciesRepresentation actualPoliciesRep = getPolicies(); - // same policies - assertExpectedPolicies(Arrays.asList("builtin-default-policy"), actualPoliciesRep); - - // each policy + // No global policies expected + assertExpectedPolicies(Collections.emptyList(), actualPoliciesRep); ClientPolicyRepresentation actualPolicyRep = getPolicyRepresentation(actualPoliciesRep, "builtin-default-policy"); - assertExpectedPolicy("builtin-default-policy", "The built-in default policy applied to all clients.", true, false, Arrays.asList("builtin-default-profile"), actualPolicyRep); - - // each condition - assertExpectedConditions(Arrays.asList(AnyClientConditionFactory.PROVIDER_ID), actualPolicyRep); - + Assert.assertNull(actualPolicyRep); } @Test public void testUpdateValidProfilesAndPolicies() throws Exception { - loadValidProfilesAndPolicies(); + setupValidProfilesAndPolicies(); assertExpectedLoadedProfiles((ClientProfilesRepresentation reps)->{ - ClientProfileRepresentation rep = getProfileRepresentation(reps, "ordinal-test-profile"); - assertExpectedProfile(rep, "ordinal-test-profile", "The profile that can be loaded.", false); + ClientProfileRepresentation rep = getProfileRepresentation(reps, "ordinal-test-profile", false); + assertExpectedProfile(rep, "ordinal-test-profile", "The profile that can be loaded."); }); assertExpectedLoadedPolicies((ClientPoliciesRepresentation reps)->{ ClientPolicyRepresentation rep = getPolicyRepresentation(reps, "new-policy"); - assertExpectedPolicy("new-policy", "duplicated profiles are ignored.", false, true, Arrays.asList("builtin-default-profile", "ordinal-test-profile", "lack-of-builtin-field-test-profile"), + assertExpectedPolicy("new-policy", "duplicated profiles are ignored.", true, Arrays.asList("global-default-profile", "ordinal-test-profile", "lack-of-builtin-field-test-profile"), rep); }); // update existing profiles String modifiedProfileDescription = "The profile has been updated."; - ClientProfilesRepresentation actualProfilesRep = getProfilesWithoutBuiltin(); + ClientProfilesRepresentation actualProfilesRep = getProfilesWithoutGlobals(); ClientProfilesBuilder profilesBuilder = new ClientProfilesBuilder(); actualProfilesRep.getProfiles().stream().forEach(i->{ if (i.getName().equals("ordinal-test-profile")) { @@ -124,19 +128,19 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { updateProfiles(profilesBuilder.toString()); assertExpectedLoadedProfiles((ClientProfilesRepresentation reps)->{ - ClientProfileRepresentation rep = getProfileRepresentation(reps, "ordinal-test-profile"); - assertExpectedProfile(rep, "ordinal-test-profile", modifiedProfileDescription, false); + ClientProfileRepresentation rep = getProfileRepresentation(reps, "ordinal-test-profile", false); + assertExpectedProfile(rep, "ordinal-test-profile", modifiedProfileDescription); }); // update existing policies String modifiedPolicyDescription = "The policy has also been updated."; - ClientPoliciesRepresentation actualPoliciesRep = getPoliciesWithoutBuiltin(); + ClientPoliciesRepresentation actualPoliciesRep = getPolicies(); ClientPoliciesBuilder policiesBuilder = new ClientPoliciesBuilder(); actualPoliciesRep.getPolicies().stream().forEach(i->{ if (i.getName().equals("new-policy")) { i.setDescription(modifiedPolicyDescription); - i.setEnable(null); + i.setEnabled(null); } policiesBuilder.addPolicy(i); }); @@ -144,7 +148,7 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { assertExpectedLoadedPolicies((ClientPoliciesRepresentation reps)->{ ClientPolicyRepresentation rep = getPolicyRepresentation(reps, "new-policy"); - assertExpectedPolicy("new-policy", modifiedPolicyDescription, false, false, Arrays.asList("builtin-default-profile", "ordinal-test-profile", "lack-of-builtin-field-test-profile"), + assertExpectedPolicy("new-policy", modifiedPolicyDescription, false, Arrays.asList("global-default-profile", "ordinal-test-profile", "lack-of-builtin-field-test-profile"), rep); }); @@ -152,10 +156,10 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { @Test public void testDuplicatedProfiles() throws Exception { - String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles()); + String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfilesWithGlobals()); // load profiles - ClientProfileRepresentation duplicatedProfileRep = (new ClientProfileBuilder()).createProfile("builtin-basic-security", "Enforce basic security level", Boolean.TRUE, null) + ClientProfileRepresentation duplicatedProfileRep = (new ClientProfileBuilder()).createProfile("builtin-basic-security", "Enforce basic security level") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig( Boolean.FALSE, @@ -167,7 +171,7 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { createPKCEEnforceExecutorConfig(Boolean.TRUE)) .toRepresentation(); - ClientProfileRepresentation loadedProfileRep = (new ClientProfileBuilder()).createProfile("ordinal-test-profile", "The profile that can be loaded.", Boolean.FALSE, null) + ClientProfileRepresentation loadedProfileRep = (new ClientProfileBuilder()).createProfile("ordinal-test-profile", "The profile that can be loaded.") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig( Boolean.TRUE, @@ -182,25 +186,43 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { .toString(); try { updateProfiles(json); + fail(); } catch (ClientPolicyException cpe) { assertEquals("Bad Request", cpe.getErrorDetail()); - String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles()); + String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfilesWithGlobals()); assertEquals(beforeUpdateProfilesJson, afterFailedUpdateProfilesJson); - return; } - fail(); + } + + @Test + public void testOverwriteBuiltinProfileNotAllowed() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile("global-default-profile", "Pershyy Profil") + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + createSecureClientAuthEnforceExecutorConfig(Boolean.TRUE, + Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID), + X509ClientAuthenticator.PROVIDER_ID)) + .toRepresentation() + ).toString(); + try { + updateProfiles(json); + fail(); + } catch (ClientPolicyException cpe) { + assertEquals("update profiles failed", cpe.getError()); + } } @Test public void testNullProfiles() throws Exception { - String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles()); + String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfilesWithGlobals()); String json = null; try { updateProfiles(json); } catch (ClientPolicyException cpe) { - assertEquals("Bad Request", cpe.getErrorDetail()); - String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles()); + assertEquals("argument \"content\" is null", cpe.getErrorDetail()); + String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfilesWithGlobals()); assertEquals(beforeUpdateProfilesJson, afterFailedUpdateProfilesJson); return; } @@ -209,7 +231,7 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { @Test public void testInvalidFormattedJsonProfiles() throws Exception { - String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles()); + String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfilesWithGlobals()); String json = "{\n" + " \"profiles\": [\n" @@ -232,8 +254,8 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { try { updateProfiles(json); } catch (ClientPolicyException cpe) { - assertEquals("Bad Request", cpe.getErrorDetail()); - String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles()); + assertThat(cpe.getErrorDetail(), Matchers.startsWith("Unrecognized field")); + String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfilesWithGlobals()); assertEquals(beforeUpdateProfilesJson, afterFailedUpdateProfilesJson); return; } @@ -242,7 +264,7 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { @Test public void testInvalidFieldTypeJsonProfiles() throws Exception { - String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles()); + String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfilesWithGlobals()); String json = "{\n" + " \"profiles\": [\n" @@ -250,14 +272,12 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { + " \"name\" : \"ordinal-test-profile\",\n" + " \"description\" : \"Not builtin profile that should be skipped.\",\n" + " \"builtin\" : \"no\",\n" - + " \"executors\": [\n" - + " {\n" + + " \"executors\": {\n" + " \"new-secure-client-authn-executor\": {\n" + " \"client-authns\": [ \"private-key-jwt\" ],\n" + " \"client-authns-augment\" : \"private-key-jwt\",\n" + " \"is-augment\" : true\n" + " }\n" - + " }\n" + " ]\n" + " }\n" + " ]\n" @@ -265,8 +285,8 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { try { updateProfiles(json); } catch (ClientPolicyException cpe) { - assertEquals("Bad Request", cpe.getErrorDetail()); - String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles()); + assertThat(cpe.getErrorDetail(), Matchers.startsWith("Unrecognized field ")); + String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfilesWithGlobals()); assertEquals(beforeUpdateProfilesJson, afterFailedUpdateProfilesJson); return; } @@ -282,24 +302,21 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { (new ClientPolicyBuilder()).createPolicy( "builtin-duplicated-new-policy", "builtin duplicated new policy is ignored.", - Boolean.FALSE, - Boolean.TRUE, - null, - Arrays.asList("builtin-default-profile")) + Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) + .addProfile("global-default-profile") .toRepresentation(); ClientPolicyRepresentation loadedPolicyRep = (new ClientPolicyBuilder()).createPolicy( "new-policy", "duplicated profiles are ignored.", - Boolean.FALSE, - Boolean.TRUE, - null, - Arrays.asList("lack-of-builtin-field-test-profile", "ordinal-test-profile")) + Boolean.TRUE) .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, createClientAccessTypeConditionConfig(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_PUBLIC, ClientAccessTypeConditionFactory.TYPE_BEARERONLY))) + .addProfile("lack-of-builtin-field-test-profile") + .addProfile("ordinal-test-profile") .toRepresentation(); String json = (new ClientPoliciesBuilder()) @@ -359,7 +376,7 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { try { updatePolicies(json); } catch (ClientPolicyException cpe) { - assertEquals("Bad Request", cpe.getErrorDetail()); + assertThat(cpe.getErrorDetail(), Matchers.startsWith("Unrecognized field ")); String afterFailedUpdatePoliciesJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(getPolicies()); assertEquals(beforeUpdatePoliciesJson, afterFailedUpdatePoliciesJson); return; @@ -386,11 +403,31 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { try { updatePolicies(json); } catch (ClientPolicyException cpe) { - assertEquals("Bad Request", cpe.getErrorDetail()); + assertThat(cpe.getErrorDetail(), Matchers.startsWith("Unrecognized field ")); String afterFailedUpdatePoliciesJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(getPolicies()); assertEquals(beforeUpdatePoliciesJson, afterFailedUpdatePoliciesJson); return; } fail(); } + + // Test that regular CRUD of realm representation object through admin REST API does not remove + @Test + public void testCRUDRealmRepresentation() throws Exception { + setupValidProfilesAndPolicies(); + + // Get the realm and assert that expected policies and profiles are present + RealmResource testRealm = realmsResouce().realm("test"); + RealmRepresentation realmRep = testRealm.toRepresentation(); + assertExpectedProfiles(realmRep.getClientProfiles(), null, Arrays.asList("ordinal-test-profile", "lack-of-builtin-field-test-profile")); + assertExpectedPolicies(Arrays.asList("new-policy", "lack-of-builtin-field-test-policy"), realmRep.getClientPolicies()); + + // Update the realm + testRealm.update(realmRep); + + // Test the realm again + realmRep = testRealm.toRepresentation(); + assertExpectedProfiles(realmRep.getClientProfiles(), null, Arrays.asList("ordinal-test-profile", "lack-of-builtin-field-test-profile")); + assertExpectedPolicies(Arrays.asList("new-policy", "lack-of-builtin-field-test-policy"), realmRep.getClientPolicies()); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java index eac2efee8a..7098f10867 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java @@ -249,7 +249,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testAdminClientAugmentedAuthType() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Pershyy Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Pershyy Profil") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig(Boolean.TRUE, Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID), @@ -260,7 +260,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Persha Polityka", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Persha Polityka", Boolean.TRUE) .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))) .addProfile(PROFILE_NAME) @@ -276,7 +276,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // update profiles json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Pershyy Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Pershyy Profil") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig(Boolean.TRUE, Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID), @@ -332,7 +332,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testCreateUpdateDeleteConditionRuntime() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Eichte profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Eichte profil") .addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID, createPKCEEnforceExecutorConfig(Boolean.TRUE)) .toRepresentation() @@ -350,7 +350,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Eischt Politik", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Eischt Politik", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) .addProfile(PROFILE_NAME) @@ -361,7 +361,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { failLoginByNotFollowingPKCE(clientId); // update policies - updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Aktualiseiert Eischt Politik", Boolean.FALSE, Boolean.TRUE, null, null) + updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Aktualiseiert Eischt Politik", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList("anothor-client-role"))) .addProfile(PROFILE_NAME) @@ -370,7 +370,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { successfulLoginAndLogout(clientId, clientSecret); // update policies - updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Aktualiseiert Eischt Politik", Boolean.FALSE, Boolean.TRUE, null, null) + updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Aktualiseiert Eischt Politik", Boolean.TRUE) .addProfile(PROFILE_NAME) .toRepresentation()); @@ -381,7 +381,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testCreateUpdateDeleteExecutorRuntime() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Purofairu Sono Ichi", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Purofairu Sono Ichi") .addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID, createPKCEEnforceExecutorConfig(Boolean.FALSE)) .toRepresentation() @@ -390,7 +390,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Porishii Sono Ichi", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Porishii Sono Ichi", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, @@ -409,7 +409,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { successfulLoginAndLogout(clientId, clientSecret); // update policies - updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Koushinsareta Porishii Sono Ichi", Boolean.FALSE, Boolean.TRUE, null, null) + updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Koushinsareta Porishii Sono Ichi", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, @@ -421,7 +421,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // update profiles updateProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Koushinsareta Purofairu Sono Ichi", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Koushinsareta Purofairu Sono Ichi") .addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID, createPKCEEnforceExecutorConfig(Boolean.TRUE)) .toRepresentation()); @@ -434,7 +434,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // update profiles updateProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Sarani Koushinsareta Purofairu Sono Ichi", Boolean.FALSE, null).toRepresentation()); + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Sarani Koushinsareta Purofairu Sono Ichi").toRepresentation()); updateClientByAdmin(cid, (ClientRepresentation clientRep) -> { OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setPkceCodeChallengeMethod(null); @@ -473,11 +473,11 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { String profileAlphaName = "MyProfile-alpha"; String profileBetaName = "MyProfile-beta"; String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(profileAlphaName, "Pierwszy Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(profileAlphaName, "Pierwszy Profil") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig(Boolean.TRUE, Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID), ClientIdAndSecretAuthenticator.PROVIDER_ID)) .toRepresentation()).addProfile( - (new ClientProfileBuilder()).createProfile(profileBetaName, "Drugi Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(profileBetaName, "Drugi Profil") .addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID, createPKCEEnforceExecutorConfig(Boolean.TRUE)) .toRepresentation() @@ -488,14 +488,14 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { String policyAlphaName = "MyPolicy-alpha"; String policyBetaName = "MyPolicy-beta"; json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(policyAlphaName, "Pierwsza Zasada", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(policyAlphaName, "Pierwsza Zasada", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName))) .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))) .addProfile(profileAlphaName) .toRepresentation()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(policyBetaName, "Drugi Zasada", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(policyBetaName, "Drugi Zasada", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(roleBetaName, roleZetaName))) .addProfile(profileBetaName) @@ -530,7 +530,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testIntentionalExceptionOnCondition() throws Exception { // register policies String json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Fyrsta Stefnan", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Fyrsta Stefnan", Boolean.TRUE) .addCondition(TestRaiseExeptionConditionFactory.PROVIDER_ID, createTestRaiseExeptionConditionConfig()) .toRepresentation() @@ -549,7 +549,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testAnyClientCondition() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil") .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -557,7 +557,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE) .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig()) .addProfile(PROFILE_NAME) @@ -590,7 +590,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testConditionWithoutNoConfiguration() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Die Erste Politik", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Die Erste Politik") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -598,22 +598,22 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientAccessTypeCondition", "Die Erste Politik", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientAccessTypeCondition", "Die Erste Politik", Boolean.TRUE) .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, null) .addProfile(PROFILE_NAME) .toRepresentation() ).addPolicy( - (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateSourceGroupsCondition", "Die Zweite Politik", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateSourceGroupsCondition", "Die Zweite Politik", Boolean.TRUE) .addCondition(ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, null) .addProfile(PROFILE_NAME) .toRepresentation() ).addPolicy( - (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateSourceRolesCondition", "Die Dritte Politik", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateSourceRolesCondition", "Die Dritte Politik", Boolean.TRUE) .addCondition(ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, null) .addProfile(PROFILE_NAME) .toRepresentation() ).addPolicy( - (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateContextCondition", "Die Vierte Politik", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateContextCondition", "Die Vierte Politik", Boolean.TRUE) .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, null) .addProfile(PROFILE_NAME) .toRepresentation() @@ -637,7 +637,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testClientUpdateSourceHostsCondition() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvni Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvni Profil") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig( Boolean.FALSE, @@ -650,8 +650,8 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prvni Politika", Boolean.FALSE, Boolean.TRUE, null, null) - .addCondition(ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prvni Politika", Boolean.TRUE) + .addCondition(ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, createClientUpdateSourceHostsConditionConfig(Arrays.asList("localhost", "127.0.0.1"))) .addProfile(PROFILE_NAME) .toRepresentation() @@ -671,8 +671,8 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // update policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Aktualizovana Prvni Politika", Boolean.FALSE, Boolean.TRUE, null, null) - .addCondition(ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Aktualizovana Prvni Politika", Boolean.TRUE) + .addCondition(ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, createClientUpdateSourceHostsConditionConfig(Arrays.asList("example.com"))) .addProfile(PROFILE_NAME) .toRepresentation() @@ -692,7 +692,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testClientUpdateSourceGroupsCondition() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profil") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig( Boolean.FALSE, @@ -705,7 +705,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politik", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politik", Boolean.TRUE) .addCondition(ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, createClientUpdateSourceGroupsConditionConfig(Arrays.asList("topGroup"))) .addProfile(PROFILE_NAME) @@ -732,7 +732,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testClientUpdateSourceRolesCondition() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Il Primo Profilo", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Il Primo Profilo") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig( Boolean.FALSE, @@ -745,9 +745,9 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Prima Politica", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Prima Politica", Boolean.TRUE) .addCondition(ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, - createClientUpdateSourceRolesConditionConfig(Arrays.asList(AdminRoles.CREATE_CLIENT))) + createClientUpdateSourceRolesConditionConfig(Arrays.asList(Constants.REALM_MANAGEMENT_CLIENT_ID + "." + AdminRoles.CREATE_CLIENT))) .addProfile(PROFILE_NAME) .toRepresentation() ).toString(); @@ -772,7 +772,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testClientScopesCondition() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Het Eerste Profiel", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Het Eerste Profiel") .addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID, createPKCEEnforceExecutorConfig(Boolean.TRUE)) .toRepresentation() @@ -781,7 +781,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Het Eerste Beleid", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Het Eerste Beleid", Boolean.TRUE) .addCondition(ClientScopesConditionFactory.PROVIDER_ID, createClientScopesConditionConfig(ClientScopesConditionFactory.OPTIONAL, Arrays.asList("offline_access", "microprofile-jwt"))) .addProfile(PROFILE_NAME) @@ -815,7 +815,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testClientAccessTypeCondition() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "El Primer Perfil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "El Primer Perfil") .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -823,7 +823,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Primera Plitica", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Primera Plitica", Boolean.TRUE) .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, createClientAccessTypeConditionConfig(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL))) .addProfile(PROFILE_NAME) @@ -854,7 +854,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testSecureResponseTypeExecutor() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil") .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -862,7 +862,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "A Primeira Politica", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "A Primeira Politica", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) .addProfile(PROFILE_NAME) @@ -921,7 +921,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { Integer availablePeriod = Integer.valueOf(SecureRequestObjectExecutor.DEFAULT_AVAILABLE_PERIOD + 400); // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil") .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID, createSecureRequestObjectExecutorConfig(availablePeriod, null)) .toRepresentation() @@ -930,7 +930,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prva Politika", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prva Politika", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) .addProfile(PROFILE_NAME) @@ -1046,7 +1046,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // update profile : no configuration - "nbf" check and available period is 3600 sec json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil") .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -1078,7 +1078,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // update profile : not check "nbf" json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil") .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID, createSecureRequestObjectExecutorConfig(null, Boolean.FALSE)) .toRepresentation() @@ -1109,7 +1109,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testSecureSessionEnforceExecutor() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen") .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -1119,7 +1119,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { String roleAlphaName = "sample-client-role-alpha"; String roleBetaName = "sample-client-role-beta"; json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(roleBetaName))) .addProfile(PROFILE_NAME) @@ -1164,7 +1164,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testSecureSigningAlgorithmEnforceExecutor() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen") .addExecutor(SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -1172,7 +1172,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forsta Policyn", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forsta Policyn", Boolean.TRUE) .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, createClientUpdateContextConditionConfig(Arrays.asList( ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, @@ -1237,7 +1237,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // update profiles, ES256 enforced json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen") .addExecutor(SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, createSecureSigningAlgorithmEnforceExecutorConfig(org.keycloak.crypto.Algorithm.ES256)) .toRepresentation() @@ -1261,7 +1261,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // update profiles, fall back to PS256 json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen") .addExecutor(SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, createSecureSigningAlgorithmEnforceExecutorConfig(org.keycloak.crypto.Algorithm.RS512)) .toRepresentation() @@ -1318,7 +1318,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // update profiles, enforce ES256 json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen") .addExecutor(SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, createSecureSigningAlgorithmEnforceExecutorConfig(org.keycloak.crypto.Algorithm.ES256)) .toRepresentation() @@ -1343,7 +1343,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testSecureClientRegisteringUriEnforceExecutor() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili") .addExecutor(SecureClientRegisteringUriEnforceExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -1351,7 +1351,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Ensimmainen Politiikka", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Ensimmainen Politiikka", Boolean.TRUE) .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, createClientUpdateContextConditionConfig(Arrays.asList( ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, @@ -1388,6 +1388,18 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { }); assertEquals(false, getClientByAdmin(cid).isServiceAccountsEnabled()); + // update policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Paivitetyn Ensimmaisen Politiikka", Boolean.TRUE) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, + createClientUpdateContextConditionConfig(Arrays.asList( + ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, + ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); + try { updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> { clientRep.setRedirectUris(Collections.singletonList("https://newredirect/*")); @@ -1531,10 +1543,11 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testSecureSigningAlgorithmForSignedJwtEnforceExecutorWithSecureAlg() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili", Boolean.FALSE, null) - .addExecutor(SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig(Boolean.FALSE)) - .toRepresentation() - ).toString(); + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili") + .addExecutor(SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig(Boolean.TRUE) + ).toRepresentation() + ) + .toString(); updateProfiles(json); // register policies @@ -1542,7 +1555,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { String roleZetaName = "sample-client-role-zeta"; String roleCommonName = "sample-client-role-common"; json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName))) .addProfile(PROFILE_NAME) @@ -1622,7 +1635,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testSecureSigningAlgorithmForSignedJwtEnforceExecutorWithNotSecureAlg() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili") .addExecutor(SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig(Boolean.FALSE)) .toRepresentation() ).toString(); @@ -1633,7 +1646,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { String roleZetaName = "sample-client-role-zeta"; String roleCommonName = "sample-client-role-common"; json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName))) .addProfile(PROFILE_NAME) @@ -1683,7 +1696,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Az Elso Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Az Elso Profil") .addExecutor(HolderOfKeyEnforceExecutorFactory.PROVIDER_ID, createHolderOfKeyEnforceExecutorConfig(Boolean.TRUE)) .addExecutor(SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, @@ -1694,7 +1707,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Az Elso Politika", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Az Elso Politika", Boolean.TRUE) .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig()) .addProfile(PROFILE_NAME) @@ -1715,7 +1728,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testNegativeLogicCondition() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen") .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -1723,7 +1736,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE) .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig()) .addProfile(PROFILE_NAME) .toRepresentation() @@ -1740,7 +1753,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { failLoginWithoutSecureSessionParameter(clientId, ERR_MSG_MISSING_NONCE); // update policies - updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE) .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig(Boolean.TRUE)) .addProfile(PROFILE_NAME) .toRepresentation()); @@ -1748,7 +1761,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { successfulLoginAndLogout(clientId, clientSecret); // update policies - updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE) .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig(Boolean.FALSE)) .addProfile(PROFILE_NAME) .toRepresentation()); @@ -1763,7 +1776,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testExtendedClientPolicyIntefacesForClientRegistrationPolicyMigration() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen") .addExecutor(TestRaiseExeptionExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -1771,7 +1784,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE) .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig()) .addProfile(PROFILE_NAME) .toRepresentation() @@ -1811,36 +1824,19 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testOverwriteBuiltinProfileNotAllowed() throws Exception { - // register profiles - String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile("builtin-default-profile", "Pershyy Profil", Boolean.FALSE, null) - .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, - createSecureClientAuthEnforceExecutorConfig(Boolean.TRUE, - Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID), - X509ClientAuthenticator.PROVIDER_ID)) - .toRepresentation() - ).toString(); - try { - updateProfiles(json); - } catch (ClientPolicyException cpe) { - assertEquals("update profiles failed", cpe.getError()); - } - } - - @Test - public void testUpdatePolicyWithoutNameNotAllowd() throws Exception { + public void testUpdatePolicyWithoutNameNotAllowed() throws Exception { // register policies String json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(null, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(null, "La Premiere Politique", Boolean.TRUE) .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig()) .addProfile(PROFILE_NAME) .toRepresentation() ).toString(); try { updatePolicies(json); + fail(); } catch (ClientPolicyException cpe) { - assertEquals("update profiles failed", cpe.getError()); + assertEquals("update policies failed", cpe.getError()); } } @@ -1848,7 +1844,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testConfidentialClientAcceptExecutorExecutor() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Erstes Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Erstes Profil") .addExecutor(ConfidentialClientAcceptExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -1856,7 +1852,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Erstes Politik", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Erstes Politik", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) .addProfile(PROFILE_NAME) @@ -1897,7 +1893,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testConsentRequiredExecutorExecutor() throws Exception { // register profiles String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Test Profile", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Test Profile") .addExecutor(ConsentRequiredExecutorFactory.PROVIDER_ID, null) .toRepresentation() ).toString(); @@ -1905,7 +1901,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Test Policy", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Test Policy", Boolean.TRUE) .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig()) .addProfile(PROFILE_NAME) @@ -2064,7 +2060,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register profiles String profileName = "MyProfile"; String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(profileName, "Primum Profile", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(profileName, "Primum Profile") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig(Boolean.FALSE, Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID), @@ -2075,7 +2071,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(policyName, "Primum Consilium", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(policyName, "Primum Consilium", Boolean.TRUE) .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))) .addProfile(profileName) @@ -2088,7 +2084,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register profiles String profileName = "MyProfile"; String json = (new ClientProfilesBuilder()).addProfile( - (new ClientProfileBuilder()).createProfile(profileName, "Primul Profil", Boolean.FALSE, null) + (new ClientProfileBuilder()).createProfile(profileName, "Primul Profil") .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, createSecureClientAuthEnforceExecutorConfig(Boolean.TRUE, Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID, JWTClientAuthenticator.PROVIDER_ID), @@ -2101,7 +2097,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { // register policies json = (new ClientPoliciesBuilder()).addPolicy( - (new ClientPolicyBuilder()).createPolicy(policyName, "Prima Politica", Boolean.FALSE, Boolean.TRUE, null, null) + (new ClientPolicyBuilder()).createPolicy(policyName, "Prima Politica", Boolean.TRUE) .addCondition(ClientRolesConditionFactory.PROVIDER_ID, createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/runonserver/InternalComponentRepresentation.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/runonserver/InternalComponentRepresentation.java index 346ccc6ecd..ecb4675592 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/runonserver/InternalComponentRepresentation.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/runonserver/InternalComponentRepresentation.java @@ -16,7 +16,7 @@ public class InternalComponentRepresentation implements FetchOnServerWrapper ModelToRepresentation.toRepresentation(session.getContext().getRealm(), true); + return (FetchOnServer) session -> ModelToRepresentation.toRepresentation(session, session.getContext().getRealm(), true); } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/runonserver/RunOnServerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/runonserver/RunOnServerTest.java index 218a2b35fe..e2aae54ba7 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/runonserver/RunOnServerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/runonserver/RunOnServerTest.java @@ -53,7 +53,7 @@ public class RunOnServerTest extends AbstractKeycloakTest { RealmRepresentation realmRep = testingClient.server().fetch(session -> { RealmModel master = session.realms().getRealm(realmName); - return ModelToRepresentation.toRepresentation(master, true); + return ModelToRepresentation.toRepresentation(session, master, true); }, RealmRepresentation.class); assertEquals(realmName, realmRep.getRealm()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties index 02f1982044..e200365114 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties @@ -96,3 +96,6 @@ log4j.logger.org.keycloak.services.clientregistration.policy=debug # Enable to log short stack traces for log entries enabled with StackUtil.getShortStackTrace() calls # log4j.logger.org.keycloak.STACK_TRACE=trace + +# Client policies +#log4j.logger.org.keycloak.services.clientpolicy=trace diff --git a/testsuite/utils/src/main/resources/log4j.properties b/testsuite/utils/src/main/resources/log4j.properties index 4c46e37804..e3f658c814 100755 --- a/testsuite/utils/src/main/resources/log4j.properties +++ b/testsuite/utils/src/main/resources/log4j.properties @@ -110,3 +110,6 @@ log4j.logger.org.apache.directory.server.ldap.LdapProtocolHandler=error #log4j.logger.org.keycloak.credential.WebAuthnCredentialProvider=debug #log4j.logger.org.keycloak.authentication.requiredactions.WebAuthnRegister=debug #log4j.logger.org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticator=debug + +# Client policies +#log4j.logger.org.keycloak.services.clientpolicy=trace diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 0b03ae6b06..2799140c9a 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -844,6 +844,46 @@ max-clients.tooltip=It will not be allowed to register a new client if count of client-scopes=Client Scopes client-scopes.tooltip=Client scopes allow you to define a common set of protocol mappers and roles, which are shared between multiple clients +# Client Policies +realm-tab-client-policies=Client Policies +client-policies-profiles=Profiles +client-policies-profiles.tooltip=Client Profile allows to setup set of executors, which are enforced for various actions done with the client. Actions can be admin actions like creating or updating client, or user actions like authentication to the client. +client-policies-policies=Policies +client-policies-policies.tooltip=Client Policy allows to bind client profiles with various conditions to specify when exactly is enforced behaviour specified by executors of the particular client profile. +client-profiles-form-view=Form View +client-profiles-json-editor=JSON Editor +global=Global +executors=Executors +client-profile-name.tooltip=Name of the client profile. Must be unique within the realm +client-profile-executors.tooltip=Executors, which will be applied for this client profile +no-executors-available=No Executors Available +push-profile-to-json=Push Profile to JSON +executor-type=Executor Type +create-executor=Create Executor +client-policy-name.tooltip=Name of the client policy. Must be unique within the realm. +client-policy-enabled.tooltip=Specifies if client policy is enabled. Disabled policies are not considered at all during evaluation of client requests. +conditions=Conditions +client-policy-conditions.tooltip=Conditions, which will be evaluated to determine if client policy should be applied during particular action or not. +no-conditions-available=No Conditions Available +condition-type=Condition Type +create-condition=Create Condition +client-profiles=Client Profiles +client-profiles.tooltip=Client Profiles applied on this policy +add-profile.placeholder=Add client profile ... +no-client-profiles-configured=No client profiles configured + +clientscopes-condition.label=Expected Scopes +clientscopes-condition.tooltip=The list of expected client scopes. Condition evaluates to true if specified client request matches some of the client scopes. It depends also whether it should be default or optional client scope based on the 'Scope Type' configured. +client-accesstype.label=Client Access Type +client-accesstype.tooltip=Access Type of the client, for which the condition will be applied. +clientroles-condition.label=Client Roles +clientroles-condition.tooltip=Client roles, which will be checked during this condition evaluation. Condition evaluates to true if client has at least one client role with the name as the client roles specified in the configuration. +clientupdatesourcegroups-condition.label=Groups +clientupdatesourcegroups-condition.tooltip=Name of groups to check. Condition evaluates to true if the entity, who creates/updates client is member of some of the specified groups. Configured groups are specified by their simple name, which must match to the name of the Keycloak group. No support for group hierarchy is used here. +clientupdate-trusted-hosts.label=Trusted hosts +clientupdate-trusted-hosts.tooltip=List of Hosts, which are trusted. In case that client registration/update request comes from the host/domain specified in this configuration, condition evaluates to true. You can use hostnames or IP addresses. If you use star at the beginning (for example '*.example.com' ) then whole domain example.com will be trusted. +clientupdatesourceroles-condition.label=Updating entity role +clientupdatesourceroles-condition.tooltip=The condition is checked during client registration/update requests and it evaluates to true if the entity (usually user), who is creating/updating client is member of the specified role. For reference the realm role, you can use the realm role name like 'my_realm_role' . For reference client role, you can use the client_id.role_name for example 'my_client.my_client_role' will refer to client role 'my_client_role' of client 'my_client'. groups=Groups diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index 81dc3d97bc..af80ed17a4 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -330,6 +330,168 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'ClientRegPolicyDetailCtrl' }) + .when('/realms/:realm/client-policies/profiles', { + templateUrl : resourceUrl + '/partials/client-policies-profiles-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientProfiles : function(ClientPoliciesProfilesLoader) { + return ClientPoliciesProfilesLoader.loadClientProfiles('true'); + }, + }, + controller : 'ClientPoliciesProfilesListCtrl' + }) + .when('/realms/:realm/client-policies/profiles-json', { + templateUrl : resourceUrl + '/partials/client-policies-profiles-json.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientProfiles : function(ClientPoliciesProfilesLoader) { + return ClientPoliciesProfilesLoader.loadClientProfiles('false'); + } + }, + controller : 'ClientPoliciesProfilesJsonCtrl' + }) + .when('/realms/:realm/client-policies/profiles-create', { + templateUrl : resourceUrl + '/partials/client-policies-profiles-edit.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientProfiles : function(ClientPoliciesProfilesLoader) { + return ClientPoliciesProfilesLoader.loadClientProfiles('false'); + } + }, + controller : 'ClientPoliciesProfilesEditCtrl' + }) + .when('/realms/:realm/client-policies/profiles-update/:profileName', { + templateUrl : resourceUrl + '/partials/client-policies-profiles-edit.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientProfiles : function(ClientPoliciesProfilesLoader) { + return ClientPoliciesProfilesLoader.loadClientProfiles('true'); + } + }, + controller : 'ClientPoliciesProfilesEditCtrl' + }) + .when('/realms/:realm/client-policies/profiles-update/:profileName/create-executor', { + templateUrl : resourceUrl + '/partials/client-policies-profiles-edit-executor.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientProfiles : function(ClientPoliciesProfilesLoader) { + return ClientPoliciesProfilesLoader.loadClientProfiles('false'); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientPoliciesProfilesEditExecutorCtrl' + }) + .when('/realms/:realm/client-policies/profiles-update/:profileName/update-executor/:executorIndex', { + templateUrl : resourceUrl + '/partials/client-policies-profiles-edit-executor.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientProfiles : function(ClientPoliciesProfilesLoader) { + return ClientPoliciesProfilesLoader.loadClientProfiles('true'); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientPoliciesProfilesEditExecutorCtrl' + }) + .when('/realms/:realm/client-policies/policies', { + templateUrl : resourceUrl + '/partials/client-policies-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientPolicies : function(ClientPoliciesLoader) { + return ClientPoliciesLoader(); + } + }, + controller : 'ClientPoliciesListCtrl' + }) + .when('/realms/:realm/client-policies/policies-json', { + templateUrl : resourceUrl + '/partials/client-policies-json.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientPolicies : function(ClientPoliciesLoader) { + return ClientPoliciesLoader(); + } + }, + controller : 'ClientPoliciesJsonCtrl' + }) + .when('/realms/:realm/client-policies/policy-create', { + templateUrl : resourceUrl + '/partials/client-policies-policy-edit.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientProfiles : function(ClientPoliciesProfilesLoader) { + return ClientPoliciesProfilesLoader.loadClientProfiles('true'); + }, + clientPolicies : function(ClientPoliciesLoader) { + return ClientPoliciesLoader(); + } + }, + controller : 'ClientPoliciesEditCtrl' + }) + .when('/realms/:realm/client-policies/policies-update/:policyName', { + templateUrl : resourceUrl + '/partials/client-policies-policy-edit.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientProfiles : function(ClientPoliciesProfilesLoader) { + return ClientPoliciesProfilesLoader.loadClientProfiles('true'); + }, + clientPolicies : function(ClientPoliciesLoader) { + return ClientPoliciesLoader(); + } + }, + controller : 'ClientPoliciesEditCtrl' + }) + .when('/realms/:realm/client-policies/policies-update/:policyName/create-condition', { + templateUrl : resourceUrl + '/partials/client-policies-policy-edit-condition.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientPolicies : function(ClientPoliciesLoader) { + return ClientPoliciesLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientPoliciesEditConditionCtrl' + }) + .when('/realms/:realm/client-policies/policies-update/:policyName/update-condition/:conditionIndex', { + templateUrl : resourceUrl + '/partials/client-policies-policy-edit-condition.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientPolicies : function(ClientPoliciesLoader) { + return ClientPoliciesLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientPoliciesEditConditionCtrl' + }) .when('/realms/:realm/keys', { templateUrl : resourceUrl + '/partials/realm-keys.html', resolve : { diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index 377733097f..e6a046b9ea 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -2966,6 +2966,655 @@ module.controller('ClientRegPolicyDetailCtrl', function ($scope, realm, clientRe }); +module.controller('ClientPoliciesProfilesListCtrl', function($scope, realm, clientProfiles, ClientPoliciesProfiles, Dialog, Notifications, $route, $location) { + console.log('ClientPoliciesProfilesListCtrl'); + $scope.realm = realm; + $scope.clientProfiles = clientProfiles; + + $scope.removeClientProfile = function(clientProfile) { + console.log("Deleting client profile from the JSON: " + clientProfile.name); + + for (var i=0 ; i < $scope.clientProfiles.profiles.length ; i++) { + var currentProfile = $scope.clientProfiles.profiles[i]; + if (currentProfile.name === clientProfile.name) { + $scope.clientProfiles.profiles.splice(i, 1); + break; + } + } + + ClientPoliciesProfiles.update({ + realm: realm.realm, + }, $scope.clientProfiles, function () { + $route.reload(); + Notifications.success("The client profile was deleted."); + }, function(errorResponse) { + Notifications.error('Failed to delete client profile. Check server log for the details'); + }); + }; + +}); + +module.controller('ClientPoliciesProfilesJsonCtrl', function($scope, realm, clientProfiles, ClientPoliciesProfiles, Dialog, Notifications, $route, $location) { + console.log('ClientPoliciesProfilesJsonCtrl'); + $scope.realm = realm; + $scope.clientProfilesString = angular.toJson(clientProfiles, true); + + $scope.save = function() { + var clientProfilesObj = null; + try { + var clientProfilesObj = angular.fromJson($scope.clientProfilesString); + } catch (e) { + Notifications.error("Provided JSON is incorrect. See browser javascript console for the details"); + console.log(e); + return; + } + var clientProfilesCompressed = angular.toJson(clientProfilesObj, false); + + ClientPoliciesProfiles.update({ + realm: realm.realm, + }, clientProfilesCompressed, function () { + $route.reload(); + Notifications.success("The client profiles configuration was updated."); + }, function(errorResponse) { + Notifications.error('Failed to update client profiles. Check browser javascript console and server log for the details'); + console.log("Error response when updating client profiles JSON: Status: " + errorResponse.status + + ", statusText: " + errorResponse.statusText + ", data: " + errorResponse.data); + }); + }; + + $scope.reset = function() { + $route.reload(); + }; + +}); + +module.controller('ClientPoliciesProfilesEditCtrl', function($scope, realm, clientProfiles, ClientPoliciesProfiles, Dialog, Notifications, $route, $location) { + var targetProfileName = $route.current.params.profileName; + $scope.createNew = targetProfileName == null; + if ($scope.createNew) { + console.log('ClientPoliciesProfilesEditCtrl: creating new profile'); + } else { + console.log('ClientPoliciesProfilesEditCtrl: updating profile ' + targetProfileName); + } + + $scope.realm = realm; + $scope.editedProfile = null; + + function getProfileByName(profilesArray) { + if (!profilesArray) return null; + for (var i=0 ; i < profilesArray.length ; i++) { + var currentProfile = profilesArray[i]; + if (targetProfileName === currentProfile.name) { + return currentProfile; + } + } + } + + if ($scope.createNew) { + $scope.editedProfile = { + name: "", + executors: [] + }; + } else { + var globalProfile = false; + $scope.editedProfile = getProfileByName(clientProfiles.profiles); + if (!$scope.editedProfile) { + $scope.editedProfile = getProfileByName(clientProfiles.globalProfiles); + globalProfile = true; + } + + if ($scope.editedProfile == null) { + console.log("Profile of name " + targetProfileName + " not found"); + throw 'Profile not found'; + } + } + + $scope.readOnly = !$scope.access.manageRealm || globalProfile; + + $scope.removeExecutor = function(executorIndex) { + console.log("remove executor of index " + executorIndex); + + // Delete executor + $scope.editedProfile.executors.splice(executorIndex, 1); + + ClientPoliciesProfiles.update({ + realm: realm.realm, + }, clientProfiles, function () { + Notifications.success("The executor was deleted."); + }, function(errorResponse) { + Notifications.error('Failed to delete executor. Check server log for the details'); + }); + } + + $scope.save = function() { + if (!$scope.editedProfile.name || $scope.editedProfile.name === '') { + Notifications.error('Name must be provided'); + return; + } + + if ($scope.createNew) { + clientProfiles.profiles.push($scope.editedProfile); + } + + ClientPoliciesProfiles.update({ + realm: realm.realm, + }, clientProfiles, function () { + if ($scope.createNew) { + Notifications.success("The client profile was created."); + $location.url('/realms/' + realm.realm + '/client-policies/profiles-update/' + $scope.editedProfile.name); + } else { + Notifications.success("The client profile was updated."); + $location.url('/realms/' + realm.realm + '/client-policies/profiles'); + } + }, function(errorResponse) { + if ($scope.createNew) { + Notifications.error('Failed to create client profile. Check server log for the details'); + } else { + Notifications.error('Failed to update client profile. Check server log for the details'); + } + }); + + }; + + $scope.back = function() { + $location.url('/realms/' + realm.realm + '/client-policies/profiles'); + }; + +}); + +module.controller('ClientPoliciesProfilesEditExecutorCtrl', function($scope, realm, serverInfo, clientProfiles, ClientPoliciesProfiles, ComponentUtils, Dialog, Notifications, $route, $location) { + var updatedExecutorIndex = $route.current.params.executorIndex; + var targetProfileName = $route.current.params.profileName; + $scope.createNew = updatedExecutorIndex == null; + if ($scope.createNew) { + console.log('ClientPoliciesProfilesEditExecutorCtrl: adding executor to profile ' + targetProfileName); + } else { + console.log('ClientPoliciesProfilesEditExecutorCtrl: updating executor with index ' + updatedExecutorIndex + ' of profile ' + targetProfileName); + } + $scope.realm = realm; + + function getProfileByName(profilesArray) { + if (!profilesArray) return null; + for (var i=0 ; i < profilesArray.length ; i++) { + var currentProfile = profilesArray[i]; + if (targetProfileName === currentProfile.name) { + return currentProfile; + } + } + } + + var globalProfile = false; + var editedProfile = getProfileByName(clientProfiles.profiles); + if (!editedProfile) { + editedProfile = getProfileByName(clientProfiles.globalProfiles); + globalProfile = true; + } + if (editedProfile == null) { + throw 'Client profile of specified name not found'; + } + + $scope.readOnly = !$scope.access.manageRealm || globalProfile; + + $scope.executorTypes = serverInfo.componentTypes['org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider']; + + for (var j=0 ; j < $scope.executorTypes.length ; j++) { + var currExecutorType = $scope.executorTypes[j]; + if (currExecutorType.properties) { + console.log("Adjusting executorType: " + currExecutorType.id); + ComponentUtils.addMvOptionsToMultivaluedLists(currExecutorType.properties); + } + } + + function getExecutorByIndex(clientProfile, executorIndex) { + if (clientProfile.executors.length <= executorIndex) { + console.error('Client profile does not have executor of specified index'); + $location.path('/notfound'); + return null; + } else { + return clientProfile.executors[executorIndex]; + } + } + + if ($scope.createNew) { + // make first type the default + $scope.executorType = $scope.executorTypes[0]; + var oldExecutorType = $scope.executorType; + initConfig(); + + $scope.$watch('executorType', function() { + if (!angular.equals($scope.executorType, oldExecutorType)) { + oldExecutorType = $scope.executorType; + initConfig(); + } + }, true); + } else { + var exec = getExecutorByIndex(editedProfile, updatedExecutorIndex); + if (exec) { + $scope.executor = { + config: exec.configuration + }; + + $scope.executorType = null; + for (var j=0 ; j < $scope.executorTypes.length ; j++) { + var currentExType = $scope.executorTypes[j]; + if (exec.executor === currentExType.id) { + $scope.executorType = currentExType; + break; + } + } + } + + } + + function toDefaultValue(configProperty) { + if (configProperty.type === 'MultivaluedString' || configProperty.type === 'MultivaluedList') { + if (configProperty.defaultValue) { + return configProperty.defaultValue; + } else { + return []; + } + } + + if (configProperty.defaultValue !== undefined) { + return configProperty.defaultValue; + } else { + return null; + } + } + + function initConfig() { + console.log("Initialized config now. ConfigType is: " + $scope.executorType.id); + $scope.executor = { + config: {} + }; + + for (let i = 0; i < $scope.executorType.properties.length; i++) { + let configProperty = $scope.executorType.properties[i]; + $scope.executor.config[configProperty.name] = toDefaultValue(configProperty); + } + } + + $scope.save = function() { + console.log("save: " + $scope.executorType.id); + + var executorName = $scope.executorType.id; + if (!editedProfile.executors) { + editedProfile.executors = []; + } + + ComponentUtils.removeLastEmptyValue($scope.executor.config); + + if ($scope.createNew) { + var selectedExecutor = { + executor: $scope.executorType.id, + configuration: $scope.executor.config + }; + editedProfile.executors.push(selectedExecutor); + } else { + var currentExecutor = getExecutorByIndex(editedProfile, updatedExecutorIndex); + if (currentExecutor) { + currentExecutor.configuration = $scope.executor.config; + } + } + + ClientPoliciesProfiles.update({ + realm: realm.realm, + }, clientProfiles, function () { + if ($scope.createNew) { + Notifications.success("Executor created successfully"); + } else { + Notifications.success("Executor updated successfully"); + } + $location.url('/realms/' + realm.realm + '/client-policies/profiles-update/' + editedProfile.name); + }); + + }; + + $scope.cancel = function() { + $location.url('/realms/' + realm.realm + '/client-policies/profiles-update/' + editedProfile.name); + }; + +}); + +module.controller('ClientPoliciesListCtrl', function($scope, realm, clientPolicies, ClientPolicies, Dialog, Notifications, $route, $location) { + console.log('ClientPoliciesListCtrl'); + $scope.realm = realm; + $scope.clientPolicies = clientPolicies; + + $scope.removeClientPolicy = function(clientPolicy) { + console.log("Deleting client policy from the JSON: " + clientPolicy.name); + + for (var i=0 ; i < $scope.clientPolicies.policies.length ; i++) { + var currentPolicy = $scope.clientPolicies.policies[i]; + if (currentPolicy.name === clientPolicy.name) { + $scope.clientPolicies.policies.splice(i, 1); + break; + } + } + + ClientPolicies.update({ + realm: realm.realm, + }, $scope.clientPolicies, function () { + $route.reload(); + Notifications.success("The client policy was deleted."); + }, function(errorResponse) { + Notifications.error('Failed to delete client policy. Check server log for the details'); + }); + }; + +}); + +module.controller('ClientPoliciesJsonCtrl', function($scope, realm, clientPolicies, Dialog, Notifications, ClientPolicies, $route, $location) { + console.log('ClientPoliciesJsonCtrl'); + $scope.realm = realm; + $scope.clientPoliciesString = angular.toJson(clientPolicies, true); + + $scope.save = function() { + var clientPoliciesObj = null; + try { + var clientPoliciesObj = angular.fromJson($scope.clientPoliciesString); + } catch (e) { + Notifications.error("Provided JSON is incorrect. See browser javascript console for the details"); + console.log(e); + return; + } + var clientPoliciesCompressed = angular.toJson(clientPoliciesObj, false); + + ClientPolicies.update({ + realm: realm.realm, + }, clientPoliciesCompressed, function () { + $route.reload(); + Notifications.success("The client policies configuration was updated."); + }, function(errorResponse) { + Notifications.error('Failed to update client policies. Check browser javascript console and server log for the details'); + console.log("Error response when updating client policies JSON: Status: " + errorResponse.status + + ", statusText: " + errorResponse.statusText + ", data: " + errorResponse.data); + }); + }; + + $scope.reset = function() { + $route.reload(); + }; +}); + +module.controller('ClientPoliciesEditCtrl', function($scope, realm, clientProfiles, clientPolicies, ClientPolicies, Dialog, Notifications, $route, $location) { + var targetPolicyName = $route.current.params.policyName; + $scope.createNew = targetPolicyName == null; + if ($scope.createNew) { + console.log('ClientPoliciesEditCtrl: creating new policy'); + } else { + console.log('ClientPoliciesEditCtrl: updating policy ' + targetPolicyName); + } + + $scope.realm = realm; + $scope.clientPolicies = clientPolicies; + $scope.clientProfiles = clientProfiles; + $scope.editedPolicy = null; + + if ($scope.createNew) { + $scope.editedPolicy = { + name: "", + enabled: true, + profiles: [], + conditions: [] + }; + } else { + for (var i=0 ; i < $scope.clientPolicies.policies.length ; i++) { + var currentPolicy = $scope.clientPolicies.policies[i]; + if (targetPolicyName === currentPolicy.name) { + $scope.editedPolicy = currentPolicy; + break; + } + } + + if ($scope.editedPolicy == null) { + console.log("Policy of name " + targetPolicyName + " not found"); + throw 'Policy not found'; + } + } + + $scope.readOnly = !$scope.access.manageRealm; + + $scope.availableProfiles = []; + var allClientProfiles = clientProfiles.profiles; + if (clientProfiles.globalProfiles) { + allClientProfiles = allClientProfiles.concat(clientProfiles.globalProfiles); + } + for (var k=0 ; k + +
+ + + + + + +
+ + +
+
+
+ +
+
+
+ +
+
+ + +
+
+
+ +
+
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-list.html new file mode 100644 index 0000000000..b2f5a330d9 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-list.html @@ -0,0 +1,74 @@ + + +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+ +
{{:: 'name' | translate}}{{:: 'description' | translate}}{{:: 'enabled' | translate}}{{:: 'actions' | translate}}
{{clientPolicy.name}}{{clientPolicy.description}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
+
+ +
+
+ + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-policy-edit-condition.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-policy-edit-condition.html new file mode 100644 index 0000000000..6d05e6a369 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-policy-edit-condition.html @@ -0,0 +1,58 @@ + + +
+ +

{{conditionType.id|capitalize}}

+

{{:: 'create-condition' | translate}}

+ +
+
+
+ +
+
+ +
+
+ {{conditionType.helpText}} +
+
+ +
+ +
+ {{conditionType.helpText}} +
+ +
+ +
+
+ + +
+
+ +
+
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-policy-edit.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-policy-edit.html new file mode 100644 index 0000000000..6856a1645c --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-policy-edit.html @@ -0,0 +1,148 @@ + + +
+ + + + + + +
+ +
+ +
+ +
+ +
+ {{:: 'client-policy-name.tooltip' | translate}} +
+ +
+ +
+ +
+
+ +
+ +
+ +
+ {{:: 'client-policy-enabled.tooltip' | translate}} +
+ +
+ +
+ +
+
+ + +
+
+ +
+ +
+ + {{:: 'conditions' | translate}} {{:: 'client-policy-conditions.tooltip' | translate}} + + + + + + + + + + + + + + + + + + + + +
+ +
{{:: 'type' | translate}}{{:: 'actions' | translate}}
{{condition.condition}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
{{:: 'no-conditions-available' | translate}}
+ +
+ +
+ {{:: 'client-profiles' | translate}}{{:: 'client-profiles.tooltip' | translate}} + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
{{:: 'name' | translate}}{{:: 'actions' | translate}}
{{profileName}}{{:: 'delete' | translate}}
{{:: 'no-client-profiles-configured' | translate}}
+
+ +
+
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-edit-executor.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-edit-executor.html new file mode 100644 index 0000000000..aa86863585 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-edit-executor.html @@ -0,0 +1,58 @@ + + +
+ +

{{executorType.id|capitalize}}

+

{{:: 'create-executor' | translate}}

+ +
+
+
+ +
+
+ +
+
+ {{executorType.helpText}} +
+
+ +
+ +
+ {{executorType.helpText}} +
+ +
+ +
+
+ + +
+
+ +
+
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-edit.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-edit.html new file mode 100644 index 0000000000..d150a7e433 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-edit.html @@ -0,0 +1,105 @@ + + +
+ + + + + + +
+ +
+ +
+ +
+ +
+ {{:: 'client-profile-name.tooltip' | translate}} +
+ +
+ +
+ +
+
+
+ +
+ +
+
+ + +
+
+ +
+ +
+ + {{:: 'executors' | translate}} {{:: 'client-profile-executors.tooltip' | translate}} + + + + + + + + + + + + + + + + + + + + +
+ +
{{:: 'type' | translate}}{{:: 'actions' | translate}}
{{executor.executor}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
{{:: 'no-executors-available' | translate}}
+ +
+ +
+
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-json.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-json.html new file mode 100644 index 0000000000..e2fae4aea0 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-json.html @@ -0,0 +1,60 @@ + + +
+ + + + + + +
+ + +
+
+
+ +
+
+
+ +
+
+ + +
+
+
+ +
+
+ + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-list.html new file mode 100644 index 0000000000..d29275e60e --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-policies-profiles-list.html @@ -0,0 +1,81 @@ + + +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
{{:: 'name' | translate}}{{:: 'description' | translate}}{{:: 'global' | translate}}{{:: 'actions' | translate}}
{{clientProfile.name}}{{clientProfile.description}}{{:: 'true' | translate}}
{{clientProfile.name}}{{clientProfile.description}}{{:: 'false' | translate}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
+
+ +
+
+ + diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html index cfa7f4f14d..56bde58dc6 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html @@ -26,6 +26,7 @@ || path[2] == 'theme-settings' || path[2] == 'localization' || path[2] == 'token-settings' + || path[2] == 'client-policies' || path[2] == 'client-registration' || path[2] == 'cache-settings' || path[2] == 'client-initial-access' diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-provider-config.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-provider-config.html index 606964182d..70225bcec5 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-provider-config.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-provider-config.html @@ -16,6 +16,9 @@ +
+ +
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html index 22b66ce960..2e449f305e 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html @@ -15,6 +15,9 @@
  • {{:: 'realm-tab-cache' | translate}}
  • {{:: 'realm-tab-tokens' | translate}}
  • {{:: 'realm-tab-client-registration' | translate}}
  • +
  • + {{:: 'realm-tab-client-policies' | translate}} +
  • {{:: 'realm-tab-security-defenses' | translate}}
  • \ No newline at end of file