KEYCLOAK-14209 Client policies admin console support. Changing of format of JSON for client policies and profiles. Remove support for default policies (#7969)

* KEYCLOAK-14209 KEYCLOAK-17988 Client policies admin console support. Changing of format of JSON for client policies and profiles. Refactoring based on feedback and remove builtin policies
This commit is contained in:
Marek Posolda 2021-05-12 16:19:55 +02:00 committed by GitHub
parent f37a24dd91
commit a6d4316084
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
99 changed files with 3267 additions and 1993 deletions

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class ClientPoliciesRepresentation {
protected List<ClientPolicyRepresentation> policies;
protected List<ClientPolicyRepresentation> policies = new ArrayList<>();
public List<ClientPolicyRepresentation> 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);
}
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientPolicyConditionConfigurationRepresentation {
private Map<String, Object> 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<String, Object> getConfigAsMap() {
return configAsMap;
}
@JsonAnySetter
public void setConfigAsMap(String name, Object value) {
this.configAsMap.put(name, value);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientPolicyExecutorConfigurationRepresentation {
private Map<String, Object> configAsMap = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> getConfigAsMap() {
return configAsMap;
}
@JsonAnySetter
public void setConfigAsMap(String name, Object value) {
this.configAsMap.put(name, value);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class ClientPolicyRepresentation {
protected String name;
protected String description;
protected Boolean builtin;
protected Boolean enable;
protected List<Object> conditions;
protected Boolean enabled;
protected List<ClientPolicyConditionRepresentation> conditions;
protected List<String> 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<Object> getConditions() {
public List<ClientPolicyConditionRepresentation> getConditions() {
return conditions;
}
public void setConditions(List<Object> conditions) {
public void setConditions(List<ClientPolicyConditionRepresentation> conditions) {
this.conditions = conditions;
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class ClientProfileRepresentation {
protected String name;
protected String description;
protected Boolean builtin;
protected List<Object> executors;
protected List<ClientPolicyExecutorRepresentation> 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<Object> getExecutors() {
public List<ClientPolicyExecutorRepresentation> getExecutors() {
return executors;
}
public void setExecutors(List<Object> executors) {
public void setExecutors(List<ClientPolicyExecutorRepresentation> executors) {
this.executors = executors;
}
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class ClientProfilesRepresentation {
protected List<ClientProfileRepresentation> profiles;
private List<ClientProfileRepresentation> profiles = new ArrayList<>();
// Global profiles, which are builtin in Keycloak.
@JsonProperty("globalProfiles")
private List<ClientProfileRepresentation> globalProfiles;
public List<ClientProfileRepresentation> getProfiles() {
return profiles;
@ -38,4 +45,24 @@ public class ClientProfilesRepresentation {
this.profiles = profiles;
}
public List<ClientProfileRepresentation> getGlobalProfiles() {
return globalProfiles;
}
public void setGlobalProfiles(List<ClientProfileRepresentation> 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);
}
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
@ -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);
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
@ -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);
}

View file

@ -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());
}

View file

@ -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";
}

View file

@ -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) {

View file

@ -1181,6 +1181,7 @@ public class RepresentationToModel {
realm.setWebAuthnPolicyPasswordless(webAuthnPolicy);
updateCibaSettings(rep, realm);
session.clientPolicy().updateRealmModelFromRepresentation(realm, rep);
if (rep.getSmtpServer() != null) {
Map<String, String> config = new HashMap(rep.getSmtpServer());

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
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<String, ClientProfileModel> 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<String, ClientProfileModel> profileMap = new HashMap<>();
for (ClientProfileRepresentation profileRep : profilesRep.getProfiles()) {
// ignore profile without name
if (profileRep.getName() == null) {
continue;
}
ClientProfileModel profileModel = new ClientProfileModel();
profileModel.setName(profileRep.getName());
profileModel.setDescription(profileRep.getDescription());
if (profileRep.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<Object> 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<ClientProfileRepresentation> 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<ClientProfileRepresentation> 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<ClientProfileRepresentation> 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<ClientProfileRepresentation> updatingProfileList = updatingProfilesRep.getProfiles();
// add existing builtin profiles to updating profiles
List<ClientProfileRepresentation> 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<String> 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<ClientPolicyModel> 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<ClientPolicyModel> 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<Object> 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<ClientPolicyRepresentation> 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<ClientPolicyRepresentation> 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<String> 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<ClientPolicyRepresentation> 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<ClientPolicyRepresentation> updatingPoliciesList = updatingPoliciesRep.getPolicies();
// add existing builtin policies to updating policies
List<ClientPolicyRepresentation> 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<String> 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<String> 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<String> 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<Entry<String, JsonNode>> it = node.fields();
while (it.hasNext()) {
Entry<String, JsonNode> 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> T convertJsonToRepresentation(String json, Class<T> 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;
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class ClientPolicyExecutorConfiguration {
public interface ClientPolicyManagerFactory extends ProviderFactory<ClientPolicyManager> {
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientPolicyManagerSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "client-policy-manager";
}
@Override
public Class<? extends Provider> getProviderClass() {
return ClientPolicyManager.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return ClientPolicyManagerFactory.class;
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractClientPolicyConditionProvider<CONFIG extends ClientPolicyConditionConfigurationRepresentation> implements ClientPolicyConditionProvider<CONFIG> {
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();
}
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class ClientPolicyConditionConfiguration {
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public interface ClientPolicyConditionProvider<CONFIG extends ClientPolicyConditionConfiguration> extends Provider {
public interface ClientPolicyConditionProvider<CONFIG extends ClientPolicyConditionConfigurationRepresentation> extends Provider {
@Override
default void close() {
@ -42,14 +43,13 @@ public interface ClientPolicyConditionProvider<CONFIG extends ClientPolicyCondit
*
* @param config
*/
default void setupConfiguration(CONFIG config) {
}
void setupConfiguration(CONFIG config);
/**
* @return Class, which should match the "config" argument of the {@link #setupConfiguration(ClientPolicyConditionConfiguration)}
* @return Class, which should match the "config" argument of the {@link #setupConfiguration(ClientPolicyConditionConfigurationRepresentation)}
*/
default Class<CONFIG> getConditionConfigurationClass() {
return (Class<CONFIG>) ClientPolicyConditionConfiguration.class;
return (Class<CONFIG>) ClientPolicyConditionConfigurationRepresentation.class;
}
/**
@ -73,9 +73,7 @@ public interface ClientPolicyConditionProvider<CONFIG extends ClientPolicyCondit
*
* @return true if the result of applyPolicy method is inverted.
*/
default boolean isNegativeLogic() {
return false;
}
boolean isNegativeLogic() throws ClientPolicyException;
default String getName() {
return getClass().toString();

View file

@ -17,11 +17,18 @@
package org.keycloak.services.clientpolicy.condition;
import org.keycloak.common.Profile;
import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public interface ClientPolicyConditionProviderFactory extends ProviderFactory<ClientPolicyConditionProvider>, ConfiguredProvider {
public interface ClientPolicyConditionProviderFactory extends ProviderFactory<ClientPolicyConditionProvider>, ConfiguredProvider, EnvironmentDependentProviderFactory {
@Override
default boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.CLIENT_POLICIES);
}
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public interface ClientPolicyExecutorProvider<CONFIG extends ClientPolicyExecutorConfiguration> extends Provider {
public interface ClientPolicyExecutorProvider<CONFIG extends ClientPolicyExecutorConfigurationRepresentation> extends Provider {
@Override
default void close() {
@ -45,10 +46,10 @@ public interface ClientPolicyExecutorProvider<CONFIG extends ClientPolicyExecuto
}
/**
* @return Class, which should match the "config" argument of the {@link #setupConfiguration(ClientPolicyExecutorConfiguration)}
* @return Class, which should match the "config" argument of the {@link #setupConfiguration(ClientPolicyExecutorConfigurationRepresentation)}
*/
default Class<CONFIG> getExecutorConfigurationClass() {
return (Class<CONFIG>) ClientPolicyExecutorConfiguration.class;
return (Class<CONFIG>) ClientPolicyExecutorConfigurationRepresentation.class;
}
/**

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public interface ClientPolicyExecutorProviderFactory extends ProviderFactory<ClientPolicyExecutorProvider>, ConfiguredProvider {
public interface ClientPolicyExecutorProviderFactory extends ProviderFactory<ClientPolicyExecutorProvider>, ConfiguredProvider, EnvironmentDependentProviderFactory {
@Override
default boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.CLIENT_POLICIES);
}
}

View file

@ -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

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public interface ClientPolicyManager {
public interface ClientPolicyManager extends Provider {
/**
* execute a method for handling an event defined in {@link ClientPolicyEvent}.
@ -37,18 +40,7 @@ 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.
* 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.
* 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 realm - the newly created realm
@ -56,34 +48,35 @@ public interface ClientPolicyManager {
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
@ -91,10 +84,10 @@ public interface ClientPolicyManager {
* 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);
}

View file

@ -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<String, ComponentExportRepresentation> components = exportComponents(realm, realm.getId());
rep.setComponents(components);
// client policies
session.clientPolicy().setupClientPoliciesOnExportingRealm(realm, rep);
return rep;
}

View file

@ -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;
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
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<String, ClientProfileModel> getClientProfilesModel(KeycloakSession session, RealmModel realm, List<ClientProfileRepresentation> globalClientProfiles) {
// get existing profiles as json
String profilesJson = getClientProfilesJsonString(realm);
if (profilesJson == null) {
return Collections.emptyMap();
}
// deserialize existing profiles (json -> representation)
ClientProfilesRepresentation profilesRep = null;
try {
profilesRep = convertClientProfilesJsonToRepresentation(profilesJson);
} catch (ClientPolicyException e) {
logger.warnv("Failed to serialize client profiles json string. err={0}, errDetail={1}", e.getError(), e.getErrorDetail());
return Collections.emptyMap();
}
if (profilesRep == null || profilesRep.getProfiles() == null) {
return Collections.emptyMap();
}
List<ClientProfileRepresentation> profiles = profilesRep.getProfiles();
// Add global profiles as well
profiles.addAll(globalClientProfiles);
// constructing existing profiles (representation -> model)
Map<String, ClientProfileModel> profileMap = new HashMap<>();
for (ClientProfileRepresentation profileRep : profilesRep.getProfiles()) {
// ignore profile without name
if (profileRep.getName() == null) {
continue;
}
ClientProfileModel profileModel = new ClientProfileModel();
profileModel.setName(profileRep.getName());
profileModel.setDescription(profileRep.getDescription());
if (profileRep.getExecutors() == null) {
profileModel.setExecutors(new ArrayList<>());
profileMap.put(profileRep.getName(), profileModel);
continue;
}
List<ClientPolicyExecutorProvider> executors = new ArrayList<>();
if (profileRep.getExecutors() != null) {
for (ClientPolicyExecutorRepresentation executorRep : profileRep.getExecutors()) {
ClientPolicyExecutorProvider provider = session.getProvider(ClientPolicyExecutorProvider.class, executorRep.getExecutorProviderId());
if (provider == null) {
// executor's provider not found. just skip it.
logger.warnf("Executor with provider ID %s not found", executorRep.getExecutorProviderId());
continue;
}
try {
ClientPolicyExecutorConfigurationRepresentation configuration = (ClientPolicyExecutorConfigurationRepresentation) JsonSerialization.mapper.convertValue(executorRep.getConfiguration(), provider.getExecutorConfigurationClass());
provider.setupConfiguration(configuration);
executors.add(provider);
} catch (IllegalArgumentException iae) {
logger.warnv("failed for Configuration Setup during setup provider {0} :: error = {1}", executorRep.getExecutorProviderId(), iae.getMessage());
}
}
}
profileModel.setExecutors(executors);
profileMap.put(profileRep.getName(), profileModel);
}
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<ClientProfileRepresentation> 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<ClientProfileRepresentation> 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<ClientProfileRepresentation> 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<ClientProfileRepresentation> globalClientProfiles) throws ClientPolicyException {
if (realm == null) {
throw new ClientPolicyException("realm not specified.");
}
// no profile contained (it is valid)
List<ClientProfileRepresentation> 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<String> 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<String> 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<ClientPolicyModel> 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<ClientPolicyModel> 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<ClientPolicyConditionProvider> conditions = new ArrayList<>();
if (policyRep.getConditions() != null) {
for (ClientPolicyConditionRepresentation conditionRep : policyRep.getConditions()) {
ClientPolicyConditionProvider provider = session.getProvider(ClientPolicyConditionProvider.class, conditionRep.getConditionProviderId());
if (provider == null) {
// condition's provider not found. just skip it.
logger.warnf("Condition with provider ID %s not found", conditionRep.getConditionProviderId());
continue;
}
try {
ClientPolicyConditionConfigurationRepresentation configuration = (ClientPolicyConditionConfigurationRepresentation) JsonSerialization.mapper.convertValue(conditionRep.getConfiguration(), provider.getConditionConfigurationClass());
provider.setupConfiguration(configuration);
conditions.add(provider);
} catch (IllegalArgumentException iae) {
logger.warnv("failed for Configuration Setup :: error = {0}", iae.getMessage());
}
}
}
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<ClientProfileRepresentation> existingGlobalProfiles) throws ClientPolicyException {
if (realm == null) {
throw new ClientPolicyException("realm not specified.");
}
// no policy contained (it is valid)
List<ClientPolicyRepresentation> 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<ClientPolicyRepresentation> 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<String> 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<String> 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);
}
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
@ -27,9 +29,8 @@ public class ClientPolicyModel implements Serializable {
protected String name;
protected String description;
protected boolean builtin;
protected boolean enable;
protected List<Object> conditions; // ClientPolicyConditionProvider is not visible so that use Object.
protected List<ClientPolicyConditionProvider> conditions;
protected List<String> 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<Object> getConditions() {
public List<ClientPolicyConditionProvider> getConditions() {
return conditions;
}
public void setConditions(List<Object> conditions) {
public void setConditions(List<ClientPolicyConditionProvider> conditions) {
this.conditions = conditions;
}

View file

@ -13,6 +13,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.services.clientpolicy;
@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
@ -27,8 +30,7 @@ public class ClientProfileModel implements Serializable {
protected String name;
protected String description;
protected boolean builtin;
protected List<Object> executors; // ClientPolicyExecutorProvider is not visible so that use Object.
protected List<ClientPolicyExecutorProvider> 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<Object> getExecutors() {
public List<ClientPolicyExecutorProvider> getExecutors() {
return executors;
}
public void setExecutors(List<Object> executors) {
public void setExecutors(List<ClientPolicyExecutorProvider> executors) {
this.executors = executors;
}
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
@ -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<List<ClientProfileRepresentation>> globalClientProfilesSupplier;
public DefaultClientPolicyManager(KeycloakSession session) {
public DefaultClientPolicyManager(KeycloakSession session, Supplier<List<ClientProfileRepresentation>> 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<String, ClientProfileModel> map = ClientPoliciesUtil.getClientProfilesModel(session, realm);
List<ClientPolicyModel> list = ClientPoliciesUtil.getEnabledClientProfilesModel(session, realm).stream().collect(Collectors.toList());
Map<String, ClientProfileModel> map = ClientPoliciesUtil.getClientProfilesModel(session, realm, globalClientProfilesSupplier.get());
List<ClientPolicyModel> 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<ClientProfileRepresentation> 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() {
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<ClientProfileRepresentation> 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<ClientProfileRepresentation> 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;
}
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class AnyClientCondition implements ClientPolicyConditionProvider<AnyClientCondition.Configuration> {
// 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<ClientPolicyConditionConfigurationRepresentation> {
public AnyClientCondition(KeycloakSession session) {
super(session);
}
@Override
public void setupConfiguration(Configuration config) {
this.configuration = config;
}
@Override
public Class<Configuration> 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<ClientPolicyConditionConfigurationRepresentation> getConditionConfigurationClass() {
return ClientPolicyConditionConfigurationRepresentation.class;
}
@Override

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientAccessTypeCondition implements ClientPolicyConditionProvider<ClientAccessTypeCondition.Configuration> {
public class ClientAccessTypeCondition extends AbstractClientPolicyConditionProvider<ClientAccessTypeCondition.Configuration> {
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<String> 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;

View file

@ -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

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientRolesCondition implements ClientPolicyConditionProvider<ClientRolesCondition.Configuration> {
public class ClientRolesCondition extends AbstractClientPolicyConditionProvider<ClientRolesCondition.Configuration> {
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<Clien
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<String> roles;
@ -83,11 +60,6 @@ public class ClientRolesCondition implements ClientPolicyConditionProvider<Clien
}
}
@Override
public boolean isNegativeLogic() {
return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue();
}
@Override
public String getProviderId() {
return ClientRolesConditionFactory.PROVIDER_ID;

View file

@ -66,7 +66,7 @@ public class ClientRolesConditionFactory implements ClientPolicyConditionProvide
@Override
public String getHelpText() {
return "The condition checks whether one of the specified client roles is applied to the client to determine whether the policy is applied.";
return "The condition checks whether one of the specified client roles exists on the client to determine whether the policy is applied. This effectively allows client administrator to create client role of specified name on the client to make sure that particular client policy will be applied on requests of this client. Condition is checked during most of OpenID Connect requests (Authorization request, token requests, introspection endpoint request etc).";
}
@Override

View file

@ -21,7 +21,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.jboss.logging.Logger;
@ -30,33 +29,22 @@ import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
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 org.keycloak.services.clientpolicy.context.AuthorizationRequestContext;
import org.keycloak.services.clientpolicy.context.TokenRequestContext;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientScopesCondition implements ClientPolicyConditionProvider<ClientScopesCondition.Configuration> {
public class ClientScopesCondition extends AbstractClientPolicyConditionProvider<ClientScopesCondition.Configuration> {
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<Clie
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 String type;
protected List<String> scope;
@ -97,11 +74,6 @@ public class ClientScopesCondition implements ClientPolicyConditionProvider<Clie
}
}
@Override
public boolean isNegativeLogic() {
return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue();
}
@Override
public String getProviderId() {
return ClientScopesConditionFactory.PROVIDER_ID;

View file

@ -18,9 +18,11 @@
package org.keycloak.services.clientpolicy.condition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.OAuth2Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
@ -40,10 +42,13 @@ public class ClientScopesConditionFactory implements ClientPolicyConditionProvid
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
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

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientUpdateContextCondition implements ClientPolicyConditionProvider<ClientUpdateContextCondition.Configuration> {
public class ClientUpdateContextCondition extends AbstractClientPolicyConditionProvider<ClientUpdateContextCondition.Configuration> {
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<String> 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;

View file

@ -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<String> updateProfileValues = Arrays.asList(BY_AUTHENTICATED_USER, BY_ANONYMOUS, BY_INITIAL_ACCESS_TOKEN, BY_REGISTRATION_ACCESS_TOKEN);
property.setOptions(updateProfileValues);
configProperties.add(property);

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientUpdateSourceGroupsCondition implements ClientPolicyConditionProvider<ClientUpdateSourceGroupsCondition.Configuration> {
public class ClientUpdateSourceGroupsCondition extends AbstractClientPolicyConditionProvider<ClientUpdateSourceGroupsCondition.Configuration> {
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<String> 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;

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientUpdateSourceHostsCondition implements ClientPolicyConditionProvider<ClientUpdateSourceHostsCondition.Configuration> {
public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyConditionProvider<ClientUpdateSourceHostsCondition.Configuration> {
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<String> 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;

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientUpdateSourceRolesCondition implements ClientPolicyConditionProvider<ClientUpdateSourceRolesCondition.Configuration> {
public class ClientUpdateSourceRolesCondition extends AbstractClientPolicyConditionProvider<ClientUpdateSourceRolesCondition.Configuration> {
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<String> 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<String> expectedRoles = instantiateRolesForMatching();
if (expectedRoles == null) return false;
// user.getRoleMappingsStream() never returns null according to {@link UserModel.getRoleMappingsStream}
Set<String> roles = user.getRoleMappingsStream().map(RoleModel::getName).collect(Collectors.toSet());
if (logger.isTraceEnabled()) {
// user.getRoleMappingsStream() never returns null according to {@link UserModel.getRoleMappingsStream}
Set<String> 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<String> instantiateRolesForMatching() {

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ConfidentialClientAcceptExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
public class ConfidentialClientAcceptExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfigurationRepresentation> {
protected final KeycloakSession session;

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ConsentRequiredExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
public class ConsentRequiredExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfigurationRepresentation> {
@Override
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {

View file

@ -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;

View file

@ -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) {

View file

@ -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<PKCEEnf
return Configuration.class;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Configuration extends ClientPolicyExecutorConfiguration {
public static class Configuration extends ClientPolicyExecutorConfigurationRepresentation {
@JsonProperty("is-augment")
protected Boolean augment;

View file

@ -36,7 +36,7 @@ public class PKCEEnforceExecutorFactory implements ClientPolicyExecutorProviderF
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 enforce usage of PKCE", ProviderConfigProperty.BOOLEAN_TYPE, false);
@Override
public ClientPolicyExecutorProvider create(KeycloakSession session) {

View file

@ -21,12 +21,12 @@ import java.util.List;
import org.keycloak.OAuthErrorException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.context.ClientCRUDContext;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
@ -51,8 +51,7 @@ public class SecureClientAuthEnforceExecutor implements ClientPolicyExecutorProv
return Configuration.class;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Configuration extends ClientPolicyExecutorConfiguration {
public static class Configuration extends ClientPolicyExecutorConfigurationRepresentation {
@JsonProperty("client-authns")
protected List<String> clientAuthns;
@JsonProperty("client-authns-augment")

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
@ -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<ProviderConfigProperty> 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<String> 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<ProviderConfigProperty> getConfigProperties() {
return new ArrayList<>(Arrays.asList(IS_AUGMENT_PROPERTY, CLIENTAUTHNS_PROPERTY, CLIENTAUTHNS_AUGMENT));
return configProperties;
}
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureClientRegisteringUriEnforceExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
public class SecureClientRegisteringUriEnforceExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfigurationRepresentation> {
private static final Logger logger = Logger.getLogger(SecureClientRegisteringUriEnforceExecutor.class);

View file

@ -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")

View file

@ -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<ProviderConfigProperty> getConfigProperties() {
return new ArrayList<>(Arrays.asList(VERIFY_NBF_PROPERTY));
return new ArrayList<>(Arrays.asList(VERIFY_NBF_PROPERTY, AVAILABLE_PERIOD_PROPERTY));
}
}

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureResponseTypeExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
public class SecureResponseTypeExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfigurationRepresentation> {
private static final Logger logger = Logger.getLogger(SecureResponseTypeExecutor.class);

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureSessionEnforceExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
public class SecureSessionEnforceExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfigurationRepresentation> {
private static final Logger logger = Logger.getLogger(SecureSessionEnforceExecutor.class);

View file

@ -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<String> 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);
}
}

View file

@ -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

View file

@ -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<SecureSigningAlgorithmForSignedJwtEnforceExecutor.Configuration> {
@ -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;

View file

@ -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) {

View file

@ -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

View file

@ -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);

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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;

View file

@ -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());

View file

@ -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

View file

@ -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"
]
}
]
}

View file

@ -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": {}
}
]
}

View file

@ -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

View file

@ -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 <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class TestRaiseExeptionCondition implements ClientPolicyConditionProvider<TestRaiseExeptionCondition.Configuration> {
// 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<TestRaiseExeptionCondition.Configuration> {
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

View file

@ -66,4 +66,8 @@ public class TestRaiseExeptionConditionFactory implements ClientPolicyConditionP
return Collections.emptyList();
}
@Override
public boolean isSupported() {
return true;
}
}

View file

@ -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<ClientPolicyExecutorConfiguration> {
public class TestRaiseExeptionExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfigurationRepresentation> {
private static final Logger logger = Logger.getLogger(TestRaiseExeptionExecutor.class);

View file

@ -63,4 +63,8 @@ public class TestRaiseExeptionExecutorFactory implements ClientPolicyExecutorPro
return Collections.emptyList();
}
@Override
public boolean isSupported() {
return true;
}
}

View file

@ -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,26 +240,25 @@ 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,
@ -254,6 +267,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
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<ClientProfilesRepresentation> 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<Object> 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<String> clientAuthns, String clientAuthnsAugment) {
protected SecureClientAuthEnforceExecutor.Configuration createSecureClientAuthEnforceExecutorConfig(Boolean isAugment, List<String> 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<Object> conditions, List<String> 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<String> types) {
protected ClientAccessTypeCondition.Configuration createClientAccessTypeConditionConfig(List<String> types) {
ClientAccessTypeCondition.Configuration config = new ClientAccessTypeCondition.Configuration();
config.setType(types);
return config;
}
protected Object createClientRolesConditionConfig(List<String> roles) {
protected ClientRolesCondition.Configuration createClientRolesConditionConfig(List<String> roles) {
ClientRolesCondition.Configuration config = new ClientRolesCondition.Configuration();
config.setRoles(roles);
return config;
}
protected Object createClientScopesConditionConfig(String type, List<String> scopes) {
protected ClientScopesCondition.Configuration createClientScopesConditionConfig(String type, List<String> scopes) {
ClientScopesCondition.Configuration config = new ClientScopesCondition.Configuration();
config.setType(type);
config.setScope(scopes);
return config;
}
protected Object createClientUpdateContextConditionConfig(List<String> updateClientSource) {
protected ClientUpdateContextCondition.Configuration createClientUpdateContextConditionConfig(List<String> updateClientSource) {
ClientUpdateContextCondition.Configuration config = new ClientUpdateContextCondition.Configuration();
config.setUpdateClientSource(updateClientSource);
return config;
}
protected Object createClientUpdateSourceGroupsConditionConfig(List<String> groups) {
protected ClientUpdateSourceGroupsCondition.Configuration createClientUpdateSourceGroupsConditionConfig(List<String> groups) {
ClientUpdateSourceGroupsCondition.Configuration config = new ClientUpdateSourceGroupsCondition.Configuration();
config.setGroups(groups);
return config;
}
protected Object createClientUpdateSourceHostsConditionConfig(List<String> trustedHosts) {
protected ClientUpdateSourceHostsCondition.Configuration createClientUpdateSourceHostsConditionConfig(List<String> trustedHosts) {
ClientUpdateSourceHostsCondition.Configuration config = new ClientUpdateSourceHostsCondition.Configuration();
config.setTrustedHosts(trustedHosts);
return config;
}
protected Object createClientUpdateSourceRolesConditionConfig(List<String> roles) {
protected ClientUpdateSourceRolesCondition.Configuration createClientUpdateSourceRolesConditionConfig(List<String> 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<ClientProfilesRepresentation, List<ClientProfileRepresentation>> profilesListGetter = global ? ClientProfilesRepresentation::getGlobalProfiles : ClientProfilesRepresentation::getProfiles;
return getCompoundRepresentation(profilesRep, name, profilesListGetter, (ClientProfileRepresentation i)->i.getName());
}
protected void assertExpectedProfiles(ClientProfilesRepresentation profilesRep, List<String> expectedProfiles) {
assertExpetedCompounds(expectedProfiles, profilesRep, (ClientProfilesRepresentation i)->i.getProfiles(), (ClientProfileRepresentation i)->i.getName());
protected void assertExpectedProfiles(ClientProfilesRepresentation profilesRep, List<String> expectedGlobalProfiles, List<String> 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<String> expectedExecutors, ClientProfileRepresentation profileRep) {
assertExpetedElement(expectedExecutors, profileRep, (ClientProfileRepresentation i)->i.getExecutors());
List<String> 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<String> clientAuthns, boolean isAugment, String clientAuthnsAugment, ClientProfileRepresentation profileRep) {
JsonNode actualExecutorConfig = assertExpectedAugmenedExecutor(isAugment, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, profileRep);
assertExpectedAugmenedExecutor(isAugment, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, profileRep);
assertNotNull(profileRep);
Map<String, Object> actualExecutorConfig = getConfigOfExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, profileRep);
assertNotNull(actualExecutorConfig);
Set<String> actualClientAuthns = new HashSet<>();
if (actualExecutorConfig.findValue("client-authns") != null) actualExecutorConfig.findValue("client-authns").elements().forEachRemaining(i->actualClientAuthns.add(i.asText()));
Set<String> actualClientAuthns = new HashSet<>((Collection<String>) 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<String, Object> 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<String, Object> 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<String> profiles, ClientPolicyRepresentation actualPolicyRep) {
protected void assertExpectedPolicy(String name, String description, boolean isEnabled, List<String> 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<String> expectedConditions, ClientPolicyRepresentation policyRep) {
assertExpetedElement(expectedConditions, policyRep, (ClientPolicyRepresentation i)->i.getConditions());
List<String> 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<String> type, ClientPolicyRepresentation policyRep) {
JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientAccessTypeConditionFactory.PROVIDER_ID);
Set<String> 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<String> roles, ClientPolicyRepresentation policyRep) {
JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientRolesConditionFactory.PROVIDER_ID);
Set<String> 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<String> 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<String> 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<String> updateClientSources, ClientPolicyRepresentation policyRep) {
JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientUpdateContextConditionFactory.PROVIDER_ID);
Set<String> 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<String> groups, ClientPolicyRepresentation policyRep) {
JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID);
Set<String> 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<String> trustedHosts, List<Boolean> hostSendingRequestMustMatch, ClientPolicyRepresentation policyRep) {
JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientUpdateSourceHostsConditionFactory.PROVIDER_ID);
List<String> actualTrustedHosts = new ArrayList<>();
if (actualConditionConfig.findValue("trusted-hosts") != null)
actualConditionConfig.findValue("trusted-hosts").elements().forEachRemaining(i->actualTrustedHosts.add(i.asText()));
assertEquals(trustedHosts, actualTrustedHosts);
List<Boolean> 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<String> trustedHosts, ClientPolicyRepresentation policyRep) {
ClientUpdateSourceHostsCondition.Configuration cfg = getConfigAsExpectedType(policyRep, ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, ClientUpdateSourceHostsCondition.Configuration.class);
Assert.assertEquals(cfg.getTrustedHosts(), trustedHosts);
}
protected void assertExpectedClientUpdateSourceRolesCondition(List<String> 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<String> 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 extends ClientPolicyConditionConfigurationRepresentation> CFG getConfigAsExpectedType(ClientPolicyRepresentation policyRep, String conditionProviderId, Class<CFG> 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 <T, R> void assertExpetedCompounds(List<String> expected, R rep, Function<R, List<T>> f, Function<T, String> g) {
private <T, R> void assertExpectedCompounds(List<String> expected, R rep, Function<R, List<T>> f, Function<T, String> g) {
assertNotNull(rep);
List<T> 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 <T> void assertExpetedElement(List<String> expected, T rep, Function<T, List<Object>> f) {
assertNotNull(rep);
List<Object> objs = f.apply(rep);
if (objs == null) {
assertNull(expected);
return;
}
Set<String> 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 <T> void assertExpectedNoConfigElement(String providerId, T rep, Function<T, List<Object>> f) {
assertNotNull(rep);
JsonNode actualConfig = getConfig(f.apply(rep), providerId);
assertEquals("", actualConfig.asText());
}
private JsonNode getConfig(List<Object> objs, String providerId) {
List<JsonNode> 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<String, Object> config = getConfigOfExecutor(executorProviderId, profileRep);
Assert.assertTrue("Expected empty configuration for provider " + executorProviderId, config.isEmpty());
}
}

View file

@ -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);
});
}

View file

@ -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());
}
}

View file

@ -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,7 +650,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
// register policies
json = (new ClientPoliciesBuilder()).addPolicy(
(new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prvni Politika", Boolean.FALSE, Boolean.TRUE, null, null)
(new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prvni Politika", Boolean.TRUE)
.addCondition(ClientUpdateSourceHostsConditionFactory.PROVIDER_ID,
createClientUpdateSourceHostsConditionConfig(Arrays.asList("localhost", "127.0.0.1")))
.addProfile(PROFILE_NAME)
@ -671,7 +671,7 @@ 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)
(new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Aktualizovana Prvni Politika", Boolean.TRUE)
.addCondition(ClientUpdateSourceHostsConditionFactory.PROVIDER_ID,
createClientUpdateSourceHostsConditionConfig(Arrays.asList("example.com")))
.addProfile(PROFILE_NAME)
@ -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,

View file

@ -16,7 +16,7 @@ public class InternalComponentRepresentation implements FetchOnServerWrapper<Com
@Override
public FetchOnServer getRunOnServer() {
return (FetchOnServer) session -> ModelToRepresentation.toRepresentation(session.getContext().getRealm(), true);
return (FetchOnServer) session -> ModelToRepresentation.toRepresentation(session, session.getContext().getRealm(), true);
}
@Override

View file

@ -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());

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 : {

View file

@ -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<allClientProfiles.length ; k++) {
var profileName = allClientProfiles[k].name;
if (!$scope.editedPolicy.profiles || !$scope.editedPolicy.profiles.includes(profileName)) {
$scope.availableProfiles.push(profileName);
}
}
$scope.removeCondition = function(conditionIndex) {
console.log("remove condition of index " + conditionIndex);
// Delete condition
$scope.editedPolicy.conditions.splice(conditionIndex, 1);
ClientPolicies.update({
realm: realm.realm,
}, $scope.clientPolicies, function () {
Notifications.success("The condition was deleted.");
}, function(errorResponse) {
Notifications.error('Failed to delete condition. Check server log for the details');
});
}
$scope.save = function() {
if (!$scope.editedPolicy.name || $scope.editedPolicy.name === '') {
Notifications.error('Name must be provided');
return;
}
if ($scope.createNew) {
$scope.clientPolicies.policies.push($scope.editedPolicy);
}
ClientPolicies.update({
realm: realm.realm,
}, $scope.clientPolicies, function () {
if ($scope.createNew) {
Notifications.success("The client policy was created.");
$location.url('/realms/' + realm.realm + '/client-policies/policies-update/' + $scope.editedPolicy.name);
} else {
Notifications.success("The client policy was updated.");
$location.url('/realms/' + realm.realm + '/client-policies/policies');
}
}, function(errorResponse) {
if ($scope.createNew) {
Notifications.error('Failed to create client policy. Check server log for the details');
} else {
Notifications.error('Failed to update client policy. Check server log for the details');
}
});
};
$scope.back = function() {
$location.url('/realms/' + realm.realm + '/client-policies/policies');
};
function moveProfileAndUpdatePolicy(arrayFrom, arrayTo, profileName, notificationsMessage) {
for (var i=0 ; i<arrayFrom.length ; i++) {
if (arrayFrom[i] === profileName) {
arrayFrom.splice(i, 1);
arrayTo.push(profileName);
break;
}
}
ClientPolicies.update({
realm: realm.realm,
}, $scope.clientPolicies, function () {
Notifications.success(notificationsMessage);
}, function(errorResponse) {
Notifications.error('Failed to update profiles of the policy. Check server log for the details');
});
}
$scope.addProfile = function(profileName) {
console.log("addProfile: " + profileName);
moveProfileAndUpdatePolicy($scope.availableProfiles, $scope.editedPolicy.profiles, profileName, "Profile added to the policy");
};
$scope.removeProfile = function(profileName) {
console.log("removeProfile: " + profileName);
moveProfileAndUpdatePolicy( $scope.editedPolicy.profiles, $scope.availableProfiles, profileName, "Profile removed from the policy");
}
});
module.controller('ClientPoliciesEditConditionCtrl', function($scope, realm, serverInfo, clientPolicies, ClientPolicies, Components, ComponentUtils, Dialog, Notifications, $route, $location) {
var updatedConditionIndex = $route.current.params.conditionIndex;
var targetPolicyName = $route.current.params.policyName;
$scope.createNew = updatedConditionIndex == null;
if ($scope.createNew) {
console.log('ClientPoliciesEditConditionCtrl: adding condition to policy ' + targetPolicyName);
} else {
console.log('ClientPoliciesEditConditionCtrl: updating condition with index ' + updatedConditionIndex + ' of policy ' + targetPolicyName);
}
$scope.realm = realm;
var editedPolicy = null;
for (var i=0 ; i < clientPolicies.policies.length ; i++) {
var currentPolicy = clientPolicies.policies[i];
if (targetPolicyName === currentPolicy.name) {
editedPolicy = currentPolicy;
break;
}
}
if (editedPolicy == null) {
throw 'Client policy of specified name not found';
}
$scope.readOnly = !$scope.access.manageRealm;
$scope.conditionTypes = serverInfo.componentTypes['org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider'];
for (var j=0 ; j < $scope.conditionTypes.length ; j++) {
var currConditionType = $scope.conditionTypes[j];
if (currConditionType.properties) {
console.log("Adjusting conditionType: " + currConditionType.id);
ComponentUtils.addMvOptionsToMultivaluedLists(currConditionType.properties);
}
}
function getConditionByIndex(clientPolicy, conditionIndex) {
if (clientPolicy.conditions.length <= conditionIndex) {
console.error('Client policy does not have condition of specified index');
$location.path('/notfound');
return null;
} else {
return clientPolicy.conditions[conditionIndex];
}
}
if ($scope.createNew) {
// make first type the default
$scope.conditionType = $scope.conditionTypes[0];
var oldConditionType = $scope.conditionType;
initConfig();
$scope.$watch('conditionType', function() {
if (!angular.equals($scope.conditionType, oldConditionType)) {
oldConditionType = $scope.conditionType;
initConfig();
}
}, true);
} else {
var cond = getConditionByIndex(editedPolicy, updatedConditionIndex);
if (cond) {
$scope.condition = {
config: cond.configuration
};
$scope.conditionType = null;
for (var j=0 ; j < $scope.conditionTypes.length ; j++) {
var currentCndType = $scope.conditionTypes[j];
if (cond.condition === currentCndType.id) {
$scope.conditionType = currentCndType;
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.conditionType.id);
$scope.condition = {
config: {}
};
for (let i = 0; i < $scope.conditionType.properties.length; i++) {
let configProperty = $scope.conditionType.properties[i];
$scope.condition.config[configProperty.name] = toDefaultValue(configProperty);
}
}
$scope.save = function() {
console.log("save: " + $scope.conditionType.id);
var conditionName = $scope.conditionType.id;
if (!editedPolicy.conditions) {
editedPolicy.conditions = [];
}
ComponentUtils.removeLastEmptyValue($scope.condition.config);
var selectedCondition;
if ($scope.createNew) {
var selectedCondition = {
condition: $scope.conditionType.id,
configuration: $scope.condition.config
};
editedPolicy.conditions.push(selectedCondition);
} else {
var currentCondition = getConditionByIndex(editedPolicy, updatedConditionIndex);
if (currentCondition) {
currentCondition.configuration = $scope.condition.config;
}
}
ClientPolicies.update({
realm: realm.realm,
}, clientPolicies, function () {
if ($scope.createNew) {
Notifications.success("Condition created successfully");
} else {
Notifications.success("Condition updated successfully");
}
$location.url('/realms/' + realm.realm + '/client-policies/policies-update/' + editedPolicy.name);
});
};
$scope.cancel = function() {
$location.url('/realms/' + realm.realm + '/client-policies/policies-update/' + editedPolicy.name);
};
});
module.controller('RealmImportCtrl', function($scope, realm, $route,
Notifications, $modal, $resource) {
$scope.rawContent = {};

View file

@ -563,8 +563,25 @@ module.factory('ClientRegistrationPolicyProvidersLoader', function(Loader, Clien
});
});
module.factory('ClientPoliciesProfilesLoader', function(Loader, ClientPoliciesProfiles, $route , $q) {
var clientPoliciesLoader = {};
clientPoliciesLoader.loadClientProfiles = function(includeGlobalProfiles) {
return Loader.get(ClientPoliciesProfiles, function() {
return {
realm : $route.current.params.realm,
includeGlobalProfiles : includeGlobalProfiles
}
})();
};
return clientPoliciesLoader;
});
module.factory('ClientPoliciesLoader', function(Loader, ClientPolicies, $route) {
return Loader.get(ClientPolicies, function() {
return {
realm: $route.current.params.realm
}
});
});

View file

@ -2198,6 +2198,27 @@ module.factory('ClientRegistrationPolicyProviders', function($resource) {
});
});
module.factory('ClientPoliciesProfiles', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/client-policies/profiles?include-global-profiles=:includeGlobalProfiles', {
realm : '@realm',
includeGlobalProfiles : '@includeGlobalProfiles'
}, {
update : {
method : 'PUT'
}
});
});
module.factory('ClientPolicies', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/client-policies/policies', {
realm : '@realm',
}, {
update : {
method : 'PUT'
}
});
});
module.factory('LDAPMapperSync', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/user-storage/:parentId/mappers/:mapperId/sync', {
realm : '@realm',

View file

@ -0,0 +1,60 @@
<!--
~ 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.
~
-->
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<kc-tabs-realm></kc-tabs-realm>
<ul class="nav nav-tabs nav-tabs-pf">
<li>
<a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'client-policies-profiles' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-profiles.tooltip' | translate}}</kc-tooltip>
</li>
<li class="active">
<a href="#/realms/{{realm.realm}}/client-policies/policies">{{:: 'client-policies-policies' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-policies.tooltip' | translate}}</kc-tooltip>
</li>
</ul>
<ul class="nav nav-tabs nav-tabs-pf">
<li><a href="#/realms/{{realm.realm}}/client-policies/policies">{{:: 'client-profiles-form-view' | translate}}</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/client-policies/policies-json">{{:: 'client-profiles-json-editor' | translate}}</a></li>
</ul>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<filedset>
<div class="form-group">
<div class="col-md-10">
<div>
<textarea id="clientPoliciesConfig" name="clientPoliciesConfig" data-ng-model="clientPoliciesString" class="form-control ng-binding" rows="20" ></textarea>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<button class="btn btn-primary" data-ng-click="save()">{{:: 'save' | translate}}</button>
<button class="btn btn-default" data-ng-click="reset()">{{:: 'reset' | translate}}</button>
</div>
</div>
</filedset>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -0,0 +1,74 @@
<!--
~ 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.
~
-->
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<kc-tabs-realm></kc-tabs-realm>
<ul class="nav nav-tabs nav-tabs-pf">
<li>
<a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'client-policies-profiles' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-profiles.tooltip' | translate}}</kc-tooltip>
</li>
<li class="active">
<a href="#/realms/{{realm.realm}}/client-policies/policies">{{:: 'client-policies-policies' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-policies.tooltip' | translate}}</kc-tooltip>
</li>
</ul>
<ul class="nav nav-tabs nav-tabs-pf">
<li class="active"><a href="#/realms/{{realm.realm}}/client-policies/policies">{{:: 'client-profiles-form-view' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/client-policies/policies-json">{{:: 'client-profiles-json-editor' | translate}}</a></li>
</ul>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="kc-table-actions" colspan="6">
<div class="form-inline">
<div class="pull-right" data-ng-show="access.manageRealm">
<a href="#/realms/{{realm.realm}}/client-policies/policy-create" class="btn btn-default">{{:: 'create' | translate}}</a>
</div>
</div>
</th>
</tr>
<tr>
<th>{{:: 'name' | translate}}</th>
<th>{{:: 'description' | translate}}</th>
<th>{{:: 'enabled' | translate}}</th>
<th colspan="2">{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="clientPolicy in clientPolicies.policies">
<td><a href="#/realms/{{realm.realm}}/client-policies/policies-update/{{clientPolicy.name}}">{{clientPolicy.name}}</a></td>
<td>{{clientPolicy.description}}</td>
<td translate="{{clientPolicy.enabled}}"></td>
<td class="kc-action-cell" data-ng-hide="clientPolicy.builtin" kc-open="/realms/{{realm.realm}}/client-policies/policies-update/{{clientPolicy.name}}">{{:: 'edit' | translate}}</td>
<td class="kc-action-cell" data-ng-hide="clientPolicy.builtin" data-ng-click="removeClientPolicy(clientPolicy)">{{:: 'delete' | translate}}</td>
</tr>
</tbody>
</table>
</fieldset>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -0,0 +1,58 @@
<!--
~ 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.
~
-->
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1 data-ng-hide="createNew">{{conditionType.id|capitalize}}</h1>
<h1 data-ng-show="createNew">{{:: 'create-condition' | translate}}</h1>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="readOnly">
<fieldset>
<div class="form-group" data-ng-show="createNew">
<label class="col-md-2 control-label" for="conditionTypeCreate">{{:: 'condition-type' | translate}}</label>
<div class="col-sm-6">
<div>
<select class="form-control" id="conditionTypeCreate"
ng-model="conditionType"
ng-options="conditionType.id for (conditionKey, conditionType) in conditionTypes">
</select>
</div>
</div>
<kc-tooltip>{{conditionType.helpText}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-hide="createNew">
<label class="col-md-2 control-label" for="conditionType">{{:: 'condition-type' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="conditionType" type="text" ng-model="conditionType.id" data-ng-readonly="true">
</div>
<kc-tooltip>{{conditionType.helpText}}</kc-tooltip>
</div>
<kc-provider-config config="condition.config" properties="conditionType.properties" realm="realm"></kc-provider-config>
</fieldset>
<div class="form-group" data-ng-hide="readOnly">
<div class="col-md-10 col-md-offset-2">
<button kc-save>{{:: 'save' | translate}}</button>
<button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
</div>
</div>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -0,0 +1,148 @@
<!--
~ 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.
~
-->
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<kc-tabs-realm></kc-tabs-realm>
<ul class="nav nav-tabs nav-tabs-pf">
<li>
<a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'client-policies-profiles' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-profiles.tooltip' | translate}}</kc-tooltip>
</li>
<li class="active">
<a href="#/realms/{{realm.realm}}/client-policies/policies">{{:: 'client-policies-policies' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-policies.tooltip' | translate}}</kc-tooltip>
</li>
</ul>
<ul class="nav nav-tabs nav-tabs-pf">
<li class="active"><a href="#/realms/{{realm.realm}}/client-policies/policies">{{:: 'client-profiles-form-view' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/client-policies/policies-json">{{:: 'client-profiles-json-editor' | translate}}</a></li>
</ul>
<form class="form-horizontal" name="realmForm" novalidate>
<fieldset class="border-top" kc-read-only="readOnly">
<div class="form-group">
<label class="col-md-2 control-label" for="clientPolicyName">{{:: 'name' | translate}} <span class="required">*</span></label>
<div class="col-sm-6">
<input class="form-control" type="text" id="clientPolicyName" name="clientPolicyName" data-ng-model="editedPolicy.name" autofocus required>
</div>
<kc-tooltip>{{:: 'client-policy-name.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="description">{{:: 'description' | translate}} </label>
<div class="col-md-6">
<textarea class="form-control" rows="5" cols="50" id="description" name="description" data-ng-model="editedPolicy.description"></textarea>
</div>
</div>
<div class="form-group clearfix block">
<label class="col-md-2 control-label" for="enabled">{{:: 'enabled' | translate}}</label>
<div class="col-sm-6">
<input ng-model="editedPolicy.enabled" name="enabled" id="enabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
<kc-tooltip>{{:: 'client-policy-enabled.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<fieldset data-ng-hide="readOnly">
<div class="form-group">
<div class="col-md-10 col-md-offset-2">
<button kc-save>{{:: 'save' | translate}}</button>
<button kc-cancel data-ng-click="back()">{{:: 'back' | translate}}</button>
</div>
</div>
</fieldset>
<fieldset data-ng-hide="createNew">
<legend><span class="text">{{:: 'conditions' | translate}}</span> <kc-tooltip>{{:: 'client-policy-conditions.tooltip' | translate}}</kc-tooltip></legend>
<table class="table table-striped table-bordered">
<thead>
<tr data-ng-hide="readOnly">
<th class="kc-table-actions" colspan="3">
<div class="form-inline">
<div class="pull-right">
<a href="#/realms/{{realm.realm}}/client-policies/policies-update/{{editedPolicy.name}}/create-condition" class="btn btn-default">{{:: 'create' | translate}}</a>
</div>
</div>
</th>
</tr>
<tr data-ng-show="editedPolicy.conditions && editedPolicy.conditions.length > 0">
<th>{{:: 'type' | translate}}</th>
<th colspan="2">{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="condition in editedPolicy.conditions">
<td><a href="#/realms/{{realm.realm}}/client-policies/policies-update/{{editedPolicy.name}}/update-condition/{{$index}}">{{condition.condition}}</a></td>
<td class="kc-action-cell" data-ng-hide="readOnly" kc-open="/realms/{{realm.realm}}/client-policies/policies-update/{{editedPolicy.name}}/update-condition/{{$index}}">{{:: 'edit' | translate}}</td>
<td class="kc-action-cell" data-ng-hide="readOnly" data-ng-click="removeCondition($index)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="!editedPolicy.conditions || editedPolicy.conditions.length == 0">
<td>{{:: 'no-conditions-available' | translate}}</td>
</tr>
</tbody>
</table>
</fieldset>
<fieldset data-ng-hide="createNew">
<legend><span class="text">{{:: 'client-profiles' | translate}}</span></legend><kc-tooltip>{{:: 'client-profiles.tooltip' | translate}}</kc-tooltip>
<table class="table table-striped table-bordered">
<thead>
<tr data-ng-hide="readOnly || availableProfiles.length == 0">
<th colspan="5" class="kc-table-actions">
<div class="pull-right">
<div>
<select class="form-control" ng-model="selectedProfile"
ng-options="p for p in availableProfiles"
data-ng-change="addProfile(selectedProfile); selectedProfile = null">
<option value="" disabled selected>{{:: 'add-profile.placeholder' | translate}}</option>
</select>
</div>
</div>
</th>
</tr>
<tr data-ng-show="editedPolicy.profiles && editedPolicy.profiles.length > 0">
<th>{{:: 'name' | translate}}</th>
<th>{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="profileName in editedPolicy.profiles">
<td><a href="#/realms/{{realm.realm}}/client-policies/profiles-update/{{profileName}}">{{profileName}}</a></td>
<td class="kc-action-cell" data-ng-hide="readOnly" data-ng-click="removeProfile(profileName)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="!editedPolicy.profiles || editedPolicy.profiles.length == 0">
<td class="text-muted">{{:: 'no-client-profiles-configured' | translate}}</td>
</tr>
</tbody>
</table>
</fieldset>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -0,0 +1,58 @@
<!--
~ 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.
~
-->
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1 data-ng-hide="createNew">{{executorType.id|capitalize}}</h1>
<h1 data-ng-show="createNew">{{:: 'create-executor' | translate}}</h1>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="readOnly">
<fieldset>
<div class="form-group" data-ng-show="createNew">
<label class="col-md-2 control-label" for="executorTypeCreate">{{:: 'executor-type' | translate}}</label>
<div class="col-sm-6">
<div>
<select class="form-control" id="executorTypeCreate"
ng-model="executorType"
ng-options="executorType.id for (executorKey, executorType) in executorTypes">
</select>
</div>
</div>
<kc-tooltip>{{executorType.helpText}}</kc-tooltip>
</div>
<div class="form-group clearfix" data-ng-hide="createNew">
<label class="col-md-2 control-label" for="executorType">{{:: 'executor-type' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="executorType" type="text" ng-model="executorType.id" data-ng-readonly="true">
</div>
<kc-tooltip>{{executorType.helpText}}</kc-tooltip>
</div>
<kc-provider-config config="executor.config" properties="executorType.properties" realm="realm"></kc-provider-config>
</fieldset>
<div class="form-group" data-ng-hide="readOnly">
<div class="col-md-10 col-md-offset-2">
<button kc-save>{{:: 'save' | translate}}</button>
<button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
</div>
</div>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -0,0 +1,105 @@
<!--
~ 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.
~
-->
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<kc-tabs-realm></kc-tabs-realm>
<ul class="nav nav-tabs nav-tabs-pf">
<li class="active">
<a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'client-policies-profiles' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-profiles.tooltip' | translate}}</kc-tooltip>
</li>
<li>
<a href="#/realms/{{realm.realm}}/client-policies/policies">{{:: 'client-policies-policies' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-policies.tooltip' | translate}}</kc-tooltip>
</li>
</ul>
<ul class="nav nav-tabs nav-tabs-pf">
<li class="active"><a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'client-profiles-form-view' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/client-policies/profiles-json">{{:: 'client-profiles-json-editor' | translate}}</a></li>
</ul>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="readOnly">
<fieldset class="border-top">
<div class="form-group">
<label class="col-md-2 control-label" for="clientProfileName">{{:: 'name' | translate}} <span class="required">*</span></label>
<div class="col-sm-6">
<input class="form-control" type="text" id="clientProfileName" name="clientProfileName" data-ng-model="editedProfile.name" autofocus required>
</div>
<kc-tooltip>{{:: 'client-profile-name.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="description">{{:: 'description' | translate}} </label>
<div class="col-md-6">
<textarea class="form-control" rows="5" cols="50" id="description" name="description" data-ng-model="editedProfile.description"></textarea>
</div>
</div>
</fieldset>
<fieldset>
<div class="form-group">
<div class="col-md-10 col-md-offset-2">
<button kc-save>{{:: 'save' | translate}}</button>
<button kc-cancel data-ng-click="back()">{{:: 'back' | translate}}</button>
</div>
</div>
</fieldset>
<fieldset data-ng-hide="createNew">
<legend><span class="text">{{:: 'executors' | translate}}</span> <kc-tooltip>{{:: 'client-profile-executors.tooltip' | translate}}</kc-tooltip></legend>
<table class="table table-striped table-bordered">
<thead>
<tr data-ng-hide="readOnly">
<th class="kc-table-actions" colspan="3">
<div class="form-inline">
<div class="pull-right">
<a href="#/realms/{{realm.realm}}/client-policies/profiles-update/{{editedProfile.name}}/create-executor" class="btn btn-default">{{:: 'create' | translate}}</a>
</div>
</div>
</th>
</tr>
<tr data-ng-show="editedProfile.executors && editedProfile.executors.length > 0">
<th>{{:: 'type' | translate}}</th>
<th colspan="2">{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="executor in editedProfile.executors">
<td><a href="#/realms/{{realm.realm}}/client-policies/profiles-update/{{editedProfile.name}}/update-executor/{{$index}}">{{executor.executor}}</a></td>
<td class="kc-action-cell" data-ng-hide="readOnly" kc-open="/realms/{{realm.realm}}/client-policies/profiles-update/{{editedProfile.name}}/update-executor/{{$index}}">{{:: 'edit' | translate}}</td>
<td class="kc-action-cell" data-ng-hide="readOnly" data-ng-click="removeExecutor($index)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="!editedProfile.executors || editedProfile.executors.length == 0">
<td>{{:: 'no-executors-available' | translate}}</td>
</tr>
</tbody>
</table>
</fieldset>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -0,0 +1,60 @@
<!--
~ 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.
~
-->
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<kc-tabs-realm></kc-tabs-realm>
<ul class="nav nav-tabs nav-tabs-pf">
<li class="active">
<a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'client-policies-profiles' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-profiles.tooltip' | translate}}</kc-tooltip>
</li>
<li>
<a href="#/realms/{{realm.realm}}/client-policies/policies">{{:: 'client-policies-policies' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-policies.tooltip' | translate}}</kc-tooltip>
</li>
</ul>
<ul class="nav nav-tabs nav-tabs-pf">
<li><a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'client-profiles-form-view' | translate}}</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/client-policies/profiles-json">{{:: 'client-profiles-json-editor' | translate}}</a></li>
</ul>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<filedset>
<div class="form-group">
<div class="col-md-10">
<div>
<textarea id="clientProfilesConfig" name="clientProfilesConfig" data-ng-model="clientProfilesString" class="form-control ng-binding" rows="20" ></textarea>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<button class="btn btn-primary" data-ng-click="save()">{{:: 'save' | translate}}</button>
<button class="btn btn-default" data-ng-click="reset()">{{:: 'reset' | translate}}</button>
</div>
</div>
</filedset>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -0,0 +1,81 @@
<!--
~ 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.
~
-->
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<kc-tabs-realm></kc-tabs-realm>
<ul class="nav nav-tabs nav-tabs-pf">
<li class="active">
<a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'client-policies-profiles' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-profiles.tooltip' | translate}}</kc-tooltip>
</li>
<li>
<a href="#/realms/{{realm.realm}}/client-policies/policies">{{:: 'client-policies-policies' | translate}}</a>
<kc-tooltip>{{:: 'client-policies-policies.tooltip' | translate}}</kc-tooltip>
</li>
</ul>
<ul class="nav nav-tabs nav-tabs-pf">
<li class="active"><a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'client-profiles-form-view' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/client-policies/profiles-json">{{:: 'client-profiles-json-editor' | translate}}</a></li>
</ul>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="kc-table-actions" colspan="6">
<div class="form-inline">
<div class="pull-right" data-ng-show="access.manageRealm">
<a href="#/realms/{{realm.realm}}/client-policies/profiles-create" class="btn btn-default">{{:: 'create' | translate}}</a>
</div>
</div>
</th>
</tr>
<tr>
<th>{{:: 'name' | translate}}</th>
<th>{{:: 'description' | translate}}</th>
<th>{{:: 'global' | translate}}</th>
<th colspan="2">{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="clientProfile in clientProfiles.globalProfiles">
<td><a href="#/realms/{{realm.realm}}/client-policies/profiles-update/{{clientProfile.name}}">{{clientProfile.name}}</a></td>
<td>{{clientProfile.description}}</td>
<td>{{:: 'true' | translate}}</td>
<td></td>
<td></td>
</tr>
<tr ng-repeat="clientProfile in clientProfiles.profiles">
<td><a href="#/realms/{{realm.realm}}/client-policies/profiles-update/{{clientProfile.name}}">{{clientProfile.name}}</a></td>
<td>{{clientProfile.description}}</td>
<td>{{:: 'false' | translate}}</td>
<td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/client-policies/profiles-update/{{clientProfile.name}}">{{:: 'edit' | translate}}</td>
<td class="kc-action-cell" data-ng-click="removeClientProfile(clientProfile)">{{:: 'delete' | translate}}</td>
</tr>
</tbody>
</table>
</fieldset>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -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'

View file

@ -16,6 +16,9 @@
<option value="" selected> {{:: 'selectOne' | translate}} </option>
</select>
</div>
<div class="col-md-6" data-ng-if="option.type == 'MultivaluedList'">
<input ui-select2="option.mvOptions" ng-model="config[ option.name ]" data-placeholder="{{:: 'selectMultiple' | translate}}..."/>
</div>
<div class="col-md-6" data-ng-if="option.type == 'Role'">
<div class="row">
<div class="col-md-8">

View file

@ -15,6 +15,9 @@
<li ng-class="{active: path[2] == 'cache-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/cache-settings">{{:: 'realm-tab-cache' | translate}}</a></li>
<li ng-class="{active: path[2] == 'token-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/token-settings">{{:: 'realm-tab-tokens' | translate}}</a></li>
<li ng-class="{active: path[2] == 'client-registration'}" data-ng-show="access.viewClients"><a href="#/realms/{{realm.realm}}/client-registration/client-initial-access">{{:: 'realm-tab-client-registration' | translate}}</a></li>
<li ng-class="{active: path[2] == 'client-policies'}" data-ng-show="access.viewRealm && serverInfo.featureEnabled('CLIENT_POLICIES')">
<a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'realm-tab-client-policies' | translate}}</a>
</li>
<li ng-class="{active: path[2] == 'defense'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/defense/headers">{{:: 'realm-tab-security-defenses' | translate}}</a></li>
</ul>
</div>