Merge remote-tracking branch 'origin/master'

This commit is contained in:
Benjamin Gamard 2019-05-07 14:38:15 +02:00
commit fcb018406d
10 changed files with 140 additions and 52 deletions

View File

@ -171,6 +171,26 @@ public class UserDao {
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.
*

View File

@ -46,7 +46,13 @@ public class User implements Loggable {
*/
@Column(name = "USE_PRIVATEKEY_C", nullable = false, length = 100)
private String privateKey;
/**
* False when the user passed the onboarding.
*/
@Column(name = "USE_ONBOARDING_B", nullable = false)
private boolean onboarding;
/**
* TOTP secret key.
*/
@ -198,6 +204,15 @@ public class User implements Loggable {
return this;
}
public boolean isOnboarding() {
return onboarding;
}
public User setOnboarding(boolean onboarding) {
this.onboarding = onboarding;
return this;
}
@Override
public String toString() {
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.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.setEmail(email);
user.setStorageQuota(storageQuota);
user.setOnboarding(true);
// Create the user
UserDao userDao = new UserDao();
@ -622,6 +623,7 @@ public class UserResource extends BaseResource {
* @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)
@ -665,8 +667,9 @@ public class UserResource extends BaseResource {
.add("email", user.getEmail())
.add("storage_quota", user.getStorageQuota())
.add("storage_current", user.getStorageCurrent())
.add("totp_enabled", user.getTotpKey() != null);
.add("totp_enabled", user.getTotpKey() != null)
.add("onboarding", user.isOnboarding());
// Base functions
JsonArrayBuilder baseFunctions = Json.createArrayBuilder();
for (String baseFunction : ((UserPrincipal) principal).getBaseFunctionSet()) {
@ -898,6 +901,39 @@ public class UserResource extends BaseResource {
.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.

View File

@ -3,7 +3,7 @@
/**
* 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
Restangular.one('auditlog').get().then(function (data) {
$scope.logs = data.logs;
@ -145,48 +145,51 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
// Onboarding
$translate('onboarding.step1.title').then(function () {
if (localStorage.onboardingDisplayed || $(window).width() < 1000) {
return;
}
localStorage.onboardingDisplayed = true;
$rootScope.onboardingEnabled = true;
$rootScope.onboardingSteps = [
{
title: $translate.instant('onboarding.step1.title'),
description: $translate.instant('onboarding.step1.description'),
position: 'centered',
width: 300
},
{
title: $translate.instant('onboarding.step2.title'),
description: $translate.instant('onboarding.step2.description'),
attachTo: '#document-add-btn',
position: 'right',
width: 300
},
{
title: $translate.instant('onboarding.step3.title'),
description: $translate.instant('onboarding.step3.description'),
attachTo: '#quick-upload-zone',
position: 'left',
width: 300
},
{
title: $translate.instant('onboarding.step4.title'),
description: $translate.instant('onboarding.step4.description'),
attachTo: '#search-box',
position: 'right',
width: 300
},
{
title: $translate.instant('onboarding.step5.title'),
description: $translate.instant('onboarding.step5.description'),
attachTo: '#navigation-tag',
position: "right",
width: 300
User.userInfo().then(function(userData) {
if (!userData.onboarding || $(window).width() < 1000) {
return;
}
];
Restangular.one('user').post('onboarded');
$rootScope.userInfo.onboarding = false;
$rootScope.onboardingEnabled = true;
$rootScope.onboardingSteps = [
{
title: $translate.instant('onboarding.step1.title'),
description: $translate.instant('onboarding.step1.description'),
position: 'centered',
width: 300
},
{
title: $translate.instant('onboarding.step2.title'),
description: $translate.instant('onboarding.step2.description'),
attachTo: '#document-add-btn',
position: 'right',
width: 300
},
{
title: $translate.instant('onboarding.step3.title'),
description: $translate.instant('onboarding.step3.description'),
attachTo: '#quick-upload-zone',
position: 'left',
width: 300
},
{
title: $translate.instant('onboarding.step4.title'),
description: $translate.instant('onboarding.step4.description'),
attachTo: '#search-box',
position: 'right',
width: 300
},
{
title: $translate.instant('onboarding.step5.title'),
description: $translate.instant('onboarding.step5.description'),
attachTo: '#navigation-tag',
position: "right",
width: 300
}
];
});
});
});

View File

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

View File

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

View File

@ -172,7 +172,7 @@ public class TestUserResource extends BaseJerseyTest {
.get();
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.readEntity(JsonObject.class);
Assert.assertEquals(true, json.getBoolean("anonymous"));
Assert.assertTrue(json.getBoolean("anonymous"));
// Check alice user information
json = target().path("/user").request()
@ -187,8 +187,20 @@ public class TestUserResource extends BaseJerseyTest {
json = target().path("/user").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, bobToken)
.get(JsonObject.class);
Assert.assertTrue(json.getBoolean("onboarding"));
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)
response = target().path("/user/login").request()
.post(Entity.form(new Form()