From 8885e710dfdfa54703c98d5d2ca84db36090a63e Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Mon, 25 Jul 2016 14:05:09 -0400 Subject: [PATCH 01/44] Progress toward confirm email feature #2170 --- scripts/database/upgrades/confirmemail.sql | 1 + src/main/java/Bundle.properties | 6 + .../iq/dataverse/api/AbstractApiBean.java | 6 +- .../edu/harvard/iq/dataverse/api/Admin.java | 37 +++ .../iq/dataverse/api/BuiltinUsers.java | 9 + .../providers/builtin/BuiltinUserPage.java | 16 +- .../users/AuthenticatedUser.java | 14 +- .../ConfirmEmailAttemptResponse.java | 31 +++ .../confirmemail/ConfirmEmailData.java | 113 +++++++++ .../confirmemail/ConfirmEmailException.java | 17 ++ .../ConfirmEmailExecResponse.java | 29 +++ .../ConfirmEmailInitResponse.java | 55 +++++ .../confirmemail/ConfirmEmailPage.java | 151 ++++++++++++ .../confirmemail/ConfirmEmailServiceBean.java | 230 ++++++++++++++++++ .../iq/dataverse/util/SystemConfig.java | 10 +- .../iq/dataverse/util/json/JsonPrinter.java | 3 +- .../util/json/NullSafeJsonBuilder.java | 8 +- src/main/webapp/confirmemail.xhtml | 144 +++++++++++ .../iq/dataverse/api/ConfirmEmailIT.java | 198 +++++++++++++++ 19 files changed, 1071 insertions(+), 7 deletions(-) create mode 100644 scripts/database/upgrades/confirmemail.sql create mode 100644 src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailAttemptResponse.java create mode 100644 src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java create mode 100644 src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailException.java create mode 100644 src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailExecResponse.java create mode 100644 src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java create mode 100644 src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java create mode 100644 src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java create mode 100644 src/main/webapp/confirmemail.xhtml create mode 100644 src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java diff --git a/scripts/database/upgrades/confirmemail.sql b/scripts/database/upgrades/confirmemail.sql new file mode 100644 index 00000000000..6296fca8a5f --- /dev/null +++ b/scripts/database/upgrades/confirmemail.sql @@ -0,0 +1 @@ +ALTER TABLE authenticateduser ADD COLUMN emailconfirmed timestamp without time zone; diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index de73e3c11c9..6d15909d0c9 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -202,6 +202,12 @@ 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 Confirmation +confirmEmail.submitRequest=Verify Email +confirmEmail.details.success=Email address confirmed! Redirecting you shortly... +confirmEmail.details.failure=We were unable to verify your email address. Please re-enter your email and click the button below to resend. + #shib.xhtml shib.btn.convertAccount=Convert Account shib.btn.createAccount=Create Account diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index 2b2cb6f03fd..c3b18f3f49c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -22,6 +22,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; @@ -175,6 +176,9 @@ String getWrappedMessageWhenJson() { @EJB protected PrivateUrlServiceBean privateUrlSvc; + @EJB + protected ConfirmEmailServiceBean confirmEmailSvc; + @PersistenceContext(unitName = "VDCNet-ejbPU") protected EntityManager em; @@ -480,4 +484,4 @@ public T get() { public T get() { return ref.get(); } -} \ No newline at end of file +} diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java index 0c131871282..17436a98c10 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java @@ -18,6 +18,8 @@ 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.ConfirmEmailExecResponse; import edu.harvard.iq.dataverse.engine.command.impl.PublishDataverseCommand; import edu.harvard.iq.dataverse.settings.Setting; import javax.json.Json; @@ -33,6 +35,8 @@ import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder; import static edu.harvard.iq.dataverse.util.json.JsonPrinter.*; +import java.sql.Timestamp; +import java.util.Date; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -520,5 +524,38 @@ public Response validate() { } return okResponse(msg); } + + //should the path for this method refer instead to the auth user ID? Yes. + // Call confirmEmailSvc.findConfirmEmailDataByDataverseUser. We need this + // to close the security hole of exposing the token on create. + @Path("confirmEmail/{token}") + @GET + public Response getConfirmEmailToken(@PathParam("token") String token) { + ConfirmEmailExecResponse confirmEmailExecResponse = confirmEmailSvc.processToken(token); + ConfirmEmailData confirmEmailData = confirmEmailExecResponse.getConfirmEmailData(); + + return null; + } + + /** + * This is just for testing the confirm email feature and is used in + * integration tests. We may delete this endpoint entirely if we can test + * everything via the xhtml file. + */ + @Path("confirmEmail/{token}") + @POST + public Response confirmTheEmail(@PathParam("token") String token) { + // TODO: move as much logic as possible to a central place like confirmEmailSvc.processToken + ConfirmEmailExecResponse confirmEmailExecResponse = confirmEmailSvc.processToken(token); + ConfirmEmailData confirmEmailData = confirmEmailExecResponse.getConfirmEmailData(); + if (confirmEmailData == null) { + return errorResponse(Status.NOT_FOUND, "Invalid token: " + token); + } + long nowInMilliseconds = new Date().getTime(); + Timestamp emailConfirmed = new Timestamp(nowInMilliseconds); + AuthenticatedUser authenticatedUser = confirmEmailData.getAuthenticatedUser(); + authenticatedUser.setEmailConfirmed(emailConfirmed); + return okResponse("Email confirmed."); + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java b/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java index 1460e11f6be..c0668d4138e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java @@ -8,6 +8,7 @@ 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.confirmemail.ConfirmEmailData; import edu.harvard.iq.dataverse.util.json.JsonPrinter; import java.sql.Timestamp; import java.util.Calendar; @@ -134,10 +135,18 @@ private Response internalSave(BuiltinUser user, String password, String key) { token.setExpireTime(new Timestamp(c.getTimeInMillis())); authSvc.save(token); + ConfirmEmailData confirmEmailData = confirmEmailSvc.createToken(au); + JsonObjectBuilder resp = Json.createObjectBuilder(); resp.add("user", json(user)); resp.add("authenticatedUser", jsonForAuthUser(au)); resp.add("apiToken", token.getTokenString()); + /** + * @todo Remove this once a superuser can retrieve the token via the + * admin API. We don't want users to get the confirm email token + * except via email. + */ + resp.add("confirmEmailToken", confirmEmailData.getToken()); alr.setInfo("builtinUser:" + user.getUserName() + " authenticatedUser:" + au.getIdentifier() ); return okResponse(resp); diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index 1093b39cdbd..8ef382731ea 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -27,6 +27,8 @@ 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.ConfirmEmailException; +import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailServiceBean; import edu.harvard.iq.dataverse.mydata.MyDataPage; import edu.harvard.iq.dataverse.passwordreset.PasswordValidator; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; @@ -63,6 +65,9 @@ @Named("DataverseUserPage") public class BuiltinUserPage implements java.io.Serializable { + @EJB + private ConfirmEmailServiceBean confirmEmailServiceBean; + private static final Logger logger = Logger.getLogger(BuiltinUserPage.class.getCanonicalName()); public enum EditMode { @@ -89,6 +94,8 @@ public enum EditMode { @EJB AuthenticationServiceBean authenticationService; @EJB + ConfirmEmailServiceBean confirmEmailService; + @EJB GroupServiceBean groupService; @Inject SettingsWrapper settingsWrapper; @@ -407,7 +414,8 @@ public void validateNewPassword(FacesContext context, UIComponent toValidate, Ob context.addMessage(toValidate.getClientId(context), message); } } - + + public void updatePassword(String userName) { String plainTextPassword = PasswordEncryption.generateRandomPassword(); @@ -592,4 +600,10 @@ public void displayNotification() { } } } + + public void sendConfirmEmail(AuthenticatedUser currentUser) throws ConfirmEmailException{ + String userEmail = currentUser.getEmail(); + confirmEmailServiceBean.beginConfirm(userEmail); + } + } diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java index f609ed959e2..8b9c8c92d13 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java @@ -70,6 +70,8 @@ public class AuthenticatedUser implements User, Serializable { private String position; private String lastName; private String firstName; + @Column(nullable = true) + private Timestamp emailConfirmed; private boolean superuser; /** @@ -118,7 +120,7 @@ public void applyDisplayInfo( AuthenticatedUserDisplayInfo inf ) { setEmail(inf.getEmailAddress()); setAffiliation( inf.getAffiliation() ); setPosition( inf.getPosition()); - + } @Override @@ -184,6 +186,14 @@ public void setFirstName(String firstName) { this.firstName = firstName; } + public Timestamp getEmailConfirmed() { + return emailConfirmed; + } + + public void setEmailConfirmed(Timestamp emailConfirmed) { + this.emailConfirmed = emailConfirmed; + } + @Override public boolean isSuperuser() { return superuser; @@ -242,4 +252,6 @@ public void setShibIdentityProvider(String shibIdentityProvider) { this.shibIdentityProvider = shibIdentityProvider; } + + } diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailAttemptResponse.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailAttemptResponse.java new file mode 100644 index 00000000000..c1d60c101b4 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailAttemptResponse.java @@ -0,0 +1,31 @@ +package edu.harvard.iq.dataverse.confirmemail; + +/** + * + * @author bsilverstein + */ +public class ConfirmEmailAttemptResponse { + + private final boolean confirmed; + private final String messageSummary; + private final String messageDetail; + + public ConfirmEmailAttemptResponse(boolean confirmed, String messageSummary, String messageDetail) { + this.confirmed = confirmed; + this.messageSummary = messageSummary; + this.messageDetail = messageDetail; + } + + public boolean isConfirmed() { + return confirmed; + } + + public String getMessageSummary() { + return messageSummary; + } + + public String getMessageDetail() { + return messageDetail; + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java new file mode 100644 index 00000000000..0a8b96c7ccc --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java @@ -0,0 +1,113 @@ +package edu.harvard.iq.dataverse.confirmemail; + +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.util.SystemConfig; +import java.io.Serializable; +import java.sql.Timestamp; +import java.util.Date; +import java.util.UUID; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +/** + * + * @author bsilverstein + */ +@Table(indexes = { + @Index(columnList = "token"), + @Index(columnList = "authenticateduser_id")}) +@NamedQueries({ + @NamedQuery(name = "ConfirmEmailData.findAll", + query = "SELECT prd FROM ConfirmEmailData prd"), + @NamedQuery(name = "ConfirmEmailData.findByUser", + query = "SELECT prd FROM ConfirmEmailData prd WHERE prd.authenticatedUser = :user"), + @NamedQuery(name = "ConfirmEmailData.findByToken", + query = "SELECT prd FROM ConfirmEmailData prd WHERE prd.token = :token") +}) +@Entity +public class ConfirmEmailData implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = true) + private String token; + + @OneToOne + @JoinColumn(nullable = false) + private AuthenticatedUser authenticatedUser; + + @Column(nullable = false) + private Timestamp created; + + @Column(nullable = false) + private Timestamp expires; + + public ConfirmEmailData(AuthenticatedUser anAuthenticatedUser) { + authenticatedUser = anAuthenticatedUser; + token = UUID.randomUUID().toString(); + long nowInMilliseconds = new Date().getTime(); + created = new Timestamp(nowInMilliseconds); + long ONE_MINUTE_IN_MILLISECONDS = 60000; + /** + * @todo: use database setting instead of jvm option for line 75 + * configurable expiration value + */ + + long futureInMilliseconds = nowInMilliseconds + (SystemConfig.getMinutesUntilConfirmEmailTokenExpires() * ONE_MINUTE_IN_MILLISECONDS); + expires = new Timestamp(new Date(futureInMilliseconds).getTime()); + } + + public boolean isExpired() { + if (this.expires == null) { + return true; + } + long expiresInMilliseconds = this.expires.getTime(); + long nowInMilliseconds = new Date().getTime(); + return nowInMilliseconds > expiresInMilliseconds; + } + + public String getToken() { + return token; + } + + public AuthenticatedUser getAuthenticatedUser() { + return authenticatedUser; + } + + public Timestamp getCreated() { + return created; + } + + public Timestamp getExpires() { + return expires; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + /** + * This is only here because it has to be: "The class should have a no-arg, + * public or protected constructor." Please use the constructor that takes + * arguments. + */ + @Deprecated + public ConfirmEmailData() { + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailException.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailException.java new file mode 100644 index 00000000000..add7cd922fb --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailException.java @@ -0,0 +1,17 @@ +package edu.harvard.iq.dataverse.confirmemail; + +/** + * + * @author bsilverstein + */ +public class ConfirmEmailException extends Exception { + + public ConfirmEmailException(String message) { + super(message); + } + + public ConfirmEmailException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailExecResponse.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailExecResponse.java new file mode 100644 index 00000000000..aaeb1b90e79 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailExecResponse.java @@ -0,0 +1,29 @@ +package edu.harvard.iq.dataverse.confirmemail; + +/** + * + * @author bsilverstein + */ +public class ConfirmEmailExecResponse { + + private String tokenQueried; + private ConfirmEmailData confirmEmailData; + + public ConfirmEmailExecResponse(String tokenQueried, ConfirmEmailData confirmEmailData) { + this.tokenQueried = tokenQueried; + this.confirmEmailData = confirmEmailData; + } + + public String getTokenQueried() { + return tokenQueried; + } + + public ConfirmEmailData getConfirmEmailData() { + return confirmEmailData; + } + + public void setConfirmEmailData(ConfirmEmailData confirmEmailData) { + this.confirmEmailData = confirmEmailData; + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java new file mode 100644 index 00000000000..c0f856aaef3 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java @@ -0,0 +1,55 @@ +package edu.harvard.iq.dataverse.confirmemail; + +import edu.harvard.iq.dataverse.util.SystemConfig; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * + * @author bsilverstein + */ +public class ConfirmEmailInitResponse { + + private boolean emailFound; + private String confirmUrl; + private ConfirmEmailData confirmEmailData; + + public ConfirmEmailInitResponse(boolean emailFound) { + this.emailFound = emailFound; + } + + public ConfirmEmailInitResponse(boolean emailFound, ConfirmEmailData confirmEmailData) { + this.emailFound = emailFound; + this.confirmEmailData = confirmEmailData; + // default to localhost + String finalHostname = "localhost"; + String configuredHostname = System.getProperty(SystemConfig.FQDN); + if (configuredHostname != null) { + if (configuredHostname.equals("localhost")) { + // must be a dev environment + finalHostname = "localhost:8181"; + } else { + finalHostname = configuredHostname; + } + } else { + try { + finalHostname = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException ex) { + // just use the dev address + } + } + this.confirmUrl = "https://" + finalHostname + "/confirmemail.xhtml?token=" + confirmEmailData.getToken(); + } + + public boolean isEmailFound() { + return emailFound; + } + + public String getConfirmUrl() { + return confirmUrl; + } + + public ConfirmEmailData getConfirmEmailData() { + return confirmEmailData; + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java new file mode 100644 index 00000000000..7045a21bf42 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java @@ -0,0 +1,151 @@ +package edu.harvard.iq.dataverse.confirmemail; + +import edu.harvard.iq.dataverse.DataverseServiceBean; +import edu.harvard.iq.dataverse.DataverseSession; +import edu.harvard.iq.dataverse.ValidateEmail; +import edu.harvard.iq.dataverse.actionlogging.ActionLogServiceBean; +import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; +import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinAuthenticationProvider; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.ejb.EJB; +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.faces.view.ViewScoped; +import javax.inject.Inject; +import javax.inject.Named; +import org.hibernate.validator.constraints.NotBlank; + +/** + * + * @author bsilverstein + */ +@ViewScoped +@Named("ConfirmEmailPage") +public class ConfirmEmailPage implements java.io.Serializable { + + private static final Logger logger = Logger.getLogger(ConfirmEmailPage.class.getCanonicalName()); + + @EJB + ConfirmEmailServiceBean confirmEmailService; + @EJB + AuthenticationServiceBean dataverseUserService; + @EJB + DataverseServiceBean dataverseService; + @EJB + AuthenticationServiceBean authSvc; + @Inject + DataverseSession session; + + @EJB + ActionLogServiceBean actionLogSvc; + + /** + * The unique string used to look up a user and continue the email + * confirmation. + */ + String token; + + /** + * The user looked up by the token who will be confirming their email. + */ + AuthenticatedUser user; + + /** + * The email address that is entered to be confirmed. + */ + @NotBlank(message = "Please enter a valid email address.") + @ValidateEmail(message = "Confirm email page default email message.") + String emailAddress; + + /** + * The link that is emailed to the user to confirm the email that contains a + * token. + */ + String confirmEmailUrl; + + ConfirmEmailData confirmEmailData; + + public void init() { + if (token != null) { + ConfirmEmailExecResponse confirmEmailExecResponse = confirmEmailService.processToken(token); + confirmEmailData = confirmEmailExecResponse.getConfirmEmailData(); + if (confirmEmailData != null) { + user = confirmEmailData.getAuthenticatedUser(); + } else { + FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Confirm Email Link", "Your email confirmation link is not valid.")); + } + } + } + + public String sendEmailConfirmLink() { + try { + ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailService.beginConfirm(emailAddress); + ConfirmEmailData confirmEmailData = confirmEmailInitResponse.getConfirmEmailData(); + if (confirmEmailData != null) { + AuthenticatedUser foundUser = confirmEmailData.getAuthenticatedUser(); + confirmEmailUrl = confirmEmailInitResponse.getConfirmUrl(); + } else { + logger.log(Level.INFO, "Couldn''t find single account using {0}", emailAddress); + } + FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Email Confirmation Initiated", "")); + } catch (ConfirmEmailException ex) { + logger.log(Level.WARNING, "Error While confirming email: " + ex.getMessage(), ex); + } + return ""; + } +//might work now... + + public String confirmEmail() throws ConfirmEmailException { + ConfirmEmailAttemptResponse response = confirmEmailService.attemptEmailConfirm(user, token, token); + try { + if (response.isConfirmed()) { + FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, response.getMessageSummary(), response.getMessageDetail())); + String builtinAuthProviderId = BuiltinAuthenticationProvider.PROVIDER_ID; + AuthenticatedUser au = authSvc.lookupUser(builtinAuthProviderId, user.getIdentifier()); + session.setUser(au); + return "/dataverse.xhtml?alias=" + dataverseService.findRootDataverse().getAlias() + "faces-redirect=true"; + } else { + FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, response.getMessageSummary(), response.getMessageDetail())); + return null; + } + + } catch (Exception ex) { + String msg = "Unable to save token for " + user.getEmail(); + throw new ConfirmEmailException(msg, ex); + } + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public AuthenticatedUser getUser() { + return user; + } + + public String getEmailAddress() { + return emailAddress; + } + + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + + public String getConfirmEmailUrl() { + return confirmEmailUrl; + } + + public ConfirmEmailData getConfirmEmailData() { + return confirmEmailData; + } + + public void setConfirmEmailData(ConfirmEmailData confirmEmailData) { + this.confirmEmailData = confirmEmailData; + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java new file mode 100644 index 00000000000..74031d0b870 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -0,0 +1,230 @@ +package edu.harvard.iq.dataverse.confirmemail; + +import edu.harvard.iq.dataverse.authorization.users.ApiToken; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; +import edu.harvard.iq.dataverse.MailServiceBean; +import edu.harvard.iq.dataverse.util.SystemConfig; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.NonUniqueResultException; +import javax.persistence.PersistenceContext; +import javax.persistence.TypedQuery; + +/** + * + * @author bsilverstein + */ +@Stateless +public class ConfirmEmailServiceBean { + + private static final Logger logger = Logger.getLogger(ConfirmEmailServiceBean.class.getCanonicalName()); + + @EJB + AuthenticationServiceBean dataverseUserService; + + @EJB + MailServiceBean mailService; + + @PersistenceContext(unitName = "VDCNet-ejbPU") + private EntityManager em; + + /** + * Initiate the email confirmation process. + * + * @param emailAddress + * @return {@link ConfirmEmailInitResponse} + */ + public ConfirmEmailInitResponse beginConfirm(String emailAddress) throws ConfirmEmailException { + deleteAllExpiredTokens(); + AuthenticatedUser user = dataverseUserService.getAuthenticatedUserByEmail(emailAddress); + if (user != null) { + return sendConfirm(user, true); + } else { + return new ConfirmEmailInitResponse(false); + } + } + + public ConfirmEmailInitResponse sendConfirm(AuthenticatedUser aUser, boolean sendEmail) throws ConfirmEmailException { + // delete old tokens for the user + List oldTokens = findConfirmEmailDataByDataverseUser(aUser); + for (ConfirmEmailData oldToken : oldTokens) { + em.remove(oldToken); + } + + // create a fresh token for the user + ConfirmEmailData confirmEmailData = new ConfirmEmailData(aUser); + try { + em.persist(confirmEmailData); + ConfirmEmailInitResponse confirmEmailInitResponse = new ConfirmEmailInitResponse(true, confirmEmailData); + if (sendEmail) { + sendConfirmEmail(aUser, confirmEmailInitResponse.getConfirmUrl()); + } + + return confirmEmailInitResponse; + } catch (Exception ex) { + String msg = "Unable to save token for " + aUser.getEmail(); + throw new ConfirmEmailException(msg, ex); + } + + } + + /** + * @todo: We expect to send two messages. One at signup and another at email + * change. + */ + private void sendConfirmEmail(AuthenticatedUser aUser, String confirmationUrl) throws ConfirmEmailException { + String messageBody = "Hi " + aUser.getFirstName() + ",\n\n" + + "Hi, " + aUser.getFirstName() + "! Welcome to dataverse.org. To begin publishing data, we require users to confirm their email with us.\n\n" + + "Please click the link below to confirm your email address:\n\n" + + confirmationUrl + "\n\n" + + "The link above will only work for the next " + SystemConfig.getMinutesUntilPasswordResetTokenExpires() + " minutes.\n\n" + + "Please contact us if you did not request this password reset or need further help.\n\n"; + + try { + String toAddress = aUser.getEmail(); + String subject = "Dataverse Password Reset Requested"; + mailService.sendSystemEmail(toAddress, subject, messageBody); + } catch (Exception ex) { + /** + * @todo get more specific about the exception that's thrown when + * `asadmin create-javamail-resource` (or equivalent) hasn't been + * run. + */ + throw new ConfirmEmailException("Problem sending password reset email possibily due to mail server not being configured."); + } + logger.log(Level.INFO, "attempted to send mail to {0}", aUser.getEmail()); + } + + /** + * Process the email confirmation token, allowing the user to confirm the + * email address or report on a invalid token. + * + * @param tokenQueried + */ + public ConfirmEmailExecResponse processToken(String tokenQueried) { + deleteAllExpiredTokens(); + ConfirmEmailExecResponse tokenUnusable = new ConfirmEmailExecResponse(tokenQueried, null); + ConfirmEmailData confirmEmailData = findSingleConfirmEmailDataByToken(tokenQueried); + if (confirmEmailData != null) { + if (confirmEmailData.isExpired()) { + // shouldn't reach here since tokens are being expired above + return tokenUnusable; + } else { + ConfirmEmailExecResponse goodTokenCanProceed = new ConfirmEmailExecResponse(tokenQueried, confirmEmailData); + return goodTokenCanProceed; + } + } else { + return tokenUnusable; + } + } + + /** + * @param token + * @return Null or a single row of email confirmation data. + */ + private ConfirmEmailData findSingleConfirmEmailDataByToken(String token) { + ConfirmEmailData confirmEmailData = null; + TypedQuery typedQuery = em.createNamedQuery("ConfirmEmailData.findByToken", ConfirmEmailData.class); + typedQuery.setParameter("token", token); + try { + confirmEmailData = typedQuery.getSingleResult(); + } catch (NoResultException | NonUniqueResultException ex) { + logger.info("When looking up " + token + " caught " + ex); + } + return confirmEmailData; + } + + public List findConfirmEmailDataByDataverseUser(AuthenticatedUser user) { + TypedQuery typedQuery = em.createNamedQuery("ConfirmEmailData.findByUser", ConfirmEmailData.class); + typedQuery.setParameter("user", user); + List confirmEmailDatas = typedQuery.getResultList(); + return confirmEmailDatas; + } + + public List findAllConfirmEmailData() { + TypedQuery typedQuery = em.createNamedQuery("ConfirmEmailData.findAll", ConfirmEmailData.class); + List confirmEmailDatas = typedQuery.getResultList(); + return confirmEmailDatas; + } + + /** + * @return The number of tokens deleted. + */ + private long deleteAllExpiredTokens() { + long numDeleted = 0; + List allData = findAllConfirmEmailData(); + for (ConfirmEmailData data : allData) { + if (data.isExpired()) { + em.remove(data); + numDeleted++; + } + } + return numDeleted; + } + + /** + * @todo Do we need this method? Delete it if unused. + */ + public ConfirmEmailAttemptResponse attemptEmailConfirm(AuthenticatedUser user, String newPassword, String token) { + + final String messageSummarySuccess = "Email Confirmed Successfully"; + final String messageDetailSuccess = ""; + + // optimistic defaults :) + String messageSummary = messageSummarySuccess; + String messageDetail = messageDetailSuccess; + + final String messageSummaryFail = "Email Confirmation Problem"; + if (user == null) { + messageSummary = messageSummaryFail; + messageDetail = "User could not be found."; + return new ConfirmEmailAttemptResponse(false, messageSummary, messageDetail); + } + if (token == null) { + logger.info("No token provided... won't be able to delete it. Email address confirmed though."); + } + + AuthenticatedUser savedUser = dataverseUserService.save(user); + + if (savedUser != null) { + messageSummary = messageSummarySuccess; + messageDetail = messageDetailSuccess; + boolean tokenDeleted = deleteToken(token); + if (!tokenDeleted) { + // suboptimal but when it expires it should be deleted + logger.info("token " + token + " for user id " + user.getId() + " was not deleted"); + } + return new ConfirmEmailAttemptResponse(true, messageSummary, messageDetail); + } else { + messageSummary = messageSummaryFail; + messageDetail = "Your email was not confirmed. Please contact support."; + logger.info("Unable to save user " + user.getId()); + return new ConfirmEmailAttemptResponse(false, messageSummary, messageDetail); + } + + } + + private boolean deleteToken(String token) { + ConfirmEmailData doomed = findSingleConfirmEmailDataByToken(token); + try { + em.remove(doomed); + return true; + } catch (Exception ex) { + logger.info("Caught exception trying to delete token " + token + " - " + ex); + return false; + } + } + + public ConfirmEmailData createToken(AuthenticatedUser au) { + ConfirmEmailData confirmEmailData = new ConfirmEmailData(au); + em.persist(confirmEmailData); + return confirmEmailData; + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java b/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java index ab762a16daf..8e63f5133a5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java @@ -201,7 +201,15 @@ public String getSolrHostColonPort() { } - + /** + * The number of minutes for which a confirmation email link is valid. + * @todo: ask Phil about making it possible to set this value as sysadmin + * + */ + public static int getMinutesUntilConfirmEmailTokenExpires() { + final int reasonableDefault = 60; + return reasonableDefault; + } /** * The number of minutes for which a password reset token is valid. Can be * overridden by {@link #PASSWORD_RESET_TIMEOUT_IN_MINUTES}. diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 3e38558f8c5..57502eb7360 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -88,7 +88,8 @@ public static JsonObjectBuilder jsonForAuthUser(AuthenticatedUser authenticatedU .add("superuser", authenticatedUser.isSuperuser()) .add("affiliation", authenticatedUser.getAffiliation()) .add("position", authenticatedUser.getPosition()) - .add("persistentUserId", authenticatedUser.getAuthenticatedUserLookup().getPersistentUserId()) + .add("persistentUserId", authenticatedUser.getAuthenticatedUserLookup().getPersistentUserId()) + .add("emailLastConfirmed", authenticatedUser.getEmailConfirmed()) .add("authenticationProviderId", authenticatedUser.getAuthenticatedUserLookup().getAuthenticationProviderId()); } diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/NullSafeJsonBuilder.java b/src/main/java/edu/harvard/iq/dataverse/util/json/NullSafeJsonBuilder.java index 452a5366576..25c57b5cf7d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/NullSafeJsonBuilder.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/NullSafeJsonBuilder.java @@ -1,8 +1,10 @@ package edu.harvard.iq.dataverse.util.json; import edu.harvard.iq.dataverse.DatasetField; +import edu.harvard.iq.dataverse.api.Util; import java.math.BigDecimal; import java.math.BigInteger; +import java.sql.Timestamp; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObject; @@ -115,6 +117,8 @@ public NullSafeJsonBuilder addStrValue( String name, DatasetField field ) { public JsonObject build() { return delegate.build(); } - - + + public NullSafeJsonBuilder add(String name, Timestamp timestamp) { + return (timestamp != null) ? add(name, Util.getDateTimeFormat().format(timestamp)) : this; + } } diff --git a/src/main/webapp/confirmemail.xhtml b/src/main/webapp/confirmemail.xhtml new file mode 100644 index 00000000000..a4c19b0d686 --- /dev/null +++ b/src/main/webapp/confirmemail.xhtml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+
+
+ + +
+
+ + + + + +
+ +
+ +
+ + + + +
+
+
+
+ + +
+
+
+
+ + + + + +
+ +
+ + + + + + + + + + + +
+
+
+
+
+ + + + + + + +
+ + + + + +
+
+
+ + + + +

#{bundle['user.updatePassword.warning']}

+
+
+
+ +
+ +
+ + +
+
+
+ +
+ + +
+
+ + Terms of Use + + +
+
+ +
+
+
+
+
+
+
+
+
+ \ No newline at end of file diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java new file mode 100644 index 00000000000..e8899cd7fb3 --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -0,0 +1,198 @@ +package edu.harvard.iq.dataverse.api; + +import com.jayway.restassured.RestAssured; +import static com.jayway.restassured.RestAssured.given; +import com.jayway.restassured.http.ContentType; +import com.jayway.restassured.path.json.JsonPath; +import com.jayway.restassured.response.Response; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailData; +import java.util.UUID; +import java.util.logging.Logger; +import javax.json.Json; +import javax.json.JsonObjectBuilder; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.Matchers.startsWith; + +/** + * + * @author bsilverstein + * + * Next steps: + * + * - Switch from POST to confirmemail.xhtml + * + * - Change first email to say, "please click to confirm your email." See + * confirmUrl in ConfirmEmailInitResponse. + * + * - Show on user page if has been email confirmed (see mockups). + * + * - Do not expose confirm email token to user. + * + * - Call confirmEmailSvc.createToken when user is created in GUI. + * + * - Call confirmEmailSvc.createToken when user changes email address. + * + * - What effect should there be of not having a confirmed email? No emails are + * sent? User can't create stuff? + * + * - Make getMinutesUntilConfirmEmailTokenExpires configurable. How long should + * the default be? 24 hours? + * + */ +public class ConfirmEmailIT { + + private static final Logger logger = Logger.getLogger(ConfirmEmailIT.class.getCanonicalName()); + + private static final String builtinUserKey = "burrito"; + private static final String idKey = "id"; + private static final String usernameKey = "userName"; + private static final String emailKey = "email"; + private static final AuthenticatedUser authenticatedUser = new AuthenticatedUser(); + private static final ConfirmEmailData emailData = new ConfirmEmailData(authenticatedUser); + private static final String confirmToken = getConfirmEmailToken(emailData); + + @BeforeClass + public static void setUp() { + RestAssured.baseURI = UtilIT.getRestAssuredBaseUri(); + } + + @Test + public void testConfirm() { + // Can't seem to get timestamp to appear in authenticated user Json output + + String email = null; + + Response createUserToConfirm = createUser(getRandomUsername(), "firstName", "lastName", email); + createUserToConfirm.prettyPrint(); + // TODO: do not expose confirm email token to user, just in email URL + String confirmEmailToken = JsonPath.from(createUserToConfirm.body().asString()).getString("data.confirmEmailToken"); + createUserToConfirm.then().assertThat() + .statusCode(200); + + //redundant? + long userIdToConfirm = JsonPath.from(createUserToConfirm.body().asString()).getLong("data.authenticatedUser.id"); + String userToConfirmApiToken = JsonPath.from(createUserToConfirm.body().asString()).getString("data.apiToken"); + String usernameToConfirm = JsonPath.from(createUserToConfirm.body().asString()).getString("data.user.userName"); +// Response getApiToken = getApiTokenUsingUsername(usernameToConfirm, usernameToConfirm); +// getApiToken.then().assertThat() +// .statusCode(200); + String junkToken = "noSuchToken"; + + Response createSuperuser = createUser(getRandomUsername(), "super", "user", email); + createSuperuser.then().assertThat() + .statusCode(200); + String superuserUsername = JsonPath.from(createSuperuser.body().asString()).getString("data.user.userName"); + String superUserApiToken = JsonPath.from(createUserToConfirm.body().asString()).getString("data.apiToken"); + + UtilIT.makeSuperUser(superuserUsername); + createSuperuser.then().assertThat() + .statusCode(200); + + /** + * @todo: Superuser GET confirm email token based on user's database ID + * (primary key). This can answer questions the superuser may have, such + * as, "Did the user's token expire?" + */ + Response getConfirmEmailData = given() + .get("/api/admin/confirmEmail/" + 42); + + Response noSuchToken = given() + .post("/api/admin/confirmEmail/" + junkToken); + noSuchToken.prettyPrint(); + noSuchToken.then().assertThat() + .statusCode(404) + .body("message", equalTo("Invalid token: " + junkToken)); + + System.out.println("not confirmed yet"); + Response getUserWithoutConfirmedEmail = UtilIT.getAuthenticatedUser(usernameToConfirm, superUserApiToken); + getUserWithoutConfirmedEmail.prettyPrint(); + getUserWithoutConfirmedEmail.then().assertThat() + .statusCode(200) + .body("data.emailLastConfirmed", nullValue()); + + /** + * + * User will call a second method within admin API to POST token to new + * endpoint /api/admin/confirmEmail/{token} + * + */ + System.out.println("real token: " + confirmEmailToken); + // This is simulating the user clicking the URL from their email client. + Response confirmEmail = given() + .post("/api/admin/confirmEmail/" + confirmEmailToken); + confirmEmail.prettyPrint(); + confirmEmail.then().assertThat() + .statusCode(200); + + /** + * @todo Switch over to this instead of the POST above, once it's + * working. + */ +// Response confirmEmailViaBrowser = given() +// .get("/confirmemail.xhtml?token=" + confirmEmailToken); +// confirmEmailViaBrowser.then().assertThat() +// .statusCode(200); + Response getUserWithConfirmedEmail = UtilIT.getAuthenticatedUser(usernameToConfirm, superUserApiToken); + getUserWithConfirmedEmail.prettyPrint(); + getUserWithConfirmedEmail.then().assertThat() + .statusCode(200) + // Checking that it's 2016 or whatever. Not y3k compliant! + .body("data.emailLastConfirmed", startsWith("2")); + } + + private Response createUser(String username, String firstName, String lastName, String email) { + String userAsJson = getUserAsJsonString(username, firstName, lastName, email); + String password = getPassword(userAsJson); + Response response = given() + .body(userAsJson) + .contentType(ContentType.JSON) + .post("/api/builtin-users?key=" + builtinUserKey + "&password=" + password); + return response; + } + + private static String getRandomUsername() { + return UUID.randomUUID().toString().substring(0, 8); + } + + private static String getUserAsJsonString(String username, String firstName, String lastName, String email) { + JsonObjectBuilder builder = Json.createObjectBuilder(); + builder.add(usernameKey, username); + builder.add("firstName", firstName); + builder.add("lastName", lastName); + if (email == null) { + builder.add(emailKey, getEmailFromUserName(username)); + } else { + builder.add(emailKey, email); + } + + String userAsJson = builder.build().toString(); + logger.fine("User to create: " + userAsJson); + return userAsJson; + } + + private static String getPassword(String jsonStr) { + String password = JsonPath.from(jsonStr).get(usernameKey); + return password; + } + + private static String getEmailFromUserName(String username) { + return username + "@mailinator.com"; + } + + private static String getConfirmEmailToken(ConfirmEmailData emailData) { + String confirmToken = emailData.getToken(); + return confirmToken; + } + + private Response getApiTokenUsingUsername(String username, String password) { + Response response = given() + .contentType(ContentType.JSON) + .get("/api/builtin-users/" + username + "/api-token?username=" + username + "&password=" + password); + return response; + } + +} From ab55794208469038b3dabe2a9122c52c93e230be Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Mon, 25 Jul 2016 16:29:52 -0400 Subject: [PATCH 02/44] Confirm Email: don't expose token to user on create #2170 --- .../edu/harvard/iq/dataverse/api/Admin.java | 30 +++++++---- .../iq/dataverse/api/BuiltinUsers.java | 10 +--- .../iq/dataverse/api/ConfirmEmailIT.java | 54 ++++--------------- 3 files changed, 32 insertions(+), 62 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java index 17436a98c10..693016f7a55 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java @@ -37,6 +37,7 @@ import static edu.harvard.iq.dataverse.util.json.JsonPrinter.*; import java.sql.Timestamp; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -524,17 +525,26 @@ public Response validate() { } return okResponse(msg); } - - //should the path for this method refer instead to the auth user ID? Yes. - // Call confirmEmailSvc.findConfirmEmailDataByDataverseUser. We need this - // to close the security hole of exposing the token on create. - @Path("confirmEmail/{token}") - @GET - public Response getConfirmEmailToken(@PathParam("token") String token) { - ConfirmEmailExecResponse confirmEmailExecResponse = confirmEmailSvc.processToken(token); - ConfirmEmailData confirmEmailData = confirmEmailExecResponse.getConfirmEmailData(); - return null; + /** + * 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) { + List confirmEmailDatas = confirmEmailSvc.findConfirmEmailDataByDataverseUser(user); + int size = confirmEmailDatas.size(); + if (size == 1) { + ConfirmEmailData confirmEmailData = confirmEmailDatas.get(0); + return okResponse(Json.createObjectBuilder().add("token", confirmEmailData.getToken())); + } + } + return errorResponse(Status.BAD_REQUEST, "Could not find confirm email token for user " + userId); } /** diff --git a/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java b/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java index c0668d4138e..45c2a27f2d3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java @@ -8,8 +8,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.confirmemail.ConfirmEmailData; -import edu.harvard.iq.dataverse.util.json.JsonPrinter; import java.sql.Timestamp; import java.util.Calendar; import java.util.logging.Level; @@ -135,18 +133,12 @@ private Response internalSave(BuiltinUser user, String password, String key) { token.setExpireTime(new Timestamp(c.getTimeInMillis())); authSvc.save(token); - ConfirmEmailData confirmEmailData = confirmEmailSvc.createToken(au); + confirmEmailSvc.createToken(au); JsonObjectBuilder resp = Json.createObjectBuilder(); resp.add("user", json(user)); resp.add("authenticatedUser", jsonForAuthUser(au)); resp.add("apiToken", token.getTokenString()); - /** - * @todo Remove this once a superuser can retrieve the token via the - * admin API. We don't want users to get the confirm email token - * except via email. - */ - resp.add("confirmEmailToken", confirmEmailData.getToken()); alr.setInfo("builtinUser:" + user.getUserName() + " authenticatedUser:" + au.getIdentifier() ); return okResponse(resp); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index e8899cd7fb3..26bcc587323 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -5,8 +5,6 @@ import com.jayway.restassured.http.ContentType; import com.jayway.restassured.path.json.JsonPath; import com.jayway.restassured.response.Response; -import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; -import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailData; import java.util.UUID; import java.util.logging.Logger; import javax.json.Json; @@ -30,8 +28,6 @@ * * - Show on user page if has been email confirmed (see mockups). * - * - Do not expose confirm email token to user. - * * - Call confirmEmailSvc.createToken when user is created in GUI. * * - Call confirmEmailSvc.createToken when user changes email address. @@ -51,9 +47,6 @@ public class ConfirmEmailIT { private static final String idKey = "id"; private static final String usernameKey = "userName"; private static final String emailKey = "email"; - private static final AuthenticatedUser authenticatedUser = new AuthenticatedUser(); - private static final ConfirmEmailData emailData = new ConfirmEmailData(authenticatedUser); - private static final String confirmToken = getConfirmEmailToken(emailData); @BeforeClass public static void setUp() { @@ -62,25 +55,19 @@ public static void setUp() { @Test public void testConfirm() { - // Can't seem to get timestamp to appear in authenticated user Json output String email = null; - + /** + * @todo Switch this to UtilIT.createRandomUser() + */ Response createUserToConfirm = createUser(getRandomUsername(), "firstName", "lastName", email); createUserToConfirm.prettyPrint(); - // TODO: do not expose confirm email token to user, just in email URL - String confirmEmailToken = JsonPath.from(createUserToConfirm.body().asString()).getString("data.confirmEmailToken"); createUserToConfirm.then().assertThat() .statusCode(200); - //redundant? long userIdToConfirm = JsonPath.from(createUserToConfirm.body().asString()).getLong("data.authenticatedUser.id"); String userToConfirmApiToken = JsonPath.from(createUserToConfirm.body().asString()).getString("data.apiToken"); String usernameToConfirm = JsonPath.from(createUserToConfirm.body().asString()).getString("data.user.userName"); -// Response getApiToken = getApiTokenUsingUsername(usernameToConfirm, usernameToConfirm); -// getApiToken.then().assertThat() -// .statusCode(200); - String junkToken = "noSuchToken"; Response createSuperuser = createUser(getRandomUsername(), "super", "user", email); createSuperuser.then().assertThat() @@ -92,14 +79,7 @@ public void testConfirm() { createSuperuser.then().assertThat() .statusCode(200); - /** - * @todo: Superuser GET confirm email token based on user's database ID - * (primary key). This can answer questions the superuser may have, such - * as, "Did the user's token expire?" - */ - Response getConfirmEmailData = given() - .get("/api/admin/confirmEmail/" + 42); - + String junkToken = "noSuchToken"; Response noSuchToken = given() .post("/api/admin/confirmEmail/" + junkToken); noSuchToken.prettyPrint(); @@ -114,13 +94,13 @@ public void testConfirm() { .statusCode(200) .body("data.emailLastConfirmed", nullValue()); - /** - * - * User will call a second method within admin API to POST token to new - * endpoint /api/admin/confirmEmail/{token} - * - */ - System.out.println("real token: " + confirmEmailToken); + Response getToken = given() + .get("/api/admin/confirmEmail/" + userIdToConfirm); + getToken.prettyPrint(); + getToken.then().assertThat() + .statusCode(200); + String confirmEmailToken = JsonPath.from(getToken.body().asString()).getString("data.token"); + // This is simulating the user clicking the URL from their email client. Response confirmEmail = given() .post("/api/admin/confirmEmail/" + confirmEmailToken); @@ -183,16 +163,4 @@ private static String getEmailFromUserName(String username) { return username + "@mailinator.com"; } - private static String getConfirmEmailToken(ConfirmEmailData emailData) { - String confirmToken = emailData.getToken(); - return confirmToken; - } - - private Response getApiTokenUsingUsername(String username, String password) { - Response response = given() - .contentType(ContentType.JSON) - .get("/api/builtin-users/" + username + "/api-token?username=" + username + "&password=" + password); - return response; - } - } From dd768d9c0865f1dddc1466ce6f693bf530787102 Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Tue, 26 Jul 2016 11:26:48 -0400 Subject: [PATCH 03/44] Successful confirmation in int. test via xhtml page/logic reformat #2170 --- .../confirmemail/ConfirmEmailPage.java | 5 ++ .../confirmemail/ConfirmEmailServiceBean.java | 11 ++++ src/main/webapp/confirmemail.xhtml | 62 +++---------------- .../iq/dataverse/api/ConfirmEmailIT.java | 18 +++--- 4 files changed, 32 insertions(+), 64 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java index 7045a21bf42..20b15d73ccf 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java @@ -69,13 +69,18 @@ public class ConfirmEmailPage implements java.io.Serializable { public void init() { if (token != null) { + logger.info("Token found"); ConfirmEmailExecResponse confirmEmailExecResponse = confirmEmailService.processToken(token); confirmEmailData = confirmEmailExecResponse.getConfirmEmailData(); if (confirmEmailData != null) { + logger.info("confirmEmailData found"); user = confirmEmailData.getAuthenticatedUser(); } else { + logger.info("confirmEmailData not found"); FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Confirm Email Link", "Your email confirmation link is not valid.")); } + } else { + logger.info("Token not found"); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index 74031d0b870..17505271cb0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -5,6 +5,8 @@ import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.MailServiceBean; import edu.harvard.iq.dataverse.util.SystemConfig; +import java.sql.Timestamp; +import java.util.Date; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -15,6 +17,7 @@ import javax.persistence.NonUniqueResultException; import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; +import javax.ws.rs.core.Response; /** * @@ -117,6 +120,14 @@ public ConfirmEmailExecResponse processToken(String tokenQueried) { return tokenUnusable; } else { ConfirmEmailExecResponse goodTokenCanProceed = new ConfirmEmailExecResponse(tokenQueried, confirmEmailData); + if (confirmEmailData == null) { + logger.info("Invalid token."); + return null; + } + long nowInMilliseconds = new Date().getTime(); + Timestamp emailConfirmed = new Timestamp(nowInMilliseconds); + AuthenticatedUser authenticatedUser = confirmEmailData.getAuthenticatedUser(); + authenticatedUser.setEmailConfirmed(emailConfirmed); return goodTokenCanProceed; } } else { diff --git a/src/main/webapp/confirmemail.xhtml b/src/main/webapp/confirmemail.xhtml index a4c19b0d686..bbd249572fd 100644 --- a/src/main/webapp/confirmemail.xhtml +++ b/src/main/webapp/confirmemail.xhtml @@ -21,12 +21,12 @@ - + - -
- - - - - -
-
-
- - - - -

#{bundle['user.updatePassword.warning']}

-
-
-
- -
- -
- - -
-
-
- -
- - -
-
- - Terms of Use - - -
-
- -
-
-
-
-
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index 26bcc587323..0f7e917040c 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -102,20 +102,20 @@ public void testConfirm() { String confirmEmailToken = JsonPath.from(getToken.body().asString()).getString("data.token"); // This is simulating the user clicking the URL from their email client. - Response confirmEmail = given() - .post("/api/admin/confirmEmail/" + confirmEmailToken); - confirmEmail.prettyPrint(); - confirmEmail.then().assertThat() - .statusCode(200); +// Response confirmEmail = given() +// .post("/api/admin/confirmEmail/" + confirmEmailToken); +// confirmEmail.prettyPrint(); +// confirmEmail.then().assertThat() +// .statusCode(200); /** * @todo Switch over to this instead of the POST above, once it's * working. */ -// Response confirmEmailViaBrowser = given() -// .get("/confirmemail.xhtml?token=" + confirmEmailToken); -// confirmEmailViaBrowser.then().assertThat() -// .statusCode(200); + Response confirmEmailViaBrowser = given() + .get("/confirmemail.xhtml?token=" + confirmEmailToken); + confirmEmailViaBrowser.then().assertThat() + .statusCode(200); Response getUserWithConfirmedEmail = UtilIT.getAuthenticatedUser(usernameToConfirm, superUserApiToken); getUserWithConfirmedEmail.prettyPrint(); getUserWithConfirmedEmail.then().assertThat() From 8b79ec1ed90e5d3d39ebf47f0648f222ad35edd1 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Tue, 26 Jul 2016 14:16:38 -0400 Subject: [PATCH 04/44] Confirm email: delete old, unused code #2170 --- .../edu/harvard/iq/dataverse/api/Admin.java | 24 ----- .../iq/dataverse/api/ConfirmEmailIT.java | 90 ++++--------------- 2 files changed, 19 insertions(+), 95 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java index 693016f7a55..a14e7bc509b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java @@ -19,7 +19,6 @@ 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.ConfirmEmailExecResponse; import edu.harvard.iq.dataverse.engine.command.impl.PublishDataverseCommand; import edu.harvard.iq.dataverse.settings.Setting; import javax.json.Json; @@ -35,8 +34,6 @@ import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder; import static edu.harvard.iq.dataverse.util.json.JsonPrinter.*; -import java.sql.Timestamp; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.logging.Level; @@ -547,25 +544,4 @@ public Response getConfirmEmailToken(@PathParam("userId") long userId) { return errorResponse(Status.BAD_REQUEST, "Could not find confirm email token for user " + userId); } - /** - * This is just for testing the confirm email feature and is used in - * integration tests. We may delete this endpoint entirely if we can test - * everything via the xhtml file. - */ - @Path("confirmEmail/{token}") - @POST - public Response confirmTheEmail(@PathParam("token") String token) { - // TODO: move as much logic as possible to a central place like confirmEmailSvc.processToken - ConfirmEmailExecResponse confirmEmailExecResponse = confirmEmailSvc.processToken(token); - ConfirmEmailData confirmEmailData = confirmEmailExecResponse.getConfirmEmailData(); - if (confirmEmailData == null) { - return errorResponse(Status.NOT_FOUND, "Invalid token: " + token); - } - long nowInMilliseconds = new Date().getTime(); - Timestamp emailConfirmed = new Timestamp(nowInMilliseconds); - AuthenticatedUser authenticatedUser = confirmEmailData.getAuthenticatedUser(); - authenticatedUser.setEmailConfirmed(emailConfirmed); - return okResponse("Email confirmed."); - } - } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index 0f7e917040c..c0d75e007e2 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -2,16 +2,11 @@ import com.jayway.restassured.RestAssured; import static com.jayway.restassured.RestAssured.given; -import com.jayway.restassured.http.ContentType; import com.jayway.restassured.path.json.JsonPath; import com.jayway.restassured.response.Response; -import java.util.UUID; import java.util.logging.Logger; -import javax.json.Json; -import javax.json.JsonObjectBuilder; import org.junit.BeforeClass; import org.junit.Test; -import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.startsWith; @@ -21,8 +16,6 @@ * * Next steps: * - * - Switch from POST to confirmemail.xhtml - * * - Change first email to say, "please click to confirm your email." See * confirmUrl in ConfirmEmailInitResponse. * @@ -56,11 +49,7 @@ public static void setUp() { @Test public void testConfirm() { - String email = null; - /** - * @todo Switch this to UtilIT.createRandomUser() - */ - Response createUserToConfirm = createUser(getRandomUsername(), "firstName", "lastName", email); + Response createUserToConfirm = UtilIT.createRandomUser(); createUserToConfirm.prettyPrint(); createUserToConfirm.then().assertThat() .statusCode(200); @@ -69,7 +58,7 @@ public void testConfirm() { String userToConfirmApiToken = JsonPath.from(createUserToConfirm.body().asString()).getString("data.apiToken"); String usernameToConfirm = JsonPath.from(createUserToConfirm.body().asString()).getString("data.user.userName"); - Response createSuperuser = createUser(getRandomUsername(), "super", "user", email); + Response createSuperuser = UtilIT.createRandomUser(); createSuperuser.then().assertThat() .statusCode(200); String superuserUsername = JsonPath.from(createSuperuser.body().asString()).getString("data.user.userName"); @@ -79,14 +68,6 @@ public void testConfirm() { createSuperuser.then().assertThat() .statusCode(200); - String junkToken = "noSuchToken"; - Response noSuchToken = given() - .post("/api/admin/confirmEmail/" + junkToken); - noSuchToken.prettyPrint(); - noSuchToken.then().assertThat() - .statusCode(404) - .body("message", equalTo("Invalid token: " + junkToken)); - System.out.println("not confirmed yet"); Response getUserWithoutConfirmedEmail = UtilIT.getAuthenticatedUser(usernameToConfirm, superUserApiToken); getUserWithoutConfirmedEmail.prettyPrint(); @@ -101,21 +82,27 @@ public void testConfirm() { .statusCode(200); String confirmEmailToken = JsonPath.from(getToken.body().asString()).getString("data.token"); - // This is simulating the user clicking the URL from their email client. -// Response confirmEmail = given() -// .post("/api/admin/confirmEmail/" + confirmEmailToken); -// confirmEmail.prettyPrint(); -// confirmEmail.then().assertThat() -// .statusCode(200); - - /** - * @todo Switch over to this instead of the POST above, once it's - * working. - */ + String junkToken = "noSuchToken"; +// Response noSuchToken = given() +// .post("/api/admin/confirmEmail/" + junkToken); +// noSuchToken.prettyPrint(); +// noSuchToken.then().assertThat() +// .statusCode(404) +// .body("message", equalTo("Invalid token: " + junkToken)); + Response confirmEmailViaBrowserJunkToken = given() + .get("/confirmemail.xhtml?token=" + junkToken); + confirmEmailViaBrowserJunkToken.then().assertThat() + /** + * @todo Make this a 404 rather than a 200. Then remove this + * commented code above (the old POST method). + */ + .statusCode(200); + Response confirmEmailViaBrowser = given() .get("/confirmemail.xhtml?token=" + confirmEmailToken); confirmEmailViaBrowser.then().assertThat() .statusCode(200); + Response getUserWithConfirmedEmail = UtilIT.getAuthenticatedUser(usernameToConfirm, superUserApiToken); getUserWithConfirmedEmail.prettyPrint(); getUserWithConfirmedEmail.then().assertThat() @@ -124,43 +111,4 @@ public void testConfirm() { .body("data.emailLastConfirmed", startsWith("2")); } - private Response createUser(String username, String firstName, String lastName, String email) { - String userAsJson = getUserAsJsonString(username, firstName, lastName, email); - String password = getPassword(userAsJson); - Response response = given() - .body(userAsJson) - .contentType(ContentType.JSON) - .post("/api/builtin-users?key=" + builtinUserKey + "&password=" + password); - return response; - } - - private static String getRandomUsername() { - return UUID.randomUUID().toString().substring(0, 8); - } - - private static String getUserAsJsonString(String username, String firstName, String lastName, String email) { - JsonObjectBuilder builder = Json.createObjectBuilder(); - builder.add(usernameKey, username); - builder.add("firstName", firstName); - builder.add("lastName", lastName); - if (email == null) { - builder.add(emailKey, getEmailFromUserName(username)); - } else { - builder.add(emailKey, email); - } - - String userAsJson = builder.build().toString(); - logger.fine("User to create: " + userAsJson); - return userAsJson; - } - - private static String getPassword(String jsonStr) { - String password = JsonPath.from(jsonStr).get(usernameKey); - return password; - } - - private static String getEmailFromUserName(String username) { - return username + "@mailinator.com"; - } - } From 3fc6e8f045ff84f5ce56b9a8060efeb4da1ad262 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Tue, 26 Jul 2016 15:31:17 -0400 Subject: [PATCH 05/44] make minutesUntilConfirmEmailTokenExpires configurable #2170 --- .../confirmemail/ConfirmEmailData.java | 10 ++----- .../confirmemail/ConfirmEmailServiceBean.java | 9 +++--- .../settings/SettingsServiceBean.java | 4 +++ .../iq/dataverse/util/SystemConfig.java | 29 +++++++++++++------ .../iq/dataverse/api/ConfirmEmailIT.java | 3 -- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java index 0a8b96c7ccc..8a81493ef99 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java @@ -1,7 +1,6 @@ package edu.harvard.iq.dataverse.confirmemail; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; -import edu.harvard.iq.dataverse.util.SystemConfig; import java.io.Serializable; import java.sql.Timestamp; import java.util.Date; @@ -53,18 +52,13 @@ public class ConfirmEmailData implements Serializable { @Column(nullable = false) private Timestamp expires; - public ConfirmEmailData(AuthenticatedUser anAuthenticatedUser) { + public ConfirmEmailData(AuthenticatedUser anAuthenticatedUser, long minutesUntilConfirmEmailTokenExpires) { authenticatedUser = anAuthenticatedUser; token = UUID.randomUUID().toString(); long nowInMilliseconds = new Date().getTime(); created = new Timestamp(nowInMilliseconds); long ONE_MINUTE_IN_MILLISECONDS = 60000; - /** - * @todo: use database setting instead of jvm option for line 75 - * configurable expiration value - */ - - long futureInMilliseconds = nowInMilliseconds + (SystemConfig.getMinutesUntilConfirmEmailTokenExpires() * ONE_MINUTE_IN_MILLISECONDS); + long futureInMilliseconds = nowInMilliseconds + (minutesUntilConfirmEmailTokenExpires * ONE_MINUTE_IN_MILLISECONDS); expires = new Timestamp(new Date(futureInMilliseconds).getTime()); } diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index 17505271cb0..adacb3315e7 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -1,6 +1,5 @@ package edu.harvard.iq.dataverse.confirmemail; -import edu.harvard.iq.dataverse.authorization.users.ApiToken; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.MailServiceBean; @@ -17,7 +16,6 @@ import javax.persistence.NonUniqueResultException; import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; -import javax.ws.rs.core.Response; /** * @@ -34,6 +32,9 @@ public class ConfirmEmailServiceBean { @EJB MailServiceBean mailService; + @EJB + SystemConfig systemConfig; + @PersistenceContext(unitName = "VDCNet-ejbPU") private EntityManager em; @@ -61,7 +62,7 @@ public ConfirmEmailInitResponse sendConfirm(AuthenticatedUser aUser, boolean sen } // create a fresh token for the user - ConfirmEmailData confirmEmailData = new ConfirmEmailData(aUser); + ConfirmEmailData confirmEmailData = new ConfirmEmailData(aUser, systemConfig.getMinutesUntilConfirmEmailTokenExpires()); try { em.persist(confirmEmailData); ConfirmEmailInitResponse confirmEmailInitResponse = new ConfirmEmailInitResponse(true, confirmEmailData); @@ -233,7 +234,7 @@ private boolean deleteToken(String token) { } public ConfirmEmailData createToken(AuthenticatedUser au) { - ConfirmEmailData confirmEmailData = new ConfirmEmailData(au); + ConfirmEmailData confirmEmailData = new ConfirmEmailData(au, systemConfig.getMinutesUntilConfirmEmailTokenExpires()); em.persist(confirmEmailData); return confirmEmailData; } diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java index 2856df3a613..fecaea5b08b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java @@ -33,6 +33,10 @@ public class SettingsServiceBean { * So there. */ public enum Key { + /** + * @todo Document this in the Installation Guide. + */ + MinutesUntilConfirmEmailTokenExpires, /** * Override Solr highlighting "fragsize" * https://wiki.apache.org/solr/HighlightingParameters#hl.fragsize diff --git a/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java b/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java index 8e63f5133a5..be65266c5a9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java @@ -199,17 +199,28 @@ public String getSolrHostColonPort() { String solrHostColonPort = settingsService.getValueForKey(SettingsServiceBean.Key.SolrHostColonPort, saneDefaultForSolrHostColonPort); return solrHostColonPort; } - - - /** - * The number of minutes for which a confirmation email link is valid. - * @todo: ask Phil about making it possible to set this value as sysadmin - * - */ - public static int getMinutesUntilConfirmEmailTokenExpires() { - final int reasonableDefault = 60; + + public int getMinutesUntilConfirmEmailTokenExpires() { + final int minutesInOneDay = 1440; + final int reasonableDefault = minutesInOneDay; + SettingsServiceBean.Key key = SettingsServiceBean.Key.MinutesUntilConfirmEmailTokenExpires; + String valueFromDatabase = settingsService.getValueForKey(key); + if (valueFromDatabase != null) { + try { + int intFromDatabase = Integer.parseInt(valueFromDatabase); + if (intFromDatabase > 0) { + return intFromDatabase; + } else { + logger.info("Returning " + reasonableDefault + " for " + key + " because value must be greater than zero, not \"" + intFromDatabase + "\"."); + } + } catch (NumberFormatException ex) { + logger.info("Returning " + reasonableDefault + " for " + key + " because value must be an integer greater than zero, not \"" + valueFromDatabase + "\"."); + } + } + logger.fine("Returning " + reasonableDefault + " for " + key); return reasonableDefault; } + /** * The number of minutes for which a password reset token is valid. Can be * overridden by {@link #PASSWORD_RESET_TIMEOUT_IN_MINUTES}. diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index c0d75e007e2..13ce2e8e73b 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -28,9 +28,6 @@ * - What effect should there be of not having a confirmed email? No emails are * sent? User can't create stuff? * - * - Make getMinutesUntilConfirmEmailTokenExpires configurable. How long should - * the default be? 24 hours? - * */ public class ConfirmEmailIT { From 814d58b880cbbac4b5cd99b9a20aa811287661fc Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Tue, 26 Jul 2016 15:54:23 -0400 Subject: [PATCH 06/44] Confirm Email: create token in central location #2170 --- .../java/edu/harvard/iq/dataverse/api/BuiltinUsers.java | 2 -- .../dataverse/authorization/AuthenticationServiceBean.java | 6 ++++++ .../java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java | 2 -- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java b/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java index 45c2a27f2d3..104fbb09baa 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java @@ -133,8 +133,6 @@ private Response internalSave(BuiltinUser user, String password, String key) { token.setExpireTime(new Timestamp(c.getTimeInMillis())); authSvc.save(token); - confirmEmailSvc.createToken(au); - JsonObjectBuilder resp = Json.createObjectBuilder(); resp.add("user", json(user)); resp.add("authenticatedUser", jsonForAuthUser(au)); diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java index 5f2e274a330..6c549846210 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java @@ -18,6 +18,7 @@ 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.ConfirmEmailServiceBean; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Calendar; @@ -72,6 +73,9 @@ public class AuthenticationServiceBean { @EJB UserNotificationServiceBean userNotificationService; + @EJB + ConfirmEmailServiceBean confirmEmailService; + @PersistenceContext(unitName = "VDCNet-ejbPU") private EntityManager em; @@ -430,6 +434,8 @@ public AuthenticatedUser createAuthenticatedUser(UserRecordIdentifier userRecord AuthenticatedUserLookup auusLookup = userRecordId.createAuthenticatedUserLookup(authenticatedUser); em.persist( auusLookup ); authenticatedUser.setAuthenticatedUserLookup(auusLookup); + + confirmEmailService.createToken(authenticatedUser); actionLogSvc.log( new ActionLogRecord(ActionLogRecord.ActionType.Auth, "createUser") .setInfo(authenticatedUser.getIdentifier())); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index 13ce2e8e73b..18271d26ce9 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -21,8 +21,6 @@ * * - Show on user page if has been email confirmed (see mockups). * - * - Call confirmEmailSvc.createToken when user is created in GUI. - * * - Call confirmEmailSvc.createToken when user changes email address. * * - What effect should there be of not having a confirmed email? No emails are From 540cb9570b19182ea7636d56007c65e5abf980e3 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Wed, 27 Jul 2016 10:54:57 -0400 Subject: [PATCH 07/44] Confirm Email: show "Not Verified" #2170 --- src/main/webapp/dataverseuser.xhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index a9ff9844789..b32428fd25d 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -315,7 +315,7 @@ #{bundle.email}
-

#{DataverseUserPage.currentUser.email}

+

#{DataverseUserPage.currentUser.email}

Not Verified

From 4bb6f7be77d20c1358aba1b93ae08cd2a669ead0 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Wed, 27 Jul 2016 10:59:17 -0400 Subject: [PATCH 08/44] set "confirmed" timestamp to null on email change, create token #2170 --- .../providers/builtin/BuiltinUserPage.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index 8ef382731ea..209d542e944 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -27,6 +27,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.ConfirmEmailData; import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailException; import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailServiceBean; import edu.harvard.iq.dataverse.mydata.MyDataPage; @@ -429,6 +430,7 @@ public void updatePassword(String userName) { public String save() { boolean passwordChanged = false; + boolean emailChanged = false; if (editMode == EditMode.CREATE || editMode == EditMode.CHANGE_PASSWORD) { if (inputPassword != null) { builtinUser.updateEncryptedPassword(PasswordEncryption.get().encrypt(inputPassword), PasswordEncryption.getLatestVersionNumber()); @@ -480,13 +482,30 @@ public String save() { } else { - authSvc.updateAuthenticatedUser(currentUser, builtinUser.getDisplayInfo()); + String emailBeforeUpdate = currentUser.getEmail(); + AuthenticatedUser savedUser = authSvc.updateAuthenticatedUser(currentUser, builtinUser.getDisplayInfo()); + String emailAfterUpdate = savedUser.getEmail(); + if (!emailBeforeUpdate.equals(emailAfterUpdate)) { + emailChanged = true; + } editMode = null; String msg = "Your account information has been successfully updated."; if (passwordChanged) { msg = "Your account password has been successfully changed."; } - JsfHelper.addFlashMessage(msg); + if (emailChanged) { + msg = msg + " Your email address changed and must be re-confirmed."; + /** + * @todo In addition to creating the token, email the link to + * the user. + */ + ConfirmEmailData confirmEmailData = confirmEmailService.createToken(savedUser); + logger.info("Confirm email token created for user " + savedUser.getId() + ": " + confirmEmailData.getToken()); + savedUser.setEmailConfirmed(null); + JsfHelper.addWarningMessage(msg); + } else { + JsfHelper.addFlashMessage(msg); + } return null; } } From a0dc4f91e4c6e66c4078566d696a2c8d8f6c1a3b Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Wed, 27 Jul 2016 11:32:40 -0400 Subject: [PATCH 09/44] Added logic to remove used token upon confirmation reflected in IT #2170 --- src/main/java/Bundle.properties | 3 +- .../confirmemail/ConfirmEmailPage.java | 5 +- .../confirmemail/ConfirmEmailServiceBean.java | 1 + src/main/webapp/confirmemail.xhtml | 75 ++++--------------- .../iq/dataverse/api/ConfirmEmailIT.java | 7 ++ 5 files changed, 27 insertions(+), 64 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index 6d15909d0c9..4b1740c05ee 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -206,7 +206,8 @@ login.error=Error validating the username, email address, or password. Please tr confirmEmail.pageTitle=Email Confirmation confirmEmail.submitRequest=Verify Email confirmEmail.details.success=Email address confirmed! Redirecting you shortly... -confirmEmail.details.failure=We were unable to verify your email address. Please re-enter your email and click the button below to resend. +confirmEmail.details.failure=We were unable to verify your email address. Please navigate to your Account Information page and click the "Verify Email" button. + #shib.xhtml shib.btn.convertAccount=Convert Account diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java index 20b15d73ccf..ef082cc1b7b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java @@ -67,7 +67,7 @@ public class ConfirmEmailPage implements java.io.Serializable { ConfirmEmailData confirmEmailData; - public void init() { + public String init() { if (token != null) { logger.info("Token found"); ConfirmEmailExecResponse confirmEmailExecResponse = confirmEmailService.processToken(token); @@ -75,12 +75,15 @@ public void init() { if (confirmEmailData != null) { logger.info("confirmEmailData found"); user = confirmEmailData.getAuthenticatedUser(); + return "/"; } else { logger.info("confirmEmailData not found"); FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Confirm Email Link", "Your email confirmation link is not valid.")); + return "/404.xhtml"; } } else { logger.info("Token not found"); + return "/404.xhtml"; } } diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index adacb3315e7..1eea683f890 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -129,6 +129,7 @@ public ConfirmEmailExecResponse processToken(String tokenQueried) { Timestamp emailConfirmed = new Timestamp(nowInMilliseconds); AuthenticatedUser authenticatedUser = confirmEmailData.getAuthenticatedUser(); authenticatedUser.setEmailConfirmed(emailConfirmed); + em.remove(confirmEmailData); return goodTokenCanProceed; } } else { diff --git a/src/main/webapp/confirmemail.xhtml b/src/main/webapp/confirmemail.xhtml index bbd249572fd..f89f15ba26e 100644 --- a/src/main/webapp/confirmemail.xhtml +++ b/src/main/webapp/confirmemail.xhtml @@ -20,75 +20,26 @@ - - +
- - - - - + + diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index 18271d26ce9..4127bef55ca 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -92,6 +92,7 @@ public void testConfirm() { * commented code above (the old POST method). */ .statusCode(200); + Response confirmEmailViaBrowser = given() .get("/confirmemail.xhtml?token=" + confirmEmailToken); @@ -104,6 +105,12 @@ public void testConfirm() { .statusCode(200) // Checking that it's 2016 or whatever. Not y3k compliant! .body("data.emailLastConfirmed", startsWith("2")); + + Response getToken2 = given() + .get("/api/admin/confirmEmail/" + userIdToConfirm); + getToken2.prettyPrint(); + getToken2.then().assertThat() + .statusCode(400); } } From 733a1807dd7622c8f1130800dfa9335c5f2df9ee Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Wed, 27 Jul 2016 11:43:46 -0400 Subject: [PATCH 10/44] Confirm Email: expect a 404 when no token found #2170 --- .../iq/dataverse/api/ConfirmEmailIT.java | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index 4127bef55ca..e56a3c1bb7d 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -5,6 +5,7 @@ import com.jayway.restassured.path.json.JsonPath; import com.jayway.restassured.response.Response; import java.util.logging.Logger; +import static junit.framework.Assert.assertEquals; import org.junit.BeforeClass; import org.junit.Test; import static org.hamcrest.CoreMatchers.nullValue; @@ -19,9 +20,7 @@ * - Change first email to say, "please click to confirm your email." See * confirmUrl in ConfirmEmailInitResponse. * - * - Show on user page if has been email confirmed (see mockups). - * - * - Call confirmEmailSvc.createToken when user changes email address. + * - Email a link to users when they change their email address. * * - What effect should there be of not having a confirmed email? No emails are * sent? User can't create stuff? @@ -31,11 +30,6 @@ public class ConfirmEmailIT { private static final Logger logger = Logger.getLogger(ConfirmEmailIT.class.getCanonicalName()); - private static final String builtinUserKey = "burrito"; - private static final String idKey = "id"; - private static final String usernameKey = "userName"; - private static final String emailKey = "email"; - @BeforeClass public static void setUp() { RestAssured.baseURI = UtilIT.getRestAssuredBaseUri(); @@ -78,21 +72,16 @@ public void testConfirm() { String confirmEmailToken = JsonPath.from(getToken.body().asString()).getString("data.token"); String junkToken = "noSuchToken"; -// Response noSuchToken = given() -// .post("/api/admin/confirmEmail/" + junkToken); -// noSuchToken.prettyPrint(); -// noSuchToken.then().assertThat() -// .statusCode(404) -// .body("message", equalTo("Invalid token: " + junkToken)); Response confirmEmailViaBrowserJunkToken = given() .get("/confirmemail.xhtml?token=" + junkToken); - confirmEmailViaBrowserJunkToken.then().assertThat() - /** - * @todo Make this a 404 rather than a 200. Then remove this - * commented code above (the old POST method). - */ - .statusCode(200); - + boolean pageReturnsProper404Response = false; + if (pageReturnsProper404Response) { + confirmEmailViaBrowserJunkToken.then().assertThat().statusCode(404); + } else { + confirmEmailViaBrowserJunkToken.then().assertThat().statusCode(200); + } + // This is a hack we can remove when the page returns a proper 404 response when no token is found. + assertEquals("404 Not Found", confirmEmailViaBrowserJunkToken.getBody().htmlPath().getString("html.head.title").substring(0, 13)); Response confirmEmailViaBrowser = given() .get("/confirmemail.xhtml?token=" + confirmEmailToken); @@ -105,7 +94,7 @@ public void testConfirm() { .statusCode(200) // Checking that it's 2016 or whatever. Not y3k compliant! .body("data.emailLastConfirmed", startsWith("2")); - + Response getToken2 = given() .get("/api/admin/confirmEmail/" + userIdToConfirm); getToken2.prettyPrint(); From 733b65e0ddf5d90a694921f3b94663b9872c29d5 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Wed, 27 Jul 2016 17:00:14 -0400 Subject: [PATCH 11/44] Confirm Email: update welcome email to include link #2170 --- src/main/java/Bundle.properties | 2 +- .../harvard/iq/dataverse/MailServiceBean.java | 7 ++++++- .../java/edu/harvard/iq/dataverse/Shib.java | 4 ++++ .../iq/dataverse/api/AbstractApiBean.java | 4 ++++ .../iq/dataverse/api/BuiltinUsers.java | 11 +++++++++++ .../confirmemail/ConfirmEmailServiceBean.java | 19 +++++++++++++++++++ .../iq/dataverse/api/ConfirmEmailIT.java | 8 +++++--- 7 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index 4b1740c05ee..1ee98d12f1a 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -246,7 +246,7 @@ 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.greeting=Hello, \n -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.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 . Please confirm your email address by clicking {2} 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}). diff --git a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java index f82b7a3e666..b92fbb4cecd 100644 --- a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java @@ -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; @@ -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()); @@ -424,8 +427,10 @@ private String getMessageTextBasedOnNotification(UserNotification userNotificati case CREATEACC: String accountCreatedMessage = BundleUtil.getStringFromBundle("notification.email.welcome", Arrays.asList( systemConfig.getGuidesBaseUrl(), - systemConfig.getVersion() + systemConfig.getVersion(), + confirmEmailService.getCreateAccountText(userNotification.getUser()) )); + logger.fine("accountCreatedMessage: " + accountCreatedMessage); return messageText += accountCreatedMessage; } diff --git a/src/main/java/edu/harvard/iq/dataverse/Shib.java b/src/main/java/edu/harvard/iq/dataverse/Shib.java index 3046a90fbbf..0fbf2929dfa 100644 --- a/src/main/java/edu/harvard/iq/dataverse/Shib.java +++ b/src/main/java/edu/harvard/iq/dataverse/Shib.java @@ -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); diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index c3b18f3f49c..a796846cb95 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -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; @@ -179,6 +180,9 @@ String getWrappedMessageWhenJson() { @EJB protected ConfirmEmailServiceBean confirmEmailSvc; + @EJB + protected UserNotificationServiceBean userNotificationSvc; + @PersistenceContext(unitName = "VDCNet-ejbPU") protected EntityManager em; diff --git a/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java b/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java index 104fbb09baa..c5a6f3a9406 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/BuiltinUsers.java @@ -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; @@ -25,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. @@ -122,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()); diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index 1eea683f890..46e678d22a4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -3,8 +3,10 @@ import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.MailServiceBean; +import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.SystemConfig; import java.sql.Timestamp; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.logging.Level; @@ -240,4 +242,21 @@ public ConfirmEmailData createToken(AuthenticatedUser au) { return confirmEmailData; } + public String getCreateAccountText(AuthenticatedUser user) { + final String emptyString = ""; + if (user == null) { + logger.info("Can't return confirm email text/link. AuthenticatedUser was null!"); + return emptyString; + } + List datas = findConfirmEmailDataByDataverseUser(user); + int size = datas.size(); + if (size != 1) { + logger.info("Can't return confirm email text/link. ConfirmEmailData rows found for user id " + user.getId() + " was " + size + " rather than 1"); + return emptyString; + } + ConfirmEmailData confirmEmailData = datas.get(0); + String confirmEmailUrl = systemConfig.getDataverseSiteUrl() + "/confirmemail.xhtml?token=" + confirmEmailData.getToken(); + return confirmEmailUrl; + } + } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index e56a3c1bb7d..cb735b09a3a 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -17,9 +17,6 @@ * * Next steps: * - * - Change first email to say, "please click to confirm your email." See - * confirmUrl in ConfirmEmailInitResponse. - * * - Email a link to users when they change their email address. * * - What effect should there be of not having a confirmed email? No emails are @@ -83,6 +80,11 @@ public void testConfirm() { // This is a hack we can remove when the page returns a proper 404 response when no token is found. assertEquals("404 Not Found", confirmEmailViaBrowserJunkToken.getBody().htmlPath().getString("html.head.title").substring(0, 13)); + boolean exitEarlyToTestManuallyInBrowser = false; + if (exitEarlyToTestManuallyInBrowser) { + return; + } + Response confirmEmailViaBrowser = given() .get("/confirmemail.xhtml?token=" + confirmEmailToken); confirmEmailViaBrowser.then().assertThat() From 4710fd79201ff0136d136ad289fc8b2727ef3a4e Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Wed, 27 Jul 2016 17:39:23 -0400 Subject: [PATCH 12/44] Confirm Email: send link on password change #2170 --- .../harvard/iq/dataverse/MailServiceBean.java | 2 +- .../providers/builtin/BuiltinUserPage.java | 14 +++++------ .../ConfirmEmailInitResponse.java | 25 ++----------------- .../confirmemail/ConfirmEmailServiceBean.java | 24 +++++++++++------- .../iq/dataverse/api/ConfirmEmailIT.java | 9 ------- 5 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java index b92fbb4cecd..0d710acf0d4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java @@ -428,7 +428,7 @@ private String getMessageTextBasedOnNotification(UserNotification userNotificati String accountCreatedMessage = BundleUtil.getStringFromBundle("notification.email.welcome", Arrays.asList( systemConfig.getGuidesBaseUrl(), systemConfig.getVersion(), - confirmEmailService.getCreateAccountText(userNotification.getUser()) + confirmEmailService.getConfirmEmailUrl(userNotification.getUser()) )); logger.fine("accountCreatedMessage: " + accountCreatedMessage); return messageText += accountCreatedMessage; diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index 209d542e944..09ff15c3f13 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -29,6 +29,7 @@ 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.confirmemail.ConfirmEmailServiceBean; import edu.harvard.iq.dataverse.mydata.MyDataPage; import edu.harvard.iq.dataverse.passwordreset.PasswordValidator; @@ -495,13 +496,12 @@ public String save() { } if (emailChanged) { msg = msg + " Your email address changed and must be re-confirmed."; - /** - * @todo In addition to creating the token, email the link to - * the user. - */ - ConfirmEmailData confirmEmailData = confirmEmailService.createToken(savedUser); - logger.info("Confirm email token created for user " + savedUser.getId() + ": " + confirmEmailData.getToken()); - savedUser.setEmailConfirmed(null); + boolean sendEmail = true; + try { + ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailService.sendConfirm(savedUser, sendEmail); + } catch (ConfirmEmailException ex) { + logger.info("Unable to send email confirmation link to user id " + savedUser.getId()); + } JsfHelper.addWarningMessage(msg); } else { JsfHelper.addFlashMessage(msg); diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java index c0f856aaef3..d87b428eb19 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java @@ -1,9 +1,5 @@ package edu.harvard.iq.dataverse.confirmemail; -import edu.harvard.iq.dataverse.util.SystemConfig; -import java.net.InetAddress; -import java.net.UnknownHostException; - /** * * @author bsilverstein @@ -18,27 +14,10 @@ public ConfirmEmailInitResponse(boolean emailFound) { this.emailFound = emailFound; } - public ConfirmEmailInitResponse(boolean emailFound, ConfirmEmailData confirmEmailData) { + public ConfirmEmailInitResponse(boolean emailFound, ConfirmEmailData confirmEmailData, String confirmUrl) { this.emailFound = emailFound; this.confirmEmailData = confirmEmailData; - // default to localhost - String finalHostname = "localhost"; - String configuredHostname = System.getProperty(SystemConfig.FQDN); - if (configuredHostname != null) { - if (configuredHostname.equals("localhost")) { - // must be a dev environment - finalHostname = "localhost:8181"; - } else { - finalHostname = configuredHostname; - } - } else { - try { - finalHostname = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException ex) { - // just use the dev address - } - } - this.confirmUrl = "https://" + finalHostname + "/confirmemail.xhtml?token=" + confirmEmailData.getToken(); + this.confirmUrl = confirmUrl; } public boolean isEmailFound() { diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index 46e678d22a4..c563e34776c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -63,13 +63,16 @@ public ConfirmEmailInitResponse sendConfirm(AuthenticatedUser aUser, boolean sen em.remove(oldToken); } + aUser.setEmailConfirmed(null); + aUser = em.merge(aUser); + // create a fresh token for the user ConfirmEmailData confirmEmailData = new ConfirmEmailData(aUser, systemConfig.getMinutesUntilConfirmEmailTokenExpires()); try { em.persist(confirmEmailData); - ConfirmEmailInitResponse confirmEmailInitResponse = new ConfirmEmailInitResponse(true, confirmEmailData); + ConfirmEmailInitResponse confirmEmailInitResponse = new ConfirmEmailInitResponse(true, confirmEmailData, getConfirmEmailUrl(aUser)); if (sendEmail) { - sendConfirmEmail(aUser, confirmEmailInitResponse.getConfirmUrl()); + sendLinkOnEmailChange(aUser, confirmEmailInitResponse.getConfirmUrl()); } return confirmEmailInitResponse; @@ -84,9 +87,12 @@ public ConfirmEmailInitResponse sendConfirm(AuthenticatedUser aUser, boolean sen * @todo: We expect to send two messages. One at signup and another at email * change. */ - private void sendConfirmEmail(AuthenticatedUser aUser, String confirmationUrl) throws ConfirmEmailException { + private void sendLinkOnEmailChange(AuthenticatedUser aUser, String confirmationUrl) throws ConfirmEmailException { + /** + * @todo Move this to Bundle.properties and use + * BundleUtil.getStringFromBundle + */ String messageBody = "Hi " + aUser.getFirstName() + ",\n\n" - + "Hi, " + aUser.getFirstName() + "! Welcome to dataverse.org. To begin publishing data, we require users to confirm their email with us.\n\n" + "Please click the link below to confirm your email address:\n\n" + confirmationUrl + "\n\n" + "The link above will only work for the next " + SystemConfig.getMinutesUntilPasswordResetTokenExpires() + " minutes.\n\n" @@ -94,7 +100,7 @@ private void sendConfirmEmail(AuthenticatedUser aUser, String confirmationUrl) t try { String toAddress = aUser.getEmail(); - String subject = "Dataverse Password Reset Requested"; + String subject = "Dataverse Email Confirmation"; mailService.sendSystemEmail(toAddress, subject, messageBody); } catch (Exception ex) { /** @@ -102,7 +108,7 @@ private void sendConfirmEmail(AuthenticatedUser aUser, String confirmationUrl) t * `asadmin create-javamail-resource` (or equivalent) hasn't been * run. */ - throw new ConfirmEmailException("Problem sending password reset email possibily due to mail server not being configured."); + throw new ConfirmEmailException("Problem sending email confirmation link possibily due to mail server not being configured."); } logger.log(Level.INFO, "attempted to send mail to {0}", aUser.getEmail()); } @@ -242,16 +248,16 @@ public ConfirmEmailData createToken(AuthenticatedUser au) { return confirmEmailData; } - public String getCreateAccountText(AuthenticatedUser user) { + public String getConfirmEmailUrl(AuthenticatedUser user) { final String emptyString = ""; if (user == null) { - logger.info("Can't return confirm email text/link. AuthenticatedUser was null!"); + logger.info("Can't return confirm email URL. AuthenticatedUser was null!"); return emptyString; } List datas = findConfirmEmailDataByDataverseUser(user); int size = datas.size(); if (size != 1) { - logger.info("Can't return confirm email text/link. ConfirmEmailData rows found for user id " + user.getId() + " was " + size + " rather than 1"); + logger.info("Can't return confirm email URL. ConfirmEmailData rows found for user id " + user.getId() + " was " + size + " rather than 1"); return emptyString; } ConfirmEmailData confirmEmailData = datas.get(0); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index cb735b09a3a..fa83e1e7a4f 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -12,16 +12,7 @@ import static org.hamcrest.Matchers.startsWith; /** - * * @author bsilverstein - * - * Next steps: - * - * - Email a link to users when they change their email address. - * - * - What effect should there be of not having a confirmed email? No emails are - * sent? User can't create stuff? - * */ public class ConfirmEmailIT { From 064d0c2fe53d32bb035419e691f893f34792e308 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Thu, 28 Jul 2016 11:28:55 -0400 Subject: [PATCH 13/44] Confirm Email: add test to exercise delete user bug #2170 --- .../edu/harvard/iq/dataverse/api/ConfirmEmailIT.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index fa83e1e7a4f..af5a8982e26 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -35,6 +35,17 @@ public void testConfirm() { String userToConfirmApiToken = JsonPath.from(createUserToConfirm.body().asString()).getString("data.apiToken"); String usernameToConfirm = JsonPath.from(createUserToConfirm.body().asString()).getString("data.user.userName"); + /** + * @todo Fix this and delete this test or move it to the bottom. + */ + boolean exerciseCannotDeleteUserBug = false; + if (exerciseCannotDeleteUserBug) { + Response deleteUser = UtilIT.deleteUser(usernameToConfirm); + deleteUser.prettyPrint(); + deleteUser.then().assertThat() + .statusCode(200); + } + Response createSuperuser = UtilIT.createRandomUser(); createSuperuser.then().assertThat() .statusCode(200); From a05f0ddb597d21436aeb45bb5bd84b493aa3df14 Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Thu, 28 Jul 2016 11:32:53 -0400 Subject: [PATCH 14/44] Refactored sendLinkOnEmailChange method to employ BundleUtil #2170 --- src/main/java/Bundle.properties | 1 + .../confirmemail/ConfirmEmailServiceBean.java | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index 1ee98d12f1a..40542126ef2 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -261,6 +261,7 @@ 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=Hi {0},\n\nPlease click the following URL to confirm your new email address:\n\n{1}\n\nThe link above will only work for the next {2} hours.\n\nPlease contact us if you did not intend this change or if you need assistance. # passwordreset.xhtml diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index c563e34776c..42259251350 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -39,7 +39,7 @@ public class ConfirmEmailServiceBean { @PersistenceContext(unitName = "VDCNet-ejbPU") private EntityManager em; - + /** * Initiate the email confirmation process. * @@ -92,11 +92,12 @@ private void sendLinkOnEmailChange(AuthenticatedUser aUser, String confirmationU * @todo Move this to Bundle.properties and use * BundleUtil.getStringFromBundle */ - String messageBody = "Hi " + aUser.getFirstName() + ",\n\n" - + "Please click the link below to confirm your email address:\n\n" - + confirmationUrl + "\n\n" - + "The link above will only work for the next " + SystemConfig.getMinutesUntilPasswordResetTokenExpires() + " minutes.\n\n" - + "Please contact us if you did not request this password reset or need further help.\n\n"; + String messageBody = BundleUtil.getStringFromBundle("notification.email.changeEmail", Arrays.asList( + aUser.getFirstName(), + confirmationUrl, + "24" + )); + logger.info("messageBody:" + messageBody); try { String toAddress = aUser.getEmail(); From 42c6fa3959c1c721f1fdf161c63fb3d1cb639ddc Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Thu, 28 Jul 2016 13:26:40 -0400 Subject: [PATCH 15/44] Confirm Email: add/remove "Email Not Verfied" immediately #2170 --- .../authorization/providers/builtin/BuiltinUserPage.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index 09ff15c3f13..34dc097e0e5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -499,6 +499,7 @@ public String save() { boolean sendEmail = true; try { ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailService.sendConfirm(savedUser, sendEmail); + currentUser = confirmEmailInitResponse.getConfirmEmailData().getAuthenticatedUser(); } catch (ConfirmEmailException ex) { logger.info("Unable to send email confirmation link to user id " + savedUser.getId()); } From cd0d596d172025f95d6be3038cc4210bea067ec0 Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Fri, 29 Jul 2016 10:44:03 -0400 Subject: [PATCH 16/44] Added testConfirmUserWithTokenCanBeDeleted() #2170 --- .../iq/dataverse/api/ConfirmEmailIT.java | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index af5a8982e26..f154bd514a0 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -35,9 +35,6 @@ public void testConfirm() { String userToConfirmApiToken = JsonPath.from(createUserToConfirm.body().asString()).getString("data.apiToken"); String usernameToConfirm = JsonPath.from(createUserToConfirm.body().asString()).getString("data.user.userName"); - /** - * @todo Fix this and delete this test or move it to the bottom. - */ boolean exerciseCannotDeleteUserBug = false; if (exerciseCannotDeleteUserBug) { Response deleteUser = UtilIT.deleteUser(usernameToConfirm); @@ -105,5 +102,28 @@ public void testConfirm() { getToken2.then().assertThat() .statusCode(400); } + + + @Test + public void testConfirmUserWithTokenCanBeDeleted(){ + + Response createUserToConfirm = UtilIT.createRandomUser(); + createUserToConfirm.prettyPrint(); + createUserToConfirm.then().assertThat() + .statusCode(200); + + long userIdToConfirm = JsonPath.from(createUserToConfirm.body().asString()).getLong("data.authenticatedUser.id"); + String userToConfirmApiToken = JsonPath.from(createUserToConfirm.body().asString()).getString("data.apiToken"); + String usernameToConfirm = JsonPath.from(createUserToConfirm.body().asString()).getString("data.user.userName"); + + boolean deleteUserBugHasBeenFixed = false; + if (deleteUserBugHasBeenFixed) { + Response deleteUser = UtilIT.deleteUser(usernameToConfirm); + deleteUser.prettyPrint(); + deleteUser.then().assertThat() + .statusCode(200); + } + + } } From 60f797b1268204e51cfc85dd20bd07326c20f9cc Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 29 Jul 2016 12:18:09 -0400 Subject: [PATCH 17/44] Confirm Email: error and success handling done? #2170 --- src/main/java/Bundle.properties | 4 +- .../confirmemail/ConfirmEmailPage.java | 39 ++++++++++++++----- src/main/webapp/confirmemail.xhtml | 27 +++---------- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index 40542126ef2..cccb62136ae 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -205,9 +205,9 @@ login.error=Error validating the username, email address, or password. Please tr #confirmemail.xhtml confirmEmail.pageTitle=Email Confirmation confirmEmail.submitRequest=Verify Email -confirmEmail.details.success=Email address confirmed! Redirecting you shortly... +confirmEmail.details.success=Email address confirmed! 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 #shib.xhtml shib.btn.convertAccount=Convert Account diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java index ef082cc1b7b..7f91233667c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java @@ -7,6 +7,8 @@ import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinAuthenticationProvider; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.util.BundleUtil; +import edu.harvard.iq.dataverse.util.JsfHelper; import java.util.logging.Level; import java.util.logging.Logger; import javax.ejb.EJB; @@ -69,22 +71,26 @@ public class ConfirmEmailPage implements java.io.Serializable { public String init() { if (token != null) { - logger.info("Token found"); ConfirmEmailExecResponse confirmEmailExecResponse = confirmEmailService.processToken(token); confirmEmailData = confirmEmailExecResponse.getConfirmEmailData(); if (confirmEmailData != null) { - logger.info("confirmEmailData found"); user = confirmEmailData.getAuthenticatedUser(); - return "/"; - } else { - logger.info("confirmEmailData not found"); - FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Confirm Email Link", "Your email confirmation link is not valid.")); - return "/404.xhtml"; + session.setUser(user); + JsfHelper.addSuccessMessage(BundleUtil.getStringFromBundle("confirmEmail.details.success")); + return "/dataverse.xhtml?faces-redirect=true"; } - } else { - logger.info("Token not found"); - return "/404.xhtml"; } + JsfHelper.addErrorMessage(BundleUtil.getStringFromBundle("confirmEmail.details.failure")); + /** + * @todo It would be nice to send a 404 response but if we enable this + * then the user sees the contents of 404.xhtml rather than the contents + * of JsfHelper.addErrorMessage above! + */ +// try { +// FacesContext.getCurrentInstance().getExternalContext().responseSendError(HttpServletResponse.SC_NOT_FOUND, null); +// } catch (IOException ex) { +// } + return null; } public String sendEmailConfirmLink() { @@ -156,4 +162,17 @@ public ConfirmEmailData getConfirmEmailData() { public void setConfirmEmailData(ConfirmEmailData confirmEmailData) { this.confirmEmailData = confirmEmailData; } + + public boolean isInvalidToken() { + if (confirmEmailData == null) { + return true; + } else { + return false; + } + } + + public String getRedirectToAccountInfoTab() { + return "/dataverseuser.xhtml?selectTab=accountInfo&faces-redirect=true"; + } + } diff --git a/src/main/webapp/confirmemail.xhtml b/src/main/webapp/confirmemail.xhtml index f89f15ba26e..3b7a283382f 100644 --- a/src/main/webapp/confirmemail.xhtml +++ b/src/main/webapp/confirmemail.xhtml @@ -20,27 +20,12 @@ - - -
- - - - - -
-
- - - - - -
-
- - - - + + + + + + From 689f509c458418c88bbdc2f1c8c044ebcb50f412 Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Fri, 29 Jul 2016 12:38:37 -0400 Subject: [PATCH 18/44] Added findSingleConfirmEmailDataByUser() and fixed deletion bug #2170 Added friendlyExpirationTime() and dynamic time measurements for email --- src/main/java/Bundle.properties | 6 ++-- .../AuthenticationServiceBean.java | 10 +++++-- .../confirmemail/ConfirmEmailServiceBean.java | 15 +++++++++- .../confirmemail/ConfirmEmailUtil.java | 30 +++++++++++++++++++ .../confirmemail/ConfirmEmailUtilTest.java | 20 +++++++++++++ 5 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java create mode 100644 src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index cccb62136ae..ba20e0f9976 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -261,8 +261,10 @@ 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=Hi {0},\n\nPlease click the following URL to confirm your new email address:\n\n{1}\n\nThe link above will only work for the next {2} hours.\n\nPlease contact us if you did not intend this change or if you need assistance. - +notification.email.changeEmail=Hi {0},\n\nPlease click the following URL to confirm your new email address:\n\n{1}\n\nThe link above will only work for the next {2}.\n\nPlease contact us if you did not intend this change or if you need assistance. +hours=hours +hour=hour +minutes=minutes # passwordreset.xhtml pageTitle.passwdReset.pre=Account Password Reset diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java index 6c549846210..f6c56c3f7f8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java @@ -18,6 +18,7 @@ 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 java.sql.SQLException; import java.sql.Timestamp; @@ -217,10 +218,13 @@ public void removeApiToken(AuthenticatedUser user){ */ public void deleteAuthenticatedUser(Object pk) { AuthenticatedUser user = em.find(AuthenticatedUser.class, pk); - - - if (user!=null) { + ConfirmEmailData confirmEmailData = confirmEmailService.findSingleConfirmEmailDataByUser(user); + + if (user != null) { ApiToken apiToken = findApiTokenByUser(user); + if (confirmEmailData != null) { + em.remove(confirmEmailData); + } if (apiToken != null) { em.remove(apiToken); } diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index 42259251350..7da5dc9d195 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -92,10 +92,11 @@ private void sendLinkOnEmailChange(AuthenticatedUser aUser, String confirmationU * @todo Move this to Bundle.properties and use * BundleUtil.getStringFromBundle */ + ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); String messageBody = BundleUtil.getStringFromBundle("notification.email.changeEmail", Arrays.asList( aUser.getFirstName(), confirmationUrl, - "24" + confirmEmailUtil.friendlyExpirationTime(systemConfig.getMinutesUntilConfirmEmailTokenExpires()) )); logger.info("messageBody:" + messageBody); @@ -168,6 +169,18 @@ public List findConfirmEmailDataByDataverseUser(AuthenticatedU List confirmEmailDatas = typedQuery.getResultList(); return confirmEmailDatas; } + + public ConfirmEmailData findSingleConfirmEmailDataByUser(AuthenticatedUser user) { + ConfirmEmailData confirmEmailData = null; + TypedQuery typedQuery = em.createNamedQuery("ConfirmEmailData.findByUser", ConfirmEmailData.class); + typedQuery.setParameter("user", user); + try { + confirmEmailData = typedQuery.getSingleResult(); + } catch (NoResultException | NonUniqueResultException ex) { + logger.info("When looking up user " + user + " caught " + ex); + } + return confirmEmailData; + } public List findAllConfirmEmailData() { TypedQuery typedQuery = em.createNamedQuery("ConfirmEmailData.findAll", ConfirmEmailData.class); diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java new file mode 100644 index 00000000000..a28f55d1b88 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java @@ -0,0 +1,30 @@ +package edu.harvard.iq.dataverse.confirmemail; + +import edu.harvard.iq.dataverse.util.BundleUtil; +import edu.harvard.iq.dataverse.util.SystemConfig; +import javax.ejb.EJB; + +public class ConfirmEmailUtil { + + + public static String friendlyExpirationTime(int expirationInt) { + String measurement; + String expirationString; + + if(expirationInt < 60){ + measurement = BundleUtil.getStringFromBundle("minutes"); + } + else if(expirationInt == 60){ + expirationInt = expirationInt/60; + measurement = BundleUtil.getStringFromBundle("hour"); + } else { + expirationInt = expirationInt/60; + measurement = BundleUtil.getStringFromBundle("hours"); + } + expirationString = Integer.toString(expirationInt); + return expirationString + " " + measurement; + } + + + +} diff --git a/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java new file mode 100644 index 00000000000..b7c3eac6780 --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java @@ -0,0 +1,20 @@ +package edu.harvard.iq.dataverse.confirmemail; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class ConfirmEmailUtilTest { + + ConfirmEmailUtil confirmEmailUtil; + + @Test + public void testFriendlyExpirationTime() { + System.out.println("1440 Minutes: " + confirmEmailUtil.friendlyExpirationTime(1440)); + assertEquals("24 hours", ConfirmEmailUtil.friendlyExpirationTime(1440)); + System.out.println("60 Minutes: " + confirmEmailUtil.friendlyExpirationTime(60)); + assertEquals("1 hour", ConfirmEmailUtil.friendlyExpirationTime(60)); + System.out.println("30 Minutes: " + confirmEmailUtil.friendlyExpirationTime(30)); + assertEquals("30 minutes", ConfirmEmailUtil.friendlyExpirationTime(30)); + } + +} From 05a43dd112313089227391c84da253c5dd066c79 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 29 Jul 2016 12:48:15 -0400 Subject: [PATCH 19/44] increase code coverage for ConfirmEmailUtil from 90% to 100% #2170 --- .../dataverse/confirmemail/ConfirmEmailUtil.java | 16 +++++----------- .../confirmemail/ConfirmEmailUtilTest.java | 9 ++++----- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java index a28f55d1b88..7106ef6f54f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java @@ -1,30 +1,24 @@ package edu.harvard.iq.dataverse.confirmemail; import edu.harvard.iq.dataverse.util.BundleUtil; -import edu.harvard.iq.dataverse.util.SystemConfig; -import javax.ejb.EJB; public class ConfirmEmailUtil { - public static String friendlyExpirationTime(int expirationInt) { String measurement; String expirationString; - - if(expirationInt < 60){ + + if (expirationInt < 60) { measurement = BundleUtil.getStringFromBundle("minutes"); - } - else if(expirationInt == 60){ - expirationInt = expirationInt/60; + } else if (expirationInt == 60) { + expirationInt = expirationInt / 60; measurement = BundleUtil.getStringFromBundle("hour"); } else { - expirationInt = expirationInt/60; + expirationInt = expirationInt / 60; measurement = BundleUtil.getStringFromBundle("hours"); } expirationString = Integer.toString(expirationInt); return expirationString + " " + measurement; } - - } diff --git a/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java index b7c3eac6780..7425e80c31e 100644 --- a/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java @@ -1,14 +1,13 @@ package edu.harvard.iq.dataverse.confirmemail; +import static org.junit.Assert.assertEquals; import org.junit.Test; -import static org.junit.Assert.*; public class ConfirmEmailUtilTest { - - ConfirmEmailUtil confirmEmailUtil; - + @Test public void testFriendlyExpirationTime() { + ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); System.out.println("1440 Minutes: " + confirmEmailUtil.friendlyExpirationTime(1440)); assertEquals("24 hours", ConfirmEmailUtil.friendlyExpirationTime(1440)); System.out.println("60 Minutes: " + confirmEmailUtil.friendlyExpirationTime(60)); @@ -16,5 +15,5 @@ public void testFriendlyExpirationTime() { System.out.println("30 Minutes: " + confirmEmailUtil.friendlyExpirationTime(30)); assertEquals("30 minutes", ConfirmEmailUtil.friendlyExpirationTime(30)); } - + } From 5c8eb2c5253d7bc8eb4e50545788b95fc2ae4391 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 29 Jul 2016 13:09:32 -0400 Subject: [PATCH 20/44] Confirm Email: add "Verify Email" button #2170 --- .../providers/builtin/BuiltinUserPage.java | 14 +++++++++++++- src/main/webapp/dataverseuser.xhtml | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index 34dc097e0e5..7b3b13641f3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -621,9 +621,21 @@ public void displayNotification() { } } - public void sendConfirmEmail(AuthenticatedUser currentUser) throws ConfirmEmailException{ + /** + * @todo Move ConfirmEmailException to a try/catch + */ + public void sendConfirmEmail() throws ConfirmEmailException { + logger.info("called sendConfirmEmail()"); String userEmail = currentUser.getEmail(); confirmEmailServiceBean.beginConfirm(userEmail); } + public boolean showVerifyEmailButton() { + /** + * @todo Show the button if the user doesn't have a timestamp AND their + * user ID is not already in the confirmemaildata table. + */ + return true; + } + } diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index b32428fd25d..4f4a3069c6a 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -316,6 +316,9 @@

#{DataverseUserPage.currentUser.email}

Not Verified

+
+ +
From 40ea2eef3ceddf8f20bd2560de55357ac97f2f17 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 29 Jul 2016 13:39:10 -0400 Subject: [PATCH 21/44] Confirm Email: clean up tests and delete user #2170 --- .../AuthenticationServiceBean.java | 13 ++++++---- .../iq/dataverse/api/ConfirmEmailIT.java | 24 +++++++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java index f6c56c3f7f8..941c63cfaa5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java @@ -214,20 +214,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); - ConfirmEmailData confirmEmailData = confirmEmailService.findSingleConfirmEmailDataByUser(user); if (user != null) { ApiToken apiToken = findApiTokenByUser(user); - if (confirmEmailData != null) { - em.remove(confirmEmailData); - } 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); } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index f154bd514a0..803ca18841d 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -76,8 +76,6 @@ public void testConfirm() { } else { confirmEmailViaBrowserJunkToken.then().assertThat().statusCode(200); } - // This is a hack we can remove when the page returns a proper 404 response when no token is found. - assertEquals("404 Not Found", confirmEmailViaBrowserJunkToken.getBody().htmlPath().getString("html.head.title").substring(0, 13)); boolean exitEarlyToTestManuallyInBrowser = false; if (exitEarlyToTestManuallyInBrowser) { @@ -102,11 +100,10 @@ public void testConfirm() { getToken2.then().assertThat() .statusCode(400); } - - + @Test - public void testConfirmUserWithTokenCanBeDeleted(){ - + public void testConfirmUserWithTokenCanBeDeleted() { + Response createUserToConfirm = UtilIT.createRandomUser(); createUserToConfirm.prettyPrint(); createUserToConfirm.then().assertThat() @@ -116,13 +113,14 @@ public void testConfirmUserWithTokenCanBeDeleted(){ String userToConfirmApiToken = JsonPath.from(createUserToConfirm.body().asString()).getString("data.apiToken"); String usernameToConfirm = JsonPath.from(createUserToConfirm.body().asString()).getString("data.user.userName"); - boolean deleteUserBugHasBeenFixed = false; - if (deleteUserBugHasBeenFixed) { - Response deleteUser = UtilIT.deleteUser(usernameToConfirm); - deleteUser.prettyPrint(); - deleteUser.then().assertThat() - .statusCode(200); - } + Response attemptToDeleteNonExistentUser = UtilIT.deleteUser("noSuchUser"); + attemptToDeleteNonExistentUser.then().assertThat() + .statusCode(400); + + Response deleteUser = UtilIT.deleteUser(usernameToConfirm); + deleteUser.prettyPrint(); + deleteUser.then().assertThat() + .statusCode(200); } From 03e4b6a11170bb315b3feaade57cc742b1935c03 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 29 Jul 2016 13:57:21 -0400 Subject: [PATCH 22/44] Confirm Email: prevent multiple tokens for a user #2170 --- .../edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java index 8a81493ef99..c751b62a3a0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailData.java @@ -43,7 +43,7 @@ public class ConfirmEmailData implements Serializable { private String token; @OneToOne - @JoinColumn(nullable = false) + @JoinColumn(nullable = false, unique = true) private AuthenticatedUser authenticatedUser; @Column(nullable = false) From 36c765087f2be0da36c722ee8cd9289ac9d9a9a1 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 29 Jul 2016 15:39:27 -0400 Subject: [PATCH 23/44] Confirm Email: refactor, delete dead code, more API tests #2170 --- .../edu/harvard/iq/dataverse/api/Admin.java | 27 ++++++++ .../providers/builtin/BuiltinUserPage.java | 2 +- .../confirmemail/ConfirmEmailPage.java | 67 ------------------- .../confirmemail/ConfirmEmailServiceBean.java | 61 ++--------------- .../iq/dataverse/api/ConfirmEmailIT.java | 13 ++++ 5 files changed, 45 insertions(+), 125 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java index a14e7bc509b..071815bd1cf 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java @@ -19,6 +19,8 @@ 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; @@ -544,4 +546,29 @@ public Response getConfirmEmailToken(@PathParam("userId") long userId) { 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.getEmail()); + 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); + } + } diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index 7b3b13641f3..cc90ece5d69 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -498,7 +498,7 @@ public String save() { msg = msg + " Your email address changed and must be re-confirmed."; boolean sendEmail = true; try { - ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailService.sendConfirm(savedUser, sendEmail); + ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailService.beginConfirm(savedUser.getEmail()); currentUser = confirmEmailInitResponse.getConfirmEmailData().getAuthenticatedUser(); } catch (ConfirmEmailException ex) { logger.info("Unable to send email confirmation link to user id " + savedUser.getId()); diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java index 7f91233667c..823d2c111f2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailPage.java @@ -1,23 +1,15 @@ package edu.harvard.iq.dataverse.confirmemail; -import edu.harvard.iq.dataverse.DataverseServiceBean; import edu.harvard.iq.dataverse.DataverseSession; -import edu.harvard.iq.dataverse.ValidateEmail; import edu.harvard.iq.dataverse.actionlogging.ActionLogServiceBean; -import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; -import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinAuthenticationProvider; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.JsfHelper; -import java.util.logging.Level; import java.util.logging.Logger; import javax.ejb.EJB; -import javax.faces.application.FacesMessage; -import javax.faces.context.FacesContext; import javax.faces.view.ViewScoped; import javax.inject.Inject; import javax.inject.Named; -import org.hibernate.validator.constraints.NotBlank; /** * @@ -31,12 +23,6 @@ public class ConfirmEmailPage implements java.io.Serializable { @EJB ConfirmEmailServiceBean confirmEmailService; - @EJB - AuthenticationServiceBean dataverseUserService; - @EJB - DataverseServiceBean dataverseService; - @EJB - AuthenticationServiceBean authSvc; @Inject DataverseSession session; @@ -54,13 +40,6 @@ public class ConfirmEmailPage implements java.io.Serializable { */ AuthenticatedUser user; - /** - * The email address that is entered to be confirmed. - */ - @NotBlank(message = "Please enter a valid email address.") - @ValidateEmail(message = "Confirm email page default email message.") - String emailAddress; - /** * The link that is emailed to the user to confirm the email that contains a * token. @@ -93,44 +72,6 @@ public String init() { return null; } - public String sendEmailConfirmLink() { - try { - ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailService.beginConfirm(emailAddress); - ConfirmEmailData confirmEmailData = confirmEmailInitResponse.getConfirmEmailData(); - if (confirmEmailData != null) { - AuthenticatedUser foundUser = confirmEmailData.getAuthenticatedUser(); - confirmEmailUrl = confirmEmailInitResponse.getConfirmUrl(); - } else { - logger.log(Level.INFO, "Couldn''t find single account using {0}", emailAddress); - } - FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Email Confirmation Initiated", "")); - } catch (ConfirmEmailException ex) { - logger.log(Level.WARNING, "Error While confirming email: " + ex.getMessage(), ex); - } - return ""; - } -//might work now... - - public String confirmEmail() throws ConfirmEmailException { - ConfirmEmailAttemptResponse response = confirmEmailService.attemptEmailConfirm(user, token, token); - try { - if (response.isConfirmed()) { - FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, response.getMessageSummary(), response.getMessageDetail())); - String builtinAuthProviderId = BuiltinAuthenticationProvider.PROVIDER_ID; - AuthenticatedUser au = authSvc.lookupUser(builtinAuthProviderId, user.getIdentifier()); - session.setUser(au); - return "/dataverse.xhtml?alias=" + dataverseService.findRootDataverse().getAlias() + "faces-redirect=true"; - } else { - FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, response.getMessageSummary(), response.getMessageDetail())); - return null; - } - - } catch (Exception ex) { - String msg = "Unable to save token for " + user.getEmail(); - throw new ConfirmEmailException(msg, ex); - } - } - public String getToken() { return token; } @@ -143,14 +84,6 @@ public AuthenticatedUser getUser() { return user; } - public String getEmailAddress() { - return emailAddress; - } - - public void setEmailAddress(String emailAddress) { - this.emailAddress = emailAddress; - } - public String getConfirmEmailUrl() { return confirmEmailUrl; } diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index 7da5dc9d195..230f6fdff98 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -39,7 +39,7 @@ public class ConfirmEmailServiceBean { @PersistenceContext(unitName = "VDCNet-ejbPU") private EntityManager em; - + /** * Initiate the email confirmation process. * @@ -56,7 +56,7 @@ public ConfirmEmailInitResponse beginConfirm(String emailAddress) throws Confirm } } - public ConfirmEmailInitResponse sendConfirm(AuthenticatedUser aUser, boolean sendEmail) throws ConfirmEmailException { + private ConfirmEmailInitResponse sendConfirm(AuthenticatedUser aUser, boolean sendEmail) throws ConfirmEmailException { // delete old tokens for the user List oldTokens = findConfirmEmailDataByDataverseUser(aUser); for (ConfirmEmailData oldToken : oldTokens) { @@ -98,7 +98,7 @@ private void sendLinkOnEmailChange(AuthenticatedUser aUser, String confirmationU confirmationUrl, confirmEmailUtil.friendlyExpirationTime(systemConfig.getMinutesUntilConfirmEmailTokenExpires()) )); - logger.info("messageBody:" + messageBody); + logger.info("messageBody:" + messageBody); try { String toAddress = aUser.getEmail(); @@ -169,7 +169,7 @@ public List findConfirmEmailDataByDataverseUser(AuthenticatedU List confirmEmailDatas = typedQuery.getResultList(); return confirmEmailDatas; } - + public ConfirmEmailData findSingleConfirmEmailDataByUser(AuthenticatedUser user) { ConfirmEmailData confirmEmailData = null; TypedQuery typedQuery = em.createNamedQuery("ConfirmEmailData.findByUser", ConfirmEmailData.class); @@ -203,59 +203,6 @@ private long deleteAllExpiredTokens() { return numDeleted; } - /** - * @todo Do we need this method? Delete it if unused. - */ - public ConfirmEmailAttemptResponse attemptEmailConfirm(AuthenticatedUser user, String newPassword, String token) { - - final String messageSummarySuccess = "Email Confirmed Successfully"; - final String messageDetailSuccess = ""; - - // optimistic defaults :) - String messageSummary = messageSummarySuccess; - String messageDetail = messageDetailSuccess; - - final String messageSummaryFail = "Email Confirmation Problem"; - if (user == null) { - messageSummary = messageSummaryFail; - messageDetail = "User could not be found."; - return new ConfirmEmailAttemptResponse(false, messageSummary, messageDetail); - } - if (token == null) { - logger.info("No token provided... won't be able to delete it. Email address confirmed though."); - } - - AuthenticatedUser savedUser = dataverseUserService.save(user); - - if (savedUser != null) { - messageSummary = messageSummarySuccess; - messageDetail = messageDetailSuccess; - boolean tokenDeleted = deleteToken(token); - if (!tokenDeleted) { - // suboptimal but when it expires it should be deleted - logger.info("token " + token + " for user id " + user.getId() + " was not deleted"); - } - return new ConfirmEmailAttemptResponse(true, messageSummary, messageDetail); - } else { - messageSummary = messageSummaryFail; - messageDetail = "Your email was not confirmed. Please contact support."; - logger.info("Unable to save user " + user.getId()); - return new ConfirmEmailAttemptResponse(false, messageSummary, messageDetail); - } - - } - - private boolean deleteToken(String token) { - ConfirmEmailData doomed = findSingleConfirmEmailDataByToken(token); - try { - em.remove(doomed); - return true; - } catch (Exception ex) { - logger.info("Caught exception trying to delete token " + token + " - " + ex); - return false; - } - } - public ConfirmEmailData createToken(AuthenticatedUser au) { ConfirmEmailData confirmEmailData = new ConfirmEmailData(au, systemConfig.getMinutesUntilConfirmEmailTokenExpires()); em.persist(confirmEmailData); diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index 803ca18841d..697621d2549 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -99,6 +99,19 @@ public void testConfirm() { getToken2.prettyPrint(); getToken2.then().assertThat() .statusCode(400); + + Response confirmAgain1 = given() + .post("/api/admin/confirmEmail/" + userIdToConfirm); + confirmAgain1.prettyPrint(); + confirmAgain1.then().assertThat() + .statusCode(200); + + Response confirmAgain2 = given() + .post("/api/admin/confirmEmail/" + userIdToConfirm); + confirmAgain2.prettyPrint(); + confirmAgain2.then().assertThat() + .statusCode(400); + } @Test From ff0607f1ba1f842e1cd4346511c78c272ad4f48f Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Mon, 1 Aug 2016 10:20:09 -0400 Subject: [PATCH 24/44] Fixed time syntax for sent email & Verify Email button logic #2170 --- src/main/java/Bundle.properties | 1 + .../providers/builtin/BuiltinUserPage.java | 9 ++++--- .../confirmemail/ConfirmEmailUtil.java | 27 ++++++++++++++----- .../confirmemail/ConfirmEmailUtilTest.java | 2 ++ 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index ba20e0f9976..b0c1dd6df47 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -265,6 +265,7 @@ notification.email.changeEmail=Hi {0},\n\nPlease click the following URL to conf hours=hours hour=hour minutes=minutes +minute=minute # passwordreset.xhtml pageTitle.passwdReset.pre=Account Password Reset diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index 7b3b13641f3..5a01aaf1b4c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -632,10 +632,13 @@ public void sendConfirmEmail() throws ConfirmEmailException { public boolean showVerifyEmailButton() { /** - * @todo Show the button if the user doesn't have a timestamp AND their - * user ID is not already in the confirmemaildata table. + * Determines whether the button to send a verification email appears on user page */ - return true; + if (confirmEmailService.findSingleConfirmEmailDataByUser(currentUser) == null + && currentUser.getEmailConfirmed() == null) { + return true; + } + return false; } } diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java index 7106ef6f54f..b275845ca62 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java @@ -7,18 +7,33 @@ public class ConfirmEmailUtil { public static String friendlyExpirationTime(int expirationInt) { String measurement; String expirationString; + long expirationLong = Long.valueOf(expirationInt); + boolean hasDecimal = false; + double expirationDouble = Double.valueOf(expirationLong); - if (expirationInt < 60) { + if(expirationLong == 1) { + measurement = BundleUtil.getStringFromBundle("minute"); + } else if (expirationLong < 60) { measurement = BundleUtil.getStringFromBundle("minutes"); - } else if (expirationInt == 60) { - expirationInt = expirationInt / 60; + } else if (expirationLong == 60) { + expirationLong = expirationLong / 60; measurement = BundleUtil.getStringFromBundle("hour"); } else { - expirationInt = expirationInt / 60; + if (expirationLong % 60 == 0) { + expirationLong = (long) (expirationLong / 60.0); + } else { + expirationDouble /= 60; + hasDecimal = true; + } measurement = BundleUtil.getStringFromBundle("hours"); } - expirationString = Integer.toString(expirationInt); - return expirationString + " " + measurement; + if (hasDecimal == true) { + expirationString = String.valueOf(expirationDouble); + return expirationString + " " + measurement; + } else { + expirationString = String.valueOf(expirationLong); + return expirationString + " " + measurement; + } } } diff --git a/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java index 7425e80c31e..01a220672f9 100644 --- a/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java @@ -14,6 +14,8 @@ public void testFriendlyExpirationTime() { assertEquals("1 hour", ConfirmEmailUtil.friendlyExpirationTime(60)); System.out.println("30 Minutes: " + confirmEmailUtil.friendlyExpirationTime(30)); assertEquals("30 minutes", ConfirmEmailUtil.friendlyExpirationTime(30)); + System.out.println("90 Minutes: " + confirmEmailUtil.friendlyExpirationTime(90)); + System.out.println("2880 minutes: " + confirmEmailUtil.friendlyExpirationTime(2880)); } } From 4f1f928a1fde8867ae72bb6ab5bb9d933c3f4893 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Mon, 1 Aug 2016 11:26:46 -0400 Subject: [PATCH 25/44] Confirm Email: Shib users don't have to confirm #2170 --- src/main/java/Bundle.properties | 3 ++- .../harvard/iq/dataverse/MailServiceBean.java | 7 ++++--- .../authorization/AuthenticationServiceBean.java | 15 +++++++++++++-- .../confirmemail/ConfirmEmailServiceBean.java | 16 +++++++++++----- .../harvard/iq/dataverse/api/ConfirmEmailIT.java | 8 -------- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index b0c1dd6df47..f242812eef1 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -246,7 +246,8 @@ 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.greeting=Hello, \n -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 . Please confirm your email address by clicking {2} +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 confirm your email address by clicking {0} 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}). diff --git a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java index 0d710acf0d4..0893909a6c0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java @@ -427,10 +427,11 @@ private String getMessageTextBasedOnNotification(UserNotification userNotificati case CREATEACC: String accountCreatedMessage = BundleUtil.getStringFromBundle("notification.email.welcome", Arrays.asList( systemConfig.getGuidesBaseUrl(), - systemConfig.getVersion(), - confirmEmailService.getConfirmEmailUrl(userNotification.getUser()) + systemConfig.getVersion() )); - logger.fine("accountCreatedMessage: " + accountCreatedMessage); + String optionalConfirmEmailAddon = confirmEmailService.optionalConfirmEmailAddonMsg(userNotification.getUser()); + accountCreatedMessage += optionalConfirmEmailAddon; + logger.info("accountCreatedMessage: " + accountCreatedMessage); return messageText += accountCreatedMessage; } diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java index 941c63cfaa5..2a0064373ad 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java @@ -442,8 +442,19 @@ public AuthenticatedUser createAuthenticatedUser(UserRecordIdentifier userRecord em.persist( auusLookup ); authenticatedUser.setAuthenticatedUserLookup(auusLookup); - confirmEmailService.createToken(authenticatedUser); - + 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())); diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index 230f6fdff98..578d2c8f338 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -3,6 +3,7 @@ import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.MailServiceBean; +import edu.harvard.iq.dataverse.authorization.providers.shib.ShibAuthenticationProvider; import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.SystemConfig; import java.sql.Timestamp; @@ -70,7 +71,7 @@ private ConfirmEmailInitResponse sendConfirm(AuthenticatedUser aUser, boolean se ConfirmEmailData confirmEmailData = new ConfirmEmailData(aUser, systemConfig.getMinutesUntilConfirmEmailTokenExpires()); try { em.persist(confirmEmailData); - ConfirmEmailInitResponse confirmEmailInitResponse = new ConfirmEmailInitResponse(true, confirmEmailData, getConfirmEmailUrl(aUser)); + ConfirmEmailInitResponse confirmEmailInitResponse = new ConfirmEmailInitResponse(true, confirmEmailData, optionalConfirmEmailAddonMsg(aUser)); if (sendEmail) { sendLinkOnEmailChange(aUser, confirmEmailInitResponse.getConfirmUrl()); } @@ -158,7 +159,7 @@ private ConfirmEmailData findSingleConfirmEmailDataByToken(String token) { try { confirmEmailData = typedQuery.getSingleResult(); } catch (NoResultException | NonUniqueResultException ex) { - logger.info("When looking up " + token + " caught " + ex); + logger.fine("When looking up " + token + " caught " + ex); } return confirmEmailData; } @@ -177,7 +178,7 @@ public ConfirmEmailData findSingleConfirmEmailDataByUser(AuthenticatedUser user) try { confirmEmailData = typedQuery.getSingleResult(); } catch (NoResultException | NonUniqueResultException ex) { - logger.info("When looking up user " + user + " caught " + ex); + logger.fine("When looking up user " + user + " caught " + ex); } return confirmEmailData; } @@ -209,12 +210,16 @@ public ConfirmEmailData createToken(AuthenticatedUser au) { return confirmEmailData; } - public String getConfirmEmailUrl(AuthenticatedUser user) { + public String optionalConfirmEmailAddonMsg(AuthenticatedUser user) { final String emptyString = ""; if (user == null) { logger.info("Can't return confirm email URL. AuthenticatedUser was null!"); return emptyString; } + if (ShibAuthenticationProvider.PROVIDER_ID.equals(user.getAuthenticatedUserLookup().getAuthenticationProviderId())) { + // Shib users don't have to confirm their email address. + return emptyString; + } List datas = findConfirmEmailDataByDataverseUser(user); int size = datas.size(); if (size != 1) { @@ -223,7 +228,8 @@ public String getConfirmEmailUrl(AuthenticatedUser user) { } ConfirmEmailData confirmEmailData = datas.get(0); String confirmEmailUrl = systemConfig.getDataverseSiteUrl() + "/confirmemail.xhtml?token=" + confirmEmailData.getToken(); - return confirmEmailUrl; + String optionalConfirmEmailMsg = BundleUtil.getStringFromBundle("notification.email.welcomeConfirmEmailAddOn", Arrays.asList(confirmEmailUrl)); + return optionalConfirmEmailMsg; } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java index 697621d2549..f3dc8b16f1d 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ConfirmEmailIT.java @@ -35,14 +35,6 @@ public void testConfirm() { String userToConfirmApiToken = JsonPath.from(createUserToConfirm.body().asString()).getString("data.apiToken"); String usernameToConfirm = JsonPath.from(createUserToConfirm.body().asString()).getString("data.user.userName"); - boolean exerciseCannotDeleteUserBug = false; - if (exerciseCannotDeleteUserBug) { - Response deleteUser = UtilIT.deleteUser(usernameToConfirm); - deleteUser.prettyPrint(); - deleteUser.then().assertThat() - .statusCode(200); - } - Response createSuperuser = UtilIT.createRandomUser(); createSuperuser.then().assertThat() .statusCode(200); From 867ed4856a42287bf32bb304a3da08fe97905e35 Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Mon, 1 Aug 2016 12:21:17 -0400 Subject: [PATCH 26/44] Updated ConfirmEmailUtilTest.java with more assertions #2170 --- .../iq/dataverse/confirmemail/ConfirmEmailUtilTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java index 01a220672f9..1b13a9740bc 100644 --- a/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java @@ -15,7 +15,15 @@ public void testFriendlyExpirationTime() { System.out.println("30 Minutes: " + confirmEmailUtil.friendlyExpirationTime(30)); assertEquals("30 minutes", ConfirmEmailUtil.friendlyExpirationTime(30)); System.out.println("90 Minutes: " + confirmEmailUtil.friendlyExpirationTime(90)); + assertEquals("1.5 hours", confirmEmailUtil.friendlyExpirationTime(90)); System.out.println("2880 minutes: " + confirmEmailUtil.friendlyExpirationTime(2880)); + assertEquals("48 hours", confirmEmailUtil.friendlyExpirationTime(2880)); + System.out.println("150 minutes: " + confirmEmailUtil.friendlyExpirationTime(150)); + assertEquals("2.5 hours", confirmEmailUtil.friendlyExpirationTime(150)); + System.out.println("165 minutes: " + confirmEmailUtil.friendlyExpirationTime(165)); + assertEquals("2.75 hours", confirmEmailUtil.friendlyExpirationTime(165)); + System.out.println("1 Minute: " + confirmEmailUtil.friendlyExpirationTime(1)); + assertEquals("1 minute", confirmEmailUtil.friendlyExpirationTime(1)); } } From 614c15730b76cbec906101251bf9388ea0d47b57 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Mon, 1 Aug 2016 13:23:42 -0400 Subject: [PATCH 27/44] Confirm email: delete method we don't need anymore #2170 --- .../edu/harvard/iq/dataverse/api/Admin.java | 6 ++---- .../confirmemail/ConfirmEmailServiceBean.java | 21 ++++++------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java index 071815bd1cf..a0f29ff8dd1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java @@ -536,10 +536,8 @@ public Response validate() { public Response getConfirmEmailToken(@PathParam("userId") long userId) { AuthenticatedUser user = authSvc.findByID(userId); if (user != null) { - List confirmEmailDatas = confirmEmailSvc.findConfirmEmailDataByDataverseUser(user); - int size = confirmEmailDatas.size(); - if (size == 1) { - ConfirmEmailData confirmEmailData = confirmEmailDatas.get(0); + ConfirmEmailData confirmEmailData = confirmEmailSvc.findSingleConfirmEmailDataByUser(user); + if (confirmEmailData != null) { return okResponse(Json.createObjectBuilder().add("token", confirmEmailData.getToken())); } } diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index 578d2c8f338..481eb9784dc 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -59,8 +59,8 @@ public ConfirmEmailInitResponse beginConfirm(String emailAddress) throws Confirm private ConfirmEmailInitResponse sendConfirm(AuthenticatedUser aUser, boolean sendEmail) throws ConfirmEmailException { // delete old tokens for the user - List oldTokens = findConfirmEmailDataByDataverseUser(aUser); - for (ConfirmEmailData oldToken : oldTokens) { + ConfirmEmailData oldToken = findSingleConfirmEmailDataByUser(aUser); + if (oldToken != null) { em.remove(oldToken); } @@ -164,13 +164,6 @@ private ConfirmEmailData findSingleConfirmEmailDataByToken(String token) { return confirmEmailData; } - public List findConfirmEmailDataByDataverseUser(AuthenticatedUser user) { - TypedQuery typedQuery = em.createNamedQuery("ConfirmEmailData.findByUser", ConfirmEmailData.class); - typedQuery.setParameter("user", user); - List confirmEmailDatas = typedQuery.getResultList(); - return confirmEmailDatas; - } - public ConfirmEmailData findSingleConfirmEmailDataByUser(AuthenticatedUser user) { ConfirmEmailData confirmEmailData = null; TypedQuery typedQuery = em.createNamedQuery("ConfirmEmailData.findByUser", ConfirmEmailData.class); @@ -213,20 +206,18 @@ public ConfirmEmailData createToken(AuthenticatedUser au) { public String optionalConfirmEmailAddonMsg(AuthenticatedUser user) { final String emptyString = ""; if (user == null) { - logger.info("Can't return confirm email URL. AuthenticatedUser was null!"); + logger.info("Can't return confirm email message. AuthenticatedUser was null!"); return emptyString; } if (ShibAuthenticationProvider.PROVIDER_ID.equals(user.getAuthenticatedUserLookup().getAuthenticationProviderId())) { // Shib users don't have to confirm their email address. return emptyString; } - List datas = findConfirmEmailDataByDataverseUser(user); - int size = datas.size(); - if (size != 1) { - logger.info("Can't return confirm email URL. ConfirmEmailData rows found for user id " + user.getId() + " was " + size + " rather than 1"); + ConfirmEmailData confirmEmailData = findSingleConfirmEmailDataByUser(user); + if (confirmEmailData == null) { + logger.info("Can't return confirm email message. No ConfirmEmailData for user id " + user.getId()); return emptyString; } - ConfirmEmailData confirmEmailData = datas.get(0); String confirmEmailUrl = systemConfig.getDataverseSiteUrl() + "/confirmemail.xhtml?token=" + confirmEmailData.getToken(); String optionalConfirmEmailMsg = BundleUtil.getStringFromBundle("notification.email.welcomeConfirmEmailAddOn", Arrays.asList(confirmEmailUrl)); return optionalConfirmEmailMsg; From 166aaf08872d2c62a65027823c78c0c173438a33 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Mon, 1 Aug 2016 13:30:58 -0400 Subject: [PATCH 28/44] Confirm Email: added todos, some cleanup #2170 --- .../confirmemail/ConfirmEmailServiceBean.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index 481eb9784dc..be4bd845597 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -44,6 +44,10 @@ public class ConfirmEmailServiceBean { /** * Initiate the email confirmation process. * + * @todo Refactor this to not take an email address as a parameter. Probably + * it should take an AuthenticatedUser instead (be sure to check for null). + * Then we could remove the dependency on AuthenticationServiceBean. + * * @param emailAddress * @return {@link ConfirmEmailInitResponse} */ @@ -89,10 +93,6 @@ private ConfirmEmailInitResponse sendConfirm(AuthenticatedUser aUser, boolean se * change. */ private void sendLinkOnEmailChange(AuthenticatedUser aUser, String confirmationUrl) throws ConfirmEmailException { - /** - * @todo Move this to Bundle.properties and use - * BundleUtil.getStringFromBundle - */ ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); String messageBody = BundleUtil.getStringFromBundle("notification.email.changeEmail", Arrays.asList( aUser.getFirstName(), @@ -103,6 +103,9 @@ private void sendLinkOnEmailChange(AuthenticatedUser aUser, String confirmationU try { String toAddress = aUser.getEmail(); + /** + * @todo Move this to Bundle.properties. + */ String subject = "Dataverse Email Confirmation"; mailService.sendSystemEmail(toAddress, subject, messageBody); } catch (Exception ex) { From 43251f67fd93b5af70eeb7f36a5034d285151467 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Mon, 1 Aug 2016 13:46:08 -0400 Subject: [PATCH 29/44] Confirm Email: clean up pull request and add todos #2170 --- .../authorization/providers/builtin/BuiltinUserPage.java | 5 +---- .../iq/dataverse/authorization/users/AuthenticatedUser.java | 3 --- .../iq/dataverse/confirmemail/ConfirmEmailServiceBean.java | 6 ++++++ .../edu/harvard/iq/dataverse/util/json/JsonPrinter.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index b592a31b261..cd9d4ad2e0e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -67,9 +67,6 @@ @Named("DataverseUserPage") public class BuiltinUserPage implements java.io.Serializable { - @EJB - private ConfirmEmailServiceBean confirmEmailServiceBean; - private static final Logger logger = Logger.getLogger(BuiltinUserPage.class.getCanonicalName()); public enum EditMode { @@ -627,7 +624,7 @@ public void displayNotification() { public void sendConfirmEmail() throws ConfirmEmailException { logger.info("called sendConfirmEmail()"); String userEmail = currentUser.getEmail(); - confirmEmailServiceBean.beginConfirm(userEmail); + confirmEmailService.beginConfirm(userEmail); } public boolean showVerifyEmailButton() { diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java index 8b9c8c92d13..15cb36c0a8a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/users/AuthenticatedUser.java @@ -120,7 +120,6 @@ public void applyDisplayInfo( AuthenticatedUserDisplayInfo inf ) { setEmail(inf.getEmailAddress()); setAffiliation( inf.getAffiliation() ); setPosition( inf.getPosition()); - } @Override @@ -252,6 +251,4 @@ public void setShibIdentityProvider(String shibIdentityProvider) { this.shibIdentityProvider = shibIdentityProvider; } - - } diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index be4bd845597..70b1d1352c9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -74,6 +74,12 @@ private ConfirmEmailInitResponse sendConfirm(AuthenticatedUser aUser, boolean se // create a fresh token for the user ConfirmEmailData confirmEmailData = new ConfirmEmailData(aUser, systemConfig.getMinutesUntilConfirmEmailTokenExpires()); try { + /** + * @todo This "persist" is causing lots of noise in Glassfish's + * server.log if a token already exists (i.e. it isn't expired and + * wasn't deleted above). Exercise this bug by running + * ConfirmEmailIT. + */ em.persist(confirmEmailData); ConfirmEmailInitResponse confirmEmailInitResponse = new ConfirmEmailInitResponse(true, confirmEmailData, optionalConfirmEmailAddonMsg(aUser)); if (sendEmail) { diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 57502eb7360..38a98fe30d1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -88,7 +88,7 @@ public static JsonObjectBuilder jsonForAuthUser(AuthenticatedUser authenticatedU .add("superuser", authenticatedUser.isSuperuser()) .add("affiliation", authenticatedUser.getAffiliation()) .add("position", authenticatedUser.getPosition()) - .add("persistentUserId", authenticatedUser.getAuthenticatedUserLookup().getPersistentUserId()) + .add("persistentUserId", authenticatedUser.getAuthenticatedUserLookup().getPersistentUserId()) .add("emailLastConfirmed", authenticatedUser.getEmailConfirmed()) .add("authenticationProviderId", authenticatedUser.getAuthenticatedUserLookup().getAuthenticationProviderId()); } From e97879c9f85206120249a5ed84701d44b10ee75d Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Tue, 2 Aug 2016 12:37:19 -0400 Subject: [PATCH 30/44] Added button success msg & information for user re: expiration #2170 --- src/main/java/Bundle.properties | 7 ++--- .../providers/builtin/BuiltinUserPage.java | 26 ++++++++++++++----- .../confirmemail/ConfirmEmailServiceBean.java | 5 +++- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index f242812eef1..02ae3f496d9 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -136,7 +136,7 @@ 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}. +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, please be sure to check your email inbox to verify your email address! notification.demoSite=Demo Site notification.requestFileAccess=File access requested for dataset: {0}. notification.grantFileAccess=Access granted for files in dataset: {0}. @@ -205,6 +205,7 @@ login.error=Error validating the username, email address, or password. Please tr #confirmemail.xhtml confirmEmail.pageTitle=Email Confirmation confirmEmail.submitRequest=Verify Email +confirmEmail.submitRequest.success=A verification email has been sent to {0}. Please note that the link will only work for the next {1}. confirmEmail.details.success=Email address confirmed! 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 @@ -247,7 +248,7 @@ notification.email.revoke.role.subject=Dataverse: Your role has been revoked notification.email.greeting=Hello, \n 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 confirm your email address by clicking {0} +notification.email.welcomeConfirmEmailAddOn=\n\nPlease confirm your email address by clicking {0}. Please note that this link will only work for {1} before expiring. 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}). @@ -262,7 +263,7 @@ 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=Hi {0},\n\nPlease click the following URL to confirm your new email address:\n\n{1}\n\nThe link above will only work for the next {2}.\n\nPlease contact us if you did not intend this change or if you need assistance. +notification.email.changeEmail=Hi {0},\n\n{1}\n\nThe link above will only work for the next {2}.\n\nPlease contact us if you did not intend this change or if you need assistance. hours=hours hour=hour minutes=minutes diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index cd9d4ad2e0e..de29ee24659 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -31,16 +31,19 @@ import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailException; import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailInitResponse; import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailServiceBean; +import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailUtil; import edu.harvard.iq.dataverse.mydata.MyDataPage; import edu.harvard.iq.dataverse.passwordreset.PasswordValidator; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.JsfHelper; import static edu.harvard.iq.dataverse.util.JsfHelper.JH; +import edu.harvard.iq.dataverse.util.SystemConfig; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Set; @@ -95,6 +98,8 @@ public enum EditMode { @EJB ConfirmEmailServiceBean confirmEmailService; @EJB + SystemConfig systemConfig; + @EJB GroupServiceBean groupService; @Inject SettingsWrapper settingsWrapper; @@ -492,7 +497,9 @@ public String save() { msg = "Your account password has been successfully changed."; } if (emailChanged) { - msg = msg + " Your email address changed and must be re-confirmed."; + ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); + String expTime = confirmEmailUtil.friendlyExpirationTime(systemConfig.getMinutesUntilConfirmEmailTokenExpires()); + msg = msg + " Your email address changed and must be re-confirmed. \n\nAlso, please note that the link will only work for the next " + expTime + " before it has expired."; boolean sendEmail = true; try { ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailService.beginConfirm(savedUser.getEmail()); @@ -618,13 +625,20 @@ public void displayNotification() { } } - /** - * @todo Move ConfirmEmailException to a try/catch - */ - public void sendConfirmEmail() throws ConfirmEmailException { + public void sendConfirmEmail() { logger.info("called sendConfirmEmail()"); String userEmail = currentUser.getEmail(); - confirmEmailService.beginConfirm(userEmail); + ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); + + try { + confirmEmailService.beginConfirm(userEmail); + List args = Arrays.asList( + userEmail, + confirmEmailUtil.friendlyExpirationTime(systemConfig.getMinutesUntilConfirmEmailTokenExpires())); + JsfHelper.addSuccessMessage(BundleUtil.getStringFromBundle("confirmEmail.submitRequest.success", args)); + } catch (ConfirmEmailException ex) { + Logger.getLogger(BuiltinUserPage.class.getName()).log(Level.SEVERE, null, ex); + } } public boolean showVerifyEmailButton() { diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index 70b1d1352c9..51135e6223d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -213,6 +213,7 @@ public ConfirmEmailData createToken(AuthenticatedUser au) { } public String optionalConfirmEmailAddonMsg(AuthenticatedUser user) { + ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); final String emptyString = ""; if (user == null) { logger.info("Can't return confirm email message. AuthenticatedUser was null!"); @@ -227,8 +228,10 @@ public String optionalConfirmEmailAddonMsg(AuthenticatedUser user) { logger.info("Can't return confirm email message. No ConfirmEmailData for user id " + user.getId()); return emptyString; } + String expTime = confirmEmailUtil.friendlyExpirationTime(systemConfig.getMinutesUntilConfirmEmailTokenExpires()); String confirmEmailUrl = systemConfig.getDataverseSiteUrl() + "/confirmemail.xhtml?token=" + confirmEmailData.getToken(); - String optionalConfirmEmailMsg = BundleUtil.getStringFromBundle("notification.email.welcomeConfirmEmailAddOn", Arrays.asList(confirmEmailUrl)); + List args = Arrays.asList(confirmEmailUrl, expTime); + String optionalConfirmEmailMsg = BundleUtil.getStringFromBundle("notification.email.welcomeConfirmEmailAddOn", args); return optionalConfirmEmailMsg; } From 872d267d31acaa3c2bb6c8182e5cc72052f488f0 Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Tue, 2 Aug 2016 13:06:47 -0400 Subject: [PATCH 31/44] Added button success msg & information for user re: expiration #2170 --- src/main/java/Bundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index 02ae3f496d9..47e732d3501 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -136,7 +136,7 @@ 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}. Also, please be sure to check your email inbox to verify your email address! +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}. notification.demoSite=Demo Site notification.requestFileAccess=File access requested for dataset: {0}. notification.grantFileAccess=Access granted for files in dataset: {0}. From bfa81626eb058b9203f29b9c67a6861adec78a40 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Tue, 2 Aug 2016 14:06:39 -0400 Subject: [PATCH 32/44] Confirm Email: add renders for 3 states #2170 --- .../providers/builtin/BuiltinUserPage.java | 15 +++++++++++++++ .../dataverse/confirmemail/ConfirmEmailUtil.java | 9 ++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index de29ee24659..01f71acc143 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -652,4 +652,19 @@ public boolean showVerifyEmailButton() { return false; } + public boolean isEmailIsValid() { +// currentUser.getEmailConfirmed() non null and not ConfirmEmailUtil.getGrandfatheredTime + return true; + } + + public boolean isEmailNotValid() { +// currentUser.getEmailConfirmed() is null + return true; + } + + public boolean isEmailGrandfathered() { + // if emailConfirmed is ConfirmEmailUtil.getGrandfatheredTime + return false; + } + } diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java index b275845ca62..1c490e79ac6 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java @@ -1,9 +1,16 @@ package edu.harvard.iq.dataverse.confirmemail; import edu.harvard.iq.dataverse.util.BundleUtil; +import java.sql.Timestamp; +import java.util.Date; public class ConfirmEmailUtil { + public static Timestamp getGrandfatheredTime() { + // change this to 2000-01-01, add test for code coverage + return new Timestamp(new Date().getTime()); + } + public static String friendlyExpirationTime(int expirationInt) { String measurement; String expirationString; @@ -11,7 +18,7 @@ public static String friendlyExpirationTime(int expirationInt) { boolean hasDecimal = false; double expirationDouble = Double.valueOf(expirationLong); - if(expirationLong == 1) { + if (expirationLong == 1) { measurement = BundleUtil.getStringFromBundle("minute"); } else if (expirationLong < 60) { measurement = BundleUtil.getStringFromBundle("minutes"); From 55657a8e46cde08aa9a2cc8abc0ecd05355b551b Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Thu, 4 Aug 2016 10:39:13 -0400 Subject: [PATCH 33/44] Added new render logic for BuiltinUserPage #2170 --- src/main/java/Bundle.properties | 3 ++- .../providers/builtin/BuiltinUserPage.java | 21 ++++++++++++++----- src/main/webapp/dataverseuser.xhtml | 11 +++++++++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index 47e732d3501..f4bb2b3dc85 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -209,6 +209,7 @@ confirmEmail.submitRequest.success=A verification email has been sent to {0}. Pl confirmEmail.details.success=Email address confirmed! 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! #shib.xhtml shib.btn.convertAccount=Convert Account @@ -263,7 +264,7 @@ 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=Hi {0},\n\n{1}\n\nThe link above will only work for the next {2}.\n\nPlease contact us if you did not intend this change or if you need assistance. +notification.email.changeEmail=Hi {0},\n{1}\n\nPlease contact us if you did not intend this change or if you need assistance. hours=hours hour=hour minutes=minutes diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index 01f71acc143..e0dbd3c003e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -653,18 +653,29 @@ public boolean showVerifyEmailButton() { } public boolean isEmailIsValid() { -// currentUser.getEmailConfirmed() non null and not ConfirmEmailUtil.getGrandfatheredTime - return true; + ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); + if (currentUser.getEmailConfirmed() != null && currentUser.getEmailConfirmed() != confirmEmailUtil.getGrandfatheredTime()) { + return true; + } else { + return false; + } } public boolean isEmailNotValid() { -// currentUser.getEmailConfirmed() is null - return true; + if (currentUser.getEmailConfirmed() == null || isEmailIsValid() == false) { + return true; + } else { + return false; + } } public boolean isEmailGrandfathered() { - // if emailConfirmed is ConfirmEmailUtil.getGrandfatheredTime + ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); + if (currentUser.getEmailConfirmed() == confirmEmailUtil.getGrandfatheredTime()) { + return true; + } else { return false; + } } } diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index 4f4a3069c6a..0f9e912a93d 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -315,7 +315,16 @@ #{bundle.email}
-

#{DataverseUserPage.currentUser.email}

Not Verified

+

#{DataverseUserPage.currentUser.email} +

+ Not Verified +
+
+ Verified +
+
+
+

From 8799e9fb588fc472f1ef5ba39432e412a092ccb1 Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Fri, 5 Aug 2016 15:34:19 -0400 Subject: [PATCH 34/44] Minor UI components added to Verification status #2170 --- src/main/java/Bundle.properties | 9 +++++---- .../providers/builtin/BuiltinUserPage.java | 10 +--------- src/main/webapp/dataverseuser.xhtml | 14 ++++++-------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index f4bb2b3dc85..4181e583621 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -203,13 +203,14 @@ login.builtin.invalidUsernameEmailOrPassword=The username, email address, or pas 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 Confirmation +confirmEmail.pageTitle=Email Verification confirmEmail.submitRequest=Verify Email confirmEmail.submitRequest.success=A verification email has been sent to {0}. Please note that the link will only work for the next {1}. -confirmEmail.details.success=Email address confirmed! +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.notVerified=Not Verified +confirmEmail.verified=Verified #shib.xhtml shib.btn.convertAccount=Convert Account @@ -249,7 +250,7 @@ notification.email.revoke.role.subject=Dataverse: Your role has been revoked notification.email.greeting=Hello, \n 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 confirm your email address by clicking {0}. Please note that this link will only work for {1} before expiring. +notification.email.welcomeConfirmEmailAddOn=\n\nPlease verify your email address by clicking {0}. Please note that this link will only work for {1} before expiring. 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}). diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index e0dbd3c003e..bdd82a894d0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -652,7 +652,7 @@ public boolean showVerifyEmailButton() { return false; } - public boolean isEmailIsValid() { + public boolean isEmailIsVerified() { ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); if (currentUser.getEmailConfirmed() != null && currentUser.getEmailConfirmed() != confirmEmailUtil.getGrandfatheredTime()) { return true; @@ -661,14 +661,6 @@ public boolean isEmailIsValid() { } } - public boolean isEmailNotValid() { - if (currentUser.getEmailConfirmed() == null || isEmailIsValid() == false) { - return true; - } else { - return false; - } - } - public boolean isEmailGrandfathered() { ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); if (currentUser.getEmailConfirmed() == confirmEmailUtil.getGrandfatheredTime()) { diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index 0f9e912a93d..50b6501aa3f 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -316,14 +316,12 @@

#{DataverseUserPage.currentUser.email} -

- Not Verified -
-
- Verified -
-
-
+ + Not Verified + + + Verified +

From 73a290e04c663435ead3baa118e156c0c1ad30ea Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Mon, 8 Aug 2016 14:05:08 -0400 Subject: [PATCH 35/44] Fixed verified status bug/defined grandfathered timestamp as Y2K #2170 --- .../providers/builtin/BuiltinUserPage.java | 2 ++ .../iq/dataverse/confirmemail/ConfirmEmailUtil.java | 8 ++++++-- .../dataverse/confirmemail/ConfirmEmailUtilTest.java | 10 ++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index bdd82a894d0..644b54e31ab 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -655,7 +655,9 @@ public boolean showVerifyEmailButton() { public boolean isEmailIsVerified() { ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); if (currentUser.getEmailConfirmed() != null && currentUser.getEmailConfirmed() != confirmEmailUtil.getGrandfatheredTime()) { + if (confirmEmailService.findSingleConfirmEmailDataByUser(currentUser) == null) { return true; + } else return false; } else { return false; } diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java index 1c490e79ac6..d2f84b956df 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtil.java @@ -7,8 +7,12 @@ public class ConfirmEmailUtil { public static Timestamp getGrandfatheredTime() { - // change this to 2000-01-01, add test for code coverage - return new Timestamp(new Date().getTime()); + /** + * Currently set to Y2K as an easter egg to easily set apart + * grandfathered accounts from post-launch accounts. + */ + Timestamp grandfatheredTime = Timestamp.valueOf("2000-01-01 00:00:00.0"); + return grandfatheredTime; } public static String friendlyExpirationTime(int expirationInt) { diff --git a/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java index 1b13a9740bc..81ec16a07d4 100644 --- a/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java @@ -1,5 +1,6 @@ package edu.harvard.iq.dataverse.confirmemail; +import java.sql.Timestamp; import static org.junit.Assert.assertEquals; import org.junit.Test; @@ -25,5 +26,14 @@ public void testFriendlyExpirationTime() { System.out.println("1 Minute: " + confirmEmailUtil.friendlyExpirationTime(1)); assertEquals("1 minute", confirmEmailUtil.friendlyExpirationTime(1)); } + + @Test + public void testGrandfatheredTime() { + ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); + System.out.println("Grandfathered Time (y2k): " + confirmEmailUtil.getGrandfatheredTime()); + assertEquals(Timestamp.valueOf("2000-01-01 00:00:00.0"), confirmEmailUtil.getGrandfatheredTime()); + } + + } From 9fa1b19e908dff37bbf01764c6618590e2d35c50 Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Tue, 16 Aug 2016 10:09:00 -0400 Subject: [PATCH 36/44] UI tweaks to account info & added line to welcome notification #2170 --- src/main/java/Bundle.properties | 2 +- src/main/webapp/dataverseuser.xhtml | 23 +++++++++++-------- .../confirmemail/ConfirmEmailUtilTest.java | 9 +++++--- .../iq/dataverse/util/BundleUtilTest.java | 3 ++- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index 4181e583621..fa982c91dea 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -136,7 +136,7 @@ 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}. +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, please be sure to check your email to verify your account! notification.demoSite=Demo Site notification.requestFileAccess=File access requested for dataset: {0}. notification.grantFileAccess=Access granted for files in dataset: {0}. diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index 50b6501aa3f..316c2bb96d9 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -315,17 +315,20 @@ #{bundle.email}
-

#{DataverseUserPage.currentUser.email} - - Not Verified - - - Verified - -

-
- +
+

#{DataverseUserPage.currentUser.email}

+
+

+ + Not Verified + + + Verified + + +

+
diff --git a/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java index 81ec16a07d4..5a2a13bbbd9 100644 --- a/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailUtilTest.java @@ -9,6 +9,7 @@ public class ConfirmEmailUtilTest { @Test public void testFriendlyExpirationTime() { ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); + System.out.println("Friendly expiration timestamp / measurement test"); System.out.println("1440 Minutes: " + confirmEmailUtil.friendlyExpirationTime(1440)); assertEquals("24 hours", ConfirmEmailUtil.friendlyExpirationTime(1440)); System.out.println("60 Minutes: " + confirmEmailUtil.friendlyExpirationTime(60)); @@ -25,15 +26,17 @@ public void testFriendlyExpirationTime() { assertEquals("2.75 hours", confirmEmailUtil.friendlyExpirationTime(165)); System.out.println("1 Minute: " + confirmEmailUtil.friendlyExpirationTime(1)); assertEquals("1 minute", confirmEmailUtil.friendlyExpirationTime(1)); + System.out.println(); } - + @Test public void testGrandfatheredTime() { ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); + System.out.println(); + System.out.println("Grandfathered account timestamp test"); System.out.println("Grandfathered Time (y2k): " + confirmEmailUtil.getGrandfatheredTime()); assertEquals(Timestamp.valueOf("2000-01-01 00:00:00.0"), confirmEmailUtil.getGrandfatheredTime()); + System.out.println(); } - - } diff --git a/src/test/java/edu/harvard/iq/dataverse/util/BundleUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/util/BundleUtilTest.java index bb58deb3030..82d3ca131f5 100644 --- a/src/test/java/edu/harvard/iq/dataverse/util/BundleUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/BundleUtilTest.java @@ -65,7 +65,8 @@ public void testGetStringFromBundleWithArguments() { Arrays.asList(BundleUtil.getStringFromBundle("shib.welcomeExistingUserMessageDefaultInstitution")))); assertEquals("Welcome to Root Dataverse! Get started by adding or finding data. " + "Have questions? Check out the User Guide." - + " Want to test out Dataverse features? Use our Demo Site.", + + " Want to test out Dataverse features? Use our Demo Site." + + " Also, please be sure to check your email to verify your account!", BundleUtil.getStringFromBundle("notification.welcome", Arrays.asList("Root", "User Guide", "Demo Site"))); } From 08f20a1c79f71380e5963f2747ff6e1bd00c3ed1 Mon Sep 17 00:00:00 2001 From: Michael Heppler Date: Tue, 16 Aug 2016 11:46:12 -0400 Subject: [PATCH 37/44] Minor layout cleanup of the email verification label and button on the Account pg. [ref #2170] --- src/main/webapp/dataverseuser.xhtml | 88 +++++++++++++++-------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index 316c2bb96d9..7580d9f21dc 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -20,7 +20,7 @@ - + @@ -31,10 +31,10 @@
@@ -58,7 +58,7 @@ - + @@ -74,46 +74,46 @@ - + - + - #{bundle['header.guides.user']} + #{bundle['header.guides.user']} - #{bundle['notification.demoSite']} - + #{bundle['notification.demoSite']} + - #{item.theObject.getDisplayName()} - + #{item.theObject.getDisplayName()} + - #{item.theObject.getOwner().getDisplayName()} + #{item.theObject.getOwner().getDisplayName()} - #{bundle['header.guides.user']} - + #{bundle['header.guides.user']} + - #{item.theObject.getDataset().getDisplayName()} + #{item.theObject.getDataset().getDisplayName()} #{item.theObject.getDataset().getOwner().getDisplayName()} - + #{bundle['header.guides.user']} - + @@ -135,7 +135,7 @@ #{item.theObject.getDataset().getOwner().getDisplayName()} - + @@ -146,7 +146,7 @@ #{item.theObject.getDataset().getOwner().getDisplayName()} - + @@ -171,14 +171,14 @@ #{item.theObject.displayName} - + #{item.theObject.getDataset().getDisplayName()} - + @@ -188,11 +188,11 @@ #{item.theObject.getDisplayName()} - + - + @@ -202,12 +202,12 @@ #{item.theObject.getDisplayName()} - + - + @@ -218,7 +218,7 @@ #{item.theObject.getOwner().getDisplayName()} - + @@ -229,7 +229,7 @@ #{item.theObject.getDisplayName()} - + @@ -238,7 +238,7 @@ #{item.theObject.getDisplayName()} - + @@ -247,7 +247,7 @@ #{item.theObject.getOwner().getDisplayName()} - + @@ -314,21 +314,25 @@ -
-
+
+

#{DataverseUserPage.currentUser.email}

-
-

- - Not Verified - +

+

- Verified + Verified + + + Not Verified -

-
+
+
+ + #{bundle['confirmEmail.submitRequest']} + +
@@ -391,8 +395,8 @@
- @@ -481,7 +485,7 @@
- From e67bc36ef1a6f892a7832778874d13d5e5210f45 Mon Sep 17 00:00:00 2001 From: Michael Heppler Date: Tue, 16 Aug 2016 15:46:07 -0400 Subject: [PATCH 38/44] Minor layout cleanup of the email messages and notifications in the Verify Email workflow on the Account pg. [ref #2170] --- src/main/java/Bundle.properties | 10 ++++++---- .../confirmemail/ConfirmEmailServiceBean.java | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index fa982c91dea..35aa6761bb2 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -136,7 +136,7 @@ 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}. Also, please be sure to check your email to verify your account! +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 and receive notifications. notification.demoSite=Demo Site notification.requestFileAccess=File access requested for dataset: {0}. notification.grantFileAccess=Access granted for files in dataset: {0}. @@ -205,7 +205,7 @@ login.error=Error validating the username, email address, or password. Please tr #confirmemail.xhtml confirmEmail.pageTitle=Email Verification confirmEmail.submitRequest=Verify Email -confirmEmail.submitRequest.success=A verification email has been sent to {0}. Please note that the link will only work for the next {1}. +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 @@ -247,10 +247,11 @@ 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 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 by clicking {0}. Please note that this link will only work for {1} before expiring. +notification.email.welcomeConfirmEmailAddOn=\n\nTo continue to receive account notifications, please 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}). @@ -265,11 +266,12 @@ 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=Hi {0},\n{1}\n\nPlease contact us if you did not intend this change or if you need assistance. +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 pageTitle.passwdReset.pre=Account Password Reset diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index 51135e6223d..c40dcf7ed19 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -112,7 +112,7 @@ private void sendLinkOnEmailChange(AuthenticatedUser aUser, String confirmationU /** * @todo Move this to Bundle.properties. */ - String subject = "Dataverse Email Confirmation"; + String subject = BundleUtil.getStringFromBundle("notification.email.verifyEmail.subject"); mailService.sendSystemEmail(toAddress, subject, messageBody); } catch (Exception ex) { /** From e615dcd97206a92f12e48ddad573c5c92c7e9597 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Thu, 18 Aug 2016 15:43:18 -0400 Subject: [PATCH 39/44] Confirm Email: add docs #2170 Also get app compiling again. Unit test failure. --- .../source/installation/administration.rst | 11 +++++++++++ doc/sphinx-guides/source/installation/config.rst | 5 +++++ .../iq/dataverse/settings/SettingsServiceBean.java | 3 --- .../edu/harvard/iq/dataverse/util/BundleUtilTest.java | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/doc/sphinx-guides/source/installation/administration.rst b/doc/sphinx-guides/source/installation/administration.rst index 6c665cbb650..59cf11652a3 100644 --- a/doc/sphinx-guides/source/installation/administration.rst +++ b/doc/sphinx-guides/source/installation/administration.rst @@ -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 +++++++++++++++++++++ diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 10a9563e66a..db6bc977aae 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -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 ++++++++++++ diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java index fecaea5b08b..2b09b40b773 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java @@ -33,9 +33,6 @@ public class SettingsServiceBean { * So there. */ public enum Key { - /** - * @todo Document this in the Installation Guide. - */ MinutesUntilConfirmEmailTokenExpires, /** * Override Solr highlighting "fragsize" diff --git a/src/test/java/edu/harvard/iq/dataverse/util/BundleUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/util/BundleUtilTest.java index 82d3ca131f5..d45ff2b87cd 100644 --- a/src/test/java/edu/harvard/iq/dataverse/util/BundleUtilTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/BundleUtilTest.java @@ -66,7 +66,7 @@ public void testGetStringFromBundleWithArguments() { assertEquals("Welcome to Root Dataverse! Get started by adding or finding data. " + "Have questions? Check out the User Guide." + " Want to test out Dataverse features? Use our Demo Site." - + " Also, please be sure to check your email to verify your account!", + + " Also, check for your welcome email to verify your address and receive notifications.", BundleUtil.getStringFromBundle("notification.welcome", Arrays.asList("Root", "User Guide", "Demo Site"))); } From 1213fabae3401d2fb98f58e30933332cb236938e Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Fri, 19 Aug 2016 10:15:04 -0400 Subject: [PATCH 40/44] Softened UI messages to inconsequential / dialed back logging #2170 --- src/main/java/Bundle.properties | 5 +++-- .../authorization/providers/builtin/BuiltinUserPage.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index 35aa6761bb2..3c985b522d0 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -136,7 +136,7 @@ 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}. Also, check for your welcome email to verify your address and receive notifications. +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}. @@ -250,8 +250,9 @@ 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\nTo continue to receive account notifications, please 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.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}). diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index 644b54e31ab..8bddccf1168 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -626,7 +626,7 @@ public void displayNotification() { } public void sendConfirmEmail() { - logger.info("called sendConfirmEmail()"); + logger.fine("called sendConfirmEmail()"); String userEmail = currentUser.getEmail(); ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); From b13f9be6e3bf5d654afd611d9978f344f20280c5 Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Thu, 1 Sep 2016 15:51:56 -0400 Subject: [PATCH 41/44] Fixed verified render,Updated info msg on email change,Fixed bundle #2170 --- src/main/java/Bundle.properties | 1 + .../providers/builtin/BuiltinUserPage.java | 26 +++++++++---------- src/main/webapp/dataverseuser.xhtml | 8 +++--- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/main/java/Bundle.properties b/src/main/java/Bundle.properties index 3c985b522d0..0c2fb11090e 100755 --- a/src/main/java/Bundle.properties +++ b/src/main/java/Bundle.properties @@ -136,6 +136,7 @@ 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. +# 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}. diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index 8bddccf1168..c56fd7fa2ae 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -499,7 +499,7 @@ public String save() { if (emailChanged) { ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); String expTime = confirmEmailUtil.friendlyExpirationTime(systemConfig.getMinutesUntilConfirmEmailTokenExpires()); - msg = msg + " Your email address changed and must be re-confirmed. \n\nAlso, please note that the link will only work for the next " + expTime + " before it has expired."; + msg = msg + " Your email address has changed and must be re-verified. Please check your inbox at " + currentUser.getEmail() + " and follow the link we've sent. \n\nAlso, please note that the link will only work for the next " + expTime + " before it has expired."; boolean sendEmail = true; try { ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailService.beginConfirm(savedUser.getEmail()); @@ -507,7 +507,8 @@ public String save() { } catch (ConfirmEmailException ex) { logger.info("Unable to send email confirmation link to user id " + savedUser.getId()); } - JsfHelper.addWarningMessage(msg); + session.setUser(currentUser); + JsfHelper.addSuccessMessage(msg); } else { JsfHelper.addFlashMessage(msg); } @@ -653,23 +654,22 @@ public boolean showVerifyEmailButton() { } public boolean isEmailIsVerified() { - ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); - if (currentUser.getEmailConfirmed() != null && currentUser.getEmailConfirmed() != confirmEmailUtil.getGrandfatheredTime()) { - if (confirmEmailService.findSingleConfirmEmailDataByUser(currentUser) == null) { + if (currentUser.getEmailConfirmed() != null && confirmEmailService.findSingleConfirmEmailDataByUser(currentUser) == null) { return true; - } else return false; - } else { - return false; - } + } else return false; } - + + public boolean isEmailNotVerified() { + if (currentUser.getEmailConfirmed() == null || confirmEmailService.findSingleConfirmEmailDataByUser(currentUser) != null) { + return true; + } else return false; + } + public boolean isEmailGrandfathered() { ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); if (currentUser.getEmailConfirmed() == confirmEmailUtil.getGrandfatheredTime()) { return true; - } else { - return false; - } + } else return false; } } diff --git a/src/main/webapp/dataverseuser.xhtml b/src/main/webapp/dataverseuser.xhtml index 7580d9f21dc..30c726e2e45 100644 --- a/src/main/webapp/dataverseuser.xhtml +++ b/src/main/webapp/dataverseuser.xhtml @@ -320,12 +320,12 @@

- - Verified - - + Not Verified + + Verified +

From 139c48f17cdeb26e3b5389fffdea0e51adb3c36a Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Fri, 2 Sep 2016 11:09:09 -0400 Subject: [PATCH 42/44] Logging level reduced to fine #2170 --- .../iq/dataverse/confirmemail/ConfirmEmailServiceBean.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index c40dcf7ed19..d3d5a3fea89 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -105,7 +105,7 @@ private void sendLinkOnEmailChange(AuthenticatedUser aUser, String confirmationU confirmationUrl, confirmEmailUtil.friendlyExpirationTime(systemConfig.getMinutesUntilConfirmEmailTokenExpires()) )); - logger.info("messageBody:" + messageBody); + logger.log(Level.FINE, "messageBody:" + messageBody); try { String toAddress = aUser.getEmail(); @@ -122,7 +122,7 @@ private void sendLinkOnEmailChange(AuthenticatedUser aUser, String confirmationU */ throw new ConfirmEmailException("Problem sending email confirmation link possibily due to mail server not being configured."); } - logger.log(Level.INFO, "attempted to send mail to {0}", aUser.getEmail()); + logger.log(Level.FINE, "attempted to send mail to {0}", aUser.getEmail()); } /** @@ -142,7 +142,7 @@ public ConfirmEmailExecResponse processToken(String tokenQueried) { } else { ConfirmEmailExecResponse goodTokenCanProceed = new ConfirmEmailExecResponse(tokenQueried, confirmEmailData); if (confirmEmailData == null) { - logger.info("Invalid token."); + logger.fine("Invalid token."); return null; } long nowInMilliseconds = new Date().getTime(); From 3fd92630a4777ca2e56fd064c9231fa5ff21f9b8 Mon Sep 17 00:00:00 2001 From: bsilverstein Date: Fri, 2 Sep 2016 13:28:55 -0400 Subject: [PATCH 43/44] Modified beginConfirm to use AuthenticatedUser instead of email #2170 --- .../java/edu/harvard/iq/dataverse/api/Admin.java | 2 +- .../providers/builtin/BuiltinUserPage.java | 5 ++--- .../confirmemail/ConfirmEmailInitResponse.java | 14 +++++++------- .../confirmemail/ConfirmEmailServiceBean.java | 14 ++++---------- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java index 4807e6b9144..5ef20c36158 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java @@ -562,7 +562,7 @@ public Response startConfirmEmailProcess(@PathParam("userId") long userId) { AuthenticatedUser user = authSvc.findByID(userId); if (user != null) { try { - ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailSvc.beginConfirm(user.getEmail()); + ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailSvc.beginConfirm(user); ConfirmEmailData confirmEmailData = confirmEmailInitResponse.getConfirmEmailData(); return okResponse( Json.createObjectBuilder() diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java index c56fd7fa2ae..6f684d07d83 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/builtin/BuiltinUserPage.java @@ -502,8 +502,7 @@ public String save() { msg = msg + " Your email address has changed and must be re-verified. Please check your inbox at " + currentUser.getEmail() + " and follow the link we've sent. \n\nAlso, please note that the link will only work for the next " + expTime + " before it has expired."; boolean sendEmail = true; try { - ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailService.beginConfirm(savedUser.getEmail()); - currentUser = confirmEmailInitResponse.getConfirmEmailData().getAuthenticatedUser(); + ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailService.beginConfirm(currentUser); } catch (ConfirmEmailException ex) { logger.info("Unable to send email confirmation link to user id " + savedUser.getId()); } @@ -632,7 +631,7 @@ public void sendConfirmEmail() { ConfirmEmailUtil confirmEmailUtil = new ConfirmEmailUtil(); try { - confirmEmailService.beginConfirm(userEmail); + confirmEmailService.beginConfirm(currentUser); List args = Arrays.asList( userEmail, confirmEmailUtil.friendlyExpirationTime(systemConfig.getMinutesUntilConfirmEmailTokenExpires())); diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java index d87b428eb19..87ed1c3a58f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailInitResponse.java @@ -6,22 +6,22 @@ */ public class ConfirmEmailInitResponse { - private boolean emailFound; + private boolean userFound; private String confirmUrl; private ConfirmEmailData confirmEmailData; - public ConfirmEmailInitResponse(boolean emailFound) { - this.emailFound = emailFound; + public ConfirmEmailInitResponse(boolean userFound) { + this.userFound = userFound; } - public ConfirmEmailInitResponse(boolean emailFound, ConfirmEmailData confirmEmailData, String confirmUrl) { - this.emailFound = emailFound; + public ConfirmEmailInitResponse(boolean userFound, ConfirmEmailData confirmEmailData, String confirmUrl) { + this.userFound = userFound; this.confirmEmailData = confirmEmailData; this.confirmUrl = confirmUrl; } - public boolean isEmailFound() { - return emailFound; + public boolean isUserFound() { + return userFound; } public String getConfirmUrl() { diff --git a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java index d3d5a3fea89..fe54dd79529 100644 --- a/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/confirmemail/ConfirmEmailServiceBean.java @@ -44,16 +44,11 @@ public class ConfirmEmailServiceBean { /** * Initiate the email confirmation process. * - * @todo Refactor this to not take an email address as a parameter. Probably - * it should take an AuthenticatedUser instead (be sure to check for null). - * Then we could remove the dependency on AuthenticationServiceBean. - * - * @param emailAddress + * @param user * @return {@link ConfirmEmailInitResponse} */ - public ConfirmEmailInitResponse beginConfirm(String emailAddress) throws ConfirmEmailException { + public ConfirmEmailInitResponse beginConfirm(AuthenticatedUser user) throws ConfirmEmailException { deleteAllExpiredTokens(); - AuthenticatedUser user = dataverseUserService.getAuthenticatedUserByEmail(emailAddress); if (user != null) { return sendConfirm(user, true); } else { @@ -70,8 +65,7 @@ private ConfirmEmailInitResponse sendConfirm(AuthenticatedUser aUser, boolean se aUser.setEmailConfirmed(null); aUser = em.merge(aUser); - - // create a fresh token for the user + // create a fresh token for the user iff they don't have an existing token ConfirmEmailData confirmEmailData = new ConfirmEmailData(aUser, systemConfig.getMinutesUntilConfirmEmailTokenExpires()); try { /** @@ -105,7 +99,7 @@ private void sendLinkOnEmailChange(AuthenticatedUser aUser, String confirmationU confirmationUrl, confirmEmailUtil.friendlyExpirationTime(systemConfig.getMinutesUntilConfirmEmailTokenExpires()) )); - logger.log(Level.FINE, "messageBody:" + messageBody); + logger.fine("messageBody:" + messageBody); try { String toAddress = aUser.getEmail(); From fe675b7ff540b3eec78701cb3dfb0abf0bd39980 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Thu, 22 Sep 2016 13:25:47 -0400 Subject: [PATCH 44/44] confirm email is slated for 4.5.1! renaming sql script #2170 --- .../upgrades/{confirmemail.sql => upgrade_v4.5_to_v4.5.1.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/database/upgrades/{confirmemail.sql => upgrade_v4.5_to_v4.5.1.sql} (100%) diff --git a/scripts/database/upgrades/confirmemail.sql b/scripts/database/upgrades/upgrade_v4.5_to_v4.5.1.sql similarity index 100% rename from scripts/database/upgrades/confirmemail.sql rename to scripts/database/upgrades/upgrade_v4.5_to_v4.5.1.sql