From ac46f44bd5d4d3d32a6aedb04621fc7013ad4ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Mon, 24 Jul 2023 13:52:13 +0200 Subject: [PATCH 1/7] Fixing OIDC scope management for client apps --- .../src/main/resources/log4j2.xml | 4 ++ .../syncope/fit/sra/OAUTH2SRAITCase.java | 7 +--- .../apache/syncope/fit/sra/OIDCSRAITCase.java | 40 ++++++++++++++----- .../syncope/wa/starter/config/WAContext.java | 6 +-- .../mapping/CASSPClientAppTOMapper.java | 4 +- .../wa/starter/mapping/ClientAppMapper.java | 4 +- .../mapping/OIDCRPClientAppTOMapper.java | 30 ++++++-------- .../mapping/RegisteredServiceMapper.java | 17 +++----- .../mapping/SAML2SPClientAppTOMapper.java | 4 +- wa/starter/src/main/resources/wa.properties | 1 + 10 files changed, 58 insertions(+), 59 deletions(-) diff --git a/fit/wa-reference/src/main/resources/log4j2.xml b/fit/wa-reference/src/main/resources/log4j2.xml index 9254c8ecab..e1468f37ed 100644 --- a/fit/wa-reference/src/main/resources/log4j2.xml +++ b/fit/wa-reference/src/main/resources/log4j2.xml @@ -52,6 +52,10 @@ under the License. + + + + diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java index bcd7369cc0..f356adf8bc 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java @@ -19,16 +19,13 @@ package org.apache.syncope.fit.sra; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; -import java.text.ParseException; import java.util.Properties; import java.util.concurrent.TimeoutException; import org.apache.http.HttpStatus; @@ -70,7 +67,7 @@ protected void checkLogout(final CloseableHttpResponse response) { } @Override - protected void checkIdToken(final JsonNode json) throws ParseException { - assertFalse(json.has("id_token")); + protected boolean checkIdToken() { + return false; } } diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java index f51f8669d2..220423df81 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java @@ -61,6 +61,7 @@ import org.apache.http.util.EntityUtils; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apache.syncope.common.lib.types.ClientAppType; +import org.apache.syncope.common.lib.types.OIDCScope; import org.apache.syncope.common.lib.types.OIDCSubjectType; import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.syncope.common.rest.api.service.wa.WAConfigService; @@ -121,6 +122,9 @@ protected static void oidcClientAppSetup( clientApp.setLogoutUri(SRA_ADDRESS + "/logout"); clientApp.setAuthPolicy(getAuthPolicy().getKey()); clientApp.setAttrReleasePolicy(getAttrReleasePolicy().getKey()); + clientApp.getScopes().add(OIDCScope.OPENID); + clientApp.getScopes().add(OIDCScope.PROFILE); + clientApp.getScopes().add(OIDCScope.EMAIL); CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, clientApp); WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); @@ -222,16 +226,26 @@ public void web() throws IOException { checkLogout(response); } - protected void checkIdToken(final JsonNode json) throws ParseException { - SignedJWT idToken = SignedJWT.parse(json.get("id_token").asText()); - assertNotNull(idToken); - JWTClaimsSet idTokenClaimsSet = idToken.getJWTClaimsSet(); - assertEquals("verdi", idTokenClaimsSet.getStringClaim("preferred_username")); + private void checkJWT(final String token, final boolean idToken) throws ParseException { + assertNotNull(token); + SignedJWT jwt = SignedJWT.parse(token); + assertNotNull(jwt); + JWTClaimsSet idTokenClaimsSet = jwt.getJWTClaimsSet(); + assertEquals("verdi", idTokenClaimsSet.getSubject()); + if (idToken) { + assertEquals("verdi", idTokenClaimsSet.getStringClaim("preferred_username")); + } assertEquals("verdi@syncope.org", idTokenClaimsSet.getStringClaim("email")); assertEquals("Verdi", idTokenClaimsSet.getStringClaim("family_name")); assertEquals("Giuseppe", idTokenClaimsSet.getStringClaim("given_name")); assertEquals("Giuseppe Verdi", idTokenClaimsSet.getStringClaim("name")); - assertEquals(Set.of("root", "child", "citizen"), Set.of(idTokenClaimsSet.getStringArrayClaim("groups"))); + if (!idToken) { + assertEquals(Set.of("root", "child", "citizen"), Set.of(idTokenClaimsSet.getStringArrayClaim("groups"))); + } + } + + protected boolean checkIdToken() { + return true; } @Test @@ -249,19 +263,23 @@ public void rest() throws IOException, ParseException { param("client_secret", CLIENT_SECRET). param("username", "verdi"). param("password", "password"). - param("scope", "openid profile email address phone offline_access syncope"); + param("scope", "openid profile email syncope"); response = WebClient.create(TOKEN_URI).post(form); assertEquals(HttpStatus.SC_OK, response.getStatus()); assertTrue(response.getHeaderString(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON)); JsonNode json = MAPPER.readTree(response.readEntity(String.class)); - // 1a. verify id_token - checkIdToken(json); + if (checkIdToken()) { + // 1a. take and verify id_token + String idToken = json.get("id_token").asText(); + assertNotNull(idToken); + checkJWT(idToken, true); + } - // 1b. take access_token + // 1b. take and verify access_token String accessToken = json.get("access_token").asText(); - assertNotNull(accessToken); + checkJWT(accessToken, false); // 2. access protected route client = WebClient.create(SRA_ADDRESS + "/protected/post"). diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java index 8871cfb159..98f5e9a365 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java @@ -204,8 +204,7 @@ public RegisteredServiceMapper registeredServiceMapper( final List accessMappers, final List attrReleaseMappers, final List ticketExpirationMappers, - final List clientAppMappers, - final CasConfigurationProperties properties) { + final List clientAppMappers) { return new RegisteredServiceMapper( Optional.ofNullable(casProperties.getAuthn().getPac4j().getCore().getName()). @@ -216,8 +215,7 @@ public RegisteredServiceMapper registeredServiceMapper( accessMappers, attrReleaseMappers, ticketExpirationMappers, - clientAppMappers, - properties); + clientAppMappers); } @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPClientAppTOMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPClientAppTOMapper.java index 26b8042b80..02f3fd505f 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPClientAppTOMapper.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPClientAppTOMapper.java @@ -21,7 +21,6 @@ import org.apache.syncope.common.lib.to.CASSPClientAppTO; import org.apache.syncope.common.lib.to.ClientAppTO; import org.apache.syncope.common.lib.wa.WAClientApp; -import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.services.CasRegisteredService; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategy; @@ -50,8 +49,7 @@ public RegisteredService map( final RegisteredServiceTicketGrantingTicketExpirationPolicy tgtExpirationPolicy, final RegisteredServiceServiceTicketExpirationPolicy stExpirationPolicy, final RegisteredServiceProxyGrantingTicketExpirationPolicy tgtProxyExpirationPolicy, - final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy, - final CasConfigurationProperties properties) { + final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy) { CASSPClientAppTO cas = CASSPClientAppTO.class.cast(clientApp.getClientAppTO()); diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java index e9fb97a459..79cf79a6de 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java @@ -20,7 +20,6 @@ import org.apache.syncope.common.lib.to.ClientAppTO; import org.apache.syncope.common.lib.wa.WAClientApp; -import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategy; import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy; @@ -44,6 +43,5 @@ RegisteredService map( RegisteredServiceTicketGrantingTicketExpirationPolicy tgtExpirationPolicy, RegisteredServiceServiceTicketExpirationPolicy stExpirationPolicy, RegisteredServiceProxyGrantingTicketExpirationPolicy tgtProxyExpirationPolicy, - RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy, - CasConfigurationProperties properties); + RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy); } 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 e0535e1e3d..3cecfba16a 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 @@ -19,18 +19,15 @@ package org.apache.syncope.wa.starter.mapping; import java.util.HashSet; -import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.apache.syncope.common.lib.to.ClientAppTO; 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.types.OIDCScope; import org.apache.syncope.common.lib.wa.WAClientApp; -import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.oidc.claims.OidcAddressScopeAttributeReleasePolicy; import org.apereo.cas.oidc.claims.OidcCustomScopeAttributeReleasePolicy; import org.apereo.cas.oidc.claims.OidcEmailScopeAttributeReleasePolicy; @@ -70,8 +67,7 @@ public RegisteredService map( final RegisteredServiceTicketGrantingTicketExpirationPolicy tgtExpirationPolicy, final RegisteredServiceServiceTicketExpirationPolicy stExpirationPolicy, final RegisteredServiceProxyGrantingTicketExpirationPolicy tgtProxyExpirationPolicy, - final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy, - final CasConfigurationProperties properties) { + final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy) { OIDCRPClientAppTO rp = OIDCRPClientAppTO.class.cast(clientApp.getClientAppTO()); OidcRegisteredService service = new OidcRegisteredService(); @@ -107,6 +103,10 @@ public RegisteredService map( } } + service.setScopes(rp.getScopes().stream(). + map(s -> s.name().toLowerCase()). + collect(Collectors.toCollection(HashSet::new))); + if (rp.getScopes().contains(OIDCScope.OPENID)) { chain.addPolicies(new OidcOpenIdScopeAttributeReleasePolicy()); } @@ -124,15 +124,15 @@ public RegisteredService map( } Set customClaims = new HashSet<>(); - if (attributeReleasePolicy instanceof BaseMappedAttributeReleasePolicy) { - customClaims.addAll(((BaseMappedAttributeReleasePolicy) attributeReleasePolicy). + if (attributeReleasePolicy instanceof BaseMappedAttributeReleasePolicy baseMapped) { + customClaims.addAll(baseMapped. getAllowedAttributes().values().stream(). map(Objects::toString).collect(Collectors.toSet())); - } else if (attributeReleasePolicy instanceof ReturnAllowedAttributeReleasePolicy) { - customClaims.addAll(((ReturnAllowedAttributeReleasePolicy) attributeReleasePolicy). + } else if (attributeReleasePolicy instanceof ReturnAllowedAttributeReleasePolicy returnAllowed) { + customClaims.addAll(returnAllowed. getAllowedAttributes().stream().collect(Collectors.toSet())); - } else if (attributeReleasePolicy instanceof ChainingAttributeReleasePolicy) { - ((ChainingAttributeReleasePolicy) attributeReleasePolicy).getPolicies().stream(). + } 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())). @@ -151,15 +151,9 @@ public RegisteredService map( customClaims.removeAll(OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } if (!customClaims.isEmpty()) { - List supportedClaims = properties.getAuthn().getOidc().getDiscovery().getClaims(); - if (!supportedClaims.containsAll(customClaims)) { - properties.getAuthn().getOidc().getDiscovery().setClaims( - Stream.concat(supportedClaims.stream(), customClaims.stream()). - distinct().collect(Collectors.toList())); - } - chain.addPolicies(new OidcCustomScopeAttributeReleasePolicy( CUSTOM_SCOPE, customClaims.stream().collect(Collectors.toList()))); + service.getScopes().add(CUSTOM_SCOPE); } setPolicies(service, authPolicy, mfaPolicy, accessStrategy, chain, 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 c7e8ca34e9..f5ff9024b5 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 @@ -25,7 +25,6 @@ import org.apache.syncope.common.lib.wa.WAClientApp; import org.apereo.cas.authentication.AuthenticationEventExecutionPlan; import org.apereo.cas.authentication.MultifactorAuthenticationProvider; -import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategy; @@ -43,7 +42,7 @@ public class RegisteredServiceMapper { - private static final Logger LOG = LoggerFactory.getLogger(RegisteredServiceMapper.class); + protected static final Logger LOG = LoggerFactory.getLogger(RegisteredServiceMapper.class); protected final String pac4jCoreName; @@ -61,8 +60,6 @@ public class RegisteredServiceMapper { protected final List clientAppMappers; - protected final CasConfigurationProperties properties; - public RegisteredServiceMapper( final String pac4jCoreName, final ObjectProvider authEventExecPlan, @@ -71,8 +68,7 @@ public RegisteredServiceMapper( final List accessMappers, final List attrReleaseMappers, final List ticketExpirationMappers, - final List clientAppMappers, - final CasConfigurationProperties properties) { + final List clientAppMappers) { this.pac4jCoreName = pac4jCoreName; this.authEventExecPlan = authEventExecPlan; @@ -82,7 +78,6 @@ public RegisteredServiceMapper( this.attrReleaseMappers = attrReleaseMappers; this.ticketExpirationMappers = ticketExpirationMappers; this.clientAppMappers = clientAppMappers; - this.properties = properties; } public RegisteredService toRegisteredService(final WAClientApp clientApp) { @@ -122,9 +117,8 @@ public RegisteredService toRegisteredService(final WAClientApp clientApp) { if (accessStrategy == null) { accessStrategy = new DefaultRegisteredServiceAccessStrategy(); } - if (accessStrategy instanceof DefaultRegisteredServiceAccessStrategy) { - ((DefaultRegisteredServiceAccessStrategy) accessStrategy). - setDelegatedAuthenticationPolicy(delegatedAuthPolicy); + if (accessStrategy instanceof DefaultRegisteredServiceAccessStrategy defaultRSAS) { + defaultRSAS.setDelegatedAuthenticationPolicy(delegatedAuthPolicy); } else { LOG.warn("Could not set delegated auth policy because access strategy is instance of {}", accessStrategy.getClass().getName()); @@ -168,7 +162,6 @@ public RegisteredService toRegisteredService(final WAClientApp clientApp) { tgtExpirationPolicy, stExpirationPolicy, tgtProxyExpirationPolicy, - stProxyExpirationPolicy, - properties); + stProxyExpirationPolicy); } } diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java index 4510592800..97cec97d6f 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java @@ -23,7 +23,6 @@ import org.apache.syncope.common.lib.to.ClientAppTO; import org.apache.syncope.common.lib.to.SAML2SPClientAppTO; import org.apache.syncope.common.lib.wa.WAClientApp; -import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategy; import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy; @@ -53,8 +52,7 @@ public RegisteredService map( final RegisteredServiceTicketGrantingTicketExpirationPolicy tgtExpirationPolicy, final RegisteredServiceServiceTicketExpirationPolicy stExpirationPolicy, final RegisteredServiceProxyGrantingTicketExpirationPolicy tgtProxyExpirationPolicy, - final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy, - final CasConfigurationProperties properties) { + final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy) { SAML2SPClientAppTO sp = SAML2SPClientAppTO.class.cast(clientApp.getClientAppTO()); SamlRegisteredService service = new SamlRegisteredService(); diff --git a/wa/starter/src/main/resources/wa.properties b/wa/starter/src/main/resources/wa.properties index b9115efa13..5268bc442a 100644 --- a/wa/starter/src/main/resources/wa.properties +++ b/wa/starter/src/main/resources/wa.properties @@ -83,6 +83,7 @@ cas.authn.saml-idp.metadata.http.metadata-backup-location=file:${syncope.conf.di cas.authn.oidc.core.issuer=${cas.server.prefix}/oidc cas.authn.oidc.discovery.id-token-signing-alg-values-supported=RS256,RS384,RS512,PS256,PS384,PS512,ES256,ES384,ES512,HS256,HS384,HS512 cas.authn.oidc.discovery.user-info-signing-alg-values-supported=RS256,RS384,RS512,PS256,PS384,PS512,ES256,ES384,ES512,HS256,HS384,HS512 +cas.authn.oidc.discovery.scopes=openid,profile,email,address,phone,syncope cas.authn.oauth.core.user-profile-view-type=FLAT # Disable access to the login endpoint From 22467ffd2dda53347cfae05dc8151aa4a67b7e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Mon, 24 Jul 2023 16:34:55 +0200 Subject: [PATCH 2/7] Supporting customization of OIDC scopes in OIDCC4UI --- .../console/wizards/AttrWizardBuilder.java | 2 +- .../panels/OIDCProvidersDirectoryPanel.java | 5 +- .../wizards/OIDCProviderWizardBuilder.java | 124 ++++++------------ .../OIDCProviderWizardBuilder$OPContinue.html | 2 + .../common/lib/to/OIDCC4UIProviderTO.java | 8 ++ .../syncope/core/logic/OIDCC4UILogic.java | 10 +- .../core/logic/oidc/OIDCClientCache.java | 4 +- .../api/entity/OIDCC4UIProvider.java | 4 + .../jpa/entity/JPAOIDCC4UIProvider.java | 17 +++ .../data/OIDCC4UIProviderDataBinderImpl.java | 2 + .../apache/syncope/fit/ui/OIDC4UIITCase.java | 8 ++ .../mapping/OIDCRPClientAppTOMapper.java | 1 + 12 files changed, 93 insertions(+), 94 deletions(-) diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/AttrWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/AttrWizardBuilder.java index 8eb63d4ef7..16009194b5 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/AttrWizardBuilder.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/AttrWizardBuilder.java @@ -44,7 +44,7 @@ protected WizardModel buildModelSteps(final Attr modelObject, final WizardModel protected static class AttrStep extends WizardStep { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 8145346883748040158L; AttrStep(final Attr modelObject) { AjaxTextFieldPanel schema = new AjaxTextFieldPanel( diff --git a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java index 283e5bbc48..b88d836840 100644 --- a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java +++ b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java @@ -137,9 +137,8 @@ protected ActionLinksTogglePanel actionTogglePanel() { @Override public void updateHeader(final AjaxRequestTarget target, final Serializable object) { - if (object instanceof OIDCC4UIProviderTO) { - setHeader(target, - StringUtils.abbreviate(((OIDCC4UIProviderTO) object).getName(), HEADER_FIRST_ABBREVIATION)); + if (object instanceof OIDCC4UIProviderTO provider) { + setHeader(target, StringUtils.abbreviate(provider.getName(), HEADER_FIRST_ABBREVIATION)); } else { super.updateHeader(target, object); } diff --git a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java index 6757241a1c..7c80d5ae03 100644 --- a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java +++ b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java @@ -23,6 +23,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.SyncopeConsoleSession; @@ -30,6 +31,7 @@ import org.apache.syncope.client.console.panels.OIDCProvidersDirectoryPanel; import org.apache.syncope.client.console.rest.ImplementationRestClient; import org.apache.syncope.client.console.rest.OIDCProviderRestClient; +import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel; import org.apache.syncope.client.console.wizards.mapping.ItemTransformersTogglePanel; import org.apache.syncope.client.console.wizards.mapping.JEXLTransformersTogglePanel; import org.apache.syncope.client.console.wizards.mapping.OIDCProviderMappingPanel; @@ -42,6 +44,7 @@ import org.apache.syncope.common.lib.to.ImplementationTO; import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO; import org.apache.syncope.common.lib.types.OIDCClientImplementationType; +import org.apache.syncope.common.lib.types.OIDCScope; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.wizard.WizardModel; @@ -107,11 +110,7 @@ protected Serializable onApplyInternal(final OIDCC4UIProviderTO modelObject) { @Override protected WizardModel buildModelSteps(final OIDCC4UIProviderTO modelObject, final WizardModel wizardModel) { wizardModel.add(new OP(modelObject)); - if (modelObject.getKey() == null) { - wizardModel.add(new OPContinue(modelObject)); - } else { - wizardModel.add(new OPContinue(modelObject, true)); - } + wizardModel.add(new OPContinue(modelObject, modelObject.getKey() != null)); Mapping mapping = new Mapping(); mapping.setOutputMarkupId(true); @@ -145,6 +144,7 @@ protected void sendWarning(final String message) { @Override protected Future> execute( final Callable> future) { + return SyncopeConsoleSession.get().execute(future); } @@ -200,61 +200,63 @@ public static class OPContinue extends WizardStep { private static final long serialVersionUID = -7087008312629522790L; - public OPContinue(final OIDCC4UIProviderTO opTO) { - final WebMarkupContainer content = new WebMarkupContainer("content"); + public OPContinue(final OIDCC4UIProviderTO opTO, final boolean readOnly) { this.setOutputMarkupId(true); + + WebMarkupContainer content = new WebMarkupContainer("content"); content.setOutputMarkupId(true); add(content); UrlValidator urlValidator = new UrlValidator(); - final AjaxTextFieldPanel issuer = new AjaxTextFieldPanel( + + AjaxTextFieldPanel issuer = new AjaxTextFieldPanel( "issuer", "issuer", new PropertyModel<>(opTO, "issuer")); issuer.addValidator(urlValidator); issuer.addRequiredLabel(); - content.add(issuer); + content.add(issuer.setReadOnly(readOnly)); - final AjaxCheckBoxPanel hasDiscovery = new AjaxCheckBoxPanel( + AjaxCheckBoxPanel hasDiscovery = new AjaxCheckBoxPanel( "hasDiscovery", "hasDiscovery", new PropertyModel<>(opTO, "hasDiscovery")); content.add(hasDiscovery); - final AjaxTextFieldPanel authorizationEndpoint = new AjaxTextFieldPanel("authorizationEndpoint", + AjaxTextFieldPanel authorizationEndpoint = new AjaxTextFieldPanel("authorizationEndpoint", "authorizationEndpoint", new PropertyModel<>(opTO, "authorizationEndpoint")); authorizationEndpoint.addRequiredLabel(); authorizationEndpoint.addValidator(urlValidator); - content.add(authorizationEndpoint); + content.add(authorizationEndpoint.setReadOnly(readOnly)); - final AjaxTextFieldPanel userinfoEndpoint = new AjaxTextFieldPanel("userinfoEndpoint", + AjaxTextFieldPanel userinfoEndpoint = new AjaxTextFieldPanel("userinfoEndpoint", "userinfoEndpoint", new PropertyModel<>(opTO, "userinfoEndpoint")); userinfoEndpoint.addValidator(urlValidator); - content.add(userinfoEndpoint); + content.add(userinfoEndpoint.setReadOnly(readOnly)); - final AjaxTextFieldPanel tokenEndpoint = new AjaxTextFieldPanel("tokenEndpoint", + AjaxTextFieldPanel tokenEndpoint = new AjaxTextFieldPanel("tokenEndpoint", "tokenEndpoint", new PropertyModel<>(opTO, "tokenEndpoint")); tokenEndpoint.addRequiredLabel(); tokenEndpoint.addValidator(urlValidator); - content.add(tokenEndpoint); + content.add(tokenEndpoint.setReadOnly(readOnly)); - final AjaxTextFieldPanel jwksUri = new AjaxTextFieldPanel("jwksUri", + AjaxTextFieldPanel jwksUri = new AjaxTextFieldPanel("jwksUri", "jwksUri", new PropertyModel<>(opTO, "jwksUri")); jwksUri.addRequiredLabel(); jwksUri.addValidator(urlValidator); - content.add(jwksUri); + content.add(jwksUri.setReadOnly(readOnly)); - final AjaxTextFieldPanel endSessionEndpoint = new AjaxTextFieldPanel("endSessionEndpoint", + AjaxTextFieldPanel endSessionEndpoint = new AjaxTextFieldPanel("endSessionEndpoint", "endSessionEndpoint", new PropertyModel<>(opTO, "endSessionEndpoint")); endSessionEndpoint.addValidator(urlValidator); - content.add(endSessionEndpoint); + content.add(endSessionEndpoint.setReadOnly(readOnly)); - final WebMarkupContainer visibleParam = new WebMarkupContainer("visibleParams"); - visibleParam.setOutputMarkupPlaceholderTag(true); - visibleParam.add(authorizationEndpoint); - visibleParam.add(userinfoEndpoint); - visibleParam.add(tokenEndpoint); - visibleParam.add(jwksUri); - visibleParam.add(endSessionEndpoint); - content.add(visibleParam); + WebMarkupContainer visibleParams = new WebMarkupContainer("visibleParams"); + visibleParams.setOutputMarkupPlaceholderTag(true); + visibleParams.add(authorizationEndpoint); + visibleParams.add(userinfoEndpoint); + visibleParams.add(tokenEndpoint); + visibleParams.add(jwksUri); + visibleParams.add(endSessionEndpoint); + content.add(visibleParams); - showHide(hasDiscovery, visibleParam); + showHide(hasDiscovery, visibleParams); hasDiscovery.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { @@ -262,70 +264,20 @@ public OPContinue(final OIDCC4UIProviderTO opTO) { @Override protected void onUpdate(final AjaxRequestTarget target) { - showHide(hasDiscovery, visibleParam); - target.add(visibleParam); + showHide(hasDiscovery, visibleParams); + target.add(visibleParams); } }); - } - - public OPContinue(final OIDCC4UIProviderTO opTO, final boolean readOnly) { - WebMarkupContainer content = new WebMarkupContainer("content"); - this.setOutputMarkupId(true); - content.setOutputMarkupId(true); - add(content); - final AjaxTextFieldPanel issuer = new AjaxTextFieldPanel( - "issuer", "issuer", new PropertyModel<>(opTO, "issuer")); - issuer.setReadOnly(readOnly); - content.add(issuer); - - final AjaxCheckBoxPanel hasDiscovery = new AjaxCheckBoxPanel( - "hasDiscovery", "hasDiscovery", new PropertyModel<>(opTO, "hasDiscovery")); - hasDiscovery.setReadOnly(readOnly); - content.add(hasDiscovery); - - final AjaxTextFieldPanel authorizationEndpoint = new AjaxTextFieldPanel("authorizationEndpoint", - "authorizationEndpoint", new PropertyModel<>(opTO, "authorizationEndpoint")); - authorizationEndpoint.setReadOnly(readOnly); - content.add(authorizationEndpoint); - - final AjaxTextFieldPanel userinfoEndpoint = new AjaxTextFieldPanel("userinfoEndpoint", - "userinfoEndpoint", new PropertyModel<>(opTO, "userinfoEndpoint")); - userinfoEndpoint.setReadOnly(readOnly); - content.add(userinfoEndpoint); - - final AjaxTextFieldPanel tokenEndpoint = new AjaxTextFieldPanel("tokenEndpoint", - "tokenEndpoint", new PropertyModel<>(opTO, "tokenEndpoint")); - tokenEndpoint.setReadOnly(readOnly); - content.add(tokenEndpoint); - - final AjaxTextFieldPanel jwksUri = new AjaxTextFieldPanel("jwksUri", - "jwksUri", new PropertyModel<>(opTO, "jwksUri")); - jwksUri.setReadOnly(readOnly); - content.add(jwksUri); - - final AjaxTextFieldPanel endSessionEndpoint = new AjaxTextFieldPanel("endSessionEndpoint", - "endSessionEndpoint", new PropertyModel<>(opTO, "endSessionEndpoint")); - endSessionEndpoint.setReadOnly(readOnly); - content.add(endSessionEndpoint); - - final WebMarkupContainer visibleParam = new WebMarkupContainer("visibleParams"); - visibleParam.setOutputMarkupPlaceholderTag(true); - visibleParam.add(authorizationEndpoint); - visibleParam.add(userinfoEndpoint); - visibleParam.add(tokenEndpoint); - visibleParam.add(jwksUri); - visibleParam.add(endSessionEndpoint); - content.add(visibleParam); + AjaxTextFieldPanel value = new AjaxTextFieldPanel("panel", "scopes", new Model<>()); + value.setChoices(Stream.of(OIDCScope.values()).map(s -> s.name().toLowerCase()).toList()); + content.add(new MultiFieldPanel.Builder( + new PropertyModel<>(opTO, "scopes")).build("scopes", "scopes", value)); } } private static void showHide(final AjaxCheckBoxPanel hasDiscovery, final WebMarkupContainer visibleParams) { - if (hasDiscovery.getField().getValue().equals("false")) { - visibleParams.setVisible(true); - } else { - visibleParams.setVisible(false); - } + visibleParams.setVisible("false".equals(hasDiscovery.getField().getValue())); } /** diff --git a/ext/oidcc4ui/client-console/src/main/resources/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder$OPContinue.html b/ext/oidcc4ui/client-console/src/main/resources/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder$OPContinue.html index 0f8de216f4..e36e9d2eee 100644 --- a/ext/oidcc4ui/client-console/src/main/resources/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder$OPContinue.html +++ b/ext/oidcc4ui/client-console/src/main/resources/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder$OPContinue.html @@ -29,6 +29,8 @@ [userinfoEndpoint] [endSessionEndpoint] + + diff --git a/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/to/OIDCC4UIProviderTO.java b/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/to/OIDCC4UIProviderTO.java index aaea125970..33acb7a9ce 100644 --- a/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/to/OIDCC4UIProviderTO.java +++ b/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/to/OIDCC4UIProviderTO.java @@ -48,6 +48,8 @@ public class OIDCC4UIProviderTO extends ItemContainer implements EntityTO { private String endSessionEndpoint; + private final List scopes = new ArrayList<>(); + private boolean hasDiscovery; private boolean createUnmatching; @@ -143,6 +145,12 @@ public void setEndSessionEndpoint(final String endSessionEndpoint) { this.endSessionEndpoint = endSessionEndpoint; } + @JacksonXmlElementWrapper(localName = "scopes") + @JacksonXmlProperty(localName = "scope") + public List getScopes() { + return scopes; + } + public UserTO getUserTemplate() { return userTemplate; } diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java index ce4faec779..e4f974bb91 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java @@ -139,6 +139,7 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat // 2. get OpenID Connect tokens String idTokenHint; JWTClaimsSet idToken; + JWTClaimsSet accessToken; try { OidcCredentials credentials = new OidcCredentials(); credentials.setCode(new AuthorizationCode(authorizationCode)); @@ -149,6 +150,8 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat idToken = credentials.getIdToken().getJWTClaimsSet(); idTokenHint = credentials.getIdToken().serialize(); + + accessToken = SignedJWT.parse(credentials.getAccessToken().getValue()).getJWTClaimsSet(); } catch (Exception e) { LOG.error("While validating Token Response", e); SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown); @@ -166,9 +169,10 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat Attr attrTO = new Attr(); attrTO.setSchema(item.getExtAttrName()); - String value = idToken.getClaim(item.getExtAttrName()) == null - ? null - : idToken.getClaim(item.getExtAttrName()).toString(); + String value = Optional.ofNullable(idToken.getClaim(item.getExtAttrName())). + or(() -> Optional.ofNullable(accessToken.getClaim(item.getExtAttrName()))). + map(Object::toString). + orElse(null); if (value != null) { attrTO.getValues().add(value); loginResponse.getAttrs().add(attrTO); diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java index 19472c8638..a4f67b3aba 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Function; +import java.util.stream.Collectors; import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO; import org.apache.syncope.core.persistence.api.entity.OIDCC4UIProvider; import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver; @@ -74,6 +75,7 @@ public static void importMetadata(final OIDCC4UIProviderTO opTO) Optional.ofNullable(metadata.getUserInfoEndpointURI()).map(URI::toASCIIString).orElse(null)); opTO.setEndSessionEndpoint( Optional.ofNullable(metadata.getEndSessionEndpointURI()).map(URI::toASCIIString).orElse(null)); + Optional.ofNullable(metadata.getScopes()).ifPresent(s -> opTO.getScopes().addAll(s.toStringList())); } protected final List cache = Collections.synchronizedList(new ArrayList<>()); @@ -103,7 +105,7 @@ public OidcClient add(final OIDCC4UIProvider op, final String callbackUrl) { cfg.setDiscoveryURI(DISCOVERY_URI.apply(op.getIssuer())); cfg.setPreferredJwsAlgorithm(JWSAlgorithm.HS256); cfg.setOpMetadataResolver(new StaticOidcOpMetadataResolver(cfg, metadata)); - cfg.setScope("openid profile email address phone offline_access"); + cfg.setScope(op.getScopes().stream().collect(Collectors.joining(" "))); cfg.setUseNonce(false); cfg.setSessionLogoutHandler(new NoOpSessionLogoutHandler()); diff --git a/ext/oidcc4ui/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCC4UIProvider.java b/ext/oidcc4ui/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCC4UIProvider.java index 325ec18bee..07df700a16 100644 --- a/ext/oidcc4ui/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCC4UIProvider.java +++ b/ext/oidcc4ui/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCC4UIProvider.java @@ -60,6 +60,10 @@ public interface OIDCC4UIProvider extends Entity { void setEndSessionEndpoint(String endSessionEndpoint); + List getScopes(); + + void setScopes(List scopes); + boolean getHasDiscovery(); void setHasDiscovery(boolean hasDiscovery); diff --git a/ext/oidcc4ui/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCC4UIProvider.java b/ext/oidcc4ui/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCC4UIProvider.java index 48bf85271c..d5e5025977 100644 --- a/ext/oidcc4ui/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCC4UIProvider.java +++ b/ext/oidcc4ui/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCC4UIProvider.java @@ -40,6 +40,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.types.OIDCClientImplementationType; import org.apache.syncope.core.persistence.api.entity.Implementation; @@ -47,6 +49,7 @@ import org.apache.syncope.core.persistence.api.entity.OIDCC4UIUserTemplate; import org.apache.syncope.core.persistence.jpa.validation.entity.OIDCC4UIProviderCheck; import org.apache.syncope.core.provisioning.api.serialization.POJOHelper; +import org.springframework.util.CollectionUtils; @Entity @Table(name = JPAOIDCC4UIProvider.TABLE) @@ -85,6 +88,8 @@ public class JPAOIDCC4UIProvider extends AbstractGeneratedKeyEntity implements O @Column(nullable = true) private String endSessionEndpoint; + private String scopes; + @Column(nullable = false) private boolean hasDiscovery; @@ -204,6 +209,18 @@ public void setEndSessionEndpoint(final String endSessionEndpoint) { this.endSessionEndpoint = endSessionEndpoint; } + @Override + public List getScopes() { + return Optional.ofNullable(scopes).map(s -> Stream.of(s.split(" ")).toList()).orElse(List.of()); + } + + @Override + public void setScopes(final List scopes) { + this.scopes = CollectionUtils.isEmpty(scopes) + ? "" + : scopes.stream().collect(Collectors.joining(" ")); + } + @Override public boolean getHasDiscovery() { return hasDiscovery; diff --git a/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java b/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java index 38e9494c48..9c7d814ec6 100644 --- a/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java +++ b/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java @@ -173,6 +173,7 @@ public OIDCC4UIProvider update(final OIDCC4UIProvider op, final OIDCC4UIProvider op.setTokenEndpoint(opTO.getTokenEndpoint()); op.setUserinfoEndpoint(opTO.getUserinfoEndpoint()); op.setEndSessionEndpoint(opTO.getEndSessionEndpoint()); + op.setScopes(opTO.getScopes()); op.setHasDiscovery(opTO.getHasDiscovery()); op.setCreateUnmatching(opTO.isCreateUnmatching()); op.setSelfRegUnmatching(opTO.isSelfRegUnmatching()); @@ -243,6 +244,7 @@ public OIDCC4UIProviderTO getOIDCProviderTO(final OIDCC4UIProvider op) { opTO.setTokenEndpoint(op.getTokenEndpoint()); opTO.setUserinfoEndpoint(op.getUserinfoEndpoint()); opTO.setEndSessionEndpoint(op.getEndSessionEndpoint()); + opTO.getScopes().addAll(op.getScopes()); opTO.setHasDiscovery(op.getHasDiscovery()); opTO.setCreateUnmatching(op.isCreateUnmatching()); opTO.setSelfRegUnmatching(op.isSelfRegUnmatching()); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java index 26e81f5de2..1f10ec6d4c 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import org.apache.http.Consts; import org.apache.http.HttpHeaders; import org.apache.http.HttpStatus; @@ -50,6 +51,7 @@ import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apache.syncope.common.lib.types.ClientAppType; import org.apache.syncope.common.lib.types.OIDCResponseType; +import org.apache.syncope.common.lib.types.OIDCScope; import org.apache.syncope.common.lib.types.OIDCSubjectType; import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.syncope.common.rest.api.service.wa.WAConfigService; @@ -92,6 +94,9 @@ private static void clientAppSetup(final String appName, final String baseAddres Set.of(OIDCResponseType.CODE, OIDCResponseType.ID_TOKEN_TOKEN, OIDCResponseType.TOKEN)); clientApp.setAuthPolicy(getAuthPolicy().getKey()); clientApp.setAttrReleasePolicy(getAttrReleasePolicy().getKey()); + clientApp.getScopes().add(OIDCScope.OPENID); + clientApp.getScopes().add(OIDCScope.PROFILE); + clientApp.getScopes().add(OIDCScope.EMAIL); CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, clientApp); WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); @@ -134,6 +139,9 @@ private static void oidcSetup( cas.setUserinfoEndpoint(cas.getIssuer() + "/profile"); cas.setEndSessionEndpoint(cas.getIssuer() + "/logout"); + cas.getScopes().addAll(Stream.of(OIDCScope.values()).map(s -> s.name().toLowerCase()).toList()); + cas.getScopes().add("syncope"); + cas.setCreateUnmatching(createUnmatching); cas.setSelfRegUnmatching(selfRegUnmatching); 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 3cecfba16a..f22e118ccd 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 @@ -138,6 +138,7 @@ public RegisteredService map( map(p -> p.getAllowedAttributes().stream().collect(Collectors.toSet())). ifPresent(customClaims::addAll); } + if (rp.getScopes().contains(OIDCScope.PROFILE)) { customClaims.removeAll(OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } From 782be0a885f8f6e872fe1ca64aef466bdadf180d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Tue, 25 Jul 2023 16:40:18 +0200 Subject: [PATCH 3/7] Cleaning up attributeReleasePolicy for OIDC client apps --- .../syncope/core/logic/OIDCC4UILogic.java | 4 --- .../apache/syncope/fit/sra/OIDCSRAITCase.java | 6 ++-- pom.xml | 2 +- .../mapping/OIDCRPClientAppTOMapper.java | 35 ++----------------- 4 files changed, 7 insertions(+), 40 deletions(-) diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java index e4f974bb91..29578eb994 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java @@ -139,7 +139,6 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat // 2. get OpenID Connect tokens String idTokenHint; JWTClaimsSet idToken; - JWTClaimsSet accessToken; try { OidcCredentials credentials = new OidcCredentials(); credentials.setCode(new AuthorizationCode(authorizationCode)); @@ -150,8 +149,6 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat idToken = credentials.getIdToken().getJWTClaimsSet(); idTokenHint = credentials.getIdToken().serialize(); - - accessToken = SignedJWT.parse(credentials.getAccessToken().getValue()).getJWTClaimsSet(); } catch (Exception e) { LOG.error("While validating Token Response", e); SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown); @@ -170,7 +167,6 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat attrTO.setSchema(item.getExtAttrName()); String value = Optional.ofNullable(idToken.getClaim(item.getExtAttrName())). - or(() -> Optional.ofNullable(accessToken.getClaim(item.getExtAttrName()))). map(Object::toString). orElse(null); if (value != null) { diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java index 220423df81..8ed895954c 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java @@ -61,6 +61,7 @@ import org.apache.http.util.EntityUtils; 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.OIDCScope; import org.apache.syncope.common.lib.types.OIDCSubjectType; import org.apache.syncope.common.rest.api.RESTHeaders; @@ -125,6 +126,7 @@ protected static void oidcClientAppSetup( clientApp.getScopes().add(OIDCScope.OPENID); clientApp.getScopes().add(OIDCScope.PROFILE); clientApp.getScopes().add(OIDCScope.EMAIL); + clientApp.getSupportedGrantTypes().add(OIDCGrantType.password); CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, clientApp); WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); @@ -239,9 +241,7 @@ private void checkJWT(final String token, final boolean idToken) throws ParseExc assertEquals("Verdi", idTokenClaimsSet.getStringClaim("family_name")); assertEquals("Giuseppe", idTokenClaimsSet.getStringClaim("given_name")); assertEquals("Giuseppe Verdi", idTokenClaimsSet.getStringClaim("name")); - if (!idToken) { - assertEquals(Set.of("root", "child", "citizen"), Set.of(idTokenClaimsSet.getStringArrayClaim("groups"))); - } + assertEquals(Set.of("root", "child", "citizen"), Set.of(idTokenClaimsSet.getStringArrayClaim("groups"))); } protected boolean checkIdToken() { diff --git a/pom.xml b/pom.xml index 8ee3f3dbc0..3b19b8cd03 100644 --- a/pom.xml +++ b/pom.xml @@ -489,7 +489,7 @@ under the License. 10.1.11 29.0.0.Final 6.2023.7 - 4.0.2 + 4.0.3 15 8.0 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 f22e118ccd..d1d66bf9dd 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 @@ -29,9 +29,7 @@ import org.apache.syncope.common.lib.types.OIDCScope; import org.apache.syncope.common.lib.wa.WAClientApp; 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.OidcOpenIdScopeAttributeReleasePolicy; import org.apereo.cas.oidc.claims.OidcPhoneScopeAttributeReleasePolicy; import org.apereo.cas.oidc.claims.OidcProfileScopeAttributeReleasePolicy; import org.apereo.cas.services.BaseMappedAttributeReleasePolicy; @@ -93,36 +91,10 @@ public RegisteredService map( } service.setLogoutUrl(rp.getLogoutUri()); - ChainingAttributeReleasePolicy chain; - if (attributeReleasePolicy instanceof ChainingAttributeReleasePolicy chainingAttributeReleasePolicy) { - chain = chainingAttributeReleasePolicy; - } else { - chain = new ChainingAttributeReleasePolicy(); - if (attributeReleasePolicy != null) { - chain.addPolicies(attributeReleasePolicy); - } - } - service.setScopes(rp.getScopes().stream(). map(s -> s.name().toLowerCase()). collect(Collectors.toCollection(HashSet::new))); - if (rp.getScopes().contains(OIDCScope.OPENID)) { - chain.addPolicies(new OidcOpenIdScopeAttributeReleasePolicy()); - } - if (rp.getScopes().contains(OIDCScope.PROFILE)) { - chain.addPolicies(new OidcProfileScopeAttributeReleasePolicy()); - } - if (rp.getScopes().contains(OIDCScope.ADDRESS)) { - chain.addPolicies(new OidcAddressScopeAttributeReleasePolicy()); - } - if (rp.getScopes().contains(OIDCScope.EMAIL)) { - chain.addPolicies(new OidcEmailScopeAttributeReleasePolicy()); - } - if (rp.getScopes().contains(OIDCScope.PHONE)) { - chain.addPolicies(new OidcPhoneScopeAttributeReleasePolicy()); - } - Set customClaims = new HashSet<>(); if (attributeReleasePolicy instanceof BaseMappedAttributeReleasePolicy baseMapped) { customClaims.addAll(baseMapped. @@ -138,7 +110,6 @@ public RegisteredService map( map(p -> p.getAllowedAttributes().stream().collect(Collectors.toSet())). ifPresent(customClaims::addAll); } - if (rp.getScopes().contains(OIDCScope.PROFILE)) { customClaims.removeAll(OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } @@ -151,13 +122,13 @@ public RegisteredService map( if (rp.getScopes().contains(OIDCScope.PHONE)) { customClaims.removeAll(OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } + if (!customClaims.isEmpty()) { - chain.addPolicies(new OidcCustomScopeAttributeReleasePolicy( - CUSTOM_SCOPE, customClaims.stream().collect(Collectors.toList()))); service.getScopes().add(CUSTOM_SCOPE); } - setPolicies(service, authPolicy, mfaPolicy, accessStrategy, chain, + // never set attribute relase policy for OIDC services to avoid becoming scope-free for CAS + setPolicies(service, authPolicy, mfaPolicy, accessStrategy, null, tgtExpirationPolicy, stExpirationPolicy, tgtProxyExpirationPolicy, stProxyExpirationPolicy); return service; From 2ee8b6132b9261821ff8c8f544116b2356821280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Thu, 27 Jul 2023 17:35:40 +0200 Subject: [PATCH 4/7] Allow to dynamically reload OIDC discovery claims --- .../syncope/common/lib/types/OIDCScope.java | 10 +-- .../wizards/OIDCProviderWizardBuilder.java | 2 +- .../apache/syncope/fit/sra/OIDCSRAITCase.java | 25 ++++++- .../apache/syncope/fit/ui/OIDC4UIITCase.java | 8 +- wa/bootstrap/pom.xml | 8 ++ .../bootstrap/WABootstrapConfiguration.java | 22 +++++- .../wa/bootstrap/WAPropertySourceLocator.java | 73 +++++++++++++++++++ .../syncope/wa/bootstrap/WARestClient.java | 2 +- .../bootstrap}/mapping/AttrReleaseMapper.java | 2 +- .../AttrRepoPropertySourceMapper.java | 3 +- .../AuthModulePropertySourceMapper.java | 3 +- .../mapping/DefaultAttrReleaseMapper.java | 2 +- .../{ => mapping}/PropertySourceMapper.java | 2 +- .../AuthModulePropertySourceMapperTest.java | 1 + wa/starter/pom.xml | 8 -- .../syncope/wa/starter/config/WAContext.java | 13 +--- .../mapping/OIDCRPClientAppTOMapper.java | 21 +++--- .../mapping/RegisteredServiceMapper.java | 15 ++-- .../wa/starter/WAServiceRegistryTest.java | 3 +- 19 files changed, 164 insertions(+), 59 deletions(-) rename wa/{starter/src/main/java/org/apache/syncope/wa/starter => bootstrap/src/main/java/org/apache/syncope/wa/bootstrap}/mapping/AttrReleaseMapper.java (96%) rename wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/{ => mapping}/AttrRepoPropertySourceMapper.java (98%) rename wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/{ => mapping}/AuthModulePropertySourceMapper.java (99%) rename wa/{starter/src/main/java/org/apache/syncope/wa/starter => bootstrap/src/main/java/org/apache/syncope/wa/bootstrap}/mapping/DefaultAttrReleaseMapper.java (99%) rename wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/{ => mapping}/PropertySourceMapper.java (99%) diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCScope.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCScope.java index a3771db4f9..57e6b58d73 100644 --- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCScope.java +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCScope.java @@ -19,10 +19,10 @@ package org.apache.syncope.common.lib.types; public enum OIDCScope { - OPENID, - PROFILE, - EMAIL, - ADDRESS, - PHONE + openid, + profile, + email, + address, + phone } diff --git a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java index 7c80d5ae03..ac06485269 100644 --- a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java +++ b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java @@ -270,7 +270,7 @@ protected void onUpdate(final AjaxRequestTarget target) { }); AjaxTextFieldPanel value = new AjaxTextFieldPanel("panel", "scopes", new Model<>()); - value.setChoices(Stream.of(OIDCScope.values()).map(s -> s.name().toLowerCase()).toList()); + value.setChoices(Stream.of(OIDCScope.values()).map(OIDCScope::name).toList()); content.add(new MultiFieldPanel.Builder( new PropertyModel<>(opTO, "scopes")).build("scopes", "scopes", value)); } diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java index 8ed895954c..fe056c1720 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.fit.sra; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.oneOf; @@ -44,6 +45,7 @@ import java.util.List; import java.util.Properties; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.http.Consts; @@ -123,9 +125,9 @@ protected static void oidcClientAppSetup( clientApp.setLogoutUri(SRA_ADDRESS + "/logout"); clientApp.setAuthPolicy(getAuthPolicy().getKey()); clientApp.setAttrReleasePolicy(getAttrReleasePolicy().getKey()); - clientApp.getScopes().add(OIDCScope.OPENID); - clientApp.getScopes().add(OIDCScope.PROFILE); - clientApp.getScopes().add(OIDCScope.EMAIL); + clientApp.getScopes().add(OIDCScope.openid); + clientApp.getScopes().add(OIDCScope.profile); + clientApp.getScopes().add(OIDCScope.email); clientApp.getSupportedGrantTypes().add(OIDCGrantType.password); CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, clientApp); @@ -250,6 +252,23 @@ protected boolean checkIdToken() { @Test public void rest() throws IOException, ParseException { + await().atMost(60, TimeUnit.SECONDS).pollInterval(20, TimeUnit.SECONDS).until(() -> { + boolean refreshed = false; + try { + String metadata = WebClient.create( + WA_ADDRESS + "/oidc/.well-known/openid-configuration").get().readEntity(String.class); + if (!metadata.contains("groups")) { + WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); + throw new IllegalStateException(); + } + + refreshed = true; + } catch (Exception e) { + // ignore + } + return refreshed; + }); + // 0. access public route WebClient client = WebClient.create(SRA_ADDRESS + "/public/post"). accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java index 1f10ec6d4c..2445e00e72 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java @@ -94,9 +94,9 @@ private static void clientAppSetup(final String appName, final String baseAddres Set.of(OIDCResponseType.CODE, OIDCResponseType.ID_TOKEN_TOKEN, OIDCResponseType.TOKEN)); clientApp.setAuthPolicy(getAuthPolicy().getKey()); clientApp.setAttrReleasePolicy(getAttrReleasePolicy().getKey()); - clientApp.getScopes().add(OIDCScope.OPENID); - clientApp.getScopes().add(OIDCScope.PROFILE); - clientApp.getScopes().add(OIDCScope.EMAIL); + clientApp.getScopes().add(OIDCScope.openid); + clientApp.getScopes().add(OIDCScope.profile); + clientApp.getScopes().add(OIDCScope.email); CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, clientApp); WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); @@ -139,7 +139,7 @@ private static void oidcSetup( cas.setUserinfoEndpoint(cas.getIssuer() + "/profile"); cas.setEndSessionEndpoint(cas.getIssuer() + "/logout"); - cas.getScopes().addAll(Stream.of(OIDCScope.values()).map(s -> s.name().toLowerCase()).toList()); + cas.getScopes().addAll(Stream.of(OIDCScope.values()).map(OIDCScope::name).toList()); cas.getScopes().add("syncope"); cas.setCreateUnmatching(createUnmatching); diff --git a/wa/bootstrap/pom.xml b/wa/bootstrap/pom.xml index fc1376806e..9d952cb8d9 100644 --- a/wa/bootstrap/pom.xml +++ b/wa/bootstrap/pom.xml @@ -57,6 +57,14 @@ under the License. org.apereo.cas cas-server-core-util-api + + org.apereo.cas + cas-server-core-authentication-attributes + + + org.apereo.cas + cas-server-support-oidc-core + org.slf4j diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java index f470e9a778..dcd0fb7a21 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java @@ -18,6 +18,10 @@ */ package org.apache.syncope.wa.bootstrap; +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.apache.syncope.wa.bootstrap.mapping.DefaultAttrReleaseMapper; import org.apereo.cas.configuration.support.CasConfigurationJasyptCipherExecutor; import org.apereo.cas.util.crypto.CipherExecutor; import org.springframework.beans.factory.annotation.Qualifier; @@ -58,8 +62,8 @@ public WARestClient waRestClient() { @Configuration(proxyBeanMethods = false) public static class PropertySourceConfiguration { - @Bean @ConditionalOnMissingBean(name = "waConfigurationCipher") + @Bean public CipherExecutor waConfigurationCipher(final Environment environment) { return new CasConfigurationJasyptCipherExecutor(environment); } @@ -76,17 +80,27 @@ public AttrRepoPropertySourceMapper attrRepoPropertySourceMapper(final WARestCli return new AttrRepoPropertySourceMapper(waRestClient); } + @ConditionalOnMissingBean + @Bean + public AttrReleaseMapper attrReleaseMapper() { + return new DefaultAttrReleaseMapper(); + } + @Bean public PropertySourceLocator configPropertySourceLocator( @Qualifier("waConfigurationCipher") final CipherExecutor waConfigurationCipher, final WARestClient waRestClient, final AuthModulePropertySourceMapper authModulePropertySourceMapper, - final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper) { + final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper, + final AttrReleaseMapper attrReleaseMapper) { return new WAPropertySourceLocator( - waRestClient, authModulePropertySourceMapper, - attrRepoPropertySourceMapper, waConfigurationCipher); + waRestClient, + authModulePropertySourceMapper, + attrRepoPropertySourceMapper, + attrReleaseMapper, + waConfigurationCipher); } } } 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 21b8ff3c2f..c74d0a71c0 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,16 +19,35 @@ 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; +import java.util.stream.Stream; 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.to.OIDCRPClientAppTO; +import org.apache.syncope.common.lib.types.OIDCScope; import org.apache.syncope.common.rest.api.service.AttrRepoService; import org.apache.syncope.common.rest.api.service.AuthModuleService; +import org.apache.syncope.common.rest.api.service.wa.WAClientAppService; import org.apache.syncope.common.rest.api.service.wa.WAConfigService; +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.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; @@ -49,17 +68,21 @@ public class WAPropertySourceLocator implements PropertySourceLocator { protected final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper; + protected final AttrReleaseMapper attrReleaseMapper; + protected final CipherExecutor configurationCipher; public WAPropertySourceLocator( final WARestClient waRestClient, final AuthModulePropertySourceMapper authModulePropertySourceMapper, final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper, + final AttrReleaseMapper attrReleaseMapper, final CipherExecutor configurationCipher) { this.waRestClient = waRestClient; this.authModulePropertySourceMapper = authModulePropertySourceMapper; this.attrRepoPropertySourceMapper = attrRepoPropertySourceMapper; + this.attrReleaseMapper = attrReleaseMapper; this.configurationCipher = configurationCipher; } @@ -109,6 +132,56 @@ public PropertySource locate(final Environment environment) { properties.putAll(index(map, prefixes)); }); + Set 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()); + + RegisteredServiceAttributeReleasePolicy attributeReleasePolicy = + attrReleaseMapper.build(clientApp.getAttrReleasePolicy()); + + Set 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(OIDCScope.profile)) { + claims.removeAll(OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS); + } + if (rp.getScopes().contains(OIDCScope.address)) { + claims.removeAll(OidcAddressScopeAttributeReleasePolicy.ALLOWED_CLAIMS); + } + if (rp.getScopes().contains(OIDCScope.email)) { + claims.removeAll(OidcEmailScopeAttributeReleasePolicy.ALLOWED_CLAIMS); + } + if (rp.getScopes().contains(OIDCScope.phone)) { + claims.removeAll(OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS); + } + + return claims.stream(); + }).collect(Collectors.toSet()); + if (!customClaims.isEmpty()) { + Stream.concat(new OidcDiscoveryProperties().getClaims().stream(), customClaims.stream()). + collect(Collectors.joining(",")); + + properties.put("cas.authn.oidc.discovery.claims", + Stream.concat(new OidcDiscoveryProperties().getClaims().stream(), customClaims.stream()). + collect(Collectors.joining(","))); + properties.put("cas.authn.oidc.core.user-defined-scopes.syncope", + customClaims.stream().collect(Collectors.joining(","))); + } + syncopeClient.getService(WAConfigService.class).list().forEach(attr -> properties.put( attr.getSchema(), attr.getValues().stream().collect(Collectors.joining(",")))); diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java index 8d48df5a02..cd7c227782 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java @@ -89,7 +89,7 @@ protected Optional getCore() { return Optional.empty(); } - protected SyncopeClient getSyncopeClient() { + public SyncopeClient getSyncopeClient() { synchronized (this) { if (client == null) { getCore().ifPresent(core -> { diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/AttrReleaseMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java similarity index 96% rename from wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/AttrReleaseMapper.java rename to wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java index 7b54d36734..021968582a 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/AttrReleaseMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.wa.starter.mapping; +package org.apache.syncope.wa.bootstrap.mapping; import org.apache.syncope.common.lib.policy.AttrReleasePolicyConf; import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO; diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AttrRepoPropertySourceMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrRepoPropertySourceMapper.java similarity index 98% rename from wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AttrRepoPropertySourceMapper.java rename to wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrRepoPropertySourceMapper.java index 28e0e239ea..cbdf8bd665 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AttrRepoPropertySourceMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrRepoPropertySourceMapper.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.wa.bootstrap; +package org.apache.syncope.wa.bootstrap.mapping; import java.util.Map; import java.util.stream.Collectors; @@ -29,6 +29,7 @@ import org.apache.syncope.common.lib.attr.SyncopeAttrRepoConf; import org.apache.syncope.common.lib.to.AttrRepoTO; import org.apache.syncope.common.lib.to.Item; +import org.apache.syncope.wa.bootstrap.WARestClient; import org.apereo.cas.configuration.CasCoreConfigurationUtils; import org.apereo.cas.configuration.model.core.authentication.AttributeRepositoryStates; import org.apereo.cas.configuration.model.core.authentication.StubPrincipalAttributesProperties; diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java similarity index 99% rename from wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapper.java rename to wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java index 0efbff13b5..28667f974f 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.wa.bootstrap; +package org.apache.syncope.wa.bootstrap.mapping; import java.util.List; import java.util.Map; @@ -46,6 +46,7 @@ import org.apache.syncope.common.lib.to.AuthModuleTO; import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.types.AuthModuleState; +import org.apache.syncope.wa.bootstrap.WARestClient; import org.apereo.cas.configuration.CasCoreConfigurationUtils; import org.apereo.cas.configuration.model.core.authentication.AuthenticationHandlerStates; import org.apereo.cas.configuration.model.support.generic.AcceptAuthenticationProperties; diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/DefaultAttrReleaseMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java similarity index 99% rename from wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/DefaultAttrReleaseMapper.java rename to wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java index 5279c15232..aa03074c32 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/DefaultAttrReleaseMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.wa.starter.mapping; +package org.apache.syncope.wa.bootstrap.mapping; import java.util.HashSet; import org.apache.syncope.common.lib.policy.AttrReleasePolicyConf; diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/PropertySourceMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/PropertySourceMapper.java similarity index 99% rename from wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/PropertySourceMapper.java rename to wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/PropertySourceMapper.java index 9730f4df06..b4c3d74ed9 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/PropertySourceMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/PropertySourceMapper.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.wa.bootstrap; +package org.apache.syncope.wa.bootstrap.mapping; import java.util.Map; import java.util.Optional; diff --git a/wa/bootstrap/src/test/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapperTest.java b/wa/bootstrap/src/test/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapperTest.java index a6934e0c6a..cab01d335f 100644 --- a/wa/bootstrap/src/test/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapperTest.java +++ b/wa/bootstrap/src/test/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapperTest.java @@ -24,6 +24,7 @@ import org.apache.syncope.common.lib.auth.OAuth20AuthModuleConf; import org.apache.syncope.common.lib.auth.SimpleMfaAuthModuleConf; import org.apache.syncope.common.lib.to.AuthModuleTO; +import org.apache.syncope.wa.bootstrap.mapping.AuthModulePropertySourceMapper; import org.junit.jupiter.api.Test; public class AuthModulePropertySourceMapperTest { diff --git a/wa/starter/pom.xml b/wa/starter/pom.xml index e5fb2e940d..c86776e873 100644 --- a/wa/starter/pom.xml +++ b/wa/starter/pom.xml @@ -221,10 +221,6 @@ under the License. org.apereo.cas cas-server-support-oidc-core-api - - org.apereo.cas - cas-server-support-oidc-core - org.apereo.cas cas-server-support-oauth-services @@ -341,10 +337,6 @@ under the License. org.apereo.cas cas-server-support-swagger - - org.apereo.cas - cas-server-core-authentication-attributes - org.apereo.cas cas-server-core-services-authentication diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java index 98f5e9a365..122597d506 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java @@ -36,6 +36,7 @@ import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop; import org.apache.syncope.wa.bootstrap.WAProperties; import org.apache.syncope.wa.bootstrap.WARestClient; +import org.apache.syncope.wa.bootstrap.mapping.AttrReleaseMapper; import org.apache.syncope.wa.starter.actuate.SyncopeCoreHealthIndicator; import org.apache.syncope.wa.starter.actuate.SyncopeWAInfoContributor; import org.apache.syncope.wa.starter.audit.WAAuditTrailManager; @@ -43,12 +44,10 @@ import org.apache.syncope.wa.starter.gauth.WAGoogleMfaAuthCredentialRepository; import org.apache.syncope.wa.starter.gauth.WAGoogleMfaAuthTokenRepository; import org.apache.syncope.wa.starter.mapping.AccessMapper; -import org.apache.syncope.wa.starter.mapping.AttrReleaseMapper; import org.apache.syncope.wa.starter.mapping.AuthMapper; import org.apache.syncope.wa.starter.mapping.CASSPClientAppTOMapper; import org.apache.syncope.wa.starter.mapping.ClientAppMapper; import org.apache.syncope.wa.starter.mapping.DefaultAccessMapper; -import org.apache.syncope.wa.starter.mapping.DefaultAttrReleaseMapper; import org.apache.syncope.wa.starter.mapping.DefaultAuthMapper; import org.apache.syncope.wa.starter.mapping.DefaultTicketExpirationMapper; import org.apache.syncope.wa.starter.mapping.HttpRequestAccessMapper; @@ -159,19 +158,13 @@ public TimeBasedAccessMapper timeBasedAccessMapper() { @ConditionalOnMissingBean @Bean - public AttrReleaseMapper defaultAttrReleaseMapper() { - return new DefaultAttrReleaseMapper(); - } - - @ConditionalOnMissingBean - @Bean - public AuthMapper defaultAuthMapper() { + public AuthMapper authMapper() { return new DefaultAuthMapper(); } @ConditionalOnMissingBean @Bean - public TicketExpirationMapper defaultTicketExpirationMapper() { + public TicketExpirationMapper ticketExpirationMapper() { return new DefaultTicketExpirationMapper(); } 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 d1d66bf9dd..f9d6da44c0 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 @@ -20,6 +20,7 @@ import java.util.HashSet; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.apache.syncope.common.lib.to.ClientAppTO; @@ -83,17 +84,15 @@ public RegisteredService map( service.setJwtAccessToken(rp.isJwtAccessToken()); service.setBypassApprovalPrompt(rp.isBypassApprovalPrompt()); service.setSupportedGrantTypes(rp.getSupportedGrantTypes().stream(). - map(OIDCGrantType::name).collect(Collectors.toCollection(HashSet::new))); + map(OIDCGrantType::name).collect(Collectors.toSet())); service.setSupportedResponseTypes(rp.getSupportedResponseTypes().stream(). - map(OIDCResponseType::getExternalForm).collect(Collectors.toCollection(HashSet::new))); - if (rp.getSubjectType() != null) { - service.setSubjectType(rp.getSubjectType().name()); - } + map(OIDCResponseType::getExternalForm).collect(Collectors.toSet())); + Optional.ofNullable(rp.getSubjectType()).ifPresent(st -> service.setSubjectType(st.name())); service.setLogoutUrl(rp.getLogoutUri()); service.setScopes(rp.getScopes().stream(). - map(s -> s.name().toLowerCase()). - collect(Collectors.toCollection(HashSet::new))); + map(OIDCScope::name). + collect(Collectors.toSet())); Set customClaims = new HashSet<>(); if (attributeReleasePolicy instanceof BaseMappedAttributeReleasePolicy baseMapped) { @@ -110,16 +109,16 @@ public RegisteredService map( map(p -> p.getAllowedAttributes().stream().collect(Collectors.toSet())). ifPresent(customClaims::addAll); } - if (rp.getScopes().contains(OIDCScope.PROFILE)) { + if (rp.getScopes().contains(OIDCScope.profile)) { customClaims.removeAll(OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } - if (rp.getScopes().contains(OIDCScope.ADDRESS)) { + if (rp.getScopes().contains(OIDCScope.address)) { customClaims.removeAll(OidcAddressScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } - if (rp.getScopes().contains(OIDCScope.EMAIL)) { + if (rp.getScopes().contains(OIDCScope.email)) { customClaims.removeAll(OidcEmailScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } - if (rp.getScopes().contains(OIDCScope.PHONE)) { + if (rp.getScopes().contains(OIDCScope.phone)) { customClaims.removeAll(OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } 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 f5ff9024b5..8c1dd47fc3 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 @@ -23,6 +23,7 @@ import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO; import org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf; import org.apache.syncope.common.lib.wa.WAClientApp; +import org.apache.syncope.wa.bootstrap.mapping.AttrReleaseMapper; import org.apereo.cas.authentication.AuthenticationEventExecutionPlan; import org.apereo.cas.authentication.MultifactorAuthenticationProvider; import org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy; @@ -81,15 +82,17 @@ public RegisteredServiceMapper( } public RegisteredService toRegisteredService(final WAClientApp clientApp) { - ClientAppMapper clientAppMapper = clientAppMappers.stream(). + return clientAppMappers.stream(). filter(m -> m.supports(clientApp.getClientAppTO())). findFirst(). - orElse(null); - if (clientAppMapper == null) { - LOG.warn("Unable to locate ClientAppMapper for {}", clientApp.getClientAppTO().getClass().getName()); - return null; - } + map(clientAppMapper -> toRegisteredService(clientApp, clientAppMapper)). + orElseGet(() -> { + LOG.warn("Unable to locate mapper for {}", clientApp.getClientAppTO().getClass().getName()); + return null; + }); + } + public RegisteredService toRegisteredService(final WAClientApp clientApp, final ClientAppMapper clientAppMapper) { RegisteredServiceAuthenticationPolicy authPolicy = null; RegisteredServiceMultifactorPolicy mfaPolicy = null; RegisteredServiceDelegatedAuthenticationPolicy delegatedAuthPolicy = 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 01f7d2b34f..1ae5282b0a 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 @@ -54,6 +54,7 @@ import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategy; import org.apereo.cas.services.RegisteredServiceDelegatedAuthenticationPolicy; +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; @@ -184,7 +185,7 @@ public void addClientApp() { assertTrue(oidc.getAuthenticationPolicy().getRequiredAuthenticationHandlers().contains("TestAuthModule")); assertTrue(((AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria) oidc. getAuthenticationPolicy().getCriteria()).isTryAll()); - assertTrue(oidc.getAttributeReleasePolicy() instanceof ChainingAttributeReleasePolicy); + assertTrue(oidc.getAttributeReleasePolicy() instanceof ReturnAllowedAttributeReleasePolicy); // 5. more client with different attributes waClientApp = new WAClientApp(); From 691f7e1a5779cd409086d9974e8006528a2caae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Fri, 28 Jul 2023 15:12:34 +0200 Subject: [PATCH 5/7] More cleanup --- .../test/resources/domains/MasterContent.xml | 6 +-- .../test/resources/domains/MasterContent.xml | 6 +-- .../client/console/pages/OIDCC4UI.java | 4 +- .../panels/OIDCProvidersDirectoryPanel.java | 10 ++-- ...itlement.java => OIDCC4UIEntitlement.java} | 6 +-- .../syncope/core/logic/OIDCC4UILogic.java | 53 +++++++++---------- .../core/logic/OIDCC4UIProviderLogic.java | 12 ++--- .../core/logic/init/OIDCC4UILoader.java | 4 +- ...DC4UIContext.java => OIDCC4UIContext.java} | 2 +- .../syncope/core/logic/SAML2SP4UILogic.java | 14 +++-- .../apache/syncope/fit/core/BatchITCase.java | 7 +-- .../syncope/fit/core/BpmnProcessITCase.java | 8 +-- .../syncope/fit/core/MailTemplateITCase.java | 16 ++---- .../apache/syncope/fit/core/RESTITCase.java | 19 ++----- .../apache/syncope/fit/core/ReportITCase.java | 7 +-- .../apache/syncope/fit/AbstractITCase.java | 24 ++++----- .../apache/syncope/fit/sra/CASSRAITCase.java | 2 + .../syncope/fit/sra/OAUTH2SRAITCase.java | 3 +- .../apache/syncope/fit/sra/OIDCSRAITCase.java | 21 +++++--- .../syncope/fit/sra/SAML2SRAITCase.java | 2 + .../syncope/fit/ui/AbstractUIITCase.java | 8 +-- ...OIDC4UIITCase.java => OIDCC4UIITCase.java} | 18 ++++--- .../syncope/fit/ui/SAML2SP4UIITCase.java | 12 +++-- .../src/test/resources/sra-oidc.properties | 1 + pom.xml | 4 +- .../test/resources/debug/sra-debug.properties | 1 + 26 files changed, 124 insertions(+), 146 deletions(-) rename ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/{OIDC4UIEntitlement.java => OIDCC4UIEntitlement.java} (91%) rename ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/{OIDC4UIContext.java => OIDCC4UIContext.java} (98%) rename fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/{OIDC4UIITCase.java => OIDCC4UIITCase.java} (95%) diff --git a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml index 4bcf49aaf5..3c123e6b98 100644 --- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml +++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml @@ -58,14 +58,14 @@ under the License. + jsonConf='{"_class":"org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf","releaseAttrs":{},"allowedAttrs":[],"excludedAttrs":[],"includeOnlyAttrs":[],"principalIdAttr":null,"principalAttrRepoConf":{"mergingStrategy":"MULTIVALUED","ignoreResolvedAttributes":false,"expiration":0,"timeUnit":"HOURS","attrRepos":[]}}'/> + jsonConf='{"_class":"org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf","releaseAttrs":{},"allowedAttrs":["cn","givenName","uid"],"excludedAttrs":[],"includeOnlyAttrs":[],"principalIdAttr":null,"principalAttrRepoConf":{"mergingStrategy":"MULTIVALUED","ignoreResolvedAttributes":false,"expiration":0,"timeUnit":"HOURS","attrRepos":[]}}'/> + items='[{"intAttrName":"mail","extAttrName":"email","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"givenName","extAttrName":"given_name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"sn","extAttrName":"family_name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"cn","extAttrName":"name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]}]'/> + jsonConf='{"_class":"org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf","releaseAttrs":{},"allowedAttrs":[],"excludedAttrs":[],"includeOnlyAttrs":[],"principalIdAttr":null,"principalAttrRepoConf":{"mergingStrategy":"MULTIVALUED","ignoreResolvedAttributes":false,"expiration":0,"timeUnit":"HOURS","attrRepos":[]}}'/> + jsonConf='{"_class":"org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf","releaseAttrs":{},"allowedAttrs":["cn","givenName","uid"],"excludedAttrs":[],"includeOnlyAttrs":[],"principalIdAttr":null,"principalAttrRepoConf":{"mergingStrategy":"MULTIVALUED","ignoreResolvedAttributes":false,"expiration":0,"timeUnit":"HOURS","attrRepos":[]}}'/> + items='[{"intAttrName":"mail","extAttrName":"email","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"givenName","extAttrName":"given_name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"sn","extAttrName":"family_name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"cn","extAttrName":"name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]}]'/> newInstance(final String id, this.addNewItemPanelBuilder(new OIDCProviderWizardBuilder( this, new OIDCC4UIProviderTO(), implementationRestClient, restClient, pageRef), true); - MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, OIDC4UIEntitlement.OP_CREATE); + MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, OIDCC4UIEntitlement.OP_CREATE); modal.size(Modal.Size.Large); @@ -183,7 +183,7 @@ public void onClick(final AjaxRequestTarget target, final OIDCC4UIProviderTO ign new AjaxWizard.EditItemActionEvent<>(object, target)); modal.header(Model.of(StringUtils.capitalize(("Edit " + object.getName())))); } - }, ActionLink.ActionType.EDIT, OIDC4UIEntitlement.OP_UPDATE); + }, ActionLink.ActionType.EDIT, OIDCC4UIEntitlement.OP_UPDATE); panel.add(new ActionLink<>() { @@ -217,7 +217,7 @@ protected Serializable onApplyInternal(final AnyWrapper modelObject) { target.add(templateModal); } - }, ActionLink.ActionType.TEMPLATE, OIDC4UIEntitlement.OP_UPDATE); + }, ActionLink.ActionType.TEMPLATE, OIDCC4UIEntitlement.OP_UPDATE); panel.add(new ActionLink<>() { @@ -235,7 +235,7 @@ public void onClick(final AjaxRequestTarget target, final OIDCC4UIProviderTO ign } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } - }, ActionLink.ActionType.DELETE, OIDC4UIEntitlement.OP_DELETE, true); + }, ActionLink.ActionType.DELETE, OIDCC4UIEntitlement.OP_DELETE, true); return panel; } diff --git a/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDC4UIEntitlement.java b/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDCC4UIEntitlement.java similarity index 91% rename from ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDC4UIEntitlement.java rename to ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDCC4UIEntitlement.java index 3f6a0a98df..8b559ffd07 100644 --- a/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDC4UIEntitlement.java +++ b/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDCC4UIEntitlement.java @@ -24,7 +24,7 @@ import java.util.Set; import java.util.TreeSet; -public final class OIDC4UIEntitlement { +public final class OIDCC4UIEntitlement { public static final String OP_READ = "OP_READ"; @@ -38,7 +38,7 @@ public final class OIDC4UIEntitlement { static { Set values = new TreeSet<>(); - for (Field field : OIDC4UIEntitlement.class.getDeclaredFields()) { + for (Field field : OIDCC4UIEntitlement.class.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers()) && String.class.equals(field.getType())) { values.add(field.getName()); } @@ -50,7 +50,7 @@ public static Set values() { return VALUES; } - private OIDC4UIEntitlement() { + private OIDCC4UIEntitlement() { // private constructor for static utility class } } diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java index 29578eb994..2daa162e13 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java @@ -41,7 +41,7 @@ import org.apache.syncope.common.lib.types.ClientExceptionType; import org.apache.syncope.common.lib.types.IdRepoEntitlement; import org.apache.syncope.core.logic.oidc.NoOpSessionStore; -import org.apache.syncope.core.logic.oidc.OIDC4UIContext; +import org.apache.syncope.core.logic.oidc.OIDCC4UIContext; import org.apache.syncope.core.logic.oidc.OIDCClientCache; import org.apache.syncope.core.logic.oidc.OIDCUserManager; import org.apache.syncope.core.persistence.api.dao.NotFoundException; @@ -114,7 +114,7 @@ public OIDCRequest createLoginRequest(final String redirectURI, final String opN // 2. create OIDCRequest WithLocationAction action = oidcClient.getRedirectionAction( - new CallContext(new OIDC4UIContext(), NoOpSessionStore.INSTANCE)). + new CallContext(new OIDCC4UIContext(), NoOpSessionStore.INSTANCE)). map(WithLocationAction.class::cast). orElseThrow(() -> { SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown); @@ -143,9 +143,8 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat OidcCredentials credentials = new OidcCredentials(); credentials.setCode(new AuthorizationCode(authorizationCode)); - OIDC4UIContext ctx = new OIDC4UIContext(); - - oidcClient.getAuthenticator().validate(new CallContext(ctx, NoOpSessionStore.INSTANCE), credentials); + oidcClient.getAuthenticator().validate( + new CallContext(new OIDCC4UIContext(), NoOpSessionStore.INSTANCE), credentials); idToken = credentials.getIdToken().getJWTClaimsSet(); idTokenHint = credentials.getIdToken().serialize(); @@ -157,8 +156,8 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat } // 3. prepare the result - OIDCLoginResponse loginResponse = new OIDCLoginResponse(); - loginResponse.setLogoutSupported(StringUtils.isNotBlank(op.getEndSessionEndpoint())); + OIDCLoginResponse loginResp = new OIDCLoginResponse(); + loginResp.setLogoutSupported(StringUtils.isNotBlank(op.getEndSessionEndpoint())); // 3a. find matching user (if any) and return the received attributes String keyValue = idToken.getSubject(); @@ -171,16 +170,16 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat orElse(null); if (value != null) { attrTO.getValues().add(value); - loginResponse.getAttrs().add(attrTO); + loginResp.getAttrs().add(attrTO); if (item.isConnObjectKey()) { keyValue = value; } } } - List matchingUsers = keyValue == null - ? List.of() - : userManager.findMatchingUser(keyValue, op.getConnObjectKeyItem().get()); + List matchingUsers = Optional.ofNullable(keyValue). + map(k -> userManager.findMatchingUser(k, op.getConnObjectKeyItem().get())). + orElse(List.of()); LOG.debug("Found {} matching users for {}", matchingUsers.size(), keyValue); // 3b. not found: create or selfreg if configured @@ -191,23 +190,23 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat String defaultUsername = keyValue; username = AuthContextUtils.callAsAdmin(AuthContextUtils.getDomain(), - () -> userManager.create(op, loginResponse, defaultUsername)); + () -> userManager.create(op, loginResp, defaultUsername)); } else if (op.isSelfRegUnmatching()) { UserTO userTO = new UserTO(); - userManager.fill(op, loginResponse, userTO); + userManager.fill(op, loginResp, userTO); - loginResponse.getAttrs().clear(); - loginResponse.getAttrs().addAll(userTO.getPlainAttrs()); + loginResp.getAttrs().clear(); + loginResp.getAttrs().addAll(userTO.getPlainAttrs()); if (StringUtils.isNotBlank(userTO.getUsername())) { - loginResponse.setUsername(userTO.getUsername()); + loginResp.setUsername(userTO.getUsername()); } else { - loginResponse.setUsername(keyValue); + loginResp.setUsername(keyValue); } - loginResponse.setSelfReg(true); + loginResp.setSelfReg(true); - return loginResponse; + return loginResp; } else { throw new NotFoundException(Optional.ofNullable(keyValue). map(value -> "User matching the provided value " + value). @@ -220,13 +219,13 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat LOG.debug("About to update {} for {}", matchingUsers.get(0), keyValue); username = AuthContextUtils.callAsAdmin(AuthContextUtils.getDomain(), - () -> userManager.update(matchingUsers.get(0), op, loginResponse)); + () -> userManager.update(matchingUsers.get(0), op, loginResp)); } else { username = matchingUsers.get(0); } } - loginResponse.setUsername(username); + loginResp.setUsername(username); // 4. generate JWT for further access Map claims = new HashMap<>(); @@ -236,18 +235,18 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat byte[] authorities = null; try { authorities = ENCRYPTOR.encode(POJOHelper.serialize( - authDataAccessor.getAuthorities(loginResponse.getUsername(), null)), CipherAlgorithm.AES). + authDataAccessor.getAuthorities(loginResp.getUsername(), null)), CipherAlgorithm.AES). getBytes(); } catch (Exception e) { LOG.error("Could not fetch authorities", e); } Pair accessTokenInfo = - accessTokenDataBinder.create(loginResponse.getUsername(), claims, authorities, true); - loginResponse.setAccessToken(accessTokenInfo.getLeft()); - loginResponse.setAccessTokenExpiryTime(accessTokenInfo.getRight()); + accessTokenDataBinder.create(loginResp.getUsername(), claims, authorities, true); + loginResp.setAccessToken(accessTokenInfo.getLeft()); + loginResp.setAccessTokenExpiryTime(accessTokenInfo.getRight()); - return loginResponse; + return loginResp; } @PreAuthorize("isAuthenticated() and not(hasRole('" + IdRepoEntitlement.ANONYMOUS + "'))") @@ -274,7 +273,7 @@ public OIDCRequest createLogoutRequest(final String accessToken, final String re profile.setIdTokenString((String) claimsSet.getClaim(JWT_CLAIM_ID_TOKEN)); WithLocationAction action = oidcClient.getLogoutAction( - new CallContext(new OIDC4UIContext(), NoOpSessionStore.INSTANCE), + new CallContext(new OIDCC4UIContext(), NoOpSessionStore.INSTANCE), profile, redirectURI). map(WithLocationAction.class::cast). diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UIProviderLogic.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UIProviderLogic.java index d933b0bd79..6bfcdd3e2b 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UIProviderLogic.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UIProviderLogic.java @@ -29,7 +29,7 @@ import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO; import org.apache.syncope.common.lib.types.ClientExceptionType; -import org.apache.syncope.common.lib.types.OIDC4UIEntitlement; +import org.apache.syncope.common.lib.types.OIDCC4UIEntitlement; import org.apache.syncope.core.logic.oidc.OIDCClientCache; import org.apache.syncope.core.persistence.api.dao.NotFoundException; import org.apache.syncope.core.persistence.api.dao.OIDCC4UIProviderDAO; @@ -60,7 +60,7 @@ public OIDCC4UIProviderLogic( this.binder = binder; } - @PreAuthorize("hasRole('" + OIDC4UIEntitlement.OP_CREATE + "')") + @PreAuthorize("hasRole('" + OIDCC4UIEntitlement.OP_CREATE + "')") public String createFromDiscovery(final OIDCC4UIProviderTO opTO) { try { OIDCClientCache.importMetadata(opTO); @@ -74,7 +74,7 @@ public String createFromDiscovery(final OIDCC4UIProviderTO opTO) { } } - @PreAuthorize("hasRole('" + OIDC4UIEntitlement.OP_CREATE + "')") + @PreAuthorize("hasRole('" + OIDCC4UIEntitlement.OP_CREATE + "')") public String create(final OIDCC4UIProviderTO opTO) { if (opTO.getConnObjectKeyItem() == null) { Item connObjectKeyItem = new Item(); @@ -94,7 +94,7 @@ public List list() { return opDAO.findAll().stream().map(binder::getOIDCProviderTO).collect(Collectors.toList()); } - @PreAuthorize("hasRole('" + OIDC4UIEntitlement.OP_READ + "')") + @PreAuthorize("hasRole('" + OIDCC4UIEntitlement.OP_READ + "')") @Transactional(readOnly = true) public OIDCC4UIProviderTO read(final String key) { OIDCC4UIProvider op = Optional.ofNullable(opDAO.find(key)). @@ -103,7 +103,7 @@ public OIDCC4UIProviderTO read(final String key) { return binder.getOIDCProviderTO(op); } - @PreAuthorize("hasRole('" + OIDC4UIEntitlement.OP_UPDATE + "')") + @PreAuthorize("hasRole('" + OIDCC4UIEntitlement.OP_UPDATE + "')") public void update(final OIDCC4UIProviderTO opTO) { OIDCC4UIProvider op = Optional.ofNullable(opDAO.find(opTO.getKey())). orElseThrow(() -> new NotFoundException("OIDC Provider '" + opTO.getKey() + '\'')); @@ -121,7 +121,7 @@ public void update(final OIDCC4UIProviderTO opTO) { oidcClientCacheLogout.removeAll(op.getName()); } - @PreAuthorize("hasRole('" + OIDC4UIEntitlement.OP_DELETE + "')") + @PreAuthorize("hasRole('" + OIDCC4UIEntitlement.OP_DELETE + "')") public void delete(final String key) { OIDCC4UIProvider op = Optional.ofNullable(opDAO.find(key)). orElseThrow(() -> new NotFoundException("OIDC Provider '" + key + '\'')); diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCC4UILoader.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCC4UILoader.java index 93d240a484..d4e4f3e4d8 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCC4UILoader.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCC4UILoader.java @@ -20,7 +20,7 @@ import org.apache.syncope.common.lib.types.EntitlementsHolder; import org.apache.syncope.common.lib.types.ImplementationTypesHolder; -import org.apache.syncope.common.lib.types.OIDC4UIEntitlement; +import org.apache.syncope.common.lib.types.OIDCC4UIEntitlement; import org.apache.syncope.common.lib.types.OIDCClientImplementationType; import org.apache.syncope.core.persistence.api.SyncopeCoreLoader; import org.springframework.core.Ordered; @@ -34,7 +34,7 @@ public int getOrder() { @Override public void load() { - EntitlementsHolder.getInstance().addAll(OIDC4UIEntitlement.values()); + EntitlementsHolder.getInstance().addAll(OIDCC4UIEntitlement.values()); ImplementationTypesHolder.getInstance().putAll(OIDCClientImplementationType.values()); } } diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDC4UIContext.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCC4UIContext.java similarity index 98% rename from ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDC4UIContext.java rename to ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCC4UIContext.java index 0520b14c21..c23b4b982e 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDC4UIContext.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCC4UIContext.java @@ -26,7 +26,7 @@ import org.pac4j.core.context.Cookie; import org.pac4j.core.context.WebContext; -public class OIDC4UIContext implements WebContext { +public class OIDCC4UIContext implements WebContext { @Override public String getRequestMethod() { diff --git a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java index 0453a1ff41..19d515a711 100644 --- a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java +++ b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java @@ -319,10 +319,8 @@ public AuthnRequest build(final SAML2MessageContext context) { @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')") public SAML2LoginResponse validateLoginResponse(final SAML2Response saml2Response) { // 0. look for IdP - SAML2SP4UIIdP idp = idpDAO.findByEntityID(saml2Response.getIdpEntityID()); - if (idp == null) { - throw new NotFoundException("SAML 2.0 IdP '" + saml2Response.getIdpEntityID() + '\''); - } + SAML2SP4UIIdP idp = Optional.ofNullable(idpDAO.findByEntityID(saml2Response.getIdpEntityID())). + orElseThrow(() -> new NotFoundException("SAML 2.0 IdP '" + saml2Response.getIdpEntityID() + '\'')); // 1. look for configured client SAML2Client saml2Client = getSAML2Client( @@ -376,7 +374,7 @@ public SAML2LoginResponse validateLoginResponse(final SAML2Response saml2Respons for (SAML2AuthenticationCredentials.SAMLAttribute attr : authCreds.getAttributes()) { if (!attr.getAttributeValues().isEmpty()) { - String attrName = attr.getFriendlyName() == null ? attr.getName() : attr.getFriendlyName(); + String attrName = Optional.ofNullable(attr.getFriendlyName()).orElse(attr.getName()); if (connObjectKeyItem != null && attrName.equals(connObjectKeyItem.getExtAttrName())) { keyValue = attr.getAttributeValues().get(0); } @@ -385,9 +383,9 @@ public SAML2LoginResponse validateLoginResponse(final SAML2Response saml2Respons } } - List matchingUsers = keyValue == null - ? List.of() - : userManager.findMatchingUser(keyValue, idp.getKey()); + List matchingUsers = Optional.ofNullable(keyValue). + map(k -> userManager.findMatchingUser(k, idp.getKey())). + orElse(List.of()); LOG.debug("Found {} matching users for {}", matchingUsers.size(), keyValue); String username; diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java index 9138255349..8b1f2d5a6e 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java @@ -32,16 +32,13 @@ import jakarta.ws.rs.core.Response; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.apache.commons.io.IOUtils; import org.apache.cxf.jaxrs.client.Client; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.syncope.client.lib.batch.BatchRequest; @@ -201,7 +198,7 @@ public void webClientSync() throws IOException { assertTrue(response.getMediaType().toString(). startsWith(RESTHeaders.multipartMixedWith(boundary.substring(2)))); - String body = IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8); + String body = response.readEntity(String.class); LOG.debug("Batch response body:\n{}", body); check(BatchPayloadParser.parse( @@ -245,7 +242,7 @@ public void webClientAsync() throws IOException { assertTrue(response.getMediaType().toString(). startsWith(RESTHeaders.multipartMixedWith(boundary.substring(2)))); - String body = IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8); + String body = response.readEntity(String.class); LOG.debug("Batch response body:\n{}", body); check(BatchPayloadParser.parse( diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java index 1d03b1097a..ac83c8976e 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java @@ -26,9 +26,6 @@ import jakarta.ws.rs.core.Response; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import org.apache.commons.io.IOUtils; import org.apache.syncope.client.lib.SyncopeClientFactoryBean; import org.apache.syncope.common.lib.to.BpmnProcess; import org.apache.syncope.fit.AbstractITCase; @@ -62,8 +59,7 @@ public void exportUserWorkflowProcess() throws IOException { Response response = BPMN_PROCESS_SERVICE.get(USER_WORKFLOW_KEY); assertTrue(response.getMediaType().toString(). startsWith(CLIENT_FACTORY.getContentType().getMediaType().toString())); - assertTrue(response.getEntity() instanceof InputStream); - String definition = IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8); + String definition = response.readEntity(String.class); assertNotNull(definition); assertFalse(definition.isEmpty()); } @@ -71,7 +67,7 @@ public void exportUserWorkflowProcess() throws IOException { @Test public void updateUserWorkflowProcess() throws IOException { Response response = BPMN_PROCESS_SERVICE.get(USER_WORKFLOW_KEY); - String definition = IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8); + String definition = response.readEntity(String.class); BPMN_PROCESS_SERVICE.set(USER_WORKFLOW_KEY, definition); } diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MailTemplateITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MailTemplateITCase.java index d1cd7fc32d..14a42482d5 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MailTemplateITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MailTemplateITCase.java @@ -27,7 +27,6 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.List; import org.apache.commons.io.IOUtils; @@ -89,10 +88,7 @@ public void crud() throws IOException { response = MAIL_TEMPLATE_SERVICE.getFormat(key, MailTemplateFormat.TEXT); assertEquals(200, response.getStatus()); assertTrue(response.getMediaType().toString().startsWith(MediaType.TEXT_PLAIN)); - assertTrue(response.getEntity() instanceof InputStream); - assertEquals( - textTemplate, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(textTemplate, response.readEntity(String.class)); // 3. set HTML String htmlTemplate = "Hi there, I am ${username}."; @@ -102,10 +98,7 @@ public void crud() throws IOException { response = MAIL_TEMPLATE_SERVICE.getFormat(key, MailTemplateFormat.HTML); assertEquals(200, response.getStatus()); assertTrue(response.getMediaType().toString().startsWith(MediaType.TEXT_HTML)); - assertTrue(response.getEntity() instanceof InputStream); - assertEquals( - htmlTemplate, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(htmlTemplate, response.readEntity(String.class)); // 4. remove HTML MAIL_TEMPLATE_SERVICE.removeFormat(key, MailTemplateFormat.HTML); @@ -120,10 +113,7 @@ public void crud() throws IOException { response = MAIL_TEMPLATE_SERVICE.getFormat(key, MailTemplateFormat.TEXT); assertEquals(200, response.getStatus()); assertTrue(response.getMediaType().toString().startsWith(MediaType.TEXT_PLAIN)); - assertTrue(response.getEntity() instanceof InputStream); - assertEquals( - textTemplate, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(textTemplate, response.readEntity(String.class)); // 5. remove mail template MAIL_TEMPLATE_SERVICE.delete(key); diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RESTITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RESTITCase.java index 35bccd020f..588ed0ee3f 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RESTITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RESTITCase.java @@ -33,10 +33,7 @@ import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.List; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.jaxrs.client.WebClient; @@ -108,9 +105,7 @@ public void noContent() throws IOException { Response response = noContentService.create(groupCR); assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); assertEquals(Preference.RETURN_NO_CONTENT.toString(), response.getHeaderString(RESTHeaders.PREFERENCE_APPLIED)); - assertEquals( - StringUtils.EMPTY, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(StringUtils.EMPTY, response.readEntity(String.class)); GroupTO group = getObject(response.getLocation(), GroupService.class, GroupTO.class); assertNotNull(group); @@ -122,16 +117,12 @@ public void noContent() throws IOException { response = noContentService.update(groupUR); assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); assertEquals(Preference.RETURN_NO_CONTENT.toString(), response.getHeaderString(RESTHeaders.PREFERENCE_APPLIED)); - assertEquals( - StringUtils.EMPTY, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(StringUtils.EMPTY, response.readEntity(String.class)); response = noContentService.delete(group.getKey()); assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); assertEquals(Preference.RETURN_NO_CONTENT.toString(), response.getHeaderString(RESTHeaders.PREFERENCE_APPLIED)); - assertEquals( - StringUtils.EMPTY, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(StringUtils.EMPTY, response.readEntity(String.class)); } @Test @@ -203,9 +194,7 @@ public void exportInternalStorageContent() throws IOException { String contentDisposition = response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION); assertNotNull(contentDisposition); - Object entity = response.getEntity(); - assertTrue(entity instanceof InputStream); - String configExport = IOUtils.toString((InputStream) entity, StandardCharsets.UTF_8.name()); + String configExport = response.readEntity(String.class); assertFalse(configExport.isEmpty()); assertTrue(configExport.length() > 1000); } diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java index 74e6a218cd..81f56233b3 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java @@ -28,14 +28,11 @@ import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Response; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.apache.commons.io.IOUtils; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.ExecTO; import org.apache.syncope.common.lib.to.ReportTO; @@ -158,9 +155,7 @@ public void executeAndExport() throws IOException { assertNotNull(response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION)); assertTrue(response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION).endsWith(".pdf")); - Object entity = response.getEntity(); - assertTrue(entity instanceof InputStream); - assertFalse(IOUtils.toString((InputStream) entity, StandardCharsets.UTF_8.name()).isEmpty()); + assertFalse(response.readEntity(String.class).isEmpty()); } @Test 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 2de353057f..939c90551b 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 @@ -22,12 +22,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.nimbusds.jose.util.IOUtils; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -53,6 +50,7 @@ import org.apache.syncope.common.rest.api.service.UserService; import org.apache.syncope.common.rest.api.service.wa.WAConfigService; import org.apache.syncope.fit.sra.AbstractSRAITCase; +import org.apereo.cas.oidc.OidcConstants; import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.FormElement; @@ -117,26 +115,22 @@ public static void waitForWARefresh() { await().atMost(60, TimeUnit.SECONDS).pollInterval(20, TimeUnit.SECONDS).until(() -> { boolean refreshed = false; try { - String metadata = IOUtils.readInputStreamToString( - (InputStream) WebClient.create( - WA_ADDRESS + "/idp/metadata").get().getEntity(), - StandardCharsets.UTF_8); + String metadata = WebClient.create( + WA_ADDRESS + "/idp/metadata").get().readEntity(String.class); if (metadata.contains("localhost:8080")) { WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); throw new IllegalStateException(); } - metadata = IOUtils.readInputStreamToString( - (InputStream) WebClient.create( - WA_ADDRESS + "/oidc/.well-known/openid-configuration").get().getEntity(), - StandardCharsets.UTF_8); + metadata = WebClient.create( + WA_ADDRESS + "/oidc/" + OidcConstants.WELL_KNOWN_OPENID_CONFIGURATION_URL). + get().readEntity(String.class); if (metadata.contains("localhost:8080")) { WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); throw new IllegalStateException(); } - metadata = IOUtils.readInputStreamToString( - (InputStream) WebClient.create( - WA_ADDRESS + "/actuator/registeredServices", "anonymous", "anonymousKey", null). - get().getEntity(), StandardCharsets.UTF_8); + metadata = WebClient.create( + WA_ADDRESS + "/actuator/registeredServices", "anonymous", "anonymousKey", null). + get().readEntity(String.class); if (metadata.contains("localhost:8080/syncope-wa")) { WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); throw new IllegalStateException(); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java index a9c0844514..825d7315bf 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java @@ -45,6 +45,7 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.CASSPClientAppTO; import org.apache.syncope.common.lib.types.ClientAppType; import org.apache.syncope.common.rest.api.RESTHeaders; @@ -71,6 +72,7 @@ public static void clientAppSetup() { orElseGet(() -> { CASSPClientAppTO app = new CASSPClientAppTO(); app.setName(appName); + app.setRealm(SyncopeConstants.ROOT_REALM); app.setClientAppId(4L); app.setServiceId("http://127.0.0.1:8080/.*"); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java index f356adf8bc..cc5eae5b65 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java @@ -51,6 +51,7 @@ public static void clientAppSetup() { } catch (Exception e) { fail("Could not load /sra-oauth2.properties", e); } + CLIENT_APP_ID = 2L; CLIENT_ID = props.getProperty("sra.oauth2.client-id"); assertNotNull(CLIENT_ID); CLIENT_SECRET = props.getProperty("sra.oauth2.client-secret"); @@ -58,7 +59,7 @@ public static void clientAppSetup() { TOKEN_URI = props.getProperty("sra.oauth2.tokenUri"); assertNotNull(TOKEN_URI); - oidcClientAppSetup(OAUTH2SRAITCase.class.getName(), "OAUTH2", 2L, CLIENT_ID, CLIENT_SECRET); + oidcClientAppSetup(OAUTH2SRAITCase.class.getName(), "OAUTH2", CLIENT_APP_ID, CLIENT_ID, CLIENT_SECRET); } @Override diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java index fe056c1720..53d1f821aa 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java @@ -61,6 +61,7 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apache.syncope.common.lib.types.ClientAppType; import org.apache.syncope.common.lib.types.OIDCGrantType; @@ -68,12 +69,15 @@ import org.apache.syncope.common.lib.types.OIDCSubjectType; import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.syncope.common.rest.api.service.wa.WAConfigService; +import org.apereo.cas.oidc.OidcConstants; import org.jsoup.Jsoup; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class OIDCSRAITCase extends AbstractSRAITCase { + protected static Long CLIENT_APP_ID; + protected static String CLIENT_ID; protected static String CLIENT_SECRET; @@ -101,6 +105,7 @@ protected static void oidcClientAppSetup( orElseGet(() -> { OIDCRPClientAppTO app = new OIDCRPClientAppTO(); app.setName(appName); + app.setRealm(SyncopeConstants.ROOT_REALM); app.setClientAppId(clientAppId); app.setClientId(clientId); app.setClientSecret(clientSecret); @@ -129,9 +134,9 @@ protected static void oidcClientAppSetup( clientApp.getScopes().add(OIDCScope.profile); clientApp.getScopes().add(OIDCScope.email); clientApp.getSupportedGrantTypes().add(OIDCGrantType.password); + clientApp.getSupportedGrantTypes().add(OIDCGrantType.authorization_code); CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, clientApp); - WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); } @BeforeAll @@ -144,17 +149,20 @@ public static void clientAppSetup() { } catch (Exception e) { fail("Could not load /sra-oidc.properties", e); } + CLIENT_APP_ID = 1L; CLIENT_ID = props.getProperty("sra.oidc.client-id"); assertNotNull(CLIENT_ID); CLIENT_SECRET = props.getProperty("sra.oidc.client-secret"); assertNotNull(CLIENT_SECRET); TOKEN_URI = WA_ADDRESS + "/oidc/accessToken"; - oidcClientAppSetup(OIDCSRAITCase.class.getName(), "OIDC", 1L, CLIENT_ID, CLIENT_SECRET); + oidcClientAppSetup(OIDCSRAITCase.class.getName(), "OIDC", CLIENT_APP_ID, CLIENT_ID, CLIENT_SECRET); } @Test public void web() throws IOException { + WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); + CloseableHttpClient httpclient = HttpClients.createDefault(); HttpClientContext context = HttpClientContext.create(); context.setCookieStore(new BasicCookieStore()); @@ -253,21 +261,22 @@ protected boolean checkIdToken() { @Test public void rest() throws IOException, ParseException { await().atMost(60, TimeUnit.SECONDS).pollInterval(20, TimeUnit.SECONDS).until(() -> { - boolean refreshed = false; try { String metadata = WebClient.create( - WA_ADDRESS + "/oidc/.well-known/openid-configuration").get().readEntity(String.class); + WA_ADDRESS + "/oidc/" + OidcConstants.WELL_KNOWN_OPENID_CONFIGURATION_URL). + get().readEntity(String.class); if (!metadata.contains("groups")) { WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); throw new IllegalStateException(); } - refreshed = true; + return true; } catch (Exception e) { // ignore } - return refreshed; + return false; }); + WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); // 0. access public route WebClient client = WebClient.create(SRA_ADDRESS + "/public/post"). diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java index dde18edef9..f2639ad1c2 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java @@ -46,6 +46,7 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.SAML2SPClientAppTO; import org.apache.syncope.common.lib.types.ClientAppType; import org.apache.syncope.common.lib.types.SAML2SPNameId; @@ -73,6 +74,7 @@ public static void clientAppSetup() { orElseGet(() -> { SAML2SPClientAppTO app = new SAML2SPClientAppTO(); app.setName(appName); + app.setRealm(SyncopeConstants.ROOT_REALM); app.setClientAppId(3L); app.setEntityId(SRA_ADDRESS); app.setMetadataLocation(SRA_ADDRESS + "/saml2/metadata"); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java index 9e5bc73a28..6378e72a94 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java @@ -84,10 +84,10 @@ protected static AttrReleasePolicyTO getAttrReleasePolicy() { DefaultAttrReleasePolicyConf policyConf = new DefaultAttrReleasePolicyConf(); policyConf.getPrincipalAttrRepoConf().getAttrRepos().add(stubAttrRepo); policyConf.getReleaseAttrs().put("attr1", "identifier"); - policyConf.getReleaseAttrs().put("firstname", "givenName"); - policyConf.getReleaseAttrs().put("surname", "sn"); - policyConf.getReleaseAttrs().put("fullname", "cn"); - policyConf.getReleaseAttrs().put("email", "mail"); + policyConf.getReleaseAttrs().put("firstname", "given_name"); + policyConf.getReleaseAttrs().put("surname", "family_name"); + policyConf.getReleaseAttrs().put("fullname", "name"); + policyConf.getReleaseAttrs().put("email", "email"); AttrReleasePolicyTO policy = new AttrReleasePolicyTO(); policy.setName(description); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java similarity index 95% rename from fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java rename to fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java index 2445e00e72..f3d222218e 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java @@ -46,6 +46,7 @@ import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.apache.syncope.client.ui.commons.panels.OIDCC4UIConstants; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; @@ -58,7 +59,7 @@ import org.jsoup.Jsoup; import org.junit.jupiter.api.BeforeAll; -public class OIDC4UIITCase extends AbstractUIITCase { +public class OIDCC4UIITCase extends AbstractUIITCase { private static void clientAppSetup(final String appName, final String baseAddress, final long appId) { OIDCRPClientAppTO clientApp = CLIENT_APP_SERVICE.list(ClientAppType.OIDCRP).stream(). @@ -68,6 +69,7 @@ private static void clientAppSetup(final String appName, final String baseAddres orElseGet(() -> { OIDCRPClientAppTO app = new OIDCRPClientAppTO(); app.setName(appName); + app.setRealm(SyncopeConstants.ROOT_REALM); app.setClientAppId(appId); app.setClientId(appName); app.setClientSecret(appName); @@ -104,8 +106,8 @@ private static void clientAppSetup(final String appName, final String baseAddres private static String getAppName(final String address) { return CONSOLE_ADDRESS.equals(address) - ? OIDC4UIITCase.class.getName() + "_Console" - : OIDC4UIITCase.class.getName() + "_Enduser"; + ? OIDCC4UIITCase.class.getName() + "_Console" + : OIDCC4UIITCase.class.getName() + "_Enduser"; } @BeforeAll @@ -153,27 +155,27 @@ private static void oidcSetup( item = new Item(); item.setIntAttrName("email"); - item.setExtAttrName("mail"); + item.setExtAttrName("email"); cas.add(item); item = new Item(); item.setIntAttrName("userId"); - item.setExtAttrName("mail"); + item.setExtAttrName("email"); cas.add(item); item = new Item(); item.setIntAttrName("firstname"); - item.setExtAttrName("givenName"); + item.setExtAttrName("given_name"); cas.add(item); item = new Item(); item.setIntAttrName("surname"); - item.setExtAttrName("sn"); + item.setExtAttrName("family_name"); cas.add(item); item = new Item(); item.setIntAttrName("fullname"); - item.setExtAttrName("cn"); + item.setExtAttrName("name"); cas.add(item); OIDCC4UI_PROVIDER_SERVICE.create(cas); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java index 6751bd66a4..f4fbfde991 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java @@ -48,6 +48,7 @@ import org.apache.http.util.EntityUtils; import org.apache.syncope.client.ui.commons.SAML2SP4UIConstants; import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.to.SAML2SP4UIIdPTO; import org.apache.syncope.common.lib.to.SAML2SPClientAppTO; @@ -68,6 +69,7 @@ private static void clientAppSetup(final String appName, final String entityId, orElseGet(() -> { SAML2SPClientAppTO app = new SAML2SPClientAppTO(); app.setName(appName); + app.setRealm(SyncopeConstants.ROOT_REALM); app.setClientAppId(appId); app.setEntityId(entityId); app.setMetadataLocation(entityId + SAML2SP4UIConstants.URL_CONTEXT + "/metadata"); @@ -136,27 +138,27 @@ public static void idpSetup() { item = new Item(); item.setIntAttrName("email"); - item.setExtAttrName("mail"); + item.setExtAttrName("email"); cas.add(item); item = new Item(); item.setIntAttrName("userId"); - item.setExtAttrName("mail"); + item.setExtAttrName("email"); cas.add(item); item = new Item(); item.setIntAttrName("firstname"); - item.setExtAttrName("givenName"); + item.setExtAttrName("given_name"); cas.add(item); item = new Item(); item.setIntAttrName("surname"); - item.setExtAttrName("sn"); + item.setExtAttrName("family_name"); cas.add(item); item = new Item(); item.setIntAttrName("fullname"); - item.setExtAttrName("cn"); + item.setExtAttrName("name"); cas.add(item); SAML2SP4UI_IDP_SERVICE.update(cas); diff --git a/fit/wa-reference/src/test/resources/sra-oidc.properties b/fit/wa-reference/src/test/resources/sra-oidc.properties index 9f7b8f3ea3..c4a60b99ba 100644 --- a/fit/wa-reference/src/test/resources/sra-oidc.properties +++ b/fit/wa-reference/src/test/resources/sra-oidc.properties @@ -22,5 +22,6 @@ sra.am-type=OIDC sra.oidc.configuration=https://localhost:9443/syncope-wa/oidc sra.oidc.client-id=oidcTestClientId sra.oidc.client-secret=oidcTestClientSecret +sra.oidc.scopes=openid,profile,email sra.global.postLogout=http://localhost:8080/logout diff --git a/pom.xml b/pom.xml index 3b19b8cd03..a404ffa6a7 100644 --- a/pom.xml +++ b/pom.xml @@ -414,7 +414,7 @@ under the License. 9.31 3.1.2 - 4.0.6 + 4.0.7 4.0.0-SNAPSHOT @@ -1212,7 +1212,7 @@ under the License. org.springframework.cloud spring-cloud-contract-wiremock - 4.0.3 + 4.0.4 test diff --git a/sra/src/test/resources/debug/sra-debug.properties b/sra/src/test/resources/debug/sra-debug.properties index 2efa2abf8d..38113343aa 100644 --- a/sra/src/test/resources/debug/sra-debug.properties +++ b/sra/src/test/resources/debug/sra-debug.properties @@ -22,6 +22,7 @@ keymaster.password=${anonymousKey} #sra.oidc.configuration=https://localhost:9443/syncope-wa/oidc #sra.oidc.client-id=oidcTestClientId #sra.oidc.client-secret=oidcTestClientSecret +#sra.oidc.scopes=openid,profile,email #sra.am-type=OAUTH2 #sra.oauth2.tokenUri=https://localhost:9443/syncope-wa/oauth2.0/accessToken From a34658b94b12e1d7ffdf3020c16cd9e03fdd6339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Fri, 28 Jul 2023 15:36:45 +0200 Subject: [PATCH 6/7] Finally fixing --- .../java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java | 4 +++- .../java/org/apache/syncope/fit/sra/OIDCSRAITCase.java | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java index cc5eae5b65..4f6cec2040 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java @@ -51,6 +51,7 @@ public static void clientAppSetup() { } catch (Exception e) { fail("Could not load /sra-oauth2.properties", e); } + SRA_REGISTRATION_ID = "OAUTH2"; CLIENT_APP_ID = 2L; CLIENT_ID = props.getProperty("sra.oauth2.client-id"); assertNotNull(CLIENT_ID); @@ -59,7 +60,8 @@ public static void clientAppSetup() { TOKEN_URI = props.getProperty("sra.oauth2.tokenUri"); assertNotNull(TOKEN_URI); - oidcClientAppSetup(OAUTH2SRAITCase.class.getName(), "OAUTH2", CLIENT_APP_ID, CLIENT_ID, CLIENT_SECRET); + oidcClientAppSetup( + OAUTH2SRAITCase.class.getName(), SRA_REGISTRATION_ID, CLIENT_APP_ID, CLIENT_ID, CLIENT_SECRET); } @Override diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java index 53d1f821aa..6abba368af 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java @@ -76,6 +76,8 @@ public class OIDCSRAITCase extends AbstractSRAITCase { + protected static String SRA_REGISTRATION_ID; + protected static Long CLIENT_APP_ID; protected static String CLIENT_ID; @@ -149,6 +151,7 @@ public static void clientAppSetup() { } catch (Exception e) { fail("Could not load /sra-oidc.properties", e); } + SRA_REGISTRATION_ID = "OIDC"; CLIENT_APP_ID = 1L; CLIENT_ID = props.getProperty("sra.oidc.client-id"); assertNotNull(CLIENT_ID); @@ -156,7 +159,8 @@ public static void clientAppSetup() { assertNotNull(CLIENT_SECRET); TOKEN_URI = WA_ADDRESS + "/oidc/accessToken"; - oidcClientAppSetup(OIDCSRAITCase.class.getName(), "OIDC", CLIENT_APP_ID, CLIENT_ID, CLIENT_SECRET); + oidcClientAppSetup( + OIDCSRAITCase.class.getName(), SRA_REGISTRATION_ID, CLIENT_APP_ID, CLIENT_ID, CLIENT_SECRET); } @Test @@ -276,6 +280,7 @@ public void rest() throws IOException, ParseException { } return false; }); + oidcClientAppSetup(getClass().getName(), SRA_REGISTRATION_ID, CLIENT_APP_ID, CLIENT_ID, CLIENT_SECRET); WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); // 0. access public route From b0111d14d2863987982de6977e20bb075282dbc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Fri, 28 Jul 2023 16:29:48 +0200 Subject: [PATCH 7/7] Finally fixing - take 2 --- .../apache/syncope/fit/sra/OIDCSRAITCase.java | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java index 6abba368af..51052845d6 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java @@ -139,6 +139,24 @@ protected static void oidcClientAppSetup( clientApp.getSupportedGrantTypes().add(OIDCGrantType.authorization_code); CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, clientApp); + + await().atMost(60, TimeUnit.SECONDS).pollInterval(20, TimeUnit.SECONDS).until(() -> { + try { + String metadata = WebClient.create( + WA_ADDRESS + "/oidc/" + OidcConstants.WELL_KNOWN_OPENID_CONFIGURATION_URL). + get().readEntity(String.class); + if (!metadata.contains("groups")) { + 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()); } @BeforeAll @@ -165,8 +183,6 @@ public static void clientAppSetup() { @Test public void web() throws IOException { - WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); - CloseableHttpClient httpclient = HttpClients.createDefault(); HttpClientContext context = HttpClientContext.create(); context.setCookieStore(new BasicCookieStore()); @@ -264,25 +280,6 @@ protected boolean checkIdToken() { @Test public void rest() throws IOException, ParseException { - await().atMost(60, TimeUnit.SECONDS).pollInterval(20, TimeUnit.SECONDS).until(() -> { - try { - String metadata = WebClient.create( - WA_ADDRESS + "/oidc/" + OidcConstants.WELL_KNOWN_OPENID_CONFIGURATION_URL). - get().readEntity(String.class); - if (!metadata.contains("groups")) { - WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); - throw new IllegalStateException(); - } - - return true; - } catch (Exception e) { - // ignore - } - return false; - }); - oidcClientAppSetup(getClass().getName(), SRA_REGISTRATION_ID, CLIENT_APP_ID, CLIENT_ID, CLIENT_SECRET); - WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); - // 0. access public route WebClient client = WebClient.create(SRA_ADDRESS + "/public/post"). accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON);