Browse Source

Check user code expiry and invalidity

Closes gh-1977

Signed-off-by: Antoine Lauzon <139174762+antoinelauzon-bell@users.noreply.github.com>
pull/2032/head
Antoine Lauzon 7 months ago committed by Joe Grandja
parent
commit
ce528eed9b
  1. 12
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java
  2. 83
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProviderTests.java

12
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2020-2023 the original author or authors.
* Copyright 2020-2025 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.
@ -109,6 +109,15 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut @@ -109,6 +109,15 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut
this.logger.trace("Retrieved authorization with user code");
}
OAuth2Authorization.Token<OAuth2UserCode> userCode = authorization.getToken(OAuth2UserCode.class);
if (!userCode.isActive()) {
if (!userCode.isInvalidated()) {
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, userCode.getToken());
this.authorizationService.save(authorization);
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}
Authentication principal = (Authentication) deviceVerificationAuthentication.getPrincipal();
if (!isPrincipalAuthenticated(principal)) {
if (this.logger.isTraceEnabled()) {
@ -161,7 +170,6 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut @@ -161,7 +170,6 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut
requestedScopes, currentAuthorizedScopes);
}
OAuth2Authorization.Token<OAuth2UserCode> userCode = authorization.getToken(OAuth2UserCode.class);
// @formatter:off
authorization = OAuth2Authorization.from(authorization)
.principalName(principal.getName())

83
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProviderTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2020-2023 the original author or authors.
* Copyright 2020-2025 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.
@ -20,6 +20,7 @@ import java.time.Instant; @@ -20,6 +20,7 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.jupiter.api.BeforeEach;
@ -145,10 +146,79 @@ public class OAuth2DeviceVerificationAuthenticationProviderTests { @@ -145,10 +146,79 @@ public class OAuth2DeviceVerificationAuthenticationProviderTests {
verifyNoInteractions(this.registeredClientRepository, this.authorizationConsentService);
}
@Test
public void authenticateWhenUserCodeIsInvalidedThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
// @formatter:off
OAuth2Authorization authorization = TestOAuth2Authorizations
.authorization(registeredClient)
.token(createDeviceCode())
.token(createUserCode(), withInvalidated())
.attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes())
.build();
// @formatter:on
given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization);
Authentication authentication = createAuthentication();
// @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(USER_CODE,
OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.registeredClientRepository, this.authorizationConsentService);
}
@Test
public void authenticateWhenUserCodeIsExpiredButNotInvalidatedThenInvalidateUserCodeAndThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
// @formatter:off
OAuth2Authorization authorization = TestOAuth2Authorizations
.authorization(registeredClient)
// Device code would also be expired but not relevant for this test
.token(createDeviceCode())
.token(createExpiredUserCode())
.attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes())
.build();
// @formatter:on
given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization);
Authentication authentication = createAuthentication();
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
// @formatter:on
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
verify(this.authorizationService).findByToken(USER_CODE,
OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE);
verify(this.authorizationService).save(authorizationCaptor.capture());
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.registeredClientRepository, this.authorizationConsentService);
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
assertThat(updatedAuthorization.getToken(OAuth2UserCode.class))
.extracting(isInvalidated())
.isEqualTo(true);
}
@Test
public void authenticateWhenPrincipalNotAuthenticatedThenReturnUnauthenticated() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
// @formatter:off
OAuth2Authorization authorization = TestOAuth2Authorizations
.authorization(registeredClient)
.token(createDeviceCode())
.token(createUserCode())
.attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes())
.build();
// @formatter:on
TestingAuthenticationToken principal = new TestingAuthenticationToken("user", null);
Authentication authentication = new OAuth2DeviceVerificationAuthenticationToken(principal, USER_CODE,
Collections.emptyMap());
@ -331,6 +401,15 @@ public class OAuth2DeviceVerificationAuthenticationProviderTests { @@ -331,6 +401,15 @@ public class OAuth2DeviceVerificationAuthenticationProviderTests {
return new OAuth2UserCode(USER_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES));
}
private static OAuth2UserCode createExpiredUserCode() {
Instant issuedAt = Instant.now().minus(45, ChronoUnit.MINUTES);
return new OAuth2UserCode(USER_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES));
}
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);
}

Loading…
Cancel
Save