diff --git a/.gitignore b/.gitignore index 8990c20..c37ad21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,17 @@ -# Eclipse project files -.project -.classpath -.settings - -# IntelliJ IDEA project files and directories -*.iml -*.ipr -*.iws -.idea/ - -# Geany project file -.geany - -# KDevelop project file and directory -.kdev4/ -*.kdev4 +# everything that starts with dot (hidden files) +.* +# except this file +!.gitignore +# except this file-extention +!.*.yml # Build targets -/target -*/target - -# Report directories -/reports -*/reports +**/target/ -# Mac-specific directory that no other operating system needs. -.DS_Store +# logs and reports +*.csv +*.log +*.zip -# JVM crash logs -hs_err_pid*.log \ No newline at end of file +# IntelliJ IDEA project files and directories +*.iml diff --git a/.travis.yml b/.travis.yml index 7a3a647..a138ba9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ jdk: openjdk8 before_install: - "./src/main/scripts/ci/before-install.sh" - "./src/main/scripts/cd/before-deploy.sh" +script: "mvn verify -B" after_success: "./src/main/scripts/ci/after-success.sh" deploy: - provider: script diff --git a/jwt/src/main/java/io/scalecube/security/jwt/DefaultJwtAuthenticator.java b/jwt/src/main/java/io/scalecube/security/jwt/DefaultJwtAuthenticator.java index 57e21c8..a49dc57 100644 --- a/jwt/src/main/java/io/scalecube/security/jwt/DefaultJwtAuthenticator.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/DefaultJwtAuthenticator.java @@ -1,26 +1,37 @@ package io.scalecube.security.jwt; import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwt; import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; import io.scalecube.security.api.Profile; +import java.util.Map; import reactor.core.publisher.Mono; public final class DefaultJwtAuthenticator implements JwtAuthenticator { - private final JwtParser jwtParser; + private final JwtKeyResolver jwtKeyResolver; public DefaultJwtAuthenticator(JwtKeyResolver jwtKeyResolver) { - jwtParser = Jwts.parser().setSigningKeyResolver(new DefaultSigningKeyResolver(jwtKeyResolver)); + this.jwtKeyResolver = jwtKeyResolver; } @Override public Mono authenticate(String token) { - return Mono.just(token) - .map(jwtParser::parseClaimsJws) - .map(Jws::getBody) - .map(this::profileFromClaims) - .onErrorMap(AuthenticationException::new); + return Mono.defer( + () -> { + String tokenWithoutSignature = token.substring(0, token.lastIndexOf(".") + 1); + + JwtParser parser = Jwts.parser(); + + Jwt claims = parser.parseClaimsJwt(tokenWithoutSignature); + + return jwtKeyResolver + .resolve((Map) claims.getHeader()) + .map(key -> parser.setSigningKey(key).parseClaimsJws(token).getBody()) + .map(this::profileFromClaims) + .onErrorMap(AuthenticationException::new); + }); } } diff --git a/jwt/src/main/java/io/scalecube/security/jwt/DefaultSigningKeyResolver.java b/jwt/src/main/java/io/scalecube/security/jwt/DefaultSigningKeyResolver.java deleted file mode 100644 index 72f345f..0000000 --- a/jwt/src/main/java/io/scalecube/security/jwt/DefaultSigningKeyResolver.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.scalecube.security.jwt; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.JwsHeader; -import io.jsonwebtoken.SigningKeyResolver; -import java.security.Key; -import java.util.HashMap; -import java.util.Map; - -final class DefaultSigningKeyResolver implements SigningKeyResolver { - - private JwtKeyResolver keyResolver; - - DefaultSigningKeyResolver(JwtKeyResolver keyResolver) { - if (keyResolver == null) { - throw new IllegalArgumentException("keyResolver have to be not null"); - } - - this.keyResolver = keyResolver; - } - - @Override - public Key resolveSigningKey(JwsHeader header, Claims claims) { - Map tokenClaims = new HashMap<>(); - tokenClaims.putAll(header); - tokenClaims.putAll(claims); - - return keyResolver.resolve(tokenClaims); - } - - @Override - public Key resolveSigningKey(JwsHeader jwsHeader, String s) { - throw new UnsupportedOperationException("Only JSON tokens are supported"); - } -} diff --git a/jwt/src/main/java/io/scalecube/security/jwt/JwtKeyResolver.java b/jwt/src/main/java/io/scalecube/security/jwt/JwtKeyResolver.java index 43ad04e..216d200 100644 --- a/jwt/src/main/java/io/scalecube/security/jwt/JwtKeyResolver.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwtKeyResolver.java @@ -2,9 +2,10 @@ import java.security.Key; import java.util.Map; +import reactor.core.publisher.Mono; @FunctionalInterface public interface JwtKeyResolver { - Key resolve(Map tokenClaims); + Mono resolve(Map jtwHeaders); } diff --git a/jwt/src/test/java/io/scalecube/security/JwtAuthenticatorTests.java b/jwt/src/test/java/io/scalecube/security/JwtAuthenticatorTests.java index dda5297..4706542 100644 --- a/jwt/src/test/java/io/scalecube/security/JwtAuthenticatorTests.java +++ b/jwt/src/test/java/io/scalecube/security/JwtAuthenticatorTests.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; + import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -22,6 +23,7 @@ import javax.crypto.spec.SecretKeySpec; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; import reactor.test.StepVerifier; class JwtAuthenticatorTests { @@ -46,7 +48,7 @@ void authenticateAuthenticateUsingKidHeaderPropertyAuthenticationSuccess() { .signWith(hmacSecretKey) .compact(); - JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> hmacSecretKey); + JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> Mono.just(hmacSecretKey)); StepVerifier.create(sut.authenticate(token)) .assertNext( @@ -71,7 +73,7 @@ void authenticateCreateTokenAndAuthenticateHmacAuthenticationSuccess() { .signWith(hmacSecretKey) .compact(); - JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> hmacSecretKey); + JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> Mono.just(hmacSecretKey)); StepVerifier.create(sut.authenticate(token)) .assertNext( @@ -100,9 +102,10 @@ void authenticateValidTokenInvalidHmacSecretAuthenticationFailedExceptionThrown( JwtAuthenticator sut = new DefaultJwtAuthenticator( map -> - new SecretKeySpec( - UUID.randomUUID().toString().getBytes(), - SignatureAlgorithm.HS256.getJcaName())); + Mono.just( + new SecretKeySpec( + UUID.randomUUID().toString().getBytes(), + SignatureAlgorithm.HS256.getJcaName()))); StepVerifier.create(sut.authenticate(token)) .expectErrorSatisfies( actualException -> @@ -128,12 +131,8 @@ void authenticateUsingKidHeaderPropertyKidIsMissingAuthenticationFailsExceptionT map -> Optional.ofNullable(map.get("kid")) .filter(String.class::isInstance) - .map( - s -> { - // Safe to cast to string, use the kid property to fetch the key - return hmacSecretKey; - }) - .orElse(null)); + .map(s -> Mono.just(hmacSecretKey)) + .orElse(Mono.empty())); StepVerifier.create(sut.authenticate(token)) .expectErrorSatisfies( @@ -156,7 +155,7 @@ void authenticateCreateTokenAndValidateRsaAuthenticationSuccess() { .signWith(keys.getPrivate()) .compact(); - JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> keys.getPublic()); + JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> Mono.just(keys.getPublic())); StepVerifier.create(sut.authenticate(token)) .assertNext( @@ -184,12 +183,8 @@ void authenticateCreateTokenAndValidateWrongKeyForAlgorithmAuthenticationFailsEx map -> Optional.ofNullable(map.get("kid")) .filter(String.class::isInstance) - .map( - s -> { - // Safe to cast to string, use the kid property to fetch the key - return hmacSecretKey; - }) - .orElse(null)); + .map(s -> Mono.just(hmacSecretKey)) + .orElse(Mono.empty())); StepVerifier.create(sut.authenticate(token)) .expectErrorSatisfies( @@ -208,7 +203,7 @@ void authenticateMissingClaimsInTokenAuthenticationSuccessProfilePropertyIsMissi .signWith(keys.getPrivate()) .compact(); - JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> keys.getPublic()); + JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> Mono.just(keys.getPublic())); StepVerifier.create(sut.authenticate(token)) .assertNext( @@ -229,12 +224,8 @@ void authenticateUnsignedTokenAuthenticationFailsExceptionThrown() { map -> Optional.ofNullable(map.get("kid")) .filter(String.class::isInstance) - .map( - s -> { - // Safe to cast to string, use the kid property to fetch the key - return hmacSecretKey; - }) - .orElse(null)); + .map(s -> Mono.just(hmacSecretKey)) + .orElse(Mono.empty())); StepVerifier.create(sut.authenticate(token)) .expectErrorSatisfies( @@ -252,7 +243,7 @@ void authenticateKeyResolverReturnNullsAuthenticationFailsExceptionThrown() { .signWith(keys.getPrivate()) .compact(); - JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> null); + JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> Mono.empty()); StepVerifier.create(sut.authenticate(token)) .expectErrorSatisfies( actualException -> @@ -276,7 +267,7 @@ void authenticateAuthenticateExpiredTokenFails() { .signWith(hmacSecretKey) .compact(); - JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> null); + JwtAuthenticator sut = new DefaultJwtAuthenticator(map -> Mono.empty()); StepVerifier.create(sut.authenticate(token)) .expectErrorSatisfies( actualException -> diff --git a/jwt/src/test/java/io/scalecube/security/acl/AccessControlTest.java b/jwt/src/test/java/io/scalecube/security/acl/AccessControlTest.java index f9fa0e3..f16b079 100644 --- a/jwt/src/test/java/io/scalecube/security/acl/AccessControlTest.java +++ b/jwt/src/test/java/io/scalecube/security/acl/AccessControlTest.java @@ -1,6 +1,7 @@ package io.scalecube.security.acl; import static org.junit.jupiter.api.Assertions.assertEquals; + import io.jsonwebtoken.Jwts; import io.scalecube.security.api.Authenticator; import io.scalecube.security.jwt.DefaultJwtAuthenticator; @@ -10,30 +11,32 @@ import javax.crypto.SecretKey; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -public class AccessControlTest { +class AccessControlTest { - // user permissions + // user permissions private static final String RESOURCE_READ = "resource/read"; private static final String RESOURCE_CREATE = "resource/create"; private static final String RESOURCE_DELETE = "resource/delete"; - + // user roles private static final String OWNER = "owner"; private static final String ADMIN = "admin"; private static final String MEMBER = "member"; - + private static SecretKey key; - private static DefaultAccessControl accessContorl; + private static DefaultAccessControl accessControl; @BeforeAll - public static void setUp() throws Exception { + static void setUp() throws Exception { key = KeyGenerator.getInstance("HmacSHA256").generateKey(); + Authenticator authenticator = - new DefaultJwtAuthenticator(m -> "1".equals(m.get("kid")) ? key : null); + new DefaultJwtAuthenticator(m -> "1".equals(m.get("kid")) ? Mono.just(key) : Mono.empty()); - accessContorl = + accessControl = DefaultAccessControl.builder() .authenticator(authenticator) .authorizer( @@ -46,16 +49,12 @@ public static void setUp() throws Exception { } @Test - public void shouldGrantAccess() throws NoSuchAlgorithmException { + void shouldGrantAccess() throws NoSuchAlgorithmException { String token = - Jwts.builder() - .setHeaderParam("kid", "1") - .claim("roles", OWNER) - .signWith(key) - .compact(); + Jwts.builder().setHeaderParam("kid", "1").claim("roles", OWNER).signWith(key).compact(); - StepVerifier.create(accessContorl.check(token, RESOURCE_CREATE)) + StepVerifier.create(accessControl.check(token, RESOURCE_CREATE)) .assertNext( profile -> { assertEquals(profile.claim("roles"), OWNER); @@ -64,16 +63,12 @@ public void shouldGrantAccess() throws NoSuchAlgorithmException { } @Test - public void shouldDenyAccess() throws NoSuchAlgorithmException { + void shouldDenyAccess() throws NoSuchAlgorithmException { String token = - Jwts.builder() - .setHeaderParam("kid", "1") - .claim("roles", MEMBER) - .signWith(key) - .compact(); + Jwts.builder().setHeaderParam("kid", "1").claim("roles", MEMBER).signWith(key).compact(); - StepVerifier.create(accessContorl.check(token, RESOURCE_DELETE)) + StepVerifier.create(accessControl.check(token, RESOURCE_DELETE)) .expectError(AccessControlException.class) .verify(); }