Skip to content

Commit

Permalink
Remove Deprecated OpenSAML 3 Support
Browse files Browse the repository at this point in the history
Closes gh-10556
  • Loading branch information
rwinch authored and jzheaux committed Sep 20, 2022
1 parent 2a487ae commit 48e31f8
Show file tree
Hide file tree
Showing 25 changed files with 14 additions and 2,273 deletions.
8 changes: 0 additions & 8 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,6 @@ updateDependenciesSettings {
selection.reject("nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency");
}
}
components.all { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
// Do not compare version due to multiple versions existing
// will cause opensaml 3.x to be updated to 4.x
if (candidate.getGroup().equals("org.opensaml")) {
selection.reject("org.opensaml maintains two different versions, so it must be updated manually");
}
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion config/spring-security-config.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ dependencies {
testImplementation project(path : ':spring-security-ldap', configuration : 'tests')
testImplementation project(path : ':spring-security-oauth2-client', configuration : 'tests')
testImplementation project(path : ':spring-security-oauth2-resource-server', configuration : 'tests')
testImplementation project(':spring-security-saml2-service-provider')
testImplementation project(path : ':spring-security-saml2-service-provider', configuration : 'tests')
testImplementation project(path : ':spring-security-saml2-service-provider', configuration : 'opensaml4MainImplementation')
testImplementation project(path : ':spring-security-web', configuration : 'tests')
testImplementation "jakarta.inject:jakarta.inject-api"
testImplementation "org.assertj:assertj-core"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
import java.util.LinkedHashMap;
import java.util.Map;

import org.opensaml.core.Version;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationManager;
Expand All @@ -33,7 +31,6 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
Expand All @@ -43,7 +40,6 @@
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml3AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
import org.springframework.security.web.AuthenticationEntryPoint;
Expand Down Expand Up @@ -200,10 +196,6 @@ public Saml2LoginConfigurer<B> authenticationRequestResolver(
* @since 6.0
*/
public Saml2LoginConfigurer<B> authenticationRequestUri(String authenticationRequestUri) {
// OpenSAML 3 is no longer supported by spring security
if (version().startsWith("3")) {
return this;
}
Assert.state(authenticationRequestUri.contains("{registrationId}"),
"authenticationRequestUri must contain {registrationId} path variable");
this.authenticationRequestUri = authenticationRequestUri;
Expand Down Expand Up @@ -345,14 +337,11 @@ private Saml2AuthenticationRequestResolver getAuthenticationRequestResolver(B ht
if (bean != null) {
return bean;
}
if (version().startsWith("4")) {
OpenSaml4AuthenticationRequestResolver openSaml4AuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(
relyingPartyRegistrationResolver(http));
openSaml4AuthenticationRequestResolver
.setRequestMatcher(new AntPathRequestMatcher(this.authenticationRequestUri));
return openSaml4AuthenticationRequestResolver;
}
return new OpenSaml3AuthenticationRequestResolver(relyingPartyRegistrationResolver(http));
OpenSaml4AuthenticationRequestResolver openSaml4AuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(
relyingPartyRegistrationResolver(http));
openSaml4AuthenticationRequestResolver
.setRequestMatcher(new AntPathRequestMatcher(this.authenticationRequestUri));
return openSaml4AuthenticationRequestResolver;
}

private AuthenticationConverter getAuthenticationConverter(B http) {
Expand All @@ -370,22 +359,8 @@ private AuthenticationConverter getAuthenticationConverter(B http) {
return authenticationConverterBean;
}

private String version() {
String version = Version.getVersion();
if (version != null) {
return version;
}
return Version.class.getModule().getDescriptor().version().map(Object::toString)
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
}

private void registerDefaultAuthenticationProvider(B http) {
if (version().startsWith("4")) {
http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider()));
}
else {
http.authenticationProvider(postProcess(new OpenSamlAuthenticationProvider()));
}
http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider()));
}

private void registerDefaultCsrfOverride(B http) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import java.util.function.Predicate;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.opensaml.core.Version;

import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationManager;
Expand All @@ -44,8 +42,6 @@
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml3LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml3LogoutResponseResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter;
Expand Down Expand Up @@ -313,15 +309,6 @@ private <C> C getBeanOrNull(Class<C> clazz) {
return this.context.getBean(clazz);
}

private String version() {
String version = Version.getVersion();
if (version != null) {
return version;
}
return Version.class.getModule().getDescriptor().version().map(Object::toString)
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
}

/**
* A configurer for SAML 2.0 LogoutRequest components
*/
Expand Down Expand Up @@ -401,10 +388,7 @@ private Saml2LogoutRequestResolver logoutRequestResolver(
if (this.logoutRequestResolver != null) {
return this.logoutRequestResolver;
}
if (version().startsWith("4")) {
return new OpenSaml4LogoutRequestResolver(relyingPartyRegistrationResolver);
}
return new OpenSaml3LogoutRequestResolver(relyingPartyRegistrationResolver);
return new OpenSaml4LogoutRequestResolver(relyingPartyRegistrationResolver);
}

}
Expand Down Expand Up @@ -471,10 +455,7 @@ private Saml2LogoutResponseValidator logoutResponseValidator() {
private Saml2LogoutResponseResolver logoutResponseResolver(
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
if (this.logoutResponseResolver == null) {
if (version().startsWith("4")) {
return new OpenSaml4LogoutResponseResolver(relyingPartyRegistrationResolver);
}
return new OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);
return new OpenSaml4LogoutResponseResolver(relyingPartyRegistrationResolver);
}
return this.logoutResponseResolver;
}
Expand Down Expand Up @@ -511,12 +492,4 @@ public boolean matches(HttpServletRequest request) {

}

private static class NoopLogoutHandler implements LogoutHandler {

@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@

import java.io.IOException;
import java.net.URLDecoder;
import java.time.Duration;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;

import jakarta.servlet.ServletException;
Expand All @@ -32,43 +30,34 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.opensaml.saml.saml2.core.Assertion;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.core.Saml2Utils;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
Expand All @@ -77,7 +66,6 @@
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
Expand All @@ -91,7 +79,6 @@
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
Expand Down Expand Up @@ -121,14 +108,6 @@
@ExtendWith(SpringTestContextExtension.class)
public class Saml2LoginConfigurerTests {

private static final Converter<Assertion, Collection<? extends GrantedAuthority>> AUTHORITIES_EXTRACTOR = (
a) -> Collections.singletonList(new SimpleGrantedAuthority("TEST"));

private static final GrantedAuthoritiesMapper AUTHORITIES_MAPPER = (authorities) -> Collections
.singletonList(new SimpleGrantedAuthority("TEST CONVERTED"));

private static final Duration RESPONSE_TIME_VALIDATION_SKEW = Duration.ZERO;

private static final String SIGNED_RESPONSE = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9ycC5leGFtcGxlLm9yZy9hY3MiIElEPSJfYzE3MzM2YTAtNTM1My00MTQ5LWI3MmMtMDNkOWY5YWYzMDdlIiBJc3N1ZUluc3RhbnQ9IjIwMjAtMDgtMDRUMjI6MDQ6NDUuMDE2WiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4KPGRzOlNpZ25lZEluZm8+CjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CjxkczpSZWZlcmVuY2UgVVJJPSIjX2MxNzMzNmEwLTUzNTMtNDE0OS1iNzJjLTAzZDlmOWFmMzA3ZSI+CjxkczpUcmFuc2Zvcm1zPgo8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPgo8L2RzOlRyYW5zZm9ybXM+CjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz4KPGRzOkRpZ2VzdFZhbHVlPjYzTmlyenFzaDVVa0h1a3NuRWUrM0hWWU5aYWFsQW1OQXFMc1lGMlRuRDA9PC9kczpEaWdlc3RWYWx1ZT4KPC9kczpSZWZlcmVuY2U+CjwvZHM6U2lnbmVkSW5mbz4KPGRzOlNpZ25hdHVyZVZhbHVlPgpLMVlvWWJVUjBTclY4RTdVMkhxTTIvZUNTOTNoV25mOExnNnozeGZWMUlyalgzSXhWYkNvMVlYcnRBSGRwRVdvYTJKKzVOMmFNbFBHJiMxMzsKN2VpbDBZRC9xdUVRamRYbTNwQTBjZmEvY25pa2RuKzVhbnM0ZWQwanU1amo2dkpvZ2w2Smt4Q25LWUpwTU9HNzhtampmb0phengrWCYjMTM7CkM2NktQVStBYUdxeGVwUEQ1ZlhRdTFKSy9Jb3lBaitaa3k4Z2Jwc3VyZHFCSEJLRWxjdnVOWS92UGY0OGtBeFZBKzdtRGhNNUMvL1AmIzEzOwp0L084Y3NZYXB2UjZjdjZrdk45QXZ1N3FRdm9qVk1McHVxZWNJZDJwTUVYb0NSSnE2Nkd4MStNTUVPeHVpMWZZQlRoMEhhYjRmK3JyJiMxMzsKOEY2V1NFRC8xZllVeHliRkJqZ1Q4d2lEWHFBRU8wSVY4ZWRQeEE9PQo8L2RzOlNpZ25hdHVyZVZhbHVlPgo8L2RzOlNpZ25hdHVyZT48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0iQWUzZjQ5OGI4LTliMTctNDA3OC05ZDM1LTg2YTA4NDA4NDk5NSIgSXNzdWVJbnN0YW50PSIyMDIwLTA4LTA0VDIyOjA0OjQ1LjA3N1oiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3Vlcj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEPnRlc3RAc2FtbC51c2VyPC9zYW1sMjpOYW1lSUQ+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90QmVmb3JlPSIyMDIwLTA4LTA0VDIxOjU5OjQ1LjA5MFoiIE5vdE9uT3JBZnRlcj0iMjA0MC0wNy0zMFQyMjowNTowNi4wODhaIiBSZWNpcGllbnQ9Imh0dHBzOi8vcnAuZXhhbXBsZS5vcmcvYWNzIi8+PC9zYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDI6U3ViamVjdD48c2FtbDI6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMjAtMDgtMDRUMjE6NTk6NDUuMDgwWiIgTm90T25PckFmdGVyPSIyMDQwLTA3LTMwVDIyOjA1OjA2LjA4N1oiLz48L3NhbWwyOkFzc2VydGlvbj48L3NhbWwycDpSZXNwb25zZT4=";

private static final AuthenticationConverter AUTHENTICATION_CONVERTER = mock(AuthenticationConverter.class);
Expand Down Expand Up @@ -197,14 +176,6 @@ public void saml2LoginWhenDefaultAndSamlAuthenticationManagerThenSamlManagerIsUs
performSaml2Login("ROLE_AUTH_MANAGER");
}

@Test
public void saml2LoginWhenConfiguringAuthenticationDefaultsUsingCustomizerThenTheProviderIsConfigured()
throws Exception {
// setup application context
this.spring.register(Saml2LoginConfigWithAuthenticationDefaultsWithPostProcessor.class).autowire();
validateSaml2WebSsoAuthenticationFilterConfiguration();
}

@Test
public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception {
this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire();
Expand Down Expand Up @@ -362,22 +333,6 @@ public void getFaviconWhenDefaultConfigurationThenDoesNotSaveAuthnRequest() thro
.andExpect(redirectedUrl("http://localhost/saml2/authenticate/registration-id"));
}

private void validateSaml2WebSsoAuthenticationFilterConfiguration() {
// get the OpenSamlAuthenticationProvider
Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain);
AuthenticationManager manager = (AuthenticationManager) ReflectionTestUtils.getField(filter,
"authenticationManager");
ProviderManager pm = (ProviderManager) manager;
AuthenticationProvider provider = pm.getProviders().stream()
.filter((p) -> p instanceof OpenSaml4AuthenticationProvider).findFirst().get();
assertThat(provider).isNotNull();
}

private Saml2WebSsoAuthenticationFilter getSaml2SsoFilter(FilterChainProxy chain) {
return (Saml2WebSsoAuthenticationFilter) chain.getFilters("/login/saml2/sso/test").stream()
.filter((f) -> f instanceof Saml2WebSsoAuthenticationFilter).findFirst().get();
}

private void performSaml2Login(String expected) throws IOException, ServletException {
// setup authentication parameters
this.request.setRequestURI("/login/saml2/sso/registration-id");
Expand Down Expand Up @@ -460,28 +415,6 @@ protected void configure(HttpSecurity http) throws Exception {

}

@Configuration
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class Saml2LoginConfigWithAuthenticationDefaultsWithPostProcessor extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
ObjectPostProcessor<OpenSamlAuthenticationProvider> processor = new ObjectPostProcessor<OpenSamlAuthenticationProvider>() {
@Override
public <O extends OpenSamlAuthenticationProvider> O postProcess(O provider) {
provider.setResponseTimeValidationSkew(RESPONSE_TIME_VALIDATION_SKEW);
provider.setAuthoritiesMapper(AUTHORITIES_MAPPER);
provider.setAuthoritiesExtractor(AUTHORITIES_EXTRACTOR);
return provider;
}
};
http.saml2Login().addObjectPostProcessor(processor);
super.configure(http);
}

}

@Configuration
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
Expand Down
8 changes: 0 additions & 8 deletions docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,6 @@ Instead, such classes as `OpenSamlAuthenticationRequestFactory` and `OpenSamlAut

For example, once your application receives a `SAMLResponse` and delegates to `Saml2WebSsoAuthenticationFilter`, the filter delegates to `OpenSamlAuthenticationProvider`:

[NOTE]
====
For backward compatibility, Spring Security will use the latest OpenSAML 3 by default.
Note, though that OpenSAML 3 has reached it's end-of-life and updating to OpenSAML 4.x is recommended.
For that reason, Spring Security supports both OpenSAML 3.x and 4.x.
If you manage your OpenSAML dependency to 4.x, then Spring Security will select its OpenSAML 4.x implementations.
====

.Authenticating an OpenSAML `Response`
image:{figures}/opensamlauthenticationprovider.png[]

Expand Down
Loading

0 comments on commit 48e31f8

Please sign in to comment.