Customize OAuth2AuthorizationConsent prior to saving
Closes gh-436
This commit is contained in:
committed by
Joe Grandja
parent
5fa1e8e3b1
commit
4ce999c014
@@ -23,14 +23,17 @@ import java.security.Principal;
|
||||
import java.text.MessageFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import org.assertj.core.matcher.AssertionMatcher;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
@@ -52,12 +55,14 @@ import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.Customizer;
|
||||
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.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
@@ -77,10 +82,13 @@ import org.springframework.security.oauth2.server.authorization.JdbcOAuth2Author
|
||||
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
|
||||
@@ -524,6 +532,60 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
assertThat(authorization).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenCustomConsentCustomizerConfiguredThenUsed() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationCustomConsentRequest.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.clientSettings(ClientSettings.builder()
|
||||
.requireAuthorizationConsent(true)
|
||||
.setting("custom.allowed-authorities", "authority-1 authority-2")
|
||||
.build())
|
||||
.build();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.build();
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||
.param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||
.param("authority", "authority-1 authority-2")
|
||||
.param(OAuth2ParameterNames.STATE, "state")
|
||||
.with(user("principal")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
|
||||
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
|
||||
assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=state");
|
||||
|
||||
String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code");
|
||||
OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE);
|
||||
|
||||
mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
|
||||
.params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization))
|
||||
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
||||
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.access_token").value(new AssertionMatcher<String>() {
|
||||
@Override
|
||||
public void assertion(String accessToken) throws AssertionError {
|
||||
Jwt jwt = jwtDecoder.decode(accessToken);
|
||||
assertThat(jwt.getClaimAsStringList(AUTHORITIES_CLAIM))
|
||||
.containsExactlyInAnyOrder("authority-1", "authority-2");
|
||||
}
|
||||
}))
|
||||
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
||||
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||
.andExpect(jsonPath("$.refresh_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").doesNotExist())
|
||||
.andReturn();
|
||||
|
||||
String json = mvcResult.getResponse().getContentAsString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenAuthorizationEndpointCustomizedThenUsed() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire();
|
||||
@@ -722,6 +784,100 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class AuthorizationServerConfigurationCustomConsentRequest extends AuthorizationServerConfiguration {
|
||||
@Autowired
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizationConsentService authorizationConsentService;
|
||||
|
||||
// @formatter:off
|
||||
@Bean
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer<>();
|
||||
authorizationServerConfigurer
|
||||
.authorizationEndpoint(authorizationEndpoint ->
|
||||
authorizationEndpoint.authenticationProvider(createProvider()));
|
||||
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
|
||||
|
||||
http
|
||||
.requestMatcher(endpointsMatcher)
|
||||
.authorizeRequests(authorizeRequests ->
|
||||
authorizeRequests.anyRequest().authenticated()
|
||||
)
|
||||
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
|
||||
.apply(authorizationServerConfigurer);
|
||||
return http.build();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
|
||||
return context -> {
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType()) &&
|
||||
OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
|
||||
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentService.findById(
|
||||
context.getRegisteredClient().getId(), context.getPrincipal().getName());
|
||||
|
||||
Set<String> authorities = new HashSet<>();
|
||||
for (GrantedAuthority authority : authorizationConsent.getAuthorities()) {
|
||||
authorities.add(authority.getAuthority());
|
||||
}
|
||||
context.getClaims().claim(AUTHORITIES_CLAIM, authorities);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private AuthenticationProvider createProvider() {
|
||||
OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationProvider(
|
||||
this.registeredClientRepository,
|
||||
this.authorizationService,
|
||||
this.authorizationConsentService);
|
||||
authorizationCodeRequestAuthenticationProvider.setAuthorizationConsentCustomizer(new ConsentCustomizer());
|
||||
|
||||
return authorizationCodeRequestAuthenticationProvider;
|
||||
}
|
||||
|
||||
static class ConsentCustomizer implements Customizer<OAuth2AuthorizationConsentContext> {
|
||||
@Override
|
||||
public void customize(OAuth2AuthorizationConsentContext authorizationConsentContext) {
|
||||
OAuth2AuthorizationConsent.Builder authorizationConsentBuilder =
|
||||
authorizationConsentContext.getAuthorizationConsentBuilder();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
authorizationConsentContext.getPrincipal();
|
||||
Map<String, Object> additionalParameters =
|
||||
authorizationCodeRequestAuthentication.getAdditionalParameters();
|
||||
RegisteredClient registeredClient = authorizationConsentContext.getRegisteredClient();
|
||||
ClientSettings clientSettings = registeredClient.getClientSettings();
|
||||
|
||||
Set<String> requestedAuthorities = authorities((String) additionalParameters.get("authority"));
|
||||
Set<String> allowedAuthorities = authorities(clientSettings.getSetting("custom.allowed-authorities"));
|
||||
for (String requestedAuthority : requestedAuthorities) {
|
||||
if (allowedAuthorities.contains(requestedAuthority)) {
|
||||
authorizationConsentBuilder.authority(new SimpleGrantedAuthority(requestedAuthority));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<String> authorities(String param) {
|
||||
Set<String> authorities = new HashSet<>();
|
||||
if (param != null) {
|
||||
List<String> authorityValues = Arrays.asList(param.split(" "));
|
||||
authorities.addAll(authorityValues);
|
||||
}
|
||||
|
||||
return authorities;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class AuthorizationServerConfigurationCustomAuthorizationEndpoint extends AuthorizationServerConfiguration {
|
||||
// @formatter:off
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2AuthorizationConsentContext}.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class OAuth2AuthorizationConsentContextTests {
|
||||
|
||||
@Test
|
||||
public void withWhenAuthorizationConsentBuilderNullThenIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2AuthorizationConsentContext.with(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationConsentBuilder cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setWhenValueNullThenThrowIllegalArgumentException() {
|
||||
OAuth2AuthorizationConsentContext.Builder builder = OAuth2AuthorizationConsentContext
|
||||
.with(OAuth2AuthorizationConsent.withId("some-client", "some-principal"));
|
||||
assertThatThrownBy(() -> builder.principal(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatThrownBy(() -> builder.registeredClient(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatThrownBy(() -> builder.authorization(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatThrownBy(() -> builder.authorizationRequest(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatThrownBy(() -> builder.put(null, ""))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAllValuesProvidedThenAllValuesAreSet() {
|
||||
OAuth2AuthorizationConsent.Builder authorizationConsentBuilder = OAuth2AuthorizationConsent
|
||||
.withId("some-client", "some-principal");
|
||||
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "password");
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken.with("test-client", principal)
|
||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||
.build();
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||
OAuth2AuthorizationRequest.class.getName());
|
||||
|
||||
OAuth2AuthorizationConsentContext context = OAuth2AuthorizationConsentContext
|
||||
.with(authorizationConsentBuilder)
|
||||
.principal(authentication)
|
||||
.registeredClient(registeredClient)
|
||||
.authorization(authorization)
|
||||
.authorizationRequest(authorizationRequest)
|
||||
.put("custom-key-1", "custom-value-1")
|
||||
.context(ctx -> ctx.put("custom-key-2", "custom-value-2"))
|
||||
.build();
|
||||
|
||||
assertThat(context.getAuthorizationConsentBuilder()).isEqualTo(authorizationConsentBuilder);
|
||||
assertThat(context.<OAuth2AuthorizationCodeRequestAuthenticationToken>getPrincipal()).isEqualTo(authentication);
|
||||
assertThat(context.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(context.getAuthorization()).isEqualTo(authorization);
|
||||
assertThat(context.getAuthorizationRequest()).isEqualTo(authorizationRequest);
|
||||
assertThat(context.<String>get("custom-key-1")).isEqualTo("custom-value-1");
|
||||
assertThat(context.<String>get("custom-key-2")).isEqualTo("custom-value-2");
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,10 @@ import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
@@ -41,8 +43,8 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||
@@ -129,6 +131,13 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
.hasMessage("authenticationValidatorResolver cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthorizationConsentCustomizerWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.authenticationProvider.setAuthorizationConsentCustomizer(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationConsentCustomizer cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
@@ -773,6 +782,53 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
assertAuthorizationConsentRequestWithAuthorizationCodeResult(registeredClient, authorization, authenticationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenCustomAuthorizationConsentCustomizerThenUsed() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> authorizedScopes = authorizationRequest.getScopes();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(authorizedScopes) // Approve all scopes
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Customizer<OAuth2AuthorizationConsentContext> authorizationConsentCustomizer = mock(Customizer.class);
|
||||
this.authenticationProvider.setAuthorizationConsentCustomizer(authorizationConsentCustomizer);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
assertAuthorizationConsentRequestWithAuthorizationCodeResult(registeredClient, authorization, authenticationResult);
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizationConsentContext> contextCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsentContext.class);
|
||||
verify(authorizationConsentCustomizer).customize(contextCaptor.capture());
|
||||
|
||||
OAuth2AuthorizationConsentContext context = contextCaptor.getValue();
|
||||
assertThat((Authentication) context.getPrincipal()).isEqualTo(authentication);
|
||||
assertThat(context.get(OAuth2AuthorizationConsent.Builder.class)).isInstanceOf(OAuth2AuthorizationConsent.Builder.class);
|
||||
assertThat(context.get(OAuth2Authorization.class)).isInstanceOf(OAuth2Authorization.class);
|
||||
assertThat(context.get(OAuth2AuthorizationRequest.class)).isInstanceOf(OAuth2AuthorizationRequest.class);
|
||||
}
|
||||
|
||||
private void assertAuthorizationConsentRequestWithAuthorizationCodeResult(
|
||||
RegisteredClient registeredClient,
|
||||
OAuth2Authorization authorization,
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult) {
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> authorizedScopes = authorizationRequest.getScopes();
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizationConsent> authorizationConsentCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsent.class);
|
||||
verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture());
|
||||
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentCaptor.getValue();
|
||||
|
||||
Reference in New Issue
Block a user