aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatteo Mara <matteo.mara@sonarsource.com>2022-06-30 10:44:29 +0200
committersonartech <sonartech@sonarsource.com>2022-07-01 20:03:06 +0000
commit5159eb8d7cda29c357aa06868a595d07fbc6f633 (patch)
tree7caf459539e67ed23199256d6df9ba18911ee069
parent05ebcc134533b03a324a2710c3fe6d711850a5c4 (diff)
downloadsonarqube-5159eb8d7cda29c357aa06868a595d07fbc6f633.tar.gz
sonarqube-5159eb8d7cda29c357aa06868a595d07fbc6f633.zip
SONAR-16565 handle token expiration at the authentication level
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java4
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDtoTest.java15
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java6
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java46
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();