Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/forgot password #421

Merged
merged 69 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
051cb5e
Added forgot password servlet as well as some UI and i18n files changes.
ivanmrsulja Sep 29, 2023
4926978
Moved labels in the correct language file.
ivanmrsulja Sep 29, 2023
65f629c
Implemented localisation and delayed successive requests to prevent S…
ivanmrsulja Oct 2, 2023
5f2bdf3
Refactored logic in order to not force user to change password upon l…
ivanmrsulja Oct 6, 2023
f3e4c90
Added generic app name loading in url.
ivanmrsulja Oct 13, 2023
74528fb
Fixed wait time bug.
ivanmrsulja Oct 13, 2023
fab4304
Link unavaliable after unsuccessfull login bug fix.
ivanmrsulja Oct 24, 2023
62bebcc
Remade message notifications to be inside VIVO context.
ivanmrsulja Oct 27, 2023
8ec6dcb
Moved password change form to separate page. Updated translation file…
ivanmrsulja Oct 30, 2023
d7852d1
Removed contact us section. Cleaned up mitigation code.
ivanmrsulja Oct 31, 2023
618c169
Added proper support page link, fixed information disclosure issue.
ivanmrsulja Oct 31, 2023
81f14d8
Fixed codestyle violations.
ivanmrsulja Nov 1, 2023
054afd5
Added captcha.
ivanmrsulja Nov 1, 2023
767e8fe
Fixed localization bug regarding Serbian labels.
ivanmrsulja Nov 2, 2023
3a96b35
Added feature toggle. Fixed concurrency issues in SPAM mitigation class.
ivanmrsulja Nov 3, 2023
fcbb7bc
Switched to use enabled/disabled instead of boolean value for configu…
ivanmrsulja Nov 7, 2023
459a690
Added example configuration for forgot password functionality feature…
ivanmrsulja Nov 7, 2023
d797bfe
Renamed showFormIfEnabled to showForm. Updated java docs.
ivanmrsulja Nov 7, 2023
6bd8fcb
Fixed bug with property loading. Improved localization.
ivanmrsulja Nov 14, 2023
c16c556
Update home/src/main/resources/rdf/i18n/de_DE/interface-i18n/firsttim…
ivanmrsulja Nov 14, 2023
015d694
Further localization improvement.
ivanmrsulja Nov 14, 2023
84c27f7
Merge conflict resolved.
ivanmrsulja Nov 14, 2023
b14ca0a
Added conditional rendering of contact form link.
ivanmrsulja Nov 15, 2023
4285a15
Update home/src/main/resources/rdf/i18n/es/interface-i18n/firsttime/v…
ivanmrsulja Nov 15, 2023
e10cc2a
Update home/src/main/resources/rdf/i18n/es/interface-i18n/firsttime/v…
ivanmrsulja Nov 15, 2023
490d6ca
Update home/src/main/resources/rdf/i18n/es/interface-i18n/firsttime/v…
ivanmrsulja Nov 15, 2023
5cf2dc9
Update home/src/main/resources/rdf/i18n/es/interface-i18n/firsttime/v…
ivanmrsulja Nov 15, 2023
0d1af5b
Update home/src/main/resources/rdf/i18n/es/interface-i18n/firsttime/v…
ivanmrsulja Nov 15, 2023
2e4ec5e
Update home/src/main/resources/rdf/i18n/es/interface-i18n/firsttime/v…
ivanmrsulja Nov 15, 2023
d153416
Update home/src/main/resources/rdf/i18n/ru_RU/interface-i18n/firsttim…
ivanmrsulja Nov 16, 2023
bba34cf
Improved localization by using arguments. Cleaned up code and added n…
ivanmrsulja Nov 16, 2023
6ef68e3
Merge conflict resolved
ivanmrsulja Nov 16, 2023
df2f2b1
Updated javadocs.
ivanmrsulja Nov 16, 2023
53e24aa
Fixed codestyle violation.
ivanmrsulja Nov 16, 2023
95c7108
Improved feature toggle, as Georgy suggested.
ivanmrsulja Nov 17, 2023
159b6da
Fixed codestyle violation.
ivanmrsulja Nov 17, 2023
8643c5c
Renamed ForgotPassword to ForgotPasswordController.
ivanmrsulja Nov 17, 2023
201245b
Update home/src/main/resources/rdf/i18n/pt_BR/interface-i18n/firsttim…
ivanmrsulja Dec 19, 2023
b1316ea
Improved french localization.
ivanmrsulja Dec 19, 2023
da2f013
Merge branch 'feature/forgot-password' of https://github.com/ivanmrsu…
ivanmrsulja Dec 19, 2023
8c92761
Merge branch 'main' into feature/forgot-password
ivanmrsulja Dec 25, 2023
c2656a4
Added configurable admin notification.
ivanmrsulja Dec 25, 2023
187cb67
Improved pt-BR localization.
ivanmrsulja Jan 9, 2024
ded5461
Merge conflict resolved.
ivanmrsulja Jan 11, 2024
4171adb
Integrated new captcha implementation into forgot password page.
ivanmrsulja Jan 11, 2024
0526d50
Improved localization.
ivanmrsulja Jan 16, 2024
3c3ce68
Addressed renaming and refactoring comments. Improved localization.
ivanmrsulja Jan 26, 2024
6b771c7
Removed spam mitigation, changed default settings to disabled.
ivanmrsulja Feb 9, 2024
19ec481
Aligned example.runtime.properties with java code.
ivanmrsulja Feb 12, 2024
56d54f9
Corrected localization .ttl files indentation.
ivanmrsulja Feb 19, 2024
35ddb2a
Merge conflict resolved.
ivanmrsulja Feb 19, 2024
50200f4
Removed leftover conflict markup. Updated styles.
ivanmrsulja Feb 20, 2024
28bf8c9
Added email length check.
ivanmrsulja Feb 21, 2024
e66f97f
Removed leftover conflict markup in .ftl files.
ivanmrsulja Feb 21, 2024
bbd4ff2
Removed unused labels.
ivanmrsulja Feb 21, 2024
c542224
Refactored localization for contact form link.
ivanmrsulja Feb 21, 2024
4348509
Removed leftover realperson files and theme styles.
ivanmrsulja Feb 22, 2024
55bbe7c
Update home/src/main/resources/rdf/i18n/en_US/interface-i18n/firsttim…
ivanmrsulja Feb 23, 2024
0c2e8d9
Added translations for admin email notification.
ivanmrsulja Mar 1, 2024
1b70450
Moved styles to separate css files, refactored selectors.
ivanmrsulja Mar 6, 2024
2831dfa
Code cleanup and slight email check refactoring.
ivanmrsulja Mar 14, 2024
a4f9209
Fixed potential NPE and improved error message localization.
ivanmrsulja Mar 25, 2024
a5ab108
Fixed minor licencing and tag usage errors.
ivanmrsulja Apr 8, 2024
a7bf2ac
Removed useless blank line in css file.
ivanmrsulja Apr 12, 2024
f7de431
Added newline at the end of file.
ivanmrsulja Apr 12, 2024
d2bc008
Updated example properties.
ivanmrsulja Apr 12, 2024
b7a5339
Updated spanish localization.
ivanmrsulja Apr 15, 2024
4644d24
Update home/src/main/resources/rdf/i18n/de_DE/interface-i18n/firsttim…
ivanmrsulja May 9, 2024
44f9bdd
Updated localization.
ivanmrsulja May 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import javax.servlet.annotation.WebServlet;

import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean;
import edu.cornell.mannlib.vitro.webapp.beans.CaptchaBundle;
import edu.cornell.mannlib.vitro.webapp.beans.CaptchaServiceBean;
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package edu.cornell.mannlib.vitro.webapp.web.widgets;

import java.util.Map;
import java.util.Objects;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
Expand All @@ -25,58 +26,16 @@
import freemarker.template.utility.DeepUnwrap;

public class LoginWidget extends Widget {
private static final Log log = LogFactory.getLog(LoginWidget.class);
private static final Log log = LogFactory.getLog(LoginWidget.class);

/** The page that kicks off the External Authentication process. */
private static final String EXTERNAL_AUTH_SETUP_URL = "/loginExternalAuth";

private static enum Macro {
LOGIN("loginForm"),
FORCE_PASSWORD_CHANGE("forcePasswordChange"),
ALREADY_LOGGED_IN("alreadyLoggedIn"),
SERVER_ERROR("error");

private final String macroName;

Macro(String macroName) {
this.macroName = macroName;
}

@Override
public String toString() {
return macroName;
}

}

private static enum TemplateVariable {
LOGIN_NAME("loginName"),
FORM_ACTION("formAction"),
INFO_MESSAGE("infoMessage"),
ERROR_MESSAGE("errorMessage"),
EXTERNAL_AUTH_NAME("externalAuthName"),
EXTERNAL_AUTH_URL("externalAuthUrl"),
CANCEL_URL("cancelUrl"),
SITE_NAME("siteName"),
MINIMUM_PASSWORD_LENGTH("minimumPasswordLength"),
MAXIMUM_PASSWORD_LENGTH("maximumPasswordLength");

private final String variableName;

TemplateVariable(String variableName) {
this.variableName = variableName;
}

@Override
public String toString() {
return variableName;
}

}
/**
* The page that kicks off the External Authentication process.
*/
private static final String EXTERNAL_AUTH_SETUP_URL = "/loginExternalAuth";

@Override
protected WidgetTemplateValues process(Environment env, Map params,
HttpServletRequest request, ServletContext context) {
HttpServletRequest request, ServletContext context) {

WidgetTemplateValues values = null;

Expand All @@ -88,29 +47,29 @@ protected WidgetTemplateValues process(Environment env, Map params,
TemplateHashModel dataModel = env.getDataModel();

switch (state) {
case LOGGED_IN:
// On the login page itself, show a message that the user is already logged in.
// Otherwise, when redirecting to login page from a page that the logged-in user
// doesn't have access to, we would just show a blank page.
if (request.getServletPath().equals(Route.LOGIN.path())) {
values = showMessageToLoggedInUser(request);
case LOGGED_IN:
// On the login page itself, show a message that the user is already logged in.
// Otherwise, when redirecting to login page from a page that the logged-in user
// doesn't have access to, we would just show a blank page.
if (request.getServletPath().equals(Route.LOGIN.path())) {
values = showMessageToLoggedInUser(request);
break;
} else {
return null;
}
case FORCED_PASSWORD_CHANGE:
values = showPasswordChangeScreen(request);
break;
} else {
return null;
}
case FORCED_PASSWORD_CHANGE:
values = showPasswordChangeScreen(request);
break;
default:
values = showLoginScreen(request, dataModel.get("siteName").toString());
default:
values = showLoginScreen(request, dataModel.get("siteName").toString());
}

values.put("urls", dataModel.get("urls"));
values.put("currentServlet", dataModel.get("currentServlet"));

@SuppressWarnings("unchecked")
Map<String, Object> dm = (Map<String, Object>) DeepUnwrap.permissiveUnwrap(dataModel);
User user = (User) dm.get("user");
User user = (User) dm.get("user");
values.put("user", user);

} catch (Exception e) {
Expand All @@ -131,17 +90,27 @@ private WidgetTemplateValues showLoginScreen(HttpServletRequest request, String
LoginProcessBean bean = LoginProcessBean.getBean(request);
log.trace("Going to login screen: " + bean);

String forgotPasswordEnabled = ConfigurationProperties.getInstance()
.getProperty("authentication.forgotPassword");

if (forgotPasswordEnabled == null) {
forgotPasswordEnabled = "disabled";
}

WidgetTemplateValues values = new WidgetTemplateValues(Macro.LOGIN.toString());
values.put(TemplateVariable.FORM_ACTION.toString(), getAuthenticateUrl(request));
values.put(TemplateVariable.LOGIN_NAME.toString(), bean.getUsername());

boolean showExternalAuth = StringUtils.isNotBlank(
ConfigurationProperties.getBean(request).getProperty(
"externalAuth.netIdHeaderName"));
if (showExternalAuth) {
values.put(TemplateVariable.EXTERNAL_AUTH_URL.toString(),
UrlBuilder.getUrl(EXTERNAL_AUTH_SETUP_URL));
}
values.put(TemplateVariable.FORGOT_PASSWORD.toString(), getForgotPasswordUrl(request));
values.put(TemplateVariable.FORGOT_PASSWORD_ENABLED.toString(),
forgotPasswordEnabled.equalsIgnoreCase("enabled"));

boolean showExternalAuth = StringUtils.isNotBlank(
ConfigurationProperties.getInstance().getProperty(
"externalAuth.netIdHeaderName"));
if (showExternalAuth) {
values.put(TemplateVariable.EXTERNAL_AUTH_URL.toString(),
UrlBuilder.getUrl(EXTERNAL_AUTH_SETUP_URL));
}

String infoMessage = bean.getInfoMessageAndClear();
if (!infoMessage.isEmpty()) {
Expand Down Expand Up @@ -170,7 +139,7 @@ private WidgetTemplateValues showPasswordChangeScreen(HttpServletRequest request
log.trace("Going to password change screen: " + bean);

WidgetTemplateValues values = new WidgetTemplateValues(
Macro.FORCE_PASSWORD_CHANGE.toString());
Macro.FORCE_PASSWORD_CHANGE.toString());
values.put(TemplateVariable.FORM_ACTION.toString(), getAuthenticateUrl(request));
values.put(TemplateVariable.CANCEL_URL.toString(), getCancelUrl(request));
values.put(TemplateVariable.MINIMUM_PASSWORD_LENGTH.toString(), UserAccount.MIN_PASSWORD_LENGTH);
Expand All @@ -185,7 +154,7 @@ private WidgetTemplateValues showPasswordChangeScreen(HttpServletRequest request

private WidgetTemplateValues showError(Exception e) {
WidgetTemplateValues values = new WidgetTemplateValues(
Macro.SERVER_ERROR.toString());
Macro.SERVER_ERROR.toString());
values.put(TemplateVariable.ERROR_MESSAGE.toString(), "Internal server error:<br /> " + e);
return values;
}
Expand All @@ -198,38 +167,95 @@ private State getCurrentLoginState(HttpServletRequest request) {
return State.LOGGED_IN;
}
if (isOutdatedLoginProcessBean(request)) {
LoginProcessBean.removeBean(request);
LoginProcessBean.removeBean(request);
}
return LoginProcessBean.getBean(request).getState();
}

/**
* A LoginProcessBean is outdated unless the the "in-process" flag is set in the
* session.
*
* Each time we hit Authenticate, the flag is set, and each time
* we draw the widget it is reset.
*/
private boolean isOutdatedLoginProcessBean(HttpServletRequest request) {
boolean inProcess = LoginInProcessFlag.checkAndReset(request);
if (!inProcess) {
log.debug("The process bean is outdated. Discard it.");
}

return !inProcess;
}

/** What's the URL for this servlet? */
/**
* A LoginProcessBean is outdated unless the the "in-process" flag is set in the
* session.
* Each time we hit Authenticate, the flag is set, and each time
* we draw the widget it is reset.
*/
private boolean isOutdatedLoginProcessBean(HttpServletRequest request) {
boolean inProcess = LoginInProcessFlag.checkAndReset(request);
if (!inProcess) {
log.debug("The process bean is outdated. Discard it.");
}

return !inProcess;
}

/**
* What's the URL for this servlet?
*/
private String getAuthenticateUrl(HttpServletRequest request) {
String contextPath = request.getContextPath();
return contextPath + "/authenticate";
}

/** What's the URL for this servlet, with the cancel parameter added? */
/**
* What's the URL for this servlet, with the cancel parameter added?
*/
private String getCancelUrl(HttpServletRequest request) {
String contextPath = request.getContextPath();
String urlParams = "?cancel=true";
return contextPath + "/authenticate" + urlParams;
}

/**
* What's the password recovery URL for this servlet?
*/
private String getForgotPasswordUrl(HttpServletRequest request) {
String contextPath = request.getContextPath();
return contextPath + "/forgotPassword";
}

private enum Macro {
LOGIN("loginForm"),
FORCE_PASSWORD_CHANGE("forcePasswordChange"),
ALREADY_LOGGED_IN("alreadyLoggedIn"),
SERVER_ERROR("error");

private final String macroName;

Macro(String macroName) {
this.macroName = macroName;
}

@Override
public String toString() {
return macroName;
}

}

private enum TemplateVariable {
LOGIN_NAME("loginName"),
FORM_ACTION("formAction"),
FORGOT_PASSWORD("forgotPassword"),
FORGOT_PASSWORD_ENABLED("forgotPasswordEnabled"),
INFO_MESSAGE("infoMessage"),
ERROR_MESSAGE("errorMessage"),
EXTERNAL_AUTH_NAME("externalAuthName"),
EXTERNAL_AUTH_URL("externalAuthUrl"),
CANCEL_URL("cancelUrl"),
SITE_NAME("siteName"),
MINIMUM_PASSWORD_LENGTH("minimumPasswordLength"),
MAXIMUM_PASSWORD_LENGTH("maximumPasswordLength");

private final String variableName;

TemplateVariable(String variableName) {
this.variableName = variableName;
}

@Override
public String toString() {
return variableName;
}

}

}
4 changes: 4 additions & 0 deletions home/src/main/resources/config/example.runtime.properties
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ proxy.eligibleTypeList = http://www.w3.org/2002/07/owl#Thing
#comma separated list of mime types allowed for upload
#fileUpload.allowedMIMETypes = image/png, application/pdf

# Feature toggle for forgot password functionality [enabled, disabled]
authentication.forgotPassword = disabled
authentication.forgotPassword.notify-admin = false
litvinovg marked this conversation as resolved.
Show resolved Hide resolved

# Captcha configuration. Available implementations are: nanocaptcha (text-based) and recaptchav2
# nanocaptcha is available in 2 difficulties (easy and hard)
# If captcha.implementation property is not provided, system will fall back to nanocaptcha implementation
Expand Down
Loading
Loading