// Copyright © 2015 HSL <https://www.hsl.fi>
// This program is dual-licensed under the EUPL v1.2 and AGPLv3 licenses.
package fi.hsl.parkandride.core.service;
import static fi.hsl.parkandride.core.ViolationAssert.*;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang3.StringUtils;
import org.jasypt.util.password.PasswordEncryptor;
import org.joda.time.DateTime;
import org.joda.time.Period;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import fi.hsl.parkandride.core.back.UserRepository;
import fi.hsl.parkandride.core.domain.NewUser;
import fi.hsl.parkandride.core.domain.Role;
import fi.hsl.parkandride.core.domain.User;
import fi.hsl.parkandride.core.domain.UserSecret;
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncryptor passwordEncryptor;
private UserService userService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
userService = new UserService(
userRepository,
new AuthenticationService(userRepository, passwordEncryptor, StringUtils.repeat('x', AuthenticationService.SECRET_MIN_LENGTH), Period.seconds(60), Period.days(60), Period.days(14)),
new ValidationService());
}
private final static Long DEFAULT_OPERATOR = 0L;
private final static String DEFAULT_PASSWORD = "paSs1234";
private final static String NEW_PASSWORD = "paSs1234_new";
private final AtomicLong seq = new AtomicLong(1L);
private final User adminActor = actor("admin_actor", Role.ADMIN);
private final User operatorActor = actor("operator_actor", Role.OPERATOR);
private final User operatorAPIActor = actor("operator_api_actor", Role.OPERATOR_API);
private final NewUser admin = input("admin", Role.ADMIN, DEFAULT_PASSWORD);
private final NewUser operator = input("operator", Role.OPERATOR, DEFAULT_PASSWORD);
private final NewUser operatorAPI = input("operator_api", Role.OPERATOR_API, null);
@Test
public void password_is_required_for_operator() {
Runnable createFn = () -> userService.createUser(operator, adminActor);
createFn.run();
verify(userRepository).insertUser(anyObject());
operator.password = null;
assertBadPassword(createFn);
operator.password = "";
assertBadPassword(createFn);
operator.password = " ";
assertBadPassword(createFn);
}
@Test
public void password_is_not_required_for_operator_api() {
operatorAPI.password = null;
userService.createUser(operatorAPI, adminActor);
verify(userRepository).insertUser(anyObject());
}
@Test
public void password_is_required_for_admin() {
Runnable createFn = () -> userService.createUser(admin, adminActor);
createFn.run();
verify(userRepository).insertUser(anyObject());
admin.password = null;
assertBadPassword(createFn);
}
@Test
public void operator_is_required_for_operator() {
Runnable createFn = () -> userService.createUser(operator, adminActor);
createFn.run();
verify(userRepository).insertUser(anyObject());
operator.operatorId = null;
assertOperatorRequired(createFn);
}
@Test
public void operator_is_required_for_operator_api() {
Runnable createFn = () -> userService.createUser(operatorAPI, adminActor);
createFn.run();
verify(userRepository).insertUser(anyObject());
operatorAPI.operatorId = null;
assertOperatorRequired(createFn);
}
@Test
public void operator_is_not_allowed_for_admin() {
Runnable createFn = () -> userService.createUser(admin, adminActor);
createFn.run();
verify(userRepository).insertUser(anyObject());
admin.operatorId = DEFAULT_OPERATOR;
assertOperatorNotAllowed(createFn);
}
@Test
public void role_is_required() {
operator.role = null;
assertNotNull(() -> userService.createUser(operator, adminActor));
}
@Test(expected = AccessDeniedException.class)
public void operator_cannot_create_admin() {
userService.createUser(admin, operatorActor);
}
@Test(expected = AccessDeniedException.class)
public void operator_cannot_create_other_operators_user() {
operatorAPI.operatorId = DEFAULT_OPERATOR + 1;
userService.createUser(operatorAPI, operatorActor);
}
@Test
public void operator_api_cannot_create_users() {
assertAccessDenied(() -> userService.createUser(admin, operatorAPIActor));
assertAccessDenied(() -> userService.createUser(operator, operatorAPIActor));
assertAccessDenied(() -> userService.createUser(input("same_operator__other_api", Role.OPERATOR_API, null), operatorAPIActor));
}
@Test
public void api_token_can_be_reset() {
DateTime now = DateTime.now();
when(userRepository.getUser(operatorAPI.id)).thenReturn(userSecret(operatorAPI));
when(userRepository.getCurrentTime()).thenReturn(now);
userService.resetToken(operatorAPI.id, adminActor);
userService.resetToken(operatorAPI.id, operatorActor);
verify(userRepository, times(2)).revokeTokens(operatorAPI.id, now);
}
@Test
public void non_api_token_cannot_be_reset() {
when(userRepository.getUser(admin.id)).thenReturn(userSecret(admin));
when(userRepository.getUser(operator.id)).thenReturn(userSecret(operator));
when(userRepository.getCurrentTime()).thenReturn(DateTime.now());
assertPerpetualTokenNotAllowed(() -> userService.resetToken(admin.id, adminActor));
assertPerpetualTokenNotAllowed(() -> userService.resetToken(operator.id, operatorActor));
}
@Test(expected = AccessDeniedException.class)
public void operator_cannot_reset_other_operators_api_token() {
NewUser otherAPI = input("other_operator_api", Role.OPERATOR_API, DEFAULT_PASSWORD);
otherAPI.operatorId = DEFAULT_OPERATOR + 1;
when(userRepository.getUser(otherAPI.id)).thenReturn(userSecret(otherAPI));
when(userRepository.getCurrentTime()).thenReturn(DateTime.now());
userService.resetToken(otherAPI.id, operatorActor);
}
@Test(expected = AccessDeniedException.class)
public void operator_api_cannot_reset_tokens() {
when(userRepository.getUser(operatorAPI.id)).thenReturn(userSecret(operatorAPI));
userService.resetToken(operatorAPI.id, operatorAPI);
}
@Test
public void operator_can_update_operator_password() {
when(userRepository.getUser(operator.id)).thenReturn(userSecret(operator));
when(passwordEncryptor.encryptPassword(NEW_PASSWORD)).thenReturn("newPassEncrypted");
userService.updatePassword(operator.id, NEW_PASSWORD, operatorActor);
verify(userRepository).updatePassword(operator.id, "newPassEncrypted");
}
@Test(expected = AccessDeniedException.class)
public void operator_cannot_update_admin_password() {
when(userRepository.getUser(admin.id)).thenReturn(userSecret(admin));
userService.updatePassword(admin.id, NEW_PASSWORD, operatorActor);
}
@Test
public void admin_can_update_operator_password() {
when(userRepository.getUser(operator.id)).thenReturn(userSecret(operator));
when(passwordEncryptor.encryptPassword(NEW_PASSWORD)).thenReturn("newPassEncrypted");
userService.updatePassword(operator.id, NEW_PASSWORD, adminActor);
verify(userRepository).updatePassword(operator.id, "newPassEncrypted");
}
@Test
public void admin_can_update_admin_password() {
when(userRepository.getUser(admin.id)).thenReturn(userSecret(admin));
when(passwordEncryptor.encryptPassword(NEW_PASSWORD)).thenReturn("newPassEncrypted");
userService.updatePassword(admin.id, NEW_PASSWORD, adminActor);
verify(userRepository).updatePassword(admin.id, "newPassEncrypted");
}
@Test(expected = AccessDeniedException.class)
public void operator_api_cannot_update_passwords() {
when(userRepository.getUser(operator.id)).thenReturn(userSecret(operator));
userService.updatePassword(operator.id, NEW_PASSWORD, operatorAPIActor);
}
@Test(expected = AccessDeniedException.class)
public void operator_cannot_update_other_operators_password() {
operator.operatorId = DEFAULT_OPERATOR + 1;
when(userRepository.getUser(operator.id)).thenReturn(userSecret(operator));
userService.updatePassword(operator.id, NEW_PASSWORD, operatorActor);
}
@Test
public void operator_api_password_cannot_be_updated() {
when(userRepository.getUser(operatorAPI.id)).thenReturn(userSecret(operatorAPI));
assertPasswordUpdateNotApplicable(() -> userService.updatePassword(operatorAPI.id, NEW_PASSWORD, adminActor));
}
@Test
public void admin_can_delete_other_admin() {
NewUser otherAdmin = input("other_admin", Role.ADMIN, DEFAULT_PASSWORD);
when(userRepository.getUser(otherAdmin.id)).thenReturn(userSecret(otherAdmin));
userService.deleteUser(otherAdmin.id, adminActor);
verify(userRepository).deleteUser(otherAdmin.id);
}
@Test
public void admin_can_delete_operator() {
when(userRepository.getUser(operator.id)).thenReturn(userSecret(operator));
userService.deleteUser(operator.id, adminActor);
verify(userRepository).deleteUser(operator.id);
}
@Test(expected = AccessDeniedException.class)
public void operator_cannot_delete_admin() {
when(userRepository.getUser(admin.id)).thenReturn(userSecret(admin));
userService.deleteUser(admin.id, operatorActor);
}
@Test
public void operator_can_delete_other_user_of_the_same_operator() {
when(userRepository.getUser(operatorAPI.id)).thenReturn(userSecret(operatorAPI));
userService.deleteUser(operatorAPI.id, operatorActor);
verify(userRepository).deleteUser(operatorAPI.id);
}
@Test(expected = AccessDeniedException.class)
public void operator_cannot_delete_other_operators_user() {
operatorAPI.operatorId = DEFAULT_OPERATOR + 1;
when(userRepository.getUser(operatorAPI.id)).thenReturn(userSecret(operatorAPI));
userService.deleteUser(operatorAPI.id, operatorActor);
}
@Test(expected = AccessDeniedException.class)
public void operator_api_cannot_delete_users() {
when(userRepository.getUser(operator.id)).thenReturn(userSecret(operator));
userService.deleteUser(operator.id, operatorAPIActor);
}
@Test
public void user_cannot_delete_itself() {
when(userRepository.getUser(admin.id)).thenReturn(userSecret(admin));
when(userRepository.getUser(operator.id)).thenReturn(userSecret(operator));
assertAccessDenied(() -> userService.deleteUser(admin.id, admin));
assertAccessDenied(() -> userService.deleteUser(operator.id, operator));
}
private UserSecret userSecret(User u) {
UserSecret us = new UserSecret();
us.user = u;
return us;
}
private NewUser input(String username, Role role, String pass) {
NewUser input = new NewUser(seq.incrementAndGet(), username, role, pass);
if (role != Role.ADMIN) {
input.operatorId = DEFAULT_OPERATOR;
}
return input;
}
private User actor(String username, Role role) {
User actor = new User(seq.incrementAndGet(), username, role);
if (role != Role.ADMIN) {
actor.operatorId = DEFAULT_OPERATOR;
}
return actor;
}
private static void assertAccessDenied(Runnable r) {
try {
r.run();
Assert.fail("did not throw AccessDeniedException");
} catch (AccessDeniedException expected) {}
}
}