This is an automated email from the ASF dual-hosted git repository. quantranhong1999 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit f513ce1c694729688f4ef8f172ce3701bed8d554 Author: Quan Tran <[email protected]> AuthorDate: Thu Jul 2 16:14:00 2026 +0700 JAMES-4195 Add JMAP OIDC authentication strategy Adds JMAP OIDC configuration parsing, userinfo/introspection resolution, bearer authentication strategy, challenge realm override support, and focused unit tests. --- server/protocols/jmap/pom.xml | 4 + .../jmap/http/OidcAuthenticationStrategy.java | 115 +++++++++++++ .../james/jmap/oidc/JMAPOidcConfiguration.java | 181 +++++++++++++++++++++ .../james/jmap/oidc/OidcEndpointsInfoResolver.java | 103 ++++++++++++ .../jmap/http/OidcAuthenticationStrategyTest.java | 67 ++++++++ .../james/jmap/oidc/JMAPOidcConfigurationTest.java | 69 ++++++++ 6 files changed, 539 insertions(+) diff --git a/server/protocols/jmap/pom.xml b/server/protocols/jmap/pom.xml index 32f57a72f5..c650ecd765 100644 --- a/server/protocols/jmap/pom.xml +++ b/server/protocols/jmap/pom.xml @@ -68,6 +68,10 @@ <artifactId>testing-base</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/http/OidcAuthenticationStrategy.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/http/OidcAuthenticationStrategy.java new file mode 100644 index 0000000000..cfb88a8747 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/http/OidcAuthenticationStrategy.java @@ -0,0 +1,115 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.http; + +import static org.apache.james.jmap.http.JWTAuthenticationStrategy.AUTHORIZATION_HEADER_PREFIX; + +import java.time.Clock; +import java.util.List; +import java.util.Optional; + +import jakarta.inject.Inject; + +import org.apache.james.core.Username; +import org.apache.james.jmap.exceptions.UnauthorizedException; +import org.apache.james.jmap.oidc.Aud; +import org.apache.james.jmap.oidc.OidcTokenCache; +import org.apache.james.jmap.oidc.Token; +import org.apache.james.jmap.oidc.TokenInfo; +import org.apache.james.jwt.introspection.TokenIntrospectionException; +import org.apache.james.jwt.userinfo.UserInfoCheckException; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.SessionProvider; + +import com.google.common.collect.ImmutableMap; + +import reactor.core.publisher.Mono; +import reactor.netty.http.server.HttpServerRequest; + +public class OidcAuthenticationStrategy implements AuthenticationStrategy { + public static final String DEFAULT_AUTHENTICATION_CHALLENGE_REALM = "james"; + + private final SessionProvider sessionProvider; + private final OidcTokenCache oidcTokenCache; + private final Clock clock; + private final List<Aud> auds; + private final String authenticationChallengeRealm; + + @Inject + public OidcAuthenticationStrategy(SessionProvider sessionProvider, OidcTokenCache oidcTokenCache, Clock clock, List<Aud> auds) { + this(sessionProvider, oidcTokenCache, clock, auds, DEFAULT_AUTHENTICATION_CHALLENGE_REALM); + } + + public OidcAuthenticationStrategy(SessionProvider sessionProvider, OidcTokenCache oidcTokenCache, Clock clock, List<Aud> auds, + String authenticationChallengeRealm) { + this.sessionProvider = sessionProvider; + this.oidcTokenCache = oidcTokenCache; + this.clock = clock; + this.auds = auds; + this.authenticationChallengeRealm = authenticationChallengeRealm; + } + + @Override + public Mono<MailboxSession> createMailboxSession(HttpServerRequest httpRequest) { + return Mono.fromCallable(() -> authHeaders(httpRequest)) + .filter(header -> header.startsWith(AUTHORIZATION_HEADER_PREFIX)) + .map(header -> header.substring(AUTHORIZATION_HEADER_PREFIX.length())) + .map(Token::new) + .flatMap(oidcTokenCache::associatedInformation) + .<TokenInfo>handle((tokenInfo, sink) -> { + if (!auds.isEmpty() && !isAudienceAccepted(tokenInfo.aud())) { + sink.error(new UnauthorizedException("Wrong audience. Expected " + auds + " got " + tokenInfo.aud())); + return; + } + if (clock.instant().isAfter(tokenInfo.exp())) { + sink.error(new UnauthorizedException("Expired token")); + return; + } + + sink.next(tokenInfo); + }) + .map(tokenInfo -> Username.of(tokenInfo.email())) + .flatMap(username -> Mono.fromCallable(() -> sessionProvider.authenticate(username) + .withoutDelegation())) + .onErrorMap(TokenIntrospectionException.class, e -> new UnauthorizedException("Invalid OIDC token when introspection check", e)) + .onErrorMap(UserInfoCheckException.class, e -> new UnauthorizedException("Invalid OIDC token when user info check", e)); + } + + private boolean isAudienceAccepted(Optional<List<Aud>> maybeTokenAudiences) { + return maybeTokenAudiences.map(tokenAudiences -> { + for (Aud aud: auds) { + if (tokenAudiences.contains(aud)) { + return true; + } + } + return false; + }) + .orElse(true); // if no audience is present in the introspection response, we ignore audience validation + } + + @Override + public AuthenticationChallenge correspondingChallenge() { + return AuthenticationChallenge.of( + AuthenticationScheme.of("Bearer"), + ImmutableMap.of("realm", authenticationChallengeRealm, + "error", "invalid_token", + "scope", "openid profile email")); + } +} diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/oidc/JMAPOidcConfiguration.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/oidc/JMAPOidcConfiguration.java new file mode 100644 index 0000000000..5e9ff5d039 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/oidc/JMAPOidcConfiguration.java @@ -0,0 +1,181 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.oidc; + +import java.io.FileNotFoundException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.configuration2.Configuration; +import org.apache.commons.configuration2.ex.ConfigurationException; +import org.apache.james.jmap.http.OidcAuthenticationStrategy; +import org.apache.james.jwt.introspection.IntrospectionEndpoint; +import org.apache.james.utils.PropertiesProvider; + +import com.google.common.collect.ImmutableList; + +public class JMAPOidcConfiguration { + private static final String AUTHENTICATION_STRATEGIES_PROPERTY = "authentication.strategy.rfc8621"; + private static final String USERINFO_URL_PROPERTY = "oidc.userInfo.url"; + private static final String INTROSPECT_URL_PROPERTY = "oidc.introspect.url"; + private static final String INTROSPECT_CREDENTIALS_PROPERTY = "oidc.introspect.credentials"; + private static final String AUDIENCE_PROPERTY = "oidc.audience"; + private static final String CLAIM_PROPERTY = "oidc.claim"; + + @FunctionalInterface + public interface RequireOidcEnabled { + Builder oidcEnabled(boolean oidcEnabled); + } + + public static class Builder { + private final boolean oidcEnabled; + private Optional<URL> oidcUserInfoUrl = Optional.empty(); + private Optional<IntrospectionEndpoint> oidcIntrospectionEndpoint = Optional.empty(); + private Optional<String> oidcIntrospectionClaim = Optional.empty(); + private Optional<List<Aud>> oidcAudience = Optional.empty(); + + private Builder(boolean oidcEnabled) { + this.oidcEnabled = oidcEnabled; + } + + public Builder oidcUserInfoUrl(Optional<URL> url) { + this.oidcUserInfoUrl = url; + return this; + } + + public Builder oidcIntrospectionEndpoint(Optional<IntrospectionEndpoint> endpoint) { + this.oidcIntrospectionEndpoint = endpoint; + return this; + } + + public Builder oidcClaim(Optional<String> claim) { + this.oidcIntrospectionClaim = claim; + return this; + } + + public Builder oidcAudience(Optional<List<Aud>> aud) { + this.oidcAudience = aud; + return this; + } + + public JMAPOidcConfiguration build() { + if (oidcEnabled) { + checkOidcFieldsPresence(); + } + + return new JMAPOidcConfiguration( + oidcEnabled, + oidcUserInfoUrl, + oidcIntrospectionEndpoint, + oidcIntrospectionClaim, + oidcAudience); + } + + private void checkOidcFieldsPresence() { + if (oidcUserInfoUrl.isEmpty() || + oidcIntrospectionEndpoint.isEmpty() || + oidcIntrospectionClaim.isEmpty()) { + throw new IllegalStateException("All OIDC fields must be defined when OIDC is enabled."); + } + } + } + + public static RequireOidcEnabled builder() { + return oidcEnabled -> new Builder(oidcEnabled); + } + + public static JMAPOidcConfiguration parseConfiguration(PropertiesProvider propertiesProvider) throws ConfigurationException, FileNotFoundException { + return parseConfiguration(propertiesProvider.getConfiguration("jmap")); + } + + public static JMAPOidcConfiguration parseConfiguration(Configuration configuration) { + List<String> authenticationStrategies = configuration.getList(String.class, AUTHENTICATION_STRATEGIES_PROPERTY, ImmutableList.of()); + boolean oidcEnabled = authenticationStrategies.stream() + .anyMatch(JMAPOidcConfiguration::isOidcAuthenticationStrategy); + + Optional<URL> oidcUserInfoUrl = Optional.ofNullable(configuration.getString(USERINFO_URL_PROPERTY, null)) + .map(JMAPOidcConfiguration::parseUrl); + Optional<URL> oidcIntrospectUrl = Optional.ofNullable(configuration.getString(INTROSPECT_URL_PROPERTY, null)) + .map(JMAPOidcConfiguration::parseUrl); + Optional<String> oidcIntrospectCreds = Optional.ofNullable(configuration.getString(INTROSPECT_CREDENTIALS_PROPERTY, null)); + Optional<List<Aud>> oidcAudience = Optional.of(configuration.getList(String.class, AUDIENCE_PROPERTY, ImmutableList.of()).stream().map(Aud::new).toList()); + Optional<String> oidcIntrospectionClaim = Optional.ofNullable(configuration.getString(CLAIM_PROPERTY, null)); + + Optional<IntrospectionEndpoint> introspectionEndpoint = oidcIntrospectUrl.map(url -> new IntrospectionEndpoint(url, oidcIntrospectCreds)); + + return JMAPOidcConfiguration.builder() + .oidcEnabled(oidcEnabled) + .oidcUserInfoUrl(oidcUserInfoUrl) + .oidcIntrospectionEndpoint(introspectionEndpoint) + .oidcAudience(oidcAudience) + .oidcClaim(oidcIntrospectionClaim) + .build(); + } + + private static boolean isOidcAuthenticationStrategy(String authenticationStrategy) { + return authenticationStrategy.equals(OidcAuthenticationStrategy.class.getSimpleName()) || + authenticationStrategy.endsWith("." + OidcAuthenticationStrategy.class.getSimpleName()); + } + + private static URL parseUrl(String rawUrl) { + try { + return new URL(rawUrl); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Invalid OIDC URL: " + rawUrl, e); + } + } + + private final boolean oidcEnabled; + private final Optional<URL> oidcUserInfoUrl; + private final Optional<IntrospectionEndpoint> introspectionEndpoint; + private final Optional<String> oidcClaim; + private final Optional<List<Aud>> aud; + + JMAPOidcConfiguration(boolean oidcEnabled, Optional<URL> oidcUserInfoUrl, Optional<IntrospectionEndpoint> introspectionEndpoint, + Optional<String> oidcClaim, Optional<List<Aud>> aud) { + this.oidcEnabled = oidcEnabled; + this.oidcUserInfoUrl = oidcUserInfoUrl; + this.introspectionEndpoint = introspectionEndpoint; + this.oidcClaim = oidcClaim; + this.aud = aud; + } + + public boolean getOidcEnabled() { + return oidcEnabled; + } + + public URL getOidcUserInfoUrl() { + return oidcUserInfoUrl.get(); + } + + public String getOidcClaim() { + return oidcClaim.get(); + } + + public IntrospectionEndpoint getIntrospectionEndpoint() { + return introspectionEndpoint.get(); + } + + public List<Aud> getAud() { + return aud.get(); + } +} diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/oidc/OidcEndpointsInfoResolver.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/oidc/OidcEndpointsInfoResolver.java new file mode 100644 index 0000000000..0ac50e101a --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/oidc/OidcEndpointsInfoResolver.java @@ -0,0 +1,103 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.oidc; + +import java.net.URL; +import java.time.Instant; +import java.util.List; +import java.util.Optional; + +import jakarta.inject.Inject; +import jakarta.inject.Named; + +import org.apache.james.core.Username; +import org.apache.james.jmap.exceptions.UnauthorizedException; +import org.apache.james.jwt.DefaultCheckTokenClient; +import org.apache.james.jwt.introspection.IntrospectionEndpoint; +import org.apache.james.jwt.introspection.TokenIntrospectionResponse; +import org.apache.james.jwt.userinfo.UserinfoResponse; +import org.apache.james.metrics.api.MetricFactory; +import org.apache.james.util.streams.Iterators; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableList; + +import reactor.core.publisher.Mono; + +public class OidcEndpointsInfoResolver implements TokenInfoResolver { + private static final String SID_PROPERTY = "sid"; + + private final DefaultCheckTokenClient checkTokenClient; + private final MetricFactory metricFactory; + private final URL userInfoURL; + private final IntrospectionEndpoint introspectionEndpoint; + private final JMAPOidcConfiguration configuration; + + @Inject + public OidcEndpointsInfoResolver(DefaultCheckTokenClient checkTokenClient, + MetricFactory metricFactory, + @Named("userInfo") URL userInfoURL, + IntrospectionEndpoint introspectionEndpoint, + JMAPOidcConfiguration configuration) { + this.checkTokenClient = checkTokenClient; + this.metricFactory = metricFactory; + this.userInfoURL = userInfoURL; + this.introspectionEndpoint = introspectionEndpoint; + this.configuration = configuration; + } + + @Override + public Mono<TokenInfo> apply(Token token) { + return Mono.zip( + Mono.from(metricFactory.decoratePublisherWithTimerMetric("userinfo-lookup", checkTokenClient.userInfo(userInfoURL, token.value()))), + Mono.from(metricFactory.decoratePublisherWithTimerMetric("introspection-lookup", checkTokenClient.introspect(introspectionEndpoint, token.value())))) + .flatMap(tokenInfos -> { + UserinfoResponse userInfo = tokenInfos.getT1(); + TokenIntrospectionResponse introspectInfo = tokenInfos.getT2(); + + Username sub = Username.of(userInfo.claimByPropertyName(configuration.getOidcClaim()) + .orElseThrow(() -> new UnauthorizedException("Invalid OIDC token: userinfo needs to include " + configuration.getOidcClaim() + " claim"))); + + return Mono.just(toTokenInfo(sub, userInfo, introspectInfo)); + }); + } + + private TokenInfo toTokenInfo(Username username, UserinfoResponse userinfoResponse, TokenIntrospectionResponse introspectionResponse) { + return new TokenInfo( + username.asString(), + userinfoResponse.claimByPropertyName(SID_PROPERTY).map(Sid::new) + .or(() -> introspectionResponse.claimByPropertyName(SID_PROPERTY).map(Sid::new)), + Instant.ofEpochSecond(introspectionResponse.exp().orElseThrow(() -> new UnauthorizedException("Expiration claim ('exp') is required in the token"))), + extractAudience(introspectionResponse)); + } + + private static Optional<List<Aud>> extractAudience(TokenIntrospectionResponse introspectionResponse) { + return Optional.ofNullable(introspectionResponse.json().get("aud")) + .map(audJson -> { + if (audJson.isArray()) { + return Iterators.toStream(audJson.iterator()) + .map(JsonNode::asText) + .map(Aud::new) + .toList(); + } + return ImmutableList.of(new Aud(audJson.asText())); + }); + } +} diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/http/OidcAuthenticationStrategyTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/http/OidcAuthenticationStrategyTest.java new file mode 100644 index 0000000000..847842bb1b --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/http/OidcAuthenticationStrategyTest.java @@ -0,0 +1,67 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.http; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.time.Clock; +import java.util.List; + +import org.apache.james.jmap.oidc.OidcTokenCache; +import org.apache.james.mailbox.SessionProvider; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableMap; + +class OidcAuthenticationStrategyTest { + @Test + void correspondingChallengeShouldUseJamesRealmByDefault() { + OidcAuthenticationStrategy testee = new OidcAuthenticationStrategy( + mock(SessionProvider.class), + mock(OidcTokenCache.class), + Clock.systemUTC(), + List.of()); + + assertThat(testee.correspondingChallenge()) + .isEqualTo(AuthenticationChallenge.of( + AuthenticationScheme.of("Bearer"), + ImmutableMap.of("realm", "james", + "error", "invalid_token", + "scope", "openid profile email"))); + } + + @Test + void correspondingChallengeShouldAllowRealmOverride() { + OidcAuthenticationStrategy testee = new OidcAuthenticationStrategy( + mock(SessionProvider.class), + mock(OidcTokenCache.class), + Clock.systemUTC(), + List.of(), + "twake_mail"); + + assertThat(testee.correspondingChallenge()) + .isEqualTo(AuthenticationChallenge.of( + AuthenticationScheme.of("Bearer"), + ImmutableMap.of("realm", "twake_mail", + "error", "invalid_token", + "scope", "openid profile email"))); + } +} diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/oidc/JMAPOidcConfigurationTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/oidc/JMAPOidcConfigurationTest.java new file mode 100644 index 0000000000..d29f38f594 --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/oidc/JMAPOidcConfigurationTest.java @@ -0,0 +1,69 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.oidc; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.commons.configuration2.BaseHierarchicalConfiguration; +import org.apache.james.jmap.http.OidcAuthenticationStrategy; +import org.junit.jupiter.api.Test; + +class JMAPOidcConfigurationTest { + @Test + void parseConfigurationShouldEnableOidcWhenSimpleNameIsConfigured() { + BaseHierarchicalConfiguration configuration = oidcConfiguration(OidcAuthenticationStrategy.class.getSimpleName()); + + assertThat(JMAPOidcConfiguration.parseConfiguration(configuration).getOidcEnabled()) + .isTrue(); + } + + @Test + void parseConfigurationShouldEnableOidcWhenJamesCanonicalNameIsConfigured() { + BaseHierarchicalConfiguration configuration = oidcConfiguration(OidcAuthenticationStrategy.class.getCanonicalName()); + + assertThat(JMAPOidcConfiguration.parseConfiguration(configuration).getOidcEnabled()) + .isTrue(); + } + + @Test + void parseConfigurationShouldEnableOidcWhenTmailCanonicalNameIsConfigured() { + BaseHierarchicalConfiguration configuration = oidcConfiguration("com.linagora.tmail.james.jmap.oidc.OidcAuthenticationStrategy"); + + assertThat(JMAPOidcConfiguration.parseConfiguration(configuration).getOidcEnabled()) + .isTrue(); + } + + @Test + void parseConfigurationShouldNotEnableOidcWhenStrategyNameOnlyContainsOidcAuthenticationStrategy() { + BaseHierarchicalConfiguration configuration = oidcConfiguration("com.example.DisabledOidcAuthenticationStrategy"); + + assertThat(JMAPOidcConfiguration.parseConfiguration(configuration).getOidcEnabled()) + .isFalse(); + } + + private BaseHierarchicalConfiguration oidcConfiguration(String authenticationStrategy) { + BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration(); + configuration.addProperty("authentication.strategy.rfc8621", authenticationStrategy); + configuration.addProperty("oidc.userInfo.url", "http://127.0.0.1/userinfo"); + configuration.addProperty("oidc.introspect.url", "http://127.0.0.1/introspect"); + configuration.addProperty("oidc.claim", "email"); + return configuration; + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
