From 64f509ee62827df74b73f5ef0f19a79721d80317 Mon Sep 17 00:00:00 2001 From: Daniel K Date: Mon, 6 Nov 2023 12:50:30 +0100 Subject: [PATCH] #75 mergefix --- .../main/resources/example.application.yaml | 3 +- .../jwt/AwsSecretsManagerKeyConfig.scala | 23 ++++---- .../rest/config/jwt/InMemoryKeyConfig.scala | 18 +++--- .../loginsvc/rest/config/jwt/KeyConfig.scala | 24 +++++--- .../rest/config/provider/ConfigProvider.scala | 2 +- .../config/provider/JwtConfigProvider.scala | 2 +- .../loginsvc/rest/service/JWTService.scala | 37 +++++------- service/src/test/resources/application.yaml | 4 +- .../AwsSecretsManagerKeyConfigTest.scala} | 49 +++++----------- .../config/jwt/InMemoryKeyConfigTest.scala | 57 +++++++++++++++++++ .../config/provider/ConfigProviderTest.scala | 6 +- .../rest/service/JWTServiceTest.scala | 12 ++-- 12 files changed, 137 insertions(+), 100 deletions(-) rename service/src/test/scala/za/co/absa/loginsvc/rest/config/{JwtConfigTest.scala => jwt/AwsSecretsManagerKeyConfigTest.scala} (53%) create mode 100644 service/src/test/scala/za/co/absa/loginsvc/rest/config/jwt/InMemoryKeyConfigTest.scala diff --git a/service/src/main/resources/example.application.yaml b/service/src/main/resources/example.application.yaml index d935708..c4808e9 100644 --- a/service/src/main/resources/example.application.yaml +++ b/service/src/main/resources/example.application.yaml @@ -6,7 +6,7 @@ loginsvc: generate-in-memory: access-exp-time: 15min refresh-exp-time: 9h - rotation-time: 9h + key-rotation-time: 9h alg-name: "RS256" #Instead of generating the key in memory #The Below Config allows for the application to fetch keys from AWS Secrets Manager. @@ -16,6 +16,7 @@ loginsvc: #private-key-field-name: "privateKey" #public-key-field-name: "publicKey" #access-exp-time: 15min + #refresh-exp-time: 9h #poll-time: 5min #alg-name: "RS256" config: diff --git a/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/AwsSecretsManagerKeyConfig.scala b/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/AwsSecretsManagerKeyConfig.scala index 7e52356..cfecf20 100644 --- a/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/AwsSecretsManagerKeyConfig.scala +++ b/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/AwsSecretsManagerKeyConfig.scala @@ -17,7 +17,6 @@ package za.co.absa.loginsvc.rest.config.jwt import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper} -import io.jsonwebtoken.SignatureAlgorithm import org.slf4j.LoggerFactory import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider import software.amazon.awssdk.regions.Region @@ -30,20 +29,22 @@ import java.security.{KeyFactory, KeyPair} import java.security.spec.{PKCS8EncodedKeySpec, X509EncodedKeySpec} import java.util.Base64 import scala.concurrent.duration.FiniteDuration -import scala.util.{Failure, Success, Try} -case class AwsSecretsManagerKeyConfig (secretName: String, - region: String, - privateKeyFieldName: String, - publicKeyFieldName: String, - algName: String, - accessExpTime: FiniteDuration, - pollTime: Option[FiniteDuration]) - extends KeyConfig { + +case class AwsSecretsManagerKeyConfig( + secretName: String, + region: String, + privateKeyFieldName: String, + publicKeyFieldName: String, + algName: String, + accessExpTime: FiniteDuration, + refreshExpTime: FiniteDuration, + pollTime: Option[FiniteDuration] +) extends KeyConfig { private val logger = LoggerFactory.getLogger(classOf[AwsSecretsManagerKeyConfig]) - override def refreshKeyTime : Option[FiniteDuration] = pollTime + override def keyRotationTime : Option[FiniteDuration] = pollTime override def keyPair(): KeyPair = { val default = DefaultCredentialsProvider.create diff --git a/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/InMemoryKeyConfig.scala b/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/InMemoryKeyConfig.scala index 7ac234d..4a43f1e 100644 --- a/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/InMemoryKeyConfig.scala +++ b/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/InMemoryKeyConfig.scala @@ -19,25 +19,25 @@ package za.co.absa.loginsvc.rest.config.jwt import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.security.Keys import org.slf4j.LoggerFactory -import za.co.absa.loginsvc.rest.config.validation.ConfigValidationResult.{ConfigValidationError, ConfigValidationSuccess} -import za.co.absa.loginsvc.rest.config.validation.{ConfigValidationException, ConfigValidationResult} -import scala.util.{Failure, Success, Try} import java.security.KeyPair import scala.concurrent.duration.FiniteDuration -case class InMemoryKeyConfig (algName: String, - accessExpTime: FiniteDuration, - rotationTime: Option[FiniteDuration]) - extends KeyConfig { +case class InMemoryKeyConfig( + algName: String, + accessExpTime: FiniteDuration, + refreshExpTime: FiniteDuration, + keyRotationTime: Option[FiniteDuration] +) extends KeyConfig { private val logger = LoggerFactory.getLogger(classOf[InMemoryKeyConfig]) - override def refreshKeyTime : Option[FiniteDuration] = rotationTime override def keyPair(): KeyPair = { - logger.info("Generating new keys") + logger.info(s"Generating new keys - every ${keyRotationTime.getOrElse("?")}") Keys.keyPairFor(SignatureAlgorithm.valueOf(algName)) } override def throwErrors(): Unit = this.validate().throwOnErrors() + } + diff --git a/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/KeyConfig.scala b/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/KeyConfig.scala index ac5f762..38f289f 100644 --- a/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/KeyConfig.scala +++ b/service/src/main/scala/za/co/absa/loginsvc/rest/config/jwt/KeyConfig.scala @@ -17,7 +17,7 @@ package za.co.absa.loginsvc.rest.config.jwt import io.jsonwebtoken.SignatureAlgorithm -import za.co.absa.loginsvc.rest.config.JwtConfig.{minAccessExpTime, minRefreshExpTime} +import org.slf4j.LoggerFactory import za.co.absa.loginsvc.rest.config.validation.{ConfigValidatable, ConfigValidationException, ConfigValidationResult} import za.co.absa.loginsvc.rest.config.validation.ConfigValidationResult.{ConfigValidationError, ConfigValidationSuccess} @@ -29,7 +29,8 @@ import scala.util.{Failure, Success, Try} trait KeyConfig extends ConfigValidatable { def algName: String def accessExpTime: FiniteDuration - def refreshKeyTime: Option[FiniteDuration] + def refreshExpTime: FiniteDuration + def keyRotationTime: Option[FiniteDuration] def keyPair(): KeyPair def throwErrors(): Unit @@ -45,6 +46,8 @@ trait KeyConfig extends ConfigValidatable { }) } + private val logger = LoggerFactory.getLogger(classOf[KeyConfig]) + override def validate(): ConfigValidationResult = { val algValidation = Try { @@ -60,19 +63,24 @@ trait KeyConfig extends ConfigValidatable { ConfigValidationError(ConfigValidationException(s"accessExpTime must be at least ${KeyConfig.minAccessExpTime}")) } else ConfigValidationSuccess - val refreshKeyTimeResult = if (refreshKeyTime.nonEmpty && refreshKeyTime.get < KeyConfig.minRefreshKeyTime) { - ConfigValidationError(ConfigValidationException(s"refreshKeyTime must be at least ${KeyConfig.minRefreshKeyTime}")) + val refreshExpTimeResult = if (refreshExpTime < KeyConfig.minRefreshExpTime) { + ConfigValidationError(ConfigValidationException(s"refreshExpTime must be at least ${KeyConfig.minRefreshExpTime}")) } else ConfigValidationSuccess - val refreshExpTimeResult = if (refreshExpTime < minRefreshExpTime) { - ConfigValidationError(ConfigValidationException(s"refreshExpTime must be at least $minRefreshExpTime")) + val keyRotationTimeResult = if (keyRotationTime.nonEmpty && keyRotationTime.get < KeyConfig.minKeyRotationTime) { + ConfigValidationError(ConfigValidationException(s"keyRotationTime must be at least ${KeyConfig.minKeyRotationTime}")) } else ConfigValidationSuccess - algValidation.merge(accessExpTimeResult).merge(refreshKeyTimeResult).merge(refreshExpTimeResult) + if (keyRotationTime.isEmpty) { + logger.warn("keyRotationTime is not set in config, key-pair will not be rotated!") + } + + algValidation.merge(accessExpTimeResult).merge(refreshExpTimeResult).merge(keyRotationTimeResult) } } object KeyConfig { val minAccessExpTime: FiniteDuration = FiniteDuration(10, TimeUnit.MILLISECONDS) - val minRefreshKeyTime: FiniteDuration = FiniteDuration(10, TimeUnit.MILLISECONDS) + val minRefreshExpTime: FiniteDuration = FiniteDuration(10, TimeUnit.MILLISECONDS) + val minKeyRotationTime: FiniteDuration = FiniteDuration(10, TimeUnit.MILLISECONDS) } diff --git a/service/src/main/scala/za/co/absa/loginsvc/rest/config/provider/ConfigProvider.scala b/service/src/main/scala/za/co/absa/loginsvc/rest/config/provider/ConfigProvider.scala index 4cdfcfe..2c3ac61 100644 --- a/service/src/main/scala/za/co/absa/loginsvc/rest/config/provider/ConfigProvider.scala +++ b/service/src/main/scala/za/co/absa/loginsvc/rest/config/provider/ConfigProvider.scala @@ -47,7 +47,7 @@ class ConfigProvider(@Value("${spring.config.location}") yamlPath: String) getOrElse(BaseConfig("")) } - def getJWTConfig : KeyConfig = { + def getJwtKeyConfig : KeyConfig = { val inMemoryKeyConfig: Option[InMemoryKeyConfig] = createConfigClass[InMemoryKeyConfig]("loginsvc.rest.jwt.generate-in-memory") val awsSecretsManagerKeyConfig: Option[AwsSecretsManagerKeyConfig] = createConfigClass[AwsSecretsManagerKeyConfig]("loginsvc.rest.jwt.aws-secrets-manager") diff --git a/service/src/main/scala/za/co/absa/loginsvc/rest/config/provider/JwtConfigProvider.scala b/service/src/main/scala/za/co/absa/loginsvc/rest/config/provider/JwtConfigProvider.scala index 5933fee..0f3837d 100644 --- a/service/src/main/scala/za/co/absa/loginsvc/rest/config/provider/JwtConfigProvider.scala +++ b/service/src/main/scala/za/co/absa/loginsvc/rest/config/provider/JwtConfigProvider.scala @@ -19,5 +19,5 @@ package za.co.absa.loginsvc.rest.config.provider import za.co.absa.loginsvc.rest.config.jwt.KeyConfig trait JwtConfigProvider { - def getJWTConfig : KeyConfig + def getJwtKeyConfig: KeyConfig } diff --git a/service/src/main/scala/za/co/absa/loginsvc/rest/service/JWTService.scala b/service/src/main/scala/za/co/absa/loginsvc/rest/service/JWTService.scala index 038c212..d3d2e2c 100644 --- a/service/src/main/scala/za/co/absa/loginsvc/rest/service/JWTService.scala +++ b/service/src/main/scala/za/co/absa/loginsvc/rest/service/JWTService.scala @@ -46,16 +46,16 @@ class JWTService @Autowired()(jwtConfigProvider: JwtConfigProvider) { private val logger = LoggerFactory.getLogger(classOf[JWTService]) private val scheduler = Executors.newSingleThreadScheduledExecutor() - private val jwtConfig = jwtConfigProvider.getJWTConfig + private val jwtConfig = jwtConfigProvider.getJwtKeyConfig @volatile private var keyPair: KeyPair = jwtConfig.keyPair() - if(jwtConfig.refreshKeyTime.nonEmpty) + if(jwtConfig.keyRotationTime.nonEmpty) { - val refreshTime = jwtConfig.refreshKeyTime.get + val refreshTime = jwtConfig.keyRotationTime.get scheduleSecretsRefresh(refreshTime) } - def generateToken(user: User): AccessToken = { + def generateAccessToken(user: User): AccessToken = { logger.info(s"Generating Token for user: ${user.name}") import scala.collection.JavaConverters._ @@ -73,8 +73,8 @@ class JWTService @Autowired()(jwtConfigProvider: JwtConfigProvider) { .setIssuedAt(issuedAt) .claim("kid", publicKeyThumbprint) .claim("groups", groupsClaim) - .applyIfDefined(user.email, (builder, value: String) => builder.claim("email", value)) - .applyIfDefined(user.displayName, (builder, value: String) => builder.claim("displayname", value)) + .applyIfDefined(user.email)((builder, value: String) => builder.claim("email", value)) + .applyIfDefined(user.displayName)((builder, value: String) => builder.claim("displayname", value)) .claim("type", Token.TokenType.Access.toString) .signWith(keyPair.getPrivate) .compact() @@ -95,7 +95,7 @@ class JWTService @Autowired()(jwtConfigProvider: JwtConfigProvider) { .setExpiration(expiration) .setIssuedAt(issuedAt) .claim("type", Token.TokenType.Refresh.toString) - .signWith(rsaKeyPair.getPrivate) + .signWith(keyPair.getPrivate) .compact() RefreshToken(tokenContent) @@ -104,7 +104,7 @@ class JWTService @Autowired()(jwtConfigProvider: JwtConfigProvider) { def refreshTokens(accessToken: AccessToken, refreshToken: RefreshToken): (AccessToken, RefreshToken) = { val oldAccessJws: Jws[Claims] = Jwts.parserBuilder() .require("type", Token.TokenType.Access.toString) - .setSigningKey(rsaKeyPair.getPublic) + .setSigningKey(keyPair.getPublic) .setClock(() => Date.from(Instant.now().minus(jwtConfig.refreshExpTime.toJava))) // allowing expired access token - up to refresh token validity window .build() .parseClaimsJws(accessToken.token) // checks requirements: type=access, signature, custom validity window @@ -114,7 +114,7 @@ class JWTService @Autowired()(jwtConfigProvider: JwtConfigProvider) { Jwts.parserBuilder() .require("type", Token.TokenType.Refresh.toString) .requireSubject(userFromOldAccessToken.name) - .setSigningKey(rsaKeyPair.getPublic) + .setSigningKey(keyPair.getPublic) .build() .parseClaimsJws(refreshToken.token) // checks username, validity, and signature. @@ -190,19 +190,12 @@ class JWTService @Autowired()(jwtConfigProvider: JwtConfigProvider) { } object JWTService { - // todo remove? not used? - implicit class JwtBuilderExt(val jwtBuilder: JwtBuilder) extends AnyVal { - def applyIfDefined[T](opt: Option[T], fn: (JwtBuilder, T) => JwtBuilder): JwtBuilder = { - OptionExt.applyIfDefined(jwtBuilder, opt, fn) - } + def extractUserFrom(claims: Claims): User = { + val name = claims.getSubject + val groups = claims.get("groups", classOf[java.util.List[String]]).asScala + val email = Option(claims.get("email", classOf[String])) + val displayName = Option(claims.get("displayname", classOf[String])) - def extractUserFrom(claims: Claims): User = { - val name = claims.getSubject - val groups = claims.get("groups", classOf[java.util.List[String]]).asScala - val email = Option(claims.get("email", classOf[String])) - val displayName = Option(claims.get("displayname", classOf[String])) - - User(name, email, displayName, groups) - } + User(name, email, displayName, groups) } } diff --git a/service/src/test/resources/application.yaml b/service/src/test/resources/application.yaml index 5fb367f..49284cc 100644 --- a/service/src/test/resources/application.yaml +++ b/service/src/test/resources/application.yaml @@ -3,9 +3,9 @@ loginsvc: # Rest General Config jwt: generate-in-memory: - refresh-exp-time: 10h access-exp-time: 15min - rotation-time: 5sec + refresh-exp-time: 10h + key-rotation-time: 5sec alg-name: "RS256" config: some-key: "BETA" diff --git a/service/src/test/scala/za/co/absa/loginsvc/rest/config/JwtConfigTest.scala b/service/src/test/scala/za/co/absa/loginsvc/rest/config/jwt/AwsSecretsManagerKeyConfigTest.scala similarity index 53% rename from service/src/test/scala/za/co/absa/loginsvc/rest/config/JwtConfigTest.scala rename to service/src/test/scala/za/co/absa/loginsvc/rest/config/jwt/AwsSecretsManagerKeyConfigTest.scala index a12c092..e7f3735 100644 --- a/service/src/test/scala/za/co/absa/loginsvc/rest/config/JwtConfigTest.scala +++ b/service/src/test/scala/za/co/absa/loginsvc/rest/config/jwt/AwsSecretsManagerKeyConfigTest.scala @@ -14,25 +14,17 @@ * limitations under the License. */ -package za.co.absa.loginsvc.rest.config +package za.co.absa.loginsvc.rest.config.jwt import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers -import za.co.absa.loginsvc.rest.config.jwt.{AwsSecretsManagerKeyConfig, InMemoryKeyConfig, KeyConfig} import za.co.absa.loginsvc.rest.config.validation.ConfigValidationException import za.co.absa.loginsvc.rest.config.validation.ConfigValidationResult.{ConfigValidationError, ConfigValidationSuccess} -import scala.concurrent.duration._ - import java.util.concurrent.TimeUnit import scala.concurrent.duration.FiniteDuration -class JwtConfigTest extends AnyFlatSpec with Matchers { - - val inMemoryKeyConfig: InMemoryKeyConfig = InMemoryKeyConfig("RS256", - 15.munites, - 2.hours, - Option(30.minutes)) +class AwsSecretsManagerKeyConfigTest extends AnyFlatSpec with Matchers { val awsSecretsManagerKeyConfig: AwsSecretsManagerKeyConfig = AwsSecretsManagerKeyConfig("Secret", "region", @@ -40,49 +32,34 @@ class JwtConfigTest extends AnyFlatSpec with Matchers { "public", "RS256", FiniteDuration(15, TimeUnit.MINUTES), + FiniteDuration(9, TimeUnit.HOURS), Option(FiniteDuration(30, TimeUnit.MINUTES))) - "inMemoryKeyConfig" should "validate expected content" in { - inMemoryKeyConfig.validate() shouldBe ConfigValidationSuccess - } - "awsSecretsManagerKeyConfig" should "validate expected content" in { awsSecretsManagerKeyConfig.validate() shouldBe ConfigValidationSuccess } - "inMemoryKeyConfig" should "fail on invalid algorithm" in { - inMemoryKeyConfig.copy(algName = "ABC").validate() shouldBe - ConfigValidationError(ConfigValidationException("Invalid algName 'ABC' was given.")) - } - - "inMemoryKeyConfig" should "fail on non-negative accessExpTime" in { - inMemoryKeyConfig.copy(accessExpTime = FiniteDuration(5, TimeUnit.MILLISECONDS)).validate() shouldBe - ConfigValidationError(ConfigValidationException(s"accessExpTime must be at least ${KeyConfig.minAccessExpTime}")) - } - - "inMemoryKeyConfig" should "fail on non-negative refreshExpTime" in { - inMemoryKeyConfig.copy(rotationTime = Option(FiniteDuration(5, TimeUnit.MILLISECONDS))).validate() shouldBe - ConfigValidationError(ConfigValidationException(s"refreshKeyTime must be at least ${KeyConfig.minRefreshKeyTime}")) - } - - // todo the third value check? - - "awsSecretsManagerKeyConfig" should "fail on invalid algorithm" in { + it should "fail on invalid algorithm" in { awsSecretsManagerKeyConfig.copy(algName = "ABC").validate() shouldBe ConfigValidationError(ConfigValidationException("Invalid algName 'ABC' was given.")) } - "awsSecretsManagerKeyConfig" should "fail on non-negative accessExpTime" in { + it should "fail on non-negative accessExpTime" in { awsSecretsManagerKeyConfig.copy(accessExpTime = FiniteDuration(5, TimeUnit.MILLISECONDS)).validate() shouldBe ConfigValidationError(ConfigValidationException(s"accessExpTime must be at least ${KeyConfig.minAccessExpTime}")) } - "awsSecretsManagerKeyConfig" should "fail on non-negative refreshExpTime" in { + it should "fail on non-negative refreshExpTime" in { + awsSecretsManagerKeyConfig.copy(refreshExpTime = FiniteDuration(5, TimeUnit.MILLISECONDS)).validate() shouldBe + ConfigValidationError(ConfigValidationException(s"refreshExpTime must be at least ${KeyConfig.minRefreshExpTime}")) + } + + it should "fail on non-negative keyRotationTime" in { awsSecretsManagerKeyConfig.copy(pollTime = Option(FiniteDuration(5, TimeUnit.MILLISECONDS))).validate() shouldBe - ConfigValidationError(ConfigValidationException(s"refreshKeyTime must be at least ${KeyConfig.minRefreshKeyTime}")) + ConfigValidationError(ConfigValidationException(s"keyRotationTime must be at least ${KeyConfig.minKeyRotationTime}")) } - "awsSecretsManagerKeyConfig" should "fail on missing value" in { + it should "fail on missing value" in { awsSecretsManagerKeyConfig.copy(secretName = null).validate() shouldBe ConfigValidationError(ConfigValidationException("secretName is empty")) } diff --git a/service/src/test/scala/za/co/absa/loginsvc/rest/config/jwt/InMemoryKeyConfigTest.scala b/service/src/test/scala/za/co/absa/loginsvc/rest/config/jwt/InMemoryKeyConfigTest.scala new file mode 100644 index 0000000..e275fc2 --- /dev/null +++ b/service/src/test/scala/za/co/absa/loginsvc/rest/config/jwt/InMemoryKeyConfigTest.scala @@ -0,0 +1,57 @@ +/* + * Copyright 2023 ABSA Group Limited + * + * 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 + * + * http://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 za.co.absa.loginsvc.rest.config.jwt + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import za.co.absa.loginsvc.rest.config.validation.ConfigValidationException +import za.co.absa.loginsvc.rest.config.validation.ConfigValidationResult.{ConfigValidationError, ConfigValidationSuccess} + +import java.util.concurrent.TimeUnit +import scala.concurrent.duration._ + +class InMemoryKeyConfigTest extends AnyFlatSpec with Matchers { + + val inMemoryKeyConfig: InMemoryKeyConfig = InMemoryKeyConfig("RS256", + 15.minutes, + 2.hours, + Option(30.minutes)) + + "inMemoryKeyConfig" should "validate expected content" in { + inMemoryKeyConfig.validate() shouldBe ConfigValidationSuccess + } + + it should "fail on invalid algorithm" in { + inMemoryKeyConfig.copy(algName = "ABC").validate() shouldBe + ConfigValidationError(ConfigValidationException("Invalid algName 'ABC' was given.")) + } + + it should "fail on non-negative accessExpTime" in { + inMemoryKeyConfig.copy(accessExpTime = FiniteDuration(5, TimeUnit.MILLISECONDS)).validate() shouldBe + ConfigValidationError(ConfigValidationException(s"accessExpTime must be at least ${KeyConfig.minAccessExpTime}")) + } + + it should "fail on non-negative refreshExpTime" in { + inMemoryKeyConfig.copy(refreshExpTime = FiniteDuration(5, TimeUnit.MILLISECONDS)).validate() shouldBe + ConfigValidationError(ConfigValidationException(s"refreshExpTime must be at least ${KeyConfig.minRefreshExpTime}")) + } + + it should "fail on non-negative keyRotationTime" in { + inMemoryKeyConfig.copy(keyRotationTime = Option(FiniteDuration(5, TimeUnit.MILLISECONDS))).validate() shouldBe + ConfigValidationError(ConfigValidationException(s"keyRotationTime must be at least ${KeyConfig.minKeyRotationTime}")) + } +} diff --git a/service/src/test/scala/za/co/absa/loginsvc/rest/config/provider/ConfigProviderTest.scala b/service/src/test/scala/za/co/absa/loginsvc/rest/config/provider/ConfigProviderTest.scala index ed45cd8..3f0aceb 100644 --- a/service/src/test/scala/za/co/absa/loginsvc/rest/config/provider/ConfigProviderTest.scala +++ b/service/src/test/scala/za/co/absa/loginsvc/rest/config/provider/ConfigProviderTest.scala @@ -38,11 +38,11 @@ class ConfigProviderTest extends AnyFlatSpec with Matchers { } "The jwtConfig properties" should "Match" in { - val keyConfig: KeyConfig = configProvider.getJWTConfig + val keyConfig: KeyConfig = configProvider.getJwtKeyConfig assert(keyConfig.algName == "RS256" && keyConfig.accessExpTime == FiniteDuration(15, TimeUnit.MINUTES) && - keyConfig.refreshKeyTime.get == FiniteDuration(5, TimeUnit.SECONDS) && - jwtConfig.refreshExpTime == FiniteDuration(10, TimeUnit.HOURS) + keyConfig.refreshExpTime == FiniteDuration(10, TimeUnit.HOURS) && + keyConfig.keyRotationTime.get == FiniteDuration(5, TimeUnit.SECONDS) ) } diff --git a/service/src/test/scala/za/co/absa/loginsvc/rest/service/JWTServiceTest.scala b/service/src/test/scala/za/co/absa/loginsvc/rest/service/JWTServiceTest.scala index 298fed6..bc3471d 100644 --- a/service/src/test/scala/za/co/absa/loginsvc/rest/service/JWTServiceTest.scala +++ b/service/src/test/scala/za/co/absa/loginsvc/rest/service/JWTServiceTest.scala @@ -23,9 +23,9 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.BeforeAndAfterEach import org.scalatest.matchers.should.Matchers import za.co.absa.loginsvc.model.User -import za.co.absa.loginsvc.rest.config.JwtConfig +import za.co.absa.loginsvc.rest.config.jwt.{InMemoryKeyConfig, KeyConfig} import za.co.absa.loginsvc.rest.config.provider.{ConfigProvider, JwtConfigProvider} -import za.co.absa.loginsvc.rest.model.{AccessToken, Token, RefreshToken} +import za.co.absa.loginsvc.rest.model.{AccessToken, RefreshToken, Token} import java.security.PublicKey import java.util @@ -202,8 +202,8 @@ class JWTServiceTest extends AnyFlatSpec with BeforeAndAfterEach with Matchers { def customTimedJwtService(accessExpTime: FiniteDuration, refreshExpTime: FiniteDuration): JWTService = { val configP = new JwtConfigProvider { - override def getJWTConfig: JwtConfig = JwtConfig( - "RS256", accessExpTime, refreshExpTime + override def getJwtKeyConfig: KeyConfig = InMemoryKeyConfig( + "RS256", accessExpTime, refreshExpTime, None ) } @@ -278,11 +278,11 @@ class JWTServiceTest extends AnyFlatSpec with BeforeAndAfterEach with Matchers { } it should "rotate an public and private keys after 5 seconds" in { - val initToken = jwtService.generateToken(userWithoutGroups) + val initToken = jwtService.generateAccessToken(userWithoutGroups) val initPublicKey = jwtService.publicKey Thread.sleep(6 * 1000) - val refreshedToken = jwtService.generateToken(userWithoutGroups) + val refreshedToken = jwtService.generateAccessToken(userWithoutGroups) assert(parseJWT(initToken).isFailure) assert(parseJWT(refreshedToken).isSuccess)