diff --git a/src/main/html/webapp/components/core/user/profile/profile.js b/src/main/html/webapp/components/core/user/profile/profile.js index 5142df29..22a71d9c 100644 --- a/src/main/html/webapp/components/core/user/profile/profile.js +++ b/src/main/html/webapp/components/core/user/profile/profile.js @@ -18,18 +18,37 @@ export default Control.extend({ "init": function(element, options) { + this.emailRequired = options.appState.attr('emailRequired'); $(element).hide(); User.findOne({ user: 'me' }, function(user) { $(element).html(template({ - user: user + user: user, + anonymousAccount: (!options.appState.attr('emailRequired')), + emailProvided: (user.attr('mail') != "" && user.attr('mail') != undefined), + userEmailDescription: options.appState.attr('userEmailDescription'), + userWithoutEmailDescription: options.appState.attr('userWithoutEmailDescription') })); options.user = user; $(element).fadeIn(); }); }, + "#anonymous click" : function(){ + if (!this.emailRequired){ + var anonymousControl = $(this.element).find("[name='anonymous']"); + var anonymous = !anonymousControl.is(':checked'); + var mail = $(this.element).find("[name='mail']"); + if (anonymous){ + mail.attr('disabled','disabled'); + } else { + mail.removeAttr('disabled'); + } + mail.val(""); + } + }, + 'submit': function(element, event) { event.preventDefault(); var user = new User(); @@ -39,10 +58,20 @@ export default Control.extend({ var fullnameError = user.checkName(fullname.val()); this.updateControl(fullname, fullnameError); + var anonymous = false; + if (!this.emailRequired){ + var anonymousControl = $(this.element).find("[name='anonymous']"); + anonymous = !anonymousControl.is(':checked'); + } + // mail var mail = $(element).find("[name='mail']"); - var mailError = user.checkMail(mail.val()); - this.updateControl(mail, mailError); + if (!anonymous){ + var mailError = user.checkMail(mail.val()); + this.updateControl(mail, mailError); + } else { + this.updateControl(mail, undefined); + } // password if password is not empty. else no password update on server side var newPassword = $(element).find("[name='new-password']"); @@ -204,4 +233,4 @@ export default Control.extend({ } -}); \ No newline at end of file +}); diff --git a/src/main/html/webapp/components/core/user/profile/profile.stache b/src/main/html/webapp/components/core/user/profile/profile.stache index e58d5281..a9b3de9d 100644 --- a/src/main/html/webapp/components/core/user/profile/profile.stache +++ b/src/main/html/webapp/components/core/user/profile/profile.stache @@ -16,12 +16,34 @@ Please fill out the form below to change your account settings or your password.
+ {{#is(anonymousAccount, false)}} +
+ {{else}} +
+
+ +
+

+ {{userEmailDescription}} +

+ + +
+


+ {{userWithoutEmailDescription}} +

+
+
+
+ + {{/is}} +

Change password

@@ -41,9 +63,9 @@ Please fill out the form below to change your account settings or your password.
- +

- +

API Access

This service provides a rich RestAPI to submit, monitor and download jobs.

@@ -59,7 +81,7 @@ Please fill out the form below to change your account settings or your password. {{../user.apiTokenMessage}} {{else}} - Expires in + Expires in +
+

+ {{userEmailDescription}} +

+ + +
+
+ +
+

+ {{userWithoutEmailDescription}} +

+
+ +
+ {{/is}} +
diff --git a/src/main/java/cloudgene/mapred/core/Template.java b/src/main/java/cloudgene/mapred/core/Template.java index fd2f9bfd..9c845940 100644 --- a/src/main/java/cloudgene/mapred/core/Template.java +++ b/src/main/java/cloudgene/mapred/core/Template.java @@ -18,6 +18,10 @@ public class Template { public static final String FOOTER_SUBMIT_JOB = "FOOTER_SUBMIT_JOB"; public static final String TERMS = "TERMS"; + + public static final String USER_EMAIL_DESCRIPTION = "USER_EMAIL_DESCRIPTION"; + + public static final String USER_WITHOUT_EMAIL_DESCRIPTION = "USER_WITHOUT_EMAIL_DESCRIPTION"; public static final Template[] SNIPPETS = new Template[] { @@ -42,7 +46,11 @@ public class Template { new Template(FOOTER_SUBMIT_JOB, ""), new Template(TERMS, "I will not attempt to re-identify or contact research participants.
" + - "I will report any inadvertent data release, security breach or other data management incident of which I become aware.") + "I will report any inadvertent data release, security breach or other data management incident of which I become aware."), + + new Template(USER_EMAIL_DESCRIPTION, "Receive email notifications when jobs are completed."), + + new Template(USER_WITHOUT_EMAIL_DESCRIPTION, "You can enter your email address at any time to upgrade your account.") }; diff --git a/src/main/java/cloudgene/mapred/core/User.java b/src/main/java/cloudgene/mapred/core/User.java index fa80a22d..1b8f7ad8 100644 --- a/src/main/java/cloudgene/mapred/core/User.java +++ b/src/main/java/cloudgene/mapred/core/User.java @@ -99,6 +99,15 @@ public boolean hasRole(String role) { return false; } + public void replaceRole(String oldRole, String newRole) { + for (int i = 0; i < roles.length; i++) { + if (roles[i].equalsIgnoreCase(oldRole)) { + roles[i] = newRole; + return; + } + } + } + public boolean isAdmin() { return hasRole(ROLE_ADMIN); } diff --git a/src/main/java/cloudgene/mapred/server/controller/UserController.java b/src/main/java/cloudgene/mapred/server/controller/UserController.java index 77840812..b2a05335 100644 --- a/src/main/java/cloudgene/mapred/server/controller/UserController.java +++ b/src/main/java/cloudgene/mapred/server/controller/UserController.java @@ -104,7 +104,9 @@ public UserResponse get(Authentication authentication, String user2) { @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Secured(SecurityRule.IS_AUTHENTICATED) public HttpResponse update(Authentication authentication, String user2, @Nullable String username, - @Parameter("full-name") String full_name, String mail, @Parameter("new-password") String new_password, @Parameter("confirm-new-password") String confirm_new_password) { + @Parameter("full-name") String full_name, @Nullable String mail, + @Nullable @Parameter("new-password") String new_password, + @Nullable @Parameter("confirm-new-password") String confirm_new_password) { User user = authenticationService.getUserByAuthentication(authentication); @@ -154,8 +156,8 @@ public HttpResponse resetPassword(@Nullable String username) { @Post("/api/v2/users/register") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Secured(SecurityRule.IS_ANONYMOUS) - public HttpResponse register(String username, @Parameter("full-name") String full_name, String mail, - @Parameter("new-password") String new_password, @Parameter("confirm-new-password") String confirm_new_password) { + public HttpResponse register(String username, @Parameter("full-name") String full_name, @Nullable String mail, + @Nullable @Parameter("new-password") String new_password, @Nullable @Parameter("confirm-new-password") String confirm_new_password) { MessageResponse response = userService.registerUser(username, mail, new_password, confirm_new_password, full_name); diff --git a/src/main/java/cloudgene/mapred/server/services/ServerService.java b/src/main/java/cloudgene/mapred/server/services/ServerService.java index 9af643c2..29724fc3 100644 --- a/src/main/java/cloudgene/mapred/server/services/ServerService.java +++ b/src/main/java/cloudgene/mapred/server/services/ServerService.java @@ -57,6 +57,9 @@ public String getRoot(User user) { data.put("background", application.getSettings().getColors().get("background")); data.put("foreground", application.getSettings().getColors().get("foreground")); data.put("footer", application.getTemplate(Template.FOOTER)); + data.put("emailRequired", application.getSettings().isEmailRequired()); + data.put("userEmailDescription", application.getTemplate(Template.USER_EMAIL_DESCRIPTION)); + data.put("userWithoutEmailDescription", application.getTemplate(Template.USER_WITHOUT_EMAIL_DESCRIPTION)); List authClients = new Vector(); for (OauthClientConfigurationProperties client : clients) { diff --git a/src/main/java/cloudgene/mapred/server/services/UserService.java b/src/main/java/cloudgene/mapred/server/services/UserService.java index 3ad659eb..51a13898 100644 --- a/src/main/java/cloudgene/mapred/server/services/UserService.java +++ b/src/main/java/cloudgene/mapred/server/services/UserService.java @@ -1,5 +1,6 @@ package cloudgene.mapred.server.services; +import java.util.Arrays; import java.util.List; import org.slf4j.Logger; @@ -31,7 +32,7 @@ public class UserService { private static final String MESSAGE_WRONG_PASSWORD = "Wrong password."; - private static final String MESSAGE_PROFILE_UPDATED = "User profile sucessfully updated."; + private static final String MESSAGE_PROFILE_UPDATED = "User profile successfully updated."; private static final String MESSAGE_NOT_ALLOWED = "You are not allowed to change this user profile."; @@ -45,7 +46,9 @@ public class UserService { private static final String MESSAGE_ACCOUNT_NOT_FOUND = "We couldn't find an account with that username or email."; - private static final String MESSAGE_EMAIL_SENT = "Email sent to %s with instructions on how to reset your password."; + private static final String MESSAGE_EMAIL_SENT = "We sent you an email with instructions on how to reset your password."; + + private static final String MESSAGE_EMAIL_NOT_AVAILABLE = "No email address is associated with the provided username. Therefore, password recovery cannot be completed."; private static final String MESSAGE_SENDING_EMAIL_FAILED = "Sending recovery email failed. "; @@ -53,7 +56,7 @@ public class UserService { private static final String MESSAGE_USER_CREATED = "User sucessfully created."; - private static final String MESAGE_EMAIL_ALREADY_REGISTERED = "E-Mail is already registered."; + private static final String MESSAGE_EMAIL_ALREADY_REGISTERED = "E-Mail is already registered."; private static final String MESSAGE_USERNAME_ALREADY_EXISTS = "Username already exists."; @@ -65,6 +68,8 @@ public class UserService { public static final String DEFAULT_ROLE = "User"; + public static final String DEFAULT_ANONYMOUS_ROLE = "Anonymous_User"; + @Inject protected Application application; @@ -156,9 +161,13 @@ public MessageResponse updateProfile(User user, String username, String full_nam return MessageResponse.error(error); } - error = User.checkMail(mail); - if (error != null) { - return MessageResponse.error(error); + boolean mailProvided = (mail != null && !mail.isEmpty()); + + if (application.getSettings().isEmailRequired() || mailProvided) { + error = User.checkMail(mail); + if (error != null) { + return MessageResponse.error(error); + } } UserDao dao = new UserDao(application.getDatabase()); @@ -166,11 +175,26 @@ public MessageResponse updateProfile(User user, String username, String full_nam newUser.setFullName(full_name); newUser.setMail(mail); - if (user.getMail() == null || !user.getMail().equals(newUser.getMail())) { + if (user.getMail() != null && !user.getMail().equals(newUser.getMail())) { log.info(String.format("User: changed email address for user %s (ID %s)", newUser.getUsername(), newUser.getId())); } + String roleMessage = ""; + if (!application.getSettings().isEmailRequired()) { + if ((newUser.getMail() == null || newUser.getMail().isEmpty()) && user.hasRole(DEFAULT_ROLE)) { + newUser.replaceRole(DEFAULT_ROLE, DEFAULT_ANONYMOUS_ROLE); + log.info(String.format("User: changed role to %s for user %s (ID %s)", DEFAULT_ANONYMOUS_ROLE, newUser.getUsername(), + newUser.getId())); + roleMessage += "

Your account has been downgraded.
To apply these changes, please log out and log back in."; + } else if ((newUser.getMail() != null && !newUser.getMail().isEmpty()) && user.hasRole(DEFAULT_ANONYMOUS_ROLE)) { + newUser.replaceRole(DEFAULT_ANONYMOUS_ROLE, DEFAULT_ROLE); + log.info(String.format("User: changed role to %s for user %s (ID %s)", DEFAULT_ROLE, newUser.getUsername(), + newUser.getId())); + roleMessage += "

Your account has been upgraded.
To apply these changes, please log out and log back in."; + } + } + // update password only when it's not empty if (new_password != null && !new_password.isEmpty()) { @@ -188,7 +212,7 @@ public MessageResponse updateProfile(User user, String username, String full_nam dao.update(newUser); - return MessageResponse.success(MESSAGE_PROFILE_UPDATED); + return MessageResponse.success(MESSAGE_PROFILE_UPDATED + roleMessage); } public MessageResponse deleteProfile(User user, String username, String password) { @@ -257,7 +281,7 @@ public MessageResponse updatePassword(String username, String token, String new_ public MessageResponse resetPassword(String username) { - if (username == null || username.isEmpty()) { + if (username == null || username.trim().isEmpty()) { return MessageResponse.error(MESSAGE_INVALID_USERNAME); } @@ -299,11 +323,16 @@ public MessageResponse resetPassword(String username) { String body = application.getTemplate(Template.RECOVERY_MAIL, user.getFullName(), application, link); try { - log.info(String.format("Password reset link requested for user '%s'", username)); + if (user.getMail()!= null && !user.getMail().isEmpty()) { + + log.info(String.format("Password reset link requested for user '%s'", username)); - MailUtil.send(application.getSettings(), user.getMail(), subject, body); + MailUtil.send(application.getSettings(), user.getMail(), subject, body); - return MessageResponse.success(String.format(MESSAGE_EMAIL_SENT, user.getMail())); + return MessageResponse.success(MESSAGE_EMAIL_SENT); + } else { + return MessageResponse.error(MESSAGE_EMAIL_NOT_AVAILABLE); + } } catch (Exception e) { @@ -332,14 +361,21 @@ public MessageResponse registerUser(String username, String mail, String new_pas } // check email - error = User.checkMail(mail); - if (error != null) { - return MessageResponse.error(error); - } - if (dao.findByMail(mail) != null) { - return MessageResponse.error(MESAGE_EMAIL_ALREADY_REGISTERED); + boolean mailProvided = (mail != null && !mail.isEmpty()); + + if (application.getSettings().isEmailRequired() || mailProvided) { + // check email + error = User.checkMail(mail); + if (error != null) { + return MessageResponse.error(error); + } + if (dao.findByMail(mail) != null) { + return MessageResponse.error(MESSAGE_EMAIL_ALREADY_REGISTERED); + } } + String[] roles = new String[] { mailProvided ? DEFAULT_ROLE : DEFAULT_ANONYMOUS_ROLE}; + // check password error = User.checkPassword(new_password, confirm_new_password); if (error != null) { @@ -356,7 +392,7 @@ public MessageResponse registerUser(String username, String mail, String new_pas newUser.setUsername(username); newUser.setFullName(full_name); newUser.setMail(mail); - newUser.setRoles(new String[] { UserService.DEFAULT_ROLE }); + newUser.setRoles(roles); newUser.setPassword(HashUtil.hashPassword(new_password)); try { @@ -367,7 +403,7 @@ public MessageResponse registerUser(String username, String mail, String new_pas // if email server configured, send mails with activation link. Else // activate user immediately. - if (application.getSettings().getMail() != null) { + if (application.getSettings().getMail() != null && mailProvided) { String activationKey = HashUtil.getActivationHash(newUser); newUser.setActive(false); @@ -389,8 +425,8 @@ public MessageResponse registerUser(String username, String mail, String new_pas } - log.info(String.format("Registration: New user %s (ID %s - email %s)", username, newUser.getId(), - newUser.getMail())); + log.info(String.format("Registration: New user %s (ID %s - email %s - roles %s)", newUser.getUsername(), + newUser.getId(), newUser.getMail(), Arrays.toString(newUser.getRoles()))); dao.insert(newUser); diff --git a/src/main/java/cloudgene/mapred/util/Settings.java b/src/main/java/cloudgene/mapred/util/Settings.java index a854e1f9..b38b9373 100644 --- a/src/main/java/cloudgene/mapred/util/Settings.java +++ b/src/main/java/cloudgene/mapred/util/Settings.java @@ -71,6 +71,8 @@ public class Settings { private boolean maintenance = false; + private boolean emailRequired = true; + private String adminMail = null; private String adminName = null; @@ -587,4 +589,12 @@ public Environment buildEnvironment() { return new Environment(this); } + public boolean isEmailRequired() { + return emailRequired; + } + + public void setEmailRequired(boolean emailRequired) { + this.emailRequired = emailRequired; + } + } \ No newline at end of file diff --git a/src/test/java/cloudgene/mapred/api/v2/users/RegisterUserTest.java b/src/test/java/cloudgene/mapred/api/v2/users/RegisterUserTest.java index 80323f00..ddbe6c93 100644 --- a/src/test/java/cloudgene/mapred/api/v2/users/RegisterUserTest.java +++ b/src/test/java/cloudgene/mapred/api/v2/users/RegisterUserTest.java @@ -7,6 +7,9 @@ import java.util.HashMap; import java.util.Map; +import cloudgene.mapred.core.User; +import cloudgene.mapred.database.UserDao; +import cloudgene.mapred.server.services.UserService; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -64,6 +67,11 @@ public void testWithCorrectData() { assertEquals(mailsBefore, mailServer.getReceivedEmailSize()); + //check role + UserDao dao = new UserDao(application.getDatabase()); + User user = dao.findByUsername("usernameunique"); + assertEquals(1, user.getRoles().length); + assertEquals(UserService.DEFAULT_ROLE, user.getRoles()[0]); } @Test @@ -298,4 +306,66 @@ public void testWithPasswordWithMissingNumber() { } + @Test + public void testWithEmptyMailAndNoMailRequired() { + + //set email required to false. + + application.getSettings().setEmailRequired(false); + + TestMailServer mailServer = TestMailServer.getInstance(); + int mailsBefore = mailServer.getReceivedEmailSize(); + + Map form = new HashMap(); + form.put("username", "abcdefgh"); + form.put("full-name", "abcdefgh abcgd"); + form.put("mail", ""); + form.put("new-password", "Password27"); + form.put("confirm-new-password", "Password27"); + + // register user + RestAssured.given().formParams(form).when().post("/api/v2/users/register").then().statusCode(200).and() + .body("success", equalTo(true)).and().body("message", equalTo("User sucessfully created.")); + assertEquals(mailsBefore, mailServer.getReceivedEmailSize()); + + application.getSettings().setEmailRequired(true); + + //check role + UserDao dao = new UserDao(application.getDatabase()); + User user = dao.findByUsername("abcdefgh"); + assertEquals(1, user.getRoles().length); + assertEquals(UserService.DEFAULT_ANONYMOUS_ROLE, user.getRoles()[0]); + } + + @Test + public void testWithMailAndNoMailRequired() { + + //set email required to false. + + application.getSettings().setEmailRequired(false); + + TestMailServer mailServer = TestMailServer.getInstance(); + int mailsBefore = mailServer.getReceivedEmailSize(); + + Map form = new HashMap(); + form.put("username", "abcdefghi"); + form.put("full-name", "abcdefgh abcgd"); + form.put("mail", "test-blabla@test.com"); + form.put("new-password", "Password27"); + form.put("confirm-new-password", "Password27"); + + // register user + RestAssured.given().formParams(form).when().post("/api/v2/users/register").then().statusCode(200).and() + .body("success", equalTo(true)).and().body("message", equalTo("User sucessfully created.")); + assertEquals(mailsBefore + 1, mailServer.getReceivedEmailSize()); + + application.getSettings().setEmailRequired(true); + + //check role + UserDao dao = new UserDao(application.getDatabase()); + User user = dao.findByUsername("abcdefghi"); + assertEquals(1, user.getRoles().length); + assertEquals(UserService.DEFAULT_ROLE, user.getRoles()[0]); + } + } diff --git a/src/test/java/cloudgene/mapred/api/v2/users/ResetPasswordTest.java b/src/test/java/cloudgene/mapred/api/v2/users/ResetPasswordTest.java index e77ab4ae..d489efd1 100644 --- a/src/test/java/cloudgene/mapred/api/v2/users/ResetPasswordTest.java +++ b/src/test/java/cloudgene/mapred/api/v2/users/ResetPasswordTest.java @@ -138,13 +138,13 @@ public void testResetPassword() { // rest password and check if mail was sent RestAssured.given().formParams(form).when().post("/api/v2/users/reset").then().statusCode(200).and() - .body("success", equalTo(true)).and().body("message", containsString("Email sent to")); + .body("success", equalTo(true)).and().body("message", containsString("We sent you an email")); assertEquals(mailsBefore + 1, mailServer.getReceivedEmailSize()); // try it a second time (nervous user) RestAssured.given().formParams(form).when().post("/api/v2/users/reset").then().statusCode(200).and() - .body("success", equalTo(true)).and().body("message", containsString("Email sent to")); + .body("success", equalTo(true)).and().body("message", containsString("We sent you an email")); assertEquals(mailsBefore + 2, mailServer.getReceivedEmailSize()); diff --git a/src/test/java/cloudgene/mapred/api/v2/users/UserProfileTest.java b/src/test/java/cloudgene/mapred/api/v2/users/UserProfileTest.java index 62be9981..22ed5727 100644 --- a/src/test/java/cloudgene/mapred/api/v2/users/UserProfileTest.java +++ b/src/test/java/cloudgene/mapred/api/v2/users/UserProfileTest.java @@ -4,10 +4,12 @@ import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsNull.nullValue; import static org.hamcrest.core.StringContains.containsString; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.HashMap; import java.util.Map; +import cloudgene.mapred.server.services.UserService; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -98,7 +100,7 @@ public void testUpdateWithCorrectCredentials() { RestAssured.given().header(accessToken).and().formParams(form).when().post("/api/v2/users/test1/profile").then() .statusCode(200).and().body("success", equalTo(true)).and() - .body("message", equalTo("User profile sucessfully updated.")); + .body("message", equalTo("User profile successfully updated.")); // try login with old password form = new HashMap(); @@ -206,4 +208,62 @@ public void testUpdatePasswordWithMissingUppercase() { } + @Test + public void testUpdateWithEmptyEmail() { + + Header accessToken = client.login("test1", "Test1Password"); + + Map form = new HashMap(); + form.put("username", "test1"); + form.put("full-name", "test1 new"); + form.put("mail", ""); + + RestAssured.given().header(accessToken).and().formParams(form).when().post("/api/v2/users/test1/profile").then().log().all() + .statusCode(200).and().body("success", equalTo(false)).and() + .body("message", containsString("E-Mail is required.")); + + } + + @Test + public void testDowngradeAndUpgradeAccount() { + + application.getSettings().setEmailRequired(false); + Header accessToken = client.login("test1", "Test1Password"); + + // downgrade by removing email + Map form = new HashMap(); + form.put("username", "test1"); + form.put("full-name", "test1 new"); + form.put("mail", ""); + + RestAssured.given().header(accessToken).and().formParams(form).when().post("/api/v2/users/test1/profile").then() + .statusCode(200).and().body("success", equalTo(true)).and() + .body("message", containsString("downgraded")); + + //check role + UserDao dao = new UserDao(application.getDatabase()); + User user = dao.findByUsername("test1"); + assertEquals(1, user.getRoles().length); + assertEquals(UserService.DEFAULT_ANONYMOUS_ROLE, user.getRoles()[0]); + + //upgrade by adding email + form = new HashMap(); + form.put("username", "test1"); + form.put("full-name", "test1 new"); + form.put("mail", "test1@test.com"); + + RestAssured.given().header(accessToken).and().formParams(form).when().post("/api/v2/users/test1/profile").then() + .statusCode(200).and().body("success", equalTo(true)).and() + .body("message", containsString("upgraded")); + + + //check role + user = dao.findByUsername("test1"); + assertEquals(1, user.getRoles().length); + assertEquals(UserService.DEFAULT_ROLE, user.getRoles()[0]); + + application.getSettings().setEmailRequired(true); + + } + }