This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 452414b4486de0e0a6ce28da388ec560e4c428ae Author: LanKhuat <[email protected]> AuthorDate: Tue Jul 28 14:23:11 2020 +0700 JAMES-3351 JWT authentication strategy --- .../apache/james/jmap/JMAPAuthenticationTest.java | 36 +++++++++--------- .../jmap/http/JWTAuthenticationStrategyTest.java | 44 ++++++++++++++-------- .../james/jmap/jwt/JWTAuthenticationStrategy.java | 36 ++++++++---------- 3 files changed, 61 insertions(+), 55 deletions(-) diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/JMAPAuthenticationTest.java b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/JMAPAuthenticationTest.java index 184bc13..280d1b6 100644 --- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/JMAPAuthenticationTest.java +++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/JMAPAuthenticationTest.java @@ -192,7 +192,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void methodShouldContainPasswordWhenValidResquest() throws Exception { + public void methodShouldContainPasswordWhenValidRequest() { given() .contentType(ContentType.JSON) .accept(ContentType.JSON) @@ -206,7 +206,7 @@ public abstract class JMAPAuthenticationTest { @Category(BasicFeature.class) @Test - public void mustReturnContinuationTokenWhenValidResquest() throws Exception { + public void mustReturnContinuationTokenWhenValidRequest() { given() .contentType(ContentType.JSON) .accept(ContentType.JSON) @@ -220,7 +220,7 @@ public abstract class JMAPAuthenticationTest { @Category(BasicFeature.class) @Test - public void mustReturnAuthenticationFailedWhenBadPassword() throws Exception { + public void mustReturnAuthenticationFailedWhenBadPassword() { String continuationToken = fromGoodContinuationTokenRequest(); given() @@ -248,7 +248,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void mustReturnRestartAuthenticationWhenContinuationTokenIsExpired() throws Exception { + public void mustReturnRestartAuthenticationWhenContinuationTokenIsExpired() { String continuationToken = fromGoodContinuationTokenRequest(); zonedDateTimeProvider.setFixedDateTime(afterExpirationDate); @@ -263,7 +263,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void mustReturnAuthenticationFailedWhenUsersRepositoryException() throws Exception { + public void mustReturnAuthenticationFailedWhenUsersRepositoryException() { String continuationToken = fromGoodContinuationTokenRequest(); given() @@ -277,7 +277,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void mustReturnCreatedWhenGoodPassword() throws Exception { + public void mustReturnCreatedWhenGoodPassword() { String continuationToken = fromGoodContinuationTokenRequest(); zonedDateTimeProvider.setFixedDateTime(newDate); @@ -293,7 +293,7 @@ public abstract class JMAPAuthenticationTest { @Category(BasicFeature.class) @Test - public void mustSendJsonContainingAccessTokenAndEndpointsWhenGoodPassword() throws Exception { + public void mustSendJsonContainingAccessTokenAndEndpointsWhenGoodPassword() { String continuationToken = fromGoodContinuationTokenRequest(); zonedDateTimeProvider.setFixedDateTime(newDate); @@ -312,7 +312,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void getMustReturnUnauthorizedWithoutAuthorizationHeader() throws Exception { + public void getMustReturnUnauthorizedWithoutAuthorizationHeader() { given() .when() .get("/authentication") @@ -321,7 +321,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void getMustReturnUnauthorizedWithoutAValidAuthorizationHeader() throws Exception { + public void getMustReturnUnauthorizedWithoutAValidAuthorizationHeader() { given() .header("Authorization", UUID.randomUUID()) .when() @@ -332,7 +332,7 @@ public abstract class JMAPAuthenticationTest { @Category(BasicFeature.class) @Test - public void getMustReturnEndpointsWhenValidAuthorizationHeader() throws Exception { + public void getMustReturnEndpointsWhenValidAuthorizationHeader() { String continuationToken = fromGoodContinuationTokenRequest(); String token = fromGoodAccessTokenRequest(continuationToken); @@ -350,7 +350,7 @@ public abstract class JMAPAuthenticationTest { @Category(BasicFeature.class) @Test - public void getMustReturnEndpointsWhenValidJwtAuthorizationHeader() throws Exception { + public void getMustReturnEndpointsWhenValidJwtAuthorizationHeader() { String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyQGRvbWFpbi50bGQifQ.U-dUPv6OU6KO5N7CooHUfMkCd" + "FJHx2F3H4fm7Q79g1BPfBSkifPj5xyVlZ0JwEGXypC4zBw9ay3l4DxzX7D_6p1Hx_ihXsoLx1Ca-WUo44x-XRSpPfgxiZjHCJkGBLMV3RZlA" + "jip-d18mxkcX3JGplX_sCQkFisduAOAHuKSUg9wI6VBgUQi_0B35FYv6tP_bD6eFtvaAUN9QyXXh8UQjEp8CO12lRz6enfLx_V6BG_fEMkee" + @@ -365,7 +365,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void getMustReturnEndpointsWhenValidUnkwnonUserJwtAuthorizationHeader() throws Exception { + public void getMustReturnEndpointsWhenValidUnknownUserJwtAuthorizationHeader() { String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1bmtub3duQGRvbWFpbi50bGQifQ.hr8AhlNIpiA3Mv_A5ZLyL" + "f1BHeBSaRDfdR_GLV_hlPdIrWv1xtwjBH86E1YnTPx2tTpr_NWTbHcR1OCkuVCpgloEnUNbE3U2l0WrGOX2Eh9dWCXOCtrNvCeSHQuvx5_8W" + "nSVENYidk7o2icE8_gz_Giwf0Z3bHJJYXfAxupv__tCkmhqt3E888VZPjs26AsqxQ29YyX0Fjx8UwKbPrH5-tnyftX-kLjjZNtahVIVtbW4v" + @@ -380,7 +380,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void getMustReturnBadCredentialsWhenInvalidJwtAuthorizationHeader() throws Exception { + public void getMustReturnBadCredentialsWhenInvalidJwtAuthorizationHeader() { String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.T04BTk" + "LXkJj24coSZkK13RfG25lpvmSl2MJ7N10KpBk9_-95EGYZdog-BDAn3PJzqVw52z-Bwjh4VOj1-j7cURu0cT4jXehhUrlCxS4n7QHZ" + "EN_bsEYGu7KzjWTpTsUiHe-rN7izXVFxDGG1TGwlmBCBnPW-EFCf9ylUsJi0r2BKNdaaPRfMIrHptH1zJBkkUziWpBN1RNLjmvlAUf" + @@ -405,7 +405,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void getMustReturnEndpointsWhenCorrectAuthentication() throws Exception { + public void getMustReturnEndpointsWhenCorrectAuthentication() { String continuationToken = fromGoodContinuationTokenRequest(); zonedDateTimeProvider.setFixedDateTime(newDate); @@ -421,7 +421,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void deleteMustReturnUnauthenticatedWithoutAuthorizationHeader() throws Exception { + public void deleteMustReturnUnauthenticatedWithoutAuthorizationHeader() { given() .when() .delete("/authentication") @@ -430,7 +430,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void deleteMustReturnUnauthenticatedWithoutAValidAuthroizationHeader() throws Exception { + public void deleteMustReturnUnauthenticatedWithoutAValidAuthroizationHeader() { given() .header("Authorization", UUID.randomUUID()) .when() @@ -440,7 +440,7 @@ public abstract class JMAPAuthenticationTest { } @Test - public void deleteMustReturnOKNoContentOnValidAuthorizationToken() throws Exception { + public void deleteMustReturnOKNoContentOnValidAuthorizationToken() { String continuationToken = fromGoodContinuationTokenRequest(); String token = fromGoodAccessTokenRequest(continuationToken); given() @@ -453,7 +453,7 @@ public abstract class JMAPAuthenticationTest { @Category(BasicFeature.class) @Test - public void deleteMustInvalidAuthorizationOnCorrectAuthorization() throws Exception { + public void deleteMustInvalidAuthorizationOnCorrectAuthorization() { String continuationToken = fromGoodContinuationTokenRequest(); zonedDateTimeProvider.setFixedDateTime(newDate); diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JWTAuthenticationStrategyTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JWTAuthenticationStrategyTest.java index 5c8b3dd..78b57ad 100644 --- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JWTAuthenticationStrategyTest.java +++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/JWTAuthenticationStrategyTest.java @@ -26,7 +26,7 @@ import static org.mockito.Mockito.when; import org.apache.james.core.Username; import org.apache.james.domainlist.api.DomainList; -import org.apache.james.jmap.exceptions.MailboxSessionCreationException; +import org.apache.james.jmap.exceptions.UnauthorizedException; import org.apache.james.jmap.jwt.JWTAuthenticationStrategy; import org.apache.james.jwt.JwtTokenVerifier; import org.apache.james.mailbox.MailboxManager; @@ -66,16 +66,16 @@ public class JWTAuthenticationStrategyTest { @Test - public void createMailboxSessionShouldThrowWhenAuthHeaderIsEmpty() { - when(mockedHeaders.getAll(AUTHORIZATION_HEADERS)) - .thenReturn(ImmutableList.of()); + public void createMailboxSessionShouldReturnEmptyWhenAuthHeaderIsEmpty() { + when(mockedHeaders.get(AUTHORIZATION_HEADERS)) + .thenReturn(""); assertThat(testee.createMailboxSession(mockedRequest).blockOptional()) .isEmpty(); } @Test - public void createMailboxSessionShouldThrownWhenAuthHeadersIsInvalid() { + public void createMailboxSessionShouldThrowWhenAuthHeadersIsInvalid() { String username = "123456789"; String validAuthHeader = "valid"; String fakeAuthHeaderWithPrefix = JWTAuthenticationStrategy.AUTHORIZATION_HEADER_PREFIX + validAuthHeader; @@ -86,20 +86,32 @@ public class JWTAuthenticationStrategyTest { when(mockedMailboxManager.createSystemSession(eq(Username.of(username)))) .thenReturn(fakeMailboxSession); - when(mockedHeaders.getAll(AUTHORIZATION_HEADERS)) - .thenReturn(ImmutableList.of(fakeAuthHeaderWithPrefix)); + when(mockedHeaders.get(AUTHORIZATION_HEADERS)) + .thenReturn(fakeAuthHeaderWithPrefix); + + assertThatThrownBy(() -> testee.createMailboxSession(mockedRequest).blockOptional()) + .isInstanceOf(UnauthorizedException.class); + } + + @Test + public void createMailboxSessionShouldReturnEmptyWhenAuthHeaderIsInvalid() { + when(mockedHeaders.get(AUTHORIZATION_HEADERS)) + .thenReturn("bad"); assertThat(testee.createMailboxSession(mockedRequest).blockOptional()) .isEmpty(); } @Test - public void createMailboxSessionShouldThrowWhenAuthHeaderIsInvalid() { + public void createMailboxSessionShouldThrowWhenMultipleHeaders() { + String authHeader1 = JWTAuthenticationStrategy.AUTHORIZATION_HEADER_PREFIX + "token1"; + String authHeader2 = JWTAuthenticationStrategy.AUTHORIZATION_HEADER_PREFIX + "token2"; + when(mockedHeaders.getAll(AUTHORIZATION_HEADERS)) - .thenReturn(ImmutableList.of("bad")); + .thenReturn(ImmutableList.of(authHeader1, authHeader2)); - assertThat(testee.createMailboxSession(mockedRequest).blockOptional()) - .isEmpty(); + assertThatThrownBy(() -> testee.createMailboxSession(mockedRequest).blockOptional()) + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -113,8 +125,8 @@ public class JWTAuthenticationStrategyTest { when(stubTokenVerifier.extractLogin(validAuthHeader)).thenReturn(username); when(mockedMailboxManager.createSystemSession(eq(Username.of(username)))) .thenReturn(fakeMailboxSession); - when(mockedHeaders.getAll(AUTHORIZATION_HEADERS)) - .thenReturn(ImmutableList.of(fakeAuthHeaderWithPrefix)); + when(mockedHeaders.get(AUTHORIZATION_HEADERS)) + .thenReturn(fakeAuthHeaderWithPrefix); MailboxSession result = testee.createMailboxSession(mockedRequest).block(); assertThat(result).isEqualTo(fakeMailboxSession); @@ -131,11 +143,11 @@ public class JWTAuthenticationStrategyTest { when(stubTokenVerifier.extractLogin(validAuthHeader)).thenReturn(username); when(mockedMailboxManager.createSystemSession(eq(Username.of(username)))) .thenReturn(fakeMailboxSession); - when(mockedHeaders.getAll(AUTHORIZATION_HEADERS)) - .thenReturn(ImmutableList.of(fakeAuthHeaderWithPrefix)); + when(mockedHeaders.get(AUTHORIZATION_HEADERS)) + .thenReturn(fakeAuthHeaderWithPrefix); assertThatThrownBy(() -> testee.createMailboxSession(mockedRequest).block()) - .isInstanceOf(MailboxSessionCreationException.class); + .isInstanceOf(UnauthorizedException.class); } } diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/jwt/JWTAuthenticationStrategy.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/jwt/JWTAuthenticationStrategy.java index 41566a7..5ffbec0 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/jwt/JWTAuthenticationStrategy.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/jwt/JWTAuthenticationStrategy.java @@ -18,12 +18,10 @@ ****************************************************************/ package org.apache.james.jmap.jwt; -import java.util.stream.Stream; - import javax.inject.Inject; import org.apache.james.core.Username; -import org.apache.james.jmap.exceptions.MailboxSessionCreationException; +import org.apache.james.jmap.exceptions.UnauthorizedException; import org.apache.james.jmap.http.AuthenticationStrategy; import org.apache.james.jwt.JwtTokenVerifier; import org.apache.james.mailbox.MailboxManager; @@ -53,28 +51,24 @@ public class JWTAuthenticationStrategy implements AuthenticationStrategy { } @Override - public Mono<MailboxSession> createMailboxSession(HttpServerRequest httpRequest) throws MailboxSessionCreationException { - Stream<Username> userLoginStream = extractTokensFromAuthHeaders(authHeaders(httpRequest)) - .filter(tokenManager::verify) - .map(tokenManager::extractLogin) - .map(Username::of) - .peek(username -> { + public Mono<MailboxSession> createMailboxSession(HttpServerRequest httpRequest) { + return Mono.fromCallable(() -> authHeaders(httpRequest)) + .filter(header -> header.startsWith(AUTHORIZATION_HEADER_PREFIX)) + .map(header -> header.substring(AUTHORIZATION_HEADER_PREFIX.length())) + .map(userJWTToken -> { + if (!tokenManager.verify(userJWTToken)) { + throw new UnauthorizedException("Failed Jwt verification"); + } + + Username username = Username.of(tokenManager.extractLogin(userJWTToken)); try { usersRepository.assertValid(username); } catch (UsersRepositoryException e) { - throw new MailboxSessionCreationException(e); + throw new UnauthorizedException("Invalid username", e); } - }); - - Stream<MailboxSession> mailboxSessionStream = userLoginStream - .map(mailboxManager::createSystemSession); - - return Mono.justOrEmpty(mailboxSessionStream.findFirst()); - } - private Stream<String> extractTokensFromAuthHeaders(Stream<String> authHeaders) { - return authHeaders - .filter(h -> h.startsWith(AUTHORIZATION_HEADER_PREFIX)) - .map(h -> h.substring(AUTHORIZATION_HEADER_PREFIX.length())); + return username; + }) + .map(mailboxManager::createSystemSession); } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
