Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
8885e71
Progress toward confirm email feature #2170
bsilverstein95 Jul 25, 2016
ab55794
Confirm Email: don't expose token to user on create #2170
pdurbin Jul 25, 2016
dd768d9
Successful confirmation in int. test via xhtml page/logic reformat #2170
bsilverstein95 Jul 26, 2016
8b79ec1
Confirm email: delete old, unused code #2170
pdurbin Jul 26, 2016
3fc6e8f
make minutesUntilConfirmEmailTokenExpires configurable #2170
pdurbin Jul 26, 2016
814d58b
Confirm Email: create token in central location #2170
pdurbin Jul 26, 2016
540cb95
Confirm Email: show "Not Verified" #2170
pdurbin Jul 27, 2016
4bb6f7b
set "confirmed" timestamp to null on email change, create token #2170
pdurbin Jul 27, 2016
a0dc4f9
Added logic to remove used token upon confirmation reflected in IT #2170
bsilverstein95 Jul 27, 2016
733a180
Confirm Email: expect a 404 when no token found #2170
pdurbin Jul 27, 2016
733b65e
Confirm Email: update welcome email to include link #2170
pdurbin Jul 27, 2016
4710fd7
Confirm Email: send link on password change #2170
pdurbin Jul 27, 2016
064d0c2
Confirm Email: add test to exercise delete user bug #2170
pdurbin Jul 28, 2016
a05f0dd
Refactored sendLinkOnEmailChange method to employ BundleUtil #2170
bsilverstein95 Jul 28, 2016
42c6fa3
Confirm Email: add/remove "Email Not Verfied" immediately #2170
pdurbin Jul 28, 2016
cd0d596
Added testConfirmUserWithTokenCanBeDeleted() #2170
bsilverstein95 Jul 29, 2016
60f797b
Confirm Email: error and success handling done? #2170
pdurbin Jul 29, 2016
689f509
Added findSingleConfirmEmailDataByUser() and fixed deletion bug #2170
bsilverstein95 Jul 29, 2016
05a43dd
increase code coverage for ConfirmEmailUtil from 90% to 100% #2170
pdurbin Jul 29, 2016
5c8eb2c
Confirm Email: add "Verify Email" button #2170
pdurbin Jul 29, 2016
40ea2ee
Confirm Email: clean up tests and delete user #2170
pdurbin Jul 29, 2016
03e4b6a
Confirm Email: prevent multiple tokens for a user #2170
pdurbin Jul 29, 2016
36c7650
Confirm Email: refactor, delete dead code, more API tests #2170
pdurbin Jul 29, 2016
ff0607f
Fixed time syntax for sent email & Verify Email button logic #2170
bsilverstein95 Aug 1, 2016
0fbbd7f
Merge branch '2170-confirm-email' of github.com:IQSS/dataverse into 2…
bsilverstein95 Aug 1, 2016
4f1f928
Confirm Email: Shib users don't have to confirm #2170
pdurbin Aug 1, 2016
867ed48
Updated ConfirmEmailUtilTest.java with more assertions #2170
bsilverstein95 Aug 1, 2016
614c157
Confirm email: delete method we don't need anymore #2170
pdurbin Aug 1, 2016
166aaf0
Confirm Email: added todos, some cleanup #2170
pdurbin Aug 1, 2016
43251f6
Confirm Email: clean up pull request and add todos #2170
pdurbin Aug 1, 2016
e97879c
Added button success msg & information for user re: expiration #2170
bsilverstein95 Aug 2, 2016
872d267
Added button success msg & information for user re: expiration #2170
bsilverstein95 Aug 2, 2016
bfa8162
Confirm Email: add renders for 3 states #2170
pdurbin Aug 2, 2016
55657a8
Added new render logic for BuiltinUserPage #2170
bsilverstein95 Aug 4, 2016
8799e9f
Minor UI components added to Verification status #2170
bsilverstein95 Aug 5, 2016
73a290e
Fixed verified status bug/defined grandfathered timestamp as Y2K #2170
bsilverstein95 Aug 8, 2016
9fa1b19
UI tweaks to account info & added line to welcome notification #2170
bsilverstein95 Aug 16, 2016
08f20a1
Minor layout cleanup of the email verification label and button on th…
mheppler Aug 16, 2016
e67bc36
Minor layout cleanup of the email messages and notifications in the V…
mheppler Aug 16, 2016
e615dcd
Confirm Email: add docs #2170
pdurbin Aug 18, 2016
1213fab
Softened UI messages to inconsequential / dialed back logging #2170
bsilverstein95 Aug 19, 2016
d9857b2
Merge branch 'develop' into 2170-confirm-email
bsilverstein95 Aug 19, 2016
b13f9be
Fixed verified render,Updated info msg on email change,Fixed bundle #…
bsilverstein95 Sep 1, 2016
209db67
Merge branch 'develop' into 2170-confirm-email
pdurbin Sep 2, 2016
139c48f
Logging level reduced to fine #2170
bsilverstein95 Sep 2, 2016
3fd9263
Modified beginConfirm to use AuthenticatedUser instead of email #2170
bsilverstein95 Sep 2, 2016
fe675b7
confirm email is slated for 4.5.1! renaming sql script #2170
pdurbin Sep 22, 2016
6e9833f
Merge branch 'develop' into 2170-confirm-email #2170
pdurbin Sep 22, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions doc/sphinx-guides/source/installation/administration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ User Administration

There isn't much in the way of user administration tools built in to Dataverse.

Confirm Email
+++++++++++++

Dataverse encourages builtin/local users to verify their email address upon signup or email change so that sysadmins can be assured that users can be contacted.

The app will send a standard welcome email with a URL the user can click, which, when activated, will store a ``lastconfirmed`` timestamp in the ``authenticateduser`` table of the database. Any time this is "null" for a user (immediately after signup and/or changing of their Dataverse email address), their current email on file is considered to not be verified. The link that is sent expires after a time (the default is 24 hours), but this is configurable by a superuser via the ``:MinutesUntilConfirmEmailTokenExpires`` config option.

Should users' URL token expire, they will see a "Verify Email" button on the account information page to send another URL.

Sysadmins can determine which users have verified their email addresses by looking for the presence of the value ``emailLastConfirmed`` in the JSON output from listing users (see the "Admin" section of the :doc:`/api/native-api`). The email addresses for Shibboleth users are re-confirmed on every login.

Deleting an API Token
+++++++++++++++++++++

Expand Down
5 changes: 5 additions & 0 deletions doc/sphinx-guides/source/installation/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,11 @@ Set ``SearchHighlightFragmentSize`` to override the default value of 100 from ht

Allow for migration of non-conformant data (especially dates) from DVN 3.x to Dataverse 4.

:MinutesUntilConfirmEmailTokenExpires
+++++++++++++++++++++++++++++++++++++

The duration in minutes before "Confirm Email" URLs expire. The default is 1440 minutes (24 hours). See also :doc:`/installation/administration`.

:ShibEnabled
++++++++++++

Expand Down
1 change: 1 addition & 0 deletions scripts/database/upgrades/upgrade_v4.5_to_v4.5.1.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE authenticateduser ADD COLUMN emailconfirmed timestamp without time zone;
21 changes: 20 additions & 1 deletion src/main/java/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ wasPublished=, was published in
wasReturnedByReviewer=, was returned by the curator of
toReview=Don't forget to publish it or send it back to the contributor!
worldMap.added=dataset had a WorldMap layer data added to it.
notification.welcome=Welcome to {0} Dataverse! Get started by adding or finding data. Have questions? Check out the {1}. Want to test out Dataverse features? Use our {2}.
# Bundle file editors, please note that "notification.welcome" is used in a unit test.
notification.welcome=Welcome to {0} Dataverse! Get started by adding or finding data. Have questions? Check out the {1}. Want to test out Dataverse features? Use our {2}. Also, check for your welcome email to verify your address.
notification.demoSite=Demo Site
notification.requestFileAccess=File access requested for dataset: {0}.
notification.grantFileAccess=Access granted for files in dataset: {0}.
Expand Down Expand Up @@ -204,6 +205,16 @@ login.builtin.invalidUsernameEmailOrPassword=The username, email address, or pas
# how do we exercise login.error? Via a password upgrade failure? See https://github.com/IQSS/dataverse/pull/2922
login.error=Error validating the username, email address, or password. Please try again. If the problem persists, contact an administrator.

#confirmemail.xhtml
confirmEmail.pageTitle=Email Verification
confirmEmail.submitRequest=Verify Email
confirmEmail.submitRequest.success=A verification email has been sent to {0}. Note, the verify link will expire after {1}.
confirmEmail.details.success=Email address verified!
confirmEmail.details.failure=We were unable to verify your email address. Please navigate to your Account Information page and click the "Verify Email" button.
confirmEmail.details.goToAccountPageButton=Go to Account Information
confirmEmail.notVerified=Not Verified
confirmEmail.verified=Verified

#shib.xhtml
shib.btn.convertAccount=Convert Account
shib.btn.createAccount=Create Account
Expand Down Expand Up @@ -420,9 +431,12 @@ notification.email.returned.dataset.subject=Dataverse: Your dataset has been ret
notification.email.create.account.subject=Dataverse: Your account has been created
notification.email.assign.role.subject=Dataverse: You have been assigned a role
notification.email.revoke.role.subject=Dataverse: Your role has been revoked
notification.email.verifyEmail.subject=Dataverse: Verify your email address

notification.email.greeting=Hello, \n
# Bundle file editors, please note that "notification.email.welcome" is used in a unit test
notification.email.welcome=Welcome to Dataverse! Get started by adding or finding data. Have questions? Check out the User Guide at {0}/{1}/user/ or contact Dataverse Support for assistance. Want to test out Dataverse features? Use our Demo Site at https://demo.dataverse.org
notification.email.welcomeConfirmEmailAddOn=\n\nPlease verify your email address at {0}. Note, the verify link will expire after {1}. Send another verification email by visiting your account page.
notification.email.requestFileAccess=File access requested for dataset: {0}. Manage permissions at {1}.
notification.email.grantFileAccess=Access granted for files in dataset: {0} (view at {1}).
notification.email.rejectFileAccess=Access rejected for requested files in dataset: {0} (view at {1}).
Expand All @@ -437,6 +451,11 @@ notification.email.worldMap.added={0} (view at {1}) had WorldMap layer data adde
notification.email.closing=\n\nThank you,\nThe Dataverse Project
notification.email.assignRole=You are now {0} for the {1} "{2}" (view at {3}).
notification.email.revokeRole=One of your roles for the {0} "{1}" has been revoked (view at {2}).
notification.email.changeEmail=Hello, {0}.{1}\n\nPlease contact us if you did not intend this change or if you need assistance.
hours=hours
hour=hour
minutes=minutes
minute=minute

# passwordreset.xhtml

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import edu.harvard.iq.dataverse.authorization.groups.Group;
import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailServiceBean;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean.Key;
import edu.harvard.iq.dataverse.util.BundleUtil;
Expand Down Expand Up @@ -62,6 +63,8 @@ public class MailServiceBean implements java.io.Serializable {
PermissionServiceBean permissionService;
@EJB
GroupServiceBean groupService;
@EJB
ConfirmEmailServiceBean confirmEmailService;

private static final Logger logger = Logger.getLogger(MailServiceBean.class.getCanonicalName());

Expand Down Expand Up @@ -426,6 +429,9 @@ private String getMessageTextBasedOnNotification(UserNotification userNotificati
systemConfig.getGuidesBaseUrl(),
systemConfig.getVersion()
));
String optionalConfirmEmailAddon = confirmEmailService.optionalConfirmEmailAddonMsg(userNotification.getUser());
accountCreatedMessage += optionalConfirmEmailAddon;
logger.info("accountCreatedMessage: " + accountCreatedMessage);
return messageText += accountCreatedMessage;
}

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/Shib.java
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ public String confirmAndCreateAccount() {
if (au != null) {
logger.fine("created user " + au.getIdentifier());
logInUserAndSetShibAttributes(au);
/**
* @todo Move this to
* AuthenticationServiceBean.createAuthenticatedUser
*/
userNotificationService.sendNotification(au,
new Timestamp(new Date().getTime()),
UserNotification.Type.CREATEACC, null);
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import edu.harvard.iq.dataverse.MetadataBlockServiceBean;
import edu.harvard.iq.dataverse.PermissionServiceBean;
import edu.harvard.iq.dataverse.RoleAssigneeServiceBean;
import edu.harvard.iq.dataverse.UserNotificationServiceBean;
import edu.harvard.iq.dataverse.UserServiceBean;
import edu.harvard.iq.dataverse.actionlogging.ActionLogServiceBean;
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
Expand All @@ -22,6 +23,7 @@
import edu.harvard.iq.dataverse.authorization.users.GuestUser;
import edu.harvard.iq.dataverse.authorization.users.PrivateUrlUser;
import edu.harvard.iq.dataverse.authorization.users.User;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailServiceBean;
import edu.harvard.iq.dataverse.engine.command.Command;
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
Expand Down Expand Up @@ -175,6 +177,12 @@ String getWrappedMessageWhenJson() {
@EJB
protected PrivateUrlServiceBean privateUrlSvc;

@EJB
protected ConfirmEmailServiceBean confirmEmailSvc;

@EJB
protected UserNotificationServiceBean userNotificationSvc;

@PersistenceContext(unitName = "VDCNet-ejbPU")
protected EntityManager em;

Expand Down Expand Up @@ -480,4 +488,4 @@ public T get() {
public T get() {
return ref.get();
}
}
}
51 changes: 50 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/api/Admin.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibServiceBean;
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibUtil;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailData;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailException;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailInitResponse;
import edu.harvard.iq.dataverse.engine.command.impl.PublishDataverseCommand;
import edu.harvard.iq.dataverse.settings.Setting;
import javax.json.Json;
Expand All @@ -34,6 +37,7 @@
import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder;
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.*;
import java.io.StringReader;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -528,6 +532,50 @@ public Response validate() {
return okResponse(msg);
}

/**
* This method is used in integration tests.
*
* @param userId The database id of an AuthenticatedUser.
* @return The confirm email token.
*/
@Path("confirmEmail/{userId}")
@GET
public Response getConfirmEmailToken(@PathParam("userId") long userId) {
AuthenticatedUser user = authSvc.findByID(userId);
if (user != null) {
ConfirmEmailData confirmEmailData = confirmEmailSvc.findSingleConfirmEmailDataByUser(user);
if (confirmEmailData != null) {
return okResponse(Json.createObjectBuilder().add("token", confirmEmailData.getToken()));
}
}
return errorResponse(Status.BAD_REQUEST, "Could not find confirm email token for user " + userId);
}

/**
* This method is used in integration tests.
*
* @param userId The database id of an AuthenticatedUser.
*/
@Path("confirmEmail/{userId}")
@POST
public Response startConfirmEmailProcess(@PathParam("userId") long userId) {
AuthenticatedUser user = authSvc.findByID(userId);
if (user != null) {
try {
ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailSvc.beginConfirm(user);
ConfirmEmailData confirmEmailData = confirmEmailInitResponse.getConfirmEmailData();
return okResponse(
Json.createObjectBuilder()
.add("tokenCreated", confirmEmailData.getCreated().toString())
.add("identifier", user.getUserIdentifier()
));
} catch (ConfirmEmailException ex) {
return errorResponse(Status.BAD_REQUEST, "Could not start confirm email process for user " + userId + ": " + ex.getLocalizedMessage());
}
}
return errorResponse(Status.BAD_REQUEST, "Could not find user based on " + userId);
}

/**
* This method is used by an integration test in UsersIT.java to exercise
* bug https://github.com/IQSS/dataverse/issues/3287 . Not for use by users!
Expand All @@ -538,10 +586,11 @@ public Response convertUserFromBcryptToSha1(String json) {
JsonReader jsonReader = Json.createReader(new StringReader(json));
JsonObject object = jsonReader.readObject();
jsonReader.close();
BuiltinUser builtinUser = builtinUserService.find(new Long(object.getInt("builtinUserId")));
BuiltinUser builtinUser = builtinUserService.find(new Long(object.getInt("builtinUserId")));
builtinUser.updateEncryptedPassword("4G7xxL9z11/JKN4jHPn4g9iIQck=", 0); // password is "sha-1Pass", 0 means SHA-1
BuiltinUser savedUser = builtinUserService.save(builtinUser);
return okResponse("foo: " + savedUser);

}

}
12 changes: 11 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.harvard.iq.dataverse.api;

import edu.harvard.iq.dataverse.UserNotification;
import edu.harvard.iq.dataverse.actionlogging.ActionLogRecord;
import edu.harvard.iq.dataverse.authorization.UserRecordIdentifier;
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinAuthenticationProvider;
Expand All @@ -8,7 +9,6 @@
import edu.harvard.iq.dataverse.authorization.providers.builtin.PasswordEncryption;
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.util.json.JsonPrinter;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.logging.Level;
Expand All @@ -26,6 +26,7 @@
import javax.ws.rs.core.Response.Status;
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.jsonForAuthUser;
import java.util.Date;

/**
* REST API bean for managing {@link BuiltinUser}s.
Expand Down Expand Up @@ -123,6 +124,15 @@ private Response internalSave(BuiltinUser user, String password, String key) {
user.getUserName(),
user.getDisplayInfo(),
false);

/**
* @todo Move this to
* AuthenticationServiceBean.createAuthenticatedUser
*/
userNotificationSvc.sendNotification(au,
new Timestamp(new Date().getTime()),
UserNotification.Type.CREATEACC, null);

ApiToken token = new ApiToken();

token.setTokenString(java.util.UUID.randomUUID().toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibAuthenticationProvider;
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailData;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailServiceBean;
import edu.harvard.iq.dataverse.passwordreset.PasswordResetData;
import edu.harvard.iq.dataverse.passwordreset.PasswordResetServiceBean;
import java.sql.SQLException;
Expand Down Expand Up @@ -74,6 +76,9 @@ public class AuthenticationServiceBean {
@EJB
UserNotificationServiceBean userNotificationService;

@EJB
ConfirmEmailServiceBean confirmEmailService;

@EJB
PasswordResetServiceBean passwordResetServiceBean;

Expand Down Expand Up @@ -214,17 +219,23 @@ public void removeApiToken(AuthenticatedUser user){
* assignments, etc.
*
* Longer term, the intention is to have a "disableAuthenticatedUser"
* method/command.
* method/command. See https://github.com/IQSS/dataverse/issues/2419
*/
public void deleteAuthenticatedUser(Object pk) {
AuthenticatedUser user = em.find(AuthenticatedUser.class, pk);


if (user!=null) {

if (user != null) {
ApiToken apiToken = findApiTokenByUser(user);
if (apiToken != null) {
em.remove(apiToken);
}
ConfirmEmailData confirmEmailData = confirmEmailService.findSingleConfirmEmailDataByUser(user);
if (confirmEmailData != null) {
/**
* @todo This could probably be a cascade delete instead.
*/
em.remove(confirmEmailData);
}
for (UserNotification notification : userNotificationService.findByUser(user.getId())) {
userNotificationService.delete(notification);
}
Expand Down Expand Up @@ -445,7 +456,20 @@ public AuthenticatedUser createAuthenticatedUser(UserRecordIdentifier userRecord
AuthenticatedUserLookup auusLookup = userRecordId.createAuthenticatedUserLookup(authenticatedUser);
em.persist( auusLookup );
authenticatedUser.setAuthenticatedUserLookup(auusLookup);


if (ShibAuthenticationProvider.PROVIDER_ID.equals(auusLookup.getAuthenticationProviderId())) {
Timestamp emailConfirmedNow = new Timestamp(new Date().getTime());
// Email addresses for Shib users are confirmed by the Identity Provider.
authenticatedUser.setEmailConfirmed(emailConfirmedNow);
authenticatedUser = save(authenticatedUser);
} else {
/**
* @todo Rather than creating a token directly here it might be
* better to do something like "startConfirmEmailProcessForNewUser".
*/
confirmEmailService.createToken(authenticatedUser);
}

actionLogSvc.log( new ActionLogRecord(ActionLogRecord.ActionType.Auth, "createUser")
.setInfo(authenticatedUser.getIdentifier()));

Expand Down
Loading