diff options
author | Matteo Mara <matteo.mara@sonarsource.com> | 2022-06-30 10:44:29 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-07-01 20:03:06 +0000 |
commit | 5159eb8d7cda29c357aa06868a595d07fbc6f633 (patch) | |
tree | 7caf459539e67ed23199256d6df9ba18911ee069 | |
parent | 05ebcc134533b03a324a2710c3fe6d711850a5c4 (diff) | |
download | sonarqube-5159eb8d7cda29c357aa06868a595d07fbc6f633.tar.gz sonarqube-5159eb8d7cda29c357aa06868a595d07fbc6f633.zip |
SONAR-16565 handle token expiration at the authentication level
4 files changed, 69 insertions, 2 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java index bcd9572efee..d749aa7b184 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java @@ -148,4 +148,8 @@ public class UserTokenDto { this.projectUuid = projectUuid; return this; } + + public boolean isExpired() { + return (this.expirationDate != null && this.getExpirationDate() < System.currentTimeMillis()); + } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDtoTest.java index 6da9f5b6d02..eca56374bd5 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDtoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDtoTest.java @@ -19,9 +19,12 @@ */ package org.sonar.db.user; +import java.time.ZoneId; +import java.time.ZonedDateTime; import org.junit.Test; import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class UserTokenDtoTest { @@ -32,4 +35,16 @@ public class UserTokenDtoTest { .isInstanceOf(IllegalStateException.class) .hasMessage("Token hash length (256) is longer than the maximum authorized (255)"); } + + @Test + public void token_isExpired_is_properly_calculated() { + UserTokenDto tokenWithNoExpirationDate = new UserTokenDto(); + UserTokenDto expiredToken = new UserTokenDto().setExpirationDate(0L); + UserTokenDto nonExpiredToken = new UserTokenDto().setExpirationDate(ZonedDateTime.now(ZoneId.systemDefault()).plusDays(10).toInstant().toEpochMilli()); + + assertThat(tokenWithNoExpirationDate.isExpired()).isFalse(); + assertThat(expiredToken.isExpired()).isTrue(); + assertThat(nonExpiredToken.isExpired()).isFalse(); + } + } diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java index be066b6a422..8bdef338bf6 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java @@ -33,6 +33,7 @@ import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationException; import org.sonar.server.exceptions.NotFoundException; +import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.server.authentication.BasicAuthentication.extractCredentialsFromHeader; public class UserTokenAuthentication { @@ -80,7 +81,7 @@ public class UserTokenAuthentication { } request.setAttribute(ACCESS_LOG_TOKEN_NAME, userToken.getName()); return new UserAuthResult(userDto, userToken, UserAuthResult.AuthType.TOKEN); - } catch (NotFoundException exception) { + } catch (NotFoundException | IllegalStateException exception ) { throw AuthenticationException.newBuilder() .setSource(AuthenticationEvent.Source.local(AuthenticationEvent.Method.BASIC_TOKEN)) .setMessage(exception.getMessage()) @@ -93,6 +94,9 @@ public class UserTokenAuthentication { if (userToken == null) { throw new NotFoundException("Token doesn't exist"); } + if (userToken.isExpired()) { + throw new IllegalStateException("The token expired on " + formatDateTime(userToken.getExpirationDate())); + } userLastConnectionDatesUpdater.updateLastConnectionDateIfNeeded(userToken); return userToken; } diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java index 3e353f20e28..44d3cc141d8 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java @@ -19,6 +19,8 @@ */ package org.sonar.server.usertoken; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Base64; import java.util.Optional; import javax.servlet.http.HttpServletRequest; @@ -43,6 +45,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.db.user.TokenType.GLOBAL_ANALYSIS_TOKEN; import static org.sonar.db.user.TokenType.PROJECT_ANALYSIS_TOKEN; import static org.sonar.db.user.TokenType.USER_TOKEN; @@ -105,6 +108,30 @@ public class UserTokenAuthenticationTest { } @Test + public void return_login_when_token_hash_found_in_db_and_future_expiration_date() { + String token = "known-token"; + String tokenHash = "123456789"; + + long expirationTimestamp = ZonedDateTime.now(ZoneId.systemDefault()).plusDays(10).toInstant().toEpochMilli(); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(token + ":")); + when(tokenGenerator.hash(token)).thenReturn(tokenHash); + UserDto user1 = db.users().insertUser(); + UserTokenDto userTokenDto = db.users().insertToken(user1, t -> t.setTokenHash(tokenHash).setExpirationDate(expirationTimestamp)); + UserDto user2 = db.users().insertUser(); + db.users().insertToken(user2, t -> t.setTokenHash("another-token-hash")); + + Optional<UserAuthResult> result = underTest.authenticate(request); + + assertThat(result).isPresent(); + assertThat(result.get().getTokenDto().getUuid()).isEqualTo(userTokenDto.getUuid()); + assertThat(result.get().getTokenDto().getExpirationDate()).isEqualTo(expirationTimestamp); + assertThat(result.get().getUserDto().getUuid()) + .isNotNull() + .contains(user1.getUuid()); + verify(userLastConnectionDatesUpdater).updateLastConnectionDateIfNeeded(any(UserTokenDto.class)); + } + + @Test public void return_absent_if_username_password_used() { when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64("login:password")); @@ -116,7 +143,7 @@ public class UserTokenAuthenticationTest { } @Test - public void return_absent_if_token_hash_is_not_found() { + public void throw_authentication_exception_if_token_hash_is_not_found() { when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(EXAMPLE_OLD_USER_TOKEN + ":")); assertThatThrownBy(() -> underTest.authenticate(request)) @@ -127,6 +154,23 @@ public class UserTokenAuthenticationTest { } @Test + public void throw_authentication_exception_if_token_is_expired() { + String token = "known-token"; + String tokenHash = "123456789"; + long expirationTimestamp = System.currentTimeMillis(); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(token + ":")); + when(tokenGenerator.hash(token)).thenReturn(tokenHash); + UserDto user1 = db.users().insertUser(); + db.users().insertToken(user1, t -> t.setTokenHash(tokenHash).setExpirationDate(expirationTimestamp)); + + assertThatThrownBy(() -> underTest.authenticate(request)) + .hasMessageContaining("The token expired on " + formatDateTime(expirationTimestamp)) + .isInstanceOf(AuthenticationException.class); + verify(userLastConnectionDatesUpdater, never()).updateLastConnectionDateIfNeeded(any(UserTokenDto.class)); + verifyNoInteractions(authenticationEvent); + } + + @Test public void authenticate_givenGlobalToken_resultContainsUuid() { UserDto user = db.users().insertUser(); String tokenName = db.users().insertToken(user, t -> t.setTokenHash(GLOBAL_ANALYSIS_TOKEN_HASH).setType(GLOBAL_ANALYSIS_TOKEN.name())).getName(); |