This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit 077f21d986c2ad2fac97f141d797a231e660db39 Author: Yannick Martel <martel@©odelutin.com> Date: Tue Nov 18 18:12:59 2014 +0100 prepare user account creation and login operrations --- coselmar-rest/pom.xml | 46 +++++++++++++++ .../java/fr/ifremer/coselmar/beans/UserBean.java | 9 +++ .../services/CoselmarRestApplicationListener.java | 4 +- .../coselmar/services/CoselmarServicesContext.java | 11 ++-- .../services/CoselmarWebServiceSupport.java | 1 + .../services/DefaultCoselmarServicesContext.java | 67 ++++++++++++++++++++++ .../services/config/CoselmarServicesConfig.java | 44 +++++++++++--- .../config/CoselmarServicesConfigOption.java | 15 +++++ .../coselmar/services/v1/UsersWebService.java | 31 +++++++++- coselmar-rest/src/main/resources/mapping | 4 +- .../src/main/webapp/js/coselmar-controllers.js | 11 +++- .../src/main/webapp/js/coselmar-user-services.js | 28 ++++----- pom.xml | 49 ++++++++++++++++ 13 files changed, 287 insertions(+), 33 deletions(-) diff --git a/coselmar-rest/pom.xml b/coselmar-rest/pom.xml index b58962c..9043d77 100644 --- a/coselmar-rest/pom.xml +++ b/coselmar-rest/pom.xml @@ -70,6 +70,10 @@ <artifactId>commons-beanutils</artifactId> </dependency> <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-email</artifactId> + </dependency> + <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency> @@ -139,6 +143,48 @@ <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> + <dependency> + <groupId>org.nuiton.topia</groupId> + <artifactId>topia-junit</artifactId> + </dependency> + <dependency> + <groupId>org.debux.webmotion</groupId> + <artifactId>webmotion-unittest</artifactId> + <exclusions> + <exclusion> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-logging-juli</artifactId> + </exclusion> + <exclusion> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-jasper</artifactId> + </exclusion> + <exclusion> + <groupId>org.eclipse.jdt.core.compiler</groupId> + <artifactId>ecj</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-core</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-jasper</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-logging-juli</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-logging-log4j</artifactId> + </dependency> </dependencies> diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserBean.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserBean.java index 81246b8..d53648e 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserBean.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserBean.java @@ -13,6 +13,7 @@ public class UserBean implements Serializable { protected String mail; protected String role; protected String qualification; + protected String password; public UserBean(String id, String firstName, String name, String mail, String role, String qualification) { this.id = id; @@ -70,4 +71,12 @@ public class UserBean implements Serializable { public void setQualification(String qualification) { this.qualification = qualification; } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarRestApplicationListener.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarRestApplicationListener.java index 830d82f..e16b7a3 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarRestApplicationListener.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarRestApplicationListener.java @@ -29,6 +29,7 @@ import java.util.Set; import com.google.common.collect.Sets; import fr.ifremer.coselmar.beans.DocumentBean; +import fr.ifremer.coselmar.beans.UserBean; import fr.ifremer.coselmar.converter.DateConverter; import fr.ifremer.coselmar.converter.JsonArrayConverter; import fr.ifremer.coselmar.converter.JsonConverter; @@ -44,7 +45,8 @@ import org.debux.webmotion.server.mapping.Mapping; public class CoselmarRestApplicationListener implements WebMotionServerListener { protected static final Set<Class<?>> BEAN_TYPES = Sets.<Class<?>>newHashSet( - DocumentBean.class + DocumentBean.class, + UserBean.class ); @Override diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarServicesContext.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarServicesContext.java index 2c4e793..43bc0a9 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarServicesContext.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarServicesContext.java @@ -50,11 +50,10 @@ public interface CoselmarServicesContext { String getCleanMail(String email); - //TODO ymartel : think about it with user management -// String generatePassword(); -// -// String generateSalt(); -// -// String encodePassword(String salt, String password); + String generatePassword(); + + String generateSalt(); + + String encodePassword(String salt, String password); } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarWebServiceSupport.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarWebServiceSupport.java index 9c9d514..386619f 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarWebServiceSupport.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarWebServiceSupport.java @@ -105,4 +105,5 @@ public abstract class CoselmarWebServiceSupport extends WebMotionController impl public void rollback() { getPersistenceContext().rollback(); } + } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/DefaultCoselmarServicesContext.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/DefaultCoselmarServicesContext.java index bfa7c08..060e5b1 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/DefaultCoselmarServicesContext.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/DefaultCoselmarServicesContext.java @@ -26,19 +26,28 @@ package fr.ifremer.coselmar.services; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.Date; import java.util.Locale; import fr.ifremer.coselmar.persistence.CoselmarPersistenceContext; import fr.ifremer.coselmar.persistence.CoselmarTopiaApplicationContext; import fr.ifremer.coselmar.services.config.CoselmarServicesConfig; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; + +import static org.apache.commons.logging.LogFactory.getLog; /** * @author ymartel <martel@codelutin.com> */ public class DefaultCoselmarServicesContext implements CoselmarServicesContext { + private static final Log log = getLog(DefaultCoselmarServicesContext.class); + protected CoselmarServicesConfig servicesConfig; protected CoselmarPersistenceContext persistenceContext; @@ -123,4 +132,62 @@ public class DefaultCoselmarServicesContext implements CoselmarServicesContext { return email == null ? null : StringUtils.lowerCase(email.trim()); } + @Override + public String generateSalt() { + + SecureRandom secureRandom; + try { + secureRandom = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + secureRandom = new SecureRandom(); + } + byte[] salt = new byte[16]; + secureRandom.nextBytes(salt); + return salt.toString(); + } + + @Override + public String generatePassword() { + + return RandomStringUtils.randomAlphanumeric(8); + } + + @Override + public String encodePassword(String salt, String password) { + + String encodedPassword = getSecurePassword(salt, password, getCoselmarServicesConfig().getEncryptionAlgorithm()); + return encodedPassword; + } + + /** + * + * @param algorithm : Instance Type that will be loaded from MessageDigest, + * should be : MD5, SHA-1, SHA-256, SHA-384, SHA-512. + */ + protected static String getSecurePassword(String salt, String passwordToHash, String algorithm) { + + String generatedPassword; + + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + digest.update(salt.getBytes()); + byte[] passwordBytes = digest.digest(passwordToHash.getBytes()); + StringBuilder stringBuilder = new StringBuilder(); + for(int i=0; i < passwordBytes.length ;i++) + { + stringBuilder.append(Integer.toString((passwordBytes[i] & 0xff) + 0x100, 16).substring(1)); + } + generatedPassword = stringBuilder.toString(); + + } catch (NoSuchAlgorithmException e) { + String errorMessage = "Unable to encode password"; + if (log.isErrorEnabled()) { + log.error(errorMessage, e); + } + throw new CoselmarTechnicalException(errorMessage); + } + + return generatedPassword; + } + } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/config/CoselmarServicesConfig.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/config/CoselmarServicesConfig.java index fe2fae6..ffbd096 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/config/CoselmarServicesConfig.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/config/CoselmarServicesConfig.java @@ -59,7 +59,8 @@ public class CoselmarServicesConfig { if (defaultValues != null) { for (Map.Entry<Object, Object> entry : defaultValues.entrySet()) { - applicationConfig.setOption((String) entry.getKey(), (String) entry.getValue()); + applicationConfig.setOption((String) entry.getKey(), + (String) entry.getValue()); } } try { @@ -70,7 +71,8 @@ public class CoselmarServicesConfig { if (log.isInfoEnabled()) { StringBuilder builder = new StringBuilder("Coselmar configuration:"); builder.append("\nFilename: ").append(filename); - List<CoselmarServicesConfigOption> options = Lists.newArrayList(CoselmarServicesConfigOption.values()); + List<CoselmarServicesConfigOption> options = + Lists.newArrayList(CoselmarServicesConfigOption.values()); for (CoselmarServicesConfigOption option : options) { builder.append(String.format("\n%1$-40s = %2$s", option.getKey(), @@ -94,21 +96,49 @@ public class CoselmarServicesConfig { } public File getDataDirectory() { - return applicationConfig.getOptionAsFile(CoselmarServicesConfigOption.DATA_DIRECTORY.key); + return applicationConfig.getOptionAsFile( + CoselmarServicesConfigOption.DATA_DIRECTORY.key); + } + + /** + * @return Le nom d'hôte du serveur SMTP. + */ + public String getSmtpHost() { + return applicationConfig.getOption(CoselmarServicesConfigOption.SMTP_HOST.key); + } + + /** + * @return Le port du serveur SMTP. + */ + public int getSmtpPort() { + return applicationConfig.getOptionAsInt(CoselmarServicesConfigOption.SMTP_PORT.key); + } + + /** + * @return L'adresse d'expéditeur pour les mails de notifications + */ + public String getSmtpFrom() { + return applicationConfig.getOption(CoselmarServicesConfigOption.SMTP_FROM.key); } public boolean isLogConfigurationProvided() { - boolean logConfigurationProvided = - StringUtils.isNotBlank(applicationConfig.getOption(CoselmarServicesConfigOption.LOG_CONFIGURATION_FILE.key)); + boolean logConfigurationProvided = StringUtils.isNotBlank( + applicationConfig.getOption(CoselmarServicesConfigOption.LOG_CONFIGURATION_FILE.key)); return logConfigurationProvided; } public File getLogConfigurationFile() { - return applicationConfig.getOptionAsFile(CoselmarServicesConfigOption.LOG_CONFIGURATION_FILE.key); + return applicationConfig.getOptionAsFile( + CoselmarServicesConfigOption.LOG_CONFIGURATION_FILE.key); } public boolean isDevMode() { - boolean isDevMode = applicationConfig.getOptionAsBoolean(CoselmarServicesConfigOption.DEV_MODE.key); + boolean isDevMode = applicationConfig.getOptionAsBoolean( + CoselmarServicesConfigOption.DEV_MODE.key); return isDevMode; } + + public String getEncryptionAlgorithm() { + return "SHA-256"; + } } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/config/CoselmarServicesConfigOption.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/config/CoselmarServicesConfigOption.java index d616a55..3572f88 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/config/CoselmarServicesConfigOption.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/config/CoselmarServicesConfigOption.java @@ -40,6 +40,21 @@ public enum CoselmarServicesConfigOption implements ConfigOptionDef { "${java.io.tmpdir}/coselmar", File.class), + SMTP_HOST( + "coselmar.smtp.host", + "Nom d'hôte du serveur SMTP", + "", String.class), + + SMTP_PORT( + "coselmar.smtp.port", + "Le port du serveur SMTP", + "25", Integer.class), + + SMTP_FROM( + "coselmar.smtp.from", + "L'adresse d'expéditeur pour les mails de notifications", + "", String.class), + LOG_CONFIGURATION_FILE( "coselmar.logConfigurationFile", "Path to the logs config file", diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java index 3add9f3..14590be 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java @@ -9,6 +9,7 @@ import fr.ifremer.coselmar.converter.BeanEntityConverter; import fr.ifremer.coselmar.persistence.entity.User; import fr.ifremer.coselmar.persistence.entity.UserRole; import fr.ifremer.coselmar.services.CoselmarWebServiceSupport; +import fr.ifremer.coselmar.services.errors.InvalidCredentialException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; @@ -57,21 +58,49 @@ public class UsersWebService extends CoselmarWebServiceSupport { userEntity.setFirstname(user.getFirstName()); userEntity.setName(user.getName()); - userEntity.setMail(user.getMail()); + String mail = getCleanMail(user.getMail()); + userEntity.setMail(mail); userEntity.setRole(UserRole.valueOf(user.getRole().toUpperCase())); userEntity.setQualification(user.getQualification()); + String password = user.getPassword(); + if (StringUtils.isBlank(password)) { + password = getServicesContext().generatePassword(); + } + //generate a password & a salt + String salt = getServicesContext().generateSalt(); + String encodedPassword = getServicesContext().encodePassword(salt, password); + userEntity.setPassword(encodedPassword); + userEntity.setSalt(salt); commit(); // send mail to user with password + + + } public void changePassword() { //TODO ymartel } + public void login(String mail, String password) throws InvalidCredentialException { + Preconditions.checkNotNull(mail); + Preconditions.checkNotNull(password); + + User user = getUserDao().forMailEquals(getCleanMail(mail)).findAny(); + String salt = user.getSalt(); + String encodedPassword = getServicesContext().encodePassword(salt, password); + + String userPassword = user.getPassword(); + if (!encodedPassword.equals(userPassword)){ + throw new InvalidCredentialException("Invalid password given"); + } + + } + public void deleteUser(String userId) { // reconstitute full id diff --git a/coselmar-rest/src/main/resources/mapping b/coselmar-rest/src/main/resources/mapping index d18f439..a84b4c0 100644 --- a/coselmar-rest/src/main/resources/mapping +++ b/coselmar-rest/src/main/resources/mapping @@ -14,7 +14,8 @@ default.render=fr.ifremer.coselmar.services.CoselmarRender [errors] -#fr.ifremer.coselmar.services.errors.CoselmarUnauthorizedException ErrorAction.on403 +#fr.ifremer.coselmar.services.errors.InvalidCredentialException ErrorAction.on401 +#fr.ifremer.coselmar.services.errors.UnauthorizedException ErrorAction.on403 #fr.ifremer.coselmar.services.CoselmarTechnicalException ErrorAction.on500 #org.nuiton.topia.persistence.TopiaNoResultException ErrorAction.on404 @@ -39,4 +40,5 @@ GET /v1/users UsersWebService.getUsers GET /v1/users/{userId} UsersWebService.getUser POST /v1/users UsersWebService.addUser DELETE /v1/users/{userId} UsersWebService.deleteUser +POST /v1/login UsersWebService.login diff --git a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js index 5e5a756..02575e1 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js @@ -143,14 +143,19 @@ coselmarControllers.controller("UsersCtrl", ['$scope', '$route', '$routeParams', // Controller for new user View -coselmarControllers.controller("NewUserCtrl", ['$scope', '$route', 'userService', function($scope, $route, userService){ +coselmarControllers.controller("NewUserCtrl", ['$scope', '$route', '$location', 'userService', function($scope, $route, $location, userService){ + + $scope.user = {'role' : 'expert'}; $scope.createNewUser = function(){ -// var user = {'privacy':$scope.privacy, 'role':'expert'}; // Call service to create a new user - userService.createUser($scope.user, $scope, function() { + userService.createUser($scope.user, function() { $location.path("/users"); + },function(error) { + //TODO ymartel 20141118 : deal with error.status or statusText + console.log("error occurs"); + console.log(error.s); }); // Reload the page diff --git a/coselmar-ui/src/main/webapp/js/coselmar-user-services.js b/coselmar-ui/src/main/webapp/js/coselmar-user-services.js index 5ff11d0..c52e69a 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-user-services.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-user-services.js @@ -32,22 +32,22 @@ function User(resource){ this.resource = resource; // this.baseURL = "v1/users"; - this.createUser = function(user, scope, successFunction){ + this.createUser = function(user, successFunction, failFunction){ + + var formData = new FormData(); + formData.append("user", JSON.stringify(user)); -// var formData = new FormData(); -// formData.append("user", JSON.stringify(user)); // Save the User - var userResource = resource(baseURL, {user:user}); -// var userResource = resource(baseURL, null, { -// 'save': { -// method:'POST', -// transformRequest: angular.identity, -// headers:{ -// 'Content-Type':undefined -// } -// } -// }); - userResource.save({user:user}, successFunction); + var userResource = resource(baseURL, null, { + 'save': { + method:'POST', + transformRequest: angular.identity, + headers:{ + 'Content-Type': undefined + } + } + }); + userResource.save(null, formData, successFunction, failFunction); } this.getUser = function(id, scope){ diff --git a/pom.xml b/pom.xml index a11ecaa..57123ee 100644 --- a/pom.xml +++ b/pom.xml @@ -143,6 +143,8 @@ <postgresqlVersion>9.1-901-1.jdbc4</postgresqlVersion> <h2Version>1.4.178</h2Version> + <tomcatEmbedVersion>7.0.50</tomcatEmbedVersion> + </properties> @@ -270,6 +272,11 @@ <version>1.9.2</version> </dependency> <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-email</artifactId> + <version>1.3.2</version> + </dependency> + <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> @@ -350,6 +357,48 @@ <version>4.11</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.nuiton.topia</groupId> + <artifactId>topia-junit</artifactId> + <version>${topiaVersion}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.debux.webmotion</groupId> + <artifactId>webmotion-unittest</artifactId> + <version>${webmotionVersion}</version> + <scope>test</scope> + </dependency> + + + <!-- for embedded test --> + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-core</artifactId> + <version>${tomcatEmbedVersion}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-logging-juli</artifactId> + <version>${tomcatEmbedVersion}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-logging-log4j</artifactId> + <version>${tomcatEmbedVersion}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-jasper</artifactId> + <version>${tomcatEmbedVersion}</version> + <scope>provided</scope> + </dependency> </dependencies> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.