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 a6e1f56d663bf878bd9fb49f7995227233f1ff6d Author: Yannick Martel <martel@©odelutin.com> Date: Fri Nov 21 11:13:21 2014 +0100 finish user creation, add login method --- coselmar-rest/pom.xml | 13 +++ ...rationMail.java => UserAccountCreatedMail.java} | 12 +- .../services/config/CoselmarServicesConfig.java | 4 + .../config/CoselmarServicesConfigOption.java | 26 +++-- .../coselmar/services/v1/UsersWebService.java | 126 ++++++++++++++++++++- .../resources/mail/UserAccountCreatedMail.mustache | 8 ++ .../mail/UserAccountCreatedMail_fr.mustache | 8 ++ .../services/AbstractCoselmarWebServiceTest.java | 2 +- .../coselmar/services/UsersWebServiceTest.java | 21 +++- .../src/test/resources/coselmar-test.properties | 33 ++++++ pom.xml | 7 ++ 11 files changed, 235 insertions(+), 25 deletions(-) diff --git a/coselmar-rest/pom.xml b/coselmar-rest/pom.xml index 9043d77..07686e7 100644 --- a/coselmar-rest/pom.xml +++ b/coselmar-rest/pom.xml @@ -127,6 +127,13 @@ </dependency> <dependency> + <groupId>com.auth0</groupId> + <artifactId>java-jwt</artifactId> + <version>1.0.0</version> + </dependency> + + <!-- databases --> + <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> @@ -138,6 +145,12 @@ <scope>runtime</scope> </dependency> + <!-- Others --> + <dependency> + <groupId>com.github.spullara.mustache.java</groupId> + <artifactId>compiler</artifactId> + </dependency> + <!-- Tests --> <dependency> <groupId>junit</groupId> diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/CoselmarRegistrationMail.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserAccountCreatedMail.java similarity index 81% rename from coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/CoselmarRegistrationMail.java rename to coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserAccountCreatedMail.java index 80a0adb..4c82196 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/CoselmarRegistrationMail.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserAccountCreatedMail.java @@ -2,6 +2,7 @@ package fr.ifremer.coselmar.beans; import java.util.Locale; +import com.google.common.base.Strings; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.nuiton.i18n.I18n; @@ -9,7 +10,7 @@ import org.nuiton.i18n.I18n; /** * @author ymartel <martel@codelutin.com> */ -public class CoselmarRegistrationMail { +public class UserAccountCreatedMail { protected Locale locale; @@ -20,7 +21,7 @@ public class CoselmarRegistrationMail { private String coselmarUrl; private String password; - public CoselmarRegistrationMail(Locale locale) { + public UserAccountCreatedMail(Locale locale) { this.locale = locale; } @@ -32,15 +33,16 @@ public class CoselmarRegistrationMail { return to; } - public void addTo(String mail) { + public void setTo(String mail) { this.to = mail; } public String getSubject() { - if (user.getName() == null) { + String fullname = Strings.nullToEmpty(user.getFirstName()) + " " + Strings.nullToEmpty(user.getName()); + if (StringUtils.isBlank(fullname)) { return I18n.l(locale, "coselmar.service.mail.UserAccountCreatedEmail.subject", user.getMail()); } - return I18n.l(locale, "coselmar.service.mail.UserAccountCreatedEmail.subject", user.getName()); + return I18n.l(locale, "coselmar.service.mail.UserAccountCreatedEmail.subject", fullname); } @Override 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 ffbd096..2ced2d9 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 @@ -138,6 +138,10 @@ public class CoselmarServicesConfig { return isDevMode; } + public String getWebSecurityKey() { + return applicationConfig.getOption(CoselmarServicesConfigOption.WEB_SECURITY_KEY.key); + } + 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 3572f88..720f302 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 @@ -35,10 +35,10 @@ import org.nuiton.i18n.I18n; public enum CoselmarServicesConfigOption implements ConfigOptionDef { DATA_DIRECTORY( - "coselmar.data.directory", - I18n.n("coselmar.configuration.data.directory"), - "${java.io.tmpdir}/coselmar", - File.class), + "coselmar.data.directory", + I18n.n("coselmar.configuration.data.directory"), + "${java.io.tmpdir}/coselmar", + File.class), SMTP_HOST( "coselmar.smtp.host", @@ -56,15 +56,19 @@ public enum CoselmarServicesConfigOption implements ConfigOptionDef { "", String.class), LOG_CONFIGURATION_FILE( - "coselmar.logConfigurationFile", - "Path to the logs config file", - null, - String.class), + "coselmar.logConfigurationFile", + "Path to the logs config file", + null, String.class), DEV_MODE( - "pollen.devMode", - "Mode développement, court-circuite l'envoi de mail", - "true", Boolean.class), + "coselmar.devMode", + "Mode développement, court-circuite l'envoi de mail", + "true", Boolean.class), + + WEB_SECURITY_KEY( + "coselmar.web.security.key", + "Clef de sécurity permettant d'encoder les token d'authentication", + "iamageek,r3477y", String.class), ; protected final String key; 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 14590be..433c7c4 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 @@ -1,17 +1,35 @@ package fr.ifremer.coselmar.services.v1; +import java.io.StringWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; - +import java.util.Locale; +import java.util.Map; + +import com.auth0.jwt.Algorithm; +import com.auth0.jwt.JWTSigner; +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheException; +import com.github.mustachejava.MustacheFactory; import com.google.common.base.Preconditions; +import fr.ifremer.coselmar.beans.UserAccountCreatedMail; import fr.ifremer.coselmar.beans.UserBean; 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.CoselmarTechnicalException; import fr.ifremer.coselmar.services.CoselmarWebServiceSupport; +import fr.ifremer.coselmar.services.config.CoselmarServicesConfig; import fr.ifremer.coselmar.services.errors.InvalidCredentialException; +import org.apache.commons.io.Charsets; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; +import org.apache.commons.mail.Email; +import org.apache.commons.mail.EmailException; +import org.apache.commons.mail.SimpleEmail; +import org.debux.webmotion.server.render.Render; import static org.apache.commons.logging.LogFactory.getLog; @@ -77,16 +95,19 @@ public class UsersWebService extends CoselmarWebServiceSupport { commit(); // send mail to user with password + UserAccountCreatedMail userAccountCreatedMail = new UserAccountCreatedMail(getServicesContext().getLocale()); + userAccountCreatedMail.setUser(user); + userAccountCreatedMail.setPassword(password); + userAccountCreatedMail.setTo(user.getMail()); - - + sendMail(userAccountCreatedMail); } public void changePassword() { //TODO ymartel } - public void login(String mail, String password) throws InvalidCredentialException { + public Render login(String mail, String password) throws InvalidCredentialException { Preconditions.checkNotNull(mail); Preconditions.checkNotNull(password); @@ -99,6 +120,22 @@ public class UsersWebService extends CoselmarWebServiceSupport { throw new InvalidCredentialException("Invalid password given"); } + // return a Json Web Token for authentication + JWTSigner jwtSigner = new JWTSigner(getCoselmarServicesConfig().getWebSecurityKey()); + JWTSigner.Options signerOption = new JWTSigner.Options(); + signerOption.setAlgorithm(Algorithm.HS512); + + Map<String, Object> claims = new HashMap<>(); + String userTopiaId = user.getTopiaId(); + String shortId = getPersistenceContext().getTopiaIdFactory().getRandomPart(userTopiaId); + claims.put("userId", shortId); + claims.put("userFirstName", user.getFirstname()); + claims.put("userName", user.getName()); + claims.put("userRole", user.getRole()); + String webToken = jwtSigner.sign(claims, signerOption); + + return renderJSON("jwt", webToken); + } public void deleteUser(String userId) { @@ -111,4 +148,85 @@ public class UsersWebService extends CoselmarWebServiceSupport { commit(); } + + + ///////////////////////////////////////////// + /////////////// MAIL PART /////////////// + ///////////////////////////////////////////// + + protected void sendMail(UserAccountCreatedMail mail) { + + if (getCoselmarServicesConfig().isDevMode()) { + + if (log.isInfoEnabled()) { + log.info("an email should have been sent if not in devMode: to = " + + mail.getTo() + ". subject = '" + mail.getSubject() + + "'. body = \n" + getBody(mail)); + } + + if (!mail.isRecipientProvided() && log.isWarnEnabled()) { + log.warn("email has no recipient, would not have been sent " + mail); + } + + } else { + + CoselmarServicesConfig applicationConfig = getCoselmarServicesConfig(); + + String body = getBody(mail); + + if (mail.isRecipientProvided()) { + + Email newEmail = new SimpleEmail(); + newEmail.setHostName(applicationConfig.getSmtpHost()); + newEmail.setSmtpPort(applicationConfig.getSmtpPort()); + newEmail.setCharset(Charsets.UTF_8.name()); + newEmail.setSubject(mail.getSubject()); + + try { + newEmail.setFrom(applicationConfig.getSmtpFrom()); + String to = mail.getTo(); + newEmail.addTo(to); + newEmail.setMsg(body); + + newEmail.send(); + + } catch (EmailException e) { + throw new CoselmarTechnicalException(e); + } + + } else { + if (log.isErrorEnabled()) { + log.error("email has no recipient, won't be sent " + mail); + } + } + } + } + + protected String getBody(UserAccountCreatedMail mail) { + + Mustache mustache = getMustache(mail); + StringWriter stringWriter = new StringWriter(); + mustache.execute(stringWriter, mail); + + return stringWriter.toString(); + } + + protected Mustache getMustache(UserAccountCreatedMail mail) { + + MustacheFactory mustacheFactory = new DefaultMustacheFactory("mail/"); + Locale locale = mail.getLocale(); + String templateName = mail.getClass().getSimpleName() + "_" + locale.getLanguage() + ".mustache"; + + Mustache mustache; + try { + mustache = mustacheFactory.compile(templateName); + + } catch (MustacheException e) { + // fallback with no locale + templateName = mail.getClass().getSimpleName() + ".mustache"; + mustache = mustacheFactory.compile(templateName); + } + + return mustache; + } } diff --git a/coselmar-rest/src/main/resources/mail/UserAccountCreatedMail.mustache b/coselmar-rest/src/main/resources/mail/UserAccountCreatedMail.mustache new file mode 100644 index 0000000..10265c0 --- /dev/null +++ b/coselmar-rest/src/main/resources/mail/UserAccountCreatedMail.mustache @@ -0,0 +1,8 @@ +Welcome {{user.name}}, + +An account has been created for you on the web application Coselmar. + +Email: {{user.email}} +Password: {{password}} + +You can now access to your account by logging on the <a href="{{coselmarUrl}}">Coselmar</a> website. \ No newline at end of file diff --git a/coselmar-rest/src/main/resources/mail/UserAccountCreatedMail_fr.mustache b/coselmar-rest/src/main/resources/mail/UserAccountCreatedMail_fr.mustache new file mode 100644 index 0000000..ec531e4 --- /dev/null +++ b/coselmar-rest/src/main/resources/mail/UserAccountCreatedMail_fr.mustache @@ -0,0 +1,8 @@ +Bonjour {{user.name}}, + +Un compte vous a été ouvert sur l'application Coselmar. + +Courriel: {{user.email}} +Password: {{password}} + +Vous pouvez dès à présent accéder à l'application en vous connectant sur <a href="{{coselmarUrl}}">Coselmar</a>. \ No newline at end of file diff --git a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/AbstractCoselmarWebServiceTest.java b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/AbstractCoselmarWebServiceTest.java index b15688a..7a795f1 100644 --- a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/AbstractCoselmarWebServiceTest.java +++ b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/AbstractCoselmarWebServiceTest.java @@ -22,7 +22,7 @@ public class AbstractCoselmarWebServiceTest extends WebMotionTest { private static final Log log = getLog(AbstractCoselmarWebServiceTest.class); @Rule - public final FakeCoselmarApplicationContext application = new FakeCoselmarApplicationContext("coselmar-services.properties"); + public final FakeCoselmarApplicationContext application = new FakeCoselmarApplicationContext("coselmar-test.properties"); @Override protected int getPort() { diff --git a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/UsersWebServiceTest.java b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/UsersWebServiceTest.java index cb50372..0cb2cc4 100644 --- a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/UsersWebServiceTest.java +++ b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/UsersWebServiceTest.java @@ -1,7 +1,12 @@ package fr.ifremer.coselmar.services; import java.util.Locale; +import java.util.Map; +import com.auth0.jwt.JWTVerifier; +import com.google.gson.Gson; +import org.apache.commons.codec.binary.Base64; +import org.apache.http.HttpResponse; import org.apache.http.client.fluent.Request; import org.apache.http.client.fluent.Response; import org.junit.Assert; @@ -30,9 +35,8 @@ public class UsersWebServiceTest extends AbstractCoselmarWebServiceTest { .addParameter("user", "{firstName: 'test', name: 'her', mail: 'test@test.org', role: 'supervisor', qualification: 'unit tester', password : 'iamatester'}") .Post(); - String content = addUserRequest.execute().returnContent().asString(); - showTestResult(content); - Assert.assertNotNull(content); + HttpResponse addUserResponse = addUserRequest.execute().returnResponse(); + Assert.assertEquals(200, addUserResponse.getStatusLine().getStatusCode()); Request loginRequest = createRequest("/v1/login") .addParameter("mail", "test@test.org") @@ -40,7 +44,16 @@ public class UsersWebServiceTest extends AbstractCoselmarWebServiceTest { .Post(); Response loginResponse = loginRequest.execute(); - Assert.assertEquals(200, loginResponse.returnResponse().getStatusLine().getStatusCode()); + String loginContent = loginResponse.returnContent().asString(); + Assert.assertNotNull(loginContent); + showTestResult(loginContent); + Gson gson = new Gson(); + Map<String, String> map = gson.fromJson(loginContent, Map.class); + + String webSecurityKey = getServiceContext().getCoselmarServicesConfig().getWebSecurityKey(); + JWTVerifier jwtVerifier = new JWTVerifier(Base64.encodeBase64String(webSecurityKey.getBytes("utf8")), "audience"); + String token = map.get("jwt"); + jwtVerifier.verify(token); } } diff --git a/coselmar-rest/src/test/resources/coselmar-test.properties b/coselmar-rest/src/test/resources/coselmar-test.properties new file mode 100644 index 0000000..abd3051 --- /dev/null +++ b/coselmar-rest/src/test/resources/coselmar-test.properties @@ -0,0 +1,33 @@ +### +# #%L +# Coselmar :: Rest Services +# $Id:$ +# $HeadURL:$ +# %% +# Copyright (C) 2014 Ifremer, Code Lutin +# %% +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program. If not, see +# <http://www.gnu.org/licenses/gpl-3.0.html>. +# #L% +### + +hibernate.dialect=org.hibernate.dialect.H2Dialect +hibernate.connection.url=jdbc:h2:file:${coselmar.data.directory}/db/coselmar +hibernate.connection.username=sa +hibernate.connection.password=sa +hibernate.connection.driver_class=org.h2.Driver +hibernate.hbm2ddl.auto=update + +coselmar.devMode=true +coselmar.version=${project.version} diff --git a/pom.xml b/pom.xml index 57123ee..e2a1f04 100644 --- a/pom.xml +++ b/pom.xml @@ -337,6 +337,13 @@ <version>${slf4jVersion}</version> </dependency> + <!-- Others --> + <dependency> + <groupId>com.github.spullara.mustache.java</groupId> + <artifactId>compiler</artifactId> + <version>0.8.15</version> + </dependency> + <!-- Test --> <dependency> <groupId>org.mockito</groupId> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.