From 92437394c6afb991a46a8184fc7dcdc3e4c743f2 Mon Sep 17 00:00:00 2001 From: Cristina Alvarino Date: Fri, 27 May 2011 14:27:49 +0200 Subject: [PATCH] Composite Handler LDAP-Database. Import of users from LDAP. Support of two types of users (LDAP and Database). FEA ItEr74S09LdapAuhentication --- .../common/entities/LDAPConfiguration.java | 8 +- .../business/users/entities/User.java | 4 +- .../business/users/entities/Users.hbm.xml | 2 +- .../LDAPCustomAuthenticationProvider.java | 207 ++++++++++++++++-- .../services/LDAPCustomContextSource.java | 36 +++ .../services/LDAPUserDetailsService.java | 11 +- ...lplanner-webapp-spring-security-config.xml | 22 +- 7 files changed, 242 insertions(+), 48 deletions(-) create mode 100644 navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPCustomContextSource.java diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/entities/LDAPConfiguration.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/entities/LDAPConfiguration.java index 8f47b0754..ddc76dafa 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/common/entities/LDAPConfiguration.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/entities/LDAPConfiguration.java @@ -26,6 +26,7 @@ import org.navalplanner.business.common.BaseEntity; * This entity will be used to store the LDAP connection properties for * authentication * + * @author Ignacio Diaz * @author Cristina Alvarino * */ @@ -48,11 +49,10 @@ public class LDAPConfiguration extends BaseEntity { private String ldapPassword; - // TODO Almacena si se guardarán los passwords del ldap en la bd + // LDAP passwords will be imported to DB or not private Boolean ldapSavePasswordsDB; - // TODO Guarda si se va a usar la autenticación con el ldap o no(la de - // navalplan) + // LDAP Authentication will be used or not private Boolean ldapAuthEnabled; public String getLdapUserId() { @@ -103,7 +103,7 @@ public class LDAPConfiguration extends BaseEntity { this.ldapPassword = ldapPassword; } - public Boolean getLdapSavePasswordsDB() { + public Boolean isLdapSavePasswordsDB() { return ldapSavePasswordsDB; } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/users/entities/User.java b/navalplanner-business/src/main/java/org/navalplanner/business/users/entities/User.java index 6f2f47cd8..2e93d6655 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/users/entities/User.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/users/entities/User.java @@ -38,6 +38,7 @@ import org.navalplanner.business.users.daos.IUserDAO; * @author Fernando Bellas Permuy * @author Jacobo Aragunde Perez * @author Cristina Alvarino Perez + * @author Ignacio Diaz Teijido * */ public class User extends BaseEntity { @@ -57,7 +58,7 @@ public class User extends BaseEntity { private Scenario lastConnectedScenario; // TODO if a user is a navalplan user or not (ldap) - private Boolean navalplanUser; + private Boolean navalplanUser = true; /** * Necessary for Hibernate. Please, do not call it. @@ -91,7 +92,6 @@ public class User extends BaseEntity { this.loginName = loginName; } - @NotEmpty(message = "password not specified") public String getPassword() { return password; } diff --git a/navalplanner-business/src/main/resources/org/navalplanner/business/users/entities/Users.hbm.xml b/navalplanner-business/src/main/resources/org/navalplanner/business/users/entities/Users.hbm.xml index 5b7ea3ef7..6ebfea337 100644 --- a/navalplanner-business/src/main/resources/org/navalplanner/business/users/entities/Users.hbm.xml +++ b/navalplanner-business/src/main/resources/org/navalplanner/business/users/entities/Users.hbm.xml @@ -21,7 +21,7 @@ - + diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPCustomAuthenticationProvider.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPCustomAuthenticationProvider.java index 2d79ab930..835e5dc23 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPCustomAuthenticationProvider.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPCustomAuthenticationProvider.java @@ -18,42 +18,65 @@ */ package org.navalplanner.web.users.services; +import org.navalplanner.business.common.IAdHocTransactionService; +import org.navalplanner.business.common.IOnTransaction; +import org.navalplanner.business.common.daos.IConfigurationDAO; +import org.navalplanner.business.common.entities.LDAPConfiguration; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.users.daos.IUserDAO; +import org.navalplanner.business.users.entities.User; +import org.navalplanner.business.users.entities.UserRole; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.filter.EqualsFilter; import org.springframework.security.AuthenticationException; import org.springframework.security.BadCredentialsException; +import org.springframework.security.providers.AuthenticationProvider; import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.security.providers.dao.AbstractUserDetailsAuthenticationProvider; import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.UserDetailsService; +import org.springframework.transaction.annotation.Transactional; /** * An extending from AbstractUserDetailsAuthenticationProvider class which is * used to implement the authentication against LDAP. * - * In the future this provider will implement all the process explained in - * * * At this time it authenticates user against LDAP and then searches it in BD to * use the BD user in application. * - * @author Ignacio Diaz - * @author Cristina Alvarino + * @author Ignacio Diaz Teijido + * @author Cristina Alvarino Perez * */ public class LDAPCustomAuthenticationProvider extends - AbstractUserDetailsAuthenticationProvider { + AbstractUserDetailsAuthenticationProvider implements + AuthenticationProvider { + + @Autowired + private IAdHocTransactionService transactionService; + + @Autowired + private IConfigurationDAO configurationDAO; + + @Autowired + private IUserDAO userDAO; + + private LDAPConfiguration configuration; // Template to search in LDAP private LdapTemplate ldapTemplate; - // Place in LDAP where username is - private String userId; - private UserDetailsService userDetailsService; + private DBPasswordEncoderService passwordEncoderService; + + private static final String COLON = ":"; + @Override protected void additionalAuthenticationChecks(UserDetails arg0, UsernamePasswordAuthenticationToken arg1) @@ -61,25 +84,169 @@ public class LDAPCustomAuthenticationProvider extends // No needed at this time } + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Transactional(readOnly = true) @Override public UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { - // Tests if the user is in LDAP, if not, throws a new - // AuthenticationException - if (!(ldapTemplate.authenticate(DistinguishedName.EMPTY_PATH, - new EqualsFilter(userId, username).toString(), authentication - .getCredentials().toString()))) { + final String usernameInserted = username; + User user = null; - throw new BadCredentialsException("User is not in LDAP."); + // Gets user from DB if exists + user = (User) transactionService + .runOnReadOnlyTransaction(new IOnTransaction() { + + @Override + public Object execute() { + try { + return userDAO.findByLoginName(usernameInserted); + } catch (InstanceNotFoundException e) { + return null; + } + } + }); + + // If user != null then exists in NavalPlan + if (null != user && user.isNavalplanUser()) { + // is a NavalPlan user, then we must authenticate against DB + String encodedPassword = passwordEncoderService.encodePassword( + authentication.getCredentials().toString(), username); + if (encodedPassword.equals(user.getPassword())) { + // user credentials are ok + return getUserDetailsService().loadUserByUsername(username); + } else { + throw new BadCredentialsException( + "Credentials are not the same as in database."); + } } else { - // Gets and returns user from DB once authenticated against LDAP - return getUserDetailsService().loadUserByUsername(username); + // is a LDAP or null user, then we must authenticate against LDAP + // if LDAP is enabled + // Gets the LDAPConfiguration properties + configuration = (LDAPConfiguration) transactionService + .runOnReadOnlyTransaction(new IOnTransaction() { + + @Override + public Object execute() { + return configurationDAO.getConfiguration() + .getLdapConfiguration(); + } + }); + + if (configuration.getLdapAuthEnabled()) { + + // Establishes the context for LDAP connection. + LDAPCustomContextSource context = (LDAPCustomContextSource) ldapTemplate + .getContextSource(); + context.setUrl(configuration.getLdapHost() + COLON + + configuration.getLdapPort()); + context.setBase(configuration.getLdapBase()); + context.setUserDn(configuration.getLdapUserDn()); + context.setPassword(configuration.getLdapPassword()); + try { + context.afterPropertiesSet(); + } catch (Exception e) { + // This exception will be never reached if the LDAP + // properties are + // well-formed. + e.printStackTrace(); + } + // Sets the new context to ldapTemplate + ldapTemplate.setContextSource(context); + + // Test authentication for user against LDAP + if (ldapTemplate.authenticate(DistinguishedName.EMPTY_PATH, + new EqualsFilter(configuration.getLdapUserId(), + username).toString(), authentication + .getCredentials().toString())) { + // Authentication against LDAP was ok + if (null == user) { + // user does not exist in NavalPlan must be imported + final User userNavalplan = User.create(); + userNavalplan.setLoginName(username); + // we must check if it is needed to save LDAP passwords + // in + // DB + String encodedPassword = null; + if (configuration.isLdapSavePasswordsDB()) + encodedPassword = passwordEncoderService + .encodePassword(authentication + .getCredentials().toString(), + username); + userNavalplan.setPassword(encodedPassword); + userNavalplan.setNavalplanUser(false); + userNavalplan.setDisabled(false); + userNavalplan.addRole(UserRole.ROLE_ADMINISTRATION); + transactionService + .runOnTransaction(new IOnTransaction() { + @Override + public Object execute() { + userDAO.save(userNavalplan); + return true; + } + }); + } else { + // user exists in NavalPlan + if (configuration.isLdapSavePasswordsDB()) { + String encodedPassword = passwordEncoderService + .encodePassword(authentication + .getCredentials().toString(), + username); + // We must test if user had password in database, + // because the configuration + // of importing passwords could be changed after the + // import of the user + // so the password could be null in database. + if (null == user.getPassword() + || !(user.getPassword() + .equals(encodedPassword))) { + user.setPassword(encodedPassword); + final User userNavalplan = user; + transactionService + .runOnTransaction(new IOnTransaction() { + @Override + public Object execute() { + userDAO.save(userNavalplan); + return true; + } + }); + } + } + } + // Gets and returns user from DB once authenticated against + // LDAP + return getUserDetailsService().loadUserByUsername(username); + } else { + throw new BadCredentialsException("User is not in LDAP."); + } + } else { + // LDAP is not enabled we must check if the LDAP user is in DB + String encodedPassword = passwordEncoderService.encodePassword( + authentication.getCredentials().toString(), username); + if (null != user.getPassword() + && encodedPassword.equals(user.getPassword())) { + // user credentials are ok + return getUserDetailsService().loadUserByUsername(username); + } else { + throw new BadCredentialsException( + "Authenticating LDAP user against LDAP. Maybe LDAP is out of service. " + + "Credentials are not the same as in database."); + } + } } } + public DBPasswordEncoderService getPasswordEncoderService() { + return passwordEncoderService; + } + + public void setPasswordEncoderService( + DBPasswordEncoderService passwordEncoderService) { + this.passwordEncoderService = passwordEncoderService; + } + // Getters and setters public LdapTemplate getLdapTemplate() { return ldapTemplate; @@ -89,14 +256,6 @@ public class LDAPCustomAuthenticationProvider extends this.ldapTemplate = ldapTemplate; } - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - public void setUserDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPCustomContextSource.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPCustomContextSource.java new file mode 100644 index 000000000..74093e86a --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPCustomContextSource.java @@ -0,0 +1,36 @@ +/* + * This file is part of NavalPlan + * + * Copyright (C) 2011 ComtecSF S.L. + * + * 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.ldap.core.support.LdapContextSource; + +/** + * A Class used to extend LDAPContextSource and set the properties for LDAP + * + * @author Cristina Alvarino Perez + * @author Ignacio Diaz Teijido + * + */ +public class LDAPCustomContextSource extends LdapContextSource { + + public LDAPCustomContextSource() { + super(); + this.setUrl("ldap://localhost:389"); + } +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPUserDetailsService.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPUserDetailsService.java index d3068c7db..69a13b99a 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPUserDetailsService.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/users/services/LDAPUserDetailsService.java @@ -43,8 +43,8 @@ import org.springframework.transaction.annotation.Transactional; * At this time it takes values from authenticated user (LDAP or DB) and gets * from DB the user properties. * - * @author Ignacio Diaz - * @author Cristina Alvarino + * @author Ignacio Diaz Teijido + * @author Cristina Alvarino Perez * */ public class LDAPUserDetailsService implements UserDetailsService { @@ -71,7 +71,10 @@ public class LDAPUserDetailsService implements UserDetailsService { scenario = PredefinedScenarios.MASTER.getScenario(); } - return new CustomUser(user.getLoginName(), user.getPassword(), + String password = user.getPassword(); + if (null == password) + password = "foo"; + return new CustomUser(user.getLoginName(), password, !user.isDisabled(), true, // accountNonExpired true, // credentialsNonExpired true, // accountNonLocked @@ -89,7 +92,5 @@ public class LDAPUserDetailsService implements UserDetailsService { } return grantedAuthorities; - } - } 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 ece1b1767..25fb1e40a 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 @@ -6,7 +6,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.1.xsd"> - @@ -64,31 +64,29 @@ class="org.springframework.security.providers.encoding.ShaPasswordEncoder"> + + --> - + these lines may be commented. --> + - + class="org.navalplanner.web.users.services.LDAPCustomContextSource"> + p:ldapTemplate-ref="ldapTemplate" + p:passwordEncoderService-ref="dbPasswordEncoderService"> @@ -111,5 +110,4 @@ Provider. --> - - + \ No newline at end of file