KEYCLOAK-16805 Client Policy : Support New Admin REST API (Implementation) (#7780)

* KEYCLOAK-16805 Client Policy : Support New Admin REST API (Implementation)

* support tests using auth-server-quarkus

* Configuration changes for ClientPolicyExecutorProvider

* Change VALUE of table REALM_ATTRIBUTES to NCLOB

* add author tag

* incorporate all review comments

Co-authored-by: mposolda <mposolda@gmail.com>
This commit is contained in:
Takashi Norimatsu 2021-04-06 23:31:10 +09:00 committed by GitHub
parent d1ad905407
commit 42dec08f3c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
102 changed files with 5345 additions and 1848 deletions

View file

@ -0,0 +1,41 @@
/*
* 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.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* 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;
public List<ClientPolicyRepresentation> getPolicies() {
return policies;
}
public void setPolicies(List<ClientPolicyRepresentation> policies) {
this.policies = policies;
}
}

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.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 List<String> profiles;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean isBuiltin() {
return builtin;
}
public void setBuiltin(Boolean builtin) {
this.builtin = builtin;
}
public Boolean isEnable() {
return enable;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
public List<Object> getConditions() {
return conditions;
}
public void setConditions(List<Object> conditions) {
this.conditions = conditions;
}
public List<String> getProfiles() {
return profiles;
}
public void setProfiles(List<String> profiles) {
this.profiles = profiles;
}
}

View file

@ -0,0 +1,68 @@
/*
* 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.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;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean isBuiltin() {
return builtin;
}
public void setBuiltin(Boolean builtin) {
this.builtin = builtin;
}
public List<Object> getExecutors() {
return executors;
}
public void setExecutors(List<Object> executors) {
this.executors = executors;
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* 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;
public List<ClientProfileRepresentation> getProfiles() {
return profiles;
}
public void setProfiles(List<ClientProfileRepresentation> profiles) {
this.profiles = profiles;
}
}

View file

@ -141,6 +141,11 @@ public class RealmRepresentation {
protected Boolean webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister; protected Boolean webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister;
protected List<String> webAuthnPolicyPasswordlessAcceptableAaguids; protected List<String> webAuthnPolicyPasswordlessAcceptableAaguids;
// Client Policies/Profiles
protected ClientProfilesRepresentation clientProfiles;
protected ClientPoliciesRepresentation clientPolicies;
protected List<UserRepresentation> users; protected List<UserRepresentation> users;
protected List<UserRepresentation> federatedUsers; protected List<UserRepresentation> federatedUsers;
protected List<ScopeMappingRepresentation> scopeMappings; protected List<ScopeMappingRepresentation> scopeMappings;
@ -1173,6 +1178,24 @@ public class RealmRepresentation {
this.webAuthnPolicyPasswordlessAcceptableAaguids = webAuthnPolicyPasswordlessAcceptableAaguids; this.webAuthnPolicyPasswordlessAcceptableAaguids = webAuthnPolicyPasswordlessAcceptableAaguids;
} }
// Client Policies/Profiles
public ClientProfilesRepresentation getClientProfiles() {
return clientProfiles;
}
public void setClientProfiles(ClientProfilesRepresentation clientProfiles) {
this.clientProfiles = clientProfiles;
}
public ClientPoliciesRepresentation getClientPolicies() {
return clientPolicies;
}
public void setClientPolicies(ClientPoliciesRepresentation clientPolicies) {
this.clientPolicies = clientPolicies;
}
public String getBrowserFlow() { public String getBrowserFlow() {
return browserFlow; return browserFlow;
} }

View file

@ -0,0 +1,26 @@
package org.keycloak.admin.client.resource;
import javax.ws.rs.Consumes;
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;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public interface ClientPoliciesPoliciesResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
String getPolicies();
@PUT
@Consumes(MediaType.APPLICATION_JSON)
Response updatePolicies(final String json);
}

View file

@ -0,0 +1,25 @@
package org.keycloak.admin.client.resource;
import javax.ws.rs.Consumes;
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;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public interface ClientPoliciesProfilesResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
String getProfiles();
@PUT
@Consumes(MediaType.APPLICATION_JSON)
Response updateProfiles(final String json);
}

View file

@ -18,6 +18,7 @@
package org.keycloak.admin.client.resource; package org.keycloak.admin.client.resource;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.representations.adapters.action.GlobalRequestResult; import org.keycloak.representations.adapters.action.GlobalRequestResult;
import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
@ -282,4 +283,9 @@ public interface RealmResource {
@Path("localization") @Path("localization")
RealmLocalizationResource localization(); RealmLocalizationResource localization();
@Path("client-policies/policies")
ClientPoliciesPoliciesResource clientPoliciesPoliciesResource();
@Path("client-policies/profiles")
ClientPoliciesProfilesResource clientPoliciesProfilesResource();
} }

View file

@ -51,4 +51,9 @@
<modifyDataType newDataType="VARCHAR(255)" tableName="CLIENT_SCOPE_CLIENT" columnName="CLIENT_ID"/> <modifyDataType newDataType="VARCHAR(255)" tableName="CLIENT_SCOPE_CLIENT" columnName="CLIENT_ID"/>
<modifyDataType newDataType="VARCHAR(255)" tableName="CLIENT_SCOPE_CLIENT" columnName="SCOPE_ID"/> <modifyDataType newDataType="VARCHAR(255)" tableName="CLIENT_SCOPE_CLIENT" columnName="SCOPE_ID"/>
</changeSet> </changeSet>
<changeSet author="keycloak" id="json-string-accomodation">
<modifyDataType tableName="REALM_ATTRIBUTE" columnName="VALUE" newDataType="NCLOB"/>
</changeSet>
</databaseChangeLog> </databaseChangeLog>

View file

@ -58,6 +58,7 @@ public class QuarkusKeycloakApplication extends KeycloakApplication {
QuarkusKeycloakSessionFactory instance = QuarkusKeycloakSessionFactory.getInstance(); QuarkusKeycloakSessionFactory instance = QuarkusKeycloakSessionFactory.getInstance();
sessionFactory = instance; sessionFactory = instance;
instance.init(); instance.init();
instance.create().clientPolicy().setupClientPoliciesOnKeycloakApp("/keycloak-default-client-profiles.json", "/keycloak-default-client-policies.json");
sessionFactory.publish(new PostMigrationEvent()); sessionFactory.publish(new PostMigrationEvent());
} }

View file

@ -93,6 +93,9 @@ public class ModelToRepresentation {
REALM_EXCLUDED_ATTRIBUTES.add("webAuthnPolicyCreateTimeoutPasswordless"); REALM_EXCLUDED_ATTRIBUTES.add("webAuthnPolicyCreateTimeoutPasswordless");
REALM_EXCLUDED_ATTRIBUTES.add("webAuthnPolicyAvoidSameAuthenticatorRegisterPasswordless"); REALM_EXCLUDED_ATTRIBUTES.add("webAuthnPolicyAvoidSameAuthenticatorRegisterPasswordless");
REALM_EXCLUDED_ATTRIBUTES.add("webAuthnPolicyAcceptableAaguidsPasswordless"); REALM_EXCLUDED_ATTRIBUTES.add("webAuthnPolicyAcceptableAaguidsPasswordless");
REALM_EXCLUDED_ATTRIBUTES.add("client-policies.profiles");
REALM_EXCLUDED_ATTRIBUTES.add("client-policies.policies");
} }

View file

@ -0,0 +1,762 @@
/*
* 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

@ -0,0 +1,82 @@
/*
* 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.Serializable;
import java.util.List;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
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<String> profiles;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isBuiltin() {
return builtin;
}
public void setBuiltin(boolean builtin) {
this.builtin = builtin;
}
public boolean isEnable() {
return enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public List<Object> getConditions() {
return conditions;
}
public void setConditions(List<Object> conditions) {
this.conditions = conditions;
}
public List<String> getProfiles() {
return profiles;
}
public void setProfiles(List<String> profiles) {
this.profiles = profiles;
}
}

View file

@ -1,48 +0,0 @@
/*
* Copyright 2020 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.keycloak.provider.Provider;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
/**
* Provides Client Policy which accommodates several Conditions and Executors.
*/
public interface ClientPolicyProvider extends Provider {
/**
* returns the list of conditions which this provider accommodates.
*
* @return list of conditions
*/
List<ClientPolicyConditionProvider> getConditions();
/**
* returns the list of executors which this provider accommodates.
*
* @return list of executors
*/
List<ClientPolicyExecutorProvider> getExecutors();
String getName();
String getProviderId();
}

View file

@ -1,46 +0,0 @@
/*
* Copyright 2020 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;
public class ClientPolicySpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "client-policy";
}
@Override
public Class<? extends Provider> getProviderClass() {
return ClientPolicyProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return ClientPolicyProviderFactory.class;
}
}

View file

@ -17,6 +17,9 @@
package org.keycloak.services.clientpolicy; package org.keycloak.services.clientpolicy;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public enum ClientPolicyVote { public enum ClientPolicyVote {
YES, YES,
NO, NO,

View file

@ -0,0 +1,64 @@
/*
* 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.Serializable;
import java.util.List;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
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.
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isBuiltin() {
return builtin;
}
public void setBuiltin(boolean builtin) {
this.builtin = builtin;
}
public List<Object> getExecutors() {
return executors;
}
public void setExecutors(List<Object> executors) {
this.executors = executors;
}
}

View file

@ -0,0 +1,30 @@
/*
* 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

@ -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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -24,17 +24,34 @@ import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.ClientPolicyVote;
/** /**
* This condition determines to which client a {@link ClientPolicyProvider} is adopted. * This condition determines to which client a client policy is adopted.
* The condition can be evaluated on the events defined in {@link ClientPolicyEvent}. * The condition can be evaluated on the events defined in {@link ClientPolicyEvent}.
* It is sufficient for the implementer of this condition to implement methods in which they are interested * It is sufficient for the implementer of this condition to implement methods in which they are interested
* and {@link isEvaluatedOnEvent} method. * and {@link isEvaluatedOnEvent} method.
*
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/ */
public interface ClientPolicyConditionProvider extends Provider { public interface ClientPolicyConditionProvider<CONFIG extends ClientPolicyConditionConfiguration> extends Provider {
@Override @Override
default void close() { default void close() {
} }
/**
* setup this condition's configuration.
*
* @param config
*/
default void setupConfiguration(CONFIG config) {
}
/**
* @return Class, which should match the "config" argument of the {@link #setupConfiguration(ClientPolicyConditionConfiguration)}
*/
default Class<CONFIG> getConditionConfigurationClass() {
return (Class<CONFIG>) ClientPolicyConditionConfiguration.class;
}
/** /**
* returns ABSTAIN if this condition is not evaluated due to its nature. * returns ABSTAIN if this condition is not evaluated due to its nature.
* returns YES if the client satisfies this condition on the event defined in {@link ClientPolicyEvent}. * returns YES if the client satisfies this condition on the event defined in {@link ClientPolicyEvent}.
@ -60,7 +77,9 @@ public interface ClientPolicyConditionProvider extends Provider {
return false; return false;
} }
String getName(); default String getName() {
return getClass().toString();
}
String getProviderId(); String getProviderId();
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,7 +17,11 @@
package org.keycloak.services.clientpolicy.condition; package org.keycloak.services.clientpolicy.condition;
import org.keycloak.component.ComponentFactory; import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.ProviderFactory;
public interface ClientPolicyConditionProviderFactory extends ComponentFactory<ClientPolicyConditionProvider, ClientPolicyConditionProvider> { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public interface ClientPolicyConditionProviderFactory extends ProviderFactory<ClientPolicyConditionProvider>, ConfiguredProvider {
} }

View file

@ -21,6 +21,9 @@ import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi; import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientPolicyConditionSpi implements Spi { public class ClientPolicyConditionSpi implements Spi {
@Override @Override

View file

@ -13,11 +13,18 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.keycloak.services.clientpolicy; package org.keycloak.services.clientpolicy.executor;
import org.keycloak.component.ComponentFactory; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
public interface ClientPolicyProviderFactory extends ComponentFactory<ClientPolicyProvider, ClientPolicyProvider> { /**
* Just adds some type-safety to the ClientPolicyExecutorConfiguration
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class ClientPolicyExecutorConfiguration {
} }

View file

@ -23,17 +23,34 @@ import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyEvent;
/** /**
* This executor specifies what action is executed on the client to which {@link ClientPolicyProvider} is adopted. * This executor specifies what action is executed on the client to which a client policy is adopted.
* The executor can be executed on the events defined in {@link ClientPolicyEvent}. * The executor can be executed on the events defined in {@link ClientPolicyEvent}.
* It is sufficient for the implementer of this executor to implement methods in which they are interested * It is sufficient for the implementer of this executor to implement methods in which they are interested
* and {@link isEvaluatedOnEvent} method. * and {@link isEvaluatedOnEvent} method.
*
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/ */
public interface ClientPolicyExecutorProvider extends Provider { public interface ClientPolicyExecutorProvider<CONFIG extends ClientPolicyExecutorConfiguration> extends Provider {
@Override @Override
default void close() { default void close() {
} }
/**
* setup this executor's configuration.
*
* @param config
*/
default void setupConfiguration(CONFIG config) {
}
/**
* @return Class, which should match the "config" argument of the {@link #setupConfiguration(ClientPolicyExecutorConfiguration)}
*/
default Class<CONFIG> getExecutorConfigurationClass() {
return (Class<CONFIG>) ClientPolicyExecutorConfiguration.class;
}
/** /**
* execute actions against the client on the event defined in {@link ClientPolicyEvent}. * execute actions against the client on the event defined in {@link ClientPolicyEvent}.
* *
@ -43,7 +60,9 @@ public interface ClientPolicyExecutorProvider extends Provider {
default void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException { default void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
} }
String getName(); default String getName() {
return getClass().toString();
}
String getProviderId(); String getProviderId();
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,7 +17,11 @@
package org.keycloak.services.clientpolicy.executor; package org.keycloak.services.clientpolicy.executor;
import org.keycloak.component.ComponentFactory; import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.ProviderFactory;
public interface ClientPolicyExecutorProviderFactory extends ComponentFactory<ClientPolicyExecutorProvider, ClientPolicyExecutorProvider> { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public interface ClientPolicyExecutorProviderFactory extends ProviderFactory<ClientPolicyExecutorProvider>, ConfiguredProvider {
} }

View file

@ -21,6 +21,9 @@ import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi; import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientPolicyExecutorSpi implements Spi { public class ClientPolicyExecutorSpi implements Spi {
@Override @Override

View file

@ -92,7 +92,6 @@ org.keycloak.crypto.CekManagementSpi
org.keycloak.crypto.ContentEncryptionSpi org.keycloak.crypto.ContentEncryptionSpi
org.keycloak.validation.ClientValidationSPI org.keycloak.validation.ClientValidationSPI
org.keycloak.headers.SecurityHeadersSpi org.keycloak.headers.SecurityHeadersSpi
org.keycloak.services.clientpolicy.ClientPolicySpi
org.keycloak.services.clientpolicy.condition.ClientPolicyConditionSpi org.keycloak.services.clientpolicy.condition.ClientPolicyConditionSpi
org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorSpi org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorSpi
org.keycloak.userprofile.UserProfileSpi org.keycloak.userprofile.UserProfileSpi

View file

@ -21,6 +21,8 @@ package org.keycloak.services.clientpolicy;
* Provides Client Policy Context. * Provides Client Policy Context.
* The implementation of this interface for handling an event defined in {@link ClientPolicyEvent} * The implementation of this interface for handling an event defined in {@link ClientPolicyEvent}
* needs to provide methods depending on this event. * needs to provide methods depending on this event.
*
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/ */
public interface ClientPolicyContext { public interface ClientPolicyContext {

View file

@ -17,6 +17,11 @@
package org.keycloak.services.clientpolicy; package org.keycloak.services.clientpolicy;
/**
* Events on which client policies mechanism detects and do its operation
*
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public enum ClientPolicyEvent { public enum ClientPolicyEvent {
REGISTER, REGISTER,

View file

@ -22,12 +22,20 @@ import javax.ws.rs.core.Response.Status;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientPolicyException extends Exception { public class ClientPolicyException extends Exception {
private String error = OAuthErrorException.INVALID_REQUEST; private String error = OAuthErrorException.INVALID_REQUEST;
private String errorDetail; private String errorDetail ="NA";
private Status errorStatus = Response.Status.BAD_REQUEST; private Status errorStatus = Response.Status.BAD_REQUEST;
public ClientPolicyException(String error) {
super(error);
setError(error);
}
public ClientPolicyException(String error, String errorDetail) { public ClientPolicyException(String error, String errorDetail) {
super(error); super(error);
setError(error); setError(error);

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,8 +17,14 @@
package org.keycloak.services.clientpolicy; package org.keycloak.services.clientpolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.RealmRepresentation;
/** /**
* Provides a method for handling an event defined in {@link ClientPolicyEvent}. * Provides a method for handling an event defined in {@link ClientPolicyEvent}.
* Also provides methods for handling client profiles and policies.
*
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/ */
public interface ClientPolicyManager { public interface ClientPolicyManager {
@ -30,4 +36,111 @@ public interface ClientPolicyManager {
*/ */
void triggerOnEvent(ClientPolicyContext context) throws ClientPolicyException; 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.
* if these operation fails, put null.
*
* @param realm - the newly created realm
*/
void setupClientPoliciesOnCreatedRealm(RealmModel realm);
/**
* when importing a realm, reads the builtin client profiles and policies
* that have already been set on keycloak application on booting keycloak and override them
* with ones loaded from the imported realm json file.
* if these operation fails, rolls them back to the builtin client profiles and policies set on keycloak application.
*
* @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);
/**
* 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.
*
* @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
* @throws {@link ClientPolicyException}
*/
void updateClientProfiles(RealmModel realm, String json) 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
* @return the json representation of the client profiles set on the realm
*/
String getClientProfiles(RealmModel realm);
/**
* when updating client policies via Admin REST API, reads the json representation of the client policies
* and overrides the existing client policies set on the realm with them.
* if these operation fails, rolls them back to the existing client policies and throw an exception.
*
* @param realm - the realm whose client policies is to be overriden by the new client policies
* @param json - the json representation of the new client policies that overrides the existing client policies set on the realm
* @throws {@link ClientPolicyException}
*/
void updateClientPolicies(RealmModel realm, String json) throws ClientPolicyException;
/**
* when getting client policies via Admin REST API, returns the existing 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 getClientPolicies(RealmModel realm);
/**
* 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.
*
* @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);
} }

View file

@ -260,6 +260,9 @@ public class ExportUtils {
MultivaluedHashMap<String, ComponentExportRepresentation> components = exportComponents(realm, realm.getId()); MultivaluedHashMap<String, ComponentExportRepresentation> components = exportComponents(realm, realm.getId());
rep.setComponents(components); rep.setComponents(components);
// client policies
session.clientPolicy().setupClientPoliciesOnExportingRealm(realm, rep);
return rep; return rep;
} }

View file

@ -46,9 +46,7 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorPageException; import org.keycloak.services.ErrorPageException;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls; import org.keycloak.services.Urls;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.LoginActionsService;

View file

@ -18,7 +18,6 @@ package org.keycloak.protocol.oidc.endpoints;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuthErrorException;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
@ -29,10 +28,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.AccessTokenIntrospectionProviderFactory; import org.keycloak.protocol.oidc.AccessTokenIntrospectionProviderFactory;
import org.keycloak.protocol.oidc.TokenIntrospectionProvider; import org.keycloak.protocol.oidc.TokenIntrospectionProvider;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil; import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.services.CorsErrorResponseException;
import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.services.clientpolicy.context.TokenIntrospectContext; import org.keycloak.services.clientpolicy.context.TokenIntrospectContext;
import javax.ws.rs.POST; import javax.ws.rs.POST;

View file

@ -52,7 +52,6 @@ import org.keycloak.services.clientpolicy.context.TokenRevokeContext;
import org.keycloak.services.managers.UserSessionCrossDCManager; import org.keycloak.services.managers.UserSessionCrossDCManager;
import org.keycloak.services.managers.UserSessionManager; import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.Cors; import org.keycloak.services.resources.Cors;
import org.keycloak.services.util.MtlsHoKTokenUtil;
import org.keycloak.util.TokenUtil; import org.keycloak.util.TokenUtil;
/** /**

View file

@ -1,49 +0,0 @@
/*
* Copyright 2020 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.jboss.logging.Logger;
public class ClientPolicyLogger {
public static void log(Logger logger, String content) {
if(!logger.isTraceEnabled()) return;
String buf = new StringBuffer()
.append("#").append(getMethodName())
.append(", ").append(content)
.toString();
logger.trace(buf);
}
public static void logv(Logger logger, String format, Object...params) {
if(!logger.isTraceEnabled()) return;
String buf = new StringBuffer()
.append("#").append(getMethodName())
.append(", ").append(format)
.toString();
logger.tracev(buf, params);
}
private static String getClassName() {
return Thread.currentThread().getStackTrace()[2].getClassName();
}
private static String getMethodName() {
return Thread.currentThread().getStackTrace()[3].getMethodName();
}
}

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,25 +17,29 @@
package org.keycloak.services.clientpolicy; package org.keycloak.services.clientpolicy;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.ClientPoliciesRepresentation;
import org.keycloak.representations.idm.ClientProfileRepresentation;
import org.keycloak.representations.idm.ClientProfilesRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class DefaultClientPolicyManager implements ClientPolicyManager { public class DefaultClientPolicyManager implements ClientPolicyManager {
private static final Logger logger = Logger.getLogger(DefaultClientPolicyManager.class); private static final Logger logger = Logger.getLogger(DefaultClientPolicyManager.class);
private final KeycloakSession session; private final KeycloakSession session;
private final Map<String, List<ClientPolicyProvider>> providersMap = new HashMap<>();
public DefaultClientPolicyManager(KeycloakSession session) { public DefaultClientPolicyManager(KeycloakSession session) {
this.session = session; this.session = session;
@ -43,60 +47,54 @@ public class DefaultClientPolicyManager implements ClientPolicyManager {
@Override @Override
public void triggerOnEvent(ClientPolicyContext context) throws ClientPolicyException { public void triggerOnEvent(ClientPolicyContext context) throws ClientPolicyException {
if (!Profile.isFeatureEnabled(Profile.Feature.CLIENT_POLICIES)) return; if (!Profile.isFeatureEnabled(Profile.Feature.CLIENT_POLICIES)) {
ClientPolicyLogger.logv(logger, "Client Policy Operation : event = {0}", context.getEvent()); return;
}
RealmModel realm = session.getContext().getRealm();
logger.tracev("POLICY OPERATION :: context realm = {0}, event = {1}", realm.getName(), context.getEvent());
doPolicyOperation( doPolicyOperation(
(ClientPolicyConditionProvider condition) -> condition.applyPolicy(context), (ClientPolicyConditionProvider condition) -> condition.applyPolicy(context),
(ClientPolicyExecutorProvider executor) -> executor.executeOnEvent(context) (ClientPolicyExecutorProvider executor) -> executor.executeOnEvent(context),
realm
); );
} }
private void doPolicyOperation(ClientConditionOperation condition, ClientExecutorOperation executor) throws ClientPolicyException { private void doPolicyOperation(ClientConditionOperation condition, ClientExecutorOperation executor, RealmModel realm) throws ClientPolicyException {
RealmModel realm = session.getContext().getRealm(); Map<String, ClientProfileModel> map = ClientPoliciesUtil.getClientProfilesModel(session, realm);
for (ClientPolicyProvider policy : getProviders(realm)) { List<ClientPolicyModel> list = ClientPoliciesUtil.getEnabledClientProfilesModel(session, realm).stream().collect(Collectors.toList());
ClientPolicyLogger.logv(logger, "Policy Operation : name = {0}, provider id = {1}", policy.getName(), policy.getProviderId());
if (!isSatisfied(policy, condition)) continue; if (list == null || list.isEmpty()) {
execute(policy, executor); logger.trace("POLICY OPERATION :: No enabled policy.");
} return;
} }
private List<ClientPolicyProvider> getProviders(RealmModel realm) { for (ClientPolicyModel policy: list) {
List<ClientPolicyProvider> providers = providersMap.get(realm.getId()); logger.tracev("POLICY OPERATION :: policy name = {0}, isBuiltin = {1}", policy.getName(), policy.isBuiltin());
if (providers == null) { if (!isSatisfied(policy, condition)) {
providers = realm.getComponentsStream(realm.getId(), ClientPolicyProvider.class.getName()) logger.tracev("POLICY UNSATISFIED :: policy name = {0}, isBuiltin = {1}", policy.getName(), policy.isBuiltin());
.map(policyModel -> { continue;
try {
ClientPolicyProvider policy = session.getProvider(ClientPolicyProvider.class, policyModel);
ClientPolicyLogger.logv(logger, "Loaded Policy Name = {0}", policyModel.getName());
session.enlistForClose(policy);
return policy;
} catch (Throwable t) {
logger.errorv(t, "Failed to load provider {0}", policyModel.getId());
return null;
} }
})
.filter(Objects::nonNull) logger.tracev("POLICY APPLIED :: policy name = {0}, isBuiltin = {1}", policy.getName(), policy.isBuiltin());
.collect(Collectors.toList()); execute(policy, executor, map);
providersMap.put(realm.getId(), providers);
} else {
ClientPolicyLogger.log(logger, "Use cached policies.");
} }
return providers;
} }
private boolean isSatisfied( private boolean isSatisfied(
ClientPolicyProvider policy, ClientPolicyModel policy,
ClientConditionOperation op) throws ClientPolicyException { ClientConditionOperation op) throws ClientPolicyException {
List<ClientPolicyConditionProvider> conditions = policy.getConditions(); if (policy.getConditions() == null || policy.getConditions().isEmpty()) {
logger.tracev("NO CONDITION :: policy name = {0}", policy.getName());
if (conditions == null || conditions.isEmpty()) {
ClientPolicyLogger.log(logger, "NEGATIVE :: This policy is not applied. No condition exists.");
return false; return false;
} }
boolean ret = false; boolean ret = false;
for (ClientPolicyConditionProvider condition : conditions) { for (Object obj : policy.getConditions()) {
ClientPolicyConditionProvider condition = (ClientPolicyConditionProvider)obj;
logger.tracev("CONDITION OPERATION :: policy name = {0}, condition name = {1}, provider id = {2}", policy.getName(), condition.getName(), condition.getProviderId());
try { try {
ClientPolicyVote vote = op.run(condition); ClientPolicyVote vote = op.run(condition);
if (condition.isNegativeLogic()) { if (condition.isNegativeLogic()) {
@ -107,48 +105,62 @@ public class DefaultClientPolicyManager implements ClientPolicyManager {
} }
} }
if (vote == ClientPolicyVote.ABSTAIN) { if (vote == ClientPolicyVote.ABSTAIN) {
ClientPolicyLogger.logv(logger, "SKIP : This condition is not evaluated due to its nature. name = {0}, provider id = {1}", condition.getName(), condition.getProviderId()); logger.tracev("CONDITION SKIP :: policy name = {0}, condition name = {1}, provider id = {2}", policy.getName(), condition.getName(), condition.getProviderId());
continue; continue;
} else if (vote == ClientPolicyVote.NO) { } else if (vote == ClientPolicyVote.NO) {
ClientPolicyLogger.logv(logger, "NEGATIVE :: This policy is not applied. condition not satisfied. name = {0}, provider id = {1}, ", condition.getName(), condition.getProviderId()); logger.tracev("CONDITION NEGATIVE :: policy name = {0}, condition name = {1}, provider id = {2}", policy.getName(), condition.getName(), condition.getProviderId());
return false; return false;
} }
ret = true; ret = true;
} catch (ClientPolicyException cpe) { } catch (ClientPolicyException e) {
ClientPolicyLogger.logv(logger, "CONDITION EXCEPTION : name = {0}, provider id = {1}, error = {2}, error_detail = {3}", condition.getName(), condition.getProviderId(), cpe.getError(), cpe.getErrorDetail()); logger.tracev("CONDITION EXCEPTION :: policy name = {0}, provider id = {1}, error = {2}, error detail = {3}", condition.getName(), condition.getProviderId(), e.getError(), e.getErrorDetail());
throw cpe; throw e;
} }
} }
if (ret == true) { if (ret == true) {
ClientPolicyLogger.log(logger, "POSITIVE :: This policy is applied."); logger.tracev("CONDITIONS SATISFIED :: policy name = {0}", policy.getName());
} else { } else {
ClientPolicyLogger.log(logger, "NEGATIVE :: This policy is not applied. No condition is evaluated."); logger.tracev("CONDITIONS UNSATISFIED :: policy name = {0}", policy.getName());
} }
return ret; return ret;
} }
private void execute( private void execute(
ClientPolicyProvider policy, ClientPolicyModel policy,
ClientExecutorOperation op) throws ClientPolicyException { ClientExecutorOperation op,
Map<String, ClientProfileModel> map) throws ClientPolicyException {
List<ClientPolicyExecutorProvider> executors = policy.getExecutors(); if (policy.getProfiles() == null || policy.getProfiles().isEmpty()) {
if (executors == null || executors.isEmpty()) { logger.tracev("NO PROFILE :: policy name = {0}", policy.getName());
ClientPolicyLogger.log(logger, "NEGATIVE :: This executor is not executed. No executor executable.");
return;
} }
for (ClientPolicyExecutorProvider executor : executors) {
for (String profileName : policy.getProfiles()) {
ClientProfileModel profile = map.get(profileName);
if (profile == null) {
logger.tracev("PROFILE NOT FOUND :: policy name = {0}, profile name = {1}", policy.getName(), profileName);
continue;
}
if (profile.getExecutors() == null || profile.getExecutors().isEmpty()) {
logger.tracev("PROFILE NO EXECUTOR :: policy name = {0}, profile name = {1}", policy.getName(), profileName);
continue;
}
for (Object obj : profile.getExecutors()) {
ClientPolicyExecutorProvider executor = (ClientPolicyExecutorProvider)obj;
logger.tracev("EXECUTION :: policy name = {0}, profile name = {1}, executor name = {2}, provider id = {3}", policy.getName(), profileName, executor.getName(), executor.getProviderId());
try { try {
op.run(executor); op.run(executor);
} catch(ClientPolicyException cpe) { } catch(ClientPolicyException e) {
ClientPolicyLogger.logv(logger, "EXECUTOR EXCEPTION : name = {0}, provider id = {1}, error = {2}, error_detail = {3}", executor.getName(), executor.getProviderId(), cpe.getError(), cpe.getErrorDetail()); logger.tracev("EXECUTOR EXCEPTION :: executor name = {0}, provider id = {1}, error = {2}, error detail = {3}", executor.getName(), executor.getProviderId(), e.getError(), e.getErrorDetail());
throw cpe; throw e;
} }
} }
} }
}
private interface ClientConditionOperation { private interface ClientConditionOperation {
ClientPolicyVote run(ClientPolicyConditionProvider condition) throws ClientPolicyException; ClientPolicyVote run(ClientPolicyConditionProvider condition) throws ClientPolicyException;
@ -158,4 +170,236 @@ public class DefaultClientPolicyManager implements ClientPolicyManager {
void run(ClientPolicyExecutorProvider executor) throws ClientPolicyException; 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);
}
@Override
public void setupClientPoliciesOnImportedRealm(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;
}
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;
}
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;
try {
validatedJsonString = getValidatedClientProfilesJson(realm, json);
} 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;
}
@Override
public void updateClientPolicies(RealmModel realm, String json) throws ClientPolicyException {
logger.tracev("UPDATE POLICIES :: realm = {0}, PUT = {1}", realm.getName(), json);
String validatedJsonString = null;
try {
validatedJsonString = getValidatedClientPoliciesJson(realm, json);
} catch (ClientPolicyException e) {
logger.warnv("VALIDATE SERIALIZE POLICIES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail());
throw e;
}
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;
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);
}
});
return policiesRep;
}
} }

View file

@ -1,127 +0,0 @@
/*
* Copyright 2020 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.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.clientpolicy.ClientPolicyProvider;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
public class DefaultClientPolicyProvider implements ClientPolicyProvider {
private static final Logger logger = Logger.getLogger(DefaultClientPolicyProvider.class);
private final KeycloakSession session;
private final ComponentModel componentModel;
private final Map<String, List<ClientPolicyConditionProvider>> conditionsMap = new HashMap<>();
private final Map<String, List<ClientPolicyExecutorProvider>> executorsMap = new HashMap<>();
public DefaultClientPolicyProvider(KeycloakSession session, ComponentModel componentModel) {
this.session = session;
this.componentModel = componentModel;
}
@Override
public void close() {
}
@Override
public List<ClientPolicyConditionProvider> getConditions() {
return getConditions(session.getContext().getRealm());
}
@Override
public List<ClientPolicyExecutorProvider> getExecutors() {
return getExecutors(session.getContext().getRealm());
}
@Override
public String getName() {
return componentModel.getName();
}
@Override
public String getProviderId() {
return componentModel.getProviderId();
}
private List<String> getConditionIds() {
return componentModel.getConfig().getList(DefaultClientPolicyProviderFactory.CONDITION_IDS);
}
private List<String> getExecutorIds() {
return componentModel.getConfig().getList(DefaultClientPolicyProviderFactory.EXECUTOR_IDS);
}
private List<ClientPolicyConditionProvider> getConditions(RealmModel realm) {
List<ClientPolicyConditionProvider> providers = conditionsMap.get(realm.getId());
if (providers == null) {
providers = new LinkedList<>();
List<String> conditionIds = getConditionIds();
if (conditionIds == null || conditionIds.isEmpty()) return null;
for(String conditionId : conditionIds) {
ComponentModel cm = session.getContext().getRealm().getComponent(conditionId);
try {
ClientPolicyConditionProvider provider = session.getProvider(ClientPolicyConditionProvider.class, cm);
providers.add(provider);
session.enlistForClose(provider);
ClientPolicyLogger.logv(logger, "Loaded Condition id = {0}, name = {1}, provider id = {2}, is negative logic = {3}", conditionId, cm.getName(), cm.getProviderId(), provider.isNegativeLogic());
} catch (Throwable t) {
logger.errorv(t, "Failed to load condition {0}", cm.getId());
}
}
conditionsMap.put(realm.getId(), providers);
} else {
ClientPolicyLogger.log(logger, "Use cached conditions.");
}
return providers;
}
private List<ClientPolicyExecutorProvider> getExecutors(RealmModel realm) {
List<ClientPolicyExecutorProvider> providers = executorsMap.get(realm.getId());
if (providers == null) {
providers = new LinkedList<>();
List<String> executorIds = getExecutorIds();
if (executorIds == null || executorIds.isEmpty()) return null;
for(String executorId : executorIds) {
ComponentModel cm = session.getContext().getRealm().getComponent(executorId);
try {
ClientPolicyExecutorProvider provider = session.getProvider(ClientPolicyExecutorProvider.class, cm);
providers.add(provider);
session.enlistForClose(provider);
ClientPolicyLogger.logv(logger, "Loaded Executor id = {0}, name = {1}, provider id = {2}", executorId, cm.getName(), cm.getProviderId());
} catch (Throwable t) {
logger.errorv(t, "Failed to load executor {0}", cm.getId());
}
}
executorsMap.put(realm.getId(), providers);
} else {
ClientPolicyLogger.log(logger, "Use cached executors.");
}
return providers;
}
}

View file

@ -1,73 +0,0 @@
/*
* Copyright 2020 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.Arrays;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.ClientPolicyProvider;
import org.keycloak.services.clientpolicy.ClientPolicyProviderFactory;
public class DefaultClientPolicyProviderFactory implements ClientPolicyProviderFactory {
public static final String PROVIDER_ID = "client-policy-provider";
public static final String CONDITION_IDS = "client-policy-condition-ids";
public static final String EXECUTOR_IDS = "client-policy-executor-ids";
private static final ProviderConfigProperty CONDITION_IDS_PROPERTY = new ProviderConfigProperty(CONDITION_IDS, null, null, ProviderConfigProperty.LIST_TYPE, null);
private static final ProviderConfigProperty EXECUTOR_IDS_PROPERTY = new ProviderConfigProperty(EXECUTOR_IDS, null, null, ProviderConfigProperty.LIST_TYPE, null);
@Override
public ClientPolicyProvider create(KeycloakSession session, ComponentModel model) {
return new DefaultClientPolicyProvider(session, model);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return null;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return Arrays.asList(CONDITION_IDS_PROPERTY, EXECUTOR_IDS_PROPERTY);
}
}

View file

@ -1,47 +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 org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
public abstract class AbstractClientPolicyConditionProvider implements ClientPolicyConditionProvider {
protected final KeycloakSession session;
protected final ComponentModel componentModel;
public AbstractClientPolicyConditionProvider(KeycloakSession session, ComponentModel componentModel) {
this.session = session;
this.componentModel = componentModel;
}
@Override
public boolean isNegativeLogic() {
return Boolean.valueOf(componentModel.getConfig().getFirst(AbstractClientPolicyConditionProviderFactory.IS_NEGATIVE_LOGIC)).booleanValue();
}
@Override
public String getName() {
return componentModel.getName();
}
@Override
public String getProviderId() {
return componentModel.getProviderId();
}
}

View file

@ -1,51 +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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
public abstract class AbstractClientPolicyConditionProviderFactory implements ClientPolicyConditionProviderFactory {
public static final String IS_NEGATIVE_LOGIC = "is-negative-logic";
private static final ProviderConfigProperty IS_NEGATIVE_LOGIC_PROPERTY = new ProviderConfigProperty(IS_NEGATIVE_LOGIC, "clientpolicycondition-is-negative-logic.label", "clientpolicycondition-is-negative-logic.tooltip", ProviderConfigProperty.BOOLEAN_TYPE, false);
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return new ArrayList<>(Arrays.asList(IS_NEGATIVE_LOGIC_PROPERTY));
}
}

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,19 +17,59 @@
package org.keycloak.services.clientpolicy.condition; package org.keycloak.services.clientpolicy.condition;
import org.jboss.logging.Logger; import java.util.Optional;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.ClientPolicyVote;
public class AnyClientCondition extends AbstractClientPolicyConditionProvider { import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
private static final Logger logger = Logger.getLogger(AnyClientCondition.class); /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class AnyClientCondition implements ClientPolicyConditionProvider<AnyClientCondition.Configuration> {
public AnyClientCondition(KeycloakSession session, ComponentModel componentModel) { // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up.
super(session, componentModel); private Configuration configuration = new Configuration();
public AnyClientCondition(KeycloakSession 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();
}
@Override
public String getProviderId() {
return AnyClientConditionFactory.PROVIDER_ID;
} }
@Override @Override

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,16 +17,36 @@
package org.keycloak.services.clientpolicy.condition; package org.keycloak.services.clientpolicy.condition;
import org.keycloak.component.ComponentModel; import java.util.Collections;
import org.keycloak.models.KeycloakSession; import java.util.List;
public class AnyClientConditionFactory extends AbstractClientPolicyConditionProviderFactory { import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class AnyClientConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "anyclient-condition"; public static final String PROVIDER_ID = "anyclient-condition";
@Override @Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyConditionProvider create(KeycloakSession session) {
return new AnyClientCondition(session, model); return new AnyClientCondition(session);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
} }
@Override @Override
@ -38,4 +58,10 @@ public class AnyClientConditionFactory extends AbstractClientPolicyConditionProv
public String getHelpText() { public String getHelpText() {
return "The condition is satisfied by any client on any event."; return "The condition is satisfied by any client on any event.";
} }
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return Collections.emptyList();
}
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -19,22 +19,75 @@ package org.keycloak.services.clientpolicy.condition;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.ClientPolicyVote;
public class ClientAccessTypeCondition extends AbstractClientPolicyConditionProvider { 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> {
private static final Logger logger = Logger.getLogger(ClientAccessTypeCondition.class); private static final Logger logger = Logger.getLogger(ClientAccessTypeCondition.class);
public ClientAccessTypeCondition(KeycloakSession session, ComponentModel componentModel) { // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up.
super(session, componentModel); 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;
}
@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;
}
protected List<String> type;
public List<String> getType() {
return type;
}
public void setType(List<String> type) {
this.type = type;
}
}
@Override
public boolean isNegativeLogic() {
return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue();
}
@Override
public String getProviderId() {
return ClientAccessTypeConditionFactory.PROVIDER_ID;
} }
@Override @Override
@ -65,22 +118,16 @@ public class ClientAccessTypeCondition extends AbstractClientPolicyConditionProv
private boolean isClientAccessTypeMatched() { private boolean isClientAccessTypeMatched() {
final String accessType = getClientAccessType(); final String accessType = getClientAccessType();
if (accessType == null) return false;
List<String> expectedAccessTypes = componentModel.getConfig().get(ClientAccessTypeConditionFactory.TYPE); List<String> expectedAccessTypes = Optional.ofNullable(configuration.getType()).orElse(Collections.emptyList());
if (expectedAccessTypes == null) expectedAccessTypes = Collections.emptyList();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
ClientPolicyLogger.log(logger, "client access type = " + accessType); logger.tracev("accessType = {0}", accessType);
expectedAccessTypes.stream().forEach(i -> ClientPolicyLogger.log(logger, "client access type expected = " + i)); expectedAccessTypes.stream().forEach(i -> logger.tracev("expected accessType = {0}", i));
} }
boolean isMatched = expectedAccessTypes.stream().anyMatch(i -> i.equals(accessType)); return expectedAccessTypes.stream().anyMatch(i -> i.equals(accessType));
if (isMatched) {
ClientPolicyLogger.log(logger, "client access type matched.");
} else {
ClientPolicyLogger.log(logger, "client access type unmatched.");
}
return isMatched;
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,30 +17,53 @@
package org.keycloak.services.clientpolicy.condition; package org.keycloak.services.clientpolicy.condition;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.keycloak.component.ComponentModel; import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
public class ClientAccessTypeConditionFactory extends AbstractClientPolicyConditionProviderFactory { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientAccessTypeConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "client-accesstype-condition"; public static final String PROVIDER_ID = "client-accesstype-condition";
public static final String TYPE = "type"; public static final String TYPE = "type";
public static final String TYPE_CONFIDENTIAL = "confidential"; public static final String TYPE_CONFIDENTIAL = "confidential";
public static final String TYPE_PUBLIC = "public"; public static final String TYPE_PUBLIC = "public";
public static final String TYPE_BEARERONLY = "bearer-only"; public static final String TYPE_BEARERONLY = "bearer-only";
private static final ProviderConfigProperty CLIENTACCESSTYPE_PROPERTY; private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static { static {
CLIENTACCESSTYPE_PROPERTY = new ProviderConfigProperty(TYPE, "client-accesstype.label", "client-accesstype.tooltip", ProviderConfigProperty.MULTIVALUED_LIST_TYPE, TYPE_CONFIDENTIAL); ProviderConfigProperty property;
CLIENTACCESSTYPE_PROPERTY.setOptions(Arrays.asList(TYPE_CONFIDENTIAL, TYPE_PUBLIC, TYPE_BEARERONLY)); property = new ProviderConfigProperty(TYPE, "client-accesstype.label", "client-accesstype.tooltip", ProviderConfigProperty.MULTIVALUED_LIST_TYPE, TYPE_CONFIDENTIAL);
List<String> updateProfileValues = Arrays.asList(TYPE_CONFIDENTIAL, TYPE_PUBLIC, TYPE_BEARERONLY);
property.setOptions(updateProfileValues);
configProperties.add(property);
} }
@Override @Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyConditionProvider create(KeycloakSession session) {
return new ClientAccessTypeCondition(session, model); return new ClientAccessTypeCondition(session);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
} }
@Override @Override
@ -55,8 +78,7 @@ public class ClientAccessTypeConditionFactory extends AbstractClientPolicyCondit
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
List<ProviderConfigProperty> l = super.getConfigProperties(); return configProperties;
l.add(CLIENTACCESSTYPE_PROPERTY);
return l;
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -19,25 +19,78 @@ package org.keycloak.services.clientpolicy.condition;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.ClientPolicyVote;
public class ClientRolesCondition extends AbstractClientPolicyConditionProvider { 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> {
private static final Logger logger = Logger.getLogger(ClientRolesCondition.class); private static final Logger logger = Logger.getLogger(ClientRolesCondition.class);
public ClientRolesCondition(KeycloakSession session, ComponentModel componentModel) { // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up.
super(session, componentModel); 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;
}
@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;
}
protected List<String> roles;
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
}
@Override
public boolean isNegativeLogic() {
return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue();
}
@Override
public String getProviderId() {
return ClientRolesConditionFactory.PROVIDER_ID;
} }
@Override @Override
@ -67,25 +120,16 @@ public class ClientRolesCondition extends AbstractClientPolicyConditionProvider
Set<String> clientRoles = client.getRolesStream().map(RoleModel::getName).collect(Collectors.toSet()); Set<String> clientRoles = client.getRolesStream().map(RoleModel::getName).collect(Collectors.toSet());
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
clientRoles.stream().forEach(i -> ClientPolicyLogger.log(logger, "client role assigned = " + i)); clientRoles.forEach(i -> logger.tracev("client role assigned = {0}", i));
rolesForMatching.stream().forEach(i -> ClientPolicyLogger.log(logger, "client role for matching = " + i)); rolesForMatching.forEach(i -> logger.tracev("client role for matching = {0}", i));
} }
boolean isMatched = rolesForMatching.removeAll(clientRoles); return rolesForMatching.removeAll(clientRoles); // may change rolesForMatching so that it has needed to be instantiated.
if (isMatched) {
ClientPolicyLogger.log(logger, "role matched.");
} else {
ClientPolicyLogger.log(logger, "role unmatched.");
}
return isMatched;
} }
private Set<String> getRolesForMatching() { private Set<String> getRolesForMatching() {
if (componentModel.getConfig() == null) return null; if (configuration.getRoles() == null) return null;
List<String> roles = componentModel.getConfig().get(ClientRolesConditionFactory.ROLES); return new HashSet<>(configuration.getRoles());
if (roles == null) return null;
return new HashSet<>(roles);
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,23 +17,46 @@
package org.keycloak.services.clientpolicy.condition; package org.keycloak.services.clientpolicy.condition;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.keycloak.component.ComponentModel; import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
public class ClientRolesConditionFactory extends AbstractClientPolicyConditionProviderFactory { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientRolesConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "clientroles-condition"; public static final String PROVIDER_ID = "clientroles-condition";
public static final String ROLES = "roles"; public static final String ROLES = "roles";
private static final ProviderConfigProperty CLIENTROLES_PROPERTY = new ProviderConfigProperty( private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
ROLES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, null);
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty(ROLES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, null);
configProperties.add(property);
}
@Override @Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyConditionProvider create(KeycloakSession session) {
return new ClientRolesCondition(session, model); return new ClientRolesCondition(session);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
} }
@Override @Override
@ -48,8 +71,7 @@ public class ClientRolesConditionFactory extends AbstractClientPolicyConditionPr
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
List<ProviderConfigProperty> l = super.getConfigProperties(); return configProperties;
l.add(CLIENTROLES_PROPERTY);
return l;
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,28 +21,90 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.ClientPolicyVote;
import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext;
import org.keycloak.services.clientpolicy.context.TokenRequestContext; import org.keycloak.services.clientpolicy.context.TokenRequestContext;
public class ClientScopesCondition extends AbstractClientPolicyConditionProvider { 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> {
private static final Logger logger = Logger.getLogger(ClientScopesCondition.class); private static final Logger logger = Logger.getLogger(ClientScopesCondition.class);
public ClientScopesCondition(KeycloakSession session, ComponentModel componentModel) { // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up.
super(session, componentModel); 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;
}
@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;
}
protected String type;
protected List<String> scope;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<String> getScope() {
return scope;
}
public void setScope(List<String> scope) {
this.scope = scope;
}
}
@Override
public boolean isNegativeLogic() {
return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue();
}
@Override
public String getProviderId() {
return ClientScopesConditionFactory.PROVIDER_ID;
} }
@Override @Override
@ -75,25 +137,27 @@ public class ClientScopesCondition extends AbstractClientPolicyConditionProvider
Set<String> defaultScopes = client.getClientScopes(true).keySet(); Set<String> defaultScopes = client.getClientScopes(true).keySet();
Set<String> optionalScopes = client.getClientScopes(false).keySet(); Set<String> optionalScopes = client.getClientScopes(false).keySet();
Set<String> expectedScopes = getScopesForMatching(); Set<String> expectedScopes = getScopesForMatching();
if (expectedScopes == null) expectedScopes = new HashSet<>(); if (expectedScopes == null) return false;
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
explicitSpecifiedScopes.stream().forEach(i -> ClientPolicyLogger.log(logger, " explicit specified client scope = " + i)); explicitSpecifiedScopes.forEach(i -> logger.tracev("explicit specified client scope = {0}", i));
defaultScopes.stream().forEach(i -> ClientPolicyLogger.log(logger, " default client scope = " + i)); defaultScopes.forEach(i -> logger.tracev("default client scope = {0}", i));
optionalScopes.stream().forEach(i -> ClientPolicyLogger.log(logger, " optional client scope = " + i)); optionalScopes.forEach(i -> logger.tracev("optional client scope = {0}", i));
expectedScopes.stream().forEach(i -> ClientPolicyLogger.log(logger, " expected scope = " + i)); expectedScopes.forEach(i -> logger.tracev("expected scope = {0}", i));
} }
boolean isDefaultScope = ClientScopesConditionFactory.DEFAULT.equals(componentModel.getConfig().getFirst(ClientScopesConditionFactory.TYPE)); boolean isDefaultScope = ClientScopesConditionFactory.DEFAULT.equals(configuration.getType());
if (isDefaultScope) { if (isDefaultScope) {
expectedScopes.retainAll(defaultScopes); expectedScopes.retainAll(defaultScopes); // may change expectedScopes so that it has needed to be instantiated.
return expectedScopes.isEmpty() ? false : true; return expectedScopes.isEmpty() ? false : true;
} else { } else {
explicitSpecifiedScopes.retainAll(expectedScopes); explicitSpecifiedScopes.retainAll(expectedScopes);
explicitSpecifiedScopes.retainAll(optionalScopes); explicitSpecifiedScopes.retainAll(optionalScopes);
if (!explicitSpecifiedScopes.isEmpty()) { if (!explicitSpecifiedScopes.isEmpty()) {
explicitSpecifiedScopes.stream().forEach(i->{ClientPolicyLogger.log(logger, " matched scope = " + i);}); if (logger.isTraceEnabled()) {
explicitSpecifiedScopes.forEach(i->logger.tracev("matched scope = {0}", i));
}
return true; return true;
} }
} }
@ -101,9 +165,9 @@ public class ClientScopesCondition extends AbstractClientPolicyConditionProvider
} }
private Set<String> getScopesForMatching() { private Set<String> getScopesForMatching() {
if (componentModel.getConfig() == null) return null; List<String> scopes = configuration.getScope();
List<String> scopes = componentModel.getConfig().get(ClientScopesConditionFactory.SCOPES);
if (scopes == null) return null; if (scopes == null) return null;
return new HashSet<>(scopes); return new HashSet<>(scopes);
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,28 +17,51 @@
package org.keycloak.services.clientpolicy.condition; package org.keycloak.services.clientpolicy.condition;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.keycloak.component.ComponentModel; import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
public class ClientScopesConditionFactory extends AbstractClientPolicyConditionProviderFactory { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientScopesConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "clientscopes-condition"; public static final String PROVIDER_ID = "clientscopes-condition";
public static final String SCOPES = "scopes"; public static final String SCOPES = "scopes";
public static final String TYPE = "type"; public static final String TYPE = "type";
public static final String DEFAULT = "Default"; public static final String DEFAULT = "Default";
public static final String OPTIONAL = "Optional"; public static final String OPTIONAL = "Optional";
private static final ProviderConfigProperty CLIENTSCOPES_PROPERTY = new ProviderConfigProperty( private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
SCOPES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "offline_access");
private static final ProviderConfigProperty CLIENTSCOPETYPE_PROPERTY = new ProviderConfigProperty( static {
TYPE, "Scope Type", "Default or Optional", ProviderConfigProperty.LIST_TYPE, OPTIONAL); ProviderConfigProperty property;
property = new ProviderConfigProperty(SCOPES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "offline_access");
configProperties.add(property);
property = new ProviderConfigProperty(TYPE, "Scope Type", "Default or Optional", ProviderConfigProperty.LIST_TYPE, OPTIONAL);
configProperties.add(property);
}
@Override @Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyConditionProvider create(KeycloakSession session) {
return new ClientScopesCondition(session, model); return new ClientScopesCondition(session);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
} }
@Override @Override
@ -53,9 +76,7 @@ public class ClientScopesConditionFactory extends AbstractClientPolicyConditionP
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
List<ProviderConfigProperty> l = super.getConfigProperties(); return configProperties;
l.add(CLIENTSCOPES_PROPERTY);
l.add(CLIENTSCOPETYPE_PROPERTY);
return l;
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -19,25 +19,79 @@ package org.keycloak.services.clientpolicy.condition;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.JsonWebToken;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.ClientPolicyVote;
import org.keycloak.services.clientpolicy.context.ClientCRUDContext; import org.keycloak.services.clientpolicy.context.ClientCRUDContext;
import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils; import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils;
import org.keycloak.util.TokenUtil; import org.keycloak.util.TokenUtil;
public class ClientUpdateContextCondition extends AbstractClientPolicyConditionProvider { 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> {
private static final Logger logger = Logger.getLogger(ClientUpdateContextCondition.class); private static final Logger logger = Logger.getLogger(ClientUpdateContextCondition.class);
public ClientUpdateContextCondition(KeycloakSession session, ComponentModel componentModel) { // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up.
super(session, componentModel); 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;
}
@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;
}
@JsonProperty("update-client-source")
protected List<String> updateClientSource;
public List<String> getUpdateClientSource() {
return updateClientSource;
}
public void setUpdateClientSource(List<String> updateClientSource) {
this.updateClientSource = updateClientSource;
}
}
@Override
public boolean isNegativeLogic() {
return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue();
}
@Override
public String getProviderId() {
return ClientUpdateContextConditionFactory.PROVIDER_ID;
} }
@Override @Override
@ -55,21 +109,15 @@ public class ClientUpdateContextCondition extends AbstractClientPolicyConditionP
private boolean isAuthMethodMatched(String authMethod) { private boolean isAuthMethodMatched(String authMethod) {
if (authMethod == null) return false; if (authMethod == null) return false;
List<String> expectedAuthMethods = componentModel.getConfig().get(ClientUpdateContextConditionFactory.UPDATE_CLIENT_SOURCE); List<String> expectedAuthMethods = configuration.getUpdateClientSource();
if (expectedAuthMethods == null) expectedAuthMethods = Collections.emptyList(); if (expectedAuthMethods == null) expectedAuthMethods = Collections.emptyList();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
ClientPolicyLogger.log(logger, "auth method = " + authMethod); logger.tracev("auth method = {0}", authMethod);
expectedAuthMethods.stream().forEach(i -> ClientPolicyLogger.log(logger, "auth method expected = " + i)); expectedAuthMethods.stream().forEach(i -> logger.tracev("auth method expected = {0}", i));
} }
boolean isMatched = expectedAuthMethods.stream().anyMatch(i -> i.equals(authMethod)); return expectedAuthMethods.stream().anyMatch(i -> i.equals(authMethod));
if (isMatched) {
ClientPolicyLogger.log(logger, "auth method matched.");
} else {
ClientPolicyLogger.log(logger, "auth method unmatched.");
}
return isMatched;
} }
private boolean isAuthMethodMatched(ClientCRUDContext context) { private boolean isAuthMethodMatched(ClientCRUDContext context) {
@ -103,4 +151,5 @@ public class ClientUpdateContextCondition extends AbstractClientPolicyConditionP
private boolean isBearerToken(JsonWebToken jwt) { private boolean isBearerToken(JsonWebToken jwt) {
return jwt != null && TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType()); return jwt != null && TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType());
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,15 +17,19 @@
package org.keycloak.services.clientpolicy.condition; package org.keycloak.services.clientpolicy.condition;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.keycloak.component.ComponentModel; import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
public class ClientUpdateContextConditionFactory extends AbstractClientPolicyConditionProviderFactory { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientUpdateContextConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "clientupdatecontext-condition"; public static final String PROVIDER_ID = "clientupdatecontext-condition";
@ -36,17 +40,31 @@ public class ClientUpdateContextConditionFactory extends AbstractClientPolicyCon
public static final String BY_INITIAL_ACCESS_TOKEN = "ByInitialAccessToken"; public static final String BY_INITIAL_ACCESS_TOKEN = "ByInitialAccessToken";
public static final String BY_REGISTRATION_ACCESS_TOKEN = "ByRegistrationAccessToken"; public static final String BY_REGISTRATION_ACCESS_TOKEN = "ByRegistrationAccessToken";
private static final ProviderConfigProperty CLIENTUPDATESOURCE_PROPERTY; private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static { static {
CLIENTUPDATESOURCE_PROPERTY = new ProviderConfigProperty( ProviderConfigProperty property;
UPDATE_CLIENT_SOURCE, null, null, ProviderConfigProperty.MULTIVALUED_LIST_TYPE, BY_AUTHENTICATED_USER); property = new ProviderConfigProperty(UPDATE_CLIENT_SOURCE, null, null, ProviderConfigProperty.MULTIVALUED_LIST_TYPE, BY_AUTHENTICATED_USER);
CLIENTUPDATESOURCE_PROPERTY.setOptions( List<String> updateProfileValues = Arrays.asList(BY_AUTHENTICATED_USER, BY_ANONYMOUS, BY_INITIAL_ACCESS_TOKEN, BY_REGISTRATION_ACCESS_TOKEN);
Arrays.asList(BY_AUTHENTICATED_USER, BY_ANONYMOUS, BY_INITIAL_ACCESS_TOKEN, BY_REGISTRATION_ACCESS_TOKEN)); property.setOptions(updateProfileValues);
configProperties.add(property);
} }
@Override @Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyConditionProvider create(KeycloakSession session) {
return new ClientUpdateContextCondition(session, model); return new ClientUpdateContextCondition(session);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
} }
@Override @Override
@ -61,8 +79,7 @@ public class ClientUpdateContextConditionFactory extends AbstractClientPolicyCon
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
List<ProviderConfigProperty> l = super.getConfigProperties(); return configProperties;
l.add(CLIENTUPDATESOURCE_PROPERTY);
return l;
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -19,19 +19,18 @@ package org.keycloak.services.clientpolicy.condition;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.JsonWebToken;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.ClientPolicyVote;
import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext; import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext;
import org.keycloak.services.clientpolicy.context.AdminClientUpdateContext; import org.keycloak.services.clientpolicy.context.AdminClientUpdateContext;
@ -39,12 +38,66 @@ import org.keycloak.services.clientpolicy.context.ClientCRUDContext;
import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext; import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext;
import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext; import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext;
public class ClientUpdateSourceGroupsCondition extends AbstractClientPolicyConditionProvider { 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> {
private static final Logger logger = Logger.getLogger(ClientUpdateSourceGroupsCondition.class); private static final Logger logger = Logger.getLogger(ClientUpdateSourceGroupsCondition.class);
public ClientUpdateSourceGroupsCondition(KeycloakSession session, ComponentModel componentModel) { // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up.
super(session, componentModel); 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;
}
@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;
}
protected List<String> groups;
public List<String> getGroups() {
return groups;
}
public void setGroups(List<String> groups) {
this.groups = groups;
}
}
@Override
public boolean isNegativeLogic() {
return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue();
}
@Override
public String getProviderId() {
return ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID;
} }
@Override @Override
@ -97,23 +150,17 @@ public class ClientUpdateSourceGroupsCondition extends AbstractClientPolicyCondi
Set<String> groups = user.getGroupsStream().map(GroupModel::getName).collect(Collectors.toSet()); Set<String> groups = user.getGroupsStream().map(GroupModel::getName).collect(Collectors.toSet());
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
groups.stream().forEach(i -> ClientPolicyLogger.log(logger, " user group = " + i)); groups.forEach(i -> logger.tracev("user group = {0}", i));
expectedGroups.stream().forEach(i -> ClientPolicyLogger.log(logger, "groups expected = " + i)); expectedGroups.forEach(i -> logger.tracev("expected user group = {0}", i));
} }
boolean isMatched = expectedGroups.removeAll(groups); return expectedGroups.removeAll(groups); // may change expectedGroups so that it has needed to be instantiated.
if (isMatched) {
ClientPolicyLogger.log(logger, "group matched.");
} else {
ClientPolicyLogger.log(logger, "group unmatched.");
}
return isMatched;
} }
private Set<String> instantiateGroupsForMatching() { private Set<String> instantiateGroupsForMatching() {
if (componentModel.getConfig() == null) return null; List<String> groups = configuration.getGroups();
List<String> roles = componentModel.getConfig().get(ClientUpdateSourceGroupsConditionFactory.GROUPS); if (groups == null) return null;
if (roles == null) return null; return new HashSet<>(groups);
return new HashSet<>(roles);
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,24 +17,46 @@
package org.keycloak.services.clientpolicy.condition; package org.keycloak.services.clientpolicy.condition;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.keycloak.component.ComponentModel; import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
public class ClientUpdateSourceGroupsConditionFactory extends AbstractClientPolicyConditionProviderFactory { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientUpdateSourceGroupsConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "clientupdatesourcegroups-condition"; public static final String PROVIDER_ID = "clientupdatesourcegroups-condition";
public static final String GROUPS = "groups"; public static final String GROUPS = "groups";
private static final ProviderConfigProperty CLIENTUPDATEGROUP_PROPERTY = new ProviderConfigProperty( private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
GROUPS, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "topGroup");
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty(GROUPS, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "topGroup");
configProperties.add(property);
}
@Override @Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyConditionProvider create(KeycloakSession session) {
return new ClientUpdateSourceGroupsCondition(session, model); return new ClientUpdateSourceGroupsCondition(session);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
} }
@Override @Override
@ -49,8 +71,7 @@ public class ClientUpdateSourceGroupsConditionFactory extends AbstractClientPoli
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
List<ProviderConfigProperty> l = super.getConfigProperties(); return configProperties;
l.add(CLIENTUPDATEGROUP_PROPERTY);
return l;
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,22 +21,87 @@ import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.ClientPolicyVote;
public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyConditionProvider { 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> {
private static final Logger logger = Logger.getLogger(ClientUpdateSourceHostsCondition.class); private static final Logger logger = Logger.getLogger(ClientUpdateSourceHostsCondition.class);
public ClientUpdateSourceHostsCondition(KeycloakSession session, ComponentModel componentModel) { // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up.
super(session, componentModel); 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;
}
@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;
}
@JsonProperty("trusted-hosts")
protected List<String> trustedHosts;
@JsonProperty("host-sending-request-must-match")
protected List<Boolean> hostSendingRequestMustMatch;
public List<String> getTrustedHosts() {
return trustedHosts;
}
public void setTrustedHosts(List<String> trustedHosts) {
this.trustedHosts = trustedHosts;
}
public List<Boolean> getHostSendingRequestMustMatch() {
return hostSendingRequestMustMatch;
}
public void setHostSendingRequestMustMatch(List<Boolean> hostSendingRequestMustMatch) {
this.hostSendingRequestMustMatch = hostSendingRequestMustMatch;
}
}
@Override
public boolean isNegativeLogic() {
return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue();
}
@Override
public String getProviderId() {
return ClientUpdateSourceHostsConditionFactory.PROVIDER_ID;
} }
@Override @Override
@ -55,7 +120,7 @@ public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyCondit
private boolean isHostMatched() { private boolean isHostMatched() {
String hostAddress = session.getContext().getConnection().getRemoteAddr(); String hostAddress = session.getContext().getConnection().getRemoteAddr();
ClientPolicyLogger.logv(logger, "Verifying remote host {0}", hostAddress); logger.tracev("Verifying remote host = {0}", hostAddress);
List<String> trustedHosts = getTrustedHosts(); List<String> trustedHosts = getTrustedHosts();
List<String> trustedDomains = getTrustedDomains(); List<String> trustedDomains = getTrustedDomains();
@ -76,16 +141,14 @@ public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyCondit
} }
protected List<String> getTrustedHosts() { protected List<String> getTrustedHosts() {
List<String> trustedHostsConfig = componentModel.getConfig().getList(ClientUpdateSourceHostsConditionFactory.TRUSTED_HOSTS); List<String> trustedHostsConfig = configuration.getTrustedHosts();
return trustedHostsConfig.stream().filter((String hostname) -> { return trustedHostsConfig.stream().filter((String hostname) -> {
return !hostname.startsWith("*."); return !hostname.startsWith("*.");
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
protected List<String> getTrustedDomains() { protected List<String> getTrustedDomains() {
List<String> trustedHostsConfig = componentModel.getConfig().getList(ClientUpdateSourceHostsConditionFactory.TRUSTED_HOSTS); List<String> trustedHostsConfig = configuration.getTrustedHosts();
List<String> domains = new LinkedList<>(); List<String> domains = new LinkedList<>();
for (String hostname : trustedHostsConfig) { for (String hostname : trustedHostsConfig) {
@ -102,14 +165,13 @@ public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyCondit
for (String confHostName : trustedHosts) { for (String confHostName : trustedHosts) {
try { try {
String hostIPAddress = InetAddress.getByName(confHostName).getHostAddress(); String hostIPAddress = InetAddress.getByName(confHostName).getHostAddress();
logger.tracev("Trying host {0} of address {1}", confHostName, hostIPAddress);
ClientPolicyLogger.logv(logger, "Trying host {0} of address {1}", confHostName, hostIPAddress);
if (hostIPAddress.equals(hostAddress)) { if (hostIPAddress.equals(hostAddress)) {
ClientPolicyLogger.logv(logger, "Successfully verified host : {0}", confHostName); logger.tracev("Successfully verified host = {0}", confHostName);
return confHostName; return confHostName;
} }
} catch (UnknownHostException uhe) { } catch (UnknownHostException uhe) {
ClientPolicyLogger.logv(logger, "Unknown host from realm configuration: {0}", confHostName); logger.tracev("Unknown host from realm configuration = {0}", confHostName);
} }
} }
@ -120,17 +182,15 @@ public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyCondit
if (!trustedDomains.isEmpty()) { if (!trustedDomains.isEmpty()) {
try { try {
String hostname = InetAddress.getByName(hostAddress).getHostName(); String hostname = InetAddress.getByName(hostAddress).getHostName();
logger.tracev("Trying verify request from address {0} of host {1} by domains", hostAddress, hostname);
ClientPolicyLogger.logv(logger, "Trying verify request from address {0} of host {1} by domains", hostAddress, hostname);
for (String confDomain : trustedDomains) { for (String confDomain : trustedDomains) {
if (hostname.endsWith(confDomain)) { if (hostname.endsWith(confDomain)) {
ClientPolicyLogger.logv(logger, "Successfully verified host {0} by trusted domain {1}", hostname, confDomain); logger.tracev("Successfully verified host {0} by trusted domain {1}", hostname, confDomain);
return hostname; return hostname;
} }
} }
} catch (UnknownHostException uhe) { } catch (UnknownHostException uhe) {
ClientPolicyLogger.logv(logger, "Request of address {0} came from unknown host. Skip verification by domains", hostAddress); logger.tracev("Request of address {0} came from unknown host. Skip verification by domains", hostAddress);
} }
} }
@ -138,13 +198,8 @@ public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyCondit
} }
boolean isHostMustMatch() { boolean isHostMustMatch() {
return parseBoolean(ClientUpdateSourceHostsConditionFactory.HOST_SENDING_REQUEST_MUST_MATCH); List<Boolean> l = configuration.getHostSendingRequestMustMatch();
if (l != null && !l.isEmpty()) return l.get(0).booleanValue();
return true;
} }
// True by default
private boolean parseBoolean(String propertyKey) {
String val = componentModel.getConfig().getFirst(propertyKey);
return val==null || Boolean.parseBoolean(val);
}
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,16 +17,18 @@
package org.keycloak.services.clientpolicy.condition; package org.keycloak.services.clientpolicy.condition;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.keycloak.component.ComponentModel; import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
public class ClientUpdateSourceHostsConditionFactory extends AbstractClientPolicyConditionProviderFactory { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientUpdateSourceHostsConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "clientupdatesourcehost-condition"; public static final String PROVIDER_ID = "clientupdatesourcehost-condition";
@ -40,8 +42,20 @@ public class ClientUpdateSourceHostsConditionFactory extends AbstractClientPolic
"host-sending-request-must-match.tooltip", ProviderConfigProperty.BOOLEAN_TYPE, "true"); "host-sending-request-must-match.tooltip", ProviderConfigProperty.BOOLEAN_TYPE, "true");
@Override @Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyConditionProvider create(KeycloakSession session) {
return new ClientUpdateSourceHostsCondition(session, model); return new ClientUpdateSourceHostsCondition(session);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
} }
@Override @Override
@ -56,21 +70,7 @@ public class ClientUpdateSourceHostsConditionFactory extends AbstractClientPolic
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
List<ProviderConfigProperty> l = super.getConfigProperties(); return Arrays.asList(TRUSTED_HOSTS_PROPERTY, HOST_SENDING_REGISTRATION_REQUEST_MUST_MATCH_PROPERTY);
l.add(TRUSTED_HOSTS_PROPERTY);
l.add(HOST_SENDING_REGISTRATION_REQUEST_MUST_MATCH_PROPERTY);
return l;
}
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
ConfigurationValidationHelper.check(config)
.checkBoolean(HOST_SENDING_REGISTRATION_REQUEST_MUST_MATCH_PROPERTY, true);
ClientUpdateSourceHostsCondition policy = new ClientUpdateSourceHostsCondition(session, config);
if (!policy.isHostMustMatch()) {
throw new ComponentValidationException("At least one of hosts verification must be enabled");
} }
} }
}

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -19,12 +19,12 @@ package org.keycloak.services.clientpolicy.condition;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
@ -32,7 +32,6 @@ import org.keycloak.models.UserModel;
import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.JsonWebToken;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.ClientPolicyVote;
import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext; import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext;
import org.keycloak.services.clientpolicy.context.AdminClientUpdateContext; import org.keycloak.services.clientpolicy.context.AdminClientUpdateContext;
@ -40,12 +39,66 @@ import org.keycloak.services.clientpolicy.context.ClientCRUDContext;
import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext; import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext;
import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext; import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext;
public class ClientUpdateSourceRolesCondition extends AbstractClientPolicyConditionProvider { 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> {
private static final Logger logger = Logger.getLogger(ClientUpdateSourceRolesCondition.class); private static final Logger logger = Logger.getLogger(ClientUpdateSourceRolesCondition.class);
public ClientUpdateSourceRolesCondition(KeycloakSession session, ComponentModel componentModel) { // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up.
super(session, componentModel); 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;
}
@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;
}
protected List<String> roles;
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
}
@Override
public boolean isNegativeLogic() {
return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue();
}
@Override
public String getProviderId() {
return ClientUpdateSourceRolesConditionFactory.PROVIDER_ID;
} }
@Override @Override
@ -98,8 +151,8 @@ public class ClientUpdateSourceRolesCondition extends AbstractClientPolicyCondit
Set<String> roles = user.getRoleMappingsStream().map(RoleModel::getName).collect(Collectors.toSet()); Set<String> roles = user.getRoleMappingsStream().map(RoleModel::getName).collect(Collectors.toSet());
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
roles.stream().forEach(i -> ClientPolicyLogger.log(logger, " user role = " + i)); roles.forEach(i -> logger.tracev("user role = {0}", i));
expectedRoles.stream().forEach(i -> ClientPolicyLogger.log(logger, "roles expected = " + i)); expectedRoles.forEach(i -> logger.tracev("roles expected = {0}", i));
} }
RealmModel realm = session.getContext().getRealm(); RealmModel realm = session.getContext().getRealm();
@ -114,18 +167,14 @@ public class ClientUpdateSourceRolesCondition extends AbstractClientPolicyCondit
return false; return false;
}); });
}); });
if (isMatched) {
ClientPolicyLogger.log(logger, "role matched.");
} else {
ClientPolicyLogger.log(logger, "role unmatched.");
}
return isMatched; return isMatched;
} }
private Set<String> instantiateRolesForMatching() { private Set<String> instantiateRolesForMatching() {
if (componentModel.getConfig() == null) return null; List<String> roles = configuration.getRoles();
List<String> roles = componentModel.getConfig().get(ClientUpdateSourceRolesConditionFactory.ROLES);
if (roles == null) return null; if (roles == null) return null;
return new HashSet<>(roles); return new HashSet<>(roles);
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,23 +17,46 @@
package org.keycloak.services.clientpolicy.condition; package org.keycloak.services.clientpolicy.condition;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.keycloak.component.ComponentModel; import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
public class ClientUpdateSourceRolesConditionFactory extends AbstractClientPolicyConditionProviderFactory { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class ClientUpdateSourceRolesConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "clientupdatesourceroles-condition"; public static final String PROVIDER_ID = "clientupdatesourceroles-condition";
public static final String ROLES = "roles"; public static final String ROLES = "roles";
private static final ProviderConfigProperty CLIENTUPDATEROLE_PROPERTY = new ProviderConfigProperty( private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
ROLES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "admin");
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty(ROLES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "admin");
configProperties.add(property);
}
@Override @Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyConditionProvider create(KeycloakSession session) {
return new ClientUpdateSourceRolesCondition(session, model); return new ClientUpdateSourceRolesCondition(session);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
} }
@Override @Override
@ -44,12 +67,12 @@ public class ClientUpdateSourceRolesConditionFactory extends AbstractClientPolic
@Override @Override
public String getHelpText() { public String getHelpText() {
return "The condition checks the role of the entity who tries to create/update the client to determine whether the policy is applied."; return "The condition checks the role of the entity who tries to create/update the client to determine whether the policy is applied.";
} }
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
List<ProviderConfigProperty> l = super.getConfigProperties(); return configProperties;
l.add(CLIENTUPDATEROLE_PROPERTY);
return l;
} }
} }

View file

@ -38,7 +38,7 @@ abstract class AbstractDynamicClientCRUDContext implements ClientCRUDContext {
this.authenticatedClient = realm.getClientByClientId(token.getIssuedFor()); this.authenticatedClient = realm.getClientByClientId(token.getIssuedFor());
} }
if (token.getSubject() != null) { if (token.getSubject() != null) {
this.authenticatedUser = session.users().getUserById(token.getSubject(), realm); this.authenticatedUser = session.users().getUserById(realm, token.getSubject());
} }
} }

View file

@ -24,6 +24,9 @@ import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyEvent;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class AuthorizationRequestContext implements ClientPolicyContext { public class AuthorizationRequestContext implements ClientPolicyContext {
private final OIDCResponseType parsedResponseType; private final OIDCResponseType parsedResponseType;

View file

@ -22,6 +22,9 @@ import javax.ws.rs.core.MultivaluedMap;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyEvent;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class LogoutRequestContext implements ClientPolicyContext { public class LogoutRequestContext implements ClientPolicyContext {
private final MultivaluedMap<String, String> params; private final MultivaluedMap<String, String> params;

View file

@ -22,6 +22,9 @@ import javax.ws.rs.core.MultivaluedMap;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyEvent;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class TokenIntrospectContext implements ClientPolicyContext { public class TokenIntrospectContext implements ClientPolicyContext {
private final MultivaluedMap<String, String> params; private final MultivaluedMap<String, String> params;

View file

@ -22,6 +22,9 @@ import javax.ws.rs.core.MultivaluedMap;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyEvent;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class TokenRefreshContext implements ClientPolicyContext { public class TokenRefreshContext implements ClientPolicyContext {
private final MultivaluedMap<String, String> params; private final MultivaluedMap<String, String> params;

View file

@ -23,6 +23,9 @@ import org.keycloak.protocol.oidc.utils.OAuth2CodeParser;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyEvent;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class TokenRequestContext implements ClientPolicyContext { public class TokenRequestContext implements ClientPolicyContext {
private final MultivaluedMap<String, String> params; private final MultivaluedMap<String, String> params;

View file

@ -22,6 +22,9 @@ import javax.ws.rs.core.MultivaluedMap;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyEvent;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class TokenRevokeContext implements ClientPolicyContext { public class TokenRevokeContext implements ClientPolicyContext {
private final MultivaluedMap<String, String> params; private final MultivaluedMap<String, String> params;

View file

@ -20,6 +20,9 @@ package org.keycloak.services.clientpolicy.context;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyEvent;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class UserInfoRequestContext implements ClientPolicyContext { public class UserInfoRequestContext implements ClientPolicyContext {
private final String tokenString; private final String tokenString;

View file

@ -1,83 +0,0 @@
/*
* Copyright 2020 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.executor;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
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 org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
public abstract class AbstractAugumentingClientRegistrationPolicyExecutor implements ClientPolicyExecutorProvider {
protected static final Logger logger = Logger.getLogger(AbstractAugumentingClientRegistrationPolicyExecutor.class);
protected static final String IS_AUGMENT = "is-augment";
protected final KeycloakSession session;
protected final ComponentModel componentModel;
public AbstractAugumentingClientRegistrationPolicyExecutor(KeycloakSession session, ComponentModel componentModel) {
this.session = session;
this.componentModel = componentModel;
}
@Override
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
switch (context.getEvent()) {
case REGISTER:
case UPDATE:
ClientCRUDContext clientUpdateContext = (ClientCRUDContext)context;
augment(clientUpdateContext.getProposedClientRepresentation());
validate(clientUpdateContext.getProposedClientRepresentation());
break;
default:
return;
}
}
@Override
public String getName() {
return componentModel.getName();
}
@Override
public String getProviderId() {
return componentModel.getProviderId();
}
/**
* overrides the client settings specified by the argument.
*
* @param rep - the client settings
*/
protected abstract void augment(ClientRepresentation rep);
/**
* validate the client settings specified by the argument to check
* whether they follows what the executor expects.
*
* @param rep - the client settings
*/
protected abstract void validate(ClientRepresentation rep) throws ClientPolicyException;
}

View file

@ -1,39 +0,0 @@
/*
* Copyright 2020 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.executor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProviderFactory;
public abstract class AbstractAugumentingClientRegistrationPolicyExecutorFactory implements ClientPolicyExecutorProviderFactory {
protected static final String IS_AUGMENT = "is-augment";
private static final ProviderConfigProperty IS_AUGMENT_PROPERTY = new ProviderConfigProperty(
IS_AUGMENT, null, null, ProviderConfigProperty.BOOLEAN_TYPE, false);
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return new ArrayList<>(Arrays.asList(IS_AUGMENT_PROPERTY));
}
}

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,9 +18,9 @@
package org.keycloak.services.clientpolicy.executor; package org.keycloak.services.clientpolicy.executor;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
@ -29,57 +29,67 @@ import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.context.ClientCRUDContext;
import org.keycloak.services.clientpolicy.context.LogoutRequestContext; import org.keycloak.services.clientpolicy.context.LogoutRequestContext;
import org.keycloak.services.clientpolicy.context.TokenRefreshContext; import org.keycloak.services.clientpolicy.context.TokenRefreshContext;
import org.keycloak.services.clientpolicy.context.TokenRevokeContext; import org.keycloak.services.clientpolicy.context.TokenRevokeContext;
import org.keycloak.services.clientpolicy.context.UserInfoRequestContext; import org.keycloak.services.clientpolicy.context.UserInfoRequestContext;
import org.keycloak.services.util.MtlsHoKTokenUtil; import org.keycloak.services.util.MtlsHoKTokenUtil;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
public class HolderOfKeyEnforceExecutor extends AbstractAugumentingClientRegistrationPolicyExecutor { public class HolderOfKeyEnforceExecutor implements ClientPolicyExecutorProvider<HolderOfKeyEnforceExecutor.Configuration> {
private final KeycloakSession session; private final KeycloakSession session;
private final ComponentModel componentModel; private Configuration configuration;
public HolderOfKeyEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { public HolderOfKeyEnforceExecutor(KeycloakSession session) {
super(session, componentModel);
this.session = session; this.session = session;
this.componentModel = componentModel;
} }
@Override @Override
public String getName() { public void setupConfiguration(Configuration config) {
return componentModel.getName(); this.configuration = config;
}
@Override
public Class<Configuration> getExecutorConfigurationClass() {
return Configuration.class;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Configuration extends ClientPolicyExecutorConfiguration {
@JsonProperty("is-augment")
protected Boolean augment;
public Boolean isAugment() {
return augment;
}
public void setAugment(Boolean augment) {
this.augment = augment;
}
} }
@Override @Override
public String getProviderId() { public String getProviderId() {
return componentModel.getProviderId(); return HolderOfKeyEnforceExecutorFactory.PROVIDER_ID;
}
@Override
protected void augment(ClientRepresentation rep) {
if (Boolean.parseBoolean(componentModel.getConfig().getFirst(AbstractAugumentingClientRegistrationPolicyExecutor.IS_AUGMENT))) {
OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).setUseMtlsHoKToken(true);
}
}
@Override
protected void validate(ClientRepresentation rep) throws ClientPolicyException {
boolean useMtlsHokToken = OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).isUseMtlsHokToken();
if (!useMtlsHokToken) {
throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: MTLS token in disabled");
}
} }
@Override @Override
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException { public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
super.executeOnEvent(context);
HttpRequest request = session.getContext().getContextObject(HttpRequest.class); HttpRequest request = session.getContext().getContextObject(HttpRequest.class);
switch (context.getEvent()) { switch (context.getEvent()) {
case REGISTER:
case UPDATE:
ClientCRUDContext clientUpdateContext = (ClientCRUDContext)context;
augment(clientUpdateContext.getProposedClientRepresentation());
validate(clientUpdateContext.getProposedClientRepresentation());
break;
case TOKEN_REQUEST: case TOKEN_REQUEST:
AccessToken.CertConf certConf = MtlsHoKTokenUtil.bindTokenWithClientCertificate(request, session); AccessToken.CertConf certConf = MtlsHoKTokenUtil.bindTokenWithClientCertificate(request, session);
if (certConf == null) { if (certConf == null) {
@ -103,6 +113,19 @@ public class HolderOfKeyEnforceExecutor extends AbstractAugumentingClientRegistr
} }
} }
private void augment(ClientRepresentation rep) {
if (configuration.isAugment()) {
OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).setUseMtlsHoKToken(true);
}
}
private void validate(ClientRepresentation rep) throws ClientPolicyException {
boolean useMtlsHokToken = OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).isUseMtlsHokToken();
if (!useMtlsHokToken) {
throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: MTLS token in disabled");
}
}
private void checkLogout(LogoutRequestContext context, HttpRequest request) throws ClientPolicyException { private void checkLogout(LogoutRequestContext context, HttpRequest request) throws ClientPolicyException {
MultivaluedMap<String, String> formParameters = context.getParams(); MultivaluedMap<String, String> formParameters = context.getParams();
String encodedRefreshToken = formParameters.getFirst(OAuth2Constants.REFRESH_TOKEN); String encodedRefreshToken = formParameters.getFirst(OAuth2Constants.REFRESH_TOKEN);
@ -161,4 +184,5 @@ public class HolderOfKeyEnforceExecutor extends AbstractAugumentingClientRegistr
throw new ClientPolicyException(Errors.NOT_ALLOWED, MtlsHoKTokenUtil.CERT_VERIFY_ERROR_DESC, Response.Status.UNAUTHORIZED); throw new ClientPolicyException(Errors.NOT_ALLOWED, MtlsHoKTokenUtil.CERT_VERIFY_ERROR_DESC, Response.Status.UNAUTHORIZED);
} }
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,21 +18,26 @@
package org.keycloak.services.clientpolicy.executor; package org.keycloak.services.clientpolicy.executor;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import java.util.Collections; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
public class HolderOfKeyEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory { public class HolderOfKeyEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory {
public static final String PROVIDER_ID = "holder-of-key-enforce-executor"; public static final String PROVIDER_ID = "holder-of-key-enforce-executor";
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);
@Override @Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new HolderOfKeyEnforceExecutor(session, model); return new HolderOfKeyEnforceExecutor(session);
} }
@Override @Override
@ -59,7 +64,7 @@ public class HolderOfKeyEnforceExecutorFactory implements ClientPolicyExecutorPr
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return Collections.emptyList(); return new ArrayList<>(Arrays.asList(IS_AUGMENT_PROPERTY));
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -23,11 +23,9 @@ import java.util.regex.Pattern;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Base64Url;
import org.keycloak.component.ComponentModel;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -41,35 +39,65 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext;
import org.keycloak.services.clientpolicy.context.ClientCRUDContext;
import org.keycloak.services.clientpolicy.context.TokenRequestContext; import org.keycloak.services.clientpolicy.context.TokenRequestContext;
import org.keycloak.services.clientpolicy.executor.AbstractAugumentingClientRegistrationPolicyExecutor;
public class PKCEEnforceExecutor extends AbstractAugumentingClientRegistrationPolicyExecutor { import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
private static final Logger logger = Logger.getLogger(PKCEEnforceExecutor.class); /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class PKCEEnforceExecutor implements ClientPolicyExecutorProvider<PKCEEnforceExecutor.Configuration> {
private static final Pattern VALID_CODE_CHALLENGE_PATTERN = Pattern.compile("^[0-9a-zA-Z\\-\\.~_]+$"); private static final Pattern VALID_CODE_CHALLENGE_PATTERN = Pattern.compile("^[0-9a-zA-Z\\-\\.~_]+$");
private static final Pattern VALID_CODE_VERIFIER_PATTERN = Pattern.compile("^[0-9a-zA-Z\\-\\.~_]+$"); private static final Pattern VALID_CODE_VERIFIER_PATTERN = Pattern.compile("^[0-9a-zA-Z\\-\\.~_]+$");
public PKCEEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { private final KeycloakSession session;
super(session, componentModel); private Configuration configuration;
public PKCEEnforceExecutor(KeycloakSession session) {
this.session = session;
} }
protected void augment(ClientRepresentation rep) { @Override
if (Boolean.valueOf(componentModel.getConfig().getFirst(AbstractAugumentingClientRegistrationPolicyExecutor.IS_AUGMENT))) public void setupConfiguration(Configuration config) {
OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).setPkceCodeChallengeMethod(OAuth2Constants.PKCE_METHOD_S256); this.configuration = config;
} }
protected void validate(ClientRepresentation rep) throws ClientPolicyException { @Override
String pkceMethod = OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).getPkceCodeChallengeMethod(); public Class<Configuration> getExecutorConfigurationClass() {
if (pkceMethod != null && pkceMethod.equals(OAuth2Constants.PKCE_METHOD_S256)) return; return Configuration.class;
throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: code_challenge_method"); }
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Configuration extends ClientPolicyExecutorConfiguration {
@JsonProperty("is-augment")
protected Boolean augment;
public Boolean isAugment() {
return augment;
}
public void setAugment(Boolean augment) {
this.augment = augment;
}
}
@Override
public String getProviderId() {
return PKCEEnforceExecutorFactory.PROVIDER_ID;
} }
@Override @Override
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException { public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
super.executeOnEvent(context);
switch (context.getEvent()) { switch (context.getEvent()) {
case REGISTER:
case UPDATE:
ClientCRUDContext clientUpdateContext = (ClientCRUDContext)context;
augment(clientUpdateContext.getProposedClientRepresentation());
validate(clientUpdateContext.getProposedClientRepresentation());
break;
case AUTHORIZATION_REQUEST: case AUTHORIZATION_REQUEST:
AuthorizationRequestContext authorizationRequestContext = (AuthorizationRequestContext)context; AuthorizationRequestContext authorizationRequestContext = (AuthorizationRequestContext)context;
executeOnAuthorizationRequest(authorizationRequestContext.getparsedResponseType(), executeOnAuthorizationRequest(authorizationRequestContext.getparsedResponseType(),
@ -85,6 +113,17 @@ public class PKCEEnforceExecutor extends AbstractAugumentingClientRegistrationPo
} }
} }
private void augment(ClientRepresentation rep) {
if (configuration.isAugment())
OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).setPkceCodeChallengeMethod(OAuth2Constants.PKCE_METHOD_S256);
}
private void validate(ClientRepresentation rep) throws ClientPolicyException {
String pkceMethod = OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).getPkceCodeChallengeMethod();
if (pkceMethod != null && pkceMethod.equals(OAuth2Constants.PKCE_METHOD_S256)) return;
throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: code_challenge_method");
}
private void executeOnAuthorizationRequest( private void executeOnAuthorizationRequest(
OIDCResponseType parsedResponseType, OIDCResponseType parsedResponseType,
AuthorizationEndpointRequest request, AuthorizationEndpointRequest request,
@ -196,4 +235,5 @@ public class PKCEEnforceExecutor extends AbstractAugumentingClientRegistrationPo
return codeVerifierEncoded; return codeVerifierEncoded;
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,23 +17,30 @@
package org.keycloak.services.clientpolicy.executor; package org.keycloak.services.clientpolicy.executor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.executor.AbstractAugumentingClientRegistrationPolicyExecutorFactory;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
public class PKCEEnforceExecutorFactory extends AbstractAugumentingClientRegistrationPolicyExecutorFactory { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class PKCEEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory {
public static final String PROVIDER_ID = "pkce-enforce-executor"; public static final String PROVIDER_ID = "pkce-enforce-executor";
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);
@Override @Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new PKCEEnforceExecutor(session, model); return new PKCEEnforceExecutor(session);
} }
@Override @Override
@ -60,7 +67,7 @@ public class PKCEEnforceExecutorFactory extends AbstractAugumentingClientRegistr
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return super.getConfigProperties(); return new ArrayList<>(Arrays.asList(IS_AUGMENT_PROPERTY));
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -19,38 +19,109 @@ package org.keycloak.services.clientpolicy.executor;
import java.util.List; import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.context.ClientCRUDContext;
public class SecureClientAuthEnforceExecutor extends AbstractAugumentingClientRegistrationPolicyExecutor { import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
private static final Logger logger = Logger.getLogger(SecureClientAuthEnforceExecutor.class); /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureClientAuthEnforceExecutor implements ClientPolicyExecutorProvider<SecureClientAuthEnforceExecutor.Configuration> {
public SecureClientAuthEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { private final KeycloakSession session;
super(session, componentModel); private Configuration configuration;
public SecureClientAuthEnforceExecutor(KeycloakSession session) {
this.session = session;
} }
protected void augment(ClientRepresentation rep) { @Override
if (Boolean.valueOf(componentModel.getConfig().getFirst(AbstractAugumentingClientRegistrationPolicyExecutor.IS_AUGMENT))) public void setupConfiguration(SecureClientAuthEnforceExecutor.Configuration config) {
this.configuration = config;
}
@Override
public Class<Configuration> getExecutorConfigurationClass() {
return Configuration.class;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Configuration extends ClientPolicyExecutorConfiguration {
@JsonProperty("client-authns")
protected List<String> clientAuthns;
@JsonProperty("client-authns-augment")
protected String clientAuthnsAugment;
@JsonProperty("is-augment")
protected Boolean augment;
public List<String> getClientAuthns() {
return clientAuthns;
}
public void setClientAuthns(List<String> clientAuthns) {
this.clientAuthns = clientAuthns;
}
public String getClientAuthnsAugment() {
return clientAuthnsAugment;
}
public void setClientAuthnsAugment(String clientAuthnsAugment) {
this.clientAuthnsAugment = clientAuthnsAugment;
}
public Boolean isAugment() {
return augment;
}
public void setAugment(Boolean augment) {
this.augment = augment;
}
}
@Override
public String getProviderId() {
return SecureClientAuthEnforceExecutorFactory.PROVIDER_ID;
}
@Override
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
switch (context.getEvent()) {
case REGISTER:
case UPDATE:
ClientCRUDContext clientUpdateContext = (ClientCRUDContext)context;
augment(clientUpdateContext.getProposedClientRepresentation());
validate(clientUpdateContext.getProposedClientRepresentation());
break;
default:
return;
}
}
private void augment(ClientRepresentation rep) {
if (configuration.isAugment())
rep.setClientAuthenticatorType(enforcedClientAuthenticatorType()); rep.setClientAuthenticatorType(enforcedClientAuthenticatorType());
} }
protected void validate(ClientRepresentation rep) throws ClientPolicyException { private void validate(ClientRepresentation rep) throws ClientPolicyException {
verifyClientAuthenticationMethod(rep.getClientAuthenticatorType()); verifyClientAuthenticationMethod(rep.getClientAuthenticatorType());
} }
private String enforcedClientAuthenticatorType() { private String enforcedClientAuthenticatorType() {
return componentModel.getConfig().getFirst(SecureClientAuthEnforceExecutorFactory.CLIENT_AUTHNS_AUGMENT); return configuration.getClientAuthnsAugment();
} }
private void verifyClientAuthenticationMethod(String clientAuthenticatorType) throws ClientPolicyException { private void verifyClientAuthenticationMethod(String clientAuthenticatorType) throws ClientPolicyException {
List<String> acceptableClientAuthn = componentModel.getConfig().getList(SecureClientAuthEnforceExecutorFactory.CLIENT_AUTHNS); List<String> acceptableClientAuthn = configuration.getClientAuthns();
if (acceptableClientAuthn != null && acceptableClientAuthn.stream().anyMatch(i->i.equals(clientAuthenticatorType))) return; if (acceptableClientAuthn != null && acceptableClientAuthn.stream().anyMatch(i->i.equals(clientAuthenticatorType))) return;
throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: token_endpoint_auth_method"); throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: token_endpoint_auth_method");
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,31 +17,37 @@
package org.keycloak.services.clientpolicy.executor; package org.keycloak.services.clientpolicy.executor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
public class SecureClientAuthEnforceExecutorFactory extends AbstractAugumentingClientRegistrationPolicyExecutorFactory { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureClientAuthEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory {
public static final String PROVIDER_ID = "secure-client-authn-executor"; public static final String PROVIDER_ID = "secure-client-authn-executor";
public static final String IS_AUGMENT = "is-augment";
public static final String CLIENT_AUTHNS = "client-authns"; public static final String CLIENT_AUTHNS = "client-authns";
public static final String CLIENT_AUTHNS_AUGMENT = "client-authns-augment"; 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( private static final ProviderConfigProperty CLIENTAUTHNS_PROPERTY = new ProviderConfigProperty(
CLIENT_AUTHNS, null, null, ProviderConfigProperty.MULTIVALUED_STRING_TYPE, null); CLIENT_AUTHNS, null, null, ProviderConfigProperty.MULTIVALUED_STRING_TYPE, null);
private static final ProviderConfigProperty CLIENTAUTHNS_AUGMENT = new ProviderConfigProperty( private static final ProviderConfigProperty CLIENTAUTHNS_AUGMENT = new ProviderConfigProperty(
CLIENT_AUTHNS_AUGMENT, null, null, ProviderConfigProperty.STRING_TYPE, JWTClientAuthenticator.PROVIDER_ID); CLIENT_AUTHNS_AUGMENT, null, null, ProviderConfigProperty.STRING_TYPE, JWTClientAuthenticator.PROVIDER_ID);
@Override @Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new SecureClientAuthEnforceExecutor(session, model); return new SecureClientAuthEnforceExecutor(session);
} }
@Override @Override
@ -68,10 +74,7 @@ public class SecureClientAuthEnforceExecutorFactory extends AbstractAugumentingC
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
List<ProviderConfigProperty> l = super.getConfigProperties(); return new ArrayList<>(Arrays.asList(IS_AUGMENT_PROPERTY, CLIENTAUTHNS_PROPERTY, CLIENTAUTHNS_AUGMENT));
l.add(CLIENTAUTHNS_PROPERTY);
l.add(CLIENTAUTHNS_AUGMENT);
return l;
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -22,11 +22,9 @@ import java.util.List;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext; import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext;
import org.keycloak.services.clientpolicy.context.AdminClientUpdateContext; import org.keycloak.services.clientpolicy.context.AdminClientUpdateContext;
import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext;
@ -34,26 +32,28 @@ import org.keycloak.services.clientpolicy.context.ClientCRUDContext;
import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext; import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext;
import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext; import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext;
public class SecureRedirectUriEnforceExecutor implements ClientPolicyExecutorProvider { import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureRedirectUriEnforceExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
private static final Logger logger = Logger.getLogger(SecureRedirectUriEnforceExecutor.class); private static final Logger logger = Logger.getLogger(SecureRedirectUriEnforceExecutor.class);
private final KeycloakSession session; private final KeycloakSession session;
private final ComponentModel componentModel;
public SecureRedirectUriEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { public SecureRedirectUriEnforceExecutor(KeycloakSession session) {
this.session = session; this.session = session;
this.componentModel = componentModel;
} }
@Override @JsonIgnoreProperties(ignoreUnknown = true)
public String getName() { public static class Configuration {
return componentModel.getName();
} }
@Override @Override
public String getProviderId() { public String getProviderId() {
return componentModel.getProviderId(); return SecureRedirectUriEnforceExecutorFactory.PROVIDER_ID;
} }
@Override @Override
@ -87,10 +87,11 @@ public class SecureRedirectUriEnforceExecutor implements ClientPolicyExecutorPro
} }
for(String redirectUri : redirectUris) { for(String redirectUri : redirectUris) {
ClientPolicyLogger.log(logger, "Redirect URI = " + redirectUri); logger.tracev("Redirect URI = {0}", redirectUri);
if (redirectUri.startsWith("http://") || redirectUri.contains("*")) { if (redirectUri.startsWith("http://") || redirectUri.contains("*")) {
throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: redirect_uris"); throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: redirect_uris");
} }
} }
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,18 +21,20 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureRedirectUriEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory { public class SecureRedirectUriEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory {
public static final String PROVIDER_ID = "secure-redirecturi-enforce-executor"; public static final String PROVIDER_ID = "secure-redirecturi-enforce-executor";
@Override @Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new SecureRedirectUriEnforceExecutor(session, model); return new SecureRedirectUriEnforceExecutor(session);
} }
@Override @Override

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -25,7 +25,6 @@ import javax.ws.rs.core.MultivaluedMap;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
@ -34,23 +33,33 @@ import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.services.Urls; import org.keycloak.services.Urls;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
private static final Logger logger = Logger.getLogger(SecureRequestObjectExecutor.class); private static final Logger logger = Logger.getLogger(SecureRequestObjectExecutor.class);
private final KeycloakSession session;
private final ComponentModel componentModel;
public static final String INVALID_REQUEST_OBJECT = "invalid_request_object"; public static final String INVALID_REQUEST_OBJECT = "invalid_request_object";
public SecureRequestObjectExecutor(KeycloakSession session, ComponentModel componentModel) { private final KeycloakSession session;
public SecureRequestObjectExecutor(KeycloakSession session) {
this.session = session; this.session = session;
this.componentModel = componentModel; }
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Configuration {
}
@Override
public String getProviderId() {
return SecureRequestObjectExecutorFactory.PROVIDER_ID;
} }
@Override @Override
@ -73,10 +82,10 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider
AuthorizationEndpointRequest request, AuthorizationEndpointRequest request,
String redirectUri, String redirectUri,
MultivaluedMap<String, String> params) throws ClientPolicyException { MultivaluedMap<String, String> params) throws ClientPolicyException {
ClientPolicyLogger.log(logger, "Authz Endpoint - authz request"); logger.trace("Authz Endpoint - authz request");
if (params == null) { if (params == null) {
ClientPolicyLogger.log(logger, "request parameter not exist."); logger.trace("request parameter not exist.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameters"); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameters");
} }
@ -85,7 +94,7 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider
// check whether whether request object exists // check whether whether request object exists
if (requestParam == null && requestUriParam == null) { if (requestParam == null && requestUriParam == null) {
ClientPolicyLogger.log(logger, "request object not exist."); logger.trace("request object not exist.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter"); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter");
} }
@ -93,26 +102,26 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider
// check whether request object exists // check whether request object exists
if (requestObject == null || requestObject.isEmpty()) { if (requestObject == null || requestObject.isEmpty()) {
ClientPolicyLogger.log(logger, "request object not exist."); logger.trace("request object not exist.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter"); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter");
} }
// check whether scope exists in both query parameter and request object // check whether scope exists in both query parameter and request object
if (params.getFirst(OIDCLoginProtocol.SCOPE_PARAM) == null || requestObject.get(OIDCLoginProtocol.SCOPE_PARAM) == null) { if (params.getFirst(OIDCLoginProtocol.SCOPE_PARAM) == null || requestObject.get(OIDCLoginProtocol.SCOPE_PARAM) == null) {
ClientPolicyLogger.log(logger, "scope does not exists."); logger.trace("scope object not exist.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter : scope"); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter : scope");
} }
// check whether "exp" claim exists // check whether "exp" claim exists
if (requestObject.get("exp") == null) { if (requestObject.get("exp") == null) {
ClientPolicyLogger.log(logger, "exp claim not incuded."); logger.trace("exp claim not incuded.");
throw new ClientPolicyException(INVALID_REQUEST_OBJECT, "Missing parameter : exp"); throw new ClientPolicyException(INVALID_REQUEST_OBJECT, "Missing parameter : exp");
} }
// check whether request object not expired // check whether request object not expired
long exp = requestObject.get("exp").asLong(); long exp = requestObject.get("exp").asLong();
if (Time.currentTime() > exp) { // TODO: Time.currentTime() is int while exp is long... if (Time.currentTime() > exp) { // TODO: Time.currentTime() is int while exp is long...
ClientPolicyLogger.log(logger, "request object expired."); logger.trace("request object expired.");
throw new ClientPolicyException(INVALID_REQUEST_OBJECT, "Request Expired"); throw new ClientPolicyException(INVALID_REQUEST_OBJECT, "Request Expired");
} }
@ -120,7 +129,7 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider
List<String> aud = new ArrayList<String>(); List<String> aud = new ArrayList<String>();
JsonNode audience = requestObject.get("aud"); JsonNode audience = requestObject.get("aud");
if (audience == null) { if (audience == null) {
ClientPolicyLogger.log(logger, "aud claim not incuded."); logger.trace("aud claim not incuded.");
throw new ClientPolicyException(INVALID_REQUEST_OBJECT, "Missing parameter : aud"); throw new ClientPolicyException(INVALID_REQUEST_OBJECT, "Missing parameter : aud");
} }
if (audience.isArray()) { if (audience.isArray()) {
@ -129,25 +138,25 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider
aud.add(audience.asText()); aud.add(audience.asText());
} }
if (aud.isEmpty()) { if (aud.isEmpty()) {
ClientPolicyLogger.log(logger, "aud claim not incuded."); logger.trace("aud claim not incuded.");
throw new ClientPolicyException(INVALID_REQUEST_OBJECT, "Missing parameter : aud"); throw new ClientPolicyException(INVALID_REQUEST_OBJECT, "Missing parameter : aud");
} }
// check whether "aud" claim points to this keycloak as authz server // check whether "aud" claim points to this keycloak as authz server
String iss = Urls.realmIssuer(session.getContext().getUri().getBaseUri(), session.getContext().getRealm().getName()); String iss = Urls.realmIssuer(session.getContext().getUri().getBaseUri(), session.getContext().getRealm().getName());
if (!aud.contains(iss)) { if (!aud.contains(iss)) {
ClientPolicyLogger.log(logger, "aud not points to the intended realm."); logger.trace("aud not points to the intended realm.");
throw new ClientPolicyException(INVALID_REQUEST_OBJECT, "Invalid parameter : aud"); throw new ClientPolicyException(INVALID_REQUEST_OBJECT, "Invalid parameter : aud");
} }
// confirm whether all parameters in query string are included in the request object, and have the same values // confirm whether all parameters in query string are included in the request object, and have the same values
// argument "request" are parameters overridden by parameters in request object // argument "request" are parameters overridden by parameters in request object
if (AuthzEndpointRequestParser.KNOWN_REQ_PARAMS.stream().filter(s->params.containsKey(s)).anyMatch(s->!isSameParameterIncluded(s, params.getFirst(s), requestObject))) { if (AuthzEndpointRequestParser.KNOWN_REQ_PARAMS.stream().filter(s->params.containsKey(s)).anyMatch(s->!isSameParameterIncluded(s, params.getFirst(s), requestObject))) {
ClientPolicyLogger.log(logger, "not all parameters in query string are included in the request object, and have the same values."); logger.trace("not all parameters in query string are included in the request object, and have the same values.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter"); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter");
} }
ClientPolicyLogger.log(logger, "Passed."); logger.trace("Passed.");
} }
private boolean isSameParameterIncluded(String param, String value, JsonNode requestObject) { private boolean isSameParameterIncluded(String param, String value, JsonNode requestObject) {
@ -156,14 +165,4 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider
return false; return false;
} }
@Override
public String getName() {
return componentModel.getName();
}
@Override
public String getProviderId() {
return componentModel.getProviderId();
}
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,18 +21,20 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureRequestObjectExecutorFactory implements ClientPolicyExecutorProviderFactory { public class SecureRequestObjectExecutorFactory implements ClientPolicyExecutorProviderFactory {
public static final String PROVIDER_ID = "secure-reqobj-executor"; public static final String PROVIDER_ID = "secure-reqobj-executor";
@Override @Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new SecureRequestObjectExecutor(session, model); return new SecureRequestObjectExecutor(session);
} }
@Override @Override

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -19,25 +19,35 @@ package org.keycloak.services.clientpolicy.executor;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext;
public class SecureResponseTypeExecutor implements ClientPolicyExecutorProvider { import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureResponseTypeExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
private static final Logger logger = Logger.getLogger(SecureResponseTypeExecutor.class); private static final Logger logger = Logger.getLogger(SecureResponseTypeExecutor.class);
protected final KeycloakSession session; protected final KeycloakSession session;
protected final ComponentModel componentModel;
public SecureResponseTypeExecutor(KeycloakSession session, ComponentModel componentModel) { public SecureResponseTypeExecutor(KeycloakSession session) {
this.session = session; this.session = session;
this.componentModel = componentModel; }
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Configuration {
}
@Override
public String getProviderId() {
return SecureResponseTypeExecutorFactory.PROVIDER_ID;
} }
@Override @Override
@ -59,30 +69,19 @@ public class SecureResponseTypeExecutor implements ClientPolicyExecutorProvider
OIDCResponseType parsedResponseType, OIDCResponseType parsedResponseType,
AuthorizationEndpointRequest request, AuthorizationEndpointRequest request,
String redirectUri) throws ClientPolicyException { String redirectUri) throws ClientPolicyException {
ClientPolicyLogger.log(logger, "Authz Endpoint - authz request"); logger.trace("Authz Endpoint - authz request");
if (parsedResponseType.hasResponseType(OIDCResponseType.CODE) && parsedResponseType.hasResponseType(OIDCResponseType.ID_TOKEN)) { if (parsedResponseType.hasResponseType(OIDCResponseType.CODE) && parsedResponseType.hasResponseType(OIDCResponseType.ID_TOKEN)) {
if (parsedResponseType.hasResponseType(OIDCResponseType.TOKEN)) { if (parsedResponseType.hasResponseType(OIDCResponseType.TOKEN)) {
ClientPolicyLogger.log(logger, "Passed. response_type = code id_token token"); logger.trace("Passed. response_type = code id_token token");
} else { } else {
ClientPolicyLogger.log(logger, "Passed. response_type = code id_token"); logger.trace("Passed. response_type = code id_token");
} }
return; return;
} }
ClientPolicyLogger.log(logger, "invalid response_type = " + parsedResponseType); logger.tracev("invalid response_type = {0}", parsedResponseType);
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "invalid response_type"); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "invalid response_type");
}
@Override
public String getName() {
return componentModel.getName();
}
@Override
public String getProviderId() {
return componentModel.getProviderId();
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,18 +21,20 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureResponseTypeExecutorFactory implements ClientPolicyExecutorProviderFactory { public class SecureResponseTypeExecutorFactory implements ClientPolicyExecutorProviderFactory {
public static final String PROVIDER_ID = "secure-responsetype-executor"; public static final String PROVIDER_ID = "secure-responsetype-executor";
@Override @Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new SecureResponseTypeExecutor(session, model); return new SecureResponseTypeExecutor(session);
} }
@Override @Override

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -19,36 +19,36 @@ package org.keycloak.services.clientpolicy.executor;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext;
import org.keycloak.util.TokenUtil; import org.keycloak.util.TokenUtil;
public class SecureSessionEnforceExecutor implements ClientPolicyExecutorProvider { import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureSessionEnforceExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
private static final Logger logger = Logger.getLogger(SecureSessionEnforceExecutor.class); private static final Logger logger = Logger.getLogger(SecureSessionEnforceExecutor.class);
private final KeycloakSession session; private final KeycloakSession session;
private final ComponentModel componentModel;
public SecureSessionEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { public SecureSessionEnforceExecutor(KeycloakSession session) {
this.session = session; this.session = session;
this.componentModel = componentModel;
} }
@Override @JsonIgnoreProperties(ignoreUnknown = true)
public String getName() { public static class Configuration {
return componentModel.getName();
} }
@Override @Override
public String getProviderId() { public String getProviderId() {
return componentModel.getProviderId(); return SecureSessionEnforceExecutorFactory.PROVIDER_ID;
} }
@Override @Override
@ -69,19 +69,19 @@ public class SecureSessionEnforceExecutor implements ClientPolicyExecutorProvide
OIDCResponseType parsedResponseType, OIDCResponseType parsedResponseType,
AuthorizationEndpointRequest request, AuthorizationEndpointRequest request,
String redirectUri) throws ClientPolicyException { String redirectUri) throws ClientPolicyException {
ClientPolicyLogger.log(logger, "Authz Endpoint - authz request"); logger.trace("Authz Endpoint - authz request");
if (TokenUtil.isOIDCRequest(request.getScope())) { if (TokenUtil.isOIDCRequest(request.getScope())) {
if(request.getNonce() == null) { if(request.getNonce() == null) {
ClientPolicyLogger.log(logger, "Missing parameter: nonce"); logger.trace("Missing parameter: nonce");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: nonce"); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: nonce");
} }
} else { } else {
if(request.getState() == null) { if(request.getState() == null) {
ClientPolicyLogger.log(logger, "Missing parameter: state"); logger.trace("Missing parameter: state");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: state"); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: state");
} }
} }
ClientPolicyLogger.log(logger, "Passed."); logger.trace("Passed.");
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,18 +21,20 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureSessionEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory { public class SecureSessionEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory {
public static final String PROVIDER_ID = "secure-session-enforce-executor"; public static final String PROVIDER_ID = "secure-session-enforce-executor";
@Override @Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new SecureSessionEnforceExecutor(session, model); return new SecureSessionEnforceExecutor(session);
} }
@Override @Override

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -23,25 +23,27 @@ import java.util.List;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.Algorithm;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext; import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext;
import org.keycloak.services.clientpolicy.context.AdminClientUpdateContext; import org.keycloak.services.clientpolicy.context.AdminClientUpdateContext;
import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext; import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext;
import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext; import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext;
public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecutorProvider { import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
private static final Logger logger = Logger.getLogger(SecureSigningAlgorithmEnforceExecutor.class); private static final Logger logger = Logger.getLogger(SecureSigningAlgorithmEnforceExecutor.class);
private final KeycloakSession session; private final KeycloakSession session;
private final ComponentModel componentModel;
private static final List<String> sigTargets = Arrays.asList( private static final List<String> sigTargets = Arrays.asList(
OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG,
@ -52,19 +54,17 @@ public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecut
private static final List<String> sigTargetsAdminRestApiOnly = Arrays.asList( private static final List<String> sigTargetsAdminRestApiOnly = Arrays.asList(
OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG); OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG);
public SecureSigningAlgorithmEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { public SecureSigningAlgorithmEnforceExecutor(KeycloakSession session) {
this.session = session; this.session = session;
this.componentModel = componentModel;
} }
@Override @JsonIgnoreProperties(ignoreUnknown = true)
public String getName() { public static class Configuration {
return componentModel.getName();
} }
@Override @Override
public String getProviderId() { public String getProviderId() {
return componentModel.getProviderId(); return SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID;
} }
@Override @Override
@ -112,7 +112,7 @@ public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecut
private void verifySecureSigningAlgorithm(String sigTarget, String sigAlg) throws ClientPolicyException { private void verifySecureSigningAlgorithm(String sigTarget, String sigAlg) throws ClientPolicyException {
if (sigAlg == null) { if (sigAlg == null) {
ClientPolicyLogger.logv(logger, "Signing algorithm not specified explicitly. signature target = {0}", sigTarget); logger.tracev("Signing algorithm not specified explicitly. signature target = {0}", sigTarget);
return; return;
} }
switch (sigAlg) { switch (sigAlg) {
@ -122,10 +122,10 @@ public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecut
case Algorithm.ES256: case Algorithm.ES256:
case Algorithm.ES384: case Algorithm.ES384:
case Algorithm.ES512: case Algorithm.ES512:
ClientPolicyLogger.logv(logger, "Passed. signature target = {0}, signature algorithm = {1}", sigTarget, sigAlg); logger.tracev("Passed. signature target = {0}, signature algorithm = {1}", sigTarget, sigAlg);
return; return;
} }
ClientPolicyLogger.logv(logger, "NOT allowed signatureAlgorithm. signature target = {0}, signature algorithm = {1}", sigTarget, sigAlg); logger.tracev("NOT allowed signatureAlgorithm. signature target = {0}, signature algorithm = {1}", sigTarget, sigAlg);
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "not allowed signature algorithm."); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "not allowed signature algorithm.");
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,18 +21,20 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureSigningAlgorithmEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory { public class SecureSigningAlgorithmEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory {
public static final String PROVIDER_ID = "securesignalg-enforce-executor"; public static final String PROVIDER_ID = "securesignalg-enforce-executor";
@Override @Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new SecureSigningAlgorithmEnforceExecutor(session, model); return new SecureSigningAlgorithmEnforceExecutor(session);
} }
@Override @Override

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,35 +21,32 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.Algorithm;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
public class SecureSigningAlgorithmForSignedJwtEnforceExecutor implements ClientPolicyExecutorProvider { import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
public class SecureSigningAlgorithmForSignedJwtEnforceExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
private static final Logger logger = Logger.getLogger(SecureSigningAlgorithmForSignedJwtEnforceExecutor.class); private static final Logger logger = Logger.getLogger(SecureSigningAlgorithmForSignedJwtEnforceExecutor.class);
private final KeycloakSession session; private final KeycloakSession session;
private final ComponentModel componentModel;
public SecureSigningAlgorithmForSignedJwtEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { public SecureSigningAlgorithmForSignedJwtEnforceExecutor(KeycloakSession session) {
this.session = session; this.session = session;
this.componentModel = componentModel;
} }
@Override @JsonIgnoreProperties(ignoreUnknown = true)
public String getName() { public static class Configuration {
return componentModel.getName();
} }
@Override @Override
public String getProviderId() { public String getProviderId() {
return componentModel.getProviderId(); return SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID;
} }
@Override @Override
@ -78,7 +75,7 @@ public class SecureSigningAlgorithmForSignedJwtEnforceExecutor implements Client
private void verifySecureSigningAlgorithm(String signatureAlgorithm) throws ClientPolicyException { private void verifySecureSigningAlgorithm(String signatureAlgorithm) throws ClientPolicyException {
if (signatureAlgorithm == null) { if (signatureAlgorithm == null) {
ClientPolicyLogger.log(logger, "Signing algorithm not specified explicitly."); logger.trace("Signing algorithm not specified explicitly.");
return; return;
} }
@ -90,10 +87,11 @@ public class SecureSigningAlgorithmForSignedJwtEnforceExecutor implements Client
case Algorithm.ES256: case Algorithm.ES256:
case Algorithm.ES384: case Algorithm.ES384:
case Algorithm.ES512: case Algorithm.ES512:
ClientPolicyLogger.log(logger, "Passed. signatureAlgorithm = " + signatureAlgorithm); logger.tracev("Passed. signatureAlgorithm = {0}", signatureAlgorithm);
return; return;
} }
ClientPolicyLogger.log(logger, "NOT allowed signatureAlgorithm = " + signatureAlgorithm); logger.tracev("NOT allowed signatureAlgorithm = {0}", signatureAlgorithm);
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "not allowed signature algorithm."); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "not allowed signature algorithm.");
} }
} }

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,7 +18,6 @@
package org.keycloak.services.clientpolicy.executor; package org.keycloak.services.clientpolicy.executor;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
@ -31,8 +30,8 @@ public class SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory implements
public static final String PROVIDER_ID = "securesignalgjwt-enforce-executor"; public static final String PROVIDER_ID = "securesignalgjwt-enforce-executor";
@Override @Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new SecureSigningAlgorithmForSignedJwtEnforceExecutor(session, model); return new SecureSigningAlgorithmForSignedJwtEnforceExecutor(session);
} }
@Override @Override

View file

@ -124,6 +124,7 @@ public class RealmManager {
createDefaultClientScopes(realm); createDefaultClientScopes(realm);
setupAuthorizationServices(realm); setupAuthorizationServices(realm);
setupClientRegistrations(realm); setupClientRegistrations(realm);
setupClientPolicies(realm);
fireRealmPostCreate(realm); fireRealmPostCreate(realm);
@ -598,6 +599,8 @@ public class RealmManager {
MigrationModelManager.migrateImport(session, realm, rep, skipUserDependent); MigrationModelManager.migrateImport(session, realm, rep, skipUserDependent);
} }
setupClientPolicies(realm, rep);
fireRealmPostCreate(realm); fireRealmPostCreate(realm);
return realm; return realm;
@ -711,6 +714,14 @@ public class RealmManager {
DefaultClientRegistrationPolicies.addDefaultPolicies(realm); 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) { private void fireRealmPostCreate(RealmModel realm) {
session.getKeycloakSessionFactory().publish(new RealmModel.RealmPostCreateEvent() { session.getKeycloakSessionFactory().publish(new RealmModel.RealmPostCreateEvent() {
@Override @Override

View file

@ -60,6 +60,7 @@ import org.keycloak.util.JsonSerialization;
import javax.transaction.SystemException; import javax.transaction.SystemException;
import javax.transaction.Transaction; import javax.transaction.Transaction;
import javax.ws.rs.core.Application; import javax.ws.rs.core.Application;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -192,6 +193,7 @@ public class KeycloakApplication extends Application {
} }
} }
session.clientPolicy().setupClientPoliciesOnKeycloakApp("/keycloak-default-client-profiles.json", "/keycloak-default-client-policies.json");
ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session); ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
exportImportManager = new ExportImportManager(session); exportImportManager = new ExportImportManager(session);

View file

@ -0,0 +1,79 @@
/*
* 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.resources.admin;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
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.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
public class ClientPoliciesResource {
protected static final Logger logger = Logger.getLogger(ClientPoliciesResource.class);
@Context
protected HttpRequest request;
@Context
protected HttpResponse response;
@Context
protected KeycloakSession session;
protected RealmModel realm;
private AdminPermissionEvaluator auth;
public ClientPoliciesResource(RealmModel realm, AdminPermissionEvaluator auth) {
this.realm = realm;
this.auth = auth;
}
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public String getPolicies() {
auth.realm().requireViewRealm();
return session.clientPolicy().getClientPolicies(realm);
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response updatePolicies(final String json) {
auth.realm().requireManageRealm();
try {
session.clientPolicy().updateClientPolicies(realm, json);
} catch (ClientPolicyException e) {
return Response.status(Status.BAD_REQUEST).entity(e.getError()).build();
}
return Response.noContent().build();
}
}

View file

@ -0,0 +1,79 @@
/*
* 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.resources.admin;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
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.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
public class ClientProfilesResource {
protected static final Logger logger = Logger.getLogger(ClientProfilesResource.class);
@Context
protected HttpRequest request;
@Context
protected HttpResponse response;
@Context
protected KeycloakSession session;
protected RealmModel realm;
private AdminPermissionEvaluator auth;
public ClientProfilesResource(RealmModel realm, AdminPermissionEvaluator auth) {
this.realm = realm;
this.auth = auth;
}
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public String getProfiles() {
auth.realm().requireViewRealm();
return session.clientPolicy().getClientProfiles(realm);
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response updateProfiles(final String json) {
auth.realm().requireManageRealm();
try {
session.clientPolicy().updateClientProfiles(realm, json);
} catch (ClientPolicyException e) {
return Response.status(Status.BAD_REQUEST).entity(e.getError()).build();
}
return Response.noContent().build();
}
}

View file

@ -1207,4 +1207,17 @@ public class RealmAdminResource {
.filter(providerId -> session.getProvider(RequiredActionProvider.class, providerId) instanceof CredentialRegistrator); .filter(providerId -> session.getProvider(RequiredActionProvider.class, providerId) instanceof CredentialRegistrator);
} }
@Path("client-policies/policies")
public ClientPoliciesResource getClientPoliciesResource() {
ClientPoliciesResource resource = new ClientPoliciesResource(realm, auth);
ResteasyProviderFactory.getInstance().injectProperties(resource);
return resource;
}
@Path("client-policies/profiles")
public ClientProfilesResource getClientProfilesResource() {
ClientProfilesResource resource = new ClientProfilesResource(realm, auth);
ResteasyProviderFactory.getInstance().injectProperties(resource);
return resource;
}
} }

View file

@ -1 +0,0 @@
org.keycloak.services.clientpolicy.DefaultClientPolicyProviderFactory

View file

@ -0,0 +1,18 @@
{
"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

@ -0,0 +1,14 @@
{
"profiles": [
{
"name": "builtin-default-profile",
"description": "The built-in default profile for enforcing basic security level to clients.",
"builtin": true,
"executors": [
{
"secure-session-enforce-executor": {}
}
]
}
]
}

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,35 +17,41 @@
package org.keycloak.testsuite.services.clientpolicy.condition; package org.keycloak.testsuite.services.clientpolicy.condition;
import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyVote; 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.ClientPolicyConditionProvider;
public class TestRaiseExeptionCondition implements ClientPolicyConditionProvider { /**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class TestRaiseExeptionCondition implements ClientPolicyConditionProvider<TestRaiseExeptionCondition.Configuration> {
private static final Logger logger = Logger.getLogger(TestRaiseExeptionCondition.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 TestRaiseExeptionCondition(KeycloakSession session) {
private final ComponentModel componentModel;
public TestRaiseExeptionCondition(KeycloakSession session, ComponentModel componentModel) {
this.session = session;
this.componentModel = componentModel;
} }
@Override @Override
public String getName() { public void setupConfiguration(Configuration config) {
return componentModel.getName(); this.configuration = config;
}
@Override
public Class<Configuration> getConditionConfigurationClass() {
return Configuration.class;
}
public static class Configuration extends ClientPolicyConditionConfiguration {
} }
@Override @Override
public String getProviderId() { public String getProviderId() {
return componentModel.getProviderId(); return TestRaiseExeptionConditionFactory.PROVIDER_ID;
} }
@Override @Override

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. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,21 +21,22 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProviderFactory; import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProviderFactory;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class TestRaiseExeptionConditionFactory implements ClientPolicyConditionProviderFactory { public class TestRaiseExeptionConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "test-raise-exception-condition"; public static final String PROVIDER_ID = "test-raise-exception-condition";
@Override @Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyConditionProvider create(KeycloakSession session) {
return new TestRaiseExeptionCondition(session, model); return new TestRaiseExeptionCondition(session);
} }
@Override @Override
@ -64,4 +65,5 @@ public class TestRaiseExeptionConditionFactory implements ClientPolicyConditionP
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return Collections.emptyList(); return Collections.emptyList();
} }
} }

View file

@ -17,37 +17,27 @@
package org.keycloak.testsuite.services.clientpolicy.executor; package org.keycloak.testsuite.services.clientpolicy.executor;
import java.util.List;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyEvent;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorConfiguration;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
public class TestRaiseExeptionExecutor implements ClientPolicyExecutorProvider { public class TestRaiseExeptionExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfiguration> {
private static final Logger logger = Logger.getLogger(TestRaiseExeptionExecutor.class); private static final Logger logger = Logger.getLogger(TestRaiseExeptionExecutor.class);
protected final KeycloakSession session; protected final KeycloakSession session;
protected final ComponentModel componentModel;
public TestRaiseExeptionExecutor(KeycloakSession session, ComponentModel componentModel) { public TestRaiseExeptionExecutor(KeycloakSession session) {
this.session = session; this.session = session;
this.componentModel = componentModel;
}
@Override
public String getName() {
return componentModel.getName();
} }
@Override @Override
public String getProviderId() { public String getProviderId() {
return componentModel.getProviderId(); return TestRaiseExeptionExecutorFactory.PROVIDER_ID;
} }
@Override @Override
@ -56,8 +46,15 @@ public class TestRaiseExeptionExecutor implements ClientPolicyExecutorProvider {
} }
private boolean isThrowExceptionNeeded(ClientPolicyEvent event) { private boolean isThrowExceptionNeeded(ClientPolicyEvent event) {
ClientPolicyLogger.log(logger, "Client Policy Trigger Event = " + event); logger.tracev("Client Policy Trigger Event = {0}", event);
List<String> l = componentModel.getConfig().get(TestRaiseExeptionExecutorFactory.TARGET_CP_EVENTS); switch (event) {
return l != null && l.stream().anyMatch(i->i.equals(event.toString())); case REGISTERED:
case UPDATED:
case UNREGISTER:
return true;
default :
return false;
}
} }
} }

View file

@ -17,12 +17,10 @@
package org.keycloak.testsuite.services.clientpolicy.executor; package org.keycloak.testsuite.services.clientpolicy.executor;
import java.util.ArrayList; import java.util.Collections;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
@ -33,13 +31,9 @@ public class TestRaiseExeptionExecutorFactory implements ClientPolicyExecutorPro
public static final String PROVIDER_ID = "test-raise-exception-executor"; public static final String PROVIDER_ID = "test-raise-exception-executor";
public static final String TARGET_CP_EVENTS = "target-cp-events";
private static final ProviderConfigProperty TARGET_CP_EVENTS_PROPERTY = new ProviderConfigProperty(
TARGET_CP_EVENTS, null, null, ProviderConfigProperty.MULTIVALUED_STRING_TYPE, null);
@Override @Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new TestRaiseExeptionExecutor(session, model); return new TestRaiseExeptionExecutor(session);
} }
@Override @Override
@ -61,12 +55,12 @@ public class TestRaiseExeptionExecutorFactory implements ClientPolicyExecutorPro
@Override @Override
public String getHelpText() { public String getHelpText() {
return null; return "NA";
} }
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return Arrays.asList(TARGET_CP_EVENTS_PROPERTY); return Collections.emptyList();
} }
} }

View file

@ -0,0 +1,106 @@
/*
* 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.testsuite.client;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
import org.junit.Test;
import org.keycloak.common.Profile;
import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
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.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
@EnableFeature(value = Profile.Feature.CLIENT_POLICIES, skipRestart = true)
@AuthServerContainerExclude({REMOTE})
public class ClientPoliciesImportExportTest extends AbstractClientPoliciesTest {
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
testRealms.add(realm);
}
@Override
protected boolean isImportAfterEachMethod() {
return true;
}
@Override
public void beforeAbstractKeycloakTestRealmImport() {
removeAllRealmsDespiteMaster();
}
@Test
public void testSingleFileRealmExportImport() throws Throwable {
testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "client-policies-exported-realm.json";
testingClient.testing().exportImport().setFile(targetFilePath);
loadValidProfilesAndPolicies();
testRealmExportImport();
}
private void testRealmExportImport() throws LifecycleException {
testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT);
testingClient.testing().exportImport().setRealmName("test");
testingClient.testing().exportImport().runExport();
// Delete some realm (and some data in admin realm)
adminClient.realm("test").remove();
Assert.assertNames(adminClient.realms().findAll(), "master");
// Configure import
testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT);
testingClient.testing().exportImport().runImport();
// Ensure data are imported back, but just for "test" realm
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);
});
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"),
rep);
});
}
}

View file

@ -0,0 +1,396 @@
/*
* 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.testsuite.client;
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.List;
import org.junit.Test;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.common.Profile;
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.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.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
@EnableFeature(value = Profile.Feature.CLIENT_POLICIES, skipRestart = true)
@AuthServerContainerExclude({REMOTE})
public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest {
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
testRealms.add(realm);
}
// Invalid formatted json profiles/policies are not accepted. Existing profiles/policies remain unchanged.
// Well-formed json but invalid semantic profiles/policies are not accepted. Existing profiles/policies remain unchanged.
// Recognized but invalid type fields are not accepted. Existing profiles/policies remain unchanged.
// Unrecognized fields of profiles/policies are not accepted. Existing profiles/policies are changed.
// Unrecognized fields of executors/conditions are accepted. Existing profiles/policies are changed.
// Duplicated fields of profiles/policies are accepted but the only last one is accepted. Existing profiles/policies are changed.
@Test
public void testLoadBuiltinProfilesAndPolicies() throws Exception {
// retrieve loaded builtin profiles
ClientProfilesRepresentation actualProfilesRep = getProfiles();
// same profiles
assertExpectedProfiles(actualProfilesRep, Arrays.asList("builtin-default-profile"));
// 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);
// each executor
assertExpectedExecutors(Arrays.asList(SecureSessionEnforceExecutorFactory.PROVIDER_ID), actualProfileRep);
// retrieve loaded builtin policies
ClientPoliciesRepresentation actualPoliciesRep = getPolicies();
// same policies
assertExpectedPolicies(Arrays.asList("builtin-default-policy"), actualPoliciesRep);
// each policy
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);
}
@Test
public void testUpdateValidProfilesAndPolicies() throws Exception {
loadValidProfilesAndPolicies();
assertExpectedLoadedProfiles((ClientProfilesRepresentation reps)->{
ClientProfileRepresentation rep = getProfileRepresentation(reps, "ordinal-test-profile");
assertExpectedProfile(rep, "ordinal-test-profile", "The profile that can be loaded.", false);
});
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"),
rep);
});
// update existing profiles
String modifiedProfileDescription = "The profile has been updated.";
ClientProfilesRepresentation actualProfilesRep = getProfilesWithoutBuiltin();
ClientProfilesBuilder profilesBuilder = new ClientProfilesBuilder();
actualProfilesRep.getProfiles().stream().forEach(i->{
if (i.getName().equals("ordinal-test-profile")) {
i.setDescription(modifiedProfileDescription);
}
profilesBuilder.addProfile(i);
});
updateProfiles(profilesBuilder.toString());
assertExpectedLoadedProfiles((ClientProfilesRepresentation reps)->{
ClientProfileRepresentation rep = getProfileRepresentation(reps, "ordinal-test-profile");
assertExpectedProfile(rep, "ordinal-test-profile", modifiedProfileDescription, false);
});
// update existing policies
String modifiedPolicyDescription = "The policy has also been updated.";
ClientPoliciesRepresentation actualPoliciesRep = getPoliciesWithoutBuiltin();
ClientPoliciesBuilder policiesBuilder = new ClientPoliciesBuilder();
actualPoliciesRep.getPolicies().stream().forEach(i->{
if (i.getName().equals("new-policy")) {
i.setDescription(modifiedPolicyDescription);
i.setEnable(null);
}
policiesBuilder.addPolicy(i);
});
updatePolicies(policiesBuilder.toString());
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"),
rep);
});
}
@Test
public void testDuplicatedProfiles() throws Exception {
String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles());
// load profiles
ClientProfileRepresentation duplicatedProfileRep = (new ClientProfileBuilder()).createProfile("builtin-basic-security", "Enforce basic security level", Boolean.TRUE, null)
.addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID,
createSecureClientAuthEnforceExecutorConfig(
Boolean.FALSE,
Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID, JWTClientAuthenticator.PROVIDER_ID),
null))
.addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID,
createPKCEEnforceExecutorConfig(Boolean.FALSE))
.addExecutor("no-such-executor",
createPKCEEnforceExecutorConfig(Boolean.TRUE))
.toRepresentation();
ClientProfileRepresentation loadedProfileRep = (new ClientProfileBuilder()).createProfile("ordinal-test-profile", "The profile that can be loaded.", Boolean.FALSE, null)
.addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID,
createSecureClientAuthEnforceExecutorConfig(
Boolean.TRUE,
Arrays.asList(JWTClientAuthenticator.PROVIDER_ID),
JWTClientAuthenticator.PROVIDER_ID))
.toRepresentation();
String json = (new ClientProfilesBuilder())
.addProfile(duplicatedProfileRep)
.addProfile(loadedProfileRep)
.addProfile(duplicatedProfileRep)
.toString();
try {
updateProfiles(json);
} catch (ClientPolicyException cpe) {
assertEquals("Bad Request", cpe.getErrorDetail());
String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles());
assertEquals(beforeUpdateProfilesJson, afterFailedUpdateProfilesJson);
return;
}
fail();
}
@Test
public void testNullProfiles() throws Exception {
String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles());
String json = null;
try {
updateProfiles(json);
} catch (ClientPolicyException cpe) {
assertEquals("Bad Request", cpe.getErrorDetail());
String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles());
assertEquals(beforeUpdateProfilesJson, afterFailedUpdateProfilesJson);
return;
}
fail();
}
@Test
public void testInvalidFormattedJsonProfiles() throws Exception {
String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles());
String json = "{\n"
+ " \"profiles\": [\n"
+ " {\n"
+ " \"name\" : \"ordinal-test-profile\",\n"
+ " \"description\" : \"invalid , added.\",\n"
+ " \"builtin\" : false,\n"
+ " \"executors\": [\n"
+ " {\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"
+ "}";
try {
updateProfiles(json);
} catch (ClientPolicyException cpe) {
assertEquals("Bad Request", cpe.getErrorDetail());
String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles());
assertEquals(beforeUpdateProfilesJson, afterFailedUpdateProfilesJson);
return;
}
fail();
}
@Test
public void testInvalidFieldTypeJsonProfiles() throws Exception {
String beforeUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles());
String json = "{\n"
+ " \"profiles\": [\n"
+ " {\n"
+ " \"name\" : \"ordinal-test-profile\",\n"
+ " \"description\" : \"Not builtin profile that should be skipped.\",\n"
+ " \"builtin\" : \"no\",\n"
+ " \"executors\": [\n"
+ " {\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"
+ "}";
try {
updateProfiles(json);
} catch (ClientPolicyException cpe) {
assertEquals("Bad Request", cpe.getErrorDetail());
String afterFailedUpdateProfilesJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(getProfiles());
assertEquals(beforeUpdateProfilesJson, afterFailedUpdateProfilesJson);
return;
}
fail();
}
@Test
public void testDuplicatedPolicies() throws Exception {
String beforeUpdatePoliciesJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(getPolicies());
// load policies
ClientPolicyRepresentation duplicatedPoliciesRep =
(new ClientPolicyBuilder()).createPolicy(
"builtin-duplicated-new-policy",
"builtin duplicated new policy is ignored.",
Boolean.FALSE,
Boolean.TRUE,
null,
Arrays.asList("builtin-default-profile"))
.addCondition(ClientRolesConditionFactory.PROVIDER_ID,
createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE)))
.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"))
.addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID,
createClientAccessTypeConditionConfig(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_PUBLIC, ClientAccessTypeConditionFactory.TYPE_BEARERONLY)))
.toRepresentation();
String json = (new ClientPoliciesBuilder())
.addPolicy(duplicatedPoliciesRep)
.addPolicy(loadedPolicyRep)
.addPolicy(duplicatedPoliciesRep)
.toString();
try {
updatePolicies(json);
} catch (ClientPolicyException cpe) {
assertEquals("Bad Request", cpe.getErrorDetail());
String afterFailedUpdatePoliciesJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(getPolicies());
assertEquals(beforeUpdatePoliciesJson, afterFailedUpdatePoliciesJson);
return;
}
fail();
}
@Test
public void testNullPolicies() throws Exception {
String beforeUpdatePoliciesJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(getPolicies());
String json = null;
try {
updatePolicies(json);
} catch (ClientPolicyException cpe) {
assertEquals("Bad Request", cpe.getErrorDetail());
String afterFailedUpdatePoliciesJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(getPolicies());
assertEquals(beforeUpdatePoliciesJson, afterFailedUpdatePoliciesJson);
return;
}
fail();
}
@Test
public void testInvalidFormattedJsonPolicies() throws Exception {
String beforeUpdatePoliciesJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(getPolicies());
String json = "{\n"
+ " \"policies\": [\n"
+ " {\n"
+ " \"name\": \"ordinal-test-policy\",\n"
+ " \"description\" : \"bracket not enclosed properly.\",\n"
+ " \"builtin\": false,\n"
+ " \"enable\": true,\n"
+ " \"conditions\": [\n"
+ " {\n"
+ " \"new-clientupdatesourcehost-condition\": {\n"
+ " \"trusted-hosts\": [\"myuniversity\"],\n"
+ " \"host-sending-request-must-match\" : [true]\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"profiles\": [ \"builtin-advanced-security\" ]\n"
+ " }\n"
+ "}";
try {
updatePolicies(json);
} catch (ClientPolicyException cpe) {
assertEquals("Bad Request", cpe.getErrorDetail());
String afterFailedUpdatePoliciesJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(getPolicies());
assertEquals(beforeUpdatePoliciesJson, afterFailedUpdatePoliciesJson);
return;
}
fail();
}
@Test
public void testInvalidFieldTypeJsonPolicies() throws Exception {
String beforeUpdatePoliciesJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(getPolicies());
String json = "{ \n"
+ " \"policies\": [ \n"
+ " { \n"
+ " \"name\": \"ordinal-test-policy\", \n"
+ " \"description\" : \"Not builtin policy that should be skipped.\", \n"
+ " \"builtin\": false, \n"
+ " \"enable\": true, \n"
+ " \"conditions\": true, \n"
+ " \"profiles\": [ \"builtin-advanced-security\" ] \n"
+ " } \n"
+ " ] \n"
+ "}";
try {
updatePolicies(json);
} catch (ClientPolicyException cpe) {
assertEquals("Bad Request", cpe.getErrorDetail());
String afterFailedUpdatePoliciesJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(getPolicies());
assertEquals(beforeUpdatePoliciesJson, afterFailedUpdatePoliciesJson);
return;
}
fail();
}
}