From d786862a609c692d1b5ddfe66b29c885ee40b006 Mon Sep 17 00:00:00 2001 From: Benjamin Gamard Date: Mon, 20 Nov 2017 21:21:50 +0100 Subject: [PATCH] Closes #167: disable users --- .../sismics/docs/core/dao/jpa/UserDao.java | 25 +++++++++++--- .../docs/core/dao/jpa/dto/UserDto.java | 16 ++++++++- .../com/sismics/docs/core/model/jpa/User.java | 22 ++++++++++--- .../src/main/resources/config.properties | 2 +- .../resources/db/update/dbupdate-014-0.sql | 2 ++ .../sismics/util/filter/SecurityFilter.java | 2 +- docs-web/src/dev/resources/config.properties | 2 +- .../docs/rest/resource/AppResource.java | 2 ++ .../docs/rest/resource/UserResource.java | 30 ++++++++++++++--- docs-web/src/main/webapp/src/locale/en.json | 3 +- .../src/partial/docs/settings.user.edit.html | 13 +++++++- .../src/partial/docs/settings.user.html | 3 +- docs-web/src/prod/resources/config.properties | 2 +- .../src/stress/resources/config.properties | 2 +- .../sismics/docs/rest/TestAppResource.java | 1 + .../sismics/docs/rest/TestUserResource.java | 33 ++++++++++++++++++- 16 files changed, 138 insertions(+), 22 deletions(-) create mode 100644 docs-core/src/main/resources/db/update/dbupdate-014-0.sql diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java index bd17e5b1..c727bddb 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java @@ -10,6 +10,7 @@ import com.sismics.docs.core.util.jpa.QueryParam; import com.sismics.docs.core.util.jpa.QueryUtil; import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.util.context.ThreadLocalContext; +import org.joda.time.DateTime; import org.mindrot.jbcrypt.BCrypt; import javax.persistence.EntityManager; @@ -37,7 +38,7 @@ public class UserDao { q.setParameter("username", username); try { User user = (User) q.getSingleResult(); - if (!BCrypt.checkpw(password, user.getPassword())) { + if (!BCrypt.checkpw(password, user.getPassword()) || user.getDisableDate() != null) { return null; } return user; @@ -98,7 +99,8 @@ public class UserDao { userFromDb.setStorageQuota(user.getStorageQuota()); userFromDb.setStorageCurrent(user.getStorageCurrent()); userFromDb.setTotpKey(user.getTotpKey()); - + userFromDb.setDisableDate(user.getDisableDate()); + // Create audit log AuditLogUtil.create(userFromDb, AuditLogType.UPDATE, userId); @@ -246,7 +248,7 @@ public class UserDao { Map parameterMap = new HashMap<>(); List criteriaList = new ArrayList<>(); - StringBuilder sb = new StringBuilder("select u.USE_ID_C as c0, u.USE_USERNAME_C as c1, u.USE_EMAIL_C as c2, u.USE_CREATEDATE_D as c3, u.USE_STORAGECURRENT_N as c4, u.USE_STORAGEQUOTA_N as c5, u.USE_TOTPKEY_C as c6"); + StringBuilder sb = new StringBuilder("select u.USE_ID_C as c0, u.USE_USERNAME_C as c1, u.USE_EMAIL_C as c2, u.USE_CREATEDATE_D as c3, u.USE_STORAGECURRENT_N as c4, u.USE_STORAGEQUOTA_N as c5, u.USE_TOTPKEY_C as c6, u.USE_DISABLEDATE_D as c7"); sb.append(" from T_USER u "); // Add search criterias @@ -283,7 +285,10 @@ public class UserDao { userDto.setCreateTimestamp(((Timestamp) o[i++]).getTime()); userDto.setStorageCurrent(((Number) o[i++]).longValue()); userDto.setStorageQuota(((Number) o[i++]).longValue()); - userDto.setTotpKey((String) o[i]); + userDto.setTotpKey((String) o[i++]); + if (o[i] != null) { + userDto.setDisableTimestamp(((Timestamp) o[i]).getTime()); + } userDtoList.add(userDto); } return userDtoList; @@ -299,4 +304,16 @@ public class UserDao { Query query = em.createNativeQuery("select sum(u.USE_STORAGECURRENT_N) from T_USER u where u.USE_DELETEDATE_D is null"); return ((Number) query.getSingleResult()).longValue(); } + + /** + * Returns the number of active users. + * + * @return Number of active users + */ + public long getActiveUserCount() { + EntityManager em = ThreadLocalContext.get().getEntityManager(); + Query query = em.createNativeQuery("select count(u.USE_ID_C) from T_USER u where u.USE_DELETEDATE_D is null and (u.USE_DISABLEDATE_D is null or u.USE_DISABLEDATE_D > :date)"); + query.setParameter("date", DateTime.now().minusMonths(1).toDate()); + return ((Number) query.getSingleResult()).longValue(); + } } diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/UserDto.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/UserDto.java index 081e3a5b..9085523c 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/UserDto.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/UserDto.java @@ -26,6 +26,11 @@ public class UserDto { */ private Long createTimestamp; + /** + * Disable date of this user. + */ + private Long disableTimestamp; + /** * Storage quota. */ @@ -72,7 +77,16 @@ public class UserDto { public void setCreateTimestamp(Long createTimestamp) { this.createTimestamp = createTimestamp; } - + + public Long getDisableTimestamp() { + return disableTimestamp; + } + + public UserDto setDisableTimestamp(Long disableTimestamp) { + this.disableTimestamp = disableTimestamp; + return this; + } + public Long getStorageQuota() { return storageQuota; } diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/User.java b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/User.java index 0f37b50c..b06d6d60 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/User.java +++ b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/User.java @@ -1,13 +1,12 @@ package com.sismics.docs.core.model.jpa; -import java.util.Date; +import com.google.common.base.MoreObjects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; - -import com.google.common.base.MoreObjects; +import java.util.Date; /** * User entity. @@ -84,6 +83,12 @@ public class User implements Loggable { @Column(name = "USE_DELETEDATE_D") private Date deleteDate; + /** + * Disable date. + */ + @Column(name = "USE_DISABLEDATE_D") + private Date disableDate; + public String getId() { return id; } @@ -147,7 +152,16 @@ public class User implements Loggable { this.deleteDate = deleteDate; return this; } - + + public Date getDisableDate() { + return disableDate; + } + + public User setDisableDate(Date disableDate) { + this.disableDate = disableDate; + return this; + } + public String getPrivateKey() { return privateKey; } diff --git a/docs-core/src/main/resources/config.properties b/docs-core/src/main/resources/config.properties index ca77caa4..f1624659 100644 --- a/docs-core/src/main/resources/config.properties +++ b/docs-core/src/main/resources/config.properties @@ -1 +1 @@ -db.version=13 \ No newline at end of file +db.version=14 \ No newline at end of file diff --git a/docs-core/src/main/resources/db/update/dbupdate-014-0.sql b/docs-core/src/main/resources/db/update/dbupdate-014-0.sql new file mode 100644 index 00000000..b4bdb01d --- /dev/null +++ b/docs-core/src/main/resources/db/update/dbupdate-014-0.sql @@ -0,0 +1,2 @@ +alter table T_USER add column USE_DISABLEDATE_D datetime; +update T_CONFIG set CFG_VALUE_C = '14' where CFG_ID_C = 'DB_VERSION'; diff --git a/docs-web-common/src/main/java/com/sismics/util/filter/SecurityFilter.java b/docs-web-common/src/main/java/com/sismics/util/filter/SecurityFilter.java index b4bad6e1..c4bb86a8 100644 --- a/docs-web-common/src/main/java/com/sismics/util/filter/SecurityFilter.java +++ b/docs-web-common/src/main/java/com/sismics/util/filter/SecurityFilter.java @@ -57,7 +57,7 @@ public abstract class SecurityFilter implements Filter { */ private void injectUser(HttpServletRequest request, User user) { // Check if the user is still valid - if (user != null && user.getDeleteDate() == null) { + if (user != null && user.getDeleteDate() == null && user.getDisableDate() == null) { injectAuthenticatedUser(request, user); } else { injectAnonymousUser(request); diff --git a/docs-web/src/dev/resources/config.properties b/docs-web/src/dev/resources/config.properties index b41be0b3..743723d1 100644 --- a/docs-web/src/dev/resources/config.properties +++ b/docs-web/src/dev/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=13 \ No newline at end of file +db.version=14 \ No newline at end of file diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java index ec4cbb15..41d2a5ad 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java @@ -63,6 +63,7 @@ public class AppResource extends BaseResource { * @apiSuccess {Boolean} guest_login True if guest login is enabled * @apiSuccess {String} total_memory Allocated JVM memory (in bytes) * @apiSuccess {String} free_memory Free JVM memory (in bytes) + * @apiSuccess {String} active_user_count Number of active users * @apiSuccess {String} global_storage_current Global storage currently used (in bytes) * @apiSuccess {String} global_storage_quota Maximum global storage (in bytes) * @apiPermission none @@ -89,6 +90,7 @@ public class AppResource extends BaseResource { .add("guest_login", guestLogin) .add("total_memory", Runtime.getRuntime().totalMemory()) .add("free_memory", Runtime.getRuntime().freeMemory()) + .add("active_user_count", userDao.getActiveUserCount()) .add("global_storage_current", userDao.getGlobalStorageCurrent()); if (globalQuota > 0) { response.add("global_storage_quota", globalQuota); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java index 1226fa27..ae092942 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java @@ -186,6 +186,7 @@ public class UserResource extends BaseResource { * @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 @@ -204,7 +205,8 @@ public class UserResource extends BaseResource { @PathParam("username") String username, @FormParam("password") String password, @FormParam("email") String email, - @FormParam("storage_quota") String storageQuotaStr) { + @FormParam("storage_quota") String storageQuotaStr, + @FormParam("disabled") Boolean disabled) { if (!authenticate()) { throw new ForbiddenClientException(); } @@ -218,7 +220,7 @@ public class UserResource extends BaseResource { UserDao userDao = new UserDao(); User user = userDao.getActiveByUsername(username); if (user == null) { - throw new ClientException("UserNotFound", "The user doesn't exist"); + throw new ClientException("UserNotFound", "The user does not exist"); } // Update the user @@ -229,6 +231,22 @@ public class UserResource extends BaseResource { 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 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 @@ -631,6 +649,7 @@ public class UserResource extends BaseResource { * @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 @@ -668,7 +687,8 @@ public class UserResource extends BaseResource { .add("groups", groups) .add("email", user.getEmail()) .add("storage_quota", user.getStorageQuota()) - .add("storage_current", user.getStorageCurrent()); + .add("storage_current", user.getStorageCurrent()) + .add("disabled", user.getDisableDate() != null); return Response.ok().entity(response.build()).build(); } @@ -688,6 +708,7 @@ public class UserResource extends BaseResource { * @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 @@ -730,7 +751,8 @@ public class UserResource extends BaseResource { .add("email", userDto.getEmail()) .add("storage_quota", userDto.getStorageQuota()) .add("storage_current", userDto.getStorageCurrent()) - .add("create_date", userDto.getCreateTimestamp())); + .add("create_date", userDto.getCreateTimestamp()) + .add("disabled", userDto.getDisableTimestamp() != null)); } JsonObjectBuilder response = Json.createObjectBuilder() diff --git a/docs-web/src/main/webapp/src/locale/en.json b/docs-web/src/main/webapp/src/locale/en.json index b17d36ac..a8c9a227 100644 --- a/docs-web/src/main/webapp/src/locale/en.json +++ b/docs-web/src/main/webapp/src/locale/en.json @@ -245,7 +245,8 @@ "storage_quota": "Storage quota", "storage_quota_placeholder": "Storage quota (in MB)", "password": "Password", - "password_confirm": "Password (confirm)" + "password_confirm": "Password (confirm)", + "disabled": "Disabled user" } }, "security": { diff --git a/docs-web/src/main/webapp/src/partial/docs/settings.user.edit.html b/docs-web/src/main/webapp/src/partial/docs/settings.user.edit.html index 9a0ffedf..a664d8ce 100644 --- a/docs-web/src/main/webapp/src/partial/docs/settings.user.edit.html +++ b/docs-web/src/main/webapp/src/partial/docs/settings.user.edit.html @@ -88,12 +88,23 @@ +
+
+
+ +
+
+
+
-
diff --git a/docs-web/src/main/webapp/src/partial/docs/settings.user.html b/docs-web/src/main/webapp/src/partial/docs/settings.user.html index 6cc614c8..d0dae4ad 100644 --- a/docs-web/src/main/webapp/src/partial/docs/settings.user.html +++ b/docs-web/src/main/webapp/src/partial/docs/settings.user.html @@ -16,7 +16,8 @@ - {{ user.username }} + {{ user.username }} + {{ user.username }} {{ user.create_date | date: dateFormat }} diff --git a/docs-web/src/prod/resources/config.properties b/docs-web/src/prod/resources/config.properties index b41be0b3..743723d1 100644 --- a/docs-web/src/prod/resources/config.properties +++ b/docs-web/src/prod/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=13 \ No newline at end of file +db.version=14 \ No newline at end of file diff --git a/docs-web/src/stress/resources/config.properties b/docs-web/src/stress/resources/config.properties index b41be0b3..743723d1 100644 --- a/docs-web/src/stress/resources/config.properties +++ b/docs-web/src/stress/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=13 \ No newline at end of file +db.version=14 \ No newline at end of file diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java index e5376fea..dab82572 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java @@ -37,6 +37,7 @@ public class TestAppResource extends BaseJerseyTest { Assert.assertTrue(totalMemory > 0 && totalMemory > freeMemory); Assert.assertFalse(json.getBoolean("guest_login")); Assert.assertTrue(json.containsKey("global_storage_current")); + Assert.assertTrue(json.getJsonNumber("active_user_count").longValue() > 0); // Rebuild Lucene index Response response = target().path("/app/batch/reindex").request() diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestUserResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestUserResource.java index 6cddb206..e9fe73a5 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestUserResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestUserResource.java @@ -57,6 +57,7 @@ public class TestUserResource extends BaseJerseyTest { Assert.assertNotNull(user.getJsonNumber("storage_current")); Assert.assertNotNull(user.getJsonNumber("create_date")); Assert.assertFalse(user.getBoolean("totp_enabled")); + Assert.assertFalse(user.getBoolean("disabled")); // Create a user KO (login length validation) Response response = target().path("/user").request() @@ -262,7 +263,7 @@ public class TestUserResource extends BaseJerseyTest { Assert.assertEquals("newadminemail@docs.com", json.getString("email")); // User admin update admin_user1 information - json = target().path("/user").request() + json = target().path("/user/admin_user1").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) .post(Entity.form(new Form() .param("email", " alice2@docs.com ")), JsonObject.class); @@ -276,6 +277,36 @@ public class TestUserResource extends BaseJerseyTest { json = response.readEntity(JsonObject.class); Assert.assertEquals("ForbiddenError", json.getString("type")); + // User admin disable admin_user1 + json = target().path("/user/admin_user1").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .post(Entity.form(new Form() + .param("disabled", "true")), JsonObject.class); + Assert.assertEquals("ok", json.getString("status")); + + // User admin_user1 tries to authenticate + response = target().path("/user/login").request() + .post(Entity.form(new Form() + .param("username", "admin_user1") + .param("password", "12345678") + .param("remember", "false"))); + Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus()); + + // User admin enable admin_user1 + json = target().path("/user/admin_user1").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .post(Entity.form(new Form() + .param("disabled", "false")), JsonObject.class); + Assert.assertEquals("ok", json.getString("status")); + + // User admin_user1 tries to authenticate + response = target().path("/user/login").request() + .post(Entity.form(new Form() + .param("username", "admin_user1") + .param("password", "12345678") + .param("remember", "false"))); + Assert.assertEquals(Status.OK.getStatusCode(), response.getStatus()); + // User admin deletes user admin_user1 json = target().path("/user/admin_user1").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)