docs/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java

1146 lines
44 KiB
Java

package com.sismics.docs.rest.resource;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.sismics.docs.core.constant.AclTargetType;
import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.*;
import com.sismics.docs.core.dao.criteria.GroupCriteria;
import com.sismics.docs.core.dao.criteria.UserCriteria;
import com.sismics.docs.core.dao.dto.GroupDto;
import com.sismics.docs.core.dao.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.RoutingUtil;
import com.sismics.docs.core.util.authentication.AuthenticationUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.rest.util.ValidationUtil;
import com.sismics.security.UserPrincipal;
import com.sismics.util.JsonUtil;
import com.sismics.util.context.ThreadLocalContext;
import com.sismics.util.filter.TokenBasedSecurityFilter;
import com.sismics.util.totp.GoogleAuthenticator;
import com.sismics.util.totp.GoogleAuthenticatorKey;
import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonObjectBuilder;
import jakarta.servlet.http.Cookie;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.NewCookie;
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import java.util.Date;
import java.util.List;
import java.util.Set;
/**
* User REST resources.
*
* @author jtremeaux
*/
@Path("/user")
public class UserResource extends BaseResource {
/**
* Creates a new user.
*
* @api {put} /user Register a new user
* @apiName PutUser
* @apiGroup User
* @apiParam {String{3..50}} username Username
* @apiParam {String{8..50}} password Password
* @apiParam {String{1..100}} email E-mail
* @apiParam {Number} storage_quota Storage quota (in bytes)
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiError (server) PrivateKeyError Error while generating a private key
* @apiError (client) AlreadyExistingUsername Login already used
* @apiError (server) UnknownError Unknown server error
* @apiPermission admin
* @apiVersion 1.5.0
*
* @param username User's username
* @param password Password
* @param email E-Mail
* @return Response
*/
@PUT
public Response register(
@FormParam("username") String username,
@FormParam("password") String password,
@FormParam("email") String email,
@FormParam("storage_quota") String storageQuotaStr) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Validate the input data
username = ValidationUtil.validateLength(username, "username", 3, 50);
ValidationUtil.validateUsername(username, "username");
password = ValidationUtil.validateLength(password, "password", 8, 50);
email = ValidationUtil.validateLength(email, "email", 1, 100);
Long storageQuota = ValidationUtil.validateLong(storageQuotaStr, "storage_quota");
ValidationUtil.validateEmail(email, "email");
// Create the user
User user = new User();
user.setRoleId(Constants.DEFAULT_USER_ROLE);
user.setUsername(username);
user.setPassword(password);
user.setEmail(email);
user.setStorageQuota(storageQuota);
user.setOnboarding(true);
// Create the user
UserDao userDao = new UserDao();
try {
userDao.create(user, principal.getId());
} catch (Exception e) {
if ("AlreadyExistingUsername".equals(e.getMessage())) {
throw new ClientException("AlreadyExistingUsername", "Login already used", e);
} else {
throw new ServerException("UnknownError", "Unknown server error", e);
}
}
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Updates the current user informations.
*
* @api {post} /user Update the current user
* @apiName PostUser
* @apiGroup User
* @apiParam {String{8..50}} password Password
* @apiParam {String{1..100}} email E-mail
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied or connected as guest
* @apiError (client) ValidationError Validation error
* @apiPermission user
* @apiVersion 1.5.0
*
* @param password Password
* @param email E-Mail
* @return Response
*/
@POST
public Response update(
@FormParam("password") String password,
@FormParam("email") String email) {
if (!authenticate() || principal.isGuest()) {
throw new ForbiddenClientException();
}
// Validate the input data
password = ValidationUtil.validateLength(password, "password", 8, 50, true);
email = ValidationUtil.validateLength(email, "email", 1, 100, true);
// Update the user
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(principal.getName());
if (email != null) {
user.setEmail(email);
}
user = userDao.update(user, principal.getId());
// Change the password
if (StringUtils.isNotBlank(password)) {
user.setPassword(password);
userDao.updatePassword(user, principal.getId());
}
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Updates a user informations.
*
* @api {post} /user/:username Update a user
* @apiName PostUserUsername
* @apiGroup User
* @apiParam {String} username Username
* @apiParam {String{8..50}} password Password
* @apiParam {String{1..100}} email E-mail
* @apiParam {Number} storage_quota Storage quota (in bytes)
* @apiParam {Boolean} disabled Disabled status
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiError (client) UserNotFound User not found
* @apiPermission admin
* @apiVersion 1.5.0
*
* @param username Username
* @param password Password
* @param email E-Mail
* @return Response
*/
@POST
@Path("{username: [a-zA-Z0-9_@.-]+}")
public Response update(
@PathParam("username") String username,
@FormParam("password") String password,
@FormParam("email") String email,
@FormParam("storage_quota") String storageQuotaStr,
@FormParam("disabled") Boolean disabled) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Validate the input data
password = ValidationUtil.validateLength(password, "password", 8, 50, true);
email = ValidationUtil.validateLength(email, "email", 1, 100, true);
// Check if the user exists
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
throw new ClientException("UserNotFound", "The user does not exist");
}
// Update the user
if (email != null) {
user.setEmail(email);
}
if (StringUtils.isNotBlank(storageQuotaStr)) {
Long storageQuota = ValidationUtil.validateLong(storageQuotaStr, "storage_quota");
user.setStorageQuota(storageQuota);
}
if (disabled != null) {
// Cannot disable the admin user or the guest user
RoleBaseFunctionDao userBaseFuction = new RoleBaseFunctionDao();
Set<String> baseFunctionSet = userBaseFuction.findByRoleId(Sets.newHashSet(user.getRoleId()));
if (Constants.GUEST_USER_ID.equals(username) || baseFunctionSet.contains(BaseFunction.ADMIN.name())) {
disabled = false;
}
if (disabled && user.getDisableDate() == null) {
// Recording the disabled date
user.setDisableDate(new Date());
} else if (!disabled && user.getDisableDate() != null) {
// Emptying the disabled date
user.setDisableDate(null);
}
}
user = userDao.update(user, principal.getId());
// Change the password
if (StringUtils.isNotBlank(password)) {
user.setPassword(password);
userDao.updatePassword(user, principal.getId());
}
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* This resource is used to authenticate the user and create a user session.
* The "session" is only used to identify the user, no other data is stored in the session.
*
* @api {post} /user/login Login a user
* @apiDescription This resource creates an authentication token and gives it back in a cookie.
* All authenticated resources will check this cookie to find the user currently logged in.
* @apiName PostUserLogin
* @apiGroup User
* @apiParam {String} username Username
* @apiParam {String} password Password (optional for guest login)
* @apiParam {String} code TOTP validation code
* @apiParam {Boolean} remember If true, create a long lasted token
* @apiSuccess {String} auth_token A cookie named auth_token containing the token ID
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationCodeRequired A TOTP validation code is required
* @apiPermission none
* @apiVersion 1.5.0
*
* @param username Username
* @param password Password
* @param longLasted Remember the user next time, create a long lasted session.
* @return Response
*/
@POST
@Path("login")
public Response login(
@FormParam("username") String username,
@FormParam("password") String password,
@FormParam("code") String validationCodeStr,
@FormParam("remember") boolean longLasted) {
// Validate the input data
username = StringUtils.strip(username);
password = StringUtils.strip(password);
// Get the user
UserDao userDao = new UserDao();
User user = null;
if (Constants.GUEST_USER_ID.equals(username)) {
if (ConfigUtil.getConfigBooleanValue(ConfigType.GUEST_LOGIN)) {
// Login as guest
user = userDao.getActiveByUsername(Constants.GUEST_USER_ID);
}
} else {
// Login as a normal user
user = AuthenticationUtil.authenticate(username, password);
}
if (user == null) {
throw new ForbiddenClientException();
}
// Two factor authentication
if (user.getTotpKey() != null) {
// If TOTP is enabled, ask a validation code
if (Strings.isNullOrEmpty(validationCodeStr)) {
throw new ClientException("ValidationCodeRequired", "An OTP validation code is required");
}
// Check the validation code
int validationCode = ValidationUtil.validateInteger(validationCodeStr, "code");
GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
if (!googleAuthenticator.authorize(user.getTotpKey(), validationCode)) {
throw new ForbiddenClientException();
}
}
// Get the remote IP
String ip = request.getHeader("x-forwarded-for");
if (Strings.isNullOrEmpty(ip)) {
ip = request.getRemoteAddr();
}
// Create a new session token
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
AuthenticationToken authenticationToken = new AuthenticationToken()
.setUserId(user.getId())
.setLongLasted(longLasted)
.setIp(StringUtils.abbreviate(ip, 45))
.setUserAgent(StringUtils.abbreviate(request.getHeader("user-agent"), 1000));
String token = authenticationTokenDao.create(authenticationToken);
// Cleanup old session tokens
authenticationTokenDao.deleteOldSessionToken(user.getId());
JsonObjectBuilder response = Json.createObjectBuilder();
int maxAge = longLasted ? TokenBasedSecurityFilter.TOKEN_LONG_LIFETIME : -1;
NewCookie cookie = new NewCookie(TokenBasedSecurityFilter.COOKIE_NAME, token, "/", null, null, maxAge, false);
return Response.ok().entity(response.build()).cookie(cookie).build();
}
/**
* Logs out the user and deletes the active session.
*
* @api {post} /user/logout Logout a user
* @apiDescription This resource deletes the authentication token created by POST /user/login and removes the cookie.
* @apiName PostUserLogout
* @apiGroup User
* @apiSuccess {String} auth_token An expired cookie named auth_token containing no value
* @apiError (client) ForbiddenError Access denied
* @apiError (server) AuthenticationTokenError Error deleting the authentication token
* @apiPermission user
* @apiVersion 1.5.0
*
* @return Response
*/
@POST
@Path("logout")
public Response logout() {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the value of the session token
String authToken = getAuthToken();
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
AuthenticationToken authenticationToken = null;
if (authToken != null) {
authenticationToken = authenticationTokenDao.get(authToken);
}
// No token : nothing to do
if (authenticationToken == null) {
throw new ForbiddenClientException();
}
// Deletes the server token
try {
authenticationTokenDao.delete(authToken);
} catch (Exception e) {
throw new ServerException("AuthenticationTokenError", "Error deleting the authentication token: " + authToken, e);
}
// Deletes the client token in the HTTP response
JsonObjectBuilder response = Json.createObjectBuilder();
NewCookie cookie = new NewCookie(TokenBasedSecurityFilter.COOKIE_NAME, null, "/", null, 1, null, -1, new Date(1), false, false);
return Response.ok().entity(response.build()).cookie(cookie).build();
}
/**
* Deletes the current user.
*
* @api {delete} /user Delete the current user
* @apiDescription All associated entities will be deleted as well.
* @apiName DeleteUser
* @apiGroup User
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied or the user cannot be deleted
* @apiError (client) UserUsedInRouteModel The user is used in a route model
* @apiPermission user
* @apiVersion 1.5.0
*
* @return Response
*/
@DELETE
public Response delete() {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Ensure that the admin or guest users are not deleted
if (hasBaseFunction(BaseFunction.ADMIN) || principal.isGuest()) {
throw new ClientException("ForbiddenError", "This user cannot be deleted");
}
// Check that this user is not used in any workflow
String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.USER, principal.getName());
if (routeModelName != null) {
throw new ClientException("UserUsedInRouteModel", routeModelName);
}
// Find linked data
DocumentDao documentDao = new DocumentDao();
List<Document> documentList = documentDao.findByUserId(principal.getId());
FileDao fileDao = new FileDao();
List<File> fileList = fileDao.findByUserId(principal.getId());
// Delete the user
UserDao userDao = new UserDao();
userDao.delete(principal.getName(), principal.getId());
sendDeletionEvents(documentList, fileList);
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Deletes a user.
*
* @api {delete} /user/:username Delete a user
* @apiDescription All associated entities will be deleted as well.
* @apiName DeleteUserUsername
* @apiGroup User
* @apiParam {String} username Username
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied or the user cannot be deleted
* @apiError (client) UserNotFound The user does not exist
* @apiError (client) UserUsedInRouteModel The user is used in a route model
* @apiPermission admin
* @apiVersion 1.5.0
*
* @param username Username
* @return Response
*/
@DELETE
@Path("{username: [a-zA-Z0-9_@.-]+}")
public Response delete(@PathParam("username") String username) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Cannot delete the guest user
if (Constants.GUEST_USER_ID.equals(username)) {
throw new ClientException("ForbiddenError", "The guest user cannot be deleted");
}
// Check that the user exists
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
throw new ClientException("UserNotFound", "The user does not exist");
}
// Ensure that the admin user is not deleted
RoleBaseFunctionDao roleBaseFunctionDao = new RoleBaseFunctionDao();
Set<String> baseFunctionSet = roleBaseFunctionDao.findByRoleId(Sets.newHashSet(user.getRoleId()));
if (baseFunctionSet.contains(BaseFunction.ADMIN.name())) {
throw new ClientException("ForbiddenError", "The admin user cannot be deleted");
}
// Check that this user is not used in any workflow
String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.USER, username);
if (routeModelName != null) {
throw new ClientException("UserUsedInRouteModel", routeModelName);
}
// Find linked data
DocumentDao documentDao = new DocumentDao();
List<Document> documentList = documentDao.findByUserId(user.getId());
FileDao fileDao = new FileDao();
List<File> fileList = fileDao.findByUserId(user.getId());
// Delete the user
userDao.delete(user.getUsername(), principal.getId());
sendDeletionEvents(documentList, fileList);
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Disable time-based one-time password for a specific user.
*
* @api {post} /user/:username/disable_totp Disable TOTP authentication for a specific user
* @apiName PostUserUsernameDisableTotp
* @apiGroup User
* @apiParam {String} username Username
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied or connected as guest
* @apiError (client) ValidationError Validation error
* @apiPermission user
* @apiVersion 1.5.0
*
* @param username Username
* @return Response
*/
@POST
@Path("{username: [a-zA-Z0-9_@.-]+}/disable_totp")
public Response disableTotpUsername(@PathParam("username") String username) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Get the user
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
throw new ForbiddenClientException();
}
// Remove the TOTP key
user.setTotpKey(null);
userDao.update(user, principal.getId());
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Returns the information about the connected user.
*
* @api {get} /user Get the current user
* @apiName GetUser
* @apiGroup User
* @apiSuccess {Boolean} anonymous True if no user is connected
* @apiSuccess {Boolean} is_default_password True if the admin has the default password
* @apiSuccess {Boolean} onboarding True if the UI needs to display the onboarding
* @apiSuccess {String} username Username
* @apiSuccess {String} email E-mail
* @apiSuccess {Number} storage_quota Storage quota (in bytes)
* @apiSuccess {Number} storage_current Quota used (in bytes)
* @apiSuccess {Boolean} totp_enabled True if TOTP authentication is enabled
* @apiSuccess {String[]} base_functions Base functions
* @apiSuccess {String[]} groups Groups
* @apiPermission none
* @apiVersion 1.5.0
*
* @return Response
*/
@GET
public Response info() {
JsonObjectBuilder response = Json.createObjectBuilder();
if (!authenticate()) {
response.add("anonymous", true);
// Check if admin has the default password
UserDao userDao = new UserDao();
User adminUser = userDao.getById("admin");
if (adminUser != null && adminUser.getDeleteDate() == null) {
response.add("is_default_password", Constants.DEFAULT_ADMIN_PASSWORD.equals(adminUser.getPassword()));
}
} else {
// Update the last connection date
String authToken = getAuthToken();
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
authenticationTokenDao.updateLastConnectionDate(authToken);
// Build the response
response.add("anonymous", false);
UserDao userDao = new UserDao();
GroupDao groupDao = new GroupDao();
User user = userDao.getById(principal.getId());
List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria()
.setUserId(user.getId())
.setRecursive(true), null);
response.add("username", user.getUsername())
.add("email", user.getEmail())
.add("storage_quota", user.getStorageQuota())
.add("storage_current", user.getStorageCurrent())
.add("totp_enabled", user.getTotpKey() != null)
.add("onboarding", user.isOnboarding());
// Base functions
JsonArrayBuilder baseFunctions = Json.createArrayBuilder();
for (String baseFunction : ((UserPrincipal) principal).getBaseFunctionSet()) {
baseFunctions.add(baseFunction);
}
// Groups
JsonArrayBuilder groups = Json.createArrayBuilder();
for (GroupDto groupDto : groupDtoList) {
groups.add(groupDto.getName());
}
response.add("base_functions", baseFunctions)
.add("groups", groups)
.add("is_default_password", hasBaseFunction(BaseFunction.ADMIN) && Constants.DEFAULT_ADMIN_PASSWORD.equals(user.getPassword()));
}
return Response.ok().entity(response.build()).build();
}
/**
* Returns the information about a user.
*
* @api {get} /user/:username Get a user
* @apiName GetUserUsername
* @apiGroup User
* @apiParam {String} username Username
* @apiSuccess {String} username Username
* @apiSuccess {String} email E-mail
* @apiSuccess {Boolean} totp_enabled True if TOTP authentication is enabled
* @apiSuccess {Number} storage_quota Storage quota (in bytes)
* @apiSuccess {Number} storage_current Quota used (in bytes)
* @apiSuccess {String[]} groups Groups
* @apiSuccess {Boolean} disabled True if the user is disabled
* @apiError (client) ForbiddenError Access denied
* @apiError (client) UserNotFound The user does not exist
* @apiPermission user
* @apiVersion 1.5.0
*
* @param username Username
* @return Response
*/
@GET
@Path("{username: [a-zA-Z0-9_@.-]+}")
@Produces(MediaType.APPLICATION_JSON)
public Response view(@PathParam("username") String username) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
throw new ClientException("UserNotFound", "The user does not exist");
}
// Groups
GroupDao groupDao = new GroupDao();
List<GroupDto> groupDtoList = groupDao.findByCriteria(
new GroupCriteria().setUserId(user.getId()),
new SortCriteria(1, true));
JsonArrayBuilder groups = Json.createArrayBuilder();
for (GroupDto groupDto : groupDtoList) {
groups.add(groupDto.getName());
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("username", user.getUsername())
.add("groups", groups)
.add("email", user.getEmail())
.add("totp_enabled", user.getTotpKey() != null)
.add("storage_quota", user.getStorageQuota())
.add("storage_current", user.getStorageCurrent())
.add("disabled", user.getDisableDate() != null);
return Response.ok().entity(response.build()).build();
}
/**
* Returns all active users.
*
* @api {get} /user/list Get users
* @apiName GetUserList
* @apiGroup User
* @apiParam {Number} sort_column Column index to sort on
* @apiParam {Boolean} asc If true, sort in ascending order
* @apiParam {String} group Filter on this group
* @apiSuccess {Object[]} users List of users
* @apiSuccess {String} users.id ID
* @apiSuccess {String} users.username Username
* @apiSuccess {String} users.email E-mail
* @apiSuccess {Boolean} users.totp_enabled True if TOTP authentication is enabled
* @apiSuccess {Number} users.storage_quota Storage quota (in bytes)
* @apiSuccess {Number} users.storage_current Quota used (in bytes)
* @apiSuccess {Number} users.create_date Create date (timestamp)
* @apiSuccess {Number} users.disabled True if the user is disabled
* @apiError (client) ForbiddenError Access denied
* @apiPermission user
* @apiVersion 1.5.0
*
* @param sortColumn Sort index
* @param asc If true, ascending sorting, else descending
* @param groupName Only return users from this group
* @return Response
*/
@GET
@Path("list")
public Response list(
@QueryParam("sort_column") Integer sortColumn,
@QueryParam("asc") Boolean asc,
@QueryParam("group") String groupName) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
JsonArrayBuilder users = Json.createArrayBuilder();
SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);
// Validate the group
String groupId = null;
if (!Strings.isNullOrEmpty(groupName)) {
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group != null) {
groupId = group.getId();
}
}
UserDao userDao = new UserDao();
List<UserDto> userDtoList = userDao.findByCriteria(new UserCriteria().setGroupId(groupId), sortCriteria);
for (UserDto userDto : userDtoList) {
users.add(Json.createObjectBuilder()
.add("id", userDto.getId())
.add("username", userDto.getUsername())
.add("email", userDto.getEmail())
.add("totp_enabled", userDto.getTotpKey() != null)
.add("storage_quota", userDto.getStorageQuota())
.add("storage_current", userDto.getStorageCurrent())
.add("create_date", userDto.getCreateTimestamp())
.add("disabled", userDto.getDisableTimestamp() != null));
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("users", users);
return Response.ok().entity(response.build()).build();
}
/**
* Returns all active sessions.
*
* @api {get} /user/session Get active sessions
* @apiDescription This resource lists all active token which can be used to log in to the current user account.
* @apiName GetUserSession
* @apiGroup User
* @apiSuccess {Object[]} sessions List of sessions
* @apiSuccess {Number} create_date Create date of this token
* @apiSuccess {String} ip IP used to log in
* @apiSuccess {String} user_agent User agent used to log in
* @apiSuccess {Number} last_connection_date Last connection date (timestamp)
* @apiSuccess {Boolean} current If true, this token is the current one
* @apiError (client) ForbiddenError Access denied
* @apiPermission user
* @apiVersion 1.5.0
*
* @return Response
*/
@GET
@Path("session")
public Response session() {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the value of the session token
String authToken = getAuthToken();
JsonArrayBuilder sessions = Json.createArrayBuilder();
// The guest user cannot see other sessions
if (!principal.isGuest()) {
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
for (AuthenticationToken authenticationToken : authenticationTokenDao.getByUserId(principal.getId())) {
JsonObjectBuilder session = Json.createObjectBuilder()
.add("create_date", authenticationToken.getCreationDate().getTime())
.add("ip", JsonUtil.nullable(authenticationToken.getIp()))
.add("user_agent", JsonUtil.nullable(authenticationToken.getUserAgent()));
if (authenticationToken.getLastConnectionDate() != null) {
session.add("last_connection_date", authenticationToken.getLastConnectionDate().getTime());
}
session.add("current", authenticationToken.getId().equals(authToken));
sessions.add(session);
}
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("sessions", sessions);
return Response.ok().entity(response.build()).build();
}
/**
* Deletes all active sessions except the one used for this request.
*
* @api {delete} /user/session Delete all sessions
* @apiDescription This resource deletes all active token linked to this account, except the one used to make this request.
* @apiName DeleteUserSession
* @apiGroup User
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied or connected as guest
* @apiPermission user
* @apiVersion 1.5.0
*
* @return Response
*/
@DELETE
@Path("session")
public Response deleteSession() {
if (!authenticate() || principal.isGuest()) {
throw new ForbiddenClientException();
}
// Get the value of the session token
String authToken = getAuthToken();
// Remove other tokens
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
authenticationTokenDao.deleteByUserId(principal.getId(), authToken);
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Mark the onboarding experience as passed.
*
* @api {post} /user/onboarded Mark the onboarding experience as passed
* @apiDescription Once the onboarding experience has been passed by the user, this resource prevent it from being displayed again.
* @apiName PostUserOnboarded
* @apiGroup User
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiPermission user
* @apiVersion 1.7.0
*
* @return Response
*/
@POST
@Path("onboarded")
public Response onboarded() {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Save it
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(principal.getName());
user.setOnboarding(false);
userDao.updateOnboarding(user);
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Enable time-based one-time password.
*
* @api {post} /user/enable_totp Enable TOTP authentication
* @apiDescription This resource enables the Time-based One-time Password authentication.
* All following login will need a validation code generated from the given secret seed.
* @apiName PostUserEnableTotp
* @apiGroup User
* @apiSuccess {String} secret Secret TOTP seed to initiate the algorithm
* @apiError (client) ForbiddenError Access denied or connected as guest
* @apiPermission user
* @apiVersion 1.5.0
*
* @return Response
*/
@POST
@Path("enable_totp")
public Response enableTotp() {
if (!authenticate() || principal.isGuest()) {
throw new ForbiddenClientException();
}
// Create a new TOTP key
GoogleAuthenticator gAuth = new GoogleAuthenticator();
final GoogleAuthenticatorKey key = gAuth.createCredentials();
// Save it
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(principal.getName());
user.setTotpKey(key.getKey());
userDao.update(user, principal.getId());
JsonObjectBuilder response = Json.createObjectBuilder()
.add("secret", key.getKey());
return Response.ok().entity(response.build()).build();
}
/**
* Test time-based one-time password.
*
* @api {post} /user/test_totp Test TOTP authentication
* @apiDescription Test a TOTP validation code.
* @apiName PostUserTestTotp
* @apiParam {String} code TOTP validation code
* @apiGroup User
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError The validation code is not valid or access denied
* @apiPermission user
* @apiVersion 1.6.0
*
* @return Response
*/
@POST
@Path("test_totp")
public Response testTotp(@FormParam("code") String validationCodeStr) {
if (!authenticate() || principal.isGuest()) {
throw new ForbiddenClientException();
}
// Get the user
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(principal.getName());
// Test the validation code
if (user.getTotpKey() != null) {
int validationCode = ValidationUtil.validateInteger(validationCodeStr, "code");
GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
if (!googleAuthenticator.authorize(user.getTotpKey(), validationCode)) {
throw new ForbiddenClientException();
}
}
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Disable time-based one-time password for the current user.
*
* @api {post} /user/disable_totp Disable TOTP authentication for the current user
* @apiName PostUserDisableTotp
* @apiGroup User
* @apiParam {String{1..100}} password Password
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied or connected as guest
* @apiError (client) ValidationError Validation error
* @apiPermission user
* @apiVersion 1.5.0
*
* @param password Password
* @return Response
*/
@POST
@Path("disable_totp")
public Response disableTotp(@FormParam("password") String password) {
if (!authenticate() || principal.isGuest()) {
throw new ForbiddenClientException();
}
// Validate the input data
password = ValidationUtil.validateLength(password, "password", 1, 100, false);
// Check the password and get the user
UserDao userDao = new UserDao();
User user = userDao.authenticate(principal.getName(), password);
if (user == null) {
throw new ForbiddenClientException();
}
// Remove the TOTP key
user.setTotpKey(null);
userDao.update(user, principal.getId());
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.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) 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);
// Prepare response
Response response = Response.ok().entity(Json.createObjectBuilder()
.add("status", "ok")
.build()).build();
// Check for user existence
UserDao userDao = new UserDao();
List<UserDto> userDtoList = userDao.findByCriteria(new UserCriteria().setUserName(username), null);
if (userDtoList.isEmpty()) {
return response;
}
UserDto user = userDtoList.get(0);
// 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
return response;
}
/**
* 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.
*
* @return Token value
*/
private String getAuthToken() {
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (TokenBasedSecurityFilter.COOKIE_NAME.equals(cookie.getName())
&& !Strings.isNullOrEmpty(cookie.getValue())) {
return cookie.getValue();
}
}
}
return null;
}
/**
* Send the events about documents and files being deleted.
* @param documentList A document list
* @param fileList A file list
*/
private void sendDeletionEvents(List<Document> documentList, List<File> fileList) {
// Raise deleted events for documents
for (Document document : documentList) {
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
documentDeletedAsyncEvent.setUserId(principal.getId());
documentDeletedAsyncEvent.setDocumentId(document.getId());
ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent);
}
// Raise deleted events for files (don't bother sending document updated event)
for (File file : fileList) {
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
fileDeletedAsyncEvent.setUserId(principal.getId());
fileDeletedAsyncEvent.setFileId(file.getId());
fileDeletedAsyncEvent.setFileSize(file.getSize());
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
}
}
}