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:
parent
d1ad905407
commit
42dec08f3c
102 changed files with 5345 additions and 1848 deletions
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
}
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
org.keycloak.services.clientpolicy.DefaultClientPolicyProviderFactory
|
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue