9 changed files with 1871 additions and 4 deletions
@ -0,0 +1,48 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2024 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.authentication; |
||||||
|
|
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link OAuth2TokenExchangeActor}. |
||||||
|
* |
||||||
|
* @author Steve Riesenberg |
||||||
|
*/ |
||||||
|
public class OAuth2TokenExchangeActorTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenClaimsNullThenThrowIllegalArgumentException() { |
||||||
|
// @formatter:off
|
||||||
|
assertThatIllegalArgumentException() |
||||||
|
.isThrownBy(() -> new OAuth2TokenExchangeActor(null)) |
||||||
|
.withMessage("claims cannot be null"); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenRequiredParametersThenCreated() { |
||||||
|
Map<String, Object> claims = Map.of("claim1", "value1"); |
||||||
|
OAuth2TokenExchangeActor actor = new OAuth2TokenExchangeActor(claims); |
||||||
|
assertThat(actor.getClaims()).isEqualTo(claims); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,702 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2024 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.authentication; |
||||||
|
|
||||||
|
import java.security.Principal; |
||||||
|
import java.time.Instant; |
||||||
|
import java.time.temporal.ChronoUnit; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.function.Consumer; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach; |
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.mockito.ArgumentCaptor; |
||||||
|
|
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||||
|
import org.springframework.security.core.Authentication; |
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||||
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2Token; |
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; |
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; |
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; |
||||||
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; |
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; |
||||||
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; |
||||||
|
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; |
||||||
|
import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; |
||||||
|
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; |
||||||
|
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; |
||||||
|
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; |
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames; |
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; |
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||||
|
import static org.mockito.ArgumentMatchers.any; |
||||||
|
import static org.mockito.ArgumentMatchers.anyString; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
import static org.mockito.Mockito.verify; |
||||||
|
import static org.mockito.Mockito.verifyNoInteractions; |
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link OAuth2TokenExchangeAuthenticationProvider}. |
||||||
|
* |
||||||
|
* @author Steve Riesenberg |
||||||
|
*/ |
||||||
|
public class OAuth2TokenExchangeAuthenticationProviderTests { |
||||||
|
private static final Set<String> RESOURCES = Set.of("https://mydomain.com/resource1", "https://mydomain.com/resource2"); |
||||||
|
private static final Set<String> AUDIENCES = Set.of("audience1", "audience2"); |
||||||
|
private static final String SUBJECT_TOKEN = "EfYu_0jEL"; |
||||||
|
private static final String ACTOR_TOKEN = "JlNE_xR1f"; |
||||||
|
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; |
||||||
|
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; |
||||||
|
|
||||||
|
private OAuth2AuthorizationService authorizationService; |
||||||
|
private OAuth2TokenGenerator<OAuth2Token> tokenGenerator; |
||||||
|
private OAuth2TokenExchangeAuthenticationProvider authenticationProvider; |
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void setUp() { |
||||||
|
this.authorizationService = mock(OAuth2AuthorizationService.class); |
||||||
|
this.tokenGenerator = mock(OAuth2TokenGenerator.class); |
||||||
|
this.authenticationProvider = new OAuth2TokenExchangeAuthenticationProvider(this.authorizationService, |
||||||
|
this.tokenGenerator); |
||||||
|
mockAuthorizationServerContext(); |
||||||
|
} |
||||||
|
|
||||||
|
@AfterEach |
||||||
|
public void tearDown() { |
||||||
|
AuthorizationServerContextHolder.resetContext(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { |
||||||
|
// @formatter:off
|
||||||
|
assertThatIllegalArgumentException() |
||||||
|
.isThrownBy(() -> new OAuth2TokenExchangeAuthenticationProvider(null, this.tokenGenerator)) |
||||||
|
.withMessage("authorizationService cannot be null"); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenTokenGeneratorNullThenThrowIllegalArgumentException() { |
||||||
|
// @formatter:off
|
||||||
|
assertThatIllegalArgumentException() |
||||||
|
.isThrownBy(() -> new OAuth2TokenExchangeAuthenticationProvider(this.authorizationService, null)) |
||||||
|
.withMessage("tokenGenerator cannot be null"); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void supportsWhenTypeOAuth2TokenExchangeAuthenticationTokenThenReturnTrue() { |
||||||
|
assertThat(this.authenticationProvider.supports(OAuth2TokenExchangeAuthenticationToken.class)).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenClientNotAuthenticatedThenThrowOAuth2AuthenticationException() { |
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken("client-1", |
||||||
|
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null, null); |
||||||
|
Authentication authentication = new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, |
||||||
|
ACCESS_TOKEN_TYPE_VALUE, clientPrincipal, null, null, RESOURCES, AUDIENCES, null, null); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenInvalidGrantTypeThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenInvalidRequestedTokenTypeThenThrowOAuth2AuthenticationException() { |
||||||
|
// @formatter:off
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) |
||||||
|
.tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE).build()) |
||||||
|
.build(); |
||||||
|
// @formatter:on
|
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenSubjectTokenNotFoundThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); |
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(null); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenSubjectTokenNotActiveThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); |
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createExpiredAccessToken(SUBJECT_TOKEN)).build(); |
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(authorization); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenSubjectTokenTypeJwtAndSubjectTokenFormatReferenceThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createJwtRequest(registeredClient); |
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN), withTokenFormat(OAuth2TokenFormat.REFERENCE)).build(); |
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(authorization); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenSubjectPrincipalNullThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); |
||||||
|
// @formatter:off
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN)) |
||||||
|
.attributes((attributes) -> attributes.remove(Principal.class.getName())) |
||||||
|
.build(); |
||||||
|
// @formatter:on
|
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(authorization); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenActorTokenNotFoundThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); |
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN)).build(); |
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn( |
||||||
|
subjectAuthorization, (OAuth2Authorization) null); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenActorTokenNotActiveThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); |
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN)).build(); |
||||||
|
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createExpiredAccessToken(ACTOR_TOKEN)).build(); |
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn( |
||||||
|
subjectAuthorization, actorAuthorization); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenActorTokenTypeJwtAndActorTokenFormatReferenceThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createJwtRequest(registeredClient); |
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN), withTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)).build(); |
||||||
|
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(ACTOR_TOKEN), withTokenFormat(OAuth2TokenFormat.REFERENCE)).build(); |
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn( |
||||||
|
subjectAuthorization, actorAuthorization); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenMayActAndActorIssClaimNotAuthorizedThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); |
||||||
|
Map<String, String> authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer", |
||||||
|
OAuth2TokenClaimNames.SUB, "actor"); |
||||||
|
// @formatter:off
|
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", authorizedActorClaims))) |
||||||
|
.build(); |
||||||
|
// @formatter:on
|
||||||
|
Map<String, Object> actorTokenClaims = Map.of(OAuth2TokenClaimNames.ISS, "invalid-issuer", |
||||||
|
OAuth2TokenClaimNames.SUB, "actor"); |
||||||
|
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(ACTOR_TOKEN), withClaims(actorTokenClaims)).build(); |
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn( |
||||||
|
subjectAuthorization, actorAuthorization); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenMayActAndActorSubClaimNotAuthorizedThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); |
||||||
|
Map<String, String> authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer", |
||||||
|
OAuth2TokenClaimNames.SUB, "actor"); |
||||||
|
// @formatter:off
|
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", authorizedActorClaims))) |
||||||
|
.build(); |
||||||
|
// @formatter:on
|
||||||
|
Map<String, Object> actorTokenClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer", OAuth2TokenClaimNames.SUB, |
||||||
|
"invalid-actor"); |
||||||
|
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(ACTOR_TOKEN), withClaims(actorTokenClaims)).build(); |
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn( |
||||||
|
subjectAuthorization, actorAuthorization); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenMayActAndImpersonationThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createImpersonationRequest(registeredClient); |
||||||
|
Map<String, String> authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer", |
||||||
|
OAuth2TokenClaimNames.SUB, "actor"); |
||||||
|
// @formatter:off
|
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", authorizedActorClaims))) |
||||||
|
.build(); |
||||||
|
// @formatter:on
|
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn( |
||||||
|
subjectAuthorization); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenInvalidScopeInRequestThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient, |
||||||
|
Set.of("invalid")); |
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN)).build(); |
||||||
|
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(ACTOR_TOKEN)).build(); |
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn( |
||||||
|
subjectAuthorization, actorAuthorization); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenInvalidScopeInSubjectAuthorizationThenThrowOAuth2AuthenticationException() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient, Set.of()); |
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN)).authorizedScopes(Set.of("invalid")).build(); |
||||||
|
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(ACTOR_TOKEN)).build(); |
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn( |
||||||
|
subjectAuthorization, actorAuthorization); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verifyNoMoreInteractions(this.authorizationService); |
||||||
|
verifyNoInteractions(this.tokenGenerator); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenNoActorTokenAndValidTokenExchangeThenReturnAccessTokenForImpersonation() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createImpersonationRequest(registeredClient); |
||||||
|
TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER"); |
||||||
|
// @formatter:off
|
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN)) |
||||||
|
.attribute(Principal.class.getName(), userPrincipal) |
||||||
|
.build(); |
||||||
|
// @formatter:on
|
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn( |
||||||
|
subjectAuthorization); |
||||||
|
OAuth2AccessToken accessToken = createAccessToken("token-value"); |
||||||
|
when(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).thenReturn(accessToken); |
||||||
|
OAuth2AccessTokenAuthenticationToken authenticationResult = |
||||||
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication); |
||||||
|
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); |
||||||
|
assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); |
||||||
|
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken); |
||||||
|
assertThat(authenticationResult.getRefreshToken()).isNull(); |
||||||
|
assertThat(authenticationResult.getAdditionalParameters()).hasSize(1); |
||||||
|
assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE)) |
||||||
|
.isEqualTo(JWT_TOKEN_TYPE_VALUE); |
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); |
||||||
|
ArgumentCaptor<OAuth2TokenContext> tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); |
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verify(this.tokenGenerator).generate(tokenContextCaptor.capture()); |
||||||
|
verify(this.authorizationService).save(authorizationCaptor.capture()); |
||||||
|
verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator); |
||||||
|
|
||||||
|
OAuth2TokenContext tokenContext = tokenContextCaptor.getValue(); |
||||||
|
assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient); |
||||||
|
assertThat(tokenContext.getAuthorization()).isEqualTo(subjectAuthorization); |
||||||
|
assertThat(tokenContext.<Authentication>getPrincipal()).isSameAs(userPrincipal); |
||||||
|
assertThat(tokenContext.getAuthorizationServerContext()).isNotNull(); |
||||||
|
assertThat(tokenContext.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); |
||||||
|
assertThat(tokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
assertThat(tokenContext.<Authentication>getAuthorizationGrant()).isEqualTo(authentication); |
||||||
|
assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); |
||||||
|
|
||||||
|
OAuth2Authorization authorization = authorizationCaptor.getValue(); |
||||||
|
assertThat(authorization.getPrincipalName()).isEqualTo(subjectAuthorization.getPrincipalName()); |
||||||
|
assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); |
||||||
|
assertThat(authorization.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); |
||||||
|
assertThat(authorization.<Authentication>getAttribute(Principal.class.getName())).isSameAs(userPrincipal); |
||||||
|
assertThat(authorization.getAccessToken().getToken()).isEqualTo(accessToken); |
||||||
|
assertThat(authorization.getRefreshToken()).isNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenNoActorTokenAndPreviousActorThenReturnAccessTokenForImpersonation() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createImpersonationRequest(registeredClient); |
||||||
|
TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER"); |
||||||
|
OAuth2TokenExchangeActor previousActor = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer1", |
||||||
|
OAuth2TokenClaimNames.SUB, "actor")); |
||||||
|
OAuth2TokenExchangeCompositeAuthenticationToken subjectPrincipal = |
||||||
|
new OAuth2TokenExchangeCompositeAuthenticationToken(userPrincipal, List.of(previousActor)); |
||||||
|
// @formatter:off
|
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN)) |
||||||
|
.attribute(Principal.class.getName(), subjectPrincipal) |
||||||
|
.build(); |
||||||
|
// @formatter:on
|
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn( |
||||||
|
subjectAuthorization); |
||||||
|
OAuth2AccessToken accessToken = createAccessToken("token-value"); |
||||||
|
when(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).thenReturn(accessToken); |
||||||
|
OAuth2AccessTokenAuthenticationToken authenticationResult = |
||||||
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication); |
||||||
|
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); |
||||||
|
assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); |
||||||
|
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken); |
||||||
|
assertThat(authenticationResult.getRefreshToken()).isNull(); |
||||||
|
assertThat(authenticationResult.getAdditionalParameters()).hasSize(1); |
||||||
|
assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE)) |
||||||
|
.isEqualTo(JWT_TOKEN_TYPE_VALUE); |
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); |
||||||
|
ArgumentCaptor<OAuth2TokenContext> tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); |
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verify(this.tokenGenerator).generate(tokenContextCaptor.capture()); |
||||||
|
verify(this.authorizationService).save(authorizationCaptor.capture()); |
||||||
|
verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator); |
||||||
|
|
||||||
|
OAuth2TokenContext tokenContext = tokenContextCaptor.getValue(); |
||||||
|
assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient); |
||||||
|
assertThat(tokenContext.getAuthorization()).isEqualTo(subjectAuthorization); |
||||||
|
assertThat(tokenContext.<Authentication>getPrincipal()).isSameAs(userPrincipal); |
||||||
|
assertThat(tokenContext.getAuthorizationServerContext()).isNotNull(); |
||||||
|
assertThat(tokenContext.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); |
||||||
|
assertThat(tokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
assertThat(tokenContext.<Authentication>getAuthorizationGrant()).isEqualTo(authentication); |
||||||
|
assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); |
||||||
|
|
||||||
|
OAuth2Authorization authorization = authorizationCaptor.getValue(); |
||||||
|
assertThat(authorization.getPrincipalName()).isEqualTo(subjectAuthorization.getPrincipalName()); |
||||||
|
assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); |
||||||
|
assertThat(authorization.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); |
||||||
|
assertThat(authorization.<Authentication>getAttribute(Principal.class.getName())).isSameAs(userPrincipal); |
||||||
|
assertThat(authorization.getAccessToken().getToken()).isEqualTo(accessToken); |
||||||
|
assertThat(authorization.getRefreshToken()).isNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticateWhenActorTokenAndValidTokenExchangeThenReturnAccessTokenForDelegation() { |
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); |
||||||
|
TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER"); |
||||||
|
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer1", |
||||||
|
OAuth2TokenClaimNames.SUB, "actor1")); |
||||||
|
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer2", |
||||||
|
OAuth2TokenClaimNames.SUB, "actor2")); |
||||||
|
OAuth2TokenExchangeCompositeAuthenticationToken subjectPrincipal = |
||||||
|
new OAuth2TokenExchangeCompositeAuthenticationToken(userPrincipal, List.of(actor1)); |
||||||
|
// @formatter:off
|
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", actor2.getClaims()))) |
||||||
|
.attribute(Principal.class.getName(), subjectPrincipal) |
||||||
|
.build(); |
||||||
|
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.principalName(actor2.getSubject()) |
||||||
|
.token(createAccessToken(ACTOR_TOKEN), withClaims(actor2.getClaims())) |
||||||
|
.build(); |
||||||
|
// @formatter:on
|
||||||
|
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn( |
||||||
|
subjectAuthorization, actorAuthorization); |
||||||
|
OAuth2AccessToken accessToken = createAccessToken("token-value"); |
||||||
|
when(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).thenReturn(accessToken); |
||||||
|
OAuth2AccessTokenAuthenticationToken authenticationResult = |
||||||
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication); |
||||||
|
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); |
||||||
|
assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); |
||||||
|
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken); |
||||||
|
assertThat(authenticationResult.getRefreshToken()).isNull(); |
||||||
|
assertThat(authenticationResult.getAdditionalParameters()).hasSize(1); |
||||||
|
assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE)) |
||||||
|
.isEqualTo(JWT_TOKEN_TYPE_VALUE); |
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); |
||||||
|
ArgumentCaptor<OAuth2TokenContext> tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); |
||||||
|
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
verify(this.tokenGenerator).generate(tokenContextCaptor.capture()); |
||||||
|
verify(this.authorizationService).save(authorizationCaptor.capture()); |
||||||
|
verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator); |
||||||
|
|
||||||
|
OAuth2TokenContext tokenContext = tokenContextCaptor.getValue(); |
||||||
|
assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient); |
||||||
|
assertThat(tokenContext.getAuthorization()).isEqualTo(subjectAuthorization); |
||||||
|
assertThat(tokenContext.getAuthorizationServerContext()).isNotNull(); |
||||||
|
assertThat(tokenContext.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); |
||||||
|
assertThat(tokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
assertThat(tokenContext.<Authentication>getAuthorizationGrant()).isEqualTo(authentication); |
||||||
|
assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); |
||||||
|
|
||||||
|
OAuth2TokenExchangeCompositeAuthenticationToken tokenContextPrincipal = tokenContext.getPrincipal(); |
||||||
|
assertThat(tokenContextPrincipal.getSubject()).isSameAs(subjectPrincipal.getSubject()); |
||||||
|
assertThat(tokenContextPrincipal.getActors()).containsExactly(actor2, actor1); |
||||||
|
|
||||||
|
OAuth2Authorization authorization = authorizationCaptor.getValue(); |
||||||
|
assertThat(authorization.getPrincipalName()).isEqualTo(subjectAuthorization.getPrincipalName()); |
||||||
|
assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); |
||||||
|
assertThat(authorization.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); |
||||||
|
assertThat(authorization.getAccessToken().getToken()).isEqualTo(accessToken); |
||||||
|
assertThat(authorization.getRefreshToken()).isNull(); |
||||||
|
|
||||||
|
OAuth2TokenExchangeCompositeAuthenticationToken authorizationPrincipal = |
||||||
|
authorization.getAttribute(Principal.class.getName()); |
||||||
|
assertThat(authorizationPrincipal).isNotNull(); |
||||||
|
assertThat(authorizationPrincipal.getSubject()).isSameAs(subjectPrincipal.getSubject()); |
||||||
|
assertThat(authorizationPrincipal.getActors()).containsExactly(actor2, actor1); |
||||||
|
} |
||||||
|
|
||||||
|
private static void mockAuthorizationServerContext() { |
||||||
|
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); |
||||||
|
TestAuthorizationServerContext authorizationServerContext = new TestAuthorizationServerContext( |
||||||
|
authorizationServerSettings, () -> "https://provider.com"); |
||||||
|
AuthorizationServerContextHolder.setContext(authorizationServerContext); |
||||||
|
} |
||||||
|
|
||||||
|
private static OAuth2TokenExchangeAuthenticationToken createDelegationRequest(RegisteredClient registeredClient) { |
||||||
|
return createDelegationRequest(registeredClient, registeredClient.getScopes()); |
||||||
|
} |
||||||
|
|
||||||
|
private static OAuth2TokenExchangeAuthenticationToken createDelegationRequest(RegisteredClient registeredClient, |
||||||
|
Set<String> requestedScopes) { |
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, |
||||||
|
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null); |
||||||
|
return new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE, |
||||||
|
clientPrincipal, ACTOR_TOKEN, ACCESS_TOKEN_TYPE_VALUE, RESOURCES, AUDIENCES, requestedScopes, null); |
||||||
|
} |
||||||
|
|
||||||
|
private static OAuth2TokenExchangeAuthenticationToken createImpersonationRequest(RegisteredClient registeredClient) { |
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, |
||||||
|
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null); |
||||||
|
return new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE, |
||||||
|
clientPrincipal, null, null, RESOURCES, AUDIENCES, registeredClient.getScopes(), null); |
||||||
|
} |
||||||
|
|
||||||
|
private static OAuth2TokenExchangeAuthenticationToken createJwtRequest(RegisteredClient registeredClient) { |
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, |
||||||
|
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null); |
||||||
|
return new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, JWT_TOKEN_TYPE_VALUE, |
||||||
|
clientPrincipal, ACTOR_TOKEN, JWT_TOKEN_TYPE_VALUE, RESOURCES, AUDIENCES, registeredClient.getScopes(), |
||||||
|
null); |
||||||
|
} |
||||||
|
|
||||||
|
private static OAuth2AccessToken createAccessToken(String tokenValue) { |
||||||
|
Instant issuedAt = Instant.now(); |
||||||
|
Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES); |
||||||
|
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, issuedAt, expiresAt); |
||||||
|
} |
||||||
|
|
||||||
|
private static OAuth2AccessToken createExpiredAccessToken(String tokenValue) { |
||||||
|
Instant issuedAt = Instant.now().minus(45, ChronoUnit.MINUTES); |
||||||
|
Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES); |
||||||
|
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, issuedAt, expiresAt); |
||||||
|
} |
||||||
|
|
||||||
|
private static Consumer<Map<String, Object>> withClaims(Map<String, Object> claims) { |
||||||
|
return (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, claims); |
||||||
|
} |
||||||
|
|
||||||
|
private static Consumer<Map<String, Object>> withTokenFormat(OAuth2TokenFormat tokenFormat) { |
||||||
|
return (metadata) -> metadata.put(OAuth2TokenFormat.class.getName(), tokenFormat.getValue()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,134 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2024 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.authentication; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||||
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||||
|
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 OAuth2TokenExchangeAuthenticationToken}. |
||||||
|
* |
||||||
|
* @author Steve Riesenberg |
||||||
|
*/ |
||||||
|
public class OAuth2TokenExchangeAuthenticationTokenTests { |
||||||
|
private static final Set<String> RESOURCES = Set.of("https://mydomain.com/resource1", "https://mydomain.com/resource2"); |
||||||
|
private static final Set<String> AUDIENCES = Set.of("audience1", "audience2"); |
||||||
|
private static final String SUBJECT_TOKEN = "EfYu_0jEL"; |
||||||
|
private static final String ACTOR_TOKEN = "JlNE_xR1f"; |
||||||
|
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; |
||||||
|
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; |
||||||
|
|
||||||
|
private RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); |
||||||
|
private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken( |
||||||
|
this.registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret()); |
||||||
|
private Set<String> scopes = Collections.singleton("scope1"); |
||||||
|
private Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1"); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() { |
||||||
|
// @formatter:off
|
||||||
|
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(null, null, null, null, null, null, null, null, null, this.additionalParameters)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||||
|
.hasMessage("clientPrincipal cannot be null"); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenRequestedTokenTypeNullOrEmptyThenThrowIllegalArgumentException() { |
||||||
|
// @formatter:off
|
||||||
|
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(null, null, null, this.clientPrincipal, null, null, null, null, null, this.additionalParameters)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||||
|
.hasMessage("requestedTokenType cannot be empty"); |
||||||
|
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken("", null, null, this.clientPrincipal, null, null, null, null, null, this.additionalParameters)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||||
|
.hasMessage("requestedTokenType cannot be empty"); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenSubjectTokenNullOrEmptyThenThrowIllegalArgumentException() { |
||||||
|
// @formatter:off
|
||||||
|
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, null, null, this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||||
|
.hasMessage("subjectToken cannot be empty"); |
||||||
|
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, "", null, this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||||
|
.hasMessage("subjectToken cannot be empty"); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenSubjectTokenTypeNullOrEmptyThenThrowIllegalArgumentException() { |
||||||
|
// @formatter:off
|
||||||
|
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, null, this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||||
|
.hasMessage("subjectTokenType cannot be empty"); |
||||||
|
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, "", this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||||
|
.hasMessage("subjectTokenType cannot be empty"); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenRequiredParametersProvidedThenCreated() { |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = new OAuth2TokenExchangeAuthenticationToken( |
||||||
|
JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE, this.clientPrincipal, null, null, null, |
||||||
|
null, null, this.additionalParameters); |
||||||
|
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); |
||||||
|
assertThat(authentication.getCredentials().toString()).isEmpty(); |
||||||
|
assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); |
||||||
|
assertThat(authentication.getRequestedTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE); |
||||||
|
assertThat(authentication.getSubjectToken()).isEqualTo(SUBJECT_TOKEN); |
||||||
|
assertThat(authentication.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
assertThat(authentication.getActorToken()).isNull(); |
||||||
|
assertThat(authentication.getActorTokenType()).isNull(); |
||||||
|
assertThat(authentication.getResources()).isEmpty(); |
||||||
|
assertThat(authentication.getAudiences()).isEmpty(); |
||||||
|
assertThat(authentication.getScopes()).isEmpty(); |
||||||
|
assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenAllParametersProvidedThenCreated() { |
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = new OAuth2TokenExchangeAuthenticationToken( |
||||||
|
JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE, this.clientPrincipal, ACTOR_TOKEN, |
||||||
|
ACCESS_TOKEN_TYPE_VALUE, RESOURCES, AUDIENCES, this.scopes, this.additionalParameters); |
||||||
|
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); |
||||||
|
assertThat(authentication.getCredentials().toString()).isEmpty(); |
||||||
|
assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); |
||||||
|
assertThat(authentication.getRequestedTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE); |
||||||
|
assertThat(authentication.getSubjectToken()).isEqualTo(SUBJECT_TOKEN); |
||||||
|
assertThat(authentication.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
assertThat(authentication.getActorToken()).isEqualTo(ACTOR_TOKEN); |
||||||
|
assertThat(authentication.getActorTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
assertThat(authentication.getResources()).isEqualTo(RESOURCES); |
||||||
|
assertThat(authentication.getAudiences()).isEqualTo(AUDIENCES); |
||||||
|
assertThat(authentication.getScopes()).isEqualTo(this.scopes); |
||||||
|
assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2024 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.authentication; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link OAuth2TokenExchangeCompositeAuthenticationToken}. |
||||||
|
* |
||||||
|
* @author Steve Riesenberg |
||||||
|
*/ |
||||||
|
public class OAuth2TokenExchangeCompositeAuthenticationTokenTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenSubjectNullThenThrowIllegalArgumentException() { |
||||||
|
// @formatter:off
|
||||||
|
assertThatIllegalArgumentException() |
||||||
|
.isThrownBy(() -> new OAuth2TokenExchangeCompositeAuthenticationToken(null, null)) |
||||||
|
.withMessage("subject cannot be null"); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenActorsNullThenThrowIllegalArgumentException() { |
||||||
|
TestingAuthenticationToken subject = new TestingAuthenticationToken("subject", null); |
||||||
|
// @formatter:off
|
||||||
|
assertThatIllegalArgumentException() |
||||||
|
.isThrownBy(() -> new OAuth2TokenExchangeCompositeAuthenticationToken(subject, null)) |
||||||
|
.withMessage("actors cannot be null"); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenRequiredParametersProvidedThenCreated() { |
||||||
|
TestingAuthenticationToken subject = new TestingAuthenticationToken("subject", null); |
||||||
|
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of("claim1", "value1")); |
||||||
|
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of("claim2", "value2")); |
||||||
|
List<OAuth2TokenExchangeActor> actors = List.of(actor1, actor2); |
||||||
|
OAuth2TokenExchangeCompositeAuthenticationToken authentication = |
||||||
|
new OAuth2TokenExchangeCompositeAuthenticationToken(subject, actors); |
||||||
|
assertThat(authentication.getSubject()).isEqualTo(subject); |
||||||
|
assertThat(authentication.getActors()).isEqualTo(actors); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,368 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2024 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.config.annotation.web.configurers; |
||||||
|
|
||||||
|
import java.security.Principal; |
||||||
|
import java.time.Instant; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.UUID; |
||||||
|
import java.util.function.Consumer; |
||||||
|
import java.util.function.Function; |
||||||
|
|
||||||
|
import com.nimbusds.jose.jwk.JWKSet; |
||||||
|
import com.nimbusds.jose.jwk.source.JWKSource; |
||||||
|
import com.nimbusds.jose.proc.SecurityContext; |
||||||
|
import org.junit.jupiter.api.AfterAll; |
||||||
|
import org.junit.jupiter.api.AfterEach; |
||||||
|
import org.junit.jupiter.api.BeforeAll; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.junit.jupiter.api.extension.ExtendWith; |
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Import; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.converter.HttpMessageConverter; |
||||||
|
import org.springframework.jdbc.core.JdbcOperations; |
||||||
|
import org.springframework.jdbc.core.JdbcTemplate; |
||||||
|
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; |
||||||
|
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; |
||||||
|
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; |
||||||
|
import org.springframework.mock.http.client.MockClientHttpResponse; |
||||||
|
import org.springframework.mock.web.MockHttpServletResponse; |
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
||||||
|
import org.springframework.security.core.Authentication; |
||||||
|
import org.springframework.security.core.authority.AuthorityUtils; |
||||||
|
import org.springframework.security.core.userdetails.User; |
||||||
|
import org.springframework.security.crypto.password.NoOpPasswordEncoder; |
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder; |
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2Token; |
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||||
|
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; |
||||||
|
import org.springframework.security.oauth2.jose.TestJwks; |
||||||
|
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; |
||||||
|
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; |
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; |
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; |
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; |
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; |
||||||
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; |
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; |
||||||
|
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; |
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; |
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; |
||||||
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; |
||||||
|
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; |
||||||
|
import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; |
||||||
|
import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; |
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames; |
||||||
|
import org.springframework.test.web.servlet.MockMvc; |
||||||
|
import org.springframework.test.web.servlet.MvcResult; |
||||||
|
import org.springframework.util.LinkedMultiValueMap; |
||||||
|
import org.springframework.util.MultiValueMap; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; |
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; |
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||||
|
|
||||||
|
/** |
||||||
|
* Integration tests for OAuth 2.0 Token Exchange Grant. |
||||||
|
* |
||||||
|
* @author Steve Riesenberg |
||||||
|
*/ |
||||||
|
@ExtendWith(SpringTestContextExtension.class) |
||||||
|
public class OAuth2TokenExchangeGrantTests { |
||||||
|
private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; |
||||||
|
private static final String RESOURCE = "https://mydomain.com/resource"; |
||||||
|
private static final String AUDIENCE = "audience"; |
||||||
|
private static final String SUBJECT_TOKEN = "EfYu_0jEL"; |
||||||
|
private static final String ACTOR_TOKEN = "JlNE_xR1f"; |
||||||
|
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; |
||||||
|
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; |
||||||
|
|
||||||
|
public final SpringTestContext spring = new SpringTestContext(); |
||||||
|
|
||||||
|
private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenResponseHttpMessageConverter = |
||||||
|
new OAuth2AccessTokenResponseHttpMessageConverter(); |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private MockMvc mvc; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private JdbcOperations jdbcOperations; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private RegisteredClientRepository registeredClientRepository; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private OAuth2AuthorizationService authorizationService; |
||||||
|
|
||||||
|
@BeforeAll |
||||||
|
public static void init() { |
||||||
|
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); |
||||||
|
AuthorizationServerConfiguration.JWK_SOURCE = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); |
||||||
|
// @formatter:off
|
||||||
|
AuthorizationServerConfiguration.DB = new EmbeddedDatabaseBuilder() |
||||||
|
.generateUniqueName(true) |
||||||
|
.setType(EmbeddedDatabaseType.HSQL) |
||||||
|
.setScriptEncoding("UTF-8") |
||||||
|
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") |
||||||
|
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") |
||||||
|
.addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") |
||||||
|
.build(); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@AfterEach |
||||||
|
public void tearDown() { |
||||||
|
this.jdbcOperations.update("truncate table oauth2_authorization"); |
||||||
|
this.jdbcOperations.update("truncate table oauth2_authorization_consent"); |
||||||
|
this.jdbcOperations.update("truncate table oauth2_registered_client"); |
||||||
|
} |
||||||
|
|
||||||
|
@AfterAll |
||||||
|
public static void destroy() { |
||||||
|
AuthorizationServerConfiguration.DB.shutdown(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void requestWhenAccessTokenRequestNotAuthenticatedThenUnauthorized() throws Exception { |
||||||
|
this.spring.register(AuthorizationServerConfiguration.class).autowire(); |
||||||
|
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
this.registeredClientRepository.save(registeredClient); |
||||||
|
|
||||||
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); |
||||||
|
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); |
||||||
|
parameters.set(OAuth2ParameterNames.SCOPE, |
||||||
|
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); |
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(parameters)) |
||||||
|
.andExpect(status().isUnauthorized()); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void requestWhenAccessTokenRequestValidAndNoActorTokenThenReturnAccessTokenResponseForImpersonation() throws Exception { |
||||||
|
this.spring.register(AuthorizationServerConfiguration.class).autowire(); |
||||||
|
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
this.registeredClientRepository.save(registeredClient); |
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user"); |
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) |
||||||
|
.attribute(Principal.class.getName(), userPrincipal).build(); |
||||||
|
this.authorizationService.save(subjectAuthorization); |
||||||
|
|
||||||
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); |
||||||
|
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); |
||||||
|
parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||||
|
parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, subjectAuthorization.getAccessToken().getToken().getTokenValue()); |
||||||
|
parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||||
|
parameters.set(OAuth2ParameterNames.RESOURCE, RESOURCE); |
||||||
|
parameters.set(OAuth2ParameterNames.AUDIENCE, AUDIENCE); |
||||||
|
parameters.set(OAuth2ParameterNames.SCOPE, |
||||||
|
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); |
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) |
||||||
|
.params(parameters) |
||||||
|
.headers(withClientAuth(registeredClient))) |
||||||
|
.andExpect(status().isOk()) |
||||||
|
.andExpect(jsonPath("$.access_token").isNotEmpty()) |
||||||
|
.andExpect(jsonPath("$.refresh_token").doesNotExist()) |
||||||
|
.andExpect(jsonPath("$.expires_in").isNumber()) |
||||||
|
.andExpect(jsonPath("$.scope").isNotEmpty()) |
||||||
|
.andExpect(jsonPath("$.token_type").isNotEmpty()) |
||||||
|
.andExpect(jsonPath("$.issued_token_type").isNotEmpty()) |
||||||
|
.andReturn(); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
MockHttpServletResponse servletResponse = mvcResult.getResponse(); |
||||||
|
MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), |
||||||
|
HttpStatus.OK); |
||||||
|
OAuth2AccessTokenResponse accessTokenResponse = |
||||||
|
this.accessTokenResponseHttpMessageConverter.read(OAuth2AccessTokenResponse.class, httpResponse); |
||||||
|
|
||||||
|
String accessToken = accessTokenResponse.getAccessToken().getTokenValue(); |
||||||
|
OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken, |
||||||
|
OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
assertThat(authorization).isNotNull(); |
||||||
|
assertThat(authorization.getAccessToken()).isNotNull(); |
||||||
|
assertThat(authorization.getAccessToken().getClaims()).isNotNull(); |
||||||
|
// We do not populate claims (e.g. `aud`) based on the resource or audience parameters
|
||||||
|
assertThat(authorization.getAccessToken().getClaims().get(OAuth2TokenClaimNames.AUD)) |
||||||
|
.isEqualTo(List.of(registeredClient.getClientId())); |
||||||
|
assertThat(authorization.getRefreshToken()).isNull(); |
||||||
|
assertThat(authorization.<Authentication>getAttribute(Principal.class.getName())).isEqualTo(userPrincipal); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void requestWhenAccessTokenRequestValidAndActorTokenThenReturnAccessTokenResponseForDelegation() throws Exception { |
||||||
|
this.spring.register(AuthorizationServerConfiguration.class).autowire(); |
||||||
|
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient() |
||||||
|
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build(); |
||||||
|
this.registeredClientRepository.save(registeredClient); |
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user"); |
||||||
|
UsernamePasswordAuthenticationToken adminPrincipal = createUserPrincipal("admin"); |
||||||
|
Map<String, Object> actorTokenClaims = new HashMap<>(); |
||||||
|
actorTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer2"); |
||||||
|
actorTokenClaims.put(OAuth2TokenClaimNames.SUB, "admin"); |
||||||
|
Map<String, Object> subjectTokenClaims = new HashMap<>(); |
||||||
|
subjectTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer1"); |
||||||
|
subjectTokenClaims.put(OAuth2TokenClaimNames.SUB, "user"); |
||||||
|
subjectTokenClaims.put("may_act", actorTokenClaims); |
||||||
|
OAuth2AccessToken subjectToken = createAccessToken(SUBJECT_TOKEN); |
||||||
|
OAuth2AccessToken actorToken = createAccessToken(ACTOR_TOKEN); |
||||||
|
// @formatter:off
|
||||||
|
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient, subjectToken, subjectTokenClaims) |
||||||
|
.id(UUID.randomUUID().toString()) |
||||||
|
.attribute(Principal.class.getName(), userPrincipal) |
||||||
|
.build(); |
||||||
|
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient, actorToken, actorTokenClaims) |
||||||
|
.id(UUID.randomUUID().toString()) |
||||||
|
.attribute(Principal.class.getName(), adminPrincipal) |
||||||
|
.build(); |
||||||
|
// @formatter:on
|
||||||
|
this.authorizationService.save(subjectAuthorization); |
||||||
|
this.authorizationService.save(actorAuthorization); |
||||||
|
|
||||||
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); |
||||||
|
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); |
||||||
|
parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||||
|
parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||||
|
parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||||
|
parameters.set(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); |
||||||
|
parameters.set(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
parameters.set(OAuth2ParameterNames.SCOPE, |
||||||
|
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); |
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) |
||||||
|
.params(parameters) |
||||||
|
.headers(withClientAuth(registeredClient))) |
||||||
|
.andExpect(status().isOk()) |
||||||
|
.andExpect(jsonPath("$.access_token").isNotEmpty()) |
||||||
|
.andExpect(jsonPath("$.refresh_token").doesNotExist()) |
||||||
|
.andExpect(jsonPath("$.expires_in").isNumber()) |
||||||
|
.andExpect(jsonPath("$.scope").isNotEmpty()) |
||||||
|
.andExpect(jsonPath("$.token_type").isNotEmpty()) |
||||||
|
.andExpect(jsonPath("$.issued_token_type").isNotEmpty()) |
||||||
|
.andReturn(); |
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
MockHttpServletResponse servletResponse = mvcResult.getResponse(); |
||||||
|
MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), |
||||||
|
HttpStatus.OK); |
||||||
|
OAuth2AccessTokenResponse accessTokenResponse = |
||||||
|
this.accessTokenResponseHttpMessageConverter.read(OAuth2AccessTokenResponse.class, httpResponse); |
||||||
|
|
||||||
|
String accessToken = accessTokenResponse.getAccessToken().getTokenValue(); |
||||||
|
OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken, |
||||||
|
OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
assertThat(authorization).isNotNull(); |
||||||
|
assertThat(authorization.getAccessToken()).isNotNull(); |
||||||
|
assertThat(authorization.getAccessToken().getClaims()).isNotNull(); |
||||||
|
assertThat(authorization.getAccessToken().getClaims().get("act")).isNotNull(); |
||||||
|
assertThat(authorization.getRefreshToken()).isNull(); |
||||||
|
assertThat(authorization.<Authentication>getAttribute(Principal.class.getName())) |
||||||
|
.isInstanceOf(OAuth2TokenExchangeCompositeAuthenticationToken.class); |
||||||
|
} |
||||||
|
|
||||||
|
private static OAuth2AccessToken createAccessToken(String tokenValue) { |
||||||
|
Instant issuedAt = Instant.now(); |
||||||
|
Instant expiresAt = issuedAt.plusSeconds(300); |
||||||
|
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, issuedAt, expiresAt); |
||||||
|
} |
||||||
|
|
||||||
|
private static UsernamePasswordAuthenticationToken createUserPrincipal(String username) { |
||||||
|
User user = new User(username, "", AuthorityUtils.createAuthorityList("ROLE_USER")); |
||||||
|
return UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities()); |
||||||
|
} |
||||||
|
|
||||||
|
private static HttpHeaders withClientAuth(RegisteredClient registeredClient) { |
||||||
|
HttpHeaders headers = new HttpHeaders(); |
||||||
|
headers.setBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()); |
||||||
|
return headers; |
||||||
|
} |
||||||
|
|
||||||
|
private static Consumer<Map<String, Object>> withInvalidated() { |
||||||
|
return (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true); |
||||||
|
} |
||||||
|
|
||||||
|
private static Function<OAuth2Authorization.Token<? extends OAuth2Token>, Boolean> isInvalidated() { |
||||||
|
return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME); |
||||||
|
} |
||||||
|
|
||||||
|
@EnableWebSecurity |
||||||
|
@Import(OAuth2AuthorizationServerConfiguration.class) |
||||||
|
static class AuthorizationServerConfiguration { |
||||||
|
|
||||||
|
static JWKSource<SecurityContext> JWK_SOURCE; |
||||||
|
|
||||||
|
static EmbeddedDatabase DB; |
||||||
|
|
||||||
|
@Bean |
||||||
|
RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { |
||||||
|
return new JdbcRegisteredClientRepository(jdbcOperations); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, |
||||||
|
RegisteredClientRepository registeredClientRepository) { |
||||||
|
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, |
||||||
|
RegisteredClientRepository registeredClientRepository) { |
||||||
|
return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
JdbcOperations jdbcOperations() { |
||||||
|
return new JdbcTemplate(DB); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
JWKSource<SecurityContext> jwkSource() { |
||||||
|
return JWK_SOURCE; |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
PasswordEncoder passwordEncoder() { |
||||||
|
return NoOpPasswordEncoder.getInstance(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,128 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2024 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.token; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.function.Consumer; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||||
|
import org.springframework.security.core.Authentication; |
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; |
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor; |
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken; |
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link DefaultOAuth2TokenClaimsConsumer}. |
||||||
|
* |
||||||
|
* @author Steve Riesenberg |
||||||
|
*/ |
||||||
|
public class DefaultOAuth2TokenClaimsConsumerTests { |
||||||
|
|
||||||
|
private OAuth2TokenContext tokenContext; |
||||||
|
|
||||||
|
private Consumer<Map<String, Object>> consumer; |
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
public void setUp() { |
||||||
|
this.tokenContext = mock(OAuth2TokenContext.class); |
||||||
|
this.consumer = new DefaultOAuth2TokenClaimsConsumer(this.tokenContext); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void acceptWhenTokenTypeIsRefreshTokenThenNoClaimsAdded() { |
||||||
|
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.REFRESH_TOKEN); |
||||||
|
Map<String, Object> claims = new LinkedHashMap<>(); |
||||||
|
this.consumer.accept(claims); |
||||||
|
assertThat(claims).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void acceptWhenAuthorizationGrantIsNullThenNoClaimsAdded() { |
||||||
|
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
when(this.tokenContext.getAuthorizationGrant()).thenReturn(null); |
||||||
|
Map<String, Object> claims = new LinkedHashMap<>(); |
||||||
|
this.consumer.accept(claims); |
||||||
|
assertThat(claims).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void acceptWhenTokenExchangeGrantAndResourcesThenNoClaimsAdded() { |
||||||
|
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( |
||||||
|
OAuth2TokenExchangeAuthenticationToken.class); |
||||||
|
when(tokenExchangeAuthentication.getResources()).thenReturn(Set.of("resource1", "resource2")); |
||||||
|
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
when(this.tokenContext.getAuthorizationGrant()).thenReturn(tokenExchangeAuthentication); |
||||||
|
Map<String, Object> claims = new LinkedHashMap<>(); |
||||||
|
this.consumer.accept(claims); |
||||||
|
// We do not populate claims (e.g. `aud`) based on the resource parameter
|
||||||
|
assertThat(claims).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void acceptWhenTokenExchangeGrantAndAudiencesThenNoClaimsAdded() { |
||||||
|
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( |
||||||
|
OAuth2TokenExchangeAuthenticationToken.class); |
||||||
|
when(tokenExchangeAuthentication.getAudiences()).thenReturn(Set.of("audience1", "audience2")); |
||||||
|
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
when(this.tokenContext.getAuthorizationGrant()).thenReturn(tokenExchangeAuthentication); |
||||||
|
Map<String, Object> claims = new LinkedHashMap<>(); |
||||||
|
this.consumer.accept(claims); |
||||||
|
// NOTE: We do not populate claims (e.g. `aud`) based on the audience parameter
|
||||||
|
assertThat(claims).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void acceptWhenTokenExchangeGrantAndDelegationThenActClaimAdded() { |
||||||
|
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( |
||||||
|
OAuth2TokenExchangeAuthenticationToken.class); |
||||||
|
when(tokenExchangeAuthentication.getAudiences()).thenReturn(Collections.emptySet()); |
||||||
|
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN); |
||||||
|
when(this.tokenContext.getAuthorizationGrant()).thenReturn(tokenExchangeAuthentication); |
||||||
|
Authentication subject = new TestingAuthenticationToken("subject", null); |
||||||
|
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer1", |
||||||
|
OAuth2TokenClaimNames.SUB, "actor1")); |
||||||
|
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer2", |
||||||
|
OAuth2TokenClaimNames.SUB, "actor2")); |
||||||
|
OAuth2TokenExchangeCompositeAuthenticationToken principal = new OAuth2TokenExchangeCompositeAuthenticationToken( |
||||||
|
subject, List.of(actor1, actor2)); |
||||||
|
when(this.tokenContext.getPrincipal()).thenReturn(principal); |
||||||
|
Map<String, Object> claims = new LinkedHashMap<>(); |
||||||
|
this.consumer.accept(claims); |
||||||
|
assertThat(claims).hasSize(1); |
||||||
|
assertThat(claims.get("act")).isNotNull(); |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
Map<String, Object> actClaim1 = (Map<String, Object>) claims.get("act"); |
||||||
|
assertThat(actClaim1.get(OAuth2TokenClaimNames.ISS)).isEqualTo("issuer1"); |
||||||
|
assertThat(actClaim1.get(OAuth2TokenClaimNames.SUB)).isEqualTo("actor1"); |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
Map<String, Object> actClaim2 = (Map<String, Object>) actClaim1.get("act"); |
||||||
|
assertThat(actClaim2.get(OAuth2TokenClaimNames.ISS)).isEqualTo("issuer2"); |
||||||
|
assertThat(actClaim2.get(OAuth2TokenClaimNames.SUB)).isEqualTo("actor2"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,336 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2024 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.web.authentication; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach; |
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import org.springframework.http.HttpMethod; |
||||||
|
import org.springframework.mock.web.MockHttpServletRequest; |
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||||
|
import org.springframework.security.core.Authentication; |
||||||
|
import org.springframework.security.core.context.SecurityContextHolder; |
||||||
|
import org.springframework.security.core.context.SecurityContextImpl; |
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; |
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link OAuth2TokenExchangeAuthenticationConverter}. |
||||||
|
* |
||||||
|
* @author Steve Riesenberg |
||||||
|
*/ |
||||||
|
public class OAuth2TokenExchangeAuthenticationConverterTests { |
||||||
|
private static final String CLIENT_ID = "client-1"; |
||||||
|
private static final String TOKEN_URI = "/oauth2/token"; |
||||||
|
private static final String SUBJECT_TOKEN = "EfYu_0jEL"; |
||||||
|
private static final String ACTOR_TOKEN = "JlNE_xR1f"; |
||||||
|
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; |
||||||
|
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; |
||||||
|
|
||||||
|
private OAuth2TokenExchangeAuthenticationConverter converter; |
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
public void setUp() { |
||||||
|
this.converter = new OAuth2TokenExchangeAuthenticationConverter(); |
||||||
|
} |
||||||
|
|
||||||
|
@AfterEach |
||||||
|
public void tearDown() { |
||||||
|
SecurityContextHolder.clearContext(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenMissingGrantTypeThenReturnNull() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
Authentication authentication = this.converter.convert(request); |
||||||
|
assertThat(authentication).isNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenInvalidResourceThenInvalidRequestError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.RESOURCE, "invalid"); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.RESOURCE) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenResourceContainsFragmentThenInvalidRequestError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/#fragment"); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.RESOURCE) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenMultipleScopeParametersThenInvalidRequestError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.SCOPE, "one"); |
||||||
|
request.addParameter(OAuth2ParameterNames.SCOPE, "two"); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.SCOPE) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenMultipleRequestedTokenTypeParametersThenInvalidRequestError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenInvalidRequestedTokenTypeThenUnsupportedTokenTypeError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, "invalid"); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenMissingSubjectTokenThenInvalidRequestError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenMultipleSubjectTokenParametersThenInvalidRequestError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, "another"); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenMissingSubjectTokenTypeThenInvalidRequestError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenMultipleSubjectTokenTypeParametersThenInvalidRequestError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenInvalidSubjectTokenTypeThenUnsupportedTokenTypeError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, "invalid"); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenMultipleActorTokenParametersThenInvalidRequestError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); |
||||||
|
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, "another"); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenActorTokenAndMissingActorTokenTypeThenInvalidRequestError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN_TYPE) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenActorTokenTypeAndMissingActorTokenThenInvalidRequestError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenInvalidActorTokenTypeThenUnsupportedTokenTypeError() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); |
||||||
|
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, "invalid"); |
||||||
|
// @formatter:off
|
||||||
|
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||||
|
.isThrownBy(() -> this.converter.convert(request)) |
||||||
|
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN_TYPE) |
||||||
|
.extracting(OAuth2AuthenticationException::getError) |
||||||
|
.extracting(OAuth2Error::getErrorCode) |
||||||
|
.isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE); |
||||||
|
// @formatter:on
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void convertWhenAllParametersThenTokenExchangeAuthenticationToken() { |
||||||
|
MockHttpServletRequest request = createRequest(); |
||||||
|
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||||
|
request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/resource1"); |
||||||
|
request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/resource2"); |
||||||
|
request.addParameter(OAuth2ParameterNames.AUDIENCE, "audience1"); |
||||||
|
request.addParameter(OAuth2ParameterNames.AUDIENCE, "audience2"); |
||||||
|
request.addParameter(OAuth2ParameterNames.SCOPE, "one two"); |
||||||
|
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||||
|
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); |
||||||
|
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||||
|
|
||||||
|
SecurityContextImpl securityContext = new SecurityContextImpl(); |
||||||
|
securityContext.setAuthentication(new TestingAuthenticationToken(CLIENT_ID, null)); |
||||||
|
SecurityContextHolder.setContext(securityContext); |
||||||
|
|
||||||
|
OAuth2TokenExchangeAuthenticationToken authentication = |
||||||
|
(OAuth2TokenExchangeAuthenticationToken) this.converter.convert(request); |
||||||
|
assertThat(authentication).isNotNull(); |
||||||
|
assertThat(authentication.getResources()).containsExactly("https://mydomain.com/resource1", |
||||||
|
"https://mydomain.com/resource2"); |
||||||
|
assertThat(authentication.getAudiences()).containsExactly("audience1", "audience2"); |
||||||
|
assertThat(authentication.getScopes()).containsExactly("one", "two"); |
||||||
|
assertThat(authentication.getRequestedTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE); |
||||||
|
assertThat(authentication.getSubjectToken()).isEqualTo(SUBJECT_TOKEN); |
||||||
|
assertThat(authentication.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||||
|
assertThat(authentication.getActorToken()).isEqualTo(ACTOR_TOKEN); |
||||||
|
assertThat(authentication.getActorTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE); |
||||||
|
} |
||||||
|
|
||||||
|
private static MockHttpServletRequest createRequest() { |
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||||
|
request.setMethod(HttpMethod.POST.name()); |
||||||
|
request.setRequestURI(TOKEN_URI); |
||||||
|
return request; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue