McPringle/sportchef

View on GitHub
src/test/java/ch/sportchef/business/authentication/control/AuthenticationServiceTest.java

Summary

Maintainability
A
2 hrs
Test Coverage
/*
 * SportChef – Sports Competition Management Software
 * Copyright (C) 2016 Marcus Fihlon
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package ch.sportchef.business.authentication.control;

import ch.sportchef.business.configuration.control.ConfigurationService;
import ch.sportchef.business.configuration.entity.Configuration;
import ch.sportchef.business.user.control.UserService;
import ch.sportchef.business.user.entity.User;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.dumbster.smtp.MailMessage;
import com.dumbster.smtp.ServerOptions;
import com.dumbster.smtp.SmtpServer;
import com.dumbster.smtp.SmtpServerFactory;
import io.jsonwebtoken.*;
import org.apache.commons.mail.EmailException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.Optional;

import static ch.sportchef.business.authentication.entity.Role.ADMIN;
import static ch.sportchef.business.authentication.entity.Role.USER;
import static ch.sportchef.hamcrest.matcher.PatternMatcher.matchesPattern;
import static java.lang.Boolean.FALSE;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;

@SuppressWarnings("OptionalGetWithoutIsPresent")
public class AuthenticationServiceTest {

    private static final String MALFORMED_TOKEN = "eyJhbGciOiJIUzUxMiJ9_eyJpYXQiOjE0NjE3ODc4NDEsImV4cCI6MTQ2MTc4ODQ0MSwiZW1haWwiOiJqb2huLmRvZUBzcG9ydGNoZWYuY2gifQ_8MJW7kRJYrc105JmorJgOA3Wn6z2Z5Ab0uteZsleKyJBLAibhDr35S0PH9trTwxYeocpIHVhQ-lCak_IVNdwSg";
    private static final String SIGNATURE_TOKEN = "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE0NjE3ODc4NDEsImV4cCI6MTQ2MTc4ODQ0MSwiZW1haWwiOiJqb2huLmRvZUBzcG9ydGNoZWYuY2gifQ.8MJW7kRJYrc105JmorJgOA3Wn6z2Z5Ab0uteZsleKyJBLAibhDr35S0PH9trTwxYeocpIHVhQ-lCak_IVNdwSg";

    private static final Long TEST_USER_ID = 1L;
    private static final String TEST_USER_FIRSTNAME = "John";
    private static final String TEST_USER_LASTNAME = "Doe";
    private static final String TEST_USER_PHONE = "+41 79 555 00 01";
    private static final String TEST_USER_EMAIL = "john.doe@sportchef.ch";

    private UserService userServiceMock;
    private ConfigurationService configurationServiceMock;
    private HealthCheckRegistry healthCheckRegistryMock;
    private SmtpServer smtpServer;

    @BeforeEach
    public void setup() {
        final User testUser = User.builder()
                .userId(TEST_USER_ID)
                .firstName(TEST_USER_FIRSTNAME)
                .lastName(TEST_USER_LASTNAME)
                .phone(TEST_USER_PHONE)
                .email(TEST_USER_EMAIL)
                .build();

        userServiceMock = mock(UserService.class);
        when(userServiceMock.findByEmail(anyString())).thenAnswer(x -> Optional.of(testUser));
        configurationServiceMock = mock(ConfigurationService.class);
        when(configurationServiceMock.getConfiguration()).thenAnswer(x -> createConfigurationMock());
        healthCheckRegistryMock = mock(HealthCheckRegistry.class);

        final ServerOptions smtpServerOptions = new ServerOptions();
        smtpServerOptions.port = 4444;
        smtpServer = SmtpServerFactory.startServer(smtpServerOptions);
    }

    @AfterEach
    public void tearDown() {
        if (smtpServer != null) {
            smtpServer.stop();
        }
    }

    private Configuration createConfigurationMock() {
        final Configuration configurationMock = mock(Configuration.class);
        when(configurationMock.getTokenSigningKey()).thenReturn("This is a Mock!");
        when(configurationMock.getTokenExpirationTime()).thenReturn(1);
        when(configurationMock.getSMTPServer()).thenReturn("localhost");
        when(configurationMock.getSMTPPort()).thenReturn(4444);
        when(configurationMock.getSMTPUser()).thenReturn("test");
        when(configurationMock.getSMTPPassword()).thenReturn("test");
        when(configurationMock.getSMTPSSL()).thenReturn(FALSE);
        when(configurationMock.getSMTPFrom()).thenReturn("noreply@sportchef.ch");
        return configurationMock;
    }

    @Test
    public void requestChallengeNotOk() {
        // arrange
        when(userServiceMock.findByEmail(TEST_USER_EMAIL)).thenReturn(Optional.empty());
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);

        // act
        final boolean ok = authenticationService.requestChallenge(TEST_USER_EMAIL);

        // assert
        assertThat(ok, is(false));
        verify(userServiceMock, times(1)).findByEmail(TEST_USER_EMAIL);
    }

    @Test
    public void validateMalformedToken() {
        // arrange
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);

        // act & assert
        assertThrows(MalformedJwtException.class,
                () -> authenticationService.validate(MALFORMED_TOKEN));
    }

    @Test
    public void validateSignatureToken() {
        // arrange
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);

        // act & assert
        assertThrows(SignatureException.class,
                () ->authenticationService.validate(SIGNATURE_TOKEN));
    }

    @Test
    public void validateExpiredToken() {
        // arrange
        final Date now = new Date();
        final Date exp = new Date(now.getTime() - 1000);
        final Claims claims = Jwts.claims();
        claims.setExpiration(exp);
        final String tokenSigningKey = configurationServiceMock.getConfiguration().getTokenSigningKey();
        final String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, tokenSigningKey)
                .compact();
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);

        // act & assert
        assertThrows(ExpiredJwtException.class,
                () -> authenticationService.validate(token));
    }

    @Test
    public void requestAndValidateChallengeAndToken() {
        // arrange
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);

        // act
        validateToken(validateChallenge(authenticationService, requestChallenge(authenticationService)));

        // assert
        assertThat(smtpServer.getEmailCount(), is(1));
        verify(userServiceMock, times(2)).findByEmail(TEST_USER_EMAIL);
    }

    @Test
    public void typoWhileLoginDoesNotLogin() {
        // arrange
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);
        requestChallenge(authenticationService);

        // act
        final Optional<String> token = authenticationService.validateChallenge(TEST_USER_EMAIL, "wrongChallenge");

        // assert
        assertThat(token.isPresent(), is(false));
    }

    @Test
    public void loginWithoutChallengeRequestedDoesNotLogin() {
        // arrange
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);

        // act
        final Optional<String> token = authenticationService.validateChallenge(TEST_USER_EMAIL, "anyChallenge");

        // assert
        assertThat(token.isPresent(), is(false));
    }

    @Test
    public void make1TypoWhileLoggingInStillWorks() {
        // arrange
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);
        final String correctChallenge = requestChallenge(authenticationService);

        // act
        authenticationService.validateChallenge(TEST_USER_EMAIL, "wrongChallenge");
        final Optional<String> token = authenticationService.validateChallenge(TEST_USER_EMAIL, correctChallenge);

        // assert
        assertThat(token.get(), matchesPattern(".{20}\\..{90}\\..{86}"));
    }

    @Test
    public void make10TyposWhileLoggingInDisablesTheChallenge() {
        // arrange
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);
        final String correctChallenge = requestChallenge(authenticationService);

        // act
        for (int i = 0; i < 10; i++) {
            authenticationService.validateChallenge(TEST_USER_EMAIL, "wrongChallenge");
        }
        final Optional<String> token = authenticationService.validateChallenge(TEST_USER_EMAIL, correctChallenge);

        // assert
        assertThat(token.isPresent(), is(false));
    }

    @Test
    public void requestChallengeWithException() {
        // arrange
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);

        // act & assert
        assertThrows(EmailException.class,
                () -> authenticationService.requestChallenge("@test"));
    }

    private String requestChallenge(@NotNull final AuthenticationService authenticationService) {
        // arrange

        // act
        final boolean ok = authenticationService.requestChallenge(TEST_USER_EMAIL);

        // assert
        assertThat(ok, is(true));
        final MailMessage newestMessage = smtpServer.getMessage(smtpServer.getMessages().length - 1);
        assertThat(newestMessage.getFirstHeaderValue("To"), is(TEST_USER_EMAIL));

        final String body = newestMessage.getBody();
        return body.substring(body.indexOf("=") + 2);
    }

    private String validateChallenge(@NotNull final AuthenticationService authenticationService,
                                     @NotNull final String challenge) {
        // arrange

        // act
        final Optional<String> token = authenticationService.validateChallenge(TEST_USER_EMAIL, challenge);

        // assert
        assertThat(token.get(), matchesPattern(".{20}\\..{90}\\..{86}"));

        return token.get();
    }

    private void validateToken(@NotNull final String token) {
        // arrange
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);

        // act
        final Optional<User> userOptional = authenticationService.validate(token);

        // assert
        assertThat(userOptional, notNullValue());
        assertThat(userOptional.isPresent(), is(true));
        assertThat(userOptional.get().getEmail(), is(TEST_USER_EMAIL));
    }

    @Test
    public void isUserInRole() {
        // arrange
        final User user = User.builder().role(USER).build();
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);

        // act
        final boolean isUserInRoleUser = authenticationService.isUserInRole(user, USER);
        final boolean isUserInRoleAdmin = authenticationService.isUserInRole(user, ADMIN);

        // assert
        assertThat(isUserInRoleUser, is(true));
        assertThat(isUserInRoleAdmin, is(false));
    }

    @Test
    public void isUserInAdmin() {
        // arrange
        final User admin = User.builder().role(ADMIN).build();
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);

        // act
        final boolean isUserInRoleUser = authenticationService.isUserInRole(admin, USER);
        final boolean isUserInRoleAdmin = authenticationService.isUserInRole(admin, ADMIN);

        // assert
        assertThat(isUserInRoleUser, is(true));
        assertThat(isUserInRoleAdmin, is(true));
    }

    @Test
    public void shortChallengeIfNoActivityOngoing() {
        // arrange
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);

        // act
        final String challenge = requestChallenge(authenticationService);

        // assert
        assertThat(challenge.length(), equalTo(5)); //(26+26+10)^5=916132832
    }

    @Test
    public void longChallengeIfActivityOngoing() {
        // arrange
        final AuthenticationService authenticationService = new AuthenticationService(userServiceMock, configurationServiceMock, healthCheckRegistryMock);
        for (int i = 0; i < 100; i++) {
            authenticationService.requestChallenge(i + TEST_USER_EMAIL);
        }

        // act
        final String challenge = requestChallenge(authenticationService);

        // assert
        assertThat(challenge.length(), equalTo(10)); //(26+26+10)^10=8.39E17
    }

}