/**
* Copyright (C) 2011 JTalks.org Team
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library 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
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jtalks.jcommune.service.transactional;
import com.google.common.collect.ImmutableMap;
import org.jtalks.common.model.dao.GroupDao;
import org.jtalks.common.model.entity.Group;
import org.jtalks.common.model.entity.User;
import org.jtalks.common.service.security.SecurityContextFacade;
import org.jtalks.jcommune.model.dao.UserDao;
import org.jtalks.jcommune.model.dto.RegisterUserDto;
import org.jtalks.jcommune.model.dto.UserDto;
import org.jtalks.jcommune.model.entity.JCUser;
import org.jtalks.jcommune.plugin.api.core.AuthenticationPlugin;
import org.jtalks.jcommune.plugin.api.core.Plugin;
import org.jtalks.jcommune.plugin.api.core.RegistrationPlugin;
import org.jtalks.jcommune.plugin.api.exceptions.NoConnectionException;
import org.jtalks.jcommune.plugin.api.exceptions.UnexpectedErrorException;
import org.jtalks.jcommune.service.Authenticator;
import org.jtalks.jcommune.service.PluginService;
import org.jtalks.jcommune.service.util.AuthenticationStatus;
import org.jtalks.jcommune.service.nontransactional.EncryptionService;
import org.jtalks.jcommune.service.nontransactional.ImageService;
import org.jtalks.jcommune.service.nontransactional.MailService;
import org.jtalks.jcommune.plugin.api.PluginLoader;
import org.jtalks.jcommune.plugin.api.filters.TypeFilter;
import org.jtalks.jcommune.service.security.AdministrationGroup;
import org.mockito.Mock;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.jtalks.jcommune.model.dto.LoginUserDto;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.testng.Assert.*;
import static org.unitils.reflectionassert.ReflectionAssert.assertReflectionEquals;
/**
* @author Andrey Pogorelov
*/
public class TransactionalAuthenticatorTest {
@Mock
private PluginLoader pluginLoader;
@Mock
private AuthenticationPlugin authPlugin;
@Mock
private RegistrationPlugin registrationPlugin;
@Mock
private EncryptionService encryptionService;
@Mock
private UserDao userDao;
@Mock
private GroupDao groupDao;
@Mock
private AuthenticationManager authenticationManager;
@Mock
private SecurityContextFacade securityFacade;
@Mock
private SecurityContext securityContext;
@Mock
private RememberMeServices rememberMeServices;
@Mock
private SessionAuthenticationStrategy sessionStrategy;
@Mock
private BindingResult bindingResult;
@Mock
private HttpServletRequest httpRequest;
@Mock
private HttpServletResponse httpResponse;
@Mock
MailService mailService;
@Mock
ImageService avatarService;
@Mock
PluginService pluginService;
@Mock
private Validator validator;
private Authenticator authenticator;
@BeforeMethod
public void setUp() throws Exception {
initMocks(this);
authenticator = new TransactionalAuthenticator(pluginLoader, userDao, groupDao,
encryptionService, mailService, avatarService, pluginService,
securityFacade, rememberMeServices, sessionStrategy, validator, authenticationManager);
}
private JCUser prepareOldUser(String username) {
JCUser oldUser = new JCUser(username, "oldEmail@email.em", "14a88b9d2f52c55b5fbcf9c5d9c11875");
when(userDao.getByUsername(username)).thenReturn(oldUser);
return oldUser;
}
private Map<String, String> createAuthInfo(String username, String email) {
Map<String, String> authInfo = new HashMap<>();
authInfo.put("username", username);
authInfo.put("email", email);
authInfo.put("firstName", "firstName");
authInfo.put("lastName", "lastName");
return authInfo;
}
private void preparePlugin(String username, String passwordHash, Map<String, String> authInfo) throws Exception {
Class authPluginClass = AuthenticationPlugin.class;
when(pluginLoader.getPluginByClassName(authPluginClass)).thenReturn(authPlugin);
when(authPlugin.authenticate(username, passwordHash)).thenReturn(authInfo);
when(authPlugin.getState()).thenReturn(Plugin.State.ENABLED);
Class cl = RegistrationPlugin.class;
when(pluginLoader.getPluginByClassName(cl)).thenReturn(registrationPlugin);
when(registrationPlugin.getState()).thenReturn(Plugin.State.ENABLED);
}
private void prepareAuth() {
UsernamePasswordAuthenticationToken expectedToken = mock(UsernamePasswordAuthenticationToken.class);
when(securityFacade.getContext()).thenReturn(securityContext);
when(expectedToken.isAuthenticated()).thenReturn(true);
when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class))).thenReturn(expectedToken);
}
@Test
public void authenticateExistingUserShouldBeSuccessful() throws Exception {
String passwordHash = "5f4dcc3b5aa765d61d8327deb882cf99";
JCUser user = getDefaultUser();
LoginUserDto loginUserDto = createDefaultLoginUserDto();
Map<String, String> authInfo = createAuthInfo(user.getUsername(), user.getEmail());
when(userDao.getByUsername(user.getUsername())).thenReturn(user);
when(encryptionService.encryptPassword(user.getPassword())).thenReturn(passwordHash);
prepareAuth();
preparePlugin(user.getUsername(), passwordHash, authInfo);
AuthenticationStatus result = authenticator.authenticate(loginUserDto, httpRequest, httpResponse);
assertEquals(result, AuthenticationStatus.AUTHENTICATED,
"Authentication existing user with correct credentials should be successful.");
}
@Test
public void authenticateNotExistingUserShouldBeSuccessful() throws Exception {
String passwordHash = "5f4dcc3b5aa765d61d8327deb882cf99";
LoginUserDto loginUserDto = createDefaultLoginUserDto();
JCUser user = getDefaultUser();
Map<String, String> authInfo = createAuthInfo(user.getUsername(), user.getEmail());
when(userDao.getByUsername(user.getUsername())).thenReturn(null).thenReturn(null).thenReturn(user);
when(encryptionService.encryptPassword(user.getPassword())).thenReturn(passwordHash);
prepareAuth();
preparePlugin(user.getUsername(), passwordHash, authInfo);
AuthenticationStatus result = authenticator.authenticate(loginUserDto, httpRequest, httpResponse);
assertEquals(result, AuthenticationStatus.AUTHENTICATED,
"Authentication not existing user with correct credentials should be successful.");
}
@Test
public void authenticateUserWithNewCredentialsShouldBeSuccessful() throws Exception {
String passwordHash = "5f4dcc3b5aa765d61d8327deb882cf99";
String email = "email@email.em";
LoginUserDto loginUserDto = createDefaultLoginUserDto();
JCUser oldUser = prepareOldUser(loginUserDto.getUserName());
Map<String, String> authInfo = createAuthInfo(oldUser.getUsername(), email);
authInfo.put("enabled", "true");
Group group = new Group(AdministrationGroup.USER.getName());
when(groupDao.getGroupByName(AdministrationGroup.USER.getName())).thenReturn(group);
when(userDao.getByUsername(oldUser.getUsername())).thenReturn(oldUser);
when(encryptionService.encryptPassword(loginUserDto.getPassword())).thenReturn(passwordHash);
UsernamePasswordAuthenticationToken expectedToken = mock(UsernamePasswordAuthenticationToken.class);
when(securityFacade.getContext()).thenReturn(securityContext);
when(expectedToken.isAuthenticated()).thenReturn(true);
when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
.thenThrow(new BadCredentialsException(null)).thenReturn(expectedToken);
preparePlugin(oldUser.getUsername(), passwordHash, authInfo);
AuthenticationStatus result = authenticator.authenticate(loginUserDto, httpRequest, httpResponse);
verify(userDao).saveOrUpdate(oldUser);
assertEquals(result, AuthenticationStatus.AUTHENTICATED,
"Authentication user with new credentials should be successful.");
}
@Test
public void authenticateNotEnabledUserShouldFail() throws Exception {
LoginUserDto loginUserDto = createDefaultLoginUserDto();
prepareOldUser(loginUserDto.getUserName());
when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
.thenThrow(new DisabledException(null));
AuthenticationStatus result = authenticator.authenticate(loginUserDto, httpRequest, httpResponse);
assertEquals(result, AuthenticationStatus.NOT_ENABLED,
"Authenticate user with bad credentials should fail.");
}
@Test
public void authenticateUserShouldBeSuccessfulIfPluginAndJCommuneUseTheSameDatabase() throws Exception {
String username = "user";
String password = "password";
String passwordHash = "5f4dcc3b5aa765d61d8327deb882cf99";
String email = "email@email.em";
LoginUserDto loginUserDto = createDefaultLoginUserDto();
Map<String, String> authInfo = createAuthInfo(username, email);
Group group = new Group(AdministrationGroup.USER.getName());
User commonUser = new User(username, email, password, null);
commonUser.getGroups().add(group);
when(groupDao.getGroupByName(group.getName())).thenReturn(group);
when(userDao.getByUsername(username)).thenReturn(null);
when(userDao.getCommonUserByUsername(username)).thenReturn(commonUser);
when(encryptionService.encryptPassword(password)).thenReturn(passwordHash);
prepareAuth();
preparePlugin(username, passwordHash, authInfo);
AuthenticationStatus result = authenticator.authenticate(loginUserDto, httpRequest, httpResponse);
assertEquals(result, AuthenticationStatus.AUTHENTICATED,
"Authentication not existing user with correct credentials should be successful " +
"if case Plugin and JCommune use the same database.");
}
@Test
public void authenticateUserWithNewCredentialsShouldFailIfPluginNotFound() throws Exception {
String passwordHash = "5f4dcc3b5aa765d61d8327deb882cf99";
LoginUserDto loginUserDto = createDefaultLoginUserDto();
JCUser oldUser = prepareOldUser(loginUserDto.getUserName());
when(userDao.getByUsername(oldUser.getUsername())).thenReturn(oldUser);
when(encryptionService.encryptPassword(loginUserDto.getPassword())).thenReturn(passwordHash);
UsernamePasswordAuthenticationToken expectedToken = mock(UsernamePasswordAuthenticationToken.class);
when(securityFacade.getContext()).thenReturn(securityContext);
when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
.thenReturn(expectedToken);
when(expectedToken.isAuthenticated()).thenReturn(false);
when(pluginLoader.getPlugins(any(TypeFilter.class))).thenReturn(Collections.EMPTY_LIST);
AuthenticationStatus result = authenticator.authenticate(loginUserDto, httpRequest, httpResponse);
assertEquals(result, AuthenticationStatus.AUTHENTICATION_FAIL,
"Authenticate user with new credentials should fail if plugin not found.");
}
@Test
public void authenticateUserWithBadCredentialsShouldFail() throws Exception {
String password = "password";
String passwordHash = "5f4dcc3b5aa765d61d8327deb882cf99";
LoginUserDto loginUserDto = createDefaultLoginUserDto();
JCUser oldUser = prepareOldUser(loginUserDto.getUserName());
when(userDao.getByUsername(oldUser.getUsername())).thenReturn(oldUser);
when(encryptionService.encryptPassword(password)).thenReturn(passwordHash);
when(securityFacade.getContext()).thenReturn(securityContext);
when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
.thenThrow(new BadCredentialsException(null));
preparePlugin(oldUser.getUsername(), passwordHash, Collections.EMPTY_MAP);
AuthenticationStatus result = authenticator.authenticate(loginUserDto, httpRequest, httpResponse);
assertEquals(result, AuthenticationStatus.AUTHENTICATION_FAIL,
"Authenticate user with bad credentials should fail.");
}
@Test(expectedExceptions = NoConnectionException.class)
public void authenticateShouldFailIfThereAreNoConnectionToAuthService() throws Exception {
String passwordHash = "5f4dcc3b5aa765d61d8327deb882cf99";
LoginUserDto loginUserDto = createDefaultLoginUserDto();
when(encryptionService.encryptPassword(loginUserDto.getPassword())).thenReturn(passwordHash);
when(authPlugin.getState()).thenReturn(Plugin.State.ENABLED);
when(userDao.getByUsername(loginUserDto.getUserName())).thenReturn(null);
Class cl = AuthenticationPlugin.class;
when(pluginLoader.getPluginByClassName(cl)).thenReturn(authPlugin);
when(authPlugin.authenticate(loginUserDto.getUserName(), passwordHash)).thenThrow(new NoConnectionException());
authenticator.authenticate(loginUserDto, httpRequest, httpResponse);
}
@Test(expectedExceptions = UnexpectedErrorException.class)
public void authenticateShouldFailIfPluginThrowsAnUnexpectedException() throws Exception {
String passwordHash = "5f4dcc3b5aa765d61d8327deb882cf99";
LoginUserDto loginUserDto = createDefaultLoginUserDto();
when(encryptionService.encryptPassword(loginUserDto.getPassword())).thenReturn(passwordHash);
when(authPlugin.getState()).thenReturn(Plugin.State.ENABLED);
Class cl = AuthenticationPlugin.class;
when(pluginLoader.getPluginByClassName(cl)).thenReturn(authPlugin);
when(authPlugin.authenticate(loginUserDto.getUserName(), passwordHash)).thenThrow(new UnexpectedErrorException());
authenticator.authenticate(loginUserDto, httpRequest, httpResponse);
}
@Test
public void registerUserWithCorrectDetailsShouldBeSuccessful() throws Exception {
RegisterUserDto userDto = createRegisterUserDto("username", "password", "email@email.em", null);
User commonUser = new User("username", "email@email.em", "password", null);
when(registrationPlugin.getState()).thenReturn(Plugin.State.ENABLED);
when(registrationPlugin.registerUser(userDto.getUserDto(), null)).thenReturn(Collections.EMPTY_MAP);
when(pluginLoader.getPlugins(any(TypeFilter.class))).thenReturn(Arrays.asList((Plugin) registrationPlugin));
when(bindingResult.hasErrors()).thenReturn(false);
when(userDao.getCommonUserByUsername("username")).thenReturn(commonUser);
authenticator.register(userDto);
verify(bindingResult, never()).rejectValue(anyString(), anyString(), anyString());
}
@Test
public void registerUserWithIncorrectDetailsShouldFail() throws Exception {
RegisterUserDto userDto = createRegisterUserDto("", "", "", null);
Map<String, String> errors = new HashMap<>();
errors.put("userDto.email", "Invalid email length");
errors.put("userDto.username", "Invalid username length");
errors.put("userDto.password", "Invalid password length");
RegistrationPlugin plugin = mock(RegistrationPlugin.class);
when(plugin.getState()).thenReturn(Plugin.State.ENABLED);
when(plugin.registerUser(userDto.getUserDto(), 1L)).thenReturn(errors);
when(pluginService.getRegistrationPlugins()).thenReturn(
new ImmutableMap.Builder<Long, RegistrationPlugin>().put(1L, plugin).build());
when(bindingResult.hasErrors()).thenReturn(true);
BindingResult result = authenticator.register(userDto);
assertEquals(result.getFieldErrors().size(), 3);
}
@Test(expectedExceptions = NoConnectionException.class)
public void registerUserShouldFailIfPluginThrowsNoConnectionException() throws Exception {
RegisterUserDto userDto = createRegisterUserDto("username", "password", "email@email.em", null);
RegistrationPlugin plugin = mock(RegistrationPlugin.class);
when(plugin.getState()).thenReturn(Plugin.State.ENABLED);
when(plugin.registerUser(userDto.getUserDto(), 1L))
.thenThrow(new NoConnectionException());
when(pluginService.getRegistrationPlugins()).thenReturn(
new ImmutableMap.Builder<Long, RegistrationPlugin>().put(1L, plugin).build());
when(bindingResult.hasErrors()).thenReturn(true);
authenticator.register(userDto);
}
@Test(expectedExceptions = UnexpectedErrorException.class)
public void registerUserShouldFailIfPluginThrowsUnexpectedErrorException() throws Exception {
RegisterUserDto userDto = createRegisterUserDto("username", "password", "email@email.em", null);
RegistrationPlugin plugin = mock(RegistrationPlugin.class);
when(plugin.getState()).thenReturn(Plugin.State.ENABLED);
when(plugin.registerUser(userDto.getUserDto(), 1L))
.thenThrow(new UnexpectedErrorException());
when(pluginService.getRegistrationPlugins()).thenReturn(
new ImmutableMap.Builder<Long, RegistrationPlugin>().put(1L, plugin).build());
authenticator.register(userDto);
}
@Test
public void defaultRegistrationShouldFailIfValidationErrorsOccurred() throws Exception {
RegisterUserDto userDto = createRegisterUserDto("username", "password", "email@email.em", null);
when(pluginLoader.getPlugins(any(TypeFilter.class))).thenReturn(Collections.EMPTY_LIST);
when(bindingResult.hasErrors()).thenReturn(true);
authenticator.register(userDto);
verify(bindingResult, never()).rejectValue(anyString(), anyString(), anyString());
}
@Test
public void defaultRegistrationWithCorrectDetailsShouldBeSuccessful() throws Exception {
RegisterUserDto userDto = createRegisterUserDto("username", "password", "email@email.em", null);
when(pluginLoader.getPlugins(any(TypeFilter.class))).thenReturn(Collections.EMPTY_LIST);
when(bindingResult.hasErrors()).thenReturn(false);
authenticator.register(userDto);
verify(bindingResult, never()).rejectValue(anyString(), anyString(), anyString());
}
@Test
public void userShouldBeRegisteredUsingEncryptedPassword() throws Exception{
String password = "password";
RegisterUserDto registerUserDto = createRegisterUserDto("username", password, "email@email.em", null);
EncryptionService realEncryptionService = new EncryptionService(new Md5PasswordEncoder());
TransactionalAuthenticator authenticatorSpy = spy(new TransactionalAuthenticator(pluginLoader, userDao, groupDao,
realEncryptionService, mailService, avatarService, pluginService,
securityFacade, rememberMeServices, sessionStrategy, validator, authenticationManager));
authenticatorSpy.register(registerUserDto);
UserDto expected = new UserDto();
expected.setEmail("email@email.em");
expected.setUsername("username");
expected.setPassword(realEncryptionService.encryptPassword(password));
verify(authenticatorSpy).registerByPlugin(refEq(expected), eq(true), any(BindingResult.class));
verify(authenticatorSpy).storeRegisteredUser(refEq(expected));
}
@Test
public void userMustHaveInitialFieldValuesWhenDefaultRegistrationFailWithValidationError() throws Exception {
Validator customValidator = new Validator() {
@Override
public boolean supports(Class<?> clazz) {
return true;
}
@Override
public void validate(Object target, Errors errors) {
errors.rejectValue("userDto.email", "", "An email format should be like mail@mail.ru");
}
};
RegisterUserDto registerUserDto = createRegisterUserDto("username", "password", "email", null);
EncryptionService realEncryptionService = new EncryptionService(new Md5PasswordEncoder());
ReflectionTestUtils.setField(authenticator, "encryptionService", realEncryptionService);
ReflectionTestUtils.setField(authenticator, "validator", customValidator);
when(pluginService.getRegistrationPlugins()).thenReturn(Collections.EMPTY_MAP);
authenticator.register(registerUserDto);
RegisterUserDto expectedRegisterUserDto = createRegisterUserDto("username", "password", "email", null);
assertReflectionEquals(expectedRegisterUserDto, registerUserDto);
}
@Test
public void userMustHaveInitialFieldValuesWhenPluginRegistrationFailWithValidationError() throws Exception {
RegisterUserDto registerUserDto = createRegisterUserDto("username", "password", "email", null);
EncryptionService realEncryptionService = new EncryptionService(new Md5PasswordEncoder());
Map<String, String> errors = new HashMap<>();
errors.put("userDto.email", "An email format should be like mail@mail.ru");
ReflectionTestUtils.setField(authenticator, "encryptionService", realEncryptionService);
when(registrationPlugin.getState()).thenReturn(Plugin.State.ENABLED);
when(registrationPlugin.validateUser(registerUserDto.getUserDto(), 1L)).thenReturn(errors);
when(pluginService.getRegistrationPlugins())
.thenReturn(new ImmutableMap.Builder<Long, RegistrationPlugin>().put(1L, registrationPlugin).build());
authenticator.register(registerUserDto);
RegisterUserDto expectedRegisterUserDto = createRegisterUserDto("username", "password", "email", null);
assertReflectionEquals(expectedRegisterUserDto, registerUserDto);
}
@Test
public void userMustHaveInitialFieldValuesWhenPluginRegistrationFailWithRegistrationError() throws Exception {
RegisterUserDto registerUserDto = createRegisterUserDto("username", "password", "email", null);
EncryptionService realEncryptionService = new EncryptionService(new Md5PasswordEncoder());
Map<String, String> errors = new HashMap<>();
errors.put("userDto.email", "An email format should be like mail@mail.ru");
ReflectionTestUtils.setField(authenticator, "encryptionService", realEncryptionService);
when(registrationPlugin.getState()).thenReturn(Plugin.State.ENABLED);
when(registrationPlugin.registerUser(registerUserDto.getUserDto(), 1L)).thenReturn(errors);
when(pluginService.getRegistrationPlugins()).thenReturn(
new ImmutableMap.Builder<Long, RegistrationPlugin>().put(1L, registrationPlugin).build());
authenticator.register(registerUserDto);
RegisterUserDto expectedRegisterUserDto = createRegisterUserDto("username", "password", "email", null);
assertReflectionEquals(expectedRegisterUserDto, registerUserDto);
}
private RegisterUserDto createRegisterUserDto(String username, String password, String email, String honeypotCaptcha) {
RegisterUserDto registerUserDto = new RegisterUserDto();
UserDto userDto = new UserDto();
userDto.setUsername(username);
userDto.setEmail(email);
userDto.setPassword(password);
registerUserDto.setUserDto(userDto);
registerUserDto.setHoneypotCaptcha(honeypotCaptcha);
return registerUserDto;
}
private JCUser getDefaultUser() {
return new JCUser("user", "email@email.em", "password");
}
private LoginUserDto createDefaultLoginUserDto() {
return new LoginUserDto("user", "password", true, "192.168.1.1");
}
}