1
0
mirror of https://github.com/sismics/docs.git synced 2025-12-13 09:46:17 +00:00

#161: password recovery by email (wip, server part done)

This commit is contained in:
Benjamin Gamard
2017-11-17 22:03:54 +01:00
parent b8176a9fe9
commit 039d881a07
29 changed files with 881 additions and 66 deletions

View File

@@ -121,6 +121,12 @@
<artifactId>jersey-container-grizzly2-servlet</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.subethamail</groupId>
<artifactId>subethasmtp-wiser</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
db.version=12
db.version=13

View File

@@ -11,6 +11,8 @@ import com.sismics.docs.core.dao.jpa.dto.GroupDto;
import com.sismics.docs.core.dao.jpa.dto.UserDto;
import com.sismics.docs.core.event.DocumentDeletedAsyncEvent;
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
import com.sismics.docs.core.event.PasswordLostEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.*;
import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.docs.core.util.EncryptionUtil;
@@ -901,7 +903,110 @@ public class UserResource extends BaseResource {
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Create a key to reset a password and send it by email.
*
* @api {post} /user/password_lost Create a key to reset a password and send it by email
* @apiName PostUserPasswordLost
* @apiGroup User
* @apiParam {String} username Username
* @apiSuccess {String} status Status OK
* @apiError (client) UserNotFound The user is not found
* @apiError (client) ValidationError Validation error
* @apiPermission none
* @apiVersion 1.5.0
*
* @param username Username
* @return Response
*/
@POST
@Path("password_lost")
@Produces(MediaType.APPLICATION_JSON)
public Response passwordLost(@FormParam("username") String username) {
authenticate();
// Validate input data
ValidationUtil.validateStringNotBlank("username", username);
// Check for user existence
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
throw new ClientException("UserNotFound", "User not found: " + username);
}
// Create the password recovery key
PasswordRecoveryDao passwordRecoveryDao = new PasswordRecoveryDao();
PasswordRecovery passwordRecovery = new PasswordRecovery();
passwordRecovery.setUsername(user.getUsername());
passwordRecoveryDao.create(passwordRecovery);
// Fire a password lost event
PasswordLostEvent passwordLostEvent = new PasswordLostEvent();
passwordLostEvent.setUser(user);
passwordLostEvent.setPasswordRecovery(passwordRecovery);
AppContext.getInstance().getMailEventBus().post(passwordLostEvent);
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Reset the user's password.
*
* @api {post} /user/password_reset Reset the user's password
* @apiName PostUserPasswordReset
* @apiGroup User
* @apiParam {String} key Password recovery key
* @apiParam {String} password New password
* @apiSuccess {String} status Status OK
* @apiError (client) KeyNotFound Password recovery key not found
* @apiError (client) ValidationError Validation error
* @apiPermission none
* @apiVersion 1.5.0
*
* @param passwordResetKey Password reset key
* @param password New password
* @return Response
*/
@POST
@Path("password_reset")
@Produces(MediaType.APPLICATION_JSON)
public Response passwordReset(
@FormParam("key") String passwordResetKey,
@FormParam("password") String password) {
authenticate();
// Validate input data
ValidationUtil.validateRequired("key", passwordResetKey);
password = ValidationUtil.validateLength(password, "password", 8, 50, true);
// Load the password recovery key
PasswordRecoveryDao passwordRecoveryDao = new PasswordRecoveryDao();
PasswordRecovery passwordRecovery = passwordRecoveryDao.getActiveById(passwordResetKey);
if (passwordRecovery == null) {
throw new ClientException("KeyNotFound", "Password recovery key not found");
}
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(passwordRecovery.getUsername());
// Change the password
user.setPassword(password);
user = userDao.updatePassword(user, principal.getId());
// Deletes password recovery requests
passwordRecoveryDao.deleteActiveByLogin(user.getUsername());
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Returns the authentication token value.
*

View File

@@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
db.version=12
db.version=13

View File

@@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
db.version=12
db.version=13

View File

@@ -1,5 +1,6 @@
package com.sismics.docs.rest;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.util.filter.TokenBasedSecurityFilter;
import com.sismics.util.totp.GoogleAuthenticator;
import org.junit.Assert;
@@ -13,6 +14,8 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Exhaustive test of the user resource.
@@ -354,4 +357,79 @@ public class TestUserResource extends BaseJerseyTest {
.get(JsonObject.class);
Assert.assertFalse(json.getBoolean("totp_enabled"));
}
@Test
public void testResetPassword() throws Exception {
// Login admin
String adminToken = clientUtil.login("admin", "admin", false);
// Change SMTP configuration to target Wiser
target().path("/app/config_smtp").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
.post(Entity.form(new Form()
.param("hostname", "localhost")
.param("port", "2500")
.param("from", "contact@sismicsdocs.com")
), JsonObject.class);
// Create absent_minded who lost his password
clientUtil.createUser("absent_minded");
// User no_such_user try to recovery its password: invalid user
Response response = target().path("/user/password_lost").request()
.post(Entity.form(new Form()
.param("username", "no_such_user")));
Assert.assertEquals(Response.Status.BAD_REQUEST, Response.Status.fromStatusCode(response.getStatus()));
JsonObject json = response.readEntity(JsonObject.class);
Assert.assertEquals("UserNotFound", json.getString("type"));
// User absent_minded try to recovery its password: OK
json = target().path("/user/password_lost").request()
.post(Entity.form(new Form()
.param("username", "absent_minded")), JsonObject.class);
Assert.assertEquals("ok", json.getString("status"));
AppContext.getInstance().waitForAsync();
String emailBody = popEmail();
Assert.assertNotNull("No email to consume", emailBody);
Assert.assertTrue(emailBody.contains("Please reset your password"));
Pattern keyPattern = Pattern.compile("/passwordreset/(.+?)\"");
Matcher keyMatcher = keyPattern.matcher(emailBody);
Assert.assertTrue("Token not found", keyMatcher.find());
String key = keyMatcher.group(1).replaceAll("=", "");
// User absent_minded resets its password: invalid key
response = target().path("/user/password_reset").request()
.post(Entity.form(new Form()
.param("key", "no_such_key")
.param("password", "87654321")));
Assert.assertEquals(Response.Status.BAD_REQUEST, Response.Status.fromStatusCode(response.getStatus()));
json = response.readEntity(JsonObject.class);
Assert.assertEquals("KeyNotFound", json.getString("type"));
// User absent_minded resets its password: password invalid
response = target().path("/user/password_reset").request()
.post(Entity.form(new Form()
.param("key", key)
.param("password", " 1 ")));
Assert.assertEquals(Response.Status.BAD_REQUEST, Response.Status.fromStatusCode(response.getStatus()));
json = response.readEntity(JsonObject.class);
Assert.assertEquals("ValidationError", json.getString("type"));
Assert.assertTrue(json.getString("message"), json.getString("message").contains("password"));
// User absent_minded resets its password: OK
json = target().path("/user/password_reset").request()
.post(Entity.form(new Form()
.param("key", key)
.param("password", "87654321")), JsonObject.class);
Assert.assertEquals("ok", json.getString("status"));
// User absent_minded resets its password: expired key
response = target().path("/user/password_reset").request()
.post(Entity.form(new Form()
.param("key", key)
.param("password", "87654321")));
Assert.assertEquals(Response.Status.BAD_REQUEST, Response.Status.fromStatusCode(response.getStatus()));
json = response.readEntity(JsonObject.class);
Assert.assertEquals("KeyNotFound", json.getString("type"));
}
}