Closes #309: store onboarding status server side

This commit is contained in:
Benjamin Gamard 2019-05-06 18:12:44 +02:00
parent 8b1c41ae1e
commit 61b12bdebd
10 changed files with 140 additions and 52 deletions

View File

@ -171,6 +171,26 @@ public class UserDao {
return user; return user;
} }
/**
* Update the onboarding status.
*
* @param user User to update
* @return Updated user
*/
public User updateOnboarding(User user) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the user
Query q = em.createQuery("select u from User u where u.id = :id and u.deleteDate is null");
q.setParameter("id", user.getId());
User userDb = (User) q.getSingleResult();
// Update the user
userDb.setOnboarding(user.isOnboarding());
return user;
}
/** /**
* Gets a user by its ID. * Gets a user by its ID.
* *

View File

@ -47,6 +47,12 @@ public class User implements Loggable {
@Column(name = "USE_PRIVATEKEY_C", nullable = false, length = 100) @Column(name = "USE_PRIVATEKEY_C", nullable = false, length = 100)
private String privateKey; private String privateKey;
/**
* False when the user passed the onboarding.
*/
@Column(name = "USE_ONBOARDING_B", nullable = false)
private boolean onboarding;
/** /**
* TOTP secret key. * TOTP secret key.
*/ */
@ -198,6 +204,15 @@ public class User implements Loggable {
return this; return this;
} }
public boolean isOnboarding() {
return onboarding;
}
public User setOnboarding(boolean onboarding) {
this.onboarding = onboarding;
return this;
}
@Override @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)

View File

@ -1 +1 @@
db.version=22 db.version=23

View File

@ -0,0 +1,2 @@
alter table T_USER add column USE_ONBOARDING_B bit not null default 1;
update T_CONFIG set CFG_VALUE_C = '23' where CFG_ID_C = 'DB_VERSION';

View File

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

View File

@ -101,6 +101,7 @@ public class UserResource extends BaseResource {
user.setPassword(password); user.setPassword(password);
user.setEmail(email); user.setEmail(email);
user.setStorageQuota(storageQuota); user.setStorageQuota(storageQuota);
user.setOnboarding(true);
// Create the user // Create the user
UserDao userDao = new UserDao(); UserDao userDao = new UserDao();
@ -622,6 +623,7 @@ public class UserResource extends BaseResource {
* @apiGroup User * @apiGroup User
* @apiSuccess {Boolean} anonymous True if no user is connected * @apiSuccess {Boolean} anonymous True if no user is connected
* @apiSuccess {Boolean} is_default_password True if the admin has the default password * @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} username Username
* @apiSuccess {String} email E-mail * @apiSuccess {String} email E-mail
* @apiSuccess {Number} storage_quota Storage quota (in bytes) * @apiSuccess {Number} storage_quota Storage quota (in bytes)
@ -665,7 +667,8 @@ public class UserResource extends BaseResource {
.add("email", user.getEmail()) .add("email", user.getEmail())
.add("storage_quota", user.getStorageQuota()) .add("storage_quota", user.getStorageQuota())
.add("storage_current", user.getStorageCurrent()) .add("storage_current", user.getStorageCurrent())
.add("totp_enabled", user.getTotpKey() != null); .add("totp_enabled", user.getTotpKey() != null)
.add("onboarding", user.isOnboarding());
// Base functions // Base functions
JsonArrayBuilder baseFunctions = Json.createArrayBuilder(); JsonArrayBuilder baseFunctions = Json.createArrayBuilder();
@ -899,6 +902,39 @@ public class UserResource extends BaseResource {
return Response.ok().entity(response.build()).build(); 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. * Enable time-based one-time password.
* *

View File

@ -3,7 +3,7 @@
/** /**
* Document default controller. * Document default controller.
*/ */
angular.module('docs').controller('DocumentDefault', function ($scope, $rootScope, $state, Restangular, Upload, $translate, $uibModal, $dialog) { angular.module('docs').controller('DocumentDefault', function ($scope, $rootScope, $state, Restangular, Upload, $translate, $uibModal, $dialog, User) {
// Load user audit log // Load user audit log
Restangular.one('auditlog').get().then(function (data) { Restangular.one('auditlog').get().then(function (data) {
$scope.logs = data.logs; $scope.logs = data.logs;
@ -145,10 +145,12 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
// Onboarding // Onboarding
$translate('onboarding.step1.title').then(function () { $translate('onboarding.step1.title').then(function () {
if (localStorage.onboardingDisplayed || $(window).width() < 1000) { User.userInfo().then(function(userData) {
if (!userData.onboarding || $(window).width() < 1000) {
return; return;
} }
localStorage.onboardingDisplayed = true; Restangular.one('user').post('onboarded');
$rootScope.userInfo.onboarding = false;
$rootScope.onboardingEnabled = true; $rootScope.onboardingEnabled = true;
@ -190,3 +192,4 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
]; ];
}); });
}); });
});

View File

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

View File

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

View File

@ -172,7 +172,7 @@ public class TestUserResource extends BaseJerseyTest {
.get(); .get();
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.readEntity(JsonObject.class); json = response.readEntity(JsonObject.class);
Assert.assertEquals(true, json.getBoolean("anonymous")); Assert.assertTrue(json.getBoolean("anonymous"));
// Check alice user information // Check alice user information
json = target().path("/user").request() json = target().path("/user").request()
@ -187,8 +187,20 @@ public class TestUserResource extends BaseJerseyTest {
json = target().path("/user").request() json = target().path("/user").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, bobToken) .cookie(TokenBasedSecurityFilter.COOKIE_NAME, bobToken)
.get(JsonObject.class); .get(JsonObject.class);
Assert.assertTrue(json.getBoolean("onboarding"));
Assert.assertEquals("bob@docs.com", json.getString("email")); Assert.assertEquals("bob@docs.com", json.getString("email"));
// Pass onboarding
target().path("/user/onboarded").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, bobToken)
.post(Entity.form(new Form()), JsonObject.class);
// Check bob user information
json = target().path("/user").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, bobToken)
.get(JsonObject.class);
Assert.assertFalse(json.getBoolean("onboarding"));
// Test login KO (user not found) // Test login KO (user not found)
response = target().path("/user/login").request() response = target().path("/user/login").request()
.post(Entity.form(new Form() .post(Entity.form(new Form()