From 42dec08f3c1c25013f79fa192dd42a17863e6a89 Mon Sep 17 00:00:00 2001 From: Takashi Norimatsu Date: Tue, 6 Apr 2021 23:31:10 +0900 Subject: [PATCH] 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 --- .../idm/ClientPoliciesRepresentation.java | 41 + .../idm/ClientPolicyRepresentation.java | 87 ++ .../idm/ClientProfileRepresentation.java | 68 + .../idm/ClientProfilesRepresentation.java | 41 + .../idm/RealmRepresentation.java | 23 + .../ClientPoliciesPoliciesResource.java | 26 + .../ClientPoliciesProfilesResource.java | 25 + .../admin/client/resource/RealmResource.java | 6 + .../META-INF/jpa-changelog-13.0.0.xml | 5 + .../keycloak/QuarkusKeycloakApplication.java | 1 + .../models/utils/ModelToRepresentation.java | 3 + .../clientpolicy/ClientPoliciesUtil.java | 762 +++++++++++ .../clientpolicy/ClientPolicyModel.java | 82 ++ .../clientpolicy/ClientPolicyProvider.java | 48 - .../clientpolicy/ClientPolicySpi.java | 46 - .../clientpolicy/ClientPolicyVote.java | 3 + .../clientpolicy/ClientProfileModel.java | 64 + .../ClientPolicyConditionConfiguration.java | 30 + .../ClientPolicyConditionProvider.java | 27 +- .../ClientPolicyConditionProviderFactory.java | 10 +- .../condition/ClientPolicyConditionSpi.java | 3 + .../ClientPolicyExecutorConfiguration.java} | 13 +- .../ClientPolicyExecutorProvider.java | 25 +- .../ClientPolicyExecutorProviderFactory.java | 10 +- .../executor/ClientPolicyExecutorSpi.java | 3 + .../services/org.keycloak.provider.Spi | 1 - .../clientpolicy/ClientPolicyContext.java | 2 + .../clientpolicy/ClientPolicyEvent.java | 5 + .../clientpolicy/ClientPolicyException.java | 10 +- .../clientpolicy/ClientPolicyManager.java | 115 +- .../exportimport/util/ExportUtils.java | 3 + .../oidc/endpoints/AuthorizationEndpoint.java | 2 - .../endpoints/TokenIntrospectionEndpoint.java | 3 - .../endpoints/TokenRevocationEndpoint.java | 1 - .../clientpolicy/ClientPolicyLogger.java | 49 - .../DefaultClientPolicyManager.java | 372 +++++- .../DefaultClientPolicyProvider.java | 127 -- .../DefaultClientPolicyProviderFactory.java | 73 -- ...AbstractClientPolicyConditionProvider.java | 47 - ...tClientPolicyConditionProviderFactory.java | 51 - .../condition/AnyClientCondition.java | 54 +- .../condition/AnyClientConditionFactory.java | 38 +- .../condition/ClientAccessTypeCondition.java | 81 +- .../ClientAccessTypeConditionFactory.java | 44 +- .../condition/ClientRolesCondition.java | 84 +- .../ClientRolesConditionFactory.java | 42 +- .../condition/ClientScopesCondition.java | 96 +- .../ClientScopesConditionFactory.java | 47 +- .../ClientUpdateContextCondition.java | 81 +- .../ClientUpdateContextConditionFactory.java | 45 +- .../ClientUpdateSourceGroupsCondition.java | 85 +- ...entUpdateSourceGroupsConditionFactory.java | 41 +- .../ClientUpdateSourceHostsCondition.java | 111 +- ...ientUpdateSourceHostsConditionFactory.java | 46 +- .../ClientUpdateSourceRolesCondition.java | 79 +- ...ientUpdateSourceRolesConditionFactory.java | 43 +- .../AbstractAdminClientCRUDContext.java | 2 +- .../AbstractDynamicClientCRUDContext.java | 2 +- .../context/AdminClientUpdateContext.java | 2 +- .../context/AuthorizationRequestContext.java | 3 + .../context/LogoutRequestContext.java | 3 + .../context/TokenIntrospectContext.java | 3 + .../context/TokenRefreshContext.java | 3 + .../context/TokenRequestContext.java | 3 + .../context/TokenRevokeContext.java | 3 + .../context/UserInfoRequestContext.java | 3 + ...ntingClientRegistrationPolicyExecutor.java | 83 -- ...ientRegistrationPolicyExecutorFactory.java | 39 - .../executor/HolderOfKeyEnforceExecutor.java | 78 +- .../HolderOfKeyEnforceExecutorFactory.java | 17 +- .../executor/PKCEEnforceExecutor.java | 72 +- .../executor/PKCEEnforceExecutorFactory.java | 23 +- .../SecureClientAuthEnforceExecutor.java | 95 +- ...ecureClientAuthEnforceExecutorFactory.java | 23 +- .../SecureRedirectUriEnforceExecutor.java | 27 +- ...cureRedirectUriEnforceExecutorFactory.java | 10 +- .../executor/SecureRequestObjectExecutor.java | 61 +- .../SecureRequestObjectExecutorFactory.java | 10 +- .../executor/SecureResponseTypeExecutor.java | 43 +- .../SecureResponseTypeExecutorFactory.java | 10 +- .../SecureSessionEnforceExecutor.java | 30 +- .../SecureSessionEnforceExecutorFactory.java | 10 +- ...SecureSigningAlgorithmEnforceExecutor.java | 28 +- ...igningAlgorithmEnforceExecutorFactory.java | 10 +- ...gAlgorithmForSignedJwtEnforceExecutor.java | 26 +- ...thmForSignedJwtEnforceExecutorFactory.java | 7 +- .../services/managers/RealmManager.java | 11 + .../resources/KeycloakApplication.java | 2 + .../admin/ClientPoliciesResource.java | 79 ++ .../admin/ClientProfilesResource.java | 79 ++ .../resources/admin/RealmAdminResource.java | 13 + ...s.clientpolicy.ClientPolicyProviderFactory | 1 - .../keycloak-default-client-policies.json | 18 + .../keycloak-default-client-profiles.json | 14 + .../condition/TestRaiseExeptionCondition.java | 34 +- .../TestRaiseExeptionConditionFactory.java | 12 +- .../executor/TestRaiseExeptionExecutor.java | 33 +- .../TestRaiseExeptionExecutorFactory.java | 18 +- .../client/AbstractClientPoliciesTest.java | 1151 +++++++++++++--- .../ClientPoliciesImportExportTest.java | 106 ++ .../client/ClientPoliciesLoadUpdateTest.java | 396 ++++++ .../testsuite/client/ClientPoliciesTest.java | 1167 +++++++++-------- 102 files changed, 5345 insertions(+), 1848 deletions(-) create mode 100644 core/src/main/java/org/keycloak/representations/idm/ClientPoliciesRepresentation.java create mode 100644 core/src/main/java/org/keycloak/representations/idm/ClientPolicyRepresentation.java create mode 100644 core/src/main/java/org/keycloak/representations/idm/ClientProfileRepresentation.java create mode 100644 core/src/main/java/org/keycloak/representations/idm/ClientProfilesRepresentation.java create mode 100644 integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesPoliciesResource.java create mode 100644 integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesProfilesResource.java create mode 100644 server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPoliciesUtil.java create mode 100644 server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyModel.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyProvider.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicySpi.java create mode 100644 server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientProfileModel.java create mode 100644 server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionConfiguration.java rename server-spi-private/src/main/java/org/keycloak/services/clientpolicy/{ClientPolicyProviderFactory.java => executor/ClientPolicyExecutorConfiguration.java} (65%) delete mode 100644 services/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyLogger.java delete mode 100644 services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProvider.java delete mode 100644 services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProviderFactory.java delete mode 100644 services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProvider.java delete mode 100644 services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProviderFactory.java delete mode 100644 services/src/main/java/org/keycloak/services/clientpolicy/executor/AbstractAugumentingClientRegistrationPolicyExecutor.java delete mode 100644 services/src/main/java/org/keycloak/services/clientpolicy/executor/AbstractAugumentingClientRegistrationPolicyExecutorFactory.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/ClientPoliciesResource.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/ClientProfilesResource.java delete mode 100644 services/src/main/resources/META-INF/services/org.keycloak.services.clientpolicy.ClientPolicyProviderFactory create mode 100644 services/src/main/resources/keycloak-default-client-policies.json create mode 100644 services/src/main/resources/keycloak-default-client-profiles.json create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesImportExportTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesLoadUpdateTest.java diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientPoliciesRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientPoliciesRepresentation.java new file mode 100644 index 0000000000..b7127581b1 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientPoliciesRepresentation.java @@ -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 Takashi Norimatsu + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ClientPoliciesRepresentation { + protected List policies; + + public List getPolicies() { + return policies; + } + + public void setPolicies(List policies) { + this.policies = policies; + } + +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientPolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyRepresentation.java new file mode 100644 index 0000000000..8b623d213d --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientPolicyRepresentation.java @@ -0,0 +1,87 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.representations.idm; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Client Policy's external representation class + * + * @author Takashi Norimatsu + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ClientPolicyRepresentation { + + protected String name; + protected String description; + protected Boolean builtin; + protected Boolean enable; + protected List conditions; + protected List 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 getConditions() { + return conditions; + } + + public void setConditions(List conditions) { + this.conditions = conditions; + } + + public List getProfiles() { + return profiles; + } + + public void setProfiles(List profiles) { + this.profiles = profiles; + } + +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientProfileRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientProfileRepresentation.java new file mode 100644 index 0000000000..7c88fd2478 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientProfileRepresentation.java @@ -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 Takashi Norimatsu + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ClientProfileRepresentation { + + protected String name; + protected String description; + protected Boolean builtin; + protected List 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 getExecutors() { + return executors; + } + + public void setExecutors(List executors) { + this.executors = executors; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientProfilesRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientProfilesRepresentation.java new file mode 100644 index 0000000000..1581e21a72 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientProfilesRepresentation.java @@ -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 Takashi Norimatsu + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ClientProfilesRepresentation { + protected List profiles; + + public List getProfiles() { + return profiles; + } + + public void setProfiles(List profiles) { + this.profiles = profiles; + } + +} diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index 396c601dd7..ed2ca93b5c 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -141,6 +141,11 @@ public class RealmRepresentation { protected Boolean webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister; protected List webAuthnPolicyPasswordlessAcceptableAaguids; + // Client Policies/Profiles + + protected ClientProfilesRepresentation clientProfiles; + protected ClientPoliciesRepresentation clientPolicies; + protected List users; protected List federatedUsers; protected List scopeMappings; @@ -1173,6 +1178,24 @@ public class RealmRepresentation { 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() { return browserFlow; } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesPoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesPoliciesResource.java new file mode 100644 index 0000000000..68545b17ec --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesPoliciesResource.java @@ -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 Takashi Norimatsu + */ +public interface ClientPoliciesPoliciesResource { + + @GET + @NoCache + @Produces(MediaType.APPLICATION_JSON) + String getPolicies(); + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + Response updatePolicies(final String json); +} + diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesProfilesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesProfilesResource.java new file mode 100644 index 0000000000..93b9517d59 --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesProfilesResource.java @@ -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 Takashi Norimatsu + */ +public interface ClientPoliciesProfilesResource { + + @GET + @NoCache + @Produces(MediaType.APPLICATION_JSON) + String getProfiles(); + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + Response updateProfiles(final String json); +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java index 525444e9da..71fec11bf2 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java @@ -18,6 +18,7 @@ package org.keycloak.admin.client.resource; import org.jboss.resteasy.annotations.cache.NoCache; +import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.representations.adapters.action.GlobalRequestResult; import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.ClientRepresentation; @@ -282,4 +283,9 @@ public interface RealmResource { @Path("localization") RealmLocalizationResource localization(); + @Path("client-policies/policies") + ClientPoliciesPoliciesResource clientPoliciesPoliciesResource(); + + @Path("client-policies/profiles") + ClientPoliciesProfilesResource clientPoliciesProfilesResource(); } diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-13.0.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-13.0.0.xml index 009521b318..40128f5e84 100644 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-13.0.0.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-13.0.0.xml @@ -51,4 +51,9 @@ + + + + + diff --git a/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java b/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java index fb731cd1ef..b055d16c7e 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java +++ b/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java @@ -58,6 +58,7 @@ public class QuarkusKeycloakApplication extends KeycloakApplication { QuarkusKeycloakSessionFactory instance = QuarkusKeycloakSessionFactory.getInstance(); sessionFactory = instance; instance.init(); + instance.create().clientPolicy().setupClientPoliciesOnKeycloakApp("/keycloak-default-client-profiles.json", "/keycloak-default-client-policies.json"); sessionFactory.publish(new PostMigrationEvent()); } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index a1860d0f67..351f6152fd 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -93,6 +93,9 @@ public class ModelToRepresentation { REALM_EXCLUDED_ATTRIBUTES.add("webAuthnPolicyCreateTimeoutPasswordless"); REALM_EXCLUDED_ATTRIBUTES.add("webAuthnPolicyAvoidSameAuthenticatorRegisterPasswordless"); REALM_EXCLUDED_ATTRIBUTES.add("webAuthnPolicyAcceptableAaguidsPasswordless"); + + REALM_EXCLUDED_ATTRIBUTES.add("client-policies.profiles"); + REALM_EXCLUDED_ATTRIBUTES.add("client-policies.policies"); } diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPoliciesUtil.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPoliciesUtil.java new file mode 100644 index 0000000000..69553ce346 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPoliciesUtil.java @@ -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 Takashi Norimatsu + */ +public class ClientPoliciesUtil { + + private static final Logger logger = Logger.getLogger(ClientPoliciesUtil.class); + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * gets existing client profiles in a realm as representation. + * not return null. + */ + public static ClientProfilesRepresentation getClientProfilesRepresentation(KeycloakSession session, RealmModel realm) throws ClientPolicyException { + ClientProfilesRepresentation profilesRep = null; + String profilesJson = null; + + // get existing profiles json + if (realm != null) { + profilesJson = session.clientPolicy().getClientProfilesJsonString(realm); + } else { + // if realm not specified, use builtin profiles set in keycloak's binary. + profilesJson = session.clientPolicy().getClientProfilesOnKeycloakApp(); + } + + // deserialize existing profiles (json -> representation) + if (profilesJson == null) { + return new ClientProfilesRepresentation(); + } + profilesRep = convertClientProfilesJsonToRepresentation(profilesJson); + if (profilesRep == null) { + return new ClientProfilesRepresentation(); + } + + return profilesRep; + } + + /** + * gets existing client profiles in a realm as model. + * not return null. + */ + public static Map getClientProfilesModel(KeycloakSession session, RealmModel realm) { + // get existing profiles as json + String profilesJson = session.clientPolicy().getClientProfilesJsonString(realm); + if (profilesJson == null) { + return Collections.emptyMap(); + } + + // deserialize existing profiles (json -> representation) + ClientProfilesRepresentation profilesRep = null; + try { + profilesRep = convertClientProfilesJsonToRepresentation(profilesJson); + } catch (ClientPolicyException e) { + logger.warnv("Failed to serialize client profiles json string. err={0}, errDetail={1}", e.getError(), e.getErrorDetail()); + return Collections.emptyMap(); + } + if (profilesRep == null || profilesRep.getProfiles() == null) { + return Collections.emptyMap(); + } + + // constructing existing profiles (representation -> model) + Map profileMap = new HashMap<>(); + for (ClientProfileRepresentation profileRep : profilesRep.getProfiles()) { + // ignore profile without name + if (profileRep.getName() == null) { + continue; + } + + ClientProfileModel profileModel = new ClientProfileModel(); + profileModel.setName(profileRep.getName()); + profileModel.setDescription(profileRep.getDescription()); + if (profileRep.isBuiltin() != null) { + profileModel.setBuiltin(profileRep.isBuiltin().booleanValue()); + } else { + profileModel.setBuiltin(false); + } + + if (profileRep.getExecutors() == null) { + profileModel.setExecutors(new ArrayList<>()); + profileMap.put(profileRep.getName(), profileModel); + continue; + } + + List executors = new ArrayList<>(); + if (profileRep.getExecutors() != null) { + profileRep.getExecutors().stream().forEach(obj->{ + JsonNode node = objectMapper.convertValue(obj, JsonNode.class); + node.fields().forEachRemaining(executor->{ + ClientPolicyExecutorProvider provider = session.getProvider(ClientPolicyExecutorProvider.class, executor.getKey()); + if (provider == null) { + // executor's provider not found. just skip it. + return; + } + + try { + ClientPolicyExecutorConfiguration configuration = (ClientPolicyExecutorConfiguration) JsonSerialization.mapper.convertValue(executor.getValue(), provider.getExecutorConfigurationClass()); + provider.setupConfiguration(configuration); + executors.add(provider); + } catch (IllegalArgumentException iae) { + logger.warnv("failed for Configuration Setup :: error = {0}", iae.getMessage()); + } + }); + }); + } + profileModel.setExecutors(executors); + + profileMap.put(profileRep.getName(), profileModel); + } + + return profileMap; + } + + /** + * get validated and modified builtin client profiles set on keycloak app as representation. + * it is loaded from json file enclosed in keycloak's binary. + * not return null. + */ + public static ClientProfilesRepresentation getValidatedBuiltinClientProfilesRepresentation(KeycloakSession session, InputStream is) throws ClientPolicyException { + // load builtin client profiles representation + ClientProfilesRepresentation proposedProfilesRep = null; + try { + proposedProfilesRep = JsonSerialization.readValue(is, ClientProfilesRepresentation.class); + } catch (Exception e) { + throw new ClientPolicyException("failed to deserialize builtin proposed client profiles json string.", e.getMessage()); + } + if (proposedProfilesRep == null) { + return new ClientProfilesRepresentation(); + } + + // no profile contained (it is valid) + List proposedProfileRepList = proposedProfilesRep.getProfiles(); + if (proposedProfileRepList == null || proposedProfileRepList.isEmpty()) { + return new ClientProfilesRepresentation(); + } + + // duplicated profile name is not allowed. + if (proposedProfileRepList.size() != proposedProfileRepList.stream().map(i->i.getName()).distinct().count()) { + throw new ClientPolicyException("proposed builtin client profile name duplicated."); + } + + // construct validated and modified profiles from builtin profiles in JSON file enclosed in keycloak binary. + ClientProfilesRepresentation updatingProfilesRep = new ClientProfilesRepresentation(); + updatingProfilesRep.setProfiles(new ArrayList<>()); + List updatingProfileList = updatingProfilesRep.getProfiles(); + + for (ClientProfileRepresentation proposedProfileRep : proposedProfilesRep.getProfiles()) { + if (proposedProfileRep.getName() == null) { + throw new ClientPolicyException("client profile without its name not allowed."); + } + + // ignore proposed ordinal profile because builtin profile can only be added. + if (proposedProfileRep.isBuiltin() == null || !proposedProfileRep.isBuiltin()) { + throw new ClientPolicyException("ordinal client profile not allowed."); + } + + ClientProfileRepresentation profileRep = new ClientProfileRepresentation(); + profileRep.setName(proposedProfileRep.getName()); + profileRep.setDescription(proposedProfileRep.getDescription()); + profileRep.setBuiltin(Boolean.TRUE); + + profileRep.setExecutors(new ArrayList<>()); // to prevent returning null + if (proposedProfileRep.getExecutors() != null) { + for (Object executor : proposedProfileRep.getExecutors()) { + if (isValidExecutor(session, executor) == false) { + throw new ClientPolicyException("proposed client profile contains the executor with its invalid configuration."); + } + profileRep.getExecutors().add(executor); + } + } + + updatingProfileList.add(profileRep); + } + + return updatingProfilesRep; + } + + /** + * convert client profiles as representation to json. + * can return null. + */ + public static String convertClientProfilesRepresentationToJson(ClientProfilesRepresentation reps) throws ClientPolicyException { + return convertRepresentationToJson(reps); + } + + /** + * convert client profiles as json to representation. + * not return null. + */ + private static ClientProfilesRepresentation convertClientProfilesJsonToRepresentation(String json) throws ClientPolicyException { + return convertJsonToRepresentation(json, ClientProfilesRepresentation.class); + } + + /** + * get validated and modified client profiles as json. + * it can be constructed by merging proposed client profiles with existing client profiles. + * can return null. + */ + public static String getValidatedClientProfilesJson(KeycloakSession session, RealmModel realm, ClientProfilesRepresentation proposedProfilesRep) throws ClientPolicyException { + return convertClientProfilesRepresentationToJson(getValidatedClientProfilesRepresentation(session, realm, proposedProfilesRep)); + } + + /** + * get validated and modified client profiles as representation. + * it can be constructed by merging proposed client profiles with existing client profiles. + * not return null. + */ + private static ClientProfilesRepresentation getValidatedClientProfilesRepresentation(KeycloakSession session, RealmModel realm, ClientProfilesRepresentation proposedProfilesRep) throws ClientPolicyException { + if (proposedProfilesRep == null) { + proposedProfilesRep = new ClientProfilesRepresentation(); + } + if (realm == null) { + throw new ClientPolicyException("realm not specified."); + } + + // deserialize existing profiles (json -> representation) + ClientProfilesRepresentation existingProfilesRep = null; + String existingProfilesJson = session.clientPolicy().getClientProfilesJsonString(realm); + if (existingProfilesJson != null) { + existingProfilesRep = convertClientProfilesJsonToRepresentation(existingProfilesJson); + if (existingProfilesRep == null) { + existingProfilesRep = new ClientProfilesRepresentation(); + } + } else { + existingProfilesRep = new ClientProfilesRepresentation(); + } + + // no profile contained (it is valid) + // back to initial builtin profiles + List proposedProfileRepList = proposedProfilesRep.getProfiles(); + if (proposedProfileRepList == null || proposedProfileRepList.isEmpty()) { + proposedProfileRepList = new ArrayList<>(); + proposedProfilesRep.setProfiles(new ArrayList<>()); + } + + // duplicated profile name is not allowed. + if (proposedProfileRepList.size() != proposedProfileRepList.stream().map(i->i.getName()).distinct().count()) { + throw new ClientPolicyException("proposed client profile name duplicated."); + } + + // construct updating profiles from existing profiles and proposed profiles + ClientProfilesRepresentation updatingProfilesRep = new ClientProfilesRepresentation(); + updatingProfilesRep.setProfiles(new ArrayList<>()); + List updatingProfileList = updatingProfilesRep.getProfiles(); + + // add existing builtin profiles to updating profiles + List existingProfileList = existingProfilesRep.getProfiles(); + if (existingProfileList != null && !existingProfileList.isEmpty()) { + existingProfileList.stream().filter(i->i.isBuiltin()).forEach(i->updatingProfileList.add(i)); + } + + for (ClientProfileRepresentation proposedProfileRep : proposedProfilesRep.getProfiles()) { + if (proposedProfileRep.getName() == null) { + throw new ClientPolicyException("client profile without its name not allowed."); + } + + // newly proposed builtin profile not allowed because builtin profile cannot added/deleted/modified. + if (proposedProfileRep.isBuiltin() != null && proposedProfileRep.isBuiltin()) { + throw new ClientPolicyException("newly builtin proposed client profile not allowed."); + } + + // not allow to overwrite builtin profiles + if (updatingProfileList.stream().anyMatch(i->proposedProfileRep.getName().equals(i.getName()))) { + throw new ClientPolicyException("proposed client profile name is the same one of the builtin profile."); + } + + // basically, proposed profile totally overrides existing profile + ClientProfileRepresentation profileRep = new ClientProfileRepresentation(); + profileRep.setName(proposedProfileRep.getName()); + profileRep.setDescription(proposedProfileRep.getDescription()); + profileRep.setBuiltin(Boolean.FALSE); + profileRep.setExecutors(new ArrayList<>()); + if (proposedProfileRep.getExecutors() != null) { + for (Object executor : proposedProfileRep.getExecutors()) { + if (isValidExecutor(session, executor) == false) { + throw new ClientPolicyException("proposed client profile contains the executor with its invalid configuration."); + } + profileRep.getExecutors().add(executor); + } + } + + updatingProfileList.add(profileRep); + } + + return updatingProfilesRep; + } + + /** + * get validated and modified builtin client profiles in a realm as representation. + * it can be constructed by merging proposed client profiles with existing client profiles. + * not return null. + */ + public static ClientProfilesRepresentation getValidatedClientProfilesRepresentation(KeycloakSession session, RealmModel realm, String profilesJson) throws ClientPolicyException { + if (profilesJson == null) { + throw new ClientPolicyException("no client profiles json."); + } + + // deserialize existing profiles (json -> representation) + ClientProfilesRepresentation proposedProfilesRep = convertClientProfilesJsonToRepresentation(profilesJson); + + return getValidatedClientProfilesRepresentation(session, realm, proposedProfilesRep); + } + + /** + * check whether the proposed executor's provider can be found in keycloak's ClientPolicyExecutorProvider list. + * not return null. + */ + private static boolean isValidExecutor(KeycloakSession session, Object executor) { + return isValidComponent(session, executor, "executor", (String providerId) -> { + Set providerSet = session.listProviderIds(ClientPolicyExecutorProvider.class); + if (providerSet != null && providerSet.contains(providerId)) { + return true; + } + logger.warnv("no executor provider found. providerId = {0}", providerId); + return false; + }); + } + + + /** + * get existing client policies in a realm as representation. + * not return null. + */ + public static ClientPoliciesRepresentation getClientPoliciesRepresentation(KeycloakSession session, RealmModel realm) throws ClientPolicyException { + ClientPoliciesRepresentation policiesRep = null; + String policiesJson = null; + + // get existing policies json + if (realm != null) { + policiesJson = session.clientPolicy().getClientPoliciesJsonString(realm); + } else { + // if realm not specified, use builtin policies set in keycloak's binary. + policiesJson = session.clientPolicy().getClientPoliciesOnKeycloakApp(); + } + + // deserialize existing policies (json -> representation) + if (policiesJson == null) { + return new ClientPoliciesRepresentation(); + } + policiesRep = convertClientPoliciesJsonToRepresentation(policiesJson); + if (policiesRep == null) { + return new ClientPoliciesRepresentation(); + } + + return policiesRep; + } + + /** + * get existing enabled client policies in a realm as model. + * not return null. + */ + public static List getEnabledClientProfilesModel(KeycloakSession session, RealmModel realm) { + // get existing profiles as json + String policiesJson = session.clientPolicy().getClientPoliciesJsonString(realm); + if (policiesJson == null) { + return Collections.emptyList(); + } + + // deserialize existing policies (json -> representation) + ClientPoliciesRepresentation policiesRep = null; + try { + policiesRep = convertClientPoliciesJsonToRepresentation(policiesJson); + } catch (ClientPolicyException e) { + logger.warnv("Failed to serialize client policies json string. err={0}, errDetail={1}", e.getError(), e.getErrorDetail()); + return Collections.emptyList(); + } + if (policiesRep == null || policiesRep.getPolicies() == null) { + return Collections.emptyList(); + } + + // constructing existing policies (representation -> model) + List policyList = new ArrayList<>(); + for (ClientPolicyRepresentation policyRep: policiesRep.getPolicies()) { + // ignore policy without name + if (policyRep.getName() == null) { + continue; + } + // pick up only enabled policy + if (policyRep.isEnable() == null || policyRep.isEnable() == false) { + continue; + } + + ClientPolicyModel policyModel = new ClientPolicyModel(); + policyModel.setName(policyRep.getName()); + policyModel.setDescription(policyRep.getDescription()); + policyModel.setEnable(true); + if (policyRep.isBuiltin() != null) { + policyModel.setBuiltin(policyRep.isBuiltin().booleanValue()); + } else { + policyModel.setBuiltin(false); + } + + List conditions = new ArrayList<>(); + if (policyRep.getConditions() != null) { + policyRep.getConditions().stream().forEach(obj->{ + JsonNode node = objectMapper.convertValue(obj, JsonNode.class); + node.fields().forEachRemaining(condition->{ + ClientPolicyConditionProvider provider = session.getProvider(ClientPolicyConditionProvider.class, condition.getKey()); + if (provider == null) { + // condition's provider not found. just skip it. + return; + } + + try { + ClientPolicyConditionConfiguration configuration = (ClientPolicyConditionConfiguration) JsonSerialization.mapper.convertValue(condition.getValue(), provider.getConditionConfigurationClass()); + provider.setupConfiguration(configuration); + conditions.add(provider); + } catch (IllegalArgumentException iae) { + logger.warnv("failed for Configuration Setup :: error = {0}", iae.getMessage()); + } + }); + }); + } + policyModel.setConditions(conditions); + + if (policyRep.getProfiles() != null) { + policyModel.setProfiles(policyRep.getProfiles().stream().collect(Collectors.toList())); + } + + policyList.add(policyModel); + } + + return policyList; + } + + /** + * get validated and modified builtin client policies set on keycloak app as representation. + * it is loaded from json file enclosed in keycloak's binary. + * not return null. + */ + public static ClientPoliciesRepresentation getValidatedBuiltinClientPoliciesRepresentation(KeycloakSession session, InputStream is) throws ClientPolicyException { + // load builtin client policies representation + ClientPoliciesRepresentation proposedPoliciesRep = null; + try { + proposedPoliciesRep = JsonSerialization.readValue(is, ClientPoliciesRepresentation.class); + } catch (Exception e) { + throw new ClientPolicyException("failed to deserialize builtin proposed client policies json string.", e.getMessage()); + } + if (proposedPoliciesRep == null) { + proposedPoliciesRep = new ClientPoliciesRepresentation(); + } + + // no policy contained (it is valid) + List proposedPolicyRepList = proposedPoliciesRep.getPolicies(); + if (proposedPolicyRepList == null || proposedPolicyRepList.isEmpty()) { + return new ClientPoliciesRepresentation(); + } + + // duplicated policy name is not allowed. + if (proposedPolicyRepList.size() != proposedPolicyRepList.stream().map(i->i.getName()).distinct().count()) { + throw new ClientPolicyException("proposed builtin client policy name duplicated."); + } + + // construct validated and modified policies from builtin profiles in JSON file enclosed in keycloak binary. + ClientPoliciesRepresentation updatingPoliciesRep = new ClientPoliciesRepresentation(); + updatingPoliciesRep.setPolicies(new ArrayList<>()); + List updatingPoliciesList = updatingPoliciesRep.getPolicies(); + + for (ClientPolicyRepresentation proposedPolicyRep : proposedPoliciesRep.getPolicies()) { + if (proposedPolicyRep.getName() == null) { + throw new ClientPolicyException("proposed client policy name missing."); + } + + // ignore proposed ordinal policy because builtin policy can only be added. + if (proposedPolicyRep.isBuiltin() == null || !proposedPolicyRep.isBuiltin()) { + throw new ClientPolicyException("ordinal client policy not allowed."); + } + + ClientPolicyRepresentation policyRep = new ClientPolicyRepresentation(); + policyRep.setName(proposedPolicyRep.getName()); + policyRep.setDescription(proposedPolicyRep.getDescription()); + policyRep.setBuiltin(Boolean.TRUE); + Boolean enabled = (proposedPolicyRep.isEnable() != null) ? proposedPolicyRep.isEnable() : Boolean.FALSE; + policyRep.setEnable(enabled); + + policyRep.setConditions(new ArrayList<>()); + if (proposedPolicyRep.getConditions() != null) { + for (Object condition : proposedPolicyRep.getConditions()) { + if (isValidCondition(session, condition) == false) { + throw new ClientPolicyException("the proposed client policy contains the condition with its invalid configuration."); + } + policyRep.getConditions().add(condition); + } + } + + Set existingProfileNames = new HashSet<>(); + ClientProfilesRepresentation reps = getClientProfilesRepresentation(session, null); + reps.getProfiles().stream().map(profile->profile.getName()).forEach(profileName->existingProfileNames.add(profileName)); + policyRep.setProfiles(new ArrayList<>()); + if (proposedPolicyRep.getProfiles() != null) { + for (String profileName : proposedPolicyRep.getProfiles()) { + if (existingProfileNames.contains(profileName) == false) { + throw new ClientPolicyException("referring not existing client profile not allowed."); + } + } + proposedPolicyRep.getProfiles().stream().distinct().forEach(profileName->policyRep.getProfiles().add(profileName)); + } + + updatingPoliciesList.add(policyRep); + } + + return updatingPoliciesRep; + } + + /** + * convert client policies as representation to json. + * can return null. + */ + public static String convertClientPoliciesRepresentationToJson(ClientPoliciesRepresentation reps) throws ClientPolicyException { + return convertRepresentationToJson(reps); + } + + /** + * convert client policies as json to representation. + * not return null. + */ + private static ClientPoliciesRepresentation convertClientPoliciesJsonToRepresentation(String json) throws ClientPolicyException { + return convertJsonToRepresentation(json, ClientPoliciesRepresentation.class); + } + + /** + * get validated and modified client policies as json. + * it can be constructed by merging proposed client policies with existing client policies. + * can return null. + */ + public static String getValidatedClientPoliciesJson(KeycloakSession session, RealmModel realm, ClientPoliciesRepresentation proposedPoliciesRep) throws ClientPolicyException { + return convertClientPoliciesRepresentationToJson(getValidatedClientPoliciesRepresentation(session, realm, proposedPoliciesRep)); + } + + /** + * get validated and modified client policies as representation. + * it can be constructed by merging proposed client policies with existing client policies. + * not return null. + */ + private static ClientPoliciesRepresentation getValidatedClientPoliciesRepresentation(KeycloakSession session, RealmModel realm, ClientPoliciesRepresentation proposedPoliciesRep) throws ClientPolicyException { + if (proposedPoliciesRep == null) { + proposedPoliciesRep = new ClientPoliciesRepresentation(); + } + if (realm == null) { + throw new ClientPolicyException("realm not specified."); + } + + // deserialize existing profiles (json -> represetation) + ClientPoliciesRepresentation existingPoliciesRep = null; + String existingPoliciesJson = session.clientPolicy().getClientPoliciesJsonString(realm); + if (existingPoliciesJson != null) { + existingPoliciesRep = convertClientPoliciesJsonToRepresentation(existingPoliciesJson); + if (existingPoliciesRep == null) { + existingPoliciesRep = new ClientPoliciesRepresentation(); + } + } else { + existingPoliciesRep = new ClientPoliciesRepresentation(); + } + + // no policy contained (it is valid) + // back to initial builtin policies + List proposedPolicyRepList = proposedPoliciesRep.getPolicies(); + if (proposedPolicyRepList == null || proposedPolicyRepList.isEmpty()) { + proposedPolicyRepList = new ArrayList<>(); + proposedPoliciesRep.setPolicies(new ArrayList<>()); + } + + // duplicated policy name is not allowed. + if (proposedPolicyRepList.size() != proposedPolicyRepList.stream().map(i->i.getName()).distinct().count()) { + throw new ClientPolicyException("proposed client policy name duplicated."); + } + + // construct updating policies from existing policies and proposed policies + ClientPoliciesRepresentation updatingPoliciesRep = new ClientPoliciesRepresentation(); + updatingPoliciesRep.setPolicies(new ArrayList<>()); + List updatingPoliciesList = updatingPoliciesRep.getPolicies(); + + // add existing builtin policies to updating policies + List existingPoliciesList = existingPoliciesRep.getPolicies(); + if (existingPoliciesList != null && !existingPoliciesList.isEmpty()) { + existingPoliciesList.stream().filter(i->i.isBuiltin()).forEach(i->updatingPoliciesList.add(i)); + } + + for (ClientPolicyRepresentation proposedPolicyRep : proposedPoliciesRep.getPolicies()) { + if (proposedPolicyRep.getName() == null) { + throw new ClientPolicyException("proposed client policy name missing."); + } + + // newly proposed builtin policy not allowed because builtin policy cannot added/deleted/modified. + Boolean enabled = (proposedPolicyRep.isEnable() != null) ? proposedPolicyRep.isEnable() : Boolean.FALSE; + if (proposedPolicyRep.isBuiltin() != null && proposedPolicyRep.isBuiltin()) { + // only enable field of the existing builtin policy can be overridden. + if (updatingPoliciesList.stream().anyMatch(i->i.getName().equals(proposedPolicyRep.getName()))) { + updatingPoliciesList.stream().filter(i->i.getName().equals(proposedPolicyRep.getName())).forEach(i->i.setEnable(enabled)); + continue; + } + throw new ClientPolicyException("newly builtin proposed client policy not allowed."); + } + + // basically, proposed policy totally overrides existing policy except for enabled field.. + ClientPolicyRepresentation policyRep = new ClientPolicyRepresentation(); + policyRep.setName(proposedPolicyRep.getName()); + policyRep.setDescription(proposedPolicyRep.getDescription()); + policyRep.setBuiltin(Boolean.FALSE); + policyRep.setEnable(enabled); + + policyRep.setConditions(new ArrayList<>()); + if (proposedPolicyRep.getConditions() != null) { + for (Object condition : proposedPolicyRep.getConditions()) { + if (isValidCondition(session, condition) == false) { + throw new ClientPolicyException("the proposed client policy contains the condition with its invalid configuration."); + } + policyRep.getConditions().add(condition); + } + } + + Set existingProfileNames = new HashSet<>(); + ClientProfilesRepresentation reps = getClientProfilesRepresentation(session, realm); + if (reps.getProfiles() != null) { + reps.getProfiles().stream().map(profile->profile.getName()).forEach(profileName->existingProfileNames.add(profileName)); + } + policyRep.setProfiles(new ArrayList<>()); + if (proposedPolicyRep.getProfiles() != null) { + for (String profileName : proposedPolicyRep.getProfiles()) { + if (existingProfileNames.contains(profileName) == false) { + throw new ClientPolicyException("referring not existing client profile not allowed."); + } + } + proposedPolicyRep.getProfiles().stream().distinct().forEach(profileName->policyRep.getProfiles().add(profileName)); + } + + updatingPoliciesList.add(policyRep); + } + + return updatingPoliciesRep; + } + + /** + * get validated and modified builtin client policies in a realm as representation. + * it can be constructed by merging proposed client policies with existing client policies. + * not return null. + */ + public static ClientPoliciesRepresentation getValidatedClientPoliciesRepresentation(KeycloakSession session, RealmModel realm, String policiesJson) throws ClientPolicyException { + if (policiesJson == null) { + throw new ClientPolicyException("no client policies json."); + } + // deserialize existing policies (json -> representation) + ClientPoliciesRepresentation proposedPoliciesRep = convertClientPoliciesJsonToRepresentation(policiesJson); + return getValidatedClientPoliciesRepresentation(session, realm, proposedPoliciesRep); + } + + /** + * check whether the proposed condition's provider can be found in keycloak's ClientPolicyConditionProvider list. + * not return null. + */ + private static boolean isValidCondition(KeycloakSession session, Object condition) { + return isValidComponent(session, condition, "condition", (String providerId) -> { + Set providerSet = session.listProviderIds(ClientPolicyConditionProvider.class); + if (providerSet != null && providerSet.contains(providerId)) { + return true; + } + logger.warnv("no executor provider found. providerId = {0}", providerId); + return false; + }); + } + + + private static boolean isValidComponent(KeycloakSession session, Object obj, String type, Predicate f) { + JsonNode node = null; + + try { + node = objectMapper.convertValue(obj, JsonNode.class); + } catch (IllegalArgumentException iae) { + logger.warnv("invalid json string representating {0}. err={1}", type, iae.getMessage()); + return false; + } + + Iterator> it = node.fields(); + while (it.hasNext()) { + Entry entry = it.next(); + // whether find provider + if(!f.test(entry.getKey())) return false; + } + return true; + } + + private static String convertRepresentationToJson(Object reps) throws ClientPolicyException { + if (reps == null) return null; + + String json = null; + try { + json = objectMapper.writeValueAsString(reps); + } catch (JsonProcessingException jpe) { + throw new ClientPolicyException(jpe.getMessage()); + } + + return json; + } + + private static T convertJsonToRepresentation(String json, Class type) throws ClientPolicyException { + if (json == null) { + throw new ClientPolicyException("no json."); + } + + T rep = null; + try { + rep = JsonSerialization.readValue(json, type); + } catch (IOException ioe) { + throw new ClientPolicyException("failed to deserialize.", ioe.getMessage()); + } + + return rep; + } + +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyModel.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyModel.java new file mode 100644 index 0000000000..6934856d14 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyModel.java @@ -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 Takashi Norimatsu + */ +public class ClientPolicyModel implements Serializable { + + protected String name; + protected String description; + protected boolean builtin; + protected boolean enable; + protected List conditions; // ClientPolicyConditionProvider is not visible so that use Object. + protected List 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 getConditions() { + return conditions; + } + + public void setConditions(List conditions) { + this.conditions = conditions; + } + + public List getProfiles() { + return profiles; + } + + public void setProfiles(List profiles) { + this.profiles = profiles; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyProvider.java deleted file mode 100644 index 365acead4b..0000000000 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyProvider.java +++ /dev/null @@ -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 getConditions(); - - /** - * returns the list of executors which this provider accommodates. - * - * @return list of executors - */ - List getExecutors(); - - String getName(); - - String getProviderId(); -} diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicySpi.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicySpi.java deleted file mode 100644 index d6b0bc70ee..0000000000 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicySpi.java +++ /dev/null @@ -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 getProviderClass() { - return ClientPolicyProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return ClientPolicyProviderFactory.class; - } - -} diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyVote.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyVote.java index 88d9c529e2..3b2b8ce3ee 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyVote.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyVote.java @@ -17,6 +17,9 @@ package org.keycloak.services.clientpolicy; +/** + * @author Takashi Norimatsu + */ public enum ClientPolicyVote { YES, NO, diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientProfileModel.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientProfileModel.java new file mode 100644 index 0000000000..a4bc0c3bdd --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientProfileModel.java @@ -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 Takashi Norimatsu + */ +public class ClientProfileModel implements Serializable { + + protected String name; + protected String description; + protected boolean builtin; + protected List executors; // ClientPolicyExecutorProvider is not visible so that use Object. + + 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 getExecutors() { + return executors; + } + + public void setExecutors(List executors) { + this.executors = executors; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionConfiguration.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionConfiguration.java new file mode 100644 index 0000000000..b1b5ce34d7 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionConfiguration.java @@ -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 Takashi Norimatsu + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ClientPolicyConditionConfiguration { +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProvider.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProvider.java index 04532adf45..4673af9cf6 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,17 +24,34 @@ import org.keycloak.services.clientpolicy.ClientPolicyException; 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}. * It is sufficient for the implementer of this condition to implement methods in which they are interested * and {@link isEvaluatedOnEvent} method. + * + * @author Takashi Norimatsu */ -public interface ClientPolicyConditionProvider extends Provider { +public interface ClientPolicyConditionProvider extends Provider { @Override 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 getConditionConfigurationClass() { + return (Class) ClientPolicyConditionConfiguration.class; + } + /** * 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}. @@ -60,7 +77,9 @@ public interface ClientPolicyConditionProvider extends Provider { return false; } - String getName(); + default String getName() { + return getClass().toString(); + } String getProviderId(); } diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProviderFactory.java index 07304fc5c7..e4d96feb1a 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionProviderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,11 @@ 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 { +/** + * @author Takashi Norimatsu + */ +public interface ClientPolicyConditionProviderFactory extends ProviderFactory, ConfiguredProvider { } diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionSpi.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionSpi.java index 895a5e8e20..311da0d9c2 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionSpi.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/condition/ClientPolicyConditionSpi.java @@ -21,6 +21,9 @@ import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.Spi; +/** + * @author Takashi Norimatsu + */ public class ClientPolicyConditionSpi implements Spi { @Override diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorConfiguration.java similarity index 65% rename from server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyProviderFactory.java rename to server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorConfiguration.java index 50fbf2be23..7c8f95a4c6 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorConfiguration.java @@ -13,11 +13,18 @@ * 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; +package org.keycloak.services.clientpolicy.executor; -import org.keycloak.component.ComponentFactory; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -public interface ClientPolicyProviderFactory extends ComponentFactory { +/** + * Just adds some type-safety to the ClientPolicyExecutorConfiguration + * + * @author Marek Posolda + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ClientPolicyExecutorConfiguration { } diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProvider.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProvider.java index 864ecf63cd..aee6ea344b 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProvider.java @@ -23,17 +23,34 @@ import org.keycloak.services.clientpolicy.ClientPolicyContext; 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}. * It is sufficient for the implementer of this executor to implement methods in which they are interested * and {@link isEvaluatedOnEvent} method. + * + * @author Takashi Norimatsu */ -public interface ClientPolicyExecutorProvider extends Provider { +public interface ClientPolicyExecutorProvider extends Provider { @Override 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 getExecutorConfigurationClass() { + return (Class) ClientPolicyExecutorConfiguration.class; + } + /** * 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 { } - String getName(); + default String getName() { + return getClass().toString(); + } String getProviderId(); } diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProviderFactory.java index 1fb72dc265..c94ccc11a6 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorProviderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,11 @@ 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 { +/** + * @author Takashi Norimatsu + */ +public interface ClientPolicyExecutorProviderFactory extends ProviderFactory, ConfiguredProvider { } diff --git a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorSpi.java b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorSpi.java index 775511a3de..e1e5e968fc 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorSpi.java +++ b/server-spi-private/src/main/java/org/keycloak/services/clientpolicy/executor/ClientPolicyExecutorSpi.java @@ -21,6 +21,9 @@ import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.Spi; +/** + * @author Takashi Norimatsu + */ public class ClientPolicyExecutorSpi implements Spi { @Override diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 1e972a7766..4063de8997 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -92,7 +92,6 @@ org.keycloak.crypto.CekManagementSpi org.keycloak.crypto.ContentEncryptionSpi org.keycloak.validation.ClientValidationSPI org.keycloak.headers.SecurityHeadersSpi -org.keycloak.services.clientpolicy.ClientPolicySpi org.keycloak.services.clientpolicy.condition.ClientPolicyConditionSpi org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorSpi org.keycloak.userprofile.UserProfileSpi diff --git a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyContext.java b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyContext.java index d8f4a3a0f9..96647981af 100644 --- a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyContext.java +++ b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyContext.java @@ -21,6 +21,8 @@ package org.keycloak.services.clientpolicy; * Provides Client Policy Context. * The implementation of this interface for handling an event defined in {@link ClientPolicyEvent} * needs to provide methods depending on this event. + * + * @author Takashi Norimatsu */ public interface ClientPolicyContext { diff --git a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyEvent.java b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyEvent.java index aa5a6db4bc..5f497a8436 100644 --- a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyEvent.java +++ b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyEvent.java @@ -17,6 +17,11 @@ package org.keycloak.services.clientpolicy; +/** + * Events on which client policies mechanism detects and do its operation + * + * @author Takashi Norimatsu + */ public enum ClientPolicyEvent { REGISTER, diff --git a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyException.java b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyException.java index ed5fe0d524..51460254a8 100644 --- a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyException.java +++ b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyException.java @@ -22,12 +22,20 @@ import javax.ws.rs.core.Response.Status; import org.keycloak.OAuthErrorException; +/** + * @author Takashi Norimatsu + */ public class ClientPolicyException extends Exception { private String error = OAuthErrorException.INVALID_REQUEST; - private String errorDetail; + private String errorDetail ="NA"; private Status errorStatus = Response.Status.BAD_REQUEST; + public ClientPolicyException(String error) { + super(error); + setError(error); + } + public ClientPolicyException(String error, String errorDetail) { super(error); setError(error); diff --git a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManager.java b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManager.java index af0e2e49c5..2ebb8b316e 100644 --- a/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManager.java +++ b/server-spi/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +17,14 @@ 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}. + * Also provides methods for handling client profiles and policies. + * + * @author Takashi Norimatsu */ public interface ClientPolicyManager { @@ -30,4 +36,111 @@ public interface ClientPolicyManager { */ void triggerOnEvent(ClientPolicyContext context) throws ClientPolicyException; + /** + * when booting keycloak, reads json representations of the builtin client profiles and policies from files + * enclosed in keycloak-services jar file and put them onto the keycloak application. + * if these operation fails, put null. + * + * @param profilesFilePath - the file path for the builtin client profiles + * @param policiesFilePath - the file path for the builtin client policies + */ + void setupClientPoliciesOnKeycloakApp(String profilesFilePath, String policiesFilePath); + + /** + * when creating a realm, reads the builtin client profiles and policies + * that have already been set on keycloak application on booting keycloak and put them onto the realm as its attribute. + * 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); + } diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index 2b0c03a292..820c49686d 100755 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -260,6 +260,9 @@ public class ExportUtils { MultivaluedHashMap components = exportComponents(realm, realm.getId()); rep.setComponents(components); + // client policies + session.clientPolicy().setupClientPoliciesOnExportingRealm(realm, rep); + return rep; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 3a374bef6b..0dd4296267 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -46,9 +46,7 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.services.ErrorPageException; import org.keycloak.services.ServicesLogger; import org.keycloak.services.Urls; -import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.DefaultClientPolicyManager; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.LoginActionsService; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenIntrospectionEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenIntrospectionEndpoint.java index f51f0636be..c613b7133e 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenIntrospectionEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenIntrospectionEndpoint.java @@ -18,7 +18,6 @@ package org.keycloak.protocol.oidc.endpoints; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.HttpRequest; -import org.keycloak.OAuthErrorException; import org.keycloak.common.ClientConnection; import org.keycloak.events.Errors; 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.TokenIntrospectionProvider; import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil; -import org.keycloak.services.CorsErrorResponseException; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.DefaultClientPolicyManager; import org.keycloak.services.clientpolicy.context.TokenIntrospectContext; import javax.ws.rs.POST; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java index 6c1d0086c5..6500d5f4bb 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java @@ -52,7 +52,6 @@ import org.keycloak.services.clientpolicy.context.TokenRevokeContext; import org.keycloak.services.managers.UserSessionCrossDCManager; import org.keycloak.services.managers.UserSessionManager; import org.keycloak.services.resources.Cors; -import org.keycloak.services.util.MtlsHoKTokenUtil; import org.keycloak.util.TokenUtil; /** diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyLogger.java b/services/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyLogger.java deleted file mode 100644 index 7c6c710e7f..0000000000 --- a/services/src/main/java/org/keycloak/services/clientpolicy/ClientPolicyLogger.java +++ /dev/null @@ -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(); - } -} diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManager.java b/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManager.java index 1ea5638a40..110fa21fb1 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManager.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,25 +17,29 @@ package org.keycloak.services.clientpolicy; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.keycloak.common.Profile; import org.keycloak.models.KeycloakSession; 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.executor.ClientPolicyExecutorProvider; +/** + * @author Takashi Norimatsu + */ public class DefaultClientPolicyManager implements ClientPolicyManager { private static final Logger logger = Logger.getLogger(DefaultClientPolicyManager.class); private final KeycloakSession session; - private final Map> providersMap = new HashMap<>(); public DefaultClientPolicyManager(KeycloakSession session) { this.session = session; @@ -43,60 +47,54 @@ public class DefaultClientPolicyManager implements ClientPolicyManager { @Override public void triggerOnEvent(ClientPolicyContext context) throws ClientPolicyException { - if (!Profile.isFeatureEnabled(Profile.Feature.CLIENT_POLICIES)) return; - ClientPolicyLogger.logv(logger, "Client Policy Operation : event = {0}", context.getEvent()); + if (!Profile.isFeatureEnabled(Profile.Feature.CLIENT_POLICIES)) { + return; + } + + RealmModel realm = session.getContext().getRealm(); + logger.tracev("POLICY OPERATION :: context realm = {0}, event = {1}", realm.getName(), context.getEvent()); + doPolicyOperation( (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 { - RealmModel realm = session.getContext().getRealm(); - for (ClientPolicyProvider policy : getProviders(realm)) { - ClientPolicyLogger.logv(logger, "Policy Operation : name = {0}, provider id = {1}", policy.getName(), policy.getProviderId()); - if (!isSatisfied(policy, condition)) continue; - execute(policy, executor); - } - } + private void doPolicyOperation(ClientConditionOperation condition, ClientExecutorOperation executor, RealmModel realm) throws ClientPolicyException { + Map map = ClientPoliciesUtil.getClientProfilesModel(session, realm); + List list = ClientPoliciesUtil.getEnabledClientProfilesModel(session, realm).stream().collect(Collectors.toList()); - private List getProviders(RealmModel realm) { - List providers = providersMap.get(realm.getId()); - if (providers == null) { - providers = realm.getComponentsStream(realm.getId(), ClientPolicyProvider.class.getName()) - .map(policyModel -> { - 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) - .collect(Collectors.toList()); - providersMap.put(realm.getId(), providers); - } else { - ClientPolicyLogger.log(logger, "Use cached policies."); + if (list == null || list.isEmpty()) { + logger.trace("POLICY OPERATION :: No enabled policy."); + return; + } + + for (ClientPolicyModel policy: list) { + logger.tracev("POLICY OPERATION :: policy name = {0}, isBuiltin = {1}", policy.getName(), policy.isBuiltin()); + if (!isSatisfied(policy, condition)) { + logger.tracev("POLICY UNSATISFIED :: policy name = {0}, isBuiltin = {1}", policy.getName(), policy.isBuiltin()); + continue; + } + + logger.tracev("POLICY APPLIED :: policy name = {0}, isBuiltin = {1}", policy.getName(), policy.isBuiltin()); + execute(policy, executor, map); } - return providers; } private boolean isSatisfied( - ClientPolicyProvider policy, + ClientPolicyModel policy, ClientConditionOperation op) throws ClientPolicyException { - List conditions = policy.getConditions(); - - if (conditions == null || conditions.isEmpty()) { - ClientPolicyLogger.log(logger, "NEGATIVE :: This policy is not applied. No condition exists."); + if (policy.getConditions() == null || policy.getConditions().isEmpty()) { + logger.tracev("NO CONDITION :: policy name = {0}", policy.getName()); return 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 { ClientPolicyVote vote = op.run(condition); if (condition.isNegativeLogic()) { @@ -107,47 +105,61 @@ public class DefaultClientPolicyManager implements ClientPolicyManager { } } 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; } 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; } ret = true; - } catch (ClientPolicyException cpe) { - ClientPolicyLogger.logv(logger, "CONDITION EXCEPTION : name = {0}, provider id = {1}, error = {2}, error_detail = {3}", condition.getName(), condition.getProviderId(), cpe.getError(), cpe.getErrorDetail()); - throw cpe; + } catch (ClientPolicyException e) { + logger.tracev("CONDITION EXCEPTION :: policy name = {0}, provider id = {1}, error = {2}, error detail = {3}", condition.getName(), condition.getProviderId(), e.getError(), e.getErrorDetail()); + throw e; } } if (ret == true) { - ClientPolicyLogger.log(logger, "POSITIVE :: This policy is applied."); + logger.tracev("CONDITIONS SATISFIED :: policy name = {0}", policy.getName()); } 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; - } private void execute( - ClientPolicyProvider policy, - ClientExecutorOperation op) throws ClientPolicyException { + ClientPolicyModel policy, + ClientExecutorOperation op, + Map map) throws ClientPolicyException { - List executors = policy.getExecutors(); - if (executors == null || executors.isEmpty()) { - ClientPolicyLogger.log(logger, "NEGATIVE :: This executor is not executed. No executor executable."); - return; + if (policy.getProfiles() == null || policy.getProfiles().isEmpty()) { + logger.tracev("NO PROFILE :: policy name = {0}", policy.getName()); } - for (ClientPolicyExecutorProvider executor : executors) { - try { - op.run(executor); - } catch(ClientPolicyException cpe) { - ClientPolicyLogger.logv(logger, "EXECUTOR EXCEPTION : name = {0}, provider id = {1}, error = {2}, error_detail = {3}", executor.getName(), executor.getProviderId(), cpe.getError(), cpe.getErrorDetail()); - throw cpe; + + 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 { + op.run(executor); + } catch(ClientPolicyException e) { + logger.tracev("EXECUTOR EXCEPTION :: executor name = {0}, provider id = {1}, error = {2}, error detail = {3}", executor.getName(), executor.getProviderId(), e.getError(), e.getErrorDetail()); + throw e; + } + } + + } } private interface ClientConditionOperation { @@ -158,4 +170,236 @@ public class DefaultClientPolicyManager implements ClientPolicyManager { void run(ClientPolicyExecutorProvider executor) throws ClientPolicyException; } + + // Client Polices Realm Attributes Keys + public static final String CLIENT_PROFILES = "client-policies.profiles"; + public static final String CLIENT_POLICIES = "client-policies.policies"; + + // builtin profiles and policies are loaded on booting keycloak at once. + // therefore, their representations are kept and remain unchanged. + // these are shared among all realms. + + // those can be null to show that no profile/policy exist + private static String builtinClientProfilesJson; + private static String builtinClientPoliciesJson; + + @Override + public void setupClientPoliciesOnKeycloakApp(String profilesFilePath, String policiesFilePath) { + logger.trace("LOAD BUILTIN PROFILE POLICIES ON KEYCLOAK"); + + // client profile can be referred from client policy so that client profile needs to be loaded at first. + // load builtin profiles on keycloak app + ClientProfilesRepresentation validatedProfilesRep = null; + try { + validatedProfilesRep = ClientPoliciesUtil.getValidatedBuiltinClientProfilesRepresentation(session, getClass().getResourceAsStream(profilesFilePath)); + } catch (ClientPolicyException cpe) { + logger.warnv("LOAD BUILTIN PROFILES ON KEYCLOAK FAILED :: error = {0}, error detail = {1}", cpe.getError(), cpe.getErrorDetail()); + return; + } + + String validatedJson = null; + try { + validatedJson = ClientPoliciesUtil.convertClientProfilesRepresentationToJson(validatedProfilesRep); + } catch (ClientPolicyException cpe) { + logger.warnv("VALIDATE SERIALIZE BUILTIN PROFILES ON KEYCLOAK FAILED :: error = {0}, error detail = {1}", cpe.getError(), cpe.getErrorDetail()); + return; + } + + builtinClientProfilesJson = validatedJson; + + // load builtin policies on keycloak app + ClientPoliciesRepresentation validatedPoliciesRep = null; + try { + validatedPoliciesRep = ClientPoliciesUtil.getValidatedBuiltinClientPoliciesRepresentation(session, getClass().getResourceAsStream(policiesFilePath)); + } catch (ClientPolicyException cpe) { + logger.warnv("LOAD BUILTIN POLICIES ON KEYCLOAK FAILED :: error = {0}, error detail = {1}", cpe.getError(), cpe.getErrorDetail()); + builtinClientProfilesJson = null; + return; + } + + validatedJson = null; + try { + validatedJson = ClientPoliciesUtil.convertClientPoliciesRepresentationToJson(validatedPoliciesRep); + } catch (ClientPolicyException cpe) { + logger.warnv("VALIDATE SERIALIZE BUILTIN POLICIES ON KEYCLOAK FAILED :: error = {0}, error detail = {1}", cpe.getError(), cpe.getErrorDetail()); + builtinClientProfilesJson = null; + return; + } + + builtinClientPoliciesJson = validatedJson; + } + + @Override + public void setupClientPoliciesOnCreatedRealm(RealmModel realm) { + logger.tracev("LOAD BUILTIN PROFILE POLICIES ON CREATED REALM :: realm = {0}", realm.getName()); + + // put already loaded builtin profiles/policies on keycloak app to newly created realm + setClientProfilesJsonString(realm, builtinClientProfilesJson); + setClientPoliciesJsonString(realm, builtinClientPoliciesJson); + } + + @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 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; + } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProvider.java b/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProvider.java deleted file mode 100644 index c6871ffd10..0000000000 --- a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProvider.java +++ /dev/null @@ -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> conditionsMap = new HashMap<>(); - private final Map> executorsMap = new HashMap<>(); - - public DefaultClientPolicyProvider(KeycloakSession session, ComponentModel componentModel) { - this.session = session; - this.componentModel = componentModel; - } - - @Override - public void close() { - } - - @Override - public List getConditions() { - return getConditions(session.getContext().getRealm()); - } - - @Override - public List getExecutors() { - return getExecutors(session.getContext().getRealm()); - } - - @Override - public String getName() { - return componentModel.getName(); - } - - @Override - public String getProviderId() { - return componentModel.getProviderId(); - } - - private List getConditionIds() { - return componentModel.getConfig().getList(DefaultClientPolicyProviderFactory.CONDITION_IDS); - } - - private List getExecutorIds() { - return componentModel.getConfig().getList(DefaultClientPolicyProviderFactory.EXECUTOR_IDS); - } - - private List getConditions(RealmModel realm) { - List providers = conditionsMap.get(realm.getId()); - if (providers == null) { - providers = new LinkedList<>(); - List 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 getExecutors(RealmModel realm) { - List providers = executorsMap.get(realm.getId()); - if (providers == null) { - providers = new LinkedList<>(); - List 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; - } - -} diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProviderFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProviderFactory.java deleted file mode 100644 index 38dfe8bb9b..0000000000 --- a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProviderFactory.java +++ /dev/null @@ -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 getConfigProperties() { - return Arrays.asList(CONDITION_IDS_PROPERTY, EXECUTOR_IDS_PROPERTY); - } - -} diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProvider.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProvider.java deleted file mode 100644 index 38578cbbcd..0000000000 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProvider.java +++ /dev/null @@ -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(); - } -} diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProviderFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProviderFactory.java deleted file mode 100644 index 0dca8a9912..0000000000 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProviderFactory.java +++ /dev/null @@ -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 getConfigProperties() { - return new ArrayList<>(Arrays.asList(IS_NEGATIVE_LOGIC_PROPERTY)); - } - -} diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientCondition.java index 86eb0ba576..eaf1244567 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,19 +17,59 @@ package org.keycloak.services.clientpolicy.condition; -import org.jboss.logging.Logger; -import org.keycloak.component.ComponentModel; +import java.util.Optional; + import org.keycloak.models.KeycloakSession; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; 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 Takashi Norimatsu + */ +public class AnyClientCondition implements ClientPolicyConditionProvider { - public AnyClientCondition(KeycloakSession session, ComponentModel componentModel) { - super(session, componentModel); + // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. + private Configuration configuration = new Configuration(); + + public AnyClientCondition(KeycloakSession session) { + } + + @Override + public void setupConfiguration(Configuration config) { + this.configuration = config; + } + + @Override + public Class getConditionConfigurationClass() { + return Configuration.class; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration extends ClientPolicyConditionConfiguration { + @JsonProperty("is-negative-logic") + protected Boolean negativeLogic; + + public Boolean isNegativeLogic() { + return negativeLogic; + } + + public void setNegativeLogic(Boolean negativeLogic) { + this.negativeLogic = negativeLogic; + } + } + + @Override + public boolean isNegativeLogic() { + return Optional.ofNullable(this.configuration.isNegativeLogic()).orElse(Boolean.FALSE).booleanValue(); + } + + @Override + public String getProviderId() { + return AnyClientConditionFactory.PROVIDER_ID; } @Override diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientConditionFactory.java index 7ca11b7c33..c7d5b819a2 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientConditionFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientConditionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,16 +17,36 @@ package org.keycloak.services.clientpolicy.condition; -import org.keycloak.component.ComponentModel; -import org.keycloak.models.KeycloakSession; +import java.util.Collections; +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 Takashi Norimatsu + */ +public class AnyClientConditionFactory implements ClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "anyclient-condition"; @Override - public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { - return new AnyClientCondition(session, model); + public ClientPolicyConditionProvider create(KeycloakSession session) { + return new AnyClientCondition(session); + } + + @Override + public void init(Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { } @Override @@ -38,4 +58,10 @@ public class AnyClientConditionFactory extends AbstractClientPolicyConditionProv public String getHelpText() { return "The condition is satisfied by any client on any event."; } + + @Override + public List getConfigProperties() { + return Collections.emptyList(); + } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java index 35211dc315..4e62c43876 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,22 +19,75 @@ package org.keycloak.services.clientpolicy.condition; import java.util.Collections; import java.util.List; +import java.util.Optional; import org.jboss.logging.Logger; -import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; -public class ClientAccessTypeCondition extends AbstractClientPolicyConditionProvider { +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Takashi Norimatsu + */ +public class ClientAccessTypeCondition implements ClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientAccessTypeCondition.class); - public ClientAccessTypeCondition(KeycloakSession session, ComponentModel componentModel) { - super(session, componentModel); + // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. + private Configuration configuration = new Configuration(); + private final KeycloakSession session; + + public ClientAccessTypeCondition(KeycloakSession session) { + this.session = session; + } + + @Override + public void setupConfiguration(Configuration config) { + this.configuration = config; + } + + @Override + public Class getConditionConfigurationClass() { + return Configuration.class; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration extends ClientPolicyConditionConfiguration { + @JsonProperty("is-negative-logic") + protected Boolean negativeLogic; + + public Boolean isNegativeLogic() { + return negativeLogic; + } + + public void setNegativeLogic(Boolean negativeLogic) { + this.negativeLogic = negativeLogic; + } + + protected List type; + + public List getType() { + return type; + } + + public void setType(List 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 @@ -65,22 +118,16 @@ public class ClientAccessTypeCondition extends AbstractClientPolicyConditionProv private boolean isClientAccessTypeMatched() { final String accessType = getClientAccessType(); + if (accessType == null) return false; - List expectedAccessTypes = componentModel.getConfig().get(ClientAccessTypeConditionFactory.TYPE); - if (expectedAccessTypes == null) expectedAccessTypes = Collections.emptyList(); + List expectedAccessTypes = Optional.ofNullable(configuration.getType()).orElse(Collections.emptyList()); if (logger.isTraceEnabled()) { - ClientPolicyLogger.log(logger, "client access type = " + accessType); - expectedAccessTypes.stream().forEach(i -> ClientPolicyLogger.log(logger, "client access type expected = " + i)); + logger.tracev("accessType = {0}", accessType); + expectedAccessTypes.stream().forEach(i -> logger.tracev("expected accessType = {0}", i)); } - boolean isMatched = 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; + return expectedAccessTypes.stream().anyMatch(i -> i.equals(accessType)); } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeConditionFactory.java index 25d66249fd..31861064a8 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeConditionFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeConditionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,30 +17,53 @@ package org.keycloak.services.clientpolicy.condition; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.keycloak.component.ComponentModel; +import org.keycloak.Config.Scope; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.provider.ProviderConfigProperty; -public class ClientAccessTypeConditionFactory extends AbstractClientPolicyConditionProviderFactory { +/** + * @author Takashi Norimatsu + */ +public class ClientAccessTypeConditionFactory implements ClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "client-accesstype-condition"; + public static final String TYPE = "type"; + public static final String TYPE_CONFIDENTIAL = "confidential"; public static final String TYPE_PUBLIC = "public"; public static final String TYPE_BEARERONLY = "bearer-only"; - private static final ProviderConfigProperty CLIENTACCESSTYPE_PROPERTY; + private static final List configProperties = new ArrayList(); + static { - CLIENTACCESSTYPE_PROPERTY = new ProviderConfigProperty(TYPE, "client-accesstype.label", "client-accesstype.tooltip", ProviderConfigProperty.MULTIVALUED_LIST_TYPE, TYPE_CONFIDENTIAL); - CLIENTACCESSTYPE_PROPERTY.setOptions(Arrays.asList(TYPE_CONFIDENTIAL, TYPE_PUBLIC, TYPE_BEARERONLY)); + ProviderConfigProperty property; + property = new ProviderConfigProperty(TYPE, "client-accesstype.label", "client-accesstype.tooltip", ProviderConfigProperty.MULTIVALUED_LIST_TYPE, TYPE_CONFIDENTIAL); + List updateProfileValues = Arrays.asList(TYPE_CONFIDENTIAL, TYPE_PUBLIC, TYPE_BEARERONLY); + property.setOptions(updateProfileValues); + configProperties.add(property); } @Override - public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { - return new ClientAccessTypeCondition(session, model); + public ClientPolicyConditionProvider create(KeycloakSession session) { + return new ClientAccessTypeCondition(session); + } + + @Override + public void init(Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { } @Override @@ -55,8 +78,7 @@ public class ClientAccessTypeConditionFactory extends AbstractClientPolicyCondit @Override public List getConfigProperties() { - List l = super.getConfigProperties(); - l.add(CLIENTACCESSTYPE_PROPERTY); - return l; + return configProperties; } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesCondition.java index b5fa9817cb..3407fff081 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,25 +19,78 @@ package org.keycloak.services.clientpolicy.condition; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.jboss.logging.Logger; -import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RoleModel; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; -public class ClientRolesCondition extends AbstractClientPolicyConditionProvider { +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Takashi Norimatsu + */ +public class ClientRolesCondition implements ClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientRolesCondition.class); - public ClientRolesCondition(KeycloakSession session, ComponentModel componentModel) { - super(session, componentModel); + // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. + private Configuration configuration = new Configuration(); + private final KeycloakSession session; + + public ClientRolesCondition(KeycloakSession session) { + this.session = session; + } + + @Override + public void setupConfiguration(Configuration config) { + this.configuration = config; + } + + @Override + public Class getConditionConfigurationClass() { + return Configuration.class; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration extends ClientPolicyConditionConfiguration { + @JsonProperty("is-negative-logic") + protected Boolean negativeLogic; + + public Boolean isNegativeLogic() { + return negativeLogic; + } + + public void setNegativeLogic(Boolean negativeLogic) { + this.negativeLogic = negativeLogic; + } + + protected List roles; + + public List getRoles() { + return roles; + } + + public void setRoles(List 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 @@ -67,25 +120,16 @@ public class ClientRolesCondition extends AbstractClientPolicyConditionProvider Set clientRoles = client.getRolesStream().map(RoleModel::getName).collect(Collectors.toSet()); if (logger.isTraceEnabled()) { - clientRoles.stream().forEach(i -> ClientPolicyLogger.log(logger, "client role assigned = " + i)); - rolesForMatching.stream().forEach(i -> ClientPolicyLogger.log(logger, "client role for matching = " + i)); + clientRoles.forEach(i -> logger.tracev("client role assigned = {0}", i)); + rolesForMatching.forEach(i -> logger.tracev("client role for matching = {0}", i)); } - boolean isMatched = rolesForMatching.removeAll(clientRoles); - if (isMatched) { - ClientPolicyLogger.log(logger, "role matched."); - } else { - ClientPolicyLogger.log(logger, "role unmatched."); - } - - return isMatched; + return rolesForMatching.removeAll(clientRoles); // may change rolesForMatching so that it has needed to be instantiated. } private Set getRolesForMatching() { - if (componentModel.getConfig() == null) return null; - List roles = componentModel.getConfig().get(ClientRolesConditionFactory.ROLES); - if (roles == null) return null; - return new HashSet<>(roles); + if (configuration.getRoles() == null) return null; + return new HashSet<>(configuration.getRoles()); } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesConditionFactory.java index 8edc837964..a6d74f3808 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesConditionFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesConditionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,23 +17,46 @@ package org.keycloak.services.clientpolicy.condition; +import java.util.ArrayList; import java.util.List; -import org.keycloak.component.ComponentModel; +import org.keycloak.Config.Scope; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.provider.ProviderConfigProperty; -public class ClientRolesConditionFactory extends AbstractClientPolicyConditionProviderFactory { +/** + * @author Takashi Norimatsu + */ +public class ClientRolesConditionFactory implements ClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "clientroles-condition"; + public static final String ROLES = "roles"; - private static final ProviderConfigProperty CLIENTROLES_PROPERTY = new ProviderConfigProperty( - ROLES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, null); + private static final List configProperties = new ArrayList(); + + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(ROLES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, null); + configProperties.add(property); + } @Override - public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { - return new ClientRolesCondition(session, model); + public ClientPolicyConditionProvider create(KeycloakSession session) { + return new ClientRolesCondition(session); + } + + @Override + public void init(Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { } @Override @@ -48,8 +71,7 @@ public class ClientRolesConditionFactory extends AbstractClientPolicyConditionPr @Override public List getConfigProperties() { - List l = super.getConfigProperties(); - l.add(CLIENTROLES_PROPERTY); - return l; + return configProperties; } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientScopesCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientScopesCondition.java index db88a6a082..fa34aa3be0 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientScopesCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientScopesCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,28 +21,90 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import org.jboss.logging.Logger; import org.keycloak.OAuth2Constants; -import org.keycloak.component.ComponentModel; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; 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 Takashi Norimatsu + */ +public class ClientScopesCondition implements ClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientScopesCondition.class); - public ClientScopesCondition(KeycloakSession session, ComponentModel componentModel) { - super(session, componentModel); + // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. + private Configuration configuration = new Configuration(); + private final KeycloakSession session; + + public ClientScopesCondition(KeycloakSession session) { + this.session = session; + } + + @Override + public void setupConfiguration(Configuration config) { + this.configuration = config; + } + + @Override + public Class getConditionConfigurationClass() { + return Configuration.class; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration extends ClientPolicyConditionConfiguration { + @JsonProperty("is-negative-logic") + protected Boolean negativeLogic; + + public Boolean isNegativeLogic() { + return negativeLogic; + } + + public void setNegativeLogic(Boolean negativeLogic) { + this.negativeLogic = negativeLogic; + } + + protected String type; + protected List scope; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List getScope() { + return scope; + } + + public void setScope(List 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 @@ -75,25 +137,27 @@ public class ClientScopesCondition extends AbstractClientPolicyConditionProvider Set defaultScopes = client.getClientScopes(true).keySet(); Set optionalScopes = client.getClientScopes(false).keySet(); Set expectedScopes = getScopesForMatching(); - if (expectedScopes == null) expectedScopes = new HashSet<>(); + if (expectedScopes == null) return false; if (logger.isTraceEnabled()) { - explicitSpecifiedScopes.stream().forEach(i -> ClientPolicyLogger.log(logger, " explicit specified client scope = " + i)); - defaultScopes.stream().forEach(i -> ClientPolicyLogger.log(logger, " default client scope = " + i)); - optionalScopes.stream().forEach(i -> ClientPolicyLogger.log(logger, " optional client scope = " + i)); - expectedScopes.stream().forEach(i -> ClientPolicyLogger.log(logger, " expected scope = " + i)); + explicitSpecifiedScopes.forEach(i -> logger.tracev("explicit specified client scope = {0}", i)); + defaultScopes.forEach(i -> logger.tracev("default client scope = {0}", i)); + optionalScopes.forEach(i -> logger.tracev("optional client scope = {0}", 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) { - expectedScopes.retainAll(defaultScopes); + expectedScopes.retainAll(defaultScopes); // may change expectedScopes so that it has needed to be instantiated. return expectedScopes.isEmpty() ? false : true; } else { explicitSpecifiedScopes.retainAll(expectedScopes); explicitSpecifiedScopes.retainAll(optionalScopes); 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; } } @@ -101,9 +165,9 @@ public class ClientScopesCondition extends AbstractClientPolicyConditionProvider } private Set getScopesForMatching() { - if (componentModel.getConfig() == null) return null; - List scopes = componentModel.getConfig().get(ClientScopesConditionFactory.SCOPES); + List scopes = configuration.getScope(); if (scopes == null) return null; return new HashSet<>(scopes); } + } \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientScopesConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientScopesConditionFactory.java index c6da86a43c..f4eb669214 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientScopesConditionFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientScopesConditionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,28 +17,51 @@ package org.keycloak.services.clientpolicy.condition; +import java.util.ArrayList; import java.util.List; -import org.keycloak.component.ComponentModel; +import org.keycloak.Config.Scope; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.provider.ProviderConfigProperty; -public class ClientScopesConditionFactory extends AbstractClientPolicyConditionProviderFactory { +/** + * @author Takashi Norimatsu + */ +public class ClientScopesConditionFactory implements ClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "clientscopes-condition"; + public static final String SCOPES = "scopes"; public static final String TYPE = "type"; public static final String DEFAULT = "Default"; public static final String OPTIONAL = "Optional"; - private static final ProviderConfigProperty CLIENTSCOPES_PROPERTY = new ProviderConfigProperty( - SCOPES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "offline_access"); - private static final ProviderConfigProperty CLIENTSCOPETYPE_PROPERTY = new ProviderConfigProperty( - TYPE, "Scope Type", "Default or Optional", ProviderConfigProperty.LIST_TYPE, OPTIONAL); + private static final List configProperties = new ArrayList(); + + static { + 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 - public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { - return new ClientScopesCondition(session, model); + public ClientPolicyConditionProvider create(KeycloakSession session) { + return new ClientScopesCondition(session); + } + + @Override + public void init(Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { } @Override @@ -53,9 +76,7 @@ public class ClientScopesConditionFactory extends AbstractClientPolicyConditionP @Override public List getConfigProperties() { - List l = super.getConfigProperties(); - l.add(CLIENTSCOPES_PROPERTY); - l.add(CLIENTSCOPETYPE_PROPERTY); - return l; + return configProperties; } + } \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextCondition.java index 69a90da4b6..e6bd4e28a3 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,25 +19,79 @@ package org.keycloak.services.clientpolicy.condition; import java.util.Collections; import java.util.List; +import java.util.Optional; import org.jboss.logging.Logger; -import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.representations.JsonWebToken; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.context.ClientCRUDContext; import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils; import org.keycloak.util.TokenUtil; -public class ClientUpdateContextCondition extends AbstractClientPolicyConditionProvider { +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Takashi Norimatsu + */ +public class ClientUpdateContextCondition implements ClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateContextCondition.class); - public ClientUpdateContextCondition(KeycloakSession session, ComponentModel componentModel) { - super(session, componentModel); + // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. + private Configuration configuration = new Configuration(); + private final KeycloakSession session; + + public ClientUpdateContextCondition(KeycloakSession session) { + this.session = session; + } + + @Override + public void setupConfiguration(Configuration config) { + this.configuration = config; + } + + @Override + public Class getConditionConfigurationClass() { + return Configuration.class; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration extends ClientPolicyConditionConfiguration { + @JsonProperty("is-negative-logic") + protected Boolean negativeLogic; + + public Boolean isNegativeLogic() { + return negativeLogic; + } + + public void setNegativeLogic(Boolean negativeLogic) { + this.negativeLogic = negativeLogic; + } + + @JsonProperty("update-client-source") + protected List updateClientSource; + + public List getUpdateClientSource() { + return updateClientSource; + } + + public void setUpdateClientSource(List 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 @@ -55,21 +109,15 @@ public class ClientUpdateContextCondition extends AbstractClientPolicyConditionP private boolean isAuthMethodMatched(String authMethod) { if (authMethod == null) return false; - List expectedAuthMethods = componentModel.getConfig().get(ClientUpdateContextConditionFactory.UPDATE_CLIENT_SOURCE); + List expectedAuthMethods = configuration.getUpdateClientSource(); if (expectedAuthMethods == null) expectedAuthMethods = Collections.emptyList(); if (logger.isTraceEnabled()) { - ClientPolicyLogger.log(logger, "auth method = " + authMethod); - expectedAuthMethods.stream().forEach(i -> ClientPolicyLogger.log(logger, "auth method expected = " + i)); + logger.tracev("auth method = {0}", authMethod); + expectedAuthMethods.stream().forEach(i -> logger.tracev("auth method expected = {0}", i)); } - boolean isMatched = expectedAuthMethods.stream().anyMatch(i -> i.equals(authMethod)); - if (isMatched) { - ClientPolicyLogger.log(logger, "auth method matched."); - } else { - ClientPolicyLogger.log(logger, "auth method unmatched."); - } - return isMatched; + return expectedAuthMethods.stream().anyMatch(i -> i.equals(authMethod)); } private boolean isAuthMethodMatched(ClientCRUDContext context) { @@ -103,4 +151,5 @@ public class ClientUpdateContextCondition extends AbstractClientPolicyConditionP private boolean isBearerToken(JsonWebToken jwt) { return jwt != null && TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType()); } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextConditionFactory.java index 1068666b5d..676d0ed9b6 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextConditionFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextConditionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,15 +17,19 @@ package org.keycloak.services.clientpolicy.condition; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.keycloak.component.ComponentModel; +import org.keycloak.Config.Scope; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; -public class ClientUpdateContextConditionFactory extends AbstractClientPolicyConditionProviderFactory { +/** + * @author Takashi Norimatsu + */ +public class ClientUpdateContextConditionFactory implements ClientPolicyConditionProviderFactory { 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_REGISTRATION_ACCESS_TOKEN = "ByRegistrationAccessToken"; - private static final ProviderConfigProperty CLIENTUPDATESOURCE_PROPERTY; + private static final List configProperties = new ArrayList(); + static { - CLIENTUPDATESOURCE_PROPERTY = new ProviderConfigProperty( - UPDATE_CLIENT_SOURCE, null, null, ProviderConfigProperty.MULTIVALUED_LIST_TYPE, BY_AUTHENTICATED_USER); - CLIENTUPDATESOURCE_PROPERTY.setOptions( - Arrays.asList(BY_AUTHENTICATED_USER, BY_ANONYMOUS, BY_INITIAL_ACCESS_TOKEN, BY_REGISTRATION_ACCESS_TOKEN)); + ProviderConfigProperty property; + property = new ProviderConfigProperty(UPDATE_CLIENT_SOURCE, null, null, ProviderConfigProperty.MULTIVALUED_LIST_TYPE, BY_AUTHENTICATED_USER); + List updateProfileValues = Arrays.asList(BY_AUTHENTICATED_USER, BY_ANONYMOUS, BY_INITIAL_ACCESS_TOKEN, BY_REGISTRATION_ACCESS_TOKEN); + property.setOptions(updateProfileValues); + configProperties.add(property); } @Override - public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { - return new ClientUpdateContextCondition(session, model); + public ClientPolicyConditionProvider create(KeycloakSession session) { + return new ClientUpdateContextCondition(session); + } + + @Override + public void init(Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { } @Override @@ -61,8 +79,7 @@ public class ClientUpdateContextConditionFactory extends AbstractClientPolicyCon @Override public List getConfigProperties() { - List l = super.getConfigProperties(); - l.add(CLIENTUPDATESOURCE_PROPERTY); - return l; + return configProperties; } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsCondition.java index 62c5adf955..90ea902d57 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,19 +19,18 @@ package org.keycloak.services.clientpolicy.condition; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.keycloak.OAuthErrorException; -import org.keycloak.component.ComponentModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; import org.keycloak.representations.JsonWebToken; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext; 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.DynamicClientUpdateContext; -public class ClientUpdateSourceGroupsCondition extends AbstractClientPolicyConditionProvider { +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Takashi Norimatsu + */ +public class ClientUpdateSourceGroupsCondition implements ClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateSourceGroupsCondition.class); - public ClientUpdateSourceGroupsCondition(KeycloakSession session, ComponentModel componentModel) { - super(session, componentModel); + // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. + private Configuration configuration = new Configuration(); + private final KeycloakSession session; + + public ClientUpdateSourceGroupsCondition(KeycloakSession session) { + this.session = session; + } + + @Override + public void setupConfiguration(Configuration config) { + this.configuration = config; + } + + @Override + public Class getConditionConfigurationClass() { + return Configuration.class; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration extends ClientPolicyConditionConfiguration { + @JsonProperty("is-negative-logic") + protected Boolean negativeLogic; + + public Boolean isNegativeLogic() { + return negativeLogic; + } + + public void setNegativeLogic(Boolean negativeLogic) { + this.negativeLogic = negativeLogic; + } + + protected List groups; + + public List getGroups() { + return groups; + } + + public void setGroups(List 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 @@ -97,23 +150,17 @@ public class ClientUpdateSourceGroupsCondition extends AbstractClientPolicyCondi Set groups = user.getGroupsStream().map(GroupModel::getName).collect(Collectors.toSet()); if (logger.isTraceEnabled()) { - groups.stream().forEach(i -> ClientPolicyLogger.log(logger, " user group = " + i)); - expectedGroups.stream().forEach(i -> ClientPolicyLogger.log(logger, "groups expected = " + i)); + groups.forEach(i -> logger.tracev("user group = {0}", i)); + expectedGroups.forEach(i -> logger.tracev("expected user group = {0}", i)); } - boolean isMatched = expectedGroups.removeAll(groups); - if (isMatched) { - ClientPolicyLogger.log(logger, "group matched."); - } else { - ClientPolicyLogger.log(logger, "group unmatched."); - } - return isMatched; + return expectedGroups.removeAll(groups); // may change expectedGroups so that it has needed to be instantiated. } private Set instantiateGroupsForMatching() { - if (componentModel.getConfig() == null) return null; - List roles = componentModel.getConfig().get(ClientUpdateSourceGroupsConditionFactory.GROUPS); - if (roles == null) return null; - return new HashSet<>(roles); + List groups = configuration.getGroups(); + if (groups == null) return null; + return new HashSet<>(groups); } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsConditionFactory.java index 6bd7c1d225..7ed3875020 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsConditionFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceGroupsConditionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,24 +17,46 @@ package org.keycloak.services.clientpolicy.condition; +import java.util.ArrayList; import java.util.List; -import org.keycloak.component.ComponentModel; +import org.keycloak.Config.Scope; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.provider.ProviderConfigProperty; -public class ClientUpdateSourceGroupsConditionFactory extends AbstractClientPolicyConditionProviderFactory { +/** + * @author Takashi Norimatsu + */ +public class ClientUpdateSourceGroupsConditionFactory implements ClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "clientupdatesourcegroups-condition"; public static final String GROUPS = "groups"; - private static final ProviderConfigProperty CLIENTUPDATEGROUP_PROPERTY = new ProviderConfigProperty( - GROUPS, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "topGroup"); + private static final List configProperties = new ArrayList(); + + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(GROUPS, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "topGroup"); + configProperties.add(property); + } @Override - public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { - return new ClientUpdateSourceGroupsCondition(session, model); + public ClientPolicyConditionProvider create(KeycloakSession session) { + return new ClientUpdateSourceGroupsCondition(session); + } + + @Override + public void init(Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { } @Override @@ -49,8 +71,7 @@ public class ClientUpdateSourceGroupsConditionFactory extends AbstractClientPoli @Override public List getConfigProperties() { - List l = super.getConfigProperties(); - l.add(CLIENTUPDATEGROUP_PROPERTY); - return l; + return configProperties; } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsCondition.java index bd7e07fbae..5903d46dbb 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,22 +21,87 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.jboss.logging.Logger; -import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; -public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyConditionProvider { +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Takashi Norimatsu + */ +public class ClientUpdateSourceHostsCondition implements ClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateSourceHostsCondition.class); - public ClientUpdateSourceHostsCondition(KeycloakSession session, ComponentModel componentModel) { - super(session, componentModel); + // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. + private Configuration configuration = new Configuration(); + private final KeycloakSession session; + + public ClientUpdateSourceHostsCondition(KeycloakSession session) { + this.session = session; + } + + @Override + public void setupConfiguration(Configuration config) { + this.configuration = config; + } + + @Override + public Class getConditionConfigurationClass() { + return Configuration.class; + } + + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration extends ClientPolicyConditionConfiguration { + @JsonProperty("is-negative-logic") + protected Boolean negativeLogic; + + public Boolean isNegativeLogic() { + return negativeLogic; + } + + public void setNegativeLogic(Boolean negativeLogic) { + this.negativeLogic = negativeLogic; + } + + @JsonProperty("trusted-hosts") + protected List trustedHosts; + @JsonProperty("host-sending-request-must-match") + protected List hostSendingRequestMustMatch; + + public List getTrustedHosts() { + return trustedHosts; + } + + public void setTrustedHosts(List trustedHosts) { + this.trustedHosts = trustedHosts; + } + + public List getHostSendingRequestMustMatch() { + return hostSendingRequestMustMatch; + } + + public void setHostSendingRequestMustMatch(List 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 @@ -55,7 +120,7 @@ public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyCondit private boolean isHostMatched() { String hostAddress = session.getContext().getConnection().getRemoteAddr(); - ClientPolicyLogger.logv(logger, "Verifying remote host {0}", hostAddress); + logger.tracev("Verifying remote host = {0}", hostAddress); List trustedHosts = getTrustedHosts(); List trustedDomains = getTrustedDomains(); @@ -76,16 +141,14 @@ public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyCondit } protected List getTrustedHosts() { - List trustedHostsConfig = componentModel.getConfig().getList(ClientUpdateSourceHostsConditionFactory.TRUSTED_HOSTS); + List trustedHostsConfig = configuration.getTrustedHosts(); return trustedHostsConfig.stream().filter((String hostname) -> { - return !hostname.startsWith("*."); - }).collect(Collectors.toList()); } protected List getTrustedDomains() { - List trustedHostsConfig = componentModel.getConfig().getList(ClientUpdateSourceHostsConditionFactory.TRUSTED_HOSTS); + List trustedHostsConfig = configuration.getTrustedHosts(); List domains = new LinkedList<>(); for (String hostname : trustedHostsConfig) { @@ -102,14 +165,13 @@ public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyCondit for (String confHostName : trustedHosts) { try { String hostIPAddress = InetAddress.getByName(confHostName).getHostAddress(); - - ClientPolicyLogger.logv(logger, "Trying host {0} of address {1}", confHostName, hostIPAddress); + logger.tracev("Trying host {0} of address {1}", confHostName, hostIPAddress); if (hostIPAddress.equals(hostAddress)) { - ClientPolicyLogger.logv(logger, "Successfully verified host : {0}", confHostName); + logger.tracev("Successfully verified host = {0}", confHostName); return confHostName; } } 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()) { try { String hostname = InetAddress.getByName(hostAddress).getHostName(); - - ClientPolicyLogger.logv(logger, "Trying verify request from address {0} of host {1} by domains", hostAddress, hostname); - + logger.tracev("Trying verify request from address {0} of host {1} by domains", hostAddress, hostname); for (String confDomain : trustedDomains) { 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; } } } 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() { - return parseBoolean(ClientUpdateSourceHostsConditionFactory.HOST_SENDING_REQUEST_MUST_MATCH); + List 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); - } - } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsConditionFactory.java index e1eef1ecbb..c696ccd225 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsConditionFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceHostsConditionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,16 +17,18 @@ package org.keycloak.services.clientpolicy.condition; +import java.util.Arrays; import java.util.List; -import org.keycloak.component.ComponentModel; -import org.keycloak.component.ComponentValidationException; +import org.keycloak.Config.Scope; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.provider.ConfigurationValidationHelper; +import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.provider.ProviderConfigProperty; -public class ClientUpdateSourceHostsConditionFactory extends AbstractClientPolicyConditionProviderFactory { +/** + * @author Takashi Norimatsu + */ +public class ClientUpdateSourceHostsConditionFactory implements ClientPolicyConditionProviderFactory { 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"); @Override - public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { - return new ClientUpdateSourceHostsCondition(session, model); + public ClientPolicyConditionProvider create(KeycloakSession session) { + return new ClientUpdateSourceHostsCondition(session); + } + + @Override + public void init(Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { } @Override @@ -56,21 +70,7 @@ public class ClientUpdateSourceHostsConditionFactory extends AbstractClientPolic @Override public List getConfigProperties() { - List l = super.getConfigProperties(); - l.add(TRUSTED_HOSTS_PROPERTY); - l.add(HOST_SENDING_REGISTRATION_REQUEST_MUST_MATCH_PROPERTY); - return l; + return Arrays.asList(TRUSTED_HOSTS_PROPERTY, HOST_SENDING_REGISTRATION_REQUEST_MUST_MATCH_PROPERTY); } - @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"); - } - - } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesCondition.java index c5708a2abb..9f4a811125 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,12 +19,12 @@ package org.keycloak.services.clientpolicy.condition; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.keycloak.OAuthErrorException; -import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; @@ -32,7 +32,6 @@ import org.keycloak.models.UserModel; import org.keycloak.representations.JsonWebToken; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext; 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.DynamicClientUpdateContext; -public class ClientUpdateSourceRolesCondition extends AbstractClientPolicyConditionProvider { +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Takashi Norimatsu + */ +public class ClientUpdateSourceRolesCondition implements ClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateSourceRolesCondition.class); - public ClientUpdateSourceRolesCondition(KeycloakSession session, ComponentModel componentModel) { - super(session, componentModel); + // to avoid null configuration, use vacant new instance to indicate that there is no configuration set up. + private Configuration configuration = new Configuration(); + private final KeycloakSession session; + + public ClientUpdateSourceRolesCondition(KeycloakSession session) { + this.session = session; + } + + @Override + public void setupConfiguration(Configuration config) { + this.configuration = config; + } + + @Override + public Class getConditionConfigurationClass() { + return Configuration.class; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration extends ClientPolicyConditionConfiguration { + @JsonProperty("is-negative-logic") + protected Boolean negativeLogic; + + public Boolean isNegativeLogic() { + return negativeLogic; + } + + public void setNegativeLogic(Boolean negativeLogic) { + this.negativeLogic = negativeLogic; + } + + protected List roles; + + public List getRoles() { + return roles; + } + + public void setRoles(List 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 @@ -98,8 +151,8 @@ public class ClientUpdateSourceRolesCondition extends AbstractClientPolicyCondit Set roles = user.getRoleMappingsStream().map(RoleModel::getName).collect(Collectors.toSet()); if (logger.isTraceEnabled()) { - roles.stream().forEach(i -> ClientPolicyLogger.log(logger, " user role = " + i)); - expectedRoles.stream().forEach(i -> ClientPolicyLogger.log(logger, "roles expected = " + i)); + roles.forEach(i -> logger.tracev("user role = {0}", i)); + expectedRoles.forEach(i -> logger.tracev("roles expected = {0}", i)); } RealmModel realm = session.getContext().getRealm(); @@ -114,18 +167,14 @@ public class ClientUpdateSourceRolesCondition extends AbstractClientPolicyCondit return false; }); }); - if (isMatched) { - ClientPolicyLogger.log(logger, "role matched."); - } else { - ClientPolicyLogger.log(logger, "role unmatched."); - } + return isMatched; } private Set instantiateRolesForMatching() { - if (componentModel.getConfig() == null) return null; - List roles = componentModel.getConfig().get(ClientUpdateSourceRolesConditionFactory.ROLES); + List roles = configuration.getRoles(); if (roles == null) return null; return new HashSet<>(roles); } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesConditionFactory.java index 31d52ea52d..4141dfb427 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesConditionFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateSourceRolesConditionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,23 +17,46 @@ package org.keycloak.services.clientpolicy.condition; +import java.util.ArrayList; import java.util.List; -import org.keycloak.component.ComponentModel; +import org.keycloak.Config.Scope; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.provider.ProviderConfigProperty; -public class ClientUpdateSourceRolesConditionFactory extends AbstractClientPolicyConditionProviderFactory { +/** + * @author Takashi Norimatsu + */ +public class ClientUpdateSourceRolesConditionFactory implements ClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "clientupdatesourceroles-condition"; + public static final String ROLES = "roles"; - private static final ProviderConfigProperty CLIENTUPDATEROLE_PROPERTY = new ProviderConfigProperty( - ROLES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "admin"); + private static final List configProperties = new ArrayList(); + + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(ROLES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "admin"); + configProperties.add(property); + } @Override - public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { - return new ClientUpdateSourceRolesCondition(session, model); + public ClientPolicyConditionProvider create(KeycloakSession session) { + return new ClientUpdateSourceRolesCondition(session); + } + + @Override + public void init(Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { } @Override @@ -44,12 +67,12 @@ public class ClientUpdateSourceRolesConditionFactory extends AbstractClientPolic @Override 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."; + } @Override public List getConfigProperties() { - List l = super.getConfigProperties(); - l.add(CLIENTUPDATEROLE_PROPERTY); - return l; + return configProperties; } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/context/AbstractAdminClientCRUDContext.java b/services/src/main/java/org/keycloak/services/clientpolicy/context/AbstractAdminClientCRUDContext.java index 3f85355076..3cfa8afcd6 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/context/AbstractAdminClientCRUDContext.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/context/AbstractAdminClientCRUDContext.java @@ -44,4 +44,4 @@ abstract class AbstractAdminClientCRUDContext implements ClientCRUDContext { public JsonWebToken getToken() { return adminAuth.getToken(); } -} +} \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/context/AbstractDynamicClientCRUDContext.java b/services/src/main/java/org/keycloak/services/clientpolicy/context/AbstractDynamicClientCRUDContext.java index 89de264f89..d8e462ddee 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/context/AbstractDynamicClientCRUDContext.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/context/AbstractDynamicClientCRUDContext.java @@ -38,7 +38,7 @@ abstract class AbstractDynamicClientCRUDContext implements ClientCRUDContext { this.authenticatedClient = realm.getClientByClientId(token.getIssuedFor()); } if (token.getSubject() != null) { - this.authenticatedUser = session.users().getUserById(token.getSubject(), realm); + this.authenticatedUser = session.users().getUserById(realm, token.getSubject()); } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/context/AdminClientUpdateContext.java b/services/src/main/java/org/keycloak/services/clientpolicy/context/AdminClientUpdateContext.java index b6da434afa..0d0789e717 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/context/AdminClientUpdateContext.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/context/AdminClientUpdateContext.java @@ -47,4 +47,4 @@ public class AdminClientUpdateContext extends AbstractAdminClientCRUDContext { public ClientModel getTargetClient() { return targetClient; } -} +} \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/context/AuthorizationRequestContext.java b/services/src/main/java/org/keycloak/services/clientpolicy/context/AuthorizationRequestContext.java index 8733563838..a0d1d95e9c 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/context/AuthorizationRequestContext.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/context/AuthorizationRequestContext.java @@ -24,6 +24,9 @@ import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyEvent; +/** + * @author Takashi Norimatsu + */ public class AuthorizationRequestContext implements ClientPolicyContext { private final OIDCResponseType parsedResponseType; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/context/LogoutRequestContext.java b/services/src/main/java/org/keycloak/services/clientpolicy/context/LogoutRequestContext.java index 4a236e4cc5..9e13999639 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/context/LogoutRequestContext.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/context/LogoutRequestContext.java @@ -22,6 +22,9 @@ import javax.ws.rs.core.MultivaluedMap; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyEvent; +/** + * @author Takashi Norimatsu + */ public class LogoutRequestContext implements ClientPolicyContext { private final MultivaluedMap params; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenIntrospectContext.java b/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenIntrospectContext.java index 53d2e3feae..acfc74b997 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenIntrospectContext.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenIntrospectContext.java @@ -22,6 +22,9 @@ import javax.ws.rs.core.MultivaluedMap; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyEvent; +/** + * @author Takashi Norimatsu + */ public class TokenIntrospectContext implements ClientPolicyContext { private final MultivaluedMap params; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRefreshContext.java b/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRefreshContext.java index e322f147bb..d07873a5c3 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRefreshContext.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRefreshContext.java @@ -22,6 +22,9 @@ import javax.ws.rs.core.MultivaluedMap; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyEvent; +/** + * @author Takashi Norimatsu + */ public class TokenRefreshContext implements ClientPolicyContext { private final MultivaluedMap params; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRequestContext.java b/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRequestContext.java index 7d35f8ad15..933a3ed5ca 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRequestContext.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRequestContext.java @@ -23,6 +23,9 @@ import org.keycloak.protocol.oidc.utils.OAuth2CodeParser; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyEvent; +/** + * @author Takashi Norimatsu + */ public class TokenRequestContext implements ClientPolicyContext { private final MultivaluedMap params; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRevokeContext.java b/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRevokeContext.java index 16c70e86f8..82832e5ad4 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRevokeContext.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/context/TokenRevokeContext.java @@ -22,6 +22,9 @@ import javax.ws.rs.core.MultivaluedMap; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyEvent; +/** + * @author Takashi Norimatsu + */ public class TokenRevokeContext implements ClientPolicyContext { private final MultivaluedMap params; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/context/UserInfoRequestContext.java b/services/src/main/java/org/keycloak/services/clientpolicy/context/UserInfoRequestContext.java index f8c80b5cbf..7c9f2c1c8d 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/context/UserInfoRequestContext.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/context/UserInfoRequestContext.java @@ -20,6 +20,9 @@ package org.keycloak.services.clientpolicy.context; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyEvent; +/** + * @author Takashi Norimatsu + */ public class UserInfoRequestContext implements ClientPolicyContext { private final String tokenString; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/AbstractAugumentingClientRegistrationPolicyExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/AbstractAugumentingClientRegistrationPolicyExecutor.java deleted file mode 100644 index 893fd01b47..0000000000 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/AbstractAugumentingClientRegistrationPolicyExecutor.java +++ /dev/null @@ -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; - -} diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/AbstractAugumentingClientRegistrationPolicyExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/AbstractAugumentingClientRegistrationPolicyExecutorFactory.java deleted file mode 100644 index c021d240af..0000000000 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/AbstractAugumentingClientRegistrationPolicyExecutorFactory.java +++ /dev/null @@ -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 getConfigProperties() { - return new ArrayList<>(Arrays.asList(IS_AUGMENT_PROPERTY)); - } - -} diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutor.java index d083a32e10..9dd65b86a1 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,9 @@ package org.keycloak.services.clientpolicy.executor; import org.jboss.resteasy.spi.HttpRequest; + import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; -import org.keycloak.component.ComponentModel; import org.keycloak.events.Errors; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; @@ -29,57 +29,67 @@ import org.keycloak.representations.RefreshToken; 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.context.LogoutRequestContext; import org.keycloak.services.clientpolicy.context.TokenRefreshContext; import org.keycloak.services.clientpolicy.context.TokenRevokeContext; import org.keycloak.services.clientpolicy.context.UserInfoRequestContext; import org.keycloak.services.util.MtlsHoKTokenUtil; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -public class HolderOfKeyEnforceExecutor extends AbstractAugumentingClientRegistrationPolicyExecutor { +public class HolderOfKeyEnforceExecutor implements ClientPolicyExecutorProvider { private final KeycloakSession session; - private final ComponentModel componentModel; + private Configuration configuration; - public HolderOfKeyEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { - super(session, componentModel); + public HolderOfKeyEnforceExecutor(KeycloakSession session) { this.session = session; - this.componentModel = componentModel; } @Override - public String getName() { - return componentModel.getName(); + public void setupConfiguration(Configuration config) { + this.configuration = config; + } + + @Override + public Class 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 public String getProviderId() { - return componentModel.getProviderId(); - } - - @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"); - } + return HolderOfKeyEnforceExecutorFactory.PROVIDER_ID; } @Override public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException { - super.executeOnEvent(context); HttpRequest request = session.getContext().getContextObject(HttpRequest.class); - switch (context.getEvent()) { + case REGISTER: + case UPDATE: + ClientCRUDContext clientUpdateContext = (ClientCRUDContext)context; + augment(clientUpdateContext.getProposedClientRepresentation()); + validate(clientUpdateContext.getProposedClientRepresentation()); + break; case TOKEN_REQUEST: AccessToken.CertConf certConf = MtlsHoKTokenUtil.bindTokenWithClientCertificate(request, session); 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 { MultivaluedMap formParameters = context.getParams(); 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); } } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutorFactory.java index 1800f7948f..5ded79e941 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/HolderOfKeyEnforceExecutorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,21 +18,26 @@ package org.keycloak.services.clientpolicy.executor; 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 java.util.Collections; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class HolderOfKeyEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory { 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 - public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { - return new HolderOfKeyEnforceExecutor(session, model); + public ClientPolicyExecutorProvider create(KeycloakSession session) { + return new HolderOfKeyEnforceExecutor(session); } @Override @@ -59,7 +64,7 @@ public class HolderOfKeyEnforceExecutorFactory implements ClientPolicyExecutorPr @Override public List getConfigProperties() { - return Collections.emptyList(); + return new ArrayList<>(Arrays.asList(IS_AUGMENT_PROPERTY)); } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutor.java index 17ce256d30..669fee05a1 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,11 +23,9 @@ import java.util.regex.Pattern; import javax.ws.rs.core.MultivaluedMap; -import org.jboss.logging.Logger; import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; import org.keycloak.common.util.Base64Url; -import org.keycloak.component.ComponentModel; import org.keycloak.events.Errors; import org.keycloak.models.ClientModel; 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.ClientPolicyException; 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.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 Takashi Norimatsu + */ +public class PKCEEnforceExecutor implements ClientPolicyExecutorProvider { 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\\-\\.~_]+$"); - public PKCEEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { - super(session, componentModel); + private final KeycloakSession session; + private Configuration configuration; + + public PKCEEnforceExecutor(KeycloakSession session) { + this.session = session; } - protected void augment(ClientRepresentation rep) { - if (Boolean.valueOf(componentModel.getConfig().getFirst(AbstractAugumentingClientRegistrationPolicyExecutor.IS_AUGMENT))) - OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).setPkceCodeChallengeMethod(OAuth2Constants.PKCE_METHOD_S256); + @Override + public void setupConfiguration(Configuration config) { + this.configuration = config; } - protected 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"); + @Override + public Class 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 + public String getProviderId() { + return PKCEEnforceExecutorFactory.PROVIDER_ID; } @Override public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException { - super.executeOnEvent(context); switch (context.getEvent()) { + case REGISTER: + case UPDATE: + ClientCRUDContext clientUpdateContext = (ClientCRUDContext)context; + augment(clientUpdateContext.getProposedClientRepresentation()); + validate(clientUpdateContext.getProposedClientRepresentation()); + break; case AUTHORIZATION_REQUEST: AuthorizationRequestContext authorizationRequestContext = (AuthorizationRequestContext)context; 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( OIDCResponseType parsedResponseType, AuthorizationEndpointRequest request, @@ -196,4 +235,5 @@ public class PKCEEnforceExecutor extends AbstractAugumentingClientRegistrationPo return codeVerifierEncoded; } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutorFactory.java index 6d6bc75279..ac85406c36 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/PKCEEnforceExecutorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,23 +17,30 @@ package org.keycloak.services.clientpolicy.executor; +import java.util.ArrayList; +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.executor.AbstractAugumentingClientRegistrationPolicyExecutorFactory; -import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; -public class PKCEEnforceExecutorFactory extends AbstractAugumentingClientRegistrationPolicyExecutorFactory { +/** + * @author Takashi Norimatsu + */ +public class PKCEEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory { 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 - public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { - return new PKCEEnforceExecutor(session, model); + public ClientPolicyExecutorProvider create(KeycloakSession session) { + return new PKCEEnforceExecutor(session); } @Override @@ -60,7 +67,7 @@ public class PKCEEnforceExecutorFactory extends AbstractAugumentingClientRegistr @Override public List getConfigProperties() { - return super.getConfigProperties(); + return new ArrayList<>(Arrays.asList(IS_AUGMENT_PROPERTY)); } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutor.java index ddde037c23..d6bb16e12c 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,38 +19,109 @@ package org.keycloak.services.clientpolicy.executor; import java.util.List; -import org.jboss.logging.Logger; import org.keycloak.OAuthErrorException; -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; -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 Takashi Norimatsu + */ +public class SecureClientAuthEnforceExecutor implements ClientPolicyExecutorProvider { - public SecureClientAuthEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { - super(session, componentModel); + private final KeycloakSession session; + private Configuration configuration; + + public SecureClientAuthEnforceExecutor(KeycloakSession session) { + this.session = session; } - protected void augment(ClientRepresentation rep) { - if (Boolean.valueOf(componentModel.getConfig().getFirst(AbstractAugumentingClientRegistrationPolicyExecutor.IS_AUGMENT))) + @Override + public void setupConfiguration(SecureClientAuthEnforceExecutor.Configuration config) { + this.configuration = config; + } + + @Override + public Class getExecutorConfigurationClass() { + return Configuration.class; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration extends ClientPolicyExecutorConfiguration { + @JsonProperty("client-authns") + protected List clientAuthns; + @JsonProperty("client-authns-augment") + protected String clientAuthnsAugment; + @JsonProperty("is-augment") + protected Boolean augment; + + public List getClientAuthns() { + return clientAuthns; + } + + public void setClientAuthns(List 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()); } - protected void validate(ClientRepresentation rep) throws ClientPolicyException { + private void validate(ClientRepresentation rep) throws ClientPolicyException { verifyClientAuthenticationMethod(rep.getClientAuthenticatorType()); } private String enforcedClientAuthenticatorType() { - return componentModel.getConfig().getFirst(SecureClientAuthEnforceExecutorFactory.CLIENT_AUTHNS_AUGMENT); + return configuration.getClientAuthnsAugment(); } private void verifyClientAuthenticationMethod(String clientAuthenticatorType) throws ClientPolicyException { - List acceptableClientAuthn = componentModel.getConfig().getList(SecureClientAuthEnforceExecutorFactory.CLIENT_AUTHNS); + List acceptableClientAuthn = configuration.getClientAuthns(); 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"); } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutorFactory.java index c17a1dc1e3..3c69fc3df6 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureClientAuthEnforceExecutorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,31 +17,37 @@ package org.keycloak.services.clientpolicy.executor; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.keycloak.Config.Scope; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; -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.executor.ClientPolicyExecutorProvider; -public class SecureClientAuthEnforceExecutorFactory extends AbstractAugumentingClientRegistrationPolicyExecutorFactory { +/** + * @author Takashi Norimatsu + */ +public class SecureClientAuthEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory { 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_AUGMENT = "client-authns-augment"; + private static final ProviderConfigProperty IS_AUGMENT_PROPERTY = new ProviderConfigProperty( + IS_AUGMENT, null, null, ProviderConfigProperty.BOOLEAN_TYPE, false); private static final ProviderConfigProperty CLIENTAUTHNS_PROPERTY = new ProviderConfigProperty( CLIENT_AUTHNS, null, null, ProviderConfigProperty.MULTIVALUED_STRING_TYPE, null); private static final ProviderConfigProperty CLIENTAUTHNS_AUGMENT = new ProviderConfigProperty( CLIENT_AUTHNS_AUGMENT, null, null, ProviderConfigProperty.STRING_TYPE, JWTClientAuthenticator.PROVIDER_ID); @Override - public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { - return new SecureClientAuthEnforceExecutor(session, model); + public ClientPolicyExecutorProvider create(KeycloakSession session) { + return new SecureClientAuthEnforceExecutor(session); } @Override @@ -68,10 +74,7 @@ public class SecureClientAuthEnforceExecutorFactory extends AbstractAugumentingC @Override public List getConfigProperties() { - List l = super.getConfigProperties(); - l.add(CLIENTAUTHNS_PROPERTY); - l.add(CLIENTAUTHNS_AUGMENT); - return l; + return new ArrayList<>(Arrays.asList(IS_AUGMENT_PROPERTY, CLIENTAUTHNS_PROPERTY, CLIENTAUTHNS_AUGMENT)); } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRedirectUriEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRedirectUriEnforceExecutor.java index 509f16f5be..5b193cbef6 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRedirectUriEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRedirectUriEnforceExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,11 +22,9 @@ import java.util.List; import org.jboss.logging.Logger; import org.keycloak.OAuthErrorException; -import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.services.clientpolicy.ClientPolicyContext; 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.AdminClientUpdateContext; 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.DynamicClientUpdateContext; -public class SecureRedirectUriEnforceExecutor implements ClientPolicyExecutorProvider { +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * @author Takashi Norimatsu + */ +public class SecureRedirectUriEnforceExecutor implements ClientPolicyExecutorProvider { private static final Logger logger = Logger.getLogger(SecureRedirectUriEnforceExecutor.class); private final KeycloakSession session; - private final ComponentModel componentModel; - public SecureRedirectUriEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { + public SecureRedirectUriEnforceExecutor(KeycloakSession session) { this.session = session; - this.componentModel = componentModel; } - @Override - public String getName() { - return componentModel.getName(); + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration { } @Override public String getProviderId() { - return componentModel.getProviderId(); + return SecureRedirectUriEnforceExecutorFactory.PROVIDER_ID; } @Override @@ -87,10 +87,11 @@ public class SecureRedirectUriEnforceExecutor implements ClientPolicyExecutorPro } for(String redirectUri : redirectUris) { - ClientPolicyLogger.log(logger, "Redirect URI = " + redirectUri); + logger.tracev("Redirect URI = {0}", redirectUri); if (redirectUri.startsWith("http://") || redirectUri.contains("*")) { throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: redirect_uris"); } } } -} + +} \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRedirectUriEnforceExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRedirectUriEnforceExecutorFactory.java index 061a2caafe..fda057a1ba 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRedirectUriEnforceExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRedirectUriEnforceExecutorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,18 +21,20 @@ import java.util.Collections; 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; +/** + * @author Takashi Norimatsu + */ public class SecureRedirectUriEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory { public static final String PROVIDER_ID = "secure-redirecturi-enforce-executor"; @Override - public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { - return new SecureRedirectUriEnforceExecutor(session, model); + public ClientPolicyExecutorProvider create(KeycloakSession session) { + return new SecureRedirectUriEnforceExecutor(session); } @Override diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutor.java index 1b8c0f2bd1..e0370fa713 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,6 @@ import javax.ws.rs.core.MultivaluedMap; import org.jboss.logging.Logger; import org.keycloak.OAuthErrorException; import org.keycloak.common.util.Time; -import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.OIDCLoginProtocol; 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.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.JsonNode; -public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider { +/** + * @author Takashi Norimatsu + */ +public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider { 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 SecureRequestObjectExecutor(KeycloakSession session, ComponentModel componentModel) { + private final KeycloakSession session; + + public SecureRequestObjectExecutor(KeycloakSession session) { this.session = session; - this.componentModel = componentModel; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration { + } + + @Override + public String getProviderId() { + return SecureRequestObjectExecutorFactory.PROVIDER_ID; } @Override @@ -73,10 +82,10 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider AuthorizationEndpointRequest request, String redirectUri, MultivaluedMap params) throws ClientPolicyException { - ClientPolicyLogger.log(logger, "Authz Endpoint - authz request"); + logger.trace("Authz Endpoint - authz request"); if (params == null) { - ClientPolicyLogger.log(logger, "request parameter not exist."); + logger.trace("request parameter not exist."); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameters"); } @@ -85,7 +94,7 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider // check whether whether request object exists 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"); } @@ -93,26 +102,26 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider // check whether request object exists 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"); } // check whether scope exists in both query parameter and request object 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"); } // check whether "exp" claim exists 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"); } // check whether request object not expired long exp = requestObject.get("exp").asLong(); 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"); } @@ -120,7 +129,7 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider List aud = new ArrayList(); JsonNode audience = requestObject.get("aud"); 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"); } if (audience.isArray()) { @@ -129,25 +138,25 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider aud.add(audience.asText()); } 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"); } // check whether "aud" claim points to this keycloak as authz server String iss = Urls.realmIssuer(session.getContext().getUri().getBaseUri(), session.getContext().getRealm().getName()); 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"); } // 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 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"); } - ClientPolicyLogger.log(logger, "Passed."); + logger.trace("Passed."); } private boolean isSameParameterIncluded(String param, String value, JsonNode requestObject) { @@ -156,14 +165,4 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider return false; } - @Override - public String getName() { - return componentModel.getName(); - } - - @Override - public String getProviderId() { - return componentModel.getProviderId(); - } - } \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutorFactory.java index 616ef46aaf..753987475c 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureRequestObjectExecutorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,18 +21,20 @@ import java.util.Collections; 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; +/** + * @author Takashi Norimatsu + */ public class SecureRequestObjectExecutorFactory implements ClientPolicyExecutorProviderFactory { public static final String PROVIDER_ID = "secure-reqobj-executor"; @Override - public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { - return new SecureRequestObjectExecutor(session, model); + public ClientPolicyExecutorProvider create(KeycloakSession session) { + return new SecureRequestObjectExecutor(session); } @Override diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutor.java index 2db1aecf01..bbb618bb6e 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,25 +19,35 @@ package org.keycloak.services.clientpolicy.executor; import org.jboss.logging.Logger; import org.keycloak.OAuthErrorException; -import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; -public class SecureResponseTypeExecutor implements ClientPolicyExecutorProvider { +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * @author Takashi Norimatsu + */ +public class SecureResponseTypeExecutor implements ClientPolicyExecutorProvider { private static final Logger logger = Logger.getLogger(SecureResponseTypeExecutor.class); protected final KeycloakSession session; - protected final ComponentModel componentModel; - public SecureResponseTypeExecutor(KeycloakSession session, ComponentModel componentModel) { + public SecureResponseTypeExecutor(KeycloakSession session) { this.session = session; - this.componentModel = componentModel; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration { + } + + @Override + public String getProviderId() { + return SecureResponseTypeExecutorFactory.PROVIDER_ID; } @Override @@ -59,30 +69,19 @@ public class SecureResponseTypeExecutor implements ClientPolicyExecutorProvider OIDCResponseType parsedResponseType, AuthorizationEndpointRequest request, 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.TOKEN)) { - ClientPolicyLogger.log(logger, "Passed. response_type = code id_token token"); + logger.trace("Passed. response_type = code id_token token"); } else { - ClientPolicyLogger.log(logger, "Passed. response_type = code id_token"); + logger.trace("Passed. response_type = code id_token"); } return; } - ClientPolicyLogger.log(logger, "invalid response_type = " + parsedResponseType); + logger.tracev("invalid response_type = {0}", parsedResponseType); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "invalid response_type"); - - } - - @Override - public String getName() { - return componentModel.getName(); - } - - @Override - public String getProviderId() { - return componentModel.getProviderId(); } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutorFactory.java index 819c41475e..967f9f667e 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureResponseTypeExecutorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,18 +21,20 @@ import java.util.Collections; 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; +/** + * @author Takashi Norimatsu + */ public class SecureResponseTypeExecutorFactory implements ClientPolicyExecutorProviderFactory { public static final String PROVIDER_ID = "secure-responsetype-executor"; @Override - public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { - return new SecureResponseTypeExecutor(session, model); + public ClientPolicyExecutorProvider create(KeycloakSession session) { + return new SecureResponseTypeExecutor(session); } @Override diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutor.java index 2729b53d6c..a6f38e25a8 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,36 +19,36 @@ package org.keycloak.services.clientpolicy.executor; import org.jboss.logging.Logger; import org.keycloak.OAuthErrorException; -import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.context.AuthorizationRequestContext; import org.keycloak.util.TokenUtil; -public class SecureSessionEnforceExecutor implements ClientPolicyExecutorProvider { +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * @author Takashi Norimatsu + */ +public class SecureSessionEnforceExecutor implements ClientPolicyExecutorProvider { private static final Logger logger = Logger.getLogger(SecureSessionEnforceExecutor.class); private final KeycloakSession session; - private final ComponentModel componentModel; - public SecureSessionEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { + public SecureSessionEnforceExecutor(KeycloakSession session) { this.session = session; - this.componentModel = componentModel; } - @Override - public String getName() { - return componentModel.getName(); + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration { } @Override public String getProviderId() { - return componentModel.getProviderId(); + return SecureSessionEnforceExecutorFactory.PROVIDER_ID; } @Override @@ -69,19 +69,19 @@ public class SecureSessionEnforceExecutor implements ClientPolicyExecutorProvide OIDCResponseType parsedResponseType, AuthorizationEndpointRequest request, String redirectUri) throws ClientPolicyException { - ClientPolicyLogger.log(logger, "Authz Endpoint - authz request"); + logger.trace("Authz Endpoint - authz request"); if (TokenUtil.isOIDCRequest(request.getScope())) { if(request.getNonce() == null) { - ClientPolicyLogger.log(logger, "Missing parameter: nonce"); + logger.trace("Missing parameter: nonce"); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: nonce"); } } else { if(request.getState() == null) { - ClientPolicyLogger.log(logger, "Missing parameter: state"); + logger.trace("Missing parameter: state"); throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: state"); } } - ClientPolicyLogger.log(logger, "Passed."); + logger.trace("Passed."); } } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutorFactory.java index 18184513f4..d3c2c5e02c 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSessionEnforceExecutorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,18 +21,20 @@ import java.util.Collections; 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; +/** + * @author Takashi Norimatsu + */ public class SecureSessionEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory { public static final String PROVIDER_ID = "secure-session-enforce-executor"; @Override - public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { - return new SecureSessionEnforceExecutor(session, model); + public ClientPolicyExecutorProvider create(KeycloakSession session) { + return new SecureSessionEnforceExecutor(session); } @Override diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutor.java index e2f865b3a1..a03a47cdaa 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,25 +23,27 @@ import java.util.List; import org.jboss.logging.Logger; import org.keycloak.OAuthErrorException; -import org.keycloak.component.ComponentModel; import org.keycloak.crypto.Algorithm; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyContext; 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.AdminClientUpdateContext; import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext; import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext; -public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecutorProvider { +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * @author Takashi Norimatsu + */ +public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecutorProvider { private static final Logger logger = Logger.getLogger(SecureSigningAlgorithmEnforceExecutor.class); private final KeycloakSession session; - private final ComponentModel componentModel; private static final List sigTargets = Arrays.asList( OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, @@ -52,19 +54,17 @@ public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecut private static final List sigTargetsAdminRestApiOnly = Arrays.asList( OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG); - public SecureSigningAlgorithmEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { + public SecureSigningAlgorithmEnforceExecutor(KeycloakSession session) { this.session = session; - this.componentModel = componentModel; } - @Override - public String getName() { - return componentModel.getName(); + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration { } @Override public String getProviderId() { - return componentModel.getProviderId(); + return SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID; } @Override @@ -112,7 +112,7 @@ public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecut private void verifySecureSigningAlgorithm(String sigTarget, String sigAlg) throws ClientPolicyException { 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; } switch (sigAlg) { @@ -122,10 +122,10 @@ public class SecureSigningAlgorithmEnforceExecutor implements ClientPolicyExecut case Algorithm.ES256: case Algorithm.ES384: 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; } - 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."); } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutorFactory.java index 3071e6837d..0e1eb62d3d 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmEnforceExecutorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,18 +21,20 @@ import java.util.Collections; 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; +/** + * @author Takashi Norimatsu + */ public class SecureSigningAlgorithmEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory { public static final String PROVIDER_ID = "securesignalg-enforce-executor"; @Override - public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { - return new SecureSigningAlgorithmEnforceExecutor(session, model); + public ClientPolicyExecutorProvider create(KeycloakSession session) { + return new SecureSigningAlgorithmEnforceExecutor(session); } @Override diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutor.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutor.java index 7759e9daec..3adbe1f8ce 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutor.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,35 +21,32 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; -import org.keycloak.component.ComponentModel; import org.keycloak.crypto.Algorithm; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInputException; import org.keycloak.models.KeycloakSession; import org.keycloak.services.clientpolicy.ClientPolicyContext; 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 { private static final Logger logger = Logger.getLogger(SecureSigningAlgorithmForSignedJwtEnforceExecutor.class); private final KeycloakSession session; - private final ComponentModel componentModel; - public SecureSigningAlgorithmForSignedJwtEnforceExecutor(KeycloakSession session, ComponentModel componentModel) { + public SecureSigningAlgorithmForSignedJwtEnforceExecutor(KeycloakSession session) { this.session = session; - this.componentModel = componentModel; } - @Override - public String getName() { - return componentModel.getName(); + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Configuration { } @Override public String getProviderId() { - return componentModel.getProviderId(); + return SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID; } @Override @@ -78,7 +75,7 @@ public class SecureSigningAlgorithmForSignedJwtEnforceExecutor implements Client private void verifySecureSigningAlgorithm(String signatureAlgorithm) throws ClientPolicyException { if (signatureAlgorithm == null) { - ClientPolicyLogger.log(logger, "Signing algorithm not specified explicitly."); + logger.trace("Signing algorithm not specified explicitly."); return; } @@ -90,10 +87,11 @@ public class SecureSigningAlgorithmForSignedJwtEnforceExecutor implements Client case Algorithm.ES256: case Algorithm.ES384: case Algorithm.ES512: - ClientPolicyLogger.log(logger, "Passed. signatureAlgorithm = " + signatureAlgorithm); + logger.tracev("Passed. signatureAlgorithm = {0}", signatureAlgorithm); 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."); } + } diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.java index e04ab50d5c..ce29da5130 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/executor/SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ package org.keycloak.services.clientpolicy.executor; 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; @@ -31,8 +30,8 @@ public class SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory implements public static final String PROVIDER_ID = "securesignalgjwt-enforce-executor"; @Override - public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { - return new SecureSigningAlgorithmForSignedJwtEnforceExecutor(session, model); + public ClientPolicyExecutorProvider create(KeycloakSession session) { + return new SecureSigningAlgorithmForSignedJwtEnforceExecutor(session); } @Override diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index 0e14cb87c0..b28375244c 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -124,6 +124,7 @@ public class RealmManager { createDefaultClientScopes(realm); setupAuthorizationServices(realm); setupClientRegistrations(realm); + setupClientPolicies(realm); fireRealmPostCreate(realm); @@ -598,6 +599,8 @@ public class RealmManager { MigrationModelManager.migrateImport(session, realm, rep, skipUserDependent); } + setupClientPolicies(realm, rep); + fireRealmPostCreate(realm); return realm; @@ -711,6 +714,14 @@ public class RealmManager { DefaultClientRegistrationPolicies.addDefaultPolicies(realm); } + private void setupClientPolicies(RealmModel realm, RealmRepresentation rep) { + session.clientPolicy().setupClientPoliciesOnImportedRealm(realm, rep); + } + + private void setupClientPolicies(RealmModel realm) { + session.clientPolicy().setupClientPoliciesOnCreatedRealm(realm); + } + private void fireRealmPostCreate(RealmModel realm) { session.getKeycloakSessionFactory().publish(new RealmModel.RealmPostCreateEvent() { @Override diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 7d6bd0597e..9993423a9c 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -60,6 +60,7 @@ import org.keycloak.util.JsonSerialization; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.ws.rs.core.Application; + import java.io.File; import java.io.FileInputStream; 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); exportImportManager = new ExportImportManager(session); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientPoliciesResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientPoliciesResource.java new file mode 100644 index 0000000000..a28a514a8c --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientPoliciesResource.java @@ -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(); + } +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientProfilesResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientProfilesResource.java new file mode 100644 index 0000000000..2b39ea3965 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientProfilesResource.java @@ -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(); + } +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index ea408229b2..684c6b07ff 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -1207,4 +1207,17 @@ public class RealmAdminResource { .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; + } } diff --git a/services/src/main/resources/META-INF/services/org.keycloak.services.clientpolicy.ClientPolicyProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.services.clientpolicy.ClientPolicyProviderFactory deleted file mode 100644 index 414c5ac9f7..0000000000 --- a/services/src/main/resources/META-INF/services/org.keycloak.services.clientpolicy.ClientPolicyProviderFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.services.clientpolicy.DefaultClientPolicyProviderFactory \ No newline at end of file diff --git a/services/src/main/resources/keycloak-default-client-policies.json b/services/src/main/resources/keycloak-default-client-policies.json new file mode 100644 index 0000000000..b10909c2e6 --- /dev/null +++ b/services/src/main/resources/keycloak-default-client-policies.json @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/services/src/main/resources/keycloak-default-client-profiles.json b/services/src/main/resources/keycloak-default-client-profiles.json new file mode 100644 index 0000000000..c193d73fce --- /dev/null +++ b/services/src/main/resources/keycloak-default-client-profiles.json @@ -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": {} + } + ] + } + ] +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionCondition.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionCondition.java index bb320fd4a4..dc1e58f32b 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionCondition.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,35 +17,41 @@ package org.keycloak.testsuite.services.clientpolicy.condition; -import org.jboss.logging.Logger; import org.keycloak.OAuthErrorException; -import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyVote; +import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionConfiguration; import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; -public class TestRaiseExeptionCondition implements ClientPolicyConditionProvider { +/** + * @author Takashi Norimatsu + */ +public class TestRaiseExeptionCondition implements ClientPolicyConditionProvider { - 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; - private final ComponentModel componentModel; - - public TestRaiseExeptionCondition(KeycloakSession session, ComponentModel componentModel) { - this.session = session; - this.componentModel = componentModel; + public TestRaiseExeptionCondition(KeycloakSession session) { } @Override - public String getName() { - return componentModel.getName(); + public void setupConfiguration(Configuration config) { + this.configuration = config; + } + + @Override + public Class getConditionConfigurationClass() { + return Configuration.class; + } + + public static class Configuration extends ClientPolicyConditionConfiguration { } @Override public String getProviderId() { - return componentModel.getProviderId(); + return TestRaiseExeptionConditionFactory.PROVIDER_ID; } @Override diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionConditionFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionConditionFactory.java index 1d9add4737..9cd081f94b 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionConditionFactory.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/condition/TestRaiseExeptionConditionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,21 +21,22 @@ import java.util.Collections; 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.condition.ClientPolicyConditionProvider; import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProviderFactory; +/** + * @author Takashi Norimatsu + */ public class TestRaiseExeptionConditionFactory implements ClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "test-raise-exception-condition"; @Override - public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { - return new TestRaiseExeptionCondition(session, model); - + public ClientPolicyConditionProvider create(KeycloakSession session) { + return new TestRaiseExeptionCondition(session); } @Override @@ -64,4 +65,5 @@ public class TestRaiseExeptionConditionFactory implements ClientPolicyConditionP public List getConfigProperties() { return Collections.emptyList(); } + } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java index 4e3f0ebd8c..020cf8f287 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutor.java @@ -17,37 +17,27 @@ package org.keycloak.testsuite.services.clientpolicy.executor; -import java.util.List; - import org.jboss.logging.Logger; -import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyLogger; +import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorConfiguration; import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; -public class TestRaiseExeptionExecutor implements ClientPolicyExecutorProvider { +public class TestRaiseExeptionExecutor implements ClientPolicyExecutorProvider { private static final Logger logger = Logger.getLogger(TestRaiseExeptionExecutor.class); protected final KeycloakSession session; - protected final ComponentModel componentModel; - public TestRaiseExeptionExecutor(KeycloakSession session, ComponentModel componentModel) { + public TestRaiseExeptionExecutor(KeycloakSession session) { this.session = session; - this.componentModel = componentModel; - } - - @Override - public String getName() { - return componentModel.getName(); } @Override public String getProviderId() { - return componentModel.getProviderId(); + return TestRaiseExeptionExecutorFactory.PROVIDER_ID; } @Override @@ -56,8 +46,15 @@ public class TestRaiseExeptionExecutor implements ClientPolicyExecutorProvider { } private boolean isThrowExceptionNeeded(ClientPolicyEvent event) { - ClientPolicyLogger.log(logger, "Client Policy Trigger Event = " + event); - List l = componentModel.getConfig().get(TestRaiseExeptionExecutorFactory.TARGET_CP_EVENTS); - return l != null && l.stream().anyMatch(i->i.equals(event.toString())); + logger.tracev("Client Policy Trigger Event = {0}", event); + switch (event) { + case REGISTERED: + case UPDATED: + case UNREGISTER: + return true; + default : + return false; + } + } -} +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutorFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutorFactory.java index 5516a814d0..45a3490a22 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutorFactory.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/services/clientpolicy/executor/TestRaiseExeptionExecutorFactory.java @@ -17,12 +17,10 @@ package org.keycloak.testsuite.services.clientpolicy.executor; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; 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; @@ -33,13 +31,9 @@ public class TestRaiseExeptionExecutorFactory implements ClientPolicyExecutorPro 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 - public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) { - return new TestRaiseExeptionExecutor(session, model); + public ClientPolicyExecutorProvider create(KeycloakSession session) { + return new TestRaiseExeptionExecutor(session); } @Override @@ -61,12 +55,12 @@ public class TestRaiseExeptionExecutorFactory implements ClientPolicyExecutorPro @Override public String getHelpText() { - return null; + return "NA"; } @Override public List getConfigProperties() { - return Arrays.asList(TARGET_CP_EVENTS_PROPERTY); + return Collections.emptyList(); } -} +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java index 6f522d8cd2..ef360d1cdf 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientPoliciesTest.java @@ -18,6 +18,8 @@ package org.keycloak.testsuite.client; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import java.io.IOException; @@ -31,17 +33,21 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; import javax.ws.rs.BadRequestException; import javax.ws.rs.core.Response; @@ -54,6 +60,7 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; +import org.jboss.logging.Logger; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -61,6 +68,7 @@ import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; import org.keycloak.adapters.AdapterUtils; import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.client.registration.Auth; import org.keycloak.client.registration.ClientRegistration; import org.keycloak.client.registration.ClientRegistrationException; @@ -68,40 +76,64 @@ import org.keycloak.common.util.Base64; import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.KeycloakUriBuilder; -import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.Time; import org.keycloak.common.util.UriUtils; import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.crypto.KeyType; import org.keycloak.crypto.SignatureSignerContext; -import org.keycloak.events.Errors; import org.keycloak.events.EventType; import org.keycloak.jose.jws.Algorithm; import org.keycloak.jose.jws.JWSBuilder; +import org.keycloak.models.AdminRoles; import org.keycloak.models.Constants; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; import org.keycloak.representations.idm.ClientInitialAccessPresentation; +import org.keycloak.representations.idm.ClientPoliciesRepresentation; +import org.keycloak.representations.idm.ClientPolicyRepresentation; +import org.keycloak.representations.idm.ClientProfileRepresentation; +import org.keycloak.representations.idm.ClientProfilesRepresentation; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.representations.oidc.TokenMetadataRepresentation; import org.keycloak.services.Urls; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.ClientPolicyProvider; -import org.keycloak.services.clientpolicy.DefaultClientPolicyProviderFactory; +import org.keycloak.services.clientpolicy.condition.AnyClientCondition; +import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientAccessTypeCondition; import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory; -import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; +import org.keycloak.services.clientpolicy.condition.ClientRolesCondition; import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientScopesCondition; import org.keycloak.services.clientpolicy.condition.ClientScopesConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateContextCondition; import org.keycloak.services.clientpolicy.condition.ClientUpdateContextConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceGroupsCondition; import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceGroupsConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceHostsCondition; import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceHostsConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceRolesCondition; import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceRolesConditionFactory; -import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider; +import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutor; +import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutor; +import org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureClientAuthEnforceExecutor; import org.keycloak.services.clientpolicy.executor.SecureClientAuthEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureRedirectUriEnforceExecutor; +import org.keycloak.services.clientpolicy.executor.SecureRedirectUriEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureRequestObjectExecutor; +import org.keycloak.services.clientpolicy.executor.SecureRequestObjectExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureResponseTypeExecutor; +import org.keycloak.services.clientpolicy.executor.SecureResponseTypeExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutor; +import org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmEnforceExecutor; +import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutor; +import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.admin.ApiUtil; @@ -109,45 +141,38 @@ import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls; import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource; import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource; import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject; +import org.keycloak.testsuite.services.clientpolicy.condition.TestRaiseExeptionCondition; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.ServerURLs; import org.keycloak.util.JsonSerialization; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +/** + * @author Takashi Norimatsu + */ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { + protected static final Logger logger = Logger.getLogger(AbstractClientPoliciesTest.class); + protected static final String REALM_NAME = "test"; protected static final String TEST_CLIENT = "test-app"; protected static final String TEST_CLIENT_SECRET = "password"; + protected static final String POLICY_NAME = "MyPolicy"; + protected static final String PROFILE_NAME = "MyProfile"; + protected static final String SAMPLE_CLIENT_ROLE = "sample-client-role"; + protected static final String ERR_MSG_MISSING_NONCE = "Missing parameter: nonce"; protected static final String ERR_MSG_MISSING_STATE = "Missing parameter: state"; protected static final String ERR_MSG_CLIENT_REG_FAIL = "Failed to send request"; - protected static final String AnyClientCondition_NAME = "AnyClientCondition"; - protected static final String ClientAccessTypeCondition_NAME = "ClientAccessTypeCondition"; - protected static final String ClientRolesCondition_NAME = "ClientRolesCondition"; - protected static final String ClientScopesCondition_NAME = "ClientScopesCondition"; - protected static final String ClientUpdateContextCondition_NAME = "ClientUpdateContextCondition"; - protected static final String ClientUpdateSourceGroupsCondition_NAME = "ClientUpdateSourceGroupsCondition"; - protected static final String ClientUpdateSourceRolesCondition_NAME = "ClientUpdateSourceRolesCondition"; - protected static final String ClientUpdateSourceHostsCondition_NAME = "ClientUpdateSourceHostsCondition"; - protected static final String TestRaiseExeptionCondition_NAME = "TestRaiseExeptionCondition"; - - protected static final String HolderOfKeyEnforceExecutor_NAME = "HolderOfKeyEnforceExecutor"; - protected static final String PKCEEnforceExecutor_NAME = "PKCEEnforceExecutor"; - protected static final String SecureClientAuthEnforceExecutor_NAME = "SecureClientAuthEnforceExecutor"; - protected static final String SecureRedirectUriEnforceExecutor_NAME = "SecureRedirectUriEnforceExecutor"; - protected static final String SecureResponseTypeExecutor_NAME = "SecureResponseTypeExecutor"; - protected static final String SecureRequestObjectExecutor_NAME = "SecureRequestObjectExecutor"; - protected static final String SecureSessionEnforceExecutor_NAME = "SecureSessionEnforceExecutor"; - protected static final String SecureSigningAlgorithmEnforceExecutor_NAME = "SecureSigningAlgorithmEnforceExecutor"; - protected static final String SecureSigningAlgorithmForSignedJwtEnforceExecutor_NAME = "SecureSigningAlgorithmForSignedJwtEnforceExecutor"; - protected ClientRegistration reg; + private static final ObjectMapper objectMapper = new ObjectMapper(); + @Rule public AssertEvents events = new AssertEvents(this); @@ -157,13 +182,171 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { reg = ClientRegistration.create().url(suiteContext.getAuthServerInfo().getContextRoot() + "/auth", REALM_NAME).build(); ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10)); reg.auth(Auth.token(token)); + } @After public void after() throws Exception { reg.close(); + revertToBuiltinProfiles(); + revertToBuiltinPolicies(); } + protected void loadValidProfilesAndPolicies() throws Exception { + // load profiles + 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(); + + ClientProfileRepresentation loadedProfileRepWithoutBuiltinField = (new ClientProfileBuilder()).createProfile("lack-of-builtin-field-test-profile", "Without builtin field that is treated as builtin=false.", null, null) + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + createSecureClientAuthEnforceExecutorConfig( + Boolean.TRUE, + Arrays.asList(JWTClientAuthenticator.PROVIDER_ID), + JWTClientAuthenticator.PROVIDER_ID)) + .addExecutor(HolderOfKeyEnforceExecutorFactory.PROVIDER_ID, + createHolderOfKeyEnforceExecutorConfig(Boolean.TRUE)) + .addExecutor(SecureRedirectUriEnforceExecutorFactory.PROVIDER_ID, null) + .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID, null) + .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, null) + .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, null) + .addExecutor(SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, null) + .addExecutor(SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, null) + .toRepresentation(); + + String json = (new ClientProfilesBuilder()) + .addProfile(loadedProfileRep) + .addProfile(loadedProfileRepWithoutBuiltinField) + .toString(); + updateProfiles(json); + + // load policies + ClientPolicyRepresentation loadedPolicyRepNotExistAndDuplicatedProfile = + (new ClientPolicyBuilder()).createPolicy( + "new-policy", + "duplicated profiles are ignored.", + Boolean.FALSE, + Boolean.TRUE, + null, + Arrays.asList("builtin-default-profile", "ordinal-test-profile", "lack-of-builtin-field-test-profile", "ordinal-test-profile")) + .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, + createClientAccessTypeConditionConfig(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_PUBLIC, ClientAccessTypeConditionFactory.TYPE_BEARERONLY))) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) + .addCondition(ClientScopesConditionFactory.PROVIDER_ID, + createClientScopesConditionConfig(ClientScopesConditionFactory.OPTIONAL, Arrays.asList(SAMPLE_CLIENT_ROLE))) + .toRepresentation(); + + ClientPolicyRepresentation loadedPolicyRepWithoutBuiltinField = + (new ClientPolicyBuilder()).createPolicy( + "lack-of-builtin-field-test-policy", + "Without builtin field that is treated as builtin=false.", + null, + null, + null, + Arrays.asList("lack-of-builtin-field-test-profile")) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, + createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))) + .addCondition(ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, + createClientUpdateSourceGroupsConditionConfig(Arrays.asList("topGroup"))) + .addCondition(ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, + createClientUpdateSourceHostsConditionConfig(Arrays.asList("localhost", "127.0.0.1"), Arrays.asList(Boolean.TRUE, Boolean.TRUE))) + .addCondition(ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, + createClientUpdateSourceRolesConditionConfig(Arrays.asList(AdminRoles.CREATE_CLIENT))) + .toRepresentation(); + + json = (new ClientPoliciesBuilder()) + .addPolicy(loadedPolicyRepNotExistAndDuplicatedProfile) + .addPolicy(loadedPolicyRepWithoutBuiltinField) + .toString(); + updatePolicies(json); + + } + + + protected void assertExpectedLoadedProfiles(Consumer modifiedAssertion) { + + // retrieve loaded builtin profiles + ClientProfilesRepresentation actualProfilesRep = getProfiles(); + + // same profiles + assertExpectedProfiles(actualProfilesRep, Arrays.asList("builtin-default-profile", "ordinal-test-profile", "lack-of-builtin-field-test-profile")); + + // each profile - builtin-default-profile + ClientProfileRepresentation actualProfileRep = getProfileRepresentation(actualProfilesRep, "builtin-default-profile"); + assertExpectedProfile(actualProfileRep, "builtin-default-profile", "The built-in default profile for enforcing basic security level to clients.", true); + + // each executor + assertExpectedExecutors(Arrays.asList(SecureSessionEnforceExecutorFactory.PROVIDER_ID), actualProfileRep); + assertExpectedSecureSessionEnforceExecutor(actualProfileRep); + + // each profile - ordinal-test-profile - updated + actualProfileRep = getProfileRepresentation(actualProfilesRep, "ordinal-test-profile"); + modifiedAssertion.accept(actualProfilesRep); + + // each executor + assertExpectedExecutors(Arrays.asList(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID), actualProfileRep); + assertExpectedSecureClientAuthEnforceExecutor(Arrays.asList(JWTClientAuthenticator.PROVIDER_ID), true, JWTClientAuthenticator.PROVIDER_ID, actualProfileRep); + + // each profile - lack-of-builtin-field-test-profile + actualProfileRep = getProfileRepresentation(actualProfilesRep, "lack-of-builtin-field-test-profile"); + assertExpectedProfile(actualProfileRep, "lack-of-builtin-field-test-profile", "Without builtin field that is treated as builtin=false.", false); + + // each executor + assertExpectedExecutors(Arrays.asList( + SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + HolderOfKeyEnforceExecutorFactory.PROVIDER_ID, + SecureRedirectUriEnforceExecutorFactory.PROVIDER_ID, + SecureRequestObjectExecutorFactory.PROVIDER_ID, + SecureResponseTypeExecutorFactory.PROVIDER_ID, + SecureSessionEnforceExecutorFactory.PROVIDER_ID, + SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, + SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID), actualProfileRep); + assertExpectedSecureClientAuthEnforceExecutor(Arrays.asList(JWTClientAuthenticator.PROVIDER_ID), true, JWTClientAuthenticator.PROVIDER_ID, actualProfileRep); + assertExpectedHolderOfKeyEnforceExecutor(true, actualProfileRep); + assertExpectedSecureRedirectUriEnforceExecutor(actualProfileRep); + assertExpectedSecureRequestObjectExecutor(actualProfileRep); + assertExpectedSecureResponseTypeExecutor(actualProfileRep); + assertExpectedSecureSessionEnforceExecutor(actualProfileRep); + assertExpectedSecureSigningAlgorithmEnforceExecutor(actualProfileRep); + assertExpectedSecureSigningAlgorithmForSignedJwtEnforceExecutor(actualProfileRep); + } + + protected void assertExpectedLoadedPolicies(Consumer modifiedAssertion) { + + // retrieve loaded builtin policies + ClientPoliciesRepresentation actualPoliciesRep = getPolicies(); + + // same policies + assertExpectedPolicies(Arrays.asList("builtin-default-policy", "new-policy", "lack-of-builtin-field-test-policy"), actualPoliciesRep); + + // each policy - new-policy - updated + ClientPolicyRepresentation actualPolicyRep = getPolicyRepresentation(actualPoliciesRep, "new-policy"); + modifiedAssertion.accept(actualPoliciesRep); + + // each condition + assertExpectedConditions(Arrays.asList(ClientAccessTypeConditionFactory.PROVIDER_ID, ClientRolesConditionFactory.PROVIDER_ID, ClientScopesConditionFactory.PROVIDER_ID), actualPolicyRep); + assertExpectedClientAccessTypeCondition(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_PUBLIC, ClientAccessTypeConditionFactory.TYPE_BEARERONLY), actualPolicyRep); + assertExpectedClientRolesCondition(Arrays.asList(SAMPLE_CLIENT_ROLE), actualPolicyRep); + assertExpectedClientScopesCondition(ClientScopesConditionFactory.OPTIONAL, Arrays.asList(SAMPLE_CLIENT_ROLE), actualPolicyRep); + + // each policy - lack-of-builtin-field-test-policy + actualPolicyRep = getPolicyRepresentation(actualPoliciesRep, "lack-of-builtin-field-test-policy"); + assertExpectedPolicy("lack-of-builtin-field-test-policy", "Without builtin field that is treated as builtin=false.", false, false, Arrays.asList("lack-of-builtin-field-test-profile"), actualPolicyRep); + + // each condition + assertExpectedConditions(Arrays.asList(ClientUpdateContextConditionFactory.PROVIDER_ID, ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, ClientUpdateSourceRolesConditionFactory.PROVIDER_ID), actualPolicyRep); + assertExpectedClientUpdateContextCondition(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER), actualPolicyRep); + assertExpectedClientUpdateSourceGroupsCondition(Arrays.asList("topGroup"), actualPolicyRep); + assertExpectedClientUpdateSourceHostsCondition(Arrays.asList("localhost", "127.0.0.1"), Arrays.asList(Boolean.TRUE, Boolean.TRUE), actualPolicyRep); + assertExpectedClientUpdateSourceRolesCondition(Arrays.asList(AdminRoles.CREATE_CLIENT), actualPolicyRep); + } + + protected String generateSuffixedName(String name) { return name + "-" + UUID.randomUUID().toString().subSequence(0, 7); } @@ -397,7 +580,6 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { protected void doIntrospectAccessToken(OAuthClient.AccessTokenResponse tokenRes, String username, String clientId, String clientSecret) throws IOException { String tokenResponse = oauth.introspectAccessTokenWithClientCredential(clientId, clientSecret, tokenRes.getAccessToken()); - ObjectMapper objectMapper = new ObjectMapper(); JsonNode jsonNode = objectMapper.readTree(tokenResponse); assertEquals(true, jsonNode.get("active").asBoolean()); assertEquals(username, jsonNode.get("username").asText()); @@ -558,195 +740,848 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { reg.oidc().delete(clientId); } - // Policy CRUD operation primitives + // Client Profiles CRUD Operations - protected String createPolicy(String name, String providerId, String subType, List conditions, List executors) { - ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyProvider.class.getName(), subType); - component.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditions); - component.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executors); - return createComponent(component); + protected static class ClientProfilesBuilder { + private final ClientProfilesRepresentation profilesRep; + + public ClientProfilesBuilder() { + profilesRep = new ClientProfilesRepresentation(); + profilesRep.setProfiles(new ArrayList<>()); + } + + public ClientProfilesBuilder addProfile(ClientProfileRepresentation rep) { + profilesRep.getProfiles().add(rep); + return this; + } + + public ClientProfilesRepresentation toRepresentation() { + return profilesRep; + } + + public String toString() { + String profilesJson = null; + try { + profilesJson = objectMapper.writeValueAsString(profilesRep); + } catch (JsonProcessingException e) { + e.printStackTrace(); + fail(); + } + return profilesJson; + } } - protected ComponentRepresentation getPolicy(String name) { - return getComponent(name, ClientPolicyProvider.class.getName()); + protected static class ClientProfileBuilder { + + private final ClientProfileRepresentation profileRep; + + public ClientProfileBuilder() { + profileRep = new ClientProfileRepresentation(); + } + + public ClientProfileBuilder createProfile(String name, String description, Boolean isBuiltin, List executors) { + if (name != null) { + profileRep.setName(name); + } + if (description != null) { + profileRep.setDescription(description); + } + if (isBuiltin != null) { + profileRep.setBuiltin(isBuiltin); + } else { + profileRep.setBuiltin(Boolean.FALSE); + } + if (executors != null) { + profileRep.setExecutors(executors); + } else { + profileRep.setExecutors(new ArrayList<>()); + } + return this; + } + + public ClientProfileBuilder addExecutor(String providerId, Object config) { + String configString = null; + if (config == null) { + configString = "{}"; + } else { + try { + configString = objectMapper.writeValueAsString(config); + } catch (JsonProcessingException e) { + fail(); + } + } + String executorJson = (new StringBuilder()) + .append("{\"") + .append(providerId) + .append("\":") + .append(configString) + .append("}") + .toString(); + JsonNode node = null; + try { + node = objectMapper.readTree(executorJson); + } catch (JsonProcessingException e) { + fail(); + } + profileRep.getExecutors().add(node); + return this; + } + + public ClientProfileRepresentation toRepresentation() { + return profileRep; + } + + public String toString() { + String profileJson = null; + try { + profileJson = objectMapper.writeValueAsString(profileRep); + } catch (JsonProcessingException e) { + e.printStackTrace(); + fail(); + } + return profileJson; + } } - protected void updatePolicy(ComponentRepresentation policy) { - updateComponent(policy); + // Client Profiles - Executor CRUD Operations + + protected Object createHolderOfKeyEnforceExecutorConfig(Boolean isAugment) { + HolderOfKeyEnforceExecutor.Configuration config = new HolderOfKeyEnforceExecutor.Configuration(); + config.setAugment(isAugment); + return config; } - protected void deletePolicy(String policyName) { - ComponentRepresentation policy = getPolicy(policyName); - List conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS); - List executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS); - conditionIds.stream().forEach(i->adminClient.realm(REALM_NAME).components().component(i).remove()); - executorIds.stream().forEach(i->adminClient.realm(REALM_NAME).components().component(i).remove()); - adminClient.realm(REALM_NAME).components().component(policy.getId()).remove(); + protected Object createPKCEEnforceExecutorConfig(Boolean isAugment) { + PKCEEnforceExecutor.Configuration config = new PKCEEnforceExecutor.Configuration(); + config.setAugment(isAugment); + return config; } - // Condition CRUD operation primitives - - // only create the component - protected String createCondition(String name, String providerId, String subType, Consumer op) { - ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyConditionProvider.class.getName(), subType); - op.accept(component); - return createComponent(component); + protected Object createSecureClientAuthEnforceExecutorConfig(Boolean isAugment, List clientAuthns, String clientAuthnsAugment) { + SecureClientAuthEnforceExecutor.Configuration config = new SecureClientAuthEnforceExecutor.Configuration(); + config.setAugment(isAugment); + config.setClientAuthns(clientAuthns); + config.setClientAuthnsAugment(clientAuthnsAugment); + return config; } - // register created component to the policy - protected void registerCondition(String conditionName, String policyName) { - ComponentRepresentation policy = getPolicy(policyName); - List conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS); - if (conditionIds == null) conditionIds = new ArrayList(); - ComponentRepresentation condition = getCondition(conditionName); - conditionIds.add(condition.getId()); - policy.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditionIds); - updatePolicy(policy); + protected Object createSecureRequestObjectExecutorConfig() { + return new SecureRequestObjectExecutor.Configuration(); } - protected ComponentRepresentation getCondition(String name) { - return getComponent(name, ClientPolicyConditionProvider.class.getName()); + protected Object createSecureResponseTypeExecutorConfig() { + return new SecureResponseTypeExecutor.Configuration(); } - protected void updateCondition(String name, Consumer op) { - ComponentRepresentation condition = getCondition(name); - op.accept(condition); - updateComponent(condition); + protected Object createSecureRedirectUriEnforceExecutorConfig() { + return new SecureRedirectUriEnforceExecutor.Configuration(); } - protected void deleteCondition(String conditionName, String policyName) { - ComponentRepresentation policy = getPolicy(policyName); - List conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS); - ComponentRepresentation condition = getCondition(conditionName); - String conditionId = condition.getId(); - adminClient.realm(REALM_NAME).components().component(conditionId).remove(); - conditionIds.remove(conditionId); - policy.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditionIds); - updatePolicy(policy); + protected Object createSecureSessionEnforceExecutorConfig() { + return new SecureSessionEnforceExecutor.Configuration(); } - // Condition configuration primitives - - protected void setConditionRegistrationMethods(ComponentRepresentation provider, List registrationMethods) { - provider.getConfig().put(ClientUpdateContextConditionFactory.UPDATE_CLIENT_SOURCE, registrationMethods); + protected Object createSecureSigningAlgorithmEnforceExecutorConfig() { + return new SecureSigningAlgorithmEnforceExecutor.Configuration(); } - protected void setConditionClientRoles(ComponentRepresentation provider, List clientRoles) { - provider.getConfig().put(ClientRolesConditionFactory.ROLES, clientRoles); + protected Object createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig() { + return new SecureSigningAlgorithmForSignedJwtEnforceExecutor.Configuration(); } - protected void setConditionClientScopes(ComponentRepresentation provider, List clientScopes) { - provider.getConfig().put(ClientScopesConditionFactory.SCOPES, clientScopes); + // Client Policies CRUD Operation + + protected static class ClientPoliciesBuilder { + private final ClientPoliciesRepresentation policiesRep; + + public ClientPoliciesBuilder() { + policiesRep = new ClientPoliciesRepresentation(); + policiesRep.setPolicies(new ArrayList<>()); + } + + public ClientPoliciesBuilder addPolicy(ClientPolicyRepresentation rep) { + policiesRep.getPolicies().add(rep); + return this; + } + + public ClientPoliciesRepresentation toRepresentation() { + return policiesRep; + } + + public String toString() { + String policiesJson = null; + try { + policiesJson = objectMapper.writeValueAsString(policiesRep); + } catch (JsonProcessingException e) { + e.printStackTrace(); + fail(); + } + return policiesJson; + } } - protected void setConditionClientAccessType(ComponentRepresentation provider, List clientAccessTypes) { - provider.getConfig().put(ClientAccessTypeConditionFactory.TYPE, clientAccessTypes); + protected static class ClientPolicyBuilder { + + private final ClientPolicyRepresentation policyRep; + + public ClientPolicyBuilder() { + policyRep = new ClientPolicyRepresentation(); + } + + public ClientPolicyBuilder createPolicy(String name, String description, Boolean isBuiltin, Boolean isEnabled, List conditions, List profiles) { + policyRep.setName(name); + if (description != null) { + policyRep.setDescription(description); + } + if (isBuiltin != null) { + policyRep.setBuiltin(isBuiltin); + } else { + policyRep.setBuiltin(Boolean.FALSE); + } + if (isEnabled != null) { + policyRep.setEnable(isEnabled); + } else { + policyRep.setEnable(Boolean.FALSE); + } + if (conditions != null) { + policyRep.setConditions(conditions); + } else { + policyRep.setConditions(new ArrayList<>()); + } + if (profiles != null) { + policyRep.setProfiles(profiles); + } else { + policyRep.setProfiles(new ArrayList<>()); + } + return this; + } + + public ClientPolicyBuilder addCondition(String providerId, Object config) { + String configString = null; + if (config == null) { + configString = "{}"; + } else { + try { + configString = objectMapper.writeValueAsString(config); + } catch (JsonProcessingException e) { + fail(); + } + } + String conditionJson = (new StringBuilder()) + .append("{\"") + .append(providerId) + .append("\":") + .append(configString) + .append("}") + .toString(); + JsonNode node = null; + try { + node = objectMapper.readTree(conditionJson); + } catch (JsonProcessingException e) { + fail(); + } + policyRep.getConditions().add(node); + return this; + } + + public ClientPolicyBuilder addProfile(String profileName) { + policyRep.getProfiles().add(profileName); + return this; + } + + public ClientPolicyRepresentation toRepresentation() { + return policyRep; + } + + public String toString() { + String policyJson = null; + try { + policyJson = objectMapper.writeValueAsString(policyRep); + } catch (JsonProcessingException e) { + fail(); + } + return policyJson; + } } - protected void setConditionClientUpdateSourceHosts(ComponentRepresentation provider, List hosts) { - provider.getConfig().putSingle(ClientUpdateSourceHostsConditionFactory.HOST_SENDING_REQUEST_MUST_MATCH, "true"); - provider.getConfig().put(ClientUpdateSourceHostsConditionFactory.TRUSTED_HOSTS, hosts); + // Client Policies - Condition CRUD Operations + + protected Object createTestRaiseExeptionConditionConfig() { + return new TestRaiseExeptionCondition.Configuration(); } - protected void setConditionClientUpdateSourceGroups(ComponentRepresentation provider, List groups) { - provider.getConfig().put(ClientUpdateSourceGroupsConditionFactory.GROUPS, groups); + protected Object createAnyClientConditionConfig() { + return new AnyClientCondition.Configuration(); } - protected void setConditionUpdatingClientSourceRoles(ComponentRepresentation provider, List groups) { - provider.getConfig().put(ClientUpdateSourceRolesConditionFactory.ROLES, groups); + protected Object createAnyClientConditionConfig(Boolean isNegativeLogic) { + AnyClientCondition.Configuration config = new AnyClientCondition.Configuration(); + config.setNegativeLogic(isNegativeLogic); + return config; } - // Executor CRUD operation primitives - - // only create the component - protected String createExecutor(String name, String providerId, String subType, Consumer op) { - ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyExecutorProvider.class.getName(), subType); - op.accept(component); - return createComponent(component); + protected Object createClientAccessTypeConditionConfig(List types) { + ClientAccessTypeCondition.Configuration config = new ClientAccessTypeCondition.Configuration(); + config.setType(types); + return config; } - // register created component to the policy - protected void registerExecutor(String executorName, String policyName) { - ComponentRepresentation policy = getPolicy(policyName); - List executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS); - if (executorIds == null) executorIds = new ArrayList(); - ComponentRepresentation executor = getExecutor(executorName); - executorIds.add(executor.getId()); - policy.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executorIds); - updatePolicy(policy); + protected Object createClientRolesConditionConfig(List roles) { + ClientRolesCondition.Configuration config = new ClientRolesCondition.Configuration(); + config.setRoles(roles); + return config; } - protected ComponentRepresentation getExecutor(String name) { - return getComponent(name, ClientPolicyExecutorProvider.class.getName()); + protected Object createClientScopesConditionConfig(String type, List scopes) { + ClientScopesCondition.Configuration config = new ClientScopesCondition.Configuration(); + config.setType(type); + config.setScope(scopes); + return config; } - protected void updateExecutor(String name, Consumer op) { - ComponentRepresentation executor = getExecutor(name); - op.accept(executor); - updateComponent(executor); + protected Object createClientUpdateContextConditionConfig(List updateClientSource) { + ClientUpdateContextCondition.Configuration config = new ClientUpdateContextCondition.Configuration(); + config.setUpdateClientSource(updateClientSource); + return config; } - protected void deleteExecutor(String executorName, String policyName) { - ComponentRepresentation policy = getPolicy(policyName); - List executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS); - ComponentRepresentation executor = getExecutor(executorName); - String executorId = executor.getId(); - adminClient.realm(REALM_NAME).components().component(executorId).remove(); - executorIds.remove(executorId); - policy.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executorIds); - updatePolicy(policy); + protected Object createClientUpdateSourceGroupsConditionConfig(List groups) { + ClientUpdateSourceGroupsCondition.Configuration config = new ClientUpdateSourceGroupsCondition.Configuration(); + config.setGroups(groups); + return config; } - // Executor configuration primitives - - protected void setExecutorAugmentActivate(ComponentRepresentation provider) { - provider.getConfig().putSingle("is-augment", Boolean.TRUE.toString()); + protected Object createClientUpdateSourceHostsConditionConfig(List trustedHosts, List hostSendingRequestMustMatch) { + ClientUpdateSourceHostsCondition.Configuration config = new ClientUpdateSourceHostsCondition.Configuration(); + config.setTrustedHosts(trustedHosts); + config.setHostSendingRequestMustMatch(hostSendingRequestMustMatch); + return config; } - protected void setExecutorAugmentDeactivate(ComponentRepresentation provider) { - provider.getConfig().putSingle("is-augment", Boolean.FALSE.toString()); + protected Object createClientUpdateSourceRolesConditionConfig(List roles) { + ClientUpdateSourceRolesCondition.Configuration config = new ClientUpdateSourceRolesCondition.Configuration(); + config.setRoles(roles); + return config; } - protected void setExecutorAcceptedClientAuthMethods(ComponentRepresentation provider, List acceptedClientAuthMethods) { - provider.getConfig().put(SecureClientAuthEnforceExecutorFactory.CLIENT_AUTHNS, acceptedClientAuthMethods); + // Profiles Operation + + protected String convertToProfilesJson(ClientProfilesRepresentation reps) { + String json = null; + try { + json = objectMapper.writeValueAsString(reps); + } catch (JsonProcessingException e) { + fail(); + } + return json; } - protected void setExecutorAugmentedClientAuthMethod(ComponentRepresentation provider, String augmentedClientAuthMethod) { - provider.getConfig().putSingle(SecureClientAuthEnforceExecutorFactory.CLIENT_AUTHNS_AUGMENT, augmentedClientAuthMethod); + protected ClientProfilesRepresentation convertToProfiles(String json) { + ClientProfilesRepresentation reps = null; + try { + reps = JsonSerialization.readValue(json, ClientProfilesRepresentation.class); + } catch (IOException e) { + fail(); + } + return reps; } - // Component on which condition/executor/policy based CRUD operation primitives + protected String getProfilesJson() { + return adminClient.realm(REALM_NAME).clientPoliciesProfilesResource().getProfiles(); + } - private ComponentRepresentation createComponentInstance(String name, String providerId, String providerType, String subType) { - ComponentRepresentation rep = new ComponentRepresentation(); - rep.setId(org.keycloak.models.utils.KeycloakModelUtils.generateId()); - rep.setName(name); - rep.setParentId(REALM_NAME); - rep.setProviderId(providerId); - rep.setProviderType(providerType); - rep.setSubType(subType); - rep.setConfig(new MultivaluedHashMap<>()); + protected void updateProfiles(String json) throws ClientPolicyException { + Response resp = adminClient.realm(REALM_NAME).clientPoliciesProfilesResource().updateProfiles(json); + if (resp.getStatus() != 204) { + throw new ClientPolicyException("update profiles failed", resp.getStatusInfo().toString()); + } + } + + protected void updateProfiles(ClientProfilesRepresentation reps) throws ClientPolicyException { + updateProfiles(convertToProfilesJson(reps)); + } + + protected void revertToBuiltinProfiles() throws ClientPolicyException { + updateProfiles("{}"); + } + + protected ClientProfilesRepresentation getProfiles() { + return convertToProfiles(getProfilesJson()); + } + + protected ClientProfilesRepresentation getProfilesWithoutBuiltin() { + ClientProfilesRepresentation reps = new ClientProfilesRepresentation(); + reps.setProfiles(new ArrayList<>()); + ClientProfilesRepresentation repsWithBuiltin = getProfiles(); + repsWithBuiltin.getProfiles().stream().filter(i->!i.isBuiltin()).forEach(j->reps.getProfiles().add(j)); + return reps; + } + + protected String convertToProfileJson(ClientProfileRepresentation rep) { + String json = null; + try { + json = objectMapper.writeValueAsString(rep); + } catch (JsonProcessingException e) { + fail(); + } + return json; + } + + protected ClientProfileRepresentation convertToProfile(String json) { + ClientProfileRepresentation rep = null; + try { + rep = JsonSerialization.readValue(json, ClientProfileRepresentation.class); + } catch (IOException e) { + fail(); + } return rep; } - private String createComponent(ComponentRepresentation cr) { - Response resp = adminClient.realm(REALM_NAME).components().add(cr); - String id = ApiUtil.getCreatedId(resp); - resp.close(); - // registered components will be removed automatically when a test method finishes regardless of its success or failure. - testContext.getOrCreateCleanup(REALM_NAME).addComponentId(id); - return id; + protected ClientProfileRepresentation getProfile(String name) { + if (name == null) return null; + + ClientProfilesRepresentation reps = getProfiles(); + if (reps == null || reps.getProfiles() == null) return null; + + if (reps.getProfiles().stream().anyMatch(i->name.equals(i.getName()))) { + return reps.getProfiles().stream().filter(i->name.equals(i.getName())).collect(Collectors.toList()).get(0); + } else { + return null; + } } - private ComponentRepresentation getComponent(String name, String providerType) { - return adminClient.realm(REALM_NAME).components().query(null, providerType, name).get(0); + protected String getProfileJson(String name) { + return convertToProfileJson(getProfile(name)); } - private void updateComponent(ComponentRepresentation cr) { - adminClient.realm(REALM_NAME).components().component(cr.getId()).update(cr); + protected void addProfile(ClientProfileRepresentation profileRep) throws ClientPolicyException { + ClientProfilesRepresentation reps = getProfilesWithoutBuiltin(); + if (reps == null || reps.getProfiles() == null) return; + reps.getProfiles().add(profileRep); + updateProfiles(convertToProfilesJson(reps)); + return; } - private void deleteComponent(String id) { - adminClient.realm(REALM_NAME).components().component(id).remove(); + protected void updateProfile(ClientProfileRepresentation profileRep) throws ClientPolicyException { + if (profileRep == null || profileRep.getName() == null) return; + String profileName = profileRep.getName(); + + ClientProfilesRepresentation reps = getProfilesWithoutBuiltin(); + + if (reps.getProfiles().stream().anyMatch(i->profileName.equals(i.getName()))) { + ClientProfileRepresentation rep = reps.getProfiles().stream().filter(i->profileName.equals(i.getName())).collect(Collectors.toList()).get(0); + reps.getProfiles().remove(rep); + reps.getProfiles().add(profileRep); + updateProfiles(convertToProfilesJson(reps)); + } else { + return; + } } + + protected void deleteProfile(String profileName) throws ClientPolicyException { + if (profileName == null) return; + + ClientProfilesRepresentation reps = getProfilesWithoutBuiltin(); + + if (reps.getProfiles().stream().anyMatch(i->profileName.equals(i.getName()))) { + ClientProfileRepresentation rep = reps.getProfiles().stream().filter(i->profileName.equals(i.getName())).collect(Collectors.toList()).get(0); + reps.getProfiles().remove(rep); + updateProfiles(convertToProfilesJson(reps)); + } else { + return; + } + } + + // Policies Operation + + protected String convertToPoliciesJson(ClientPoliciesRepresentation reps) { + String json = null; + try { + json = objectMapper.writeValueAsString(reps); + } catch (JsonProcessingException e) { + fail(); + } + return json; + } + + protected ClientPoliciesRepresentation convertToPolicies(String json) { + ClientPoliciesRepresentation reps = null; + try { + reps = JsonSerialization.readValue(json, ClientPoliciesRepresentation.class); + } catch (IOException e) { + fail(); + } + return reps; + } + + protected String getPoliciesJson() { + return adminClient.realm(REALM_NAME).clientPoliciesPoliciesResource().getPolicies(); + } + + protected void updatePolicies(String json) throws ClientPolicyException { + Response resp = adminClient.realm(REALM_NAME).clientPoliciesPoliciesResource().updatePolicies(json); + if (resp.getStatus() != 204) { + throw new ClientPolicyException("update profiles failed", resp.getStatusInfo().toString()); + } + } + + protected void updatePolicies(ClientPoliciesRepresentation reps) throws ClientPolicyException { + updatePolicies(convertToPoliciesJson(reps)); + } + + protected void revertToBuiltinPolicies() throws ClientPolicyException { + updatePolicies("{}"); + } + + protected ClientPoliciesRepresentation getPolicies() { + return convertToPolicies(getPoliciesJson()); + } + + protected ClientPoliciesRepresentation getPoliciesWithoutBuiltin() { + ClientPoliciesRepresentation reps = new ClientPoliciesRepresentation(); + reps.setPolicies(new ArrayList<>()); + ClientPoliciesRepresentation repsWithBuiltin = getPolicies(); + repsWithBuiltin.getPolicies().stream().filter(i->!i.isBuiltin()).forEach(j->reps.getPolicies().add(j)); + return reps; + } + + protected String convertToPolicyJson(ClientPolicyRepresentation rep) { + String json = null; + try { + json = objectMapper.writeValueAsString(rep); + } catch (JsonProcessingException e) { + fail(); + } + return json; + } + + protected ClientPolicyRepresentation convertToPolicy(String json) { + ClientPolicyRepresentation rep = null; + try { + rep = JsonSerialization.readValue(json, ClientPolicyRepresentation.class); + } catch (IOException e) { + fail(); + } + return rep; + } + + protected ClientPolicyRepresentation getPolicy(String name) { + if (name == null) return null; + + ClientPoliciesRepresentation reps = getPolicies(); + if (reps == null || reps.getPolicies() == null) return null; + + if (reps.getPolicies().stream().anyMatch(i->name.equals(i.getName()))) { + return reps.getPolicies().stream().filter(i->name.equals(i.getName())).collect(Collectors.toList()).get(0); + } else { + return null; + } + } + + protected String getPolicyJson(String name) { + return convertToPolicyJson(getPolicy(name)); + } + + protected void addPolicy(ClientPolicyRepresentation policyRep) throws ClientPolicyException { + ClientPoliciesRepresentation reps = getPoliciesWithoutBuiltin(); + if (reps == null || reps.getPolicies() == null) return; + reps.getPolicies().add(policyRep); + updatePolicies(convertToPoliciesJson(reps)); + return; + } + + protected void updatePolicy(ClientPolicyRepresentation policyRep) throws ClientPolicyException { + if (policyRep == null || policyRep.getName() == null) return; + String policyName = policyRep.getName(); + + ClientPoliciesRepresentation reps = getPoliciesWithoutBuiltin(); + + if (reps.getPolicies().stream().anyMatch(i->policyName.equals(i.getName()))) { + ClientPolicyRepresentation rep = reps.getPolicies().stream().filter(i->policyName.equals(i.getName())).collect(Collectors.toList()).get(0); + reps.getPolicies().remove(rep); + reps.getPolicies().add(policyRep); + updatePolicies(convertToPoliciesJson(reps)); + } else { + return; + } + } + + protected void deletePolicy(String policyName) throws ClientPolicyException { + if (policyName == null) return; + + ClientPoliciesRepresentation reps = getPoliciesWithoutBuiltin(); + + if (reps.getPolicies().stream().anyMatch(i->policyName.equals(i.getName()))) { + ClientPolicyRepresentation rep = reps.getPolicies().stream().filter(i->policyName.equals(i.getName())).collect(Collectors.toList()).get(0); + reps.getPolicies().remove(rep); + updatePolicies(convertToPoliciesJson(reps)); + } else { + return; + } + } + + // Assertions about profiles + + // profiles + + protected ClientProfilesRepresentation getProfilesRepresentation(String json) { + return getCompoundsRepresentation(json, ClientProfilesRepresentation.class); + } + + // profile + + protected ClientProfileRepresentation getProfileRepresentation(ClientProfilesRepresentation profilesRep, String name) { + return getCompoundRepresentation(profilesRep, name, (ClientProfilesRepresentation i)->i.getProfiles(), (ClientProfileRepresentation i)->i.getName()); + } + + protected void assertExpectedProfiles(ClientProfilesRepresentation profilesRep, List expectedProfiles) { + assertExpetedCompounds(expectedProfiles, profilesRep, (ClientProfilesRepresentation i)->i.getProfiles(), (ClientProfileRepresentation i)->i.getName()); + } + + protected void assertExpectedProfile(ClientProfileRepresentation actualProfileRep, String name, String description, boolean isBuiltin) { + assertNotNull(actualProfileRep); + assertEquals(description, actualProfileRep.getDescription()); + assertEquals(isBuiltin, actualProfileRep.isBuiltin()); + } + + // executors + + protected void assertExpectedExecutors(List expectedExecutors, ClientProfileRepresentation profileRep) { + assertExpetedElement(expectedExecutors, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + } + + protected void assertExpectedHolderOfKeyEnforceExecutor(boolean isAugment, ClientProfileRepresentation profileRep) { + assertExpectedAugmenedExecutor(isAugment, HolderOfKeyEnforceExecutorFactory.PROVIDER_ID, profileRep); + } + + protected void assertExpectedPKCEEnforceExecutor(boolean isAugment, ClientProfileRepresentation profileRep) { + assertExpectedAugmenedExecutor(isAugment, PKCEEnforceExecutorFactory.PROVIDER_ID, profileRep); + } + + protected void assertExpectedSecureClientAuthEnforceExecutor(List clientAuthns, boolean isAugment, String clientAuthnsAugment, ClientProfileRepresentation profileRep) { + JsonNode actualExecutorConfig = assertExpectedAugmenedExecutor(isAugment, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, profileRep); + + Set actualClientAuthns = new HashSet<>(); + if (actualExecutorConfig.findValue("client-authns") != null) actualExecutorConfig.findValue("client-authns").elements().forEachRemaining(i->actualClientAuthns.add(i.asText())); + assertEquals(new HashSet<>(clientAuthns), actualClientAuthns); + + String actualClientAuthnAugment = null; + if (actualExecutorConfig.findValue("client-authns-augment") != null) actualClientAuthnAugment = actualExecutorConfig.findValue("client-authns-augment").asText(); + assertEquals(clientAuthnsAugment, actualClientAuthnAugment); + } + + protected void assertExpectedSecureRedirectUriEnforceExecutor(ClientProfileRepresentation profileRep) { + assertExpectedNoConfigElement(SecureRedirectUriEnforceExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + } + + protected void assertExpectedSecureRequestObjectExecutor(ClientProfileRepresentation profileRep) { + assertExpectedNoConfigElement(SecureRequestObjectExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + } + + protected void assertExpectedSecureResponseTypeExecutor(ClientProfileRepresentation profileRep) { + assertExpectedNoConfigElement(SecureResponseTypeExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + } + + protected void assertExpectedSecureSessionEnforceExecutor(ClientProfileRepresentation profileRep) { + assertExpectedNoConfigElement(SecureSessionEnforceExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + } + + protected void assertExpectedSecureSigningAlgorithmEnforceExecutor(ClientProfileRepresentation profileRep) { + assertExpectedNoConfigElement(SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + } + + protected void assertExpectedSecureSigningAlgorithmForSignedJwtEnforceExecutor(ClientProfileRepresentation profileRep) { + assertExpectedNoConfigElement(SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, profileRep, (ClientProfileRepresentation i)->i.getExecutors()); + } + + protected JsonNode assertExpectedAugmenedExecutor(boolean isAugment, String providerId, ClientProfileRepresentation profileRep) { + assertNotNull(profileRep); + JsonNode actualExecutorConfig = getConfig(profileRep.getExecutors(), providerId); + assertNotNull(actualExecutorConfig); + + boolean actualIsAugment = false; + if (actualExecutorConfig.findValue("is-augment") != null) actualIsAugment = actualExecutorConfig.findValue("is-augment").asBoolean(); + assertEquals(isAugment, actualIsAugment); + + return actualExecutorConfig; + } + + // Assertions about policies + + // policies + + protected ClientPoliciesRepresentation getPoliciesRepresentation(String json) { + return getCompoundsRepresentation(json, ClientPoliciesRepresentation.class); + } + + // policy + + protected ClientPolicyRepresentation getPolicyRepresentation(ClientPoliciesRepresentation policiesRep, String name) { + return getCompoundRepresentation(policiesRep, name, (ClientPoliciesRepresentation i)->i.getPolicies(), (ClientPolicyRepresentation i)->i.getName()); + } + + protected void assertExpectedPolicies(List expectedPolicies, ClientPoliciesRepresentation policiesRep) { + assertNotNull(policiesRep); + List reps = policiesRep.getPolicies(); + if (reps == null) { + assertNull(expectedPolicies); + return; + } + Set actualPolicies = reps.stream().map(i->i.getName()).collect(Collectors.toSet()); + assertEquals(new HashSet<>(expectedPolicies), actualPolicies); + } + + protected void assertExpectedPolicy(String name, String description, boolean isBuiltin, boolean isEnabled, List profiles, ClientPolicyRepresentation actualPolicyRep) { + assertNotNull(actualPolicyRep); + assertEquals(description, actualPolicyRep.getDescription()); + assertEquals(isBuiltin, actualPolicyRep.isBuiltin()); + assertEquals(isEnabled, actualPolicyRep.isEnable()); + assertEquals(new HashSet<>(profiles), new HashSet<>(actualPolicyRep.getProfiles())); + } + + // conditions + + protected void assertExpectedConditions(List expectedConditions, ClientPolicyRepresentation policyRep) { + assertExpetedElement(expectedConditions, policyRep, (ClientPolicyRepresentation i)->i.getConditions()); + } + + protected void assertExpectedAnyClientCondition(ClientPolicyRepresentation profileRep) { + assertExpectedNoConfigElement(AnyClientConditionFactory.PROVIDER_ID, profileRep, (ClientPolicyRepresentation i)->i.getConditions()); + } + + protected void assertExpectedClientAccessTypeCondition(List type, ClientPolicyRepresentation policyRep) { + JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientAccessTypeConditionFactory.PROVIDER_ID); + + Set actualTypes = new HashSet<>(); + if (actualConditionConfig.findValue("type") != null) + actualConditionConfig.findValue("type").elements().forEachRemaining(i->actualTypes.add(i.asText())); + assertEquals(new HashSet<>(type), actualTypes); + } + + protected void assertExpectedClientRolesCondition(List roles, ClientPolicyRepresentation policyRep) { + JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientRolesConditionFactory.PROVIDER_ID); + + Set actualRoles = new HashSet<>(); + if (actualConditionConfig.findValue("roles") != null) + actualConditionConfig.findValue("roles").elements().forEachRemaining(i->actualRoles.add(i.asText())); + assertEquals(new HashSet<>(roles), actualRoles); + } + + protected void assertExpectedClientScopesCondition(String type, List scopes, ClientPolicyRepresentation policyRep) { + JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientScopesConditionFactory.PROVIDER_ID); + + String actualType = null; + if (actualConditionConfig.findValue("type") != null) actualType = actualConditionConfig.findValue("type").asText(); + assertEquals(type, actualType); + + Set actualScopes = new HashSet<>(); + if (actualConditionConfig.findValue("scope") != null) + actualConditionConfig.findValue("scope").elements().forEachRemaining(i->actualScopes.add(i.asText())); + assertEquals(new HashSet<>(scopes), actualScopes); + } + + protected void assertExpectedClientUpdateContextCondition(List updateClientSources, ClientPolicyRepresentation policyRep) { + JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientUpdateContextConditionFactory.PROVIDER_ID); + + Set actualUpdateClientSources = new HashSet<>(); + if (actualConditionConfig.findValue("update-client-source") != null) + actualConditionConfig.findValue("update-client-source").elements().forEachRemaining(i->actualUpdateClientSources.add(i.asText())); + assertEquals(new HashSet<>(updateClientSources), actualUpdateClientSources); + } + + protected void assertExpectedClientUpdateSourceGroupsCondition(List groups, ClientPolicyRepresentation policyRep) { + JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID); + + Set actualGroups = new HashSet<>(); + if (actualConditionConfig.findValue("groups") != null) + actualConditionConfig.findValue("groups").elements().forEachRemaining(i->actualGroups.add(i.asText())); + assertEquals(new HashSet<>(groups), actualGroups); + } + + protected void assertExpectedClientUpdateSourceHostsCondition(List trustedHosts, List hostSendingRequestMustMatch, ClientPolicyRepresentation policyRep) { + JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientUpdateSourceHostsConditionFactory.PROVIDER_ID); + + List actualTrustedHosts = new ArrayList<>(); + if (actualConditionConfig.findValue("trusted-hosts") != null) + actualConditionConfig.findValue("trusted-hosts").elements().forEachRemaining(i->actualTrustedHosts.add(i.asText())); + assertEquals(trustedHosts, actualTrustedHosts); + + List actualHostSendingRequestMustMatch = new ArrayList<>(); + if (actualConditionConfig.findValue("host-sending-request-must-match") != null) + actualConditionConfig.findValue("host-sending-request-must-match").elements().forEachRemaining(i->actualHostSendingRequestMustMatch.add(i.asBoolean())); + assertEquals(trustedHosts, actualTrustedHosts); + } + + protected void assertExpectedClientUpdateSourceRolesCondition(List roles, ClientPolicyRepresentation policyRep) { + JsonNode actualConditionConfig = getConfig(policyRep.getConditions(), ClientUpdateSourceRolesConditionFactory.PROVIDER_ID); + + Set actualRoles = new HashSet<>(); + if (actualConditionConfig.findValue("roles") != null) + actualConditionConfig.findValue("roles").elements().forEachRemaining(i->actualRoles.add(i.asText())); + assertEquals(new HashSet<>(roles), actualRoles); + } + + // profiles/policies common (compounds) + + private T getCompoundsRepresentation(String json, Class clazz) { + T rep = null; + try { + rep = JsonSerialization.readValue(json, clazz); + } catch (IOException ioe) { + fail(); + } + return rep; + } + + private void assertExpetedCompounds(List expected, R rep, Function> f, Function g) { + assertNotNull(rep); + List reps = f.apply(rep); + if (reps == null) { + assertNull(expected); + return; + } + Set actual = reps.stream().map(i->g.apply(i)).collect(Collectors.toSet()); + assertEquals(new HashSet<>(expected), actual); + } + + // profile/policy common (compound) + + private T getCompoundRepresentation(R rep, String name, Function> f, Function g) { + assertNotNull(rep); + if (f.apply(rep) == null) return null; + List reps = f.apply(rep).stream().filter(i->g.apply(i).equals(name)).collect(Collectors.toList()); + if (reps == null) return null; + if (reps.size() != 1) return null; + return reps.get(0); + } + + // condition/executor common (element) + + private void assertExpetedElement(List expected, T rep, Function> f) { + assertNotNull(rep); + List objs = f.apply(rep); + if (objs == null) { + assertNull(expected); + return; + } + Set actual = objs.stream().map(i->{ + JsonNode node = objectMapper.convertValue(i, JsonNode.class); + return node.fieldNames().next(); + }).collect(Collectors.toSet()); + assertEquals(new HashSet<>(expected), actual); + } + + private void assertExpectedNoConfigElement(String providerId, T rep, Function> f) { + assertNotNull(rep); + JsonNode actualConfig = getConfig(f.apply(rep), providerId); + assertEquals("", actualConfig.asText()); + } + + private JsonNode getConfig(List objs, String providerId) { + List nodes = objs.stream().map(i->objectMapper.convertValue(i, JsonNode.class)) + .filter(j->j.fieldNames().next().equals(providerId)).collect(Collectors.toList()); + if (nodes == null || nodes.size() != 1) return null; + return nodes.get(0); + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesImportExportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesImportExportTest.java new file mode 100644 index 0000000000..652c633234 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesImportExportTest.java @@ -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 Takashi Norimatsu + */ +@EnableFeature(value = Profile.Feature.CLIENT_POLICIES, skipRestart = true) +@AuthServerContainerExclude({REMOTE}) +public class ClientPoliciesImportExportTest extends AbstractClientPoliciesTest { + + @Override + public void addTestRealms(List 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); + }); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesLoadUpdateTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesLoadUpdateTest.java new file mode 100644 index 0000000000..e69a2125ac --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesLoadUpdateTest.java @@ -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 Takashi Norimatsu + */ +@EnableFeature(value = Profile.Feature.CLIENT_POLICIES, skipRestart = true) +@AuthServerContainerExclude({REMOTE}) +public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest { + + @Override + public void addTestRealms(List 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(); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java index e49b8218ac..aa59f57b63 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientPoliciesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Red Hat, Inc. and/or its affiliates + * Copyright 2021 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +32,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; -import java.util.stream.Collectors; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; @@ -45,6 +44,7 @@ import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.RolesResource; import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator; @@ -65,7 +65,6 @@ import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.representations.AccessToken; import org.keycloak.representations.RefreshToken; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -74,26 +73,24 @@ import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.representations.oidc.TokenMetadataRepresentation; import org.keycloak.services.clientpolicy.ClientPolicyEvent; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.DefaultClientPolicyProviderFactory; -import org.keycloak.services.clientpolicy.condition.AbstractClientPolicyConditionProviderFactory; 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.condition.ClientScopesConditionFactory; import org.keycloak.services.clientpolicy.condition.ClientUpdateContextConditionFactory; import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceGroupsConditionFactory; import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceHostsConditionFactory; import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceRolesConditionFactory; -import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory; -import org.keycloak.services.clientpolicy.condition.ClientScopesConditionFactory; import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutorFactory; import org.keycloak.services.clientpolicy.executor.SecureClientAuthEnforceExecutorFactory; import org.keycloak.services.clientpolicy.executor.SecureRedirectUriEnforceExecutorFactory; -import org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutorFactory; -import org.keycloak.services.clientpolicy.executor.SecureRequestObjectExecutor; import org.keycloak.services.clientpolicy.executor.SecureRequestObjectExecutorFactory; import org.keycloak.services.clientpolicy.executor.SecureResponseTypeExecutorFactory; import org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutorFactory; import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmEnforceExecutorFactory; import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory; +import org.keycloak.services.clientpolicy.executor.SecureRequestObjectExecutor; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.EnableFeature; @@ -105,21 +102,19 @@ import org.keycloak.testsuite.services.clientpolicy.executor.TestRaiseExeptionEx import org.keycloak.testsuite.updaters.ClientAttributeUpdater; import org.keycloak.testsuite.util.MutualTLSUtils; import org.keycloak.testsuite.util.OAuthClient; - -import org.keycloak.admin.client.resource.RolesResource; import org.keycloak.testsuite.util.RoleBuilder; - import org.keycloak.testsuite.util.ServerURLs; import org.keycloak.util.JsonSerialization; +/** + * @author Takashi Norimatsu + */ @EnableFeature(value = Profile.Feature.CLIENT_POLICIES, skipRestart = true) public class ClientPoliciesTest extends AbstractClientPoliciesTest { private static final Logger logger = Logger.getLogger(ClientPoliciesTest.class); - private static final String POLICY_NAME = "MyPolicy"; private static final String CLIENT_NAME = "Zahlungs-App"; - private static final String SAMPLE_CLIENT_ROLE = "sample-client-role"; private static final String TEST_USER_NAME = "test-user@localhost"; private static final String TEST_USER_PASSWORD = "password"; @@ -158,20 +153,20 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testAdminClientRegisterUnacceptableAuthType() { + public void testAdminClientRegisterUnacceptableAuthType() throws Exception { setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); try { createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID); }); fail(); - } catch (ClientPolicyException cpe) { - assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, cpe.getError()); + } catch (ClientPolicyException e) { + assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage()); } } @Test - public void testAdminClientRegisterAcceptableAuthType() throws ClientPolicyException { + public void testAdminClientRegisterAcceptableAuthType() throws Exception { setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); @@ -180,18 +175,18 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testAdminClientRegisterDefaultAuthType() { + public void testAdminClientRegisterDefaultAuthType() throws Exception { setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); try { createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {}); fail(); - } catch (ClientPolicyException cpe) { - assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, cpe.getError()); + } catch (ClientPolicyException e) { + assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage()); } } @Test - public void testAdminClientUpdateUnacceptableAuthType() throws ClientPolicyException { + public void testAdminClientUpdateUnacceptableAuthType() throws Exception { setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); @@ -209,7 +204,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testAdminClientUpdateAcceptableAuthType() throws ClientPolicyException { + public void testAdminClientUpdateAcceptableAuthType() throws Exception { setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { @@ -225,7 +220,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testAdminClientUpdateDefaultAuthType() throws ClientPolicyException { + public void testAdminClientUpdateDefaultAuthType() throws Exception { setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { @@ -242,13 +237,27 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testAdminClientAugmentedAuthType() throws ClientPolicyException { - setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); + public void testAdminClientAugmentedAuthType() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Pershyy Profil", Boolean.FALSE, null) + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + createSecureClientAuthEnforceExecutorConfig(Boolean.TRUE, + Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID), + X509ClientAuthenticator.PROVIDER_ID)) + .toRepresentation() + ).toString(); + updateProfiles(json); - updateExecutor(SecureClientAuthEnforceExecutor_NAME, (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - setExecutorAugmentedClientAuthMethod(provider, X509ClientAuthenticator.PROVIDER_ID); - }); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Persha Polityka", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, + createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); String cId = createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> { clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID); @@ -256,18 +265,25 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { assertEquals(X509ClientAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); - updateExecutor(SecureClientAuthEnforceExecutor_NAME, (ComponentRepresentation provider) -> { - setExecutorAugmentedClientAuthMethod(provider, JWTClientAuthenticator.PROVIDER_ID); - }); + // update profiles + json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Pershyy Profil", Boolean.FALSE, null) + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + createSecureClientAuthEnforceExecutorConfig(Boolean.TRUE, + Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID), + JWTClientAuthenticator.PROVIDER_ID)) + .toRepresentation() + ).toString(); + updateProfiles(json); - updateClientByAdmin(cId, (ClientRepresentation clientRep) -> { - clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); - }); - assertEquals(JWTClientAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); + updateClientByAdmin(cId, (ClientRepresentation clientRep) -> { + clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); + }); + assertEquals(JWTClientAuthenticator.PROVIDER_ID, getClientByAdmin(cId).getClientAuthenticatorType()); } @Test - public void testDynamicClientRegisterAndUpdate() throws ClientRegistrationException { + public void testDynamicClientRegisterAndUpdate() throws Exception { setupPolicyClientIdAndSecretNotAcceptableAuthType(POLICY_NAME); String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {}); @@ -283,7 +299,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testCreateDeletePolicyRuntime() throws ClientRegistrationException { + public void testCreateDeletePolicyRuntime() throws Exception { String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {}); OIDCClientRepresentation clientRep = getClientDynamically(clientId); assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, clientRep.getTokenEndpointAuthMethod()); @@ -304,17 +320,15 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testCreateUpdateDeleteConditionRuntime() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - String executorName = PKCEEnforceExecutor_NAME; - createExecutor(executorName, PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - }); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + public void testCreateUpdateDeleteConditionRuntime() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Eichte profil", Boolean.FALSE, null) + .addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID, + createPKCEEnforceExecutorConfig(Boolean.TRUE)) + .toRepresentation() + ).toString(); + updateProfiles(json); String clientId = generateSuffixedName(CLIENT_NAME); String clientSecret = "secret"; @@ -324,46 +338,57 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(SAMPLE_CLIENT_ROLE).build()); successfulLoginAndLogout(clientId, clientSecret); - - String conditionName = ClientRolesCondition_NAME; - createCondition(conditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, Arrays.asList(SAMPLE_CLIENT_ROLE)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); + + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Eischt Politik", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); failLoginByNotFollowingPKCE(clientId); - updateCondition(conditionName, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, Arrays.asList("anothor-client-role")); - }); + // update policies + updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Aktualiseiert Eischt Politik", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList("anothor-client-role"))) + .addProfile(PROFILE_NAME) + .toRepresentation()); successfulLoginAndLogout(clientId, clientSecret); - deleteCondition(conditionName, policyName); + // update policies + updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Dei Aktualiseiert Eischt Politik", Boolean.FALSE, Boolean.TRUE, null, null) + .addProfile(PROFILE_NAME) + .toRepresentation()); successfulLoginAndLogout(clientId, clientSecret); } @Test - public void testCreateUpdateDeleteExecutorRuntime() throws ClientRegistrationException, ClientPolicyException { - String policyName = generateSuffixedName(CLIENT_NAME); - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testCreateUpdateDeleteExecutorRuntime() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Purofairu Sono Ichi", Boolean.FALSE, null) + .addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID, + createPKCEEnforceExecutorConfig(Boolean.FALSE)) + .toRepresentation() + ).toString(); + updateProfiles(json); - String roleConditionName = ClientRolesCondition_NAME; - createCondition(roleConditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, Arrays.asList(SAMPLE_CLIENT_ROLE)); - }); - registerCondition(roleConditionName, policyName); - logger.info("... Registered Condition : " + roleConditionName); - - String updateConditionName = ClientUpdateContextCondition_NAME; - createCondition(updateConditionName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER)); - }); - registerCondition(updateConditionName, policyName); - logger.info("... Registered Condition : " + updateConditionName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Porishii Sono Ichi", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, + createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))) + .toRepresentation() + ).toString(); + updatePolicies(json); String clientId = generateSuffixedName(CLIENT_NAME); String clientSecret = "secret"; @@ -374,18 +399,23 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { successfulLoginAndLogout(clientId, clientSecret); - String executorName = PKCEEnforceExecutor_NAME; - createExecutor(executorName, PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAugmentDeactivate(provider); - }); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // update policies + updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Koushinsareta Porishii Sono Ichi", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, + createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))) + .addProfile(PROFILE_NAME) + .toRepresentation()); failLoginByNotFollowingPKCE(clientId); - updateExecutor(executorName, (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - }); + // update profiles + updateProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Koushinsareta Purofairu Sono Ichi", Boolean.FALSE, null) + .addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID, + createPKCEEnforceExecutorConfig(Boolean.TRUE)) + .toRepresentation()); updateClientByAdmin(cid, (ClientRepresentation clientRep) -> { clientRep.setServiceAccountsEnabled(Boolean.FALSE); @@ -393,8 +423,9 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { assertEquals(false, getClientByAdmin(cid).isServiceAccountsEnabled()); assertEquals(OAuth2Constants.PKCE_METHOD_S256, OIDCAdvancedConfigWrapper.fromClientRepresentation(getClientByAdmin(cid)).getPkceCodeChallengeMethod()); - deleteExecutor(executorName, policyName); - logger.info("... Deleted Executor : " + executorName); + // update profiles + updateProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Sarani Koushinsareta Purofairu Sono Ichi", Boolean.FALSE, null).toRepresentation()); updateClientByAdmin(cid, (ClientRepresentation clientRep) -> { OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setPkceCodeChallengeMethod(null); @@ -423,56 +454,45 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testMultiplePolicies() throws ClientRegistrationException, ClientPolicyException { + public void testMultiplePolicies() throws Exception { String roleAlphaName = "sample-client-role-alpha"; String roleBetaName = "sample-client-role-beta"; String roleZetaName = "sample-client-role-zeta"; String roleCommonName = "sample-client-role-common"; + // register profiles + String profileAlphaName = "MyProfile-alpha"; + String profileBetaName = "MyProfile-beta"; + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(profileAlphaName, "Pierwszy Profil", Boolean.FALSE, null) + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + createSecureClientAuthEnforceExecutorConfig(Boolean.TRUE, Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID), ClientIdAndSecretAuthenticator.PROVIDER_ID)) + .toRepresentation()).addProfile( + (new ClientProfileBuilder()).createProfile(profileBetaName, "Drugi Profil", Boolean.FALSE, null) + .addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID, + createPKCEEnforceExecutorConfig(Boolean.TRUE)) + .toRepresentation() + ).toString(); + updateProfiles(json); + + // register policies String policyAlphaName = "MyPolicy-alpha"; - createPolicy(policyAlphaName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyAlphaName); - - String roleConditionAlphaName = generateSuffixedName(ClientRolesCondition_NAME); - createCondition(roleConditionAlphaName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, Arrays.asList(roleAlphaName, roleZetaName)); - }); - registerCondition(roleConditionAlphaName, policyAlphaName); - logger.info("... Registered Condition : " + roleConditionAlphaName); - - String updateConditionAlphaName = generateSuffixedName(ClientUpdateContextCondition_NAME); - createCondition(updateConditionAlphaName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER)); - }); - registerCondition(updateConditionAlphaName, policyAlphaName); - logger.info("... Registered Condition : " + updateConditionAlphaName); - - String clientAuthExecutorAlphaName = generateSuffixedName(SecureClientAuthEnforceExecutor_NAME); - createExecutor(clientAuthExecutorAlphaName, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider, Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID)); - setExecutorAugmentActivate(provider); - setExecutorAugmentedClientAuthMethod(provider, ClientIdAndSecretAuthenticator.PROVIDER_ID); - }); - registerExecutor(clientAuthExecutorAlphaName, policyAlphaName); - logger.info("... Registered Executor : " + clientAuthExecutorAlphaName); - String policyBetaName = "MyPolicy-beta"; - createPolicy(policyBetaName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyBetaName); - - String roleConditionBetaName = generateSuffixedName(ClientRolesCondition_NAME); - createCondition(roleConditionBetaName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, Arrays.asList(roleBetaName, roleZetaName)); - }); - registerCondition(roleConditionBetaName, policyBetaName); - logger.info("... Registered Condition : " + roleConditionBetaName); - - String pkceExecutorBetaName = generateSuffixedName(PKCEEnforceExecutor_NAME); - createExecutor(pkceExecutorBetaName, PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - }); - registerExecutor(pkceExecutorBetaName, policyBetaName); - logger.info("... Registered Executor : " + pkceExecutorBetaName); + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(policyAlphaName, "Pierwsza Zasada", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName))) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, + createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))) + .addProfile(profileAlphaName) + .toRepresentation()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(policyBetaName, "Drugi Zasada", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(roleBetaName, roleZetaName))) + .addProfile(profileBetaName) + .toRepresentation() + ).toString(); + updatePolicies(json); String clientAlphaId = generateSuffixedName("Alpha-App"); String clientAlphaSecret = "secretAlpha"; @@ -498,39 +518,44 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testIntentionalExceptionOnCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - String conditionName = TestRaiseExeptionCondition_NAME; - createCondition(conditionName, TestRaiseExeptionConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); + public void testIntentionalExceptionOnCondition() throws Exception { + // register policies + String json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Fyrsta Stefnan", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(TestRaiseExeptionConditionFactory.PROVIDER_ID, + createTestRaiseExeptionConditionConfig()) + .toRepresentation() + ).toString(); + updatePolicies(json); try { createClientByAdmin(generateSuffixedName(CLIENT_NAME), (ClientRepresentation clientRep) -> {}); fail(); - } catch (ClientPolicyException cpe) { - assertEquals(OAuthErrorException.SERVER_ERROR, cpe.getError()); + } catch (ClientPolicyException e) { + assertEquals(OAuthErrorException.SERVER_ERROR, e.getMessage()); } } @Test - public void testAnyClientCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testAnyClientCondition() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil", Boolean.FALSE, null) + .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, + createSecureSessionEnforceExecutorConfig()) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = AnyClientCondition_NAME; - createCondition(conditionName, AnyClientConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureSessionEnforceExecutor_NAME; - createExecutor(executorName, SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(AnyClientConditionFactory.PROVIDER_ID, + createAnyClientConditionConfig()) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); String clientAlphaId = generateSuffixedName("Alpha-App"); String clientAlphaSecret = "secretAlpha"; @@ -554,46 +579,38 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testConditionWithoutNoConfiguration() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy-ClientAccessTypeCondition"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - String conditionName = ClientAccessTypeCondition_NAME; - createCondition(conditionName, ClientAccessTypeConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); + public void testConditionWithoutNoConfiguration() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Die Erste Politik", Boolean.FALSE, null) + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null) + .toRepresentation() + ).toString(); + updateProfiles(json); - policyName = "MyPolicy-ClientUpdateSourceGroupsCondition"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - conditionName = ClientUpdateSourceGroupsCondition_NAME; - createCondition(conditionName, ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - policyName = "MyPolicy-ClientUpdateSourceRolesCondition"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - conditionName = ClientUpdateSourceRolesCondition_NAME; - createCondition(conditionName, ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - policyName = "MyPolicy-ClientUpdateContextCondition"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - conditionName = ClientUpdateContextCondition_NAME; - createCondition(conditionName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - policyName = "MyPolicy-SecureSessionEnforceExecutor"; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - String executorName = SecureSessionEnforceExecutor_NAME; - createExecutor(executorName, SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientAccessTypeCondition", "Die Erste Politik", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, null) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).addPolicy( + (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateSourceGroupsCondition", "Die Zweite Politik", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, null) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).addPolicy( + (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateSourceRolesCondition", "Die Dritte Politik", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, null) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).addPolicy( + (new ClientPolicyBuilder()).createPolicy("MyPolicy-ClientUpdateContextCondition", "Die Vierte Politik", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, null) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); String clientId = generateSuffixedName(CLIENT_NAME); String clientSecret = "secret"; @@ -606,27 +623,32 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { successfulLoginAndLogout(clientId, clientSecret); } + @AuthServerContainerExclude(AuthServer.REMOTE) @Test - public void testClientUpdateSourceHostsCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testClientUpdateSourceHostsCondition() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvni Profil", Boolean.FALSE, null) + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + createSecureClientAuthEnforceExecutorConfig( + Boolean.FALSE, + Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID), + null) + ) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = ClientUpdateSourceHostsCondition_NAME; - createCondition(conditionName, ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientUpdateSourceHosts(provider, Arrays.asList("localhost", "127.0.0.1")); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureClientAuthEnforceExecutor_NAME; - createExecutor(executorName, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider, Arrays.asList( - JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID)); - }); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prvni Politika", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, + createClientUpdateSourceHostsConditionConfig(Arrays.asList("localhost", "127.0.0.1"), Arrays.asList(Boolean.TRUE, Boolean.TRUE))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); String clientId = generateSuffixedName(CLIENT_NAME); String clientSecret = "secret"; @@ -635,13 +657,20 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { clientRep.setSecret(clientSecret); }); fail(); - } catch (ClientPolicyException cpe) { - assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, cpe.getError()); + } catch (ClientPolicyException e) { + assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage()); } - updateCondition(conditionName, (ComponentRepresentation provider) -> { - setConditionClientUpdateSourceHosts(provider, Arrays.asList("example.com")); - }); + // update policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Aktualizovana Prvni Politika", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateSourceHostsConditionFactory.PROVIDER_ID, + createClientUpdateSourceHostsConditionConfig(Arrays.asList("example.com"), Arrays.asList(Boolean.TRUE))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); + try { createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { clientRep.setSecret(clientSecret); @@ -652,24 +681,29 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testClientUpdateSourceGroupsCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testClientUpdateSourceGroupsCondition() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profil", Boolean.FALSE, null) + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + createSecureClientAuthEnforceExecutorConfig( + Boolean.FALSE, + Arrays.asList(JWTClientAuthenticator.PROVIDER_ID), + null) + ) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = ClientUpdateSourceGroupsCondition_NAME; - createCondition(conditionName, ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientUpdateSourceGroups(provider, Arrays.asList("topGroup")); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureClientAuthEnforceExecutor_NAME; - createExecutor(executorName, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider,Arrays.asList(JWTClientAuthenticator.PROVIDER_ID)); - }); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politik", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateSourceGroupsConditionFactory.PROVIDER_ID, + createClientUpdateSourceGroupsConditionConfig(Arrays.asList("topGroup"))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); try { authCreateClients(); @@ -687,24 +721,29 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testClientUpdateSourceRolesCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testClientUpdateSourceRolesCondition() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Il Primo Profilo", Boolean.FALSE, null) + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + createSecureClientAuthEnforceExecutorConfig( + Boolean.FALSE, + Arrays.asList(JWTClientSecretAuthenticator.PROVIDER_ID), + null) + ) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = ClientUpdateSourceRolesCondition_NAME; - createCondition(conditionName, ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionUpdatingClientSourceRoles(provider, Arrays.asList(AdminRoles.CREATE_CLIENT)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureClientAuthEnforceExecutor_NAME; - createExecutor(executorName, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider, Arrays.asList(JWTClientAuthenticator.PROVIDER_ID)); - }); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Prima Politica", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateSourceRolesConditionFactory.PROVIDER_ID, + createClientUpdateSourceRolesConditionConfig(Arrays.asList(AdminRoles.CREATE_CLIENT))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); try { authCreateClients(); @@ -722,24 +761,25 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testClientScopesCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testClientScopesCondition() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Het Eerste Profiel", Boolean.FALSE, null) + .addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID, + createPKCEEnforceExecutorConfig(Boolean.TRUE)) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = ClientScopesCondition_NAME; - createCondition(conditionName, ClientScopesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientScopes(provider, Arrays.asList("offline_access", "microprofile-jwt")); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = PKCEEnforceExecutor_NAME; - createExecutor(executorName, PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - }); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Het Eerste Beleid", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientScopesConditionFactory.PROVIDER_ID, + createClientScopesConditionConfig(ClientScopesConditionFactory.OPTIONAL, Arrays.asList("offline_access", "microprofile-jwt"))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); String clientId = generateSuffixedName(CLIENT_NAME); String clientSecret = "secret"; @@ -764,22 +804,25 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testClientAccessTypeCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testClientAccessTypeCondition() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "El Primer Perfil", Boolean.FALSE, null) + .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, + createSecureSessionEnforceExecutorConfig()) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = ClientAccessTypeCondition_NAME; - createCondition(conditionName, ClientAccessTypeConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientAccessType(provider, Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureSessionEnforceExecutor_NAME; - createExecutor(executorName, SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Primera Plitica", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, + createClientAccessTypeConditionConfig(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); // confidential client String clientAlphaId = generateSuffixedName("Alpha-App"); @@ -801,22 +844,25 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testSecureResponseTypeExecutor() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testSecureResponseTypeExecutor() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "O Primeiro Perfil", Boolean.FALSE, null) + .addExecutor(SecureResponseTypeExecutorFactory.PROVIDER_ID, + createSecureResponseTypeExecutorConfig()) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = ClientRolesCondition_NAME; - createCondition(conditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, Arrays.asList(SAMPLE_CLIENT_ROLE)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureResponseTypeExecutor_NAME; - createExecutor(executorName, SecureResponseTypeExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "A Primeira Politica", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); String clientId = generateSuffixedName(CLIENT_NAME); String clientSecret = "secret"; @@ -865,22 +911,25 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testSecureRequestObjectExecutor() throws ClientRegistrationException, ClientPolicyException, URISyntaxException, IOException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testSecureRequestObjectExecutor() throws Exception, URISyntaxException, IOException { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Prvy Profil", Boolean.FALSE, null) + .addExecutor(SecureRequestObjectExecutorFactory.PROVIDER_ID, + createSecureRequestObjectExecutorConfig()) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = ClientRolesCondition_NAME; - createCondition(conditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, Arrays.asList(SAMPLE_CLIENT_ROLE)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executor = SecureRequestObjectExecutor_NAME; - createExecutor(executor, SecureRequestObjectExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerExecutor(executor, policyName); - logger.info("... Registered Executor : " + executor); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Prva Politika", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); String clientId = generateSuffixedName(CLIENT_NAME); String clientSecret = "secret"; @@ -966,24 +1015,27 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testSecureSessionEnforceExecutor() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testSecureSessionEnforceExecutor() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen", Boolean.FALSE, null) + .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, + createSecureSessionEnforceExecutorConfig()) + .toRepresentation() + ).toString(); + updateProfiles(json); + // register policies String roleAlphaName = "sample-client-role-alpha"; String roleBetaName = "sample-client-role-beta"; - String conditionName = ClientRolesCondition_NAME; - createCondition(conditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, Arrays.asList(roleBetaName)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureSessionEnforceExecutor_NAME; - createExecutor(executorName, SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(roleBetaName))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); String clientAlphaId = generateSuffixedName("Alpha-App"); String clientAlphaSecret = "secretAlpha"; @@ -1019,25 +1071,29 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testSecureSigningAlgorithmEnforceExecutor() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testSecureSigningAlgorithmEnforceExecutor() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen", Boolean.FALSE, null) + .addExecutor(SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, + createSecureSigningAlgorithmEnforceExecutorConfig()) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = ClientUpdateContextCondition_NAME; - createCondition(conditionName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, Arrays.asList( - ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, - ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN, - ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forsta Policyn", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, + createClientUpdateContextConditionConfig(Arrays.asList( + ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, + ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN, + ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); - String execuorName = SecureSigningAlgorithmEnforceExecutor_NAME; - createExecutor(execuorName, SecureSigningAlgorithmEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerExecutor(execuorName, policyName); - logger.info("... Registered Executor : " + execuorName); // create by Admin REST API - fail try { @@ -1047,8 +1103,8 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { clientRep.getAttributes().put(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, Algorithm.none.name()); }); fail(); - } catch (ClientPolicyException cpe) { - assertEquals(OAuthErrorException.INVALID_REQUEST, cpe.getError()); + } catch (ClientPolicyException e) { + assertEquals(OAuthErrorException.INVALID_REQUEST, e.getMessage()); } // create by Admin REST API - success @@ -1089,8 +1145,8 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { clientRep.getAttributes().put(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, org.keycloak.crypto.Algorithm.RS384); }); fail(); - } catch (ClientPolicyException cpe) { - assertEquals(OAuthErrorException.INVALID_REQUEST, cpe.getError()); + } catch (ClientPolicyException e) { + assertEquals(OAuthErrorException.INVALID_REQUEST, e.getMessage()); } // create dynamically - success @@ -1122,25 +1178,28 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testSecureRedirectUriEnforceExecutor() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testSecureRedirectUriEnforceExecutor() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili", Boolean.FALSE, null) + .addExecutor(SecureRedirectUriEnforceExecutorFactory.PROVIDER_ID, + createSecureRedirectUriEnforceExecutorConfig()) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = ClientUpdateContextCondition_NAME; - createCondition(conditionName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, Arrays.asList( - ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, - ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN, - ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureRedirectUriEnforceExecutor_NAME; - createExecutor(executorName, SecureRedirectUriEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Ensimmainen Politiikka", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, + createClientUpdateContextConditionConfig(Arrays.asList( + ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, + ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN, + ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); try { createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> { @@ -1150,11 +1209,19 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } catch (ClientRegistrationException e) { assertEquals(ERR_MSG_CLIENT_REG_FAIL, e.getMessage()); } - updateCondition(conditionName, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, Arrays.asList( - ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, - ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)); - }); + + // update policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Paivitetyn Ensimmaisen Politiikka", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, + createClientUpdateContextConditionConfig(Arrays.asList( + ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER, + ClientUpdateContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); + try { createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {}); } catch (Exception e) { @@ -1164,36 +1231,39 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { @Test public void testSecureSigningAlgorithmForSignedJwtEnforceExecutor() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Ensimmainen Profiili", Boolean.FALSE, null) + .addExecutor(SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, + createSecureSigningAlgorithmForSignedJwtEnforceExecutorConfig()) + .toRepresentation() + ).toString(); + updateProfiles(json); + + // register policies String roleAlphaName = "sample-client-role-alpha"; String roleZetaName = "sample-client-role-zeta"; String roleCommonName = "sample-client-role-common"; - - // policy including client role condition - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - String conditionName = ClientRolesCondition_NAME; - createCondition(conditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, Arrays.asList(roleAlphaName, roleZetaName)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureSigningAlgorithmForSignedJwtEnforceExecutor_NAME; - createExecutor(executorName, SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forste Politikken", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(roleAlphaName, roleZetaName))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); // create a client with client role String clientId = generateSuffixedName(CLIENT_NAME); - createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { - clientRep.setDefaultRoles(Arrays.asList(roleAlphaName, roleCommonName).toArray(new String[2])); + String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { clientRep.setSecret("secret"); clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID); clientRep.setAttributes(new HashMap<>()); clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, org.keycloak.crypto.Algorithm.ES256); }); + adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(roleAlphaName).build()); + adminClient.realm(REALM_NAME).clients().get(cid).roles().create(RoleBuilder.create().name(roleCommonName).build()); + ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId); ClientRepresentation clientRep = clientResource.toRepresentation(); @@ -1255,46 +1325,52 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { public void testHolderOfKeyEnforceExecutor() throws Exception { Assume.assumeTrue("This test must be executed with enabled TLS.", ServerURLs.AUTH_SERVER_SSL_REQUIRED); - final String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Az Elso Profil", Boolean.FALSE, null) + .addExecutor(HolderOfKeyEnforceExecutorFactory.PROVIDER_ID, + createHolderOfKeyEnforceExecutorConfig(Boolean.TRUE)) + .toRepresentation() + ).toString(); + updateProfiles(json); - final String conditionName = AnyClientCondition_NAME; - createCondition(conditionName, AnyClientConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - final String executorName = HolderOfKeyEnforceExecutor_NAME; - createExecutor(executorName, HolderOfKeyEnforceExecutorFactory.PROVIDER_ID, null, this::setExecutorAugmentActivate); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Az Elso Politika", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(AnyClientConditionFactory.PROVIDER_ID, + createAnyClientConditionConfig()) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); try (ClientAttributeUpdater cau = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, TEST_CLIENT)) { ClientRepresentation clientRep = cau.getResource().toRepresentation(); Assert.assertNotNull(clientRep); OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseMtlsHoKToken(true); cau.update(); - checkMtlsFlow(); } } @Test - public void testNegativeLogicCondition() throws ClientRegistrationException, ClientPolicyException { - String policyName = POLICY_NAME; - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + public void testNegativeLogicCondition() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen", Boolean.FALSE, null) + .addExecutor(SecureSessionEnforceExecutorFactory.PROVIDER_ID, createSecureSessionEnforceExecutorConfig()) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = AnyClientCondition_NAME; - createCondition(conditionName, AnyClientConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureSessionEnforceExecutor_NAME; - createExecutor(executorName, SecureSessionEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {}); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig()) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); String clientId = generateSuffixedName(CLIENT_NAME); String clientSecret = "secretBeta"; @@ -1304,13 +1380,21 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { try { failLoginWithoutSecureSessionParameter(clientId, ERR_MSG_MISSING_NONCE); - updateCondition("AnyClientCondition", (ComponentRepresentation provider) -> { - provider.getConfig().putSingle(AbstractClientPolicyConditionProviderFactory.IS_NEGATIVE_LOGIC, Boolean.TRUE.toString()); - }); + + // update policies + updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig(Boolean.TRUE)) + .addProfile(PROFILE_NAME) + .toRepresentation()); + successfulLoginAndLogout(clientId, clientSecret); - updateCondition("AnyClientCondition", (ComponentRepresentation provider) -> { - provider.getConfig().putSingle(AbstractClientPolicyConditionProviderFactory.IS_NEGATIVE_LOGIC, Boolean.FALSE.toString()); - }); + + // update policies + updatePolicy((new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig(Boolean.FALSE)) + .addProfile(PROFILE_NAME) + .toRepresentation()); + failLoginWithoutSecureSessionParameter(clientId, ERR_MSG_MISSING_NONCE); } catch (Exception e) { fail(); @@ -1318,72 +1402,90 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } @Test - public void testExtendedClientPolicyIntefacesForClientRegistrationPolicyMigration() throws ClientRegistrationException, ClientPolicyException { - String policyName = "MyPolicy"; + public void testExtendedClientPolicyIntefacesForClientRegistrationPolicyMigration() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forste Profilen", Boolean.FALSE, null) + .addExecutor(TestRaiseExeptionExecutorFactory.PROVIDER_ID, null) + .toRepresentation() + ).toString(); + updateProfiles(json); + + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig()) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); + String clientName = "ByAdmin-App" + KeycloakModelUtils.generateId().substring(0, 7); - String executorName = "TestRaiseExeptionExecutor"; - - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - createCondition("AnyClientConditionFactory", AnyClientConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - }); - registerCondition("AnyClientConditionFactory", policyName); - logger.info("... Registered Condition : AnyClientConditionFactory"); - - createExecutor(executorName, TestRaiseExeptionExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - provider.getConfig().put(TestRaiseExeptionExecutorFactory.TARGET_CP_EVENTS, Arrays.asList(ClientPolicyEvent.REGISTERED.toString())); - }); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); - String clientId = null; + try { - try { - createClientByAdmin(clientName, (ClientRepresentation clientRep) -> { - }); - fail(); - } catch (ClientPolicyException cpe) { - assertEquals(ClientPolicyEvent.REGISTERED.toString(), cpe.getError()); - } + createClientByAdmin(clientName, (ClientRepresentation clientRep) -> {}); + fail(); + } catch (ClientPolicyException cpe) { + assertEquals(ClientPolicyEvent.REGISTERED.toString(), cpe.getError()); + } - updateTargetCPEvents(executorName, Arrays.asList(ClientPolicyEvent.UPDATED)); - - clientId = getClientByAdminWithName(clientName).getId(); - assertEquals(true, getClientByAdmin(clientId).isEnabled()); - try { - updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> { - clientRep.setEnabled(false); - }); - fail(); - } catch (ClientPolicyException cpe) { - assertEquals(ClientPolicyEvent.UPDATED.toString(), cpe.getError()); - } - assertEquals(false, getClientByAdmin(clientId).isEnabled()); - - updateTargetCPEvents(executorName, Arrays.asList(ClientPolicyEvent.VIEW)); - try { - getClientByAdmin(clientId); - } catch (ClientPolicyException cpe) { - assertEquals(ClientPolicyEvent.VIEW.toString(), cpe.getError()); - } - - updateTargetCPEvents(executorName, Arrays.asList(ClientPolicyEvent.UNREGISTER)); - try { - deleteClientByAdmin(clientId); - } catch (ClientPolicyException cpe) { - assertEquals(ClientPolicyEvent.UNREGISTER.toString(), cpe.getError()); - } - } finally { - updateExecutor(executorName, (ComponentRepresentation provider) -> { - provider.getConfig().put(TestRaiseExeptionExecutorFactory.TARGET_CP_EVENTS, Collections.singletonList((String)null)); + clientId = getClientByAdminWithName(clientName).getId(); + assertEquals(true, getClientByAdmin(clientId).isEnabled()); + try { + updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setEnabled(false); }); + fail(); + } catch (ClientPolicyException cpe) { + assertEquals(ClientPolicyEvent.UPDATED.toString(), cpe.getError()); + } + assertEquals(false, getClientByAdmin(clientId).isEnabled()); + + try { deleteClientByAdmin(clientId); + fail(); + } catch (ClientPolicyException cpe) { + assertEquals(ClientPolicyEvent.UNREGISTER.toString(), cpe.getError()); } // TODO : For dynamic client registration, the existing test scheme can not distinguish when the exception happens on which event so that the migrated client policy executors test them afterwards. } + @Test + public void testOverwriteBuiltinProfileNotAllowed() throws Exception { + // register profiles + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile("builtin-default-profile", "Pershyy Profil", Boolean.FALSE, null) + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + createSecureClientAuthEnforceExecutorConfig(Boolean.TRUE, + Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID), + X509ClientAuthenticator.PROVIDER_ID)) + .toRepresentation() + ).toString(); + try { + updateProfiles(json); + } catch (ClientPolicyException cpe) { + assertEquals("update profiles failed", cpe.getError()); + } + } + + @Test + public void testUpdatePolicyWithoutNameNotAllowd() throws Exception { + // register policies + String json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(null, "La Premiere Politique", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(AnyClientConditionFactory.PROVIDER_ID, createAnyClientConditionConfig()) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + try { + updatePolicies(json); + } catch (ClientPolicyException cpe) { + assertEquals("update profiles failed", cpe.getError()); + } + } + private void checkMtlsFlow() throws IOException { // Check login. OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD); @@ -1501,61 +1603,56 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } } - private void setupPolicyClientIdAndSecretNotAcceptableAuthType(String policyName) { - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); - - String conditionName = ClientUpdateContextCondition_NAME; - createCondition(conditionName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureClientAuthEnforceExecutor_NAME; - createExecutor(executorName, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider, Arrays.asList( - JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID)); - }); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + private void setupPolicyClientIdAndSecretNotAcceptableAuthType(String policyName) throws ClientPolicyException { + // register profiles + String profileName = "MyProfile"; + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(profileName, "Primum Profile", Boolean.FALSE, null) + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + createSecureClientAuthEnforceExecutorConfig(Boolean.FALSE, + Arrays.asList(JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID), + null)) + .toRepresentation() + ).toString(); + updateProfiles(json); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(policyName, "Primum Consilium", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, + createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdateContextConditionFactory.BY_AUTHENTICATED_USER))) + .addProfile(profileName) + .toRepresentation() + ).toString(); + updatePolicies(json); } - private void setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(String policyName) { - logger.info("Setup Policy"); - createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); - logger.info("... Created Policy : " + policyName); + private void setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(String policyName) throws ClientPolicyException { + // register profiles + String profileName = "MyProfile"; + String json = (new ClientProfilesBuilder()).addProfile( + (new ClientProfileBuilder()).createProfile(profileName, "Primul Profil", Boolean.FALSE, null) + .addExecutor(SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, + createSecureClientAuthEnforceExecutorConfig(Boolean.TRUE, + Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID, JWTClientAuthenticator.PROVIDER_ID), + ClientIdAndSecretAuthenticator.PROVIDER_ID)) + .addExecutor(PKCEEnforceExecutorFactory.PROVIDER_ID, + createPKCEEnforceExecutorConfig(Boolean.TRUE)) + .toRepresentation() + ).toString(); + updateProfiles(json); - String conditionName = ClientUpdateContextCondition_NAME; - createCondition(conditionName, ClientUpdateContextConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionRegistrationMethods(provider, Arrays.asList(ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - conditionName = ClientRolesCondition_NAME; - createCondition(conditionName, ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setConditionClientRoles(provider, Arrays.asList(SAMPLE_CLIENT_ROLE)); - }); - registerCondition(conditionName, policyName); - logger.info("... Registered Condition : " + conditionName); - - String executorName = SecureClientAuthEnforceExecutor_NAME; - createExecutor(executorName, SecureClientAuthEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAcceptedClientAuthMethods(provider, Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID, JWTClientAuthenticator.PROVIDER_ID)); - setExecutorAugmentedClientAuthMethod(provider, ClientIdAndSecretAuthenticator.PROVIDER_ID); - setExecutorAugmentActivate(provider); - }); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); - - executorName = PKCEEnforceExecutor_NAME; - createExecutor(executorName, PKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> { - setExecutorAugmentActivate(provider); - }); - registerExecutor(executorName, policyName); - logger.info("... Registered Executor : " + executorName); + // register policies + json = (new ClientPoliciesBuilder()).addPolicy( + (new ClientPolicyBuilder()).createPolicy(policyName, "Prima Politica", Boolean.FALSE, Boolean.TRUE, null, null) + .addCondition(ClientRolesConditionFactory.PROVIDER_ID, + createClientRolesConditionConfig(Arrays.asList(SAMPLE_CLIENT_ROLE))) + .addCondition(ClientUpdateContextConditionFactory.PROVIDER_ID, + createClientUpdateContextConditionConfig(Arrays.asList(ClientUpdateContextConditionFactory.BY_INITIAL_ACCESS_TOKEN))) + .addProfile(profileName) + .toRepresentation() + ).toString(); + updatePolicies(json); } private void successfulLoginAndLogout(String clientId, String clientSecret) { @@ -1659,10 +1756,4 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); assertEquals(ERR_MSG_MISSING_NONCE, oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION)); } - - private void updateTargetCPEvents(String executorName, List events) { - updateExecutor(executorName, (ComponentRepresentation provider) -> { - provider.getConfig().put(TestRaiseExeptionExecutorFactory.TARGET_CP_EVENTS, events.stream().map(i->i.toString()).collect(Collectors.toList())); - }); - } -} \ No newline at end of file +}