From c4bf8ecdf00f7c8d97dee7182c52d514d2265153 Mon Sep 17 00:00:00 2001 From: Takashi Norimatsu Date: Thu, 21 Jan 2021 14:08:20 +0900 Subject: [PATCH] KEYCLOAK-16880 Client Policy - Condition : Negative Logic Support --- .../ClientPolicyConditionProvider.java | 12 +++++ .../DefaultClientPolicyManager.java | 7 +++ .../DefaultClientPolicyProvider.java | 2 +- ...AbstractClientPolicyConditionProvider.java | 47 +++++++++++++++++ ...tClientPolicyConditionProviderFactory.java | 51 +++++++++++++++++++ .../condition/AnyClientCondition.java | 19 ++----- .../condition/AnyClientConditionFactory.java | 26 +--------- .../condition/ClientAccessTypeCondition.java | 18 +------ .../ClientAccessTypeConditionFactory.java | 32 +++--------- .../condition/ClientRolesCondition.java | 19 ++----- .../ClientRolesConditionFactory.java | 32 +++--------- .../condition/ClientScopesCondition.java | 18 +------ .../ClientScopesConditionFactory.java | 36 ++++--------- .../ClientUpdateContextCondition.java | 19 +------ .../ClientUpdateContextConditionFactory.java | 35 ++++--------- .../ClientUpdateSourceGroupsCondition.java | 20 +------- ...entUpdateSourceGroupsConditionFactory.java | 31 +++-------- .../ClientUpdateSourceHostsCondition.java | 18 +------ ...ientUpdateSourceHostsConditionFactory.java | 22 ++------ .../ClientUpdateSourceRolesCondition.java | 18 +------ ...ientUpdateSourceRolesConditionFactory.java | 35 +++---------- .../testsuite/client/ClientPoliciesTest.java | 38 ++++++++++++++ 22 files changed, 223 insertions(+), 332 deletions(-) create mode 100644 services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProvider.java create mode 100644 services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProviderFactory.java 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 06225dfcec..04532adf45 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 @@ -48,6 +48,18 @@ public interface ClientPolicyConditionProvider extends Provider { return ClientPolicyVote.ABSTAIN; } + /** + * tells whether the result of applyPolicy method is inverted or not as follows. + * ClientPolicyVote.YES is inverted to ClientPolicyVote.NO + * ClientPolicyVote.NO is inverted to ClientPolicyVote.YES + * ClientPolicyVote.ABSTAIN remains unchanged + * + * @return true if the result of applyPolicy method is inverted. + */ + default boolean isNegativeLogic() { + return false; + } + String getName(); String getProviderId(); 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 f03a10cdc1..90600e1053 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManager.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyManager.java @@ -96,6 +96,13 @@ public class DefaultClientPolicyManager implements ClientPolicyManager { for (ClientPolicyConditionProvider condition : conditions) { try { ClientPolicyVote vote = op.run(condition); + if (condition.isNegativeLogic()) { + if (vote == ClientPolicyVote.YES) { + vote = ClientPolicyVote.NO; + } else if (vote == ClientPolicyVote.NO) { + vote = ClientPolicyVote.YES; + } + } 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()); continue; diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProvider.java b/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProvider.java index 053efbf869..c6871ffd10 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProvider.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/DefaultClientPolicyProvider.java @@ -88,7 +88,7 @@ public class DefaultClientPolicyProvider implements ClientPolicyProvider { 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}", conditionId, cm.getName(), cm.getProviderId()); + 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()); } 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 new file mode 100644 index 0000000000..38578cbbcd --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProvider.java @@ -0,0 +1,47 @@ +/* + * 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 new file mode 100644 index 0000000000..0dca8a9912 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/AbstractClientPolicyConditionProviderFactory.java @@ -0,0 +1,51 @@ +/* + * 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 bd629e643b..86eb0ba576 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 @@ -24,15 +24,12 @@ import org.keycloak.services.clientpolicy.ClientPolicyContext; import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyVote; -public class AnyClientCondition implements ClientPolicyConditionProvider { +public class AnyClientCondition extends AbstractClientPolicyConditionProvider { + private static final Logger logger = Logger.getLogger(AnyClientCondition.class); - private final KeycloakSession session; - private final ComponentModel componentModel; - public AnyClientCondition(KeycloakSession session, ComponentModel componentModel) { - this.session = session; - this.componentModel = componentModel; + super(session, componentModel); } @Override @@ -40,14 +37,4 @@ public class AnyClientCondition implements ClientPolicyConditionProvider { return ClientPolicyVote.YES; } - @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/AnyClientConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/AnyClientConditionFactory.java index 6975cd7c7e..7ca11b7c33 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 @@ -17,16 +17,10 @@ package org.keycloak.services.clientpolicy.condition; -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; -public class AnyClientConditionFactory implements ClientPolicyConditionProviderFactory { +public class AnyClientConditionFactory extends AbstractClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "anyclient-condition"; @@ -35,18 +29,6 @@ public class AnyClientConditionFactory implements ClientPolicyConditionProviderF return new AnyClientCondition(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; @@ -56,10 +38,4 @@ public class AnyClientConditionFactory implements ClientPolicyConditionProviderF 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 ceb7cd5441..35211dc315 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 @@ -29,26 +29,12 @@ import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; -public class ClientAccessTypeCondition implements ClientPolicyConditionProvider { +public class ClientAccessTypeCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientAccessTypeCondition.class); - private final KeycloakSession session; - private final ComponentModel componentModel; - public ClientAccessTypeCondition(KeycloakSession session, ComponentModel componentModel) { - this.session = session; - this.componentModel = componentModel; - } - - @Override - public String getName() { - return componentModel.getName(); - } - - @Override - public String getProviderId() { - return componentModel.getProviderId(); + super(session, componentModel); } @Override 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 6096931121..25d66249fd 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 @@ -17,17 +17,14 @@ 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.component.ComponentModel; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.provider.ProviderConfigProperty; -public class ClientAccessTypeConditionFactory implements ClientPolicyConditionProviderFactory { +public class ClientAccessTypeConditionFactory extends AbstractClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "client-accesstype-condition"; public static final String TYPE = "type"; @@ -35,14 +32,10 @@ public class ClientAccessTypeConditionFactory implements ClientPolicyConditionPr public static final String TYPE_PUBLIC = "public"; public static final String TYPE_BEARERONLY = "bearer-only"; - private static final List configProperties = new ArrayList(); - + private static final ProviderConfigProperty CLIENTACCESSTYPE_PROPERTY; static { - 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); + 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)); } @Override @@ -50,18 +43,6 @@ public class ClientAccessTypeConditionFactory implements ClientPolicyConditionPr return new ClientAccessTypeCondition(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; @@ -74,7 +55,8 @@ public class ClientAccessTypeConditionFactory implements ClientPolicyConditionPr @Override public List getConfigProperties() { - return configProperties; + List l = super.getConfigProperties(); + l.add(CLIENTACCESSTYPE_PROPERTY); + return l; } - } 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 019bf1f7a9..b5fa9817cb 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 @@ -32,15 +32,12 @@ import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; -public class ClientRolesCondition implements ClientPolicyConditionProvider { +public class ClientRolesCondition extends AbstractClientPolicyConditionProvider { + private static final Logger logger = Logger.getLogger(ClientRolesCondition.class); - private final KeycloakSession session; - private final ComponentModel componentModel; - public ClientRolesCondition(KeycloakSession session, ComponentModel componentModel) { - this.session = session; - this.componentModel = componentModel; + super(session, componentModel); } @Override @@ -91,14 +88,4 @@ public class ClientRolesCondition implements ClientPolicyConditionProvider { return new HashSet<>(roles); } - @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/ClientRolesConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientRolesConditionFactory.java index 382e23c104..8edc837964 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 @@ -17,44 +17,23 @@ package org.keycloak.services.clientpolicy.condition; -import java.util.ArrayList; 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; -public class ClientRolesConditionFactory implements ClientPolicyConditionProviderFactory { +public class ClientRolesConditionFactory extends AbstractClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "clientroles-condition"; public static final String ROLES = "roles"; - 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); - } + private static final ProviderConfigProperty CLIENTROLES_PROPERTY = new ProviderConfigProperty( + ROLES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, null); @Override public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { return new ClientRolesCondition(session, model); - - } - - @Override - public void init(Scope config) { - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public void close() { } @Override @@ -69,7 +48,8 @@ public class ClientRolesConditionFactory implements ClientPolicyConditionProvide @Override public List getConfigProperties() { - return configProperties; + List l = super.getConfigProperties(); + l.add(CLIENTROLES_PROPERTY); + return l; } - } 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 eca7e4c563..42d78db78a 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 @@ -37,16 +37,12 @@ import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.TokenRequestContext; -public class ClientScopesCondition implements ClientPolicyConditionProvider { +public class ClientScopesCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientScopesCondition.class); - private final KeycloakSession session; - private final ComponentModel componentModel; - public ClientScopesCondition(KeycloakSession session, ComponentModel componentModel) { - this.session = session; - this.componentModel = componentModel; + super(session, componentModel); } @Override @@ -63,16 +59,6 @@ public class ClientScopesCondition implements ClientPolicyConditionProvider { } } - @Override - public String getName() { - return componentModel.getName(); - } - - @Override - public String getProviderId() { - return componentModel.getProviderId(); - } - private boolean isScopeMatched(AuthenticatedClientSessionModel clientSession) { if (clientSession == null) return false; return isScopeMatched(clientSession.getNote(OAuth2Constants.SCOPE), clientSession.getClient()); 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 fab98f1cdd..c6da86a43c 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 @@ -17,16 +17,13 @@ package org.keycloak.services.clientpolicy.condition; -import java.util.ArrayList; 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; -public class ClientScopesConditionFactory implements ClientPolicyConditionProviderFactory { +public class ClientScopesConditionFactory extends AbstractClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "clientscopes-condition"; public static final String SCOPES = "scopes"; @@ -34,33 +31,16 @@ public class ClientScopesConditionFactory implements ClientPolicyConditionProvid public static final String DEFAULT = "Default"; public static final String OPTIONAL = "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); - } + 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); @Override public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { return new ClientScopesCondition(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; @@ -73,7 +53,9 @@ public class ClientScopesConditionFactory implements ClientPolicyConditionProvid @Override public List getConfigProperties() { - return configProperties; + List l = super.getConfigProperties(); + l.add(CLIENTSCOPES_PROPERTY); + l.add(CLIENTSCOPETYPE_PROPERTY); + return l; } - } \ 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 0c8edabb3b..9f73ec70f8 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 @@ -29,20 +29,15 @@ import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; import org.keycloak.services.clientpolicy.ClientUpdateContext; -import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider; import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils; import org.keycloak.util.TokenUtil; -public class ClientUpdateContextCondition implements ClientPolicyConditionProvider { +public class ClientUpdateContextCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateContextCondition.class); - private final KeycloakSession session; - private final ComponentModel componentModel; - public ClientUpdateContextCondition(KeycloakSession session, ComponentModel componentModel) { - this.session = session; - this.componentModel = componentModel; + super(session, componentModel); } @Override @@ -108,14 +103,4 @@ public class ClientUpdateContextCondition implements ClientPolicyConditionProvid private boolean isBearerToken(JsonWebToken jwt) { return jwt != null && TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType()); } - - @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/ClientUpdateContextConditionFactory.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientUpdateContextConditionFactory.java index 8e96ca53fa..1068666b5d 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 @@ -17,19 +17,15 @@ 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.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; -public class ClientUpdateContextConditionFactory implements ClientPolicyConditionProviderFactory { +public class ClientUpdateContextConditionFactory extends AbstractClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "clientupdatecontext-condition"; @@ -40,32 +36,18 @@ public class ClientUpdateContextConditionFactory implements ClientPolicyConditio public static final String BY_INITIAL_ACCESS_TOKEN = "ByInitialAccessToken"; public static final String BY_REGISTRATION_ACCESS_TOKEN = "ByRegistrationAccessToken"; - private static final List configProperties = new ArrayList(); - + private static final ProviderConfigProperty CLIENTUPDATESOURCE_PROPERTY; static { - 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); + 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)); } @Override public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { return new ClientUpdateContextCondition(session, model); } - - @Override - public void init(Scope config) { - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public void close() { - } @Override public String getId() { @@ -79,7 +61,8 @@ public class ClientUpdateContextConditionFactory implements ClientPolicyConditio @Override public List getConfigProperties() { - return configProperties; + List l = super.getConfigProperties(); + l.add(CLIENTUPDATESOURCE_PROPERTY); + return l; } - } 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 cc38df2170..f3c4307330 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 @@ -17,7 +17,6 @@ package org.keycloak.services.clientpolicy.condition; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -25,7 +24,6 @@ import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.keycloak.OAuthErrorException; -import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; @@ -41,26 +39,12 @@ import org.keycloak.services.clientpolicy.ClientUpdateContext; import org.keycloak.services.clientpolicy.DynamicClientRegisterContext; import org.keycloak.services.clientpolicy.DynamicClientUpdateContext; -public class ClientUpdateSourceGroupsCondition implements ClientPolicyConditionProvider { +public class ClientUpdateSourceGroupsCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateSourceGroupsCondition.class); - private final KeycloakSession session; - private final ComponentModel componentModel; - public ClientUpdateSourceGroupsCondition(KeycloakSession session, ComponentModel componentModel) { - this.session = session; - this.componentModel = componentModel; - } - - @Override - public String getName() { - return componentModel.getName(); - } - - @Override - public String getProviderId() { - return componentModel.getProviderId(); + super(session, componentModel); } @Override 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 482eb278b8..6bd7c1d225 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 @@ -17,46 +17,26 @@ package org.keycloak.services.clientpolicy.condition; -import java.util.ArrayList; 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; -public class ClientUpdateSourceGroupsConditionFactory implements ClientPolicyConditionProviderFactory { +public class ClientUpdateSourceGroupsConditionFactory extends AbstractClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "clientupdatesourcegroups-condition"; public static final String GROUPS = "groups"; - 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); - } + private static final ProviderConfigProperty CLIENTUPDATEGROUP_PROPERTY = new ProviderConfigProperty( + GROUPS, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "topGroup"); @Override public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { return new ClientUpdateSourceGroupsCondition(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; @@ -69,7 +49,8 @@ public class ClientUpdateSourceGroupsConditionFactory implements ClientPolicyCon @Override public List getConfigProperties() { - return configProperties; + List l = super.getConfigProperties(); + l.add(CLIENTUPDATEGROUP_PROPERTY); + return l; } - } 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 3ed9d778a4..bd7e07fbae 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 @@ -31,26 +31,12 @@ import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyLogger; import org.keycloak.services.clientpolicy.ClientPolicyVote; -public class ClientUpdateSourceHostsCondition implements ClientPolicyConditionProvider { +public class ClientUpdateSourceHostsCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateSourceHostsCondition.class); - private final KeycloakSession session; - private final ComponentModel componentModel; - public ClientUpdateSourceHostsCondition(KeycloakSession session, ComponentModel componentModel) { - this.session = session; - this.componentModel = componentModel; - } - - @Override - public String getName() { - return componentModel.getName(); - } - - @Override - public String getProviderId() { - return componentModel.getProviderId(); + super(session, componentModel); } @Override 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 15411df64c..e1eef1ecbb 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 @@ -17,19 +17,16 @@ package org.keycloak.services.clientpolicy.condition; -import java.util.Arrays; import java.util.List; -import org.keycloak.Config.Scope; import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentValidationException; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; import org.keycloak.provider.ConfigurationValidationHelper; import org.keycloak.provider.ProviderConfigProperty; -public class ClientUpdateSourceHostsConditionFactory implements ClientPolicyConditionProviderFactory { +public class ClientUpdateSourceHostsConditionFactory extends AbstractClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "clientupdatesourcehost-condition"; @@ -47,18 +44,6 @@ public class ClientUpdateSourceHostsConditionFactory implements ClientPolicyCond return new ClientUpdateSourceHostsCondition(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; @@ -71,7 +56,10 @@ public class ClientUpdateSourceHostsConditionFactory implements ClientPolicyCond @Override public List getConfigProperties() { - return Arrays.asList(TRUSTED_HOSTS_PROPERTY, HOST_SENDING_REGISTRATION_REQUEST_MUST_MATCH_PROPERTY); + List l = super.getConfigProperties(); + l.add(TRUSTED_HOSTS_PROPERTY); + l.add(HOST_SENDING_REGISTRATION_REQUEST_MUST_MATCH_PROPERTY); + return l; } @Override 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 664c4a19b0..5d8f06c799 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 @@ -40,26 +40,12 @@ import org.keycloak.services.clientpolicy.ClientUpdateContext; import org.keycloak.services.clientpolicy.DynamicClientRegisterContext; import org.keycloak.services.clientpolicy.DynamicClientUpdateContext; -public class ClientUpdateSourceRolesCondition implements ClientPolicyConditionProvider { +public class ClientUpdateSourceRolesCondition extends AbstractClientPolicyConditionProvider { private static final Logger logger = Logger.getLogger(ClientUpdateSourceRolesCondition.class); - private final KeycloakSession session; - private final ComponentModel componentModel; - public ClientUpdateSourceRolesCondition(KeycloakSession session, ComponentModel componentModel) { - this.session = session; - this.componentModel = componentModel; - } - - @Override - public String getName() { - return componentModel.getName(); - } - - @Override - public String getProviderId() { - return componentModel.getProviderId(); + super(session, componentModel); } @Override 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 df0870eb1d..31d52ea52d 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 @@ -17,48 +17,25 @@ package org.keycloak.services.clientpolicy.condition; -import java.util.ArrayList; 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; -public class ClientUpdateSourceRolesConditionFactory implements ClientPolicyConditionProviderFactory { +public class ClientUpdateSourceRolesConditionFactory extends AbstractClientPolicyConditionProviderFactory { public static final String PROVIDER_ID = "clientupdatesourceroles-condition"; public static final String ROLES = "roles"; - 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); - } + private static final ProviderConfigProperty CLIENTUPDATEROLE_PROPERTY = new ProviderConfigProperty( + ROLES, PROVIDER_ID + ".label", PROVIDER_ID + ".tooltip", ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "admin"); @Override public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) { return new ClientUpdateSourceRolesCondition(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; @@ -67,12 +44,12 @@ public class ClientUpdateSourceRolesConditionFactory implements ClientPolicyCond @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() { - return configProperties; + List l = super.getConfigProperties(); + l.add(CLIENTUPDATEROLE_PROPERTY); + return l; } - } 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 9908cb3d59..67d7105f24 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 @@ -74,6 +74,7 @@ import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.representations.oidc.TokenMetadataRepresentation; 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.ClientUpdateContextConditionFactory; @@ -1279,6 +1280,43 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest { } } + @Test + public void testNegativeLogicCondition() throws ClientRegistrationException, ClientPolicyException { + String policyName = POLICY_NAME; + createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null); + logger.info("... Created Policy : " + policyName); + + 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); + + String clientId = generateSuffixedName(CLIENT_NAME); + String clientSecret = "secretBeta"; + createClientByAdmin(clientId, (ClientRepresentation clientRep) -> { + clientRep.setSecret(clientSecret); + }); + + try { + failLoginWithoutSecureSessionParameter(clientId, ERR_MSG_MISSING_NONCE); + updateCondition("AnyClientCondition", (ComponentRepresentation provider) -> { + provider.getConfig().putSingle(AbstractClientPolicyConditionProviderFactory.IS_NEGATIVE_LOGIC, Boolean.TRUE.toString()); + }); + successfulLoginAndLogout(clientId, clientSecret); + updateCondition("AnyClientCondition", (ComponentRepresentation provider) -> { + provider.getConfig().putSingle(AbstractClientPolicyConditionProviderFactory.IS_NEGATIVE_LOGIC, Boolean.FALSE.toString()); + }); + failLoginWithoutSecureSessionParameter(clientId, ERR_MSG_MISSING_NONCE); + } catch (Exception e) { + fail(); + } + } + private void checkMtlsFlow() throws IOException { // Check login. OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);