From 03d98349206a21ca38adb76dd53a0dd8945adb2b Mon Sep 17 00:00:00 2001 From: Javier Moran Rua Date: Fri, 11 Dec 2009 13:11:58 +0100 Subject: [PATCH] ItEr38S17ArquitecturaServidorItEr37S07: General refactoring to Spring Security integration and passwords encoded with SHA-2 (SHA-512). General refactoring to Spring Security integration and passwords econded with SHA-2 (SHA-512). "naval_user" and "user_roles" tables must be removed after applying this patch. --- ...ootstrap.java => IUsersBootstrapInDB.java} | 4 +- ...Bootstrap.java => UsersBootstrapInDB.java} | 23 +++++- .../services/DBPasswordEncoderService.java | 80 +++++++++++++++++++ ...Service.java => DBUserDetailsService.java} | 4 +- ...ce.java => IDBPasswordEncoderService.java} | 17 ++-- ...lplanner-webapp-spring-security-config.xml | 40 +++++++--- .../web/test/WebappGlobalNames.java | 7 +- ...pTest.java => UsersBootstrapInDBTest.java} | 14 ++-- .../DBPasswordEncoderServiceTest.java | 80 +++++++++++++++++++ ...est.java => DBUserDetailsServiceTest.java} | 15 ++-- ...ner-webapp-spring-security-config-test.xml | 23 ++++++ 11 files changed, 264 insertions(+), 43 deletions(-) rename navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/{IUsersBootstrap.java => IUsersBootstrapInDB.java} (88%) rename navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/{UsersBootstrap.java => UsersBootstrapInDB.java} (72%) create mode 100644 navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/DBPasswordEncoderService.java rename navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/{DefaultUserDetailsService.java => DBUserDetailsService.java} (95%) rename navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/{IPasswordEncoderService.java => IDBPasswordEncoderService.java} (72%) rename navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/bootstrap/{UsersBootstrapTest.java => UsersBootstrapInDBTest.java} (84%) create mode 100644 navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/services/DBPasswordEncoderServiceTest.java rename navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/services/{UserDetailsServiceTest.java => DBUserDetailsServiceTest.java} (85%) create mode 100644 navalplanner-webapp/src/test/resources/navalplanner-webapp-spring-security-config-test.xml diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/IUsersBootstrap.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/IUsersBootstrapInDB.java similarity index 88% rename from navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/IUsersBootstrap.java rename to navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/IUsersBootstrapInDB.java index de1d1bc0a..c3ab82f2d 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/IUsersBootstrap.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/IUsersBootstrapInDB.java @@ -23,8 +23,8 @@ package org.navalplanner.web.users.bootstrap; import org.navalplanner.business.IDataBootstrap; /** - * It creates necessary users initially. + * It registers mandatory users in the database (if not registered yet). * * @author Fernando Bellas Permuy */ -public interface IUsersBootstrap extends IDataBootstrap {} +public interface IUsersBootstrapInDB extends IDataBootstrap {} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/UsersBootstrap.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/UsersBootstrapInDB.java similarity index 72% rename from navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/UsersBootstrap.java rename to navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/UsersBootstrapInDB.java index 38e2e42d9..96b273452 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/UsersBootstrap.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/bootstrap/UsersBootstrapInDB.java @@ -22,20 +22,28 @@ package org.navalplanner.web.users.bootstrap; import org.navalplanner.business.users.daos.IUserDAO; import org.navalplanner.business.users.entities.User; +import org.navalplanner.web.users.services.IDBPasswordEncoderService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * @author Fernando Bellas Permuy */ -@Service @Transactional -public class UsersBootstrap implements IUsersBootstrap { +public class UsersBootstrapInDB implements IUsersBootstrapInDB { @Autowired private IUserDAO userDAO; + private IDBPasswordEncoderService dbPasswordEncoderService; + + public void setDbPasswordEncoderService( + IDBPasswordEncoderService dbPasswordEncoderService) { + + this.dbPasswordEncoderService = dbPasswordEncoderService; + + } + @Override public void loadRequiredData() { @@ -49,11 +57,18 @@ public class UsersBootstrap implements IUsersBootstrap { if (!userDAO.existsByLoginName(u.getLoginName())) { - userDAO.save(User.create(u.getLoginName(), u.getClearPassword(), + userDAO.save(User.create(u.getLoginName(), getEncodedPassword(u), u.getInitialRoles())); } } + private String getEncodedPassword(MandatoryUser u) { + + return dbPasswordEncoderService.encodePassword(u.getClearPassword(), + u.getLoginName()); + + } + } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/DBPasswordEncoderService.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/DBPasswordEncoderService.java new file mode 100644 index 000000000..1a5759e7a --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/DBPasswordEncoderService.java @@ -0,0 +1,80 @@ +/* + * This file is part of ###PROJECT_NAME### + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * 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 . + */ + +package org.navalplanner.web.users.services; + +import org.springframework.security.GrantedAuthority; +import org.springframework.security.providers.dao.SaltSource; +import org.springframework.security.providers.encoding.PasswordEncoder; +import org.springframework.security.userdetails.User; +import org.springframework.security.userdetails.UserDetails; + +/** + * For maximum flexibility, the implementation uses the password encoder and + * the salt source configured in the Spring Security configuration file (in + * consequence, it is possible to change the configuration to use any password + * encoder and/or salt source without modifying the implementation of this + * service). The only restriction the implementation imposes is that when using + * a reflection-based salt source, the "username" property must be specified. + * + * @author Fernando Bellas Permuy + */ +public class DBPasswordEncoderService implements IDBPasswordEncoderService { + + private SaltSource saltSource; + + private PasswordEncoder passwordEncoder; + + public void setSaltSource(SaltSource saltSource) { + this.saltSource = saltSource; + } + + public void setPasswordEncoder(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + } + + @Override + /** + * The second parameter, loginName, is used as a salt if the + * configured salt source is ReflectionSaltSource (which must + * be configured to use "username" property as a salt). + */ + public String encodePassword(String clearPassword, String loginName) { + + /* + * The only important parameter in User's constructor is "loginName", + * which corresponds to the "username" property if the "saltSource" is + * "ReflectionSaltSource". Note that "SystemWideSaltSource" ignores + * the "user" passed as a parameter to "saltSource.getSalt". + */ + UserDetails userDetails = new User(loginName, clearPassword, true, + true, true, true, new GrantedAuthority[0]); + + Object salt = null; + + if (saltSource != null) { + salt = saltSource.getSalt(userDetails); + } + + return passwordEncoder.encodePassword(clearPassword, salt); + + } + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/DefaultUserDetailsService.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/DBUserDetailsService.java similarity index 95% rename from navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/DefaultUserDetailsService.java rename to navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/DBUserDetailsService.java index 549da5170..740786d53 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/DefaultUserDetailsService.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/DBUserDetailsService.java @@ -35,7 +35,6 @@ import org.springframework.security.GrantedAuthorityImpl; import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.UserDetailsService; import org.springframework.security.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** @@ -45,8 +44,7 @@ import org.springframework.transaction.annotation.Transactional; * * @author Fernando Bellas Permuy */ -@Service("defaultUserDetailsService") -public class DefaultUserDetailsService implements UserDetailsService { +public class DBUserDetailsService implements UserDetailsService { @Autowired private IUserDAO userDAO; diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/IPasswordEncoderService.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/IDBPasswordEncoderService.java similarity index 72% rename from navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/IPasswordEncoderService.java rename to navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/IDBPasswordEncoderService.java index 60ac87886..83899d97c 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/IPasswordEncoderService.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/IDBPasswordEncoderService.java @@ -23,14 +23,7 @@ package org.navalplanner.web.users.services; /** * Service for encoding passwords when information about users * is stored in the database. In particular, it must be used to encode a - * password when creating a user and to change a user's password. For - * maximum flexibility, the implementation of the service uses the password - * encoder and the salt source configured in the Spring Security configuration - * file (in consequence, it is possible to change the configuration to use - * any password encoder and/or salt source without modifying the - * implementation of this service). The only restriction the implementation - * imposes is that when using a reflection-based salt source, the "username" - * property must be specified. + * password when creating a user and to change a user's password. * * When information about users is maintained externally (e.g. in a LDAP * server), this service is not used, since the Web application is not @@ -38,6 +31,12 @@ package org.navalplanner.web.users.services; * * @author Fernando Bellas Permuy */ -public interface IPasswordEncoderService { +public interface IDBPasswordEncoderService { + + /** + * Encodes a clear password. The second parameter (which must be the + * login name) may be used as a salt. + */ + public String encodePassword(String clearPassword, String loginName); } diff --git a/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-security-config.xml b/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-security-config.xml index b7b0f1b10..7ff84b2c3 100644 --- a/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-security-config.xml +++ b/navalplanner-webapp/src/main/resources/navalplanner-webapp-spring-security-config.xml @@ -3,6 +3,7 @@ @@ -28,16 +29,33 @@ - - - - - + + + + + + + + + + + + + + + + + diff --git a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/WebappGlobalNames.java b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/WebappGlobalNames.java index b6c0242a1..4c3646e3c 100644 --- a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/WebappGlobalNames.java +++ b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/WebappGlobalNames.java @@ -24,9 +24,14 @@ package org.navalplanner.web.test; * A class containing constants for global names. * * @author Manuel Rego Casasnovas + * @author Fernando Bellas Permuy */ public class WebappGlobalNames { - public final static String WEBAPP_SPRING_CONFIG_TEST_FILE = "classpath:/navalplanner-webapp-spring-config-test.xml"; + public final static String WEBAPP_SPRING_CONFIG_TEST_FILE = + "classpath:/navalplanner-webapp-spring-config-test.xml"; + + public final static String WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE = + "classpath:/navalplanner-webapp-spring-security-config-test.xml"; } diff --git a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/bootstrap/UsersBootstrapTest.java b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/bootstrap/UsersBootstrapInDBTest.java similarity index 84% rename from navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/bootstrap/UsersBootstrapTest.java rename to navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/bootstrap/UsersBootstrapInDBTest.java index 7eaf1dc13..df4a6320f 100644 --- a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/bootstrap/UsersBootstrapTest.java +++ b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/bootstrap/UsersBootstrapInDBTest.java @@ -24,13 +24,14 @@ import static org.junit.Assert.assertEquals; import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE; import static org.navalplanner.web.WebappGlobalNames.WEBAPP_SPRING_CONFIG_FILE; import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_CONFIG_TEST_FILE; +import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE; import org.junit.Test; import org.junit.runner.RunWith; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; import org.navalplanner.business.users.daos.IUserDAO; import org.navalplanner.business.users.entities.User; -import org.navalplanner.web.users.bootstrap.IUsersBootstrap; +import org.navalplanner.web.users.bootstrap.IUsersBootstrapInDB; import org.navalplanner.web.users.bootstrap.MandatoryUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; @@ -38,18 +39,19 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; /** - * Tests for IUsersBootstrap. + * Tests for IUsersBootstrapInDB. * * @author Fernando Bellas Permuy */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE, - WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE }) +@ContextConfiguration(locations = {BUSINESS_SPRING_CONFIG_FILE, + WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE, + WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE}) @Transactional -public class UsersBootstrapTest { +public class UsersBootstrapInDBTest { @Autowired - private IUsersBootstrap usersBootstrap; + private IUsersBootstrapInDB usersBootstrap; @Autowired private IUserDAO userDAO; diff --git a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/services/DBPasswordEncoderServiceTest.java b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/services/DBPasswordEncoderServiceTest.java new file mode 100644 index 000000000..a5256fa77 --- /dev/null +++ b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/services/DBPasswordEncoderServiceTest.java @@ -0,0 +1,80 @@ +/* + * This file is part of ###PROJECT_NAME### + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * 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 . + */ + +package org.navalplanner.web.test.users.services; + +import static org.junit.Assert.assertEquals; +import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE; +import static org.navalplanner.web.WebappGlobalNames.WEBAPP_SPRING_CONFIG_FILE; +import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_CONFIG_TEST_FILE; +import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.users.daos.IUserDAO; +import org.navalplanner.business.users.entities.User; +import org.navalplanner.web.users.bootstrap.IUsersBootstrapInDB; +import org.navalplanner.web.users.bootstrap.MandatoryUser; +import org.navalplanner.web.users.services.IDBPasswordEncoderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +/** + * Tests for DBPasswordEncoderService. + * + * @author Fernando Bellas Permuy + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = {BUSINESS_SPRING_CONFIG_FILE, + WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE, + WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE}) +@Transactional +public class DBPasswordEncoderServiceTest { + + @Autowired + private IDBPasswordEncoderService dbPasswordEncoderService; + + @Autowired + private IUsersBootstrapInDB usersBootstrap; + + @Autowired + private IUserDAO userDAO; + + @Test + public void testEncodePassword() throws InstanceNotFoundException { + + usersBootstrap.loadRequiredData(); + + for (MandatoryUser u : MandatoryUser.values()) { + + String encodedPassword = dbPasswordEncoderService.encodePassword( + u.getClearPassword(), u.getLoginName()); + User user = userDAO.findByLoginName(u.getLoginName()); + + assertEquals(user.getPassword(), encodedPassword); + + } + + } + +} diff --git a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/services/UserDetailsServiceTest.java b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/services/DBUserDetailsServiceTest.java similarity index 85% rename from navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/services/UserDetailsServiceTest.java rename to navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/services/DBUserDetailsServiceTest.java index b3e86c6e5..605d5b5d3 100644 --- a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/services/UserDetailsServiceTest.java +++ b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/users/services/DBUserDetailsServiceTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertEquals; import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE; import static org.navalplanner.web.WebappGlobalNames.WEBAPP_SPRING_CONFIG_FILE; import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_CONFIG_TEST_FILE; +import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE; import java.util.HashSet; import java.util.Set; @@ -31,7 +32,7 @@ import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; import org.navalplanner.business.users.entities.UserRole; -import org.navalplanner.web.users.bootstrap.IUsersBootstrap; +import org.navalplanner.web.users.bootstrap.IUsersBootstrapInDB; import org.navalplanner.web.users.bootstrap.MandatoryUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.GrantedAuthority; @@ -42,22 +43,22 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; /** - * Tests for implementations of Spring Security's - * UserDetailsService. + * Tests for DBUserDetailsService. * * @author Fernando Bellas Permuy */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE, - WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE }) +@ContextConfiguration(locations = {BUSINESS_SPRING_CONFIG_FILE, + WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE, + WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE}) @Transactional -public class UserDetailsServiceTest { +public class DBUserDetailsServiceTest { @Autowired private UserDetailsService userDetailsService; @Autowired - private IUsersBootstrap usersBootstrap; + private IUsersBootstrapInDB usersBootstrap; @Test public void testLoadUserByUsername() { diff --git a/navalplanner-webapp/src/test/resources/navalplanner-webapp-spring-security-config-test.xml b/navalplanner-webapp/src/test/resources/navalplanner-webapp-spring-security-config-test.xml new file mode 100644 index 000000000..3566ec820 --- /dev/null +++ b/navalplanner-webapp/src/test/resources/navalplanner-webapp-spring-security-config-test.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file