diff --git a/modules/authentication/portal-authentication-token/src/main/java/de/cuioss/portal/authentication/token/ParsedToken.java b/modules/authentication/portal-authentication-token/src/main/java/de/cuioss/portal/authentication/token/ParsedToken.java index fb3d72fc..3304c03e 100644 --- a/modules/authentication/portal-authentication-token/src/main/java/de/cuioss/portal/authentication/token/ParsedToken.java +++ b/modules/authentication/portal-authentication-token/src/main/java/de/cuioss/portal/authentication/token/ParsedToken.java @@ -133,4 +133,18 @@ public OffsetDateTime getExpirationTime() { return OffsetDateTime .ofInstant(Instant.ofEpochSecond(jsonWebToken.getExpirationTime()), ZoneId.systemDefault()); } + + /** + * @return the subject from the underlying JWT token + */ + public String getSubject() { + return jsonWebToken.getSubject(); + } + + /** + * @return the issuer from the underlying JWT token + */ + public String getIssuer() { + return jsonWebToken.getIssuer(); + } } diff --git a/modules/authentication/portal-authentication-token/src/main/java/de/cuioss/portal/authentication/token/TokenFactory.java b/modules/authentication/portal-authentication-token/src/main/java/de/cuioss/portal/authentication/token/TokenFactory.java new file mode 100644 index 00000000..910d52ad --- /dev/null +++ b/modules/authentication/portal-authentication-token/src/main/java/de/cuioss/portal/authentication/token/TokenFactory.java @@ -0,0 +1,59 @@ +package de.cuioss.portal.authentication.token; + +import io.smallrye.jwt.auth.principal.JWTParser; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * Factory for creating different types of tokens. + */ +@ApplicationScoped +@RequiredArgsConstructor(access = AccessLevel.PRIVATE, onConstructor_ = @Inject) +public class TokenFactory { + + private final JWTParser tokenParser; + + /** + * Creates a new token factory using the given parser. + * + * @param tokenParser The parser to use for token validation, must not be null + * @return A new TokenFactory instance + */ + public static TokenFactory of(@NonNull JWTParser tokenParser) { + return new TokenFactory(tokenParser); + } + + /** + * Creates an access token from the given token string. + * + * @param tokenString The token string to parse, must not be null + * @return The parsed access token + */ + public ParsedAccessToken createAccessToken(@NonNull String tokenString) { + return ParsedAccessToken.fromTokenString(tokenString, tokenParser); + } + + /** + * Creates an ID token from the given token string. + * + * @param tokenString The token string to parse, must not be null + * @return The parsed ID token + */ + public ParsedIdToken createIdToken(@NonNull String tokenString) { + return ParsedIdToken.fromTokenString(tokenString, tokenParser); + } + + /** + * Creates a refresh token from the given token string. + * + * @param tokenString The token string to parse, must not be null + * @return The parsed refresh token + */ + public ParsedRefreshToken createRefreshToken(@NonNull String tokenString) { + return ParsedRefreshToken.fromTokenString(tokenString); + } + +} diff --git a/modules/authentication/portal-authentication-token/src/test/java/de/cuioss/portal/authentication/token/ParsedTokenTest.java b/modules/authentication/portal-authentication-token/src/test/java/de/cuioss/portal/authentication/token/ParsedTokenTest.java index 0632882e..faebc4db 100644 --- a/modules/authentication/portal-authentication-token/src/test/java/de/cuioss/portal/authentication/token/ParsedTokenTest.java +++ b/modules/authentication/portal-authentication-token/src/test/java/de/cuioss/portal/authentication/token/ParsedTokenTest.java @@ -20,7 +20,6 @@ import de.cuioss.test.juli.TestLogLevel; import de.cuioss.test.juli.junit5.EnableTestLogger; import de.cuioss.tools.logging.CuiLogger; -import io.smallrye.jwt.auth.principal.DefaultJWTParser; import org.eclipse.microprofile.jwt.JsonWebToken; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -28,7 +27,9 @@ import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; -import static de.cuioss.portal.authentication.token.TestTokenProducer.*; +import static de.cuioss.portal.authentication.token.TestTokenProducer.DEFAULT_TOKEN_PARSER; +import static de.cuioss.portal.authentication.token.TestTokenProducer.SOME_SCOPES; +import static de.cuioss.portal.authentication.token.TestTokenProducer.validSignedJWTWithClaims; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -41,20 +42,24 @@ class ParsedTokenTest { @NullAndEmptySource @ValueSource(strings = " ") void shouldProvideEmptyFallbackOnEmptyInput(String initialTokenString) { - JsonWebToken jsonWebToken = ParsedToken.jsonWebTokenFrom(initialTokenString, TestTokenProducer.DEFAULT_TOKEN_PARSER, LOGGER); + JsonWebToken jsonWebToken = ParsedToken.jsonWebTokenFrom(initialTokenString, + TestTokenProducer.DEFAULT_TOKEN_PARSER, LOGGER); Assertions.assertEquals(ParsedToken.EMPTY_WEB_TOKEN, jsonWebToken); - LogAsserts.assertSingleLogMessagePresentContaining(TestLogLevel.WARN, LogMessages.TOKEN_IS_EMPTY.resolveIdentifierString()); + LogAsserts.assertSingleLogMessagePresentContaining(TestLogLevel.WARN, + LogMessages.TOKEN_IS_EMPTY.resolveIdentifierString()); } @Test void shouldProvideEmptyFallbackOnParseError() { String initialTokenString = Generators.letterStrings(10, 20).next(); - JsonWebToken jsonWebToken = ParsedToken.jsonWebTokenFrom(initialTokenString, TestTokenProducer.DEFAULT_TOKEN_PARSER, LOGGER); + JsonWebToken jsonWebToken = ParsedToken.jsonWebTokenFrom(initialTokenString, + TestTokenProducer.DEFAULT_TOKEN_PARSER, LOGGER); Assertions.assertEquals(ParsedToken.EMPTY_WEB_TOKEN, jsonWebToken); - LogAsserts.assertSingleLogMessagePresentContaining(TestLogLevel.WARN, LogMessages.COULD_NOT_PARSE_TOKEN.resolveIdentifierString()); + LogAsserts.assertSingleLogMessagePresentContaining(TestLogLevel.WARN, + LogMessages.COULD_NOT_PARSE_TOKEN.resolveIdentifierString()); } @Test @@ -62,10 +67,11 @@ void shouldProvideEmptyFallbackOnInvalidIssuer() { String initialTokenString = TestTokenProducer.validSignedJWTWithClaims(TestTokenProducer.SOME_SCOPES); JsonWebToken jsonWebToken = ParsedToken - .jsonWebTokenFrom(initialTokenString, new DefaultJWTParser(TestTokenProducer.TEST_AUTH_CONTEXT_INFO_WRONG_ISSUER), LOGGER); + .jsonWebTokenFrom(initialTokenString, TestTokenProducer.WRONG_ISSUER_TOKEN_PARSER, LOGGER); Assertions.assertEquals(ParsedToken.EMPTY_WEB_TOKEN, jsonWebToken); - LogAsserts.assertSingleLogMessagePresentContaining(TestLogLevel.WARN, LogMessages.COULD_NOT_PARSE_TOKEN.resolveIdentifierString()); + LogAsserts.assertSingleLogMessagePresentContaining(TestLogLevel.WARN, + LogMessages.COULD_NOT_PARSE_TOKEN.resolveIdentifierString()); } @Test @@ -73,10 +79,11 @@ void shouldProvideEmptyFallbackOnWrongPublicKey() { String initialTokenString = TestTokenProducer.validSignedJWTWithClaims(TestTokenProducer.SOME_SCOPES); JsonWebToken jsonWebToken = ParsedToken - .jsonWebTokenFrom(initialTokenString, new DefaultJWTParser(TestTokenProducer.TEST_AUTH_CONTEXT_INFO_WRONG_PUBLIC_KEY), - LOGGER); + .jsonWebTokenFrom(initialTokenString, + TestTokenProducer.WRONG_SIGNATURE_TOKEN_PARSER, LOGGER); Assertions.assertEquals(ParsedToken.EMPTY_WEB_TOKEN, jsonWebToken); - LogAsserts.assertSingleLogMessagePresentContaining(TestLogLevel.WARN, LogMessages.COULD_NOT_PARSE_TOKEN.resolveIdentifierString()); + LogAsserts.assertSingleLogMessagePresentContaining(TestLogLevel.WARN, + LogMessages.COULD_NOT_PARSE_TOKEN.resolveIdentifierString()); } @Test diff --git a/modules/authentication/portal-authentication-token/src/test/java/de/cuioss/portal/authentication/token/TestTokenProducer.java b/modules/authentication/portal-authentication-token/src/test/java/de/cuioss/portal/authentication/token/TestTokenProducer.java index 2afad381..13f29648 100644 --- a/modules/authentication/portal-authentication-token/src/test/java/de/cuioss/portal/authentication/token/TestTokenProducer.java +++ b/modules/authentication/portal-authentication-token/src/test/java/de/cuioss/portal/authentication/token/TestTokenProducer.java @@ -52,17 +52,21 @@ public class TestTokenProducer { public static final String SOME_ID_TOKEN = BASE_PATH + "some-id-token.json"; - public static final JWTAuthContextInfo TEST_AUTH_CONTEXT_INFO = new JWTAuthContextInfo(PUBLIC_KEY, ISSUER); - public static final JWTAuthContextInfo TEST_AUTH_CONTEXT_INFO_WRONG_PUBLIC_KEY = - new JWTAuthContextInfo(PUBLIC_KEY_OTHER, ISSUER); + public static final JWTAuthContextInfo TEST_AUTH_CONTEXT_INFO_WRONG_PUBLIC_KEY = new JWTAuthContextInfo( + PUBLIC_KEY_OTHER, ISSUER); - public static final JWTAuthContextInfo TEST_AUTH_CONTEXT_INFO_WRONG_ISSUER = - new JWTAuthContextInfo(PUBLIC_KEY, new StringBuilder(ISSUER).reverse().toString()); + public static final JWTAuthContextInfo TEST_AUTH_CONTEXT_INFO_WRONG_ISSUER = new JWTAuthContextInfo(PUBLIC_KEY, + new StringBuilder(ISSUER).reverse().toString()); public static final JWTParser DEFAULT_TOKEN_PARSER = new DefaultJWTParser(TEST_AUTH_CONTEXT_INFO); + public static final JWTParser WRONG_ISSUER_TOKEN_PARSER = new DefaultJWTParser(TEST_AUTH_CONTEXT_INFO_WRONG_ISSUER); + + public static final JWTParser WRONG_SIGNATURE_TOKEN_PARSER = new DefaultJWTParser( + TEST_AUTH_CONTEXT_INFO_WRONG_PUBLIC_KEY); + public static final String SUBJECT = Generators.letterStrings(10, 12).next(); public static String validSignedJWTWithClaims(String claims) { diff --git a/modules/authentication/portal-authentication-token/src/test/java/de/cuioss/portal/authentication/token/TokenFactoryTest.java b/modules/authentication/portal-authentication-token/src/test/java/de/cuioss/portal/authentication/token/TokenFactoryTest.java new file mode 100644 index 00000000..a5a2f8f8 --- /dev/null +++ b/modules/authentication/portal-authentication-token/src/test/java/de/cuioss/portal/authentication/token/TokenFactoryTest.java @@ -0,0 +1,78 @@ +package de.cuioss.portal.authentication.token; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TokenFactoryTest { + + private TokenFactory tokenFactory; + + @BeforeEach + void setUp() { + tokenFactory = TokenFactory.of(TestTokenProducer.DEFAULT_TOKEN_PARSER); + } + + @Test + void shouldCreateAccessToken() { + var token = TestTokenProducer.validSignedJWTWithClaims(TestTokenProducer.SOME_SCOPES); + var parsedToken = tokenFactory.createAccessToken(token); + + assertNotNull(parsedToken); + assertFalse(parsedToken.getScopes().isEmpty()); + assertNotNull(parsedToken.getSubject()); + assertNotNull(parsedToken.getIssuer()); + } + + @Test + void shouldCreateIdToken() { + var token = TestTokenProducer.validSignedJWTWithClaims(TestTokenProducer.SOME_ID_TOKEN); + var parsedToken = tokenFactory.createIdToken(token); + + assertNotNull(parsedToken); + assertNotNull(parsedToken.getSubject()); + assertNotNull(parsedToken.getIssuer()); + } + + @Test + void shouldCreateRefreshToken() { + var token = TestTokenProducer.validSignedJWTWithClaims(TestTokenProducer.REFRESH_TOKEN); + var parsedToken = tokenFactory.createRefreshToken(token); + + assertNotNull(parsedToken); + } + + @Test + void shouldHandleExpiredToken() { + var expiredToken = TestTokenProducer.validSignedJWTExpireAt( + Instant.now().minus(1, ChronoUnit.HOURS)); + var token = tokenFactory.createAccessToken(expiredToken); + + assertNotNull(token); + assertTrue(token.isEmpty()); + } + + @Test + void shouldHandleInvalidIssuer() { + var wrongIssuerTokenFactory = TokenFactory.of(TestTokenProducer.WRONG_ISSUER_TOKEN_PARSER); + var token = TestTokenProducer.validSignedJWTWithClaims(TestTokenProducer.SOME_SCOPES); + var parsedToken = wrongIssuerTokenFactory.createAccessToken(token); + assertNotNull(parsedToken); + assertTrue(parsedToken.isEmpty()); + } + + @Test + void shouldHandleInvalidSignature() { + var wrongSignatureTokenFactory = TokenFactory.of(TestTokenProducer.WRONG_SIGNATURE_TOKEN_PARSER); + var token = TestTokenProducer.validSignedJWTWithClaims(TestTokenProducer.SOME_SCOPES); + var parsedToken = wrongSignatureTokenFactory.createAccessToken(token); + assertNotNull(parsedToken); + assertTrue(parsedToken.isEmpty()); + } +}