Repository: keycloak/keycloak (Java)

Issue:
Improve `ClientScopesCondition` to make possible any scope type
Can we improve `Client Scopes Condition` and make sure that `Scope type` can be either “default” or “optional” or “both” ? Currently we don’t have value “both”. 

Right now, we can already support use-cases like:
- Do something if scope `foo` is available as default client scope of client triggering the request
- Do something if scope `foo` is available as optional client scope of client triggering the request and it is used in the `scope` parameter


But AFAIK we cannot easily support use-case like:
 
- Do something if scope `foo` is applied in the request (either automatically as default scope or by the parameter if client has it declared as optional scope)

 

This means that we cannot support use-case from #37124 easily (due the fact that condition needs to be declared either as “default” scope foo OR “optional” scope foo)




Fix (patch):
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 0c1da6b1df5a..555bcc5d5840 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
@@ -148,22 +148,28 @@ private boolean isScopeMatched(String explicitScopes, ClientModel client) {
             expectedScopes.forEach(i -> logger.tracev("expected scope = {0}", i));
         }
 
-        boolean isDefaultScope = ClientScopesConditionFactory.DEFAULT.equals(configuration.getType());
-
-        if (isDefaultScope) {
-            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()) {
+        switch (configuration.getType()) {
+            case ClientScopesConditionFactory.DEFAULT:
+                expectedScopes.retainAll(defaultScopes);
+                return !expectedScopes.isEmpty();
+
+            case ClientScopesConditionFactory.OPTIONAL:
+                explicitSpecifiedScopes.retainAll(expectedScopes);
+                explicitSpecifiedScopes.retainAll(optionalScopes);
                 if (logger.isTraceEnabled()) {
                     explicitSpecifiedScopes.forEach(i->logger.tracev("matched scope = {0}", i));
                 }
-                return true;
-            }
+                return !explicitSpecifiedScopes.isEmpty();
+
+            case ClientScopesConditionFactory.ANY:
+                explicitSpecifiedScopes.retainAll(expectedScopes);
+                explicitSpecifiedScopes.retainAll(optionalScopes);
+                expectedScopes.retainAll(defaultScopes);
+                return !expectedScopes.isEmpty() || !explicitSpecifiedScopes.isEmpty();
+
+            default:
+                return false;
         }
-        return false;
     }
 
     private Set<String> getScopesForMatching() {
@@ -171,5 +177,4 @@ private Set<String> getScopesForMatching() {
         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 e16b5e74e022..ba3cb9617b11 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
@@ -36,6 +36,7 @@ public class ClientScopesConditionFactory extends AbstractClientPolicyConditionP
     public static final String TYPE = "type";
     public static final String DEFAULT = "Default";
     public static final String OPTIONAL = "Optional";
+    public static final String ANY = "Any";
 
     private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
 
@@ -46,9 +47,10 @@ public class ClientScopesConditionFactory extends AbstractClientPolicyConditionP
         configProperties.add(property);
         property = new ProviderConfigProperty(TYPE, "Scope Type",
                 "If set to 'Default', condition evaluates to true if client has some default scopes of the values specified by the 'Expected Scopes' property. " +
-                        "If set to 'Optional', condition evaluates to true if client has some optional scopes of the values specified by the 'Expected Scopes' property and at the same time, the scope were used as a value of 'scope' parameter in the request",
+                        "If set to 'Optional', condition evaluates to true if client has some optional scopes of the values specified by the 'Expected Scopes' property and at the same time, the scope were used as a value of 'scope' parameter in the request. " +
+                        "If set to 'Any', condition evaluates to true if either of the 'Default' or 'Optional' conditions is satisfied.",
                 ProviderConfigProperty.LIST_TYPE, OPTIONAL);
-        property.setOptions(Arrays.asList(DEFAULT, OPTIONAL));
+        property.setOptions(Arrays.asList(DEFAULT, OPTIONAL, ANY));
         configProperties.add(property);
     }
 
@@ -71,4 +73,4 @@ public String getHelpText() {
     public List<ProviderConfigProperty> getConfigProperties() {
         return configProperties;
     }
-}
\ No newline at end of file
+}