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:
@@ -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>
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=12
|
||||
db.version=13
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=12
|
||||
db.version=13
|
||||
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=12
|
||||
db.version=13
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user