This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 66de2edf18 [SYNCOPE-1862] Fix mapping of AttrReleasePolicy for OIDC 
client apps (#982)
66de2edf18 is described below

commit 66de2edf18576a051abeef34cf65ea96821c5059
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Wed Feb 5 19:16:12 2025 +0100

    [SYNCOPE-1862] Fix mapping of AttrReleasePolicy for OIDC client apps (#982)
---
 .../org/apache/syncope/fit/AbstractITCase.java     |   4 +-
 .../apache/syncope/fit/sra/AbstractOIDCITCase.java |  41 +++++
 .../apache/syncope/fit/sra/AbstractSRAITCase.java  |  33 ----
 .../org/apache/syncope/fit/ui/OIDCC4UIITCase.java  |  20 +++
 .../wa/bootstrap/WAPropertySourceLocator.java      |  56 ++----
 .../wa/bootstrap/mapping/AttrReleaseMapper.java    |   3 +-
 .../mapping/DefaultAttrReleaseMapper.java          | 192 +++++++++++++++++----
 .../starter/mapping/OIDCRPClientAppTOMapper.java   |  42 +----
 .../starter/mapping/RegisteredServiceMapper.java   |   5 +-
 .../syncope/wa/starter/WAServiceRegistryTest.java  |   3 +-
 .../src/test/resources/debug/wa-debug.properties   |   7 +-
 11 files changed, 247 insertions(+), 159 deletions(-)

diff --git 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index a3038f2ec1..e0ec2b2f69 100644
--- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -65,9 +65,9 @@ public abstract class AbstractITCase {
 
     protected static final String ADMIN_PWD = "password";
 
-    private static final String ANONYMOUS_USER = "anonymous";
+    protected static final String ANONYMOUS_USER = "anonymous";
 
-    private static final String ANONYMOUS_KEY = "anonymousKey";
+    protected static final String ANONYMOUS_KEY = "anonymousKey";
 
     protected static final String CORE_ADDRESS = 
"https://localhost:9443/syncope/rest";;
 
diff --git 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractOIDCITCase.java
 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractOIDCITCase.java
index 2f5ae3042c..c01f6087a0 100644
--- 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractOIDCITCase.java
+++ 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractOIDCITCase.java
@@ -58,10 +58,13 @@ import org.apache.http.message.BasicNameValuePair;
 import org.apache.http.util.EntityUtils;
 import org.apache.syncope.common.lib.OIDCScopeConstants;
 import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO;
+import org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf;
 import org.apache.syncope.common.lib.to.OIDCRPClientAppTO;
 import org.apache.syncope.common.lib.types.ClientAppType;
 import org.apache.syncope.common.lib.types.OIDCGrantType;
 import org.apache.syncope.common.lib.types.OIDCSubjectType;
+import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.service.wa.WAConfigService;
 import org.apereo.cas.oidc.OidcConstants;
@@ -80,6 +83,37 @@ abstract class AbstractOIDCITCase extends AbstractSRAITCase {
 
     protected static String TOKEN_URI;
 
+    protected static AttrReleasePolicyTO getAttrReleasePolicy() {
+        String description = "SRA attr release policy";
+
+        return POLICY_SERVICE.list(PolicyType.ATTR_RELEASE).stream().
+                map(AttrReleasePolicyTO.class::cast).
+                filter(policy -> description.equals(policy.getName())
+                && policy.getConf() instanceof DefaultAttrReleasePolicyConf).
+                findFirst().
+                orElseGet(() -> {
+                    DefaultAttrReleasePolicyConf policyConf = new 
DefaultAttrReleasePolicyConf();
+                    policyConf.getReleaseAttrs().put("surname", "family_name");
+                    policyConf.getReleaseAttrs().put("fullname", "name");
+                    policyConf.getReleaseAttrs().put("firstname", 
"given_name");
+                    policyConf.getReleaseAttrs().put("email", "email");
+                    policyConf.getReleaseAttrs().put("groups", "groups");
+
+                    AttrReleasePolicyTO policy = new AttrReleasePolicyTO();
+                    policy.setName(description);
+                    policy.setConf(policyConf);
+
+                    Response response = 
POLICY_SERVICE.create(PolicyType.ATTR_RELEASE, policy);
+                    if (response.getStatusInfo().getStatusCode() != 
Response.Status.CREATED.getStatusCode()) {
+                        fail("Could not create Test Attr Release Policy");
+                    }
+
+                    return POLICY_SERVICE.read(
+                            PolicyType.ATTR_RELEASE,
+                            
response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+                });
+    }
+
     protected static void oidcClientAppSetup(
             final String appName,
             final String sraRegistrationId,
@@ -136,6 +170,13 @@ abstract class AbstractOIDCITCase extends 
AbstractSRAITCase {
                     
WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of());
                     throw new IllegalStateException();
                 }
+                metadata = WebClient.create(
+                        WA_ADDRESS + "/actuator/env", ANONYMOUS_USER, 
ANONYMOUS_KEY, null).
+                        get().readEntity(String.class);
+                if 
(!metadata.contains("cas.authn.oidc.core.user-defined-scopes.syncope")) {
+                    
WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of());
+                    throw new IllegalStateException();
+                }
 
                 return true;
             } catch (Exception e) {
diff --git 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractSRAITCase.java
 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractSRAITCase.java
index 2e979013c9..bb7c74c6e4 100644
--- 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractSRAITCase.java
+++ 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractSRAITCase.java
@@ -49,9 +49,7 @@ import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.http.HttpStatus;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.util.EntityUtils;
-import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO;
 import org.apache.syncope.common.lib.policy.AuthPolicyTO;
-import org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf;
 import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
 import org.apache.syncope.common.lib.to.SRARouteTO;
 import org.apache.syncope.common.lib.types.PolicyType;
@@ -241,37 +239,6 @@ abstract class AbstractSRAITCase extends AbstractITCase {
                 });
     }
 
-    protected static AttrReleasePolicyTO getAttrReleasePolicy() {
-        String description = "SRA attr release policy";
-
-        return POLICY_SERVICE.list(PolicyType.ATTR_RELEASE).stream().
-                map(AttrReleasePolicyTO.class::cast).
-                filter(policy -> description.equals(policy.getName())
-                && policy.getConf() instanceof DefaultAttrReleasePolicyConf).
-                findFirst().
-                orElseGet(() -> {
-                    DefaultAttrReleasePolicyConf policyConf = new 
DefaultAttrReleasePolicyConf();
-                    policyConf.getAllowedAttrs().add("family_name");
-                    policyConf.getAllowedAttrs().add("name");
-                    policyConf.getAllowedAttrs().add("given_name");
-                    policyConf.getAllowedAttrs().add("email");
-                    policyConf.getAllowedAttrs().add("groups");
-
-                    AttrReleasePolicyTO policy = new AttrReleasePolicyTO();
-                    policy.setName(description);
-                    policy.setConf(policyConf);
-
-                    Response response = 
POLICY_SERVICE.create(PolicyType.ATTR_RELEASE, policy);
-                    if (response.getStatusInfo().getStatusCode() != 
Response.Status.CREATED.getStatusCode()) {
-                        fail("Could not create Test Attr Release Policy");
-                    }
-
-                    return POLICY_SERVICE.read(
-                            PolicyType.ATTR_RELEASE,
-                            
response.getHeaderString(RESTHeaders.RESOURCE_KEY));
-                });
-    }
-
     protected static ObjectNode checkGetResponse(
             final CloseableHttpResponse response, final String 
originalRequestURI) throws IOException {
 
diff --git 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
index 6505af1172..e5d4158dc8 100644
--- 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
+++ 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.fit.ui;
 
+import static org.awaitility.Awaitility.await;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -30,6 +31,8 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.http.Consts;
 import org.apache.http.HttpHeaders;
 import org.apache.http.HttpStatus;
@@ -100,6 +103,23 @@ public class OIDCC4UIITCase extends AbstractUIITCase {
         clientApp.getScopes().add(OIDCScopeConstants.EMAIL);
 
         CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, clientApp);
+
+        await().atMost(60, TimeUnit.SECONDS).pollInterval(20, 
TimeUnit.SECONDS).until(() -> {
+            try {
+                String metadata = WebClient.create(
+                        WA_ADDRESS + "/actuator/env", ANONYMOUS_USER, 
ANONYMOUS_KEY, null).
+                        get().readEntity(String.class);
+                if 
(!metadata.contains("cas.authn.oidc.core.user-defined-scopes.syncope")) {
+                    
WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of());
+                    throw new IllegalStateException();
+                }
+
+                return true;
+            } catch (Exception e) {
+                // ignore
+            }
+            return false;
+        });
         WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, 
List.of());
     }
 
diff --git 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java
 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java
index 0c5a609bbb..a840a1be14 100644
--- 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java
+++ 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java
@@ -19,9 +19,7 @@
 package org.apache.syncope.wa.bootstrap;
 
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
@@ -30,7 +28,6 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.client.lib.SyncopeClient;
-import org.apache.syncope.common.lib.OIDCScopeConstants;
 import org.apache.syncope.common.lib.to.OIDCRPClientAppTO;
 import org.apache.syncope.common.rest.api.service.AttrRepoService;
 import org.apache.syncope.common.rest.api.service.AuthModuleService;
@@ -40,14 +37,9 @@ import 
org.apache.syncope.wa.bootstrap.mapping.AttrReleaseMapper;
 import org.apache.syncope.wa.bootstrap.mapping.AttrRepoPropertySourceMapper;
 import org.apache.syncope.wa.bootstrap.mapping.AuthModulePropertySourceMapper;
 import org.apereo.cas.configuration.model.support.oidc.OidcDiscoveryProperties;
-import org.apereo.cas.oidc.claims.OidcAddressScopeAttributeReleasePolicy;
-import org.apereo.cas.oidc.claims.OidcEmailScopeAttributeReleasePolicy;
-import org.apereo.cas.oidc.claims.OidcPhoneScopeAttributeReleasePolicy;
-import org.apereo.cas.oidc.claims.OidcProfileScopeAttributeReleasePolicy;
-import org.apereo.cas.services.BaseMappedAttributeReleasePolicy;
+import org.apereo.cas.oidc.claims.OidcCustomScopeAttributeReleasePolicy;
 import org.apereo.cas.services.ChainingAttributeReleasePolicy;
 import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy;
-import org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy;
 import org.apereo.cas.util.crypto.CipherExecutor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -133,43 +125,23 @@ public class WAPropertySourceLocator implements 
PropertySourceLocator {
         });
 
         Set<String> customClaims = 
syncopeClient.getService(WAClientAppService.class).list().stream().
-                filter(clientApp -> clientApp.getAttrReleasePolicy() != null
-                && clientApp.getClientAppTO() instanceof OIDCRPClientAppTO).
-                flatMap(clientApp -> {
-                    OIDCRPClientAppTO rp = 
OIDCRPClientAppTO.class.cast(clientApp.getClientAppTO());
-
+                filter(app -> app.getClientAppTO() instanceof 
OIDCRPClientAppTO && app.getAttrReleasePolicy() != null).
+                flatMap(app -> {
                     RegisteredServiceAttributeReleasePolicy 
attributeReleasePolicy =
-                            
attrReleaseMapper.build(clientApp.getAttrReleasePolicy());
-
-                    Set<String> claims = new HashSet<>();
-                    if (attributeReleasePolicy instanceof 
BaseMappedAttributeReleasePolicy baseMapped) {
-                        claims.addAll(baseMapped.
-                                getAllowedAttributes().values().stream().
-                                
map(Objects::toString).collect(Collectors.toSet()));
-                    } else if (attributeReleasePolicy instanceof 
ReturnAllowedAttributeReleasePolicy returnAllowed) {
-                        claims.addAll(returnAllowed.
-                                
getAllowedAttributes().stream().collect(Collectors.toSet()));
-                    } else if (attributeReleasePolicy instanceof 
ChainingAttributeReleasePolicy chaining) {
-                        chaining.getPolicies().stream().
-                                
filter(ReturnAllowedAttributeReleasePolicy.class::isInstance).
-                                
findFirst().map(ReturnAllowedAttributeReleasePolicy.class::cast).
-                                map(p -> 
p.getAllowedAttributes().stream().collect(Collectors.toSet())).
-                                ifPresent(claims::addAll);
-                    }
-                    if (rp.getScopes().contains(OIDCScopeConstants.PROFILE)) {
-                        
claims.removeAll(OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
-                    }
-                    if (rp.getScopes().contains(OIDCScopeConstants.ADDRESS)) {
-                        
claims.removeAll(OidcAddressScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
-                    }
-                    if (rp.getScopes().contains(OIDCScopeConstants.EMAIL)) {
-                        
claims.removeAll(OidcEmailScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
+                            attrReleaseMapper.build(app.getClientAppTO(), 
app.getAttrReleasePolicy());
+
+                    if (attributeReleasePolicy instanceof 
OidcCustomScopeAttributeReleasePolicy custom) {
+                        return custom.getAllowedAttributes().stream();
                     }
-                    if (rp.getScopes().contains(OIDCScopeConstants.PHONE)) {
-                        
claims.removeAll(OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
+
+                    if (attributeReleasePolicy instanceof 
ChainingAttributeReleasePolicy chain) {
+                        return chain.getPolicies().stream().
+                                
filter(OidcCustomScopeAttributeReleasePolicy.class::isInstance).
+                                
map(OidcCustomScopeAttributeReleasePolicy.class::cast).
+                                flatMap(p -> 
p.getAllowedAttributes().stream());
                     }
 
-                    return claims.stream();
+                    return Stream.empty();
                 }).collect(Collectors.toSet());
         if (!customClaims.isEmpty()) {
             Stream.concat(new OidcDiscoveryProperties().getClaims().stream(), 
customClaims.stream()).
diff --git 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java
 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java
index 021968582a..028ebdefee 100644
--- 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java
+++ 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java
@@ -20,11 +20,12 @@ package org.apache.syncope.wa.bootstrap.mapping;
 
 import org.apache.syncope.common.lib.policy.AttrReleasePolicyConf;
 import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO;
+import org.apache.syncope.common.lib.to.ClientAppTO;
 import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy;
 
 public interface AttrReleaseMapper {
 
     boolean supports(AttrReleasePolicyConf conf);
 
-    RegisteredServiceAttributeReleasePolicy build(AttrReleasePolicyTO policy);
+    RegisteredServiceAttributeReleasePolicy build(ClientAppTO clientApp, 
AttrReleasePolicyTO policy);
 }
diff --git 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java
 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java
index e144d4ce85..07cb261d3e 100644
--- 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java
+++ 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java
@@ -18,15 +18,28 @@
  */
 package org.apache.syncope.wa.bootstrap.mapping;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.syncope.common.lib.OIDCScopeConstants;
 import org.apache.syncope.common.lib.policy.AttrReleasePolicyConf;
 import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO;
 import org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf;
+import org.apache.syncope.common.lib.to.ClientAppTO;
+import org.apache.syncope.common.lib.to.OIDCRPClientAppTO;
 import 
org.apereo.cas.authentication.principal.DefaultPrincipalAttributesRepository;
 import 
org.apereo.cas.authentication.principal.cache.AbstractPrincipalAttributesRepository;
 import 
org.apereo.cas.authentication.principal.cache.CachingPrincipalAttributesRepository;
 import 
org.apereo.cas.configuration.model.core.authentication.PrincipalAttributesCoreProperties.MergingStrategyTypes;
 import org.apereo.cas.configuration.support.TriStateBoolean;
+import org.apereo.cas.oidc.claims.BaseOidcScopeAttributeReleasePolicy;
+import org.apereo.cas.oidc.claims.OidcAddressScopeAttributeReleasePolicy;
+import org.apereo.cas.oidc.claims.OidcCustomScopeAttributeReleasePolicy;
+import org.apereo.cas.oidc.claims.OidcEmailScopeAttributeReleasePolicy;
+import org.apereo.cas.oidc.claims.OidcPhoneScopeAttributeReleasePolicy;
+import org.apereo.cas.oidc.claims.OidcProfileScopeAttributeReleasePolicy;
 import org.apereo.cas.services.AbstractRegisteredServiceAttributeReleasePolicy;
 import org.apereo.cas.services.ChainingAttributeReleasePolicy;
 import org.apereo.cas.services.DenyAllAttributeReleasePolicy;
@@ -34,22 +47,113 @@ import 
org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy;
 import org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy;
 import org.apereo.cas.services.ReturnMappedAttributeReleasePolicy;
 import org.apereo.cas.services.consent.DefaultRegisteredServiceConsentPolicy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class DefaultAttrReleaseMapper implements AttrReleaseMapper {
 
+    protected static final Logger LOG = 
LoggerFactory.getLogger(AttrReleaseMapper.class);
+
+    protected static void warnMissingScope(
+            final String clientApp,
+            final String internal,
+            final Object external,
+            final String scope) {
+
+        LOG.warn("OIDC client app {} defines claim mapping {}={} but does not 
specify the related scope {}",
+                clientApp, internal, external, scope);
+    }
+
     @Override
     public boolean supports(final AttrReleasePolicyConf conf) {
         return DefaultAttrReleasePolicyConf.class.equals(conf.getClass());
     }
 
+    protected Map<String, BaseOidcScopeAttributeReleasePolicy> buildOidc(
+            final OIDCRPClientAppTO rp,
+            final DefaultAttrReleasePolicyConf conf) {
+
+        Map<String, BaseOidcScopeAttributeReleasePolicy> policies = new 
HashMap<>();
+
+        conf.getReleaseAttrs().forEach((internal, external) -> {
+            if 
(OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString()))
 {
+                if (rp.getScopes().contains(OIDCScopeConstants.PROFILE)) {
+                    policies.computeIfAbsent(
+                            OIDCScopeConstants.PROFILE,
+                            k -> new OidcProfileScopeAttributeReleasePolicy()).
+                            getClaimMappings().put(external.toString(), 
internal);
+                } else {
+                    warnMissingScope(rp.getName(), internal, external, 
OIDCScopeConstants.PROFILE);
+                }
+            } else if 
(OidcEmailScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString()))
 {
+                if (rp.getScopes().contains(OIDCScopeConstants.EMAIL)) {
+                    policies.computeIfAbsent(
+                            OIDCScopeConstants.EMAIL,
+                            k -> new OidcEmailScopeAttributeReleasePolicy()).
+                            getClaimMappings().put(external.toString(), 
internal);
+                } else {
+                    warnMissingScope(rp.getName(), internal, external, 
OIDCScopeConstants.EMAIL);
+                }
+            } else if 
(OidcAddressScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString()))
 {
+                if (rp.getScopes().contains(OIDCScopeConstants.ADDRESS)) {
+                    policies.computeIfAbsent(
+                            OIDCScopeConstants.ADDRESS,
+                            k -> new OidcAddressScopeAttributeReleasePolicy()).
+                            getClaimMappings().put(external.toString(), 
internal);
+                } else {
+                    warnMissingScope(rp.getName(), internal, external, 
OIDCScopeConstants.ADDRESS);
+                }
+            } else if 
(OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString()))
 {
+                if (rp.getScopes().contains(OIDCScopeConstants.PHONE)) {
+                    policies.computeIfAbsent(
+                            OIDCScopeConstants.PHONE,
+                            k -> new OidcPhoneScopeAttributeReleasePolicy()).
+                            getClaimMappings().put(external.toString(), 
internal);
+                } else {
+                    warnMissingScope(rp.getName(), internal, external, 
OIDCScopeConstants.PHONE);
+                }
+            } else {
+                BaseOidcScopeAttributeReleasePolicy custom = 
policies.computeIfAbsent(
+                        OIDCScopeConstants.SYNCOPE,
+                        k -> new OidcCustomScopeAttributeReleasePolicy(
+                                OIDCScopeConstants.SYNCOPE, new 
ArrayList<>()));
+
+                custom.getAllowedAttributes().add(external.toString());
+                custom.getClaimMappings().put(external.toString(), internal);
+            }
+        });
+
+        return policies;
+    }
+
+    protected Optional<DefaultRegisteredServiceConsentPolicy> 
buildConsentPolicy(
+            final AttrReleasePolicyTO policy,
+            final DefaultAttrReleasePolicyConf conf) {
+
+        if (conf.getExcludedAttrs().isEmpty() && 
conf.getIncludeOnlyAttrs().isEmpty()) {
+            return Optional.empty();
+        }
+
+        DefaultRegisteredServiceConsentPolicy consentPolicy = new 
DefaultRegisteredServiceConsentPolicy(
+                new HashSet<>(conf.getExcludedAttrs()), new 
HashSet<>(conf.getIncludeOnlyAttrs()));
+        consentPolicy.setOrder(policy.getOrder());
+        
consentPolicy.setStatus(TriStateBoolean.fromBoolean(policy.getStatus()));
+        return Optional.of(consentPolicy);
+    }
+
     @Override
-    public RegisteredServiceAttributeReleasePolicy build(final 
AttrReleasePolicyTO policy) {
+    public RegisteredServiceAttributeReleasePolicy build(final ClientAppTO 
app, final AttrReleasePolicyTO policy) {
         DefaultAttrReleasePolicyConf conf = (DefaultAttrReleasePolicyConf) 
policy.getConf();
 
+        Map<String, BaseOidcScopeAttributeReleasePolicy> oidc = null;
         ReturnMappedAttributeReleasePolicy returnMapped = null;
         if (!conf.getReleaseAttrs().isEmpty()) {
-            returnMapped = new ReturnMappedAttributeReleasePolicy();
-            returnMapped.setAllowedAttributes(conf.getReleaseAttrs());
+            if (app instanceof OIDCRPClientAppTO rp) {
+                oidc = buildOidc(rp, conf);
+            } else {
+                returnMapped = new ReturnMappedAttributeReleasePolicy();
+                returnMapped.setAllowedAttributes(conf.getReleaseAttrs());
+            }
         }
 
         ReturnAllowedAttributeReleasePolicy returnAllowed = null;
@@ -58,50 +162,64 @@ public class DefaultAttrReleaseMapper implements 
AttrReleaseMapper {
             returnAllowed.setAllowedAttributes(conf.getAllowedAttrs());
         }
 
-        AbstractRegisteredServiceAttributeReleasePolicy attributeReleasePolicy;
-        if (returnMapped == null && returnAllowed == null) {
-            attributeReleasePolicy = new DenyAllAttributeReleasePolicy();
-        } else if (returnMapped != null) {
-            attributeReleasePolicy = returnMapped;
+        ChainingAttributeReleasePolicy chain = new 
ChainingAttributeReleasePolicy();
+        AbstractRegisteredServiceAttributeReleasePolicy single = null;
+        if (oidc == null) {
+            if (returnMapped == null && returnAllowed == null) {
+                single = new DenyAllAttributeReleasePolicy();
+            } else if (returnMapped != null && returnAllowed == null) {
+                single = returnMapped;
+            } else if (returnMapped == null && returnAllowed != null) {
+                single = returnAllowed;
+            } else {
+                chain.addPolicies(returnMapped, returnAllowed);
+            }
         } else {
-            attributeReleasePolicy = returnAllowed;
+            if (oidc.size() == 1) {
+                single = oidc.values().iterator().next();
+            } else {
+                // if present, add the custom scope at the end of the chain
+                oidc.entrySet().stream().
+                        filter(entry -> 
!OIDCScopeConstants.SYNCOPE.equals(entry.getKey())).
+                        forEach(entry -> chain.addPolicies(entry.getValue()));
+                
Optional.ofNullable(oidc.get(OIDCScopeConstants.SYNCOPE)).ifPresent(chain::addPolicies);
+            }
         }
 
-        DefaultRegisteredServiceConsentPolicy consentPolicy = new 
DefaultRegisteredServiceConsentPolicy(
-                new HashSet<>(conf.getExcludedAttrs()), new 
HashSet<>(conf.getIncludeOnlyAttrs()));
-        consentPolicy.setOrder(policy.getOrder());
-        
consentPolicy.setStatus(TriStateBoolean.fromBoolean(policy.getStatus()));
-        attributeReleasePolicy.setConsentPolicy(consentPolicy);
+        Optional<DefaultRegisteredServiceConsentPolicy> consentPolicy = 
buildConsentPolicy(policy, conf);
+        if (!chain.getPolicies().isEmpty()) {
+            chain.getPolicies().stream().
+                    
filter(AbstractRegisteredServiceAttributeReleasePolicy.class::isInstance).
+                    
map(AbstractRegisteredServiceAttributeReleasePolicy.class::cast).
+                    forEach(p -> {
+                        consentPolicy.ifPresent(p::setConsentPolicy);
+                        
Optional.ofNullable(conf.getPrincipalIdAttr()).ifPresent(p::setPrincipalIdAttribute);
+                    });
 
-        if (conf.getPrincipalIdAttr() != null) {
-            
attributeReleasePolicy.setPrincipalIdAttribute(conf.getPrincipalIdAttr());
+            if (conf.getPrincipalAttrRepoConf() != null && 
!conf.getPrincipalAttrRepoConf().getAttrRepos().isEmpty()) {
+                DefaultAttrReleasePolicyConf.PrincipalAttrRepoConf parc = 
conf.getPrincipalAttrRepoConf();
+                
chain.setMergingPolicy(MergingStrategyTypes.valueOf(parc.getMergingStrategy().name()));
+            }
         }
+        Optional.ofNullable(single).ifPresent(p -> {
+            consentPolicy.ifPresent(p::setConsentPolicy);
+            
Optional.ofNullable(conf.getPrincipalIdAttr()).ifPresent(p::setPrincipalIdAttribute);
 
-        if (conf.getPrincipalAttrRepoConf() != null && 
!conf.getPrincipalAttrRepoConf().getAttrRepos().isEmpty()) {
-            DefaultAttrReleasePolicyConf.PrincipalAttrRepoConf parc = 
conf.getPrincipalAttrRepoConf();
+            if (conf.getPrincipalAttrRepoConf() != null && 
!conf.getPrincipalAttrRepoConf().getAttrRepos().isEmpty()) {
+                DefaultAttrReleasePolicyConf.PrincipalAttrRepoConf parc = 
conf.getPrincipalAttrRepoConf();
 
-            AbstractPrincipalAttributesRepository par = parc.getExpiration() > 0
-                    ? new 
CachingPrincipalAttributesRepository(parc.getTimeUnit().name(), 
parc.getExpiration())
-                    : new DefaultPrincipalAttributesRepository();
+                AbstractPrincipalAttributesRepository par = 
parc.getExpiration() > 0
+                        ? new 
CachingPrincipalAttributesRepository(parc.getTimeUnit().name(), 
parc.getExpiration())
+                        : new DefaultPrincipalAttributesRepository();
 
-            
par.setMergingStrategy(MergingStrategyTypes.valueOf(parc.getMergingStrategy().name()));
-            par.setIgnoreResolvedAttributes(par.isIgnoreResolvedAttributes());
-            par.setAttributeRepositoryIds(new HashSet<>(parc.getAttrRepos()));
-            attributeReleasePolicy.setPrincipalAttributesRepository(par);
-        }
+                
par.setMergingStrategy(MergingStrategyTypes.valueOf(parc.getMergingStrategy().name()));
+                
par.setIgnoreResolvedAttributes(par.isIgnoreResolvedAttributes());
+                par.setAttributeRepositoryIds(new 
HashSet<>(parc.getAttrRepos()));
 
-        if (returnMapped != null && returnAllowed != null) {
-            ChainingAttributeReleasePolicy chain = new 
ChainingAttributeReleasePolicy();
-            chain.addPolicies(returnMapped, returnAllowed);
-
-            if (conf.getPrincipalAttrRepoConf() != null && 
!conf.getPrincipalAttrRepoConf().getAttrRepos().isEmpty()) {
-                DefaultAttrReleasePolicyConf.PrincipalAttrRepoConf parc = 
conf.getPrincipalAttrRepoConf();
-                
chain.setMergingPolicy(MergingStrategyTypes.valueOf(parc.getMergingStrategy().name()));
+                p.setPrincipalAttributesRepository(par);
             }
+        });
 
-            return chain;
-        }
-
-        return attributeReleasePolicy;
+        return single == null ? chain : single;
     }
 }
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPClientAppTOMapper.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPClientAppTOMapper.java
index 7612dd40e9..2ae5e10c37 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPClientAppTOMapper.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPClientAppTOMapper.java
@@ -21,7 +21,6 @@ package org.apache.syncope.wa.starter.mapping;
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.OIDCScopeConstants;
@@ -30,11 +29,7 @@ import org.apache.syncope.common.lib.to.OIDCRPClientAppTO;
 import org.apache.syncope.common.lib.types.OIDCGrantType;
 import org.apache.syncope.common.lib.types.OIDCResponseType;
 import org.apache.syncope.common.lib.wa.WAClientApp;
-import org.apereo.cas.oidc.claims.OidcAddressScopeAttributeReleasePolicy;
-import org.apereo.cas.oidc.claims.OidcEmailScopeAttributeReleasePolicy;
-import org.apereo.cas.oidc.claims.OidcPhoneScopeAttributeReleasePolicy;
-import org.apereo.cas.oidc.claims.OidcProfileScopeAttributeReleasePolicy;
-import org.apereo.cas.services.BaseMappedAttributeReleasePolicy;
+import org.apereo.cas.oidc.claims.OidcCustomScopeAttributeReleasePolicy;
 import org.apereo.cas.services.ChainingAttributeReleasePolicy;
 import org.apereo.cas.services.OidcRegisteredService;
 import org.apereo.cas.services.RegisteredService;
@@ -46,7 +41,6 @@ import 
org.apereo.cas.services.RegisteredServiceProxyGrantingTicketExpirationPol
 import org.apereo.cas.services.RegisteredServiceProxyTicketExpirationPolicy;
 import org.apereo.cas.services.RegisteredServiceServiceTicketExpirationPolicy;
 import 
org.apereo.cas.services.RegisteredServiceTicketGrantingTicketExpirationPolicy;
-import org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy;
 
 public class OIDCRPClientAppTOMapper extends AbstractClientAppMapper {
 
@@ -98,40 +92,14 @@ public class OIDCRPClientAppTOMapper extends 
AbstractClientAppMapper {
 
         service.setScopes(new HashSet<>(rp.getScopes()));
 
-        Set<String> customClaims = new HashSet<>();
-        if (attributeReleasePolicy instanceof BaseMappedAttributeReleasePolicy 
baseMapped) {
-            customClaims.addAll(baseMapped.
-                    getAllowedAttributes().values().stream().
-                    map(Objects::toString).collect(Collectors.toSet()));
-        } else if (attributeReleasePolicy instanceof 
ReturnAllowedAttributeReleasePolicy returnAllowed) {
-            customClaims.addAll(returnAllowed.
-                    
getAllowedAttributes().stream().collect(Collectors.toSet()));
-        } else if (attributeReleasePolicy instanceof 
ChainingAttributeReleasePolicy chaining) {
-            chaining.getPolicies().stream().
-                    
filter(ReturnAllowedAttributeReleasePolicy.class::isInstance).
-                    
findFirst().map(ReturnAllowedAttributeReleasePolicy.class::cast).
-                    map(p -> 
p.getAllowedAttributes().stream().collect(Collectors.toSet())).
-                    ifPresent(customClaims::addAll);
-        }
-        if (rp.getScopes().contains(OIDCScopeConstants.PROFILE)) {
-            
customClaims.removeAll(OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
-        }
-        if (rp.getScopes().contains(OIDCScopeConstants.ADDRESS)) {
-            
customClaims.removeAll(OidcAddressScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
-        }
-        if (rp.getScopes().contains(OIDCScopeConstants.EMAIL)) {
-            
customClaims.removeAll(OidcEmailScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
-        }
-        if (rp.getScopes().contains(OIDCScopeConstants.PHONE)) {
-            
customClaims.removeAll(OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
-        }
+        if (attributeReleasePolicy instanceof 
OidcCustomScopeAttributeReleasePolicy
+                || (attributeReleasePolicy instanceof 
ChainingAttributeReleasePolicy chain
+                && 
chain.getPolicies().stream().anyMatch(OidcCustomScopeAttributeReleasePolicy.class::isInstance)))
 {
 
-        if (!customClaims.isEmpty()) {
             service.getScopes().add(OIDCScopeConstants.SYNCOPE);
         }
 
-        // never set attribute relase policy for OIDC services to avoid 
becoming scope-free for CAS
-        setPolicies(service, authPolicy, mfaPolicy, accessStrategy, null,
+        setPolicies(service, authPolicy, mfaPolicy, accessStrategy, 
attributeReleasePolicy,
                 tgtExpirationPolicy, stExpirationPolicy, 
tgtProxyExpirationPolicy, stProxyExpirationPolicy);
 
         return service;
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java
index 8c1dd47fc3..1e7b1a1932 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java
@@ -137,8 +137,9 @@ public class RegisteredServiceMapper {
         Optional<AttrReleaseMapper> attrReleaseMapper = 
attrReleaseMappers.stream().
                 filter(m -> m.supports(attrReleasePolicyTO.getConf())).
                 findFirst();
-        RegisteredServiceAttributeReleasePolicy attributeReleasePolicy =
-                attrReleaseMapper.map(mapper -> 
mapper.build(attrReleasePolicyTO)).orElse(null);
+        RegisteredServiceAttributeReleasePolicy attributeReleasePolicy = 
attrReleaseMapper.
+                map(mapper -> mapper.build(clientApp.getClientAppTO(), 
attrReleasePolicyTO)).
+                orElse(null);
 
         RegisteredServiceTicketGrantingTicketExpirationPolicy 
tgtExpirationPolicy = null;
         RegisteredServiceServiceTicketExpirationPolicy stExpirationPolicy = 
null;
diff --git 
a/wa/starter/src/test/java/org/apache/syncope/wa/starter/WAServiceRegistryTest.java
 
b/wa/starter/src/test/java/org/apache/syncope/wa/starter/WAServiceRegistryTest.java
index 665e62ff47..4d2baaf93b 100644
--- 
a/wa/starter/src/test/java/org/apache/syncope/wa/starter/WAServiceRegistryTest.java
+++ 
b/wa/starter/src/test/java/org/apache/syncope/wa/starter/WAServiceRegistryTest.java
@@ -56,7 +56,6 @@ import org.apereo.cas.services.RegisteredService;
 import org.apereo.cas.services.RegisteredServiceAccessStrategy;
 import org.apereo.cas.services.RegisteredServiceDelegatedAuthenticationPolicy;
 import org.apereo.cas.services.RegisteredServiceLogoutType;
-import org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy;
 import org.apereo.cas.services.ServicesManager;
 import org.apereo.cas.support.saml.services.SamlRegisteredService;
 import org.apereo.cas.util.RandomUtils;
@@ -190,7 +189,7 @@ public class WAServiceRegistryTest extends AbstractTest {
         
assertTrue(oidc.getAuthenticationPolicy().getRequiredAuthenticationHandlers().contains("TestAuthModule"));
         
assertTrue(((AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria)
 oidc.
                 getAuthenticationPolicy().getCriteria()).isTryAll());
-        assertTrue(oidc.getAttributeReleasePolicy() instanceof 
ReturnAllowedAttributeReleasePolicy);
+        assertTrue(oidc.getAttributeReleasePolicy() instanceof 
DenyAllAttributeReleasePolicy);
         
assertEquals(RegisteredServiceLogoutType.valueOf(oidcrpto.getLogoutType().name()),
 oidc.getLogoutType());
 
         // 5. more client with different attributes 
diff --git a/wa/starter/src/test/resources/debug/wa-debug.properties 
b/wa/starter/src/test/resources/debug/wa-debug.properties
index 6d144fc690..22a486ef8f 100644
--- a/wa/starter/src/test/resources/debug/wa-debug.properties
+++ b/wa/starter/src/test/resources/debug/wa-debug.properties
@@ -16,8 +16,8 @@
 # under the License.
 spring.main.allow-circular-references=true
 
-keymaster.address=http://localhost:9080/syncope/rest/keymaster
-#keymaster.address=https://localhost:9443/syncope/rest/keymaster
+#keymaster.address=http://localhost:9080/syncope/rest/keymaster
+keymaster.address=https://localhost:9443/syncope/rest/keymaster
 keymaster.username=${anonymousUser}
 keymaster.password=${anonymousKey}
 
@@ -27,7 +27,8 @@ cas.server.name=http://localhost:8080
 cas.server.prefix=${cas.server.name}/syncope-wa
 cas.authn.accept.users=admin::password
 
-cas.authn.syncope.url=http://localhost:9080/syncope
+#cas.authn.syncope.url=http://localhost:9080/syncope
+cas.authn.syncope.url=https://localhost:9443/syncope
 cas.authn.syncope.name=DefaultSyncopeAuthModule
 
 ##


Reply via email to