Skip to content

Commit

Permalink
Allow to dynamically reload OIDC discovery claims
Browse files Browse the repository at this point in the history
  • Loading branch information
ilgrosso committed Jul 27, 2023
1 parent 782be0a commit 2ee8b61
Show file tree
Hide file tree
Showing 19 changed files with 164 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
package org.apache.syncope.common.lib.types;

public enum OIDCScope {
OPENID,
PROFILE,
EMAIL,
ADDRESS,
PHONE
openid,
profile,
email,
address,
phone

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>(
new PropertyModel<>(opTO, "scopes")).build("scopes", "scopes", value));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions wa/bootstrap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ under the License.
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-util-api</artifactId>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-authentication-attributes</artifactId>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-oidc-core</artifactId>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -58,8 +62,8 @@ public WARestClient waRestClient() {
@Configuration(proxyBeanMethods = false)
public static class PropertySourceConfiguration {

@Bean
@ConditionalOnMissingBean(name = "waConfigurationCipher")
@Bean
public CipherExecutor<String, String> waConfigurationCipher(final Environment environment) {
return new CasConfigurationJasyptCipherExecutor(environment);
}
Expand All @@ -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<String, String> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -49,17 +68,21 @@ public class WAPropertySourceLocator implements PropertySourceLocator {

protected final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper;

protected final AttrReleaseMapper attrReleaseMapper;

protected final CipherExecutor<String, String> configurationCipher;

public WAPropertySourceLocator(
final WARestClient waRestClient,
final AuthModulePropertySourceMapper authModulePropertySourceMapper,
final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper,
final AttrReleaseMapper attrReleaseMapper,
final CipherExecutor<String, String> configurationCipher) {

this.waRestClient = waRestClient;
this.authModulePropertySourceMapper = authModulePropertySourceMapper;
this.attrRepoPropertySourceMapper = attrRepoPropertySourceMapper;
this.attrReleaseMapper = attrReleaseMapper;
this.configurationCipher = configurationCipher;
}

Expand Down Expand Up @@ -109,6 +132,56 @@ public PropertySource<?> locate(final Environment environment) {
properties.putAll(index(map, prefixes));
});

Set<String> customClaims = syncopeClient.getService(WAClientAppService.class).list().stream().
filter(clientApp -> clientApp.getAttrReleasePolicy() != null
&& clientApp.getClientAppTO() instanceof OIDCRPClientAppTO).
flatMap(clientApp -> {
OIDCRPClientAppTO rp = OIDCRPClientAppTO.class.cast(clientApp.getClientAppTO());

RegisteredServiceAttributeReleasePolicy attributeReleasePolicy =
attrReleaseMapper.build(clientApp.getAttrReleasePolicy());

Set<String> claims = new HashSet<>();
if (attributeReleasePolicy instanceof BaseMappedAttributeReleasePolicy baseMapped) {
claims.addAll(baseMapped.
getAllowedAttributes().values().stream().
map(Objects::toString).collect(Collectors.toSet()));
} else if (attributeReleasePolicy instanceof ReturnAllowedAttributeReleasePolicy returnAllowed) {
claims.addAll(returnAllowed.
getAllowedAttributes().stream().collect(Collectors.toSet()));
} else if (attributeReleasePolicy instanceof ChainingAttributeReleasePolicy chaining) {
chaining.getPolicies().stream().
filter(ReturnAllowedAttributeReleasePolicy.class::isInstance).
findFirst().map(ReturnAllowedAttributeReleasePolicy.class::cast).
map(p -> p.getAllowedAttributes().stream().collect(Collectors.toSet())).
ifPresent(claims::addAll);
}
if (rp.getScopes().contains(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(","))));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ protected Optional<NetworkService> getCore() {
return Optional.empty();
}

protected SyncopeClient getSyncopeClient() {
public SyncopeClient getSyncopeClient() {
synchronized (this) {
if (client == null) {
getCore().ifPresent(core -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 0 additions & 8 deletions wa/starter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,6 @@ under the License.
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-oidc-core-api</artifactId>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-oidc-core</artifactId>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-oauth-services</artifactId>
Expand Down Expand Up @@ -341,10 +337,6 @@ under the License.
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-swagger</artifactId>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-authentication-attributes</artifactId>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-services-authentication</artifactId>
Expand Down
Loading

0 comments on commit 2ee8b61

Please sign in to comment.