- Forms for Login, including new password recover form and account activation form


git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3974 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2016-04-06 13:44:27 +00:00
parent f234e7a0e5
commit 4bd63b1b45
24 changed files with 1361 additions and 379 deletions

View File

@ -36,7 +36,6 @@ import com.arsdigita.bebop.event.TableActionEvent;
import com.arsdigita.bebop.event.TableActionListener;
import com.arsdigita.bebop.form.CheckboxGroup;
import com.arsdigita.bebop.form.Option;
import com.arsdigita.bebop.form.OptionGroup;
import com.arsdigita.bebop.form.Password;
import com.arsdigita.bebop.form.Submit;
import com.arsdigita.bebop.form.TextField;
@ -47,12 +46,12 @@ import com.arsdigita.bebop.table.TableColumn;
import com.arsdigita.bebop.table.TableColumnModel;
import com.arsdigita.globalization.GlobalizedMessage;
import com.arsdigita.mail.Mail;
import com.arsdigita.ui.login.UserForm;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.core.EmailAddress;
import org.libreccm.security.ChallengeManager;
import org.libreccm.security.User;
import org.libreccm.security.UserManager;
import org.libreccm.security.UserRepository;
@ -337,19 +336,15 @@ public class UserAdmin extends BoxPanel {
"ui.admin.user_details.generate_password.confirm",
ADMIN_BUNDLE));
generatePasswordLink.addActionListener(e -> {
final UserRepository userRepository = CdiUtil.createCdiUtil()
.findBean(UserRepository.class);
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final UserRepository userRepository = cdiUtil.findBean(
UserRepository.class);
final User user = userRepository.findById(Long.parseLong(
selectedUserId.getSelectedKey(e.getPageState())));
final Mail mail = new Mail(
user.getPrimaryEmailAddress().getAddress(),
"libreccm.example",
"New password has been generated.");
mail.setBody("Das eine Test-Email");
final ChallengeManager challengeManager = cdiUtil.findBean(
ChallengeManager.class);
try {
mail.send();
challengeManager.sendPasswordRecover(user);
} catch (MessagingException ex) {
LOGGER.error("Failed to send email to user.", ex);
}
@ -394,7 +389,7 @@ public class UserAdmin extends BoxPanel {
final Object key,
final int row,
final int column) {
return new ControlLink((Label) value);
return new ControlLink((Component) value);
}
});

View File

@ -94,15 +94,10 @@ public class ChangePasswordForm extends Form
public void register(final Page page) {
super.register(page);
page.addRequestListener(m_listener);
page.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent event) {
page.addActionListener((final ActionEvent event) -> {
PageState state = event.getPageState();
m_oldPasswordLabel.setVisible(state, true);
m_oldPassword.setVisible(state, true);
}
});
}
@ -123,10 +118,7 @@ public class ChangePasswordForm extends Form
add(m_returnURL);
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final Subject subject = cdiUtil.findBean(Subject.class);
final Shiro shiro = cdiUtil.findBean(Shiro.class);
final KernelConfig kernelConfig = KernelConfig.getConfig();
final User user = shiro.getUser();
final Label greeting;

View File

@ -26,83 +26,90 @@ import com.arsdigita.globalization.GlobalizedMessage;
*/
public interface LoginConstants {
public final static GlobalizedMessage SUBMIT = LoginHelper.getMessage(
public static final String LOGIN_BUNDLE
= "com.arsdigita.ui.login.LoginResources";
public static final GlobalizedMessage SUBMIT = LoginHelper.getMessage(
"login.submit");
public final static GlobalizedMessage PRIMARY_EMAIL = LoginHelper
public static final GlobalizedMessage PRIMARY_EMAIL = LoginHelper
.getMessage("login.primaryEmail");
public final static GlobalizedMessage ADDITIONAL_EMAIL = LoginHelper
public static final GlobalizedMessage ADDITIONAL_EMAIL = LoginHelper
.getMessage("login.additionalEmail");
public final static GlobalizedMessage SCREEN_NAME = LoginHelper.getMessage(
public static final GlobalizedMessage SCREEN_NAME = LoginHelper.getMessage(
"login.screenName");
public final static GlobalizedMessage FIRST_NAME = LoginHelper.getMessage(
public static final GlobalizedMessage FIRST_NAME = LoginHelper.getMessage(
"login.firstName");
public final static GlobalizedMessage LAST_NAME = LoginHelper.getMessage(
public static final GlobalizedMessage LAST_NAME = LoginHelper.getMessage(
"login.lastName");
public final static GlobalizedMessage PASSWORD = LoginHelper.getMessage(
public static final GlobalizedMessage PASSWORD = LoginHelper.getMessage(
"login.password", new Object[]{
PasswordValidationListener.MIN_LENGTH});
public final static GlobalizedMessage PASSWORD_CONFIRMATION = LoginHelper
public static final GlobalizedMessage PASSWORD_CONFIRMATION = LoginHelper
.getMessage("login.passwordConfirm");
public final static GlobalizedMessage PASSWORD_QUESTION = LoginHelper
public static final GlobalizedMessage PASSWORD_QUESTION = LoginHelper
.getMessage("login.passwordQuestion");
public final static GlobalizedMessage PASSWORD_ANSWER = LoginHelper
public static final GlobalizedMessage PASSWORD_ANSWER = LoginHelper
.getMessage("login.passwordAnswer");
public final static GlobalizedMessage URL_MSG = LoginHelper.getMessage(
public static final GlobalizedMessage URL_MSG = LoginHelper.getMessage(
"login.url");
public final static GlobalizedMessage BIO = LoginHelper.getMessage(
public static final GlobalizedMessage BIO = LoginHelper.getMessage(
"login.bio");
public final static GlobalizedMessage ERROR_DUPLICATE_SN = LoginHelper
public static final GlobalizedMessage ERROR_DUPLICATE_SN = LoginHelper
.getMessage("login.error.duplicateScreenName");
public final static GlobalizedMessage ERROR_DUPLICATE_EMAIL = LoginHelper
public static final GlobalizedMessage ERROR_DUPLICATE_EMAIL = LoginHelper
.getMessage("login.error.duplicateEmail");
public final static GlobalizedMessage ERROR_MISMATCH_PASSWORD = LoginHelper
public static final GlobalizedMessage ERROR_MISMATCH_PASSWORD = LoginHelper
.getMessage("login.error.mismatchPassword");
public final static GlobalizedMessage ERROR_BAD_PASSWORD = LoginHelper
public static final GlobalizedMessage ERROR_BAD_PASSWORD = LoginHelper
.getMessage("login.error.badPassword");
public final static GlobalizedMessage ERROR_LOGIN_FAIL = LoginHelper
public static final GlobalizedMessage ERROR_LOGIN_FAIL = LoginHelper
.getMessage("login.error.loginFail");
public final static GlobalizedMessage ERROR_BAD_ANSWER = LoginHelper
public static final GlobalizedMessage ERROR_BAD_ANSWER = LoginHelper
.getMessage("login.error.badAnswer");
public final static GlobalizedMessage ERROR_BAD_EMAIL = LoginHelper
public static final GlobalizedMessage ERROR_BAD_EMAIL = LoginHelper
.getMessage("login.error.badEmail");
public final static GlobalizedMessage ERROR_BANNED_EMAIL = LoginHelper
public static final GlobalizedMessage ERROR_BANNED_EMAIL = LoginHelper
.getMessage("login.error.bannedEmail");
public final static String FORM_EMAIL = "emailAddress";
public final static String FORM_SCREEN_NAME = "screenName";
public static final String FORM_EMAIL = "emailAddress";
public static final String FORM_SCREEN_NAME = "screenName";
// Should not really be named email. Kept this way due to external tests
// depending on this value.
public final static String FORM_LOGIN = "email";
public static final String FORM_LOGIN = "email";
public final static String FORM_ADDITIONAL_EMAIL = "additional_email";
public final static String FORM_FIRST_NAME = "firstname";
public final static String FORM_LAST_NAME = "lastname";
public final static String FORM_PASSWORD = "password";
public final static String FORM_PASSWORD_CONFIRMATION
public static final String FORM_ADDITIONAL_EMAIL = "additional_email";
public static final String FORM_FIRST_NAME = "firstname";
public static final String FORM_LAST_NAME = "lastname";
public static final String FORM_GIVEN_NAME = "givenName";
public static final String FORM_FAMILY_NAME = "familyName";
public static final String FORM_USER_NAME = "username";
public static final String FORM_PASSWORD = "password";
public static final String FORM_PASSWORD_CONFIRMATION
= "password_confirmation";
public final static String FORM_PASSWORD_QUESTION = "question";
public final static String FORM_PASSWORD_ANSWER = "answer";
public final static String FORM_URL = "url";
public final static String FORM_URL_DEFAULT = "http://";
public final static String FORM_BIO = "biography";
public final static String FORM_TIMESTAMP = "timestamp";
public final static String FORM_PERSISTENT_LOGIN_P = "persistentCookieP";
public final static String FORM_PERSISTENT_LOGIN_P_DEFAULT = "1";
public static final String FORM_PASSWORD_QUESTION = "question";
public static final String FORM_PASSWORD_ANSWER = "answer";
public static final String FORM_URL = "url";
public static final String FORM_URL_DEFAULT = "http://";
public static final String FORM_BIO = "biography";
public static final String FORM_TIMESTAMP = "timestamp";
public static final String FORM_PERSISTENT_LOGIN_P = "persistentCookieP";
public static final String FORM_PERSISTENT_LOGIN_P_DEFAULT = "1";
public final static int TIMESTAMP_LIFETIME_SECS = 300;
public final static int MAX_NAME_LEN = 60;
public static final int TIMESTAMP_LIFETIME_SECS = 300;
public static final int MAX_NAME_LEN = 60;
/** URL_MSG stub of Login page in ServletPath format (with leading slash and
without trailing slash */
/**
* URL_MSG stub of Login page in ServletPath format (with leading slash and
* without trailing slash
*/
// Don't modify without adapting instantiation in Loader class and
// updating existing databases (table applications)!
public final static String LOGIN_PAGE_URL = "/register/";
public final static String LOGIN_SERVLET_PATH = "/login/*";
public static final String LOGIN_PAGE_URL = "/register/";
public static final String LOGIN_SERVLET_PATH = "/login/*";
}

View File

@ -73,48 +73,69 @@ public class LoginServlet extends BebopApplicationServlet {
// Define various URLs to subpages of Login to manage administrative tasks.
// ////////////////////////////////////////////////////////////////////////
/**
* PathInfo into the Login application to access the (optional) newUser *
* PathInfo into the Login application to access the <em>edit profile</em>
* page. Ends with "/" because it is a servlet/directory
*/
public static final String EDIT_USER_PROFILE_PATH_INFO = "/edit-profile/";
/**
* PathInfo into the Login application to access the (optional) newUser *
* PathInfo into the Login application to access the (optional) <em>new
* user</em>
* page. Ends with "/" because it is a servlet/directory
*/
public static final String NEW_USER_PATH_INFO = "/new-user/";
/**
* PathInfo into the Login application to access the (optional) newUser *
* page. Ends with "/" because it is a servlet/directory
* PathInfo into the Login application to access the activate account page.
* Ends with "/" because it is a servlet/directory
*/
public static final String ACTIVATE_ACCOUNT_PATH_INFO = "/active-account/";
/**
* PathInfo into the Login application to access the <em>change
* password</em> page. Ends with "/" because it is a servlet/directory
*/
public static final String CHANGE_USER_PASSWORD_PATH_INFO
= "/change-password/";
/**
* PathInfo into the Login application to access the (optional) newUser
* page. Ends with "/" because it is a servlet/directory
* PathInfo into the Login application to access the <em>recover
* password</em> page. Ends with "/" because it is a servlet/directory.
*/
public static final String RECOVER_USER_PASSWORD_PATH_INFO
= "/recover-password/";
/**
* PathInfo into the Login application to access the <em>password reset</em>
* page which allows the user to replace a forgotten password with a new one
* (using a previously requested one time auth token). Ends with "/" because
* it is a servlet/directory
*/
public static final String RESET_USER_PASSWORD_PATH_INFO = "/reset-password";
/**
* PathInfo into the Login application to access the <em>verify email</em>
* page. Ends with "/" because it is a servlet/directory
*/
public static final String VERIFY_EMAIL = "/verify-email/";
/**
* PathInfo into the Login application to access the (optional) newUser
* page. Ends with "/" because it is a servlet/directory
* PathInfo into the Login application to access the (optional) <em>explain
* persistent</em> cookies page page. Ends with "/" because it is a
* servlet/directory
*/
public static final String EXPLAIN_PERSISTENT_COOKIES_PATH_INFO
= "/explain-persistent-cookies/";
/**
* PathInfo into the Login application to access the (optional) newUser
* PathInfo into the Login application to access the <em>login
* expired-page</em>
* page. Ends with "/" because it is a servlet/directory
*/
public static final String LOGIN_EXPIRED_PATH_INFO = "/login-expired/";
/**
* PathInfo into the Login application to access the (optional) newUser
* PathInfo into the Login application to access the <em>logout</em>
* page. Ends with "/" because it is a servlet/directory
*/
public static final String LOGOUT_PATH_INFO = "/logout/";
@ -123,7 +144,7 @@ public class LoginServlet extends BebopApplicationServlet {
* Base URL_MSG of the Login application for internal use, fetched from
* Login domain class.
*/
private final static String s_loginURL = LOGIN_PAGE_URL;
private final static String LOGIN_URL = LOGIN_PAGE_URL;
// define namespace URI
final static String SUBSITE_NS_URI = "http://www.arsdigita.com/subsite/1.0";
@ -159,7 +180,8 @@ public class LoginServlet extends BebopApplicationServlet {
/* Create and add login page (index page of Login application) to the
* page map. KernelSecurityConfig determines whether to create a link
* to a NewUserRegistrationForm or to skip.*/
* to a NewUserRegistrationForm or to skip.
*/
put("/",
buildSimplePage(
"login.userRegistrationForm.title",
@ -174,16 +196,24 @@ public class LoginServlet extends BebopApplicationServlet {
disableClientCaching(EDIT_USER_PROFILE_PATH_INFO);
/* Determines if a NewUserRegistrationForm has to be created by quering
* Kernel.getSecurityConfig() and acts appropriately */
* Kernel.getSecurityConfig() and acts appropriately
*/
if (SecurityConfig.getConfig().isAutoRegistrationEnabled()) {
put(NEW_USER_PATH_INFO,
buildSimplePage("login.userNewForm.title",
new UserNewForm(),
"register"));
disableClientCaching(NEW_USER_PATH_INFO);
put(ACTIVATE_ACCOUNT_PATH_INFO,
buildSimplePage("login.userActiveActivateAccount.title",
new UserAccountActivationForm(),
"activate"));
disableClientCaching(ACTIVATE_ACCOUNT_PATH_INFO);
}
/* Create ExplainPersistentCookiesPage and add to the page map */
/* Create ExplainPersistentCookiesPage and add to the page map
*/
put(EXPLAIN_PERSISTENT_COOKIES_PATH_INFO,
buildSimplePage("login.explainCookiesPage.title",
new ElementComponent(
@ -191,25 +221,30 @@ public class LoginServlet extends BebopApplicationServlet {
SUBSITE_NS_URI),
"cookies"));
/* Create ChangeUserPasswordPage and add to the page map */
//Create ChangeUserPasswordPage and add to the page map
put(CHANGE_USER_PASSWORD_PATH_INFO,
buildSimplePage("login.changePasswordPage.title",
new ChangePasswordForm(),
"changepassword"));
disableClientCaching(CHANGE_USER_PASSWORD_PATH_INFO);
//Disabled until we decide what procedure we will use in the future.
//Certainly not the old question/answer approach because it not secure
//and not user friendly.
/* Build the password recover page, retrieve its URL_MSG and store in map */
// put(RECOVER_USER_PASSWORD_PATH_INFO,
// buildSimplePage("login.recoverPasswordPage.title",
// new RecoverPasswordPanel(),
// "recoverpassword"));
//Build the password recover page.
put(RECOVER_USER_PASSWORD_PATH_INFO,
buildSimplePage("login.recoverPasswordPage.title",
new RecoverPasswordForm(),
"recover-password"));
// Build the reset password page.
put(RESET_USER_PASSWORD_PATH_INFO,
buildSimplePage("login.resetPasswordPage.title",
new ResetPasswordForm(),
"reset-password"));
// Build the login expire page, retrieve its URL_MSG and store in map
put(LOGIN_EXPIRED_PATH_INFO, buildExpiredPage());
/* Create Logout Page and add to the page map */
/* Create Logout Page and add to the page map
*/
put(LOGOUT_PATH_INFO, buildLogOutPage());
disableClientCaching(LOGOUT_PATH_INFO);
@ -335,7 +370,7 @@ public class LoginServlet extends BebopApplicationServlet {
}
/**
* Provides an (absolute) URL_MSG to a user profile editig page. It is
* Provides an (absolute) URL_MSG to a user profile edit page. It is
* relative to document root without any constant prefix if there is one
* configured.
*
@ -349,11 +384,11 @@ public class LoginServlet extends BebopApplicationServlet {
* @return url to EditUserProfile page as String
*/
public static String getEditUserProfilePageURL() {
return s_loginURL + EDIT_USER_PROFILE_PATH_INFO;
return LOGIN_URL + EDIT_USER_PROFILE_PATH_INFO;
}
public static String getChangePasswordPageURL() {
return s_loginURL + CHANGE_USER_PASSWORD_PATH_INFO;
return LOGIN_URL + CHANGE_USER_PASSWORD_PATH_INFO;
}
/**
@ -371,7 +406,7 @@ public class LoginServlet extends BebopApplicationServlet {
* @return url to new user registration page as String
*/
public static String getNewUserPageURL() {
return s_loginURL + NEW_USER_PATH_INFO;
return LOGIN_URL + NEW_USER_PATH_INFO;
}
/**
@ -389,7 +424,7 @@ public class LoginServlet extends BebopApplicationServlet {
* @return url String for new user registration page as String
*/
public static String getRecoverPasswordPageURL() {
return s_loginURL + RECOVER_USER_PASSWORD_PATH_INFO;
return LOGIN_URL + RECOVER_USER_PASSWORD_PATH_INFO;
}
/**
@ -407,7 +442,7 @@ public class LoginServlet extends BebopApplicationServlet {
* @return url String for new user registration page as String
*/
public static String getCookiesExplainPageURL() {
return s_loginURL + EXPLAIN_PERSISTENT_COOKIES_PATH_INFO;
return LOGIN_URL + EXPLAIN_PERSISTENT_COOKIES_PATH_INFO;
}
/**
@ -425,7 +460,7 @@ public class LoginServlet extends BebopApplicationServlet {
* @return url String for new user registration page as String
*/
public static String getLoginExpiredPageURL() {
return s_loginURL + LOGIN_EXPIRED_PATH_INFO;
return LOGIN_URL + LOGIN_EXPIRED_PATH_INFO;
}
/**
@ -443,8 +478,8 @@ public class LoginServlet extends BebopApplicationServlet {
* @return URL_MSG for logout page as String
*/
public static String getLogoutPageURL() {
return s_loginURL.substring(0,
s_loginURL.length() - 1) + LOGOUT_PATH_INFO;
return LOGIN_URL.substring(0,
LOGIN_URL.length() - 1) + LOGOUT_PATH_INFO;
}
}

View File

@ -0,0 +1,174 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package com.arsdigita.ui.login;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.FormData;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.Link;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SaveCancelSection;
import com.arsdigita.bebop.form.TextField;
import com.arsdigita.bebop.parameters.NotEmptyValidationListener;
import com.arsdigita.bebop.parameters.StringLengthValidationListener;
import com.arsdigita.globalization.GlobalizedMessage;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.security.ChallengeManager;
import org.libreccm.security.User;
import org.libreccm.security.UserRepository;
import javax.mail.MessagingException;
import static com.arsdigita.ui.login.LoginConstants.*;
import static com.arsdigita.ui.login.LoginServlet.*;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class RecoverPasswordForm extends Form {
private static final String EMAIL = "email";
private BoxPanel formPanel;
private TextField email;
private SaveCancelSection saveCancelSection;
private BoxPanel finishedMessagePanel;
public RecoverPasswordForm() {
super("recover-password");
addWidgets();
addListeners();
}
private void addWidgets() {
formPanel = new BoxPanel(BoxPanel.VERTICAL);
email = new TextField(EMAIL);
email.setLabel(new GlobalizedMessage(
"login.form.recover_password.email.label",
LOGIN_BUNDLE));
email.setHint(new GlobalizedMessage(
"login.form.recover_password.email.hint",
LOGIN_BUNDLE));
email.setMaxLength(256);
email.setSize(48);
email.addValidationListener(new NotEmptyValidationListener());
email.addValidationListener(new StringLengthValidationListener(256));
formPanel.add(email);
saveCancelSection = new SaveCancelSection();
formPanel.add(saveCancelSection);
add(formPanel);
finishedMessagePanel = new BoxPanel(BoxPanel.VERTICAL);
finishedMessagePanel.add(new Label(new GlobalizedMessage(
"login.form.recover_password.finished_message", LOGIN_BUNDLE)));
final Link link = new Link(
new Label(
new GlobalizedMessage(
"login.form.recover_password.finished_message.link",
LOGIN_BUNDLE)),
LOGIN_PAGE_URL + RESET_USER_PASSWORD_PATH_INFO);
finishedMessagePanel.add(link);
}
private void addListeners() {
// addValidationListener(e -> {
// final PageState state = e.getPageState();
//
// if (saveCancelSection.getSaveButton().isSelected(state)) {
// final FormData data = e.getFormData();
//
// final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
// final UserRepository userRepository = cdiUtil.findBean(
// UserRepository.class);
//
// final User user = userRepository.findByEmailAddress(
// (String) data.get(EMAIL));
// if (user == null) {
// data.addError(new GlobalizedMessage(
// "login.form.recover_password.error", LOGIN_BUNDLE));
// }
// }
// });
addProcessListener(e -> {
final PageState state = e.getPageState();
if (saveCancelSection.getSaveButton().isSelected(state)) {
final FormData data = e.getFormData();
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final UserRepository userRepository = cdiUtil.findBean(
UserRepository.class);
final User user = userRepository.findByEmailAddress(
(String) data.get(EMAIL));
// if (user == null) {
// throw new FormProcessException(
// "No user for provided email address found. This should "
// + "not happen because we checked this in the "
// + "validation listener.",
// new GlobalizedMessage(
// "login.form.recover_password.error", LOGIN_BUNDLE));
// }
// We don't show an error message if there is no matching user
// account. This way we don't provide an attacker with
// the valuable information that there is user account for
// a particular email address.
if (user != null) {
final ChallengeManager challengeManager = cdiUtil.findBean(
ChallengeManager.class);
try {
challengeManager.sendPasswordRecover(user);
} catch (MessagingException ex) {
throw new FormProcessException(
"Failed to send password recovery instructions.",
new GlobalizedMessage(
"login.form.recover_password.error.send_challenge_failed",
LOGIN_BUNDLE),
ex);
}
}
formPanel.setVisible(state, false);
finishedMessagePanel.setVisible(state, true);
data.clear();
}
}
);
}
@Override
public void register(final Page page) {
super.register(page);
page.setVisibleDefault(formPanel, true);
page.setVisibleDefault(finishedMessagePanel, false);
}
}

View File

@ -0,0 +1,256 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package com.arsdigita.ui.login;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.FormData;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SaveCancelSection;
import com.arsdigita.bebop.form.Password;
import com.arsdigita.bebop.form.TextField;
import com.arsdigita.bebop.parameters.NotEmptyValidationListener;
import com.arsdigita.bebop.parameters.StringLengthValidationListener;
import com.arsdigita.globalization.GlobalizedMessage;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.configuration.ConfigurationManager;
import org.libreccm.security.ChallengeFailedException;
import org.libreccm.security.ChallengeManager;
import org.libreccm.security.OneTimeAuthConfig;
import org.libreccm.security.OneTimeAuthManager;
import org.libreccm.security.OneTimeAuthToken;
import org.libreccm.security.OneTimeAuthTokenPurpose;
import org.libreccm.security.User;
import org.libreccm.security.UserRepository;
import java.util.List;
import static com.arsdigita.ui.login.LoginConstants.*;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class ResetPasswordForm extends Form {
private static final String EMAIL = "email";
private static final String AUTH_TOKEN = "authToken";
private static final String PASSWORD = "password";
private static final String PASSWORD_CONFIRMATION = "passwordconfirmation";
private BoxPanel formPanel;
private TextField email;
private TextField authToken;
private Password password;
private Password passwordConfirmation;
private SaveCancelSection saveCancelSection;
private BoxPanel successPanel;
public ResetPasswordForm() {
super("reset-password");
addWidgets();
addListeners();
}
private void addWidgets() {
formPanel = new BoxPanel(BoxPanel.VERTICAL);
email = new TextField(EMAIL);
email.setLabel(new GlobalizedMessage(
"login.form.reset_password.email.label",
LOGIN_BUNDLE));
email.setHint(new GlobalizedMessage(
"login.form.reset_password.email.hint",
LOGIN_BUNDLE));
email.setMaxLength(256);
email.setSize(48);
email.addValidationListener(new NotEmptyValidationListener());
email.addValidationListener(new StringLengthValidationListener(256));
formPanel.add(email);
final ConfigurationManager confManager = CdiUtil.createCdiUtil()
.findBean(ConfigurationManager.class);
final OneTimeAuthConfig oneTimeAuthConfig = confManager
.findConfiguration(OneTimeAuthConfig.class);
authToken = new TextField(AUTH_TOKEN);
authToken.setLabel(new GlobalizedMessage(
"login.form.reset_password.auth_token.label", LOGIN_BUNDLE));
authToken.setHint(new GlobalizedMessage(
"login.form.reset_password.auth_token.hint", LOGIN_BUNDLE));
authToken.setMaxLength(oneTimeAuthConfig.getTokenLength());
authToken.setSize(oneTimeAuthConfig.getTokenLength());
formPanel.add(authToken);
password = new Password(PASSWORD);
password.setLabel(new GlobalizedMessage(
"login.form.reset_password.password.label", LOGIN_BUNDLE));
password.setHint(new GlobalizedMessage(
"login.form.reset_password.password.hint", LOGIN_BUNDLE));
password.setMaxLength(256);
password.setSize(32);
password.addValidationListener(new NotEmptyValidationListener());
password.addValidationListener(new StringLengthValidationListener(256));
formPanel.add(password);
passwordConfirmation = new Password(PASSWORD_CONFIRMATION);
passwordConfirmation.setLabel(new GlobalizedMessage(
"login.form.reset_password.password_confirmation.label",
LOGIN_BUNDLE));
passwordConfirmation.setHint(new GlobalizedMessage(
"login.form.reset_password.password_confirmation.hint",
LOGIN_BUNDLE));
passwordConfirmation.setMaxLength(256);
passwordConfirmation.setSize(32);
passwordConfirmation.addValidationListener(
new NotEmptyValidationListener());
passwordConfirmation.addValidationListener(
new StringLengthValidationListener(256));
formPanel.add(passwordConfirmation);
saveCancelSection = new SaveCancelSection();
formPanel.add(saveCancelSection);
add(formPanel);
successPanel = new BoxPanel(BoxPanel.VERTICAL);
successPanel.add(new Label(new GlobalizedMessage(
"login.form.reset_password.scucess", LOGIN_BUNDLE)));
add(successPanel);
}
private void addListeners() {
addValidationListener(e -> {
final PageState state = e.getPageState();
if (saveCancelSection.getSaveButton().isSelected(state)) {
final FormData data = e.getFormData();
final String emailData = data.getString(EMAIL);
final String authTokenData = data.getString(AUTH_TOKEN);
final String passwordData = data.getString(PASSWORD);
final String passwordConfirmationData = data.getString(
PASSWORD_CONFIRMATION);
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final UserRepository userRepository = cdiUtil.findBean(
UserRepository.class);
final User user = userRepository.findByEmailAddress(emailData);
if (user == null) {
data.addError(new GlobalizedMessage(
"login.form.reset_password.error", LOGIN_BUNDLE));
return;
}
final OneTimeAuthManager oneTimeAuthManager = cdiUtil.findBean(
OneTimeAuthManager.class);
if (!oneTimeAuthManager.validTokenExistsForUser(
user, OneTimeAuthTokenPurpose.RECOVER_PASSWORD)) {
data.addError(new GlobalizedMessage(
"login.form.reset_password.error", LOGIN_BUNDLE));
return;
}
final List<OneTimeAuthToken> tokens = oneTimeAuthManager
.retrieveForUser(
user, OneTimeAuthTokenPurpose.ACCOUNT_ACTIVATION);
boolean result = false;
for (OneTimeAuthToken token : tokens) {
if (oneTimeAuthManager.verify(token, authTokenData)) {
result = true;
break;
}
}
if (!result) {
data.addError(new GlobalizedMessage(
"login.form.reset_password.error", LOGIN_BUNDLE));
return;
}
if (!passwordData.equals(passwordConfirmationData)) {
data.addError(new GlobalizedMessage(
"login.form.reset_password.error.password_mismatch",
LOGIN_BUNDLE));
}
}
});
addProcessListener(e -> {
final PageState state = e.getPageState();
if (saveCancelSection.getSaveButton().isSelected(state)) {
final FormData data = e.getFormData();
final String emailData = data.getString(EMAIL);
final String authTokenData = data.getString(AUTH_TOKEN);
final String passwordData = data.getString(PASSWORD);
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final UserRepository userRepository = cdiUtil.findBean(
UserRepository.class);
final User user = userRepository.findByEmailAddress(emailData);
if (user == null) {
throw new FormProcessException(
"No matching user found. This should not happen because "
+ "we verified that just a few moments ago.",
new GlobalizedMessage(
"login.form.reset_password.error"));
}
final ChallengeManager challengeManager = cdiUtil.findBean(
ChallengeManager.class);
try {
challengeManager.finishPasswordRecover(user,
authTokenData,
passwordData);
} catch (ChallengeFailedException ex) {
throw new FormProcessException(
"Failed to finish password recovery.",
new GlobalizedMessage(
"login.form.account_activation.error.failed"),
ex);
}
formPanel.setVisible(state, false);
successPanel.setVisible(state, true);
data.clear();
}
});
}
@Override
public void register(final Page page) {
super.register(page);
page.setVisibleDefault(formPanel, true);
page.setVisibleDefault(successPanel, false);
}
}

View File

@ -0,0 +1,209 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package com.arsdigita.ui.login;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.FormData;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SaveCancelSection;
import com.arsdigita.bebop.form.TextField;
import com.arsdigita.bebop.parameters.NotEmptyValidationListener;
import com.arsdigita.bebop.parameters.StringLengthValidationListener;
import com.arsdigita.globalization.GlobalizedMessage;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.configuration.ConfigurationManager;
import org.libreccm.security.ChallengeFailedException;
import org.libreccm.security.ChallengeManager;
import org.libreccm.security.OneTimeAuthConfig;
import org.libreccm.security.OneTimeAuthManager;
import org.libreccm.security.OneTimeAuthToken;
import org.libreccm.security.OneTimeAuthTokenPurpose;
import org.libreccm.security.User;
import org.libreccm.security.UserRepository;
import java.util.List;
import static com.arsdigita.ui.login.LoginConstants.*;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class UserAccountActivationForm extends Form {
private static final String EMAIL = "email";
private static final String AUTH_TOKEN = "authtoken";
private BoxPanel formPanel;
private TextField email;
private TextField authToken;
private SaveCancelSection saveCancelSection;
private BoxPanel successPanel;
public UserAccountActivationForm() {
super("user-activate-account");
addWidgets();
addListeners();
}
private void addWidgets() {
formPanel = new BoxPanel(BoxPanel.VERTICAL);
email = new TextField(EMAIL);
email.setLabel(new GlobalizedMessage(
"login.form.account_activation.email.label", LOGIN_BUNDLE));
email.setHint(new GlobalizedMessage(
"login.form.account_activation.email.hint", LOGIN_BUNDLE));
email.setMaxLength(256);
email.setSize(48);
email.addValidationListener(new NotEmptyValidationListener());
email.addValidationListener(new StringLengthValidationListener(256));
formPanel.add(email);
final ConfigurationManager confManager = CdiUtil.createCdiUtil()
.findBean(ConfigurationManager.class);
final OneTimeAuthConfig oneTimeAuthConfig = confManager
.findConfiguration(OneTimeAuthConfig.class);
authToken = new TextField(AUTH_TOKEN);
authToken.setLabel(new GlobalizedMessage(
"login.form.account_activation.auth_token.label", LOGIN_BUNDLE));
authToken.setHint(new GlobalizedMessage(
"login.form.account_activation.auth_token.hint", LOGIN_BUNDLE));
authToken.setMaxLength(oneTimeAuthConfig.getTokenLength());
authToken.setSize(oneTimeAuthConfig.getTokenLength());
formPanel.add(authToken);
saveCancelSection = new SaveCancelSection();
formPanel.add(saveCancelSection);
add(formPanel);
successPanel = new BoxPanel(BoxPanel.VERTICAL);
successPanel.add(new Label(new GlobalizedMessage(
"login.form.account_activation.success", LOGIN_BUNDLE)));
add(successPanel);
}
private void addListeners() {
addValidationListener(e -> {
final PageState state = e.getPageState();
if (saveCancelSection.getSaveButton().isSelected(state)) {
final FormData data = e.getFormData();
final String emailData = (String) data.get(EMAIL);
final String authTokenData = (String) data.get(AUTH_TOKEN);
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final UserRepository userRepository = cdiUtil.findBean(
UserRepository.class);
final User user = userRepository.findByEmailAddress(emailData);
if (user == null) {
data.addError(new GlobalizedMessage(
"login.form.account_activation.error", LOGIN_BUNDLE));
return;
}
final OneTimeAuthManager oneTimeAuthManager = cdiUtil.findBean(
OneTimeAuthManager.class);
if (!oneTimeAuthManager.validTokenExistsForUser(
user, OneTimeAuthTokenPurpose.ACCOUNT_ACTIVATION)) {
data.addError(new GlobalizedMessage(
"login.form.account_activation.error", LOGIN_BUNDLE));
return;
}
final List<OneTimeAuthToken> tokens = oneTimeAuthManager
.retrieveForUser(
user, OneTimeAuthTokenPurpose.ACCOUNT_ACTIVATION);
boolean result = false;
for (OneTimeAuthToken token : tokens) {
if (oneTimeAuthManager.verify(token, authTokenData)) {
result = true;
break;
}
}
if (!result) {
data.addError(new GlobalizedMessage(
"login.form.account_activation.error", LOGIN_BUNDLE));
}
}
});
addProcessListener(e -> {
final PageState state = e.getPageState();
if (saveCancelSection.getSaveButton().isSelected(state)) {
final FormData data = e.getFormData();
final String emailData = (String) data.get(EMAIL);
final String authTokenData = (String) data.get(AUTH_TOKEN);
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final UserRepository userRepository = cdiUtil.findBean(
UserRepository.class);
final User user = userRepository.findByEmailAddress(emailData);
if (user == null) {
throw new FormProcessException(
"No matching user found. This should not happen because "
+ "we verified that just a few moments ago.",
new GlobalizedMessage(
"login.form.account_activation.error"));
}
final ChallengeManager challengeManager = cdiUtil.findBean(
ChallengeManager.class);
try {
challengeManager.finishAccountActivation(user,
authTokenData);
} catch (ChallengeFailedException ex) {
throw new FormProcessException(
"Failed to finish account activation.",
new GlobalizedMessage(
"login.form.account_activation.error.failed"),
ex);
}
formPanel.setVisible(state, false);
successPanel.setVisible(state, true);
data.clear();
}
});
}
@Override
public void register(final Page page) {
super.register(page);
page.setVisibleDefault(formPanel, true);
page.setVisibleDefault(successPanel, false);
}
}

View File

@ -38,6 +38,7 @@ import javax.servlet.http.HttpServletRequest;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.core.EmailAddress;
import org.libreccm.security.ChallengeManager;
import org.libreccm.security.User;
import org.libreccm.security.Shiro;
@ -114,9 +115,17 @@ public class UserEditForm extends UserForm
user.setGivenName((String) m_firstName.getValue(state));
user.setFamilyName((String) m_lastName.getValue(state));
user.setName((String) m_screenName.getValue(state));
final String emailValue = (String) data.get(FORM_EMAIL);
if (!emailValue.equals(user.getPrimaryEmailAddress().getAddress())) {
final EmailAddress newAddress = new EmailAddress();
newAddress.setAddress(data.get(FORM_EMAIL).toString());
newAddress.setVerified(false);
user.setPrimaryEmailAddress(newAddress);
final ChallengeManager challengeManager = CdiUtil.createCdiUtil()
.findBean(ChallengeManager.class);
challengeManager.createEmailVerification(user);
}
userRepository.save(user);
// redirect to workspace or return URL_MSG, if specified

View File

@ -1,10 +1,10 @@
/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
@ -13,192 +13,272 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package com.arsdigita.ui.login;
import com.arsdigita.bebop.ColumnPanel;
import com.arsdigita.bebop.Container;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.FormData;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.Link;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.event.FormInitListener;
import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.event.FormValidationListener;
import com.arsdigita.bebop.form.Hidden;
import com.arsdigita.bebop.parameters.ArrayParameter;
import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.bebop.parameters.URLParameter;
import com.arsdigita.kernel.KernelConfig;
import com.arsdigita.ui.UI;
import com.arsdigita.web.URL;
import com.arsdigita.web.ReturnSignal;
import com.arsdigita.bebop.SaveCancelSection;
import com.arsdigita.bebop.form.Password;
import com.arsdigita.bebop.form.TextField;
import com.arsdigita.bebop.parameters.NotEmptyValidationListener;
import com.arsdigita.bebop.parameters.StringLengthValidationListener;
import com.arsdigita.globalization.GlobalizedMessage;
import com.arsdigita.kernel.security.SecurityConfig;
import java.util.concurrent.Callable;
import static com.arsdigita.ui.login.LoginConstants.*;
import javax.mail.internet.InternetAddress;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.security.Shiro;
import org.libreccm.configuration.ConfigurationManager;
import org.libreccm.security.ChallengeManager;
import org.libreccm.security.User;
import org.libreccm.security.UserManager;
import org.libreccm.security.UserRepository;
import javax.mail.MessagingException;
import static com.arsdigita.ui.login.LoginConstants.*;
import static com.arsdigita.ui.login.LoginServlet.*;
/**
* Creates a new user. Collects user's basic info, such as email, password,
* first name, last name, etc; then tries to create the user in the database. If
* returnURL is passed in to the form, then redirects to that URL_MSG; otherwise
* redirects to the user workspace.
*
*
* @author Michael Bryzek
* @author Roger Hsueh
* @author Sameer Ajmani
*
* @version $Id$
*
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class UserNewForm extends UserForm implements FormInitListener,
FormProcessListener,
FormValidationListener {
public class UserNewForm extends Form {
private static final Logger s_log = Logger.getLogger(UserNewForm.class);
// private static final Logger LOGGER = LogManager.getLogger(UserNewForm.class);
private static final String USERNAME = "username";
private static final String GIVEN_NAME = "givenname";
private static final String FAMILY_NAME = "familyname";
private static final String EMAIL = "email";
private static final String PASSWORD = "password";
private static final String PASSWORD_CONFIRMATION = "passwordconfirmation";
static final String FORM_NAME = "user-new";
private Hidden m_loginName;
private Hidden m_returnURL;
private Hidden m_persistent;
private BoxPanel formPanel;
private TextField userName;
private TextField givenName;
private TextField familyName;
private TextField email;
private Password password;
private Password passwordConfirm;
private SaveCancelSection saveCancelSection;
private BoxPanel finishedMessagePanel;
public UserNewForm() {
this(new ColumnPanel(2));
super("user-new");
addWidgets();
addListeners();
}
@Override
protected User getUser(final PageState state) {
return null; // don't load any data into form
// public UserNewForm(String name) {
// super(name);
// addWidgets();
// addListeners();
// }
//
// public UserNewForm(final String name, final Container container) {
// super(name, container);
// addWidgets();
// addListeners();
// }
private void addWidgets() {
formPanel = new BoxPanel(BoxPanel.VERTICAL);
userName = new TextField(USERNAME);
userName.setLabel(new GlobalizedMessage(
"login.form.new_user.username.label", LOGIN_BUNDLE));
userName.setHint(new GlobalizedMessage(
"login.form.new_user.username.hint", LOGIN_BUNDLE));
userName.setMaxLength(256);
userName.setSize(32);
userName.addValidationListener(new NotEmptyValidationListener());
userName.addValidationListener(new StringLengthValidationListener(256));
formPanel.add(userName);
givenName = new TextField(GIVEN_NAME);
givenName.setLabel(new GlobalizedMessage(
"login.form.new_user.givenname.label", LOGIN_BUNDLE));
givenName.setHint(new GlobalizedMessage(
"login.form.new_user.givenname.hint", LOGIN_BUNDLE));
givenName.setMaxLength(256);
givenName.setSize(32);
givenName.addValidationListener(new NotEmptyValidationListener());
givenName.addValidationListener(new StringLengthValidationListener(256));
formPanel.add(givenName);
familyName = new TextField(FAMILY_NAME);
familyName.setLabel(new GlobalizedMessage(
"login.form.new_user.familyname.label", LOGIN_BUNDLE));
familyName.setHint(new GlobalizedMessage(
"login.form.new_user.familyname.hint", LOGIN_BUNDLE));
familyName.setMaxLength(256);
familyName.setSize(32);
familyName.addValidationListener(new NotEmptyValidationListener());
familyName.addValidationListener(
new StringLengthValidationListener(256));
formPanel.add(familyName);
email = new TextField(EMAIL);
email.setLabel(new GlobalizedMessage("login.form.new_user.email.label",
LOGIN_BUNDLE));
email.setHint(new GlobalizedMessage("login.form.new_user.email.hint",
LOGIN_BUNDLE));
email.setMaxLength(256);
email.setSize(48);
email.addValidationListener(new NotEmptyValidationListener());
email.addValidationListener(new StringLengthValidationListener(256));
formPanel.add(email);
password = new Password(PASSWORD);
password.setLabel(new GlobalizedMessage(
"login.form.new_user.password.label", LOGIN_BUNDLE));
password.setHint(new GlobalizedMessage(
"login.form.new_user.password.hint", LOGIN_BUNDLE));
password.setMaxLength(256);
password.setSize(32);
password.addValidationListener(new NotEmptyValidationListener());
formPanel.add(password);
passwordConfirm = new Password(PASSWORD_CONFIRMATION);
passwordConfirm.setLabel(new GlobalizedMessage(
"login.form.new_user.password_confirmation.label", LOGIN_BUNDLE));
passwordConfirm.setHint(new GlobalizedMessage(
"login.form.new_user.password_confirmation.hint", LOGIN_BUNDLE));
passwordConfirm.setMaxLength(256);
passwordConfirm.setSize(32);
passwordConfirm.addValidationListener(new NotEmptyValidationListener());
formPanel.add(passwordConfirm);
saveCancelSection = new SaveCancelSection();
formPanel.add(saveCancelSection);
add(formPanel);
finishedMessagePanel = new BoxPanel(BoxPanel.VERTICAL);
finishedMessagePanel.add(new Label(new GlobalizedMessage(
"login.form.new_user.finshed_message", LOGIN_BUNDLE)));
final Link link = new Link(
new Label(
new GlobalizedMessage(
"login.form.new_user.finished_message.activate_link",
LOGIN_BUNDLE)),
LOGIN_PAGE_URL + ACTIVATE_ACCOUNT_PATH_INFO);
finishedMessagePanel.add(link);
add(finishedMessagePanel);
}
public UserNewForm(final Container panel) {
super(FORM_NAME, panel, true);
private void addListeners() {
addValidationListener(e -> {
final PageState state = e.getPageState();
addInitListener(this);
addValidationListener(this);
addProcessListener(this);
// save return URL_MSG
m_returnURL = new Hidden(new URLParameter(
LoginHelper.RETURN_URL_PARAM_NAME));
m_returnURL.setPassIn(true);
add(m_returnURL);
// save email address or screen name
m_loginName = new Hidden(new StringParameter(FORM_LOGIN));
m_loginName.setPassIn(true);
add(m_loginName);
// save persistent flag
ArrayParameter cookieP = new ArrayParameter(FORM_PERSISTENT_LOGIN_P);
m_persistent = new Hidden(cookieP);
m_persistent.setPassIn(true);
add(m_persistent);
}
@Override
public void init(final FormSectionEvent event)
throws FormProcessException {
PageState state = event.getPageState();
// clear passwords from form data
m_password.setValue(state, "");
m_confirm.setValue(state, "");
String loginName = (String) m_loginName.getValue(state);
if (loginName != null) {
if (KernelConfig.getConfig().emailIsPrimaryIdentifier()) {
m_email.setValue(state, loginName);
} else {
m_screenName.setValue(state, loginName);
}
}
}
@Override
public void process(final FormSectionEvent event)
throws FormProcessException {
PageState state = event.getPageState();
final InternetAddress address = (InternetAddress) m_email
.getValue(state);
final String email = address.getAddress();
// TODO: set additional emails
final String password = (String) m_password.getValue(state);
final String firstName = (String) m_firstName.getValue(state);
final String lastName = (String) m_lastName.getValue(state);
final String screenName;
if (KernelConfig.getConfig().emailIsPrimaryIdentifier()) {
screenName = null;
} else {
screenName = (String) m_screenName.getValue(state);
}
final Exception[] formExceptions = new Exception[]{null};
if (saveCancelSection.getSaveButton().isSelected(state)) {
final FormData data = e.getFormData();
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final Shiro shiro = cdiUtil.findBean(Shiro.class);
shiro.getSystemUser().execute(new Callable<Void>() {
@Override
public Void call() throws Exception {
final UserManager userManager = CdiUtil.createCdiUtil()
.findBean(UserManager.class);
userManager.createUser(firstName,
lastName,
screenName,
email,
password);
return null;
final ConfigurationManager confManager = cdiUtil.findBean(
ConfigurationManager.class);
final SecurityConfig securityConfig = confManager
.findConfiguration(
SecurityConfig.class);
if (!securityConfig.isAutoRegistrationEnabled()) {
data.addError(new GlobalizedMessage(
"login.form.new_user.error.autoregistration_not_enabled",
LOGIN_BUNDLE));
return;
}
final UserRepository userRepository = cdiUtil.findBean(
UserRepository.class);
//check if there is already an account for the provided email
if (userRepository.findByEmailAddress((String) data.get(
EMAIL))
!= null) {
data.addError(new GlobalizedMessage(
"login.form.new_user.error.email_already_registered",
LOGIN_BUNDLE));
return;
}
//check if username is already in use
if (userRepository.findByName((String) data.get(USERNAME))
!= null) {
data.addError(new GlobalizedMessage(
"login.form.new_user.error.username_already_in_use",
LOGIN_BUNDLE));
}
//Check if password and confirmation match
final String passwordData = (String) data.get(PASSWORD);
final String confirmation = (String) data.get(
PASSWORD_CONFIRMATION);
if (!passwordData.equals(confirmation)) {
data.addError(new GlobalizedMessage(
"login.form.new_user.error.passwords_do_not_match",
LOGIN_BUNDLE));
}
}
});
addProcessListener(e -> {
final PageState state = e.getPageState();
if (saveCancelSection.getSaveButton().isSelected(state)) {
//Neuen User anlegen, mit banned = true
final FormData data = e.getFormData();
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final UserRepository userRepository = cdiUtil.findBean(
UserRepository.class);
final UserManager userManager = cdiUtil.findBean(
UserManager.class);
final String givenNameData = (String) data.get(GIVEN_NAME);
final String familyNameData = (String) data
.get(FAMILY_NAME);
final String username = (String) data.get(USERNAME);
final String emailAddress = (String) data.get(EMAIL);
final String passwordData = (String) data.get(PASSWORD);
final User user = userManager.createUser(givenNameData,
familyNameData,
username,
emailAddress,
passwordData);
user.setBanned(true);
userRepository.save(user);
//challenge erzeugen
final ChallengeManager challengeManager = cdiUtil.findBean(
ChallengeManager.class);
try {
final String loginName;
if (KernelConfig.getConfig().emailIsPrimaryIdentifier()) {
loginName = email;
} else {
loginName = screenName;
challengeManager.sendAccountActivation(user);
} catch (MessagingException ex) {
throw new FormProcessException(
"Failed to send account activation challenge.",
new GlobalizedMessage(
"login.form_new_user.error.creating_challenge_failed",
LOGIN_BUNDLE), ex);
}
final Subject subject = cdiUtil.findBean(Subject.class);
if (subject.isAuthenticated()) {
subject.logout();
formPanel.setVisible(state, false);
finishedMessagePanel.setVisible(state, true);
}
});
}
final UsernamePasswordToken token = new UsernamePasswordToken(
loginName, password);
subject.login(token);
} catch (AuthenticationException ex) {
s_log.error("login failed for new user", ex);
throw new FormProcessException(ex);
}
@Override
public void register(final Page page) {
super.register(page);
// redirect to workspace or return URL_MSG, if specified
final HttpServletRequest req = state.getRequest();
final String url = UI.getWorkspaceURL();
final URL fallback = com.arsdigita.web.URL.there(req, url);
throw new ReturnSignal(req, fallback);
page.setVisibleDefault(formPanel, true);
page.setVisibleDefault(finishedMessagePanel, false);
}
}

View File

@ -39,6 +39,34 @@ import javax.mail.MessagingException;
import javax.servlet.ServletContext;
/**
* A service class for managing several so called challenges. These challenges
* are using a {@link OneTimeAuthToken} and are used to verify email addresses
* and recover passwords.
*
* For each challenge type there are three methods:
*
* <ul>
* <li>a {@code create} method returning a string with the text to be send to
* the user</li>
* <li>a {@code send} method which creates a challenge using the create method
* and sends it to the user per email</li>
* <li>a {@code finish} method which accepts a {@link OneTimeAuthToken} and
* executes the final action of the challenge</li>
* </ul>
*
* The {@code create} method are {@code public} to provide maximum flexibility
* for the users of the class.
*
* The texts used by this class can be customised using the
* {@link EmailTemplates} configuration. Each template supports two
* placeholders:
*
* <dl>
* <dt><code>link</code></dt>
* <dd>The link to the page for submitting the {@link OneTimeAuthToken}</dd>
* <dt><code>expires_date</code></dt>
* <dd>The time on which the {@link OneTimeAuthToken} expires.</dd>
* </dl>
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ -297,8 +325,12 @@ public class ChallengeManager {
for (OneTimeAuthToken token : tokens) {
if (oneTimeAuthManager.isValid(token)) {
//A token which still valid is not deleted, therefore we can't
//combine the two conditions.
if (oneTimeAuthManager.verify(token, submittedToken)) {
oneTimeAuthManager.invalidate(token);
return true;
}
} else {
oneTimeAuthManager.invalidate(token);
}

View File

@ -24,6 +24,7 @@ import org.libreccm.configuration.ConfigurationManager;
import org.libreccm.configuration.Setting;
/**
* Configuration for the one time authentication system.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/

View File

@ -33,6 +33,7 @@ import javax.persistence.TypedQuery;
import org.apache.commons.lang.RandomStringUtils;
/**
* This class manages the generation and delation of {@link OneTimeAuthToken}s.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ -137,7 +138,7 @@ public class OneTimeAuthManager {
return false;
} else {
boolean result = false;
for(OneTimeAuthToken token : tokens) {
for (OneTimeAuthToken token : tokens) {
if (isValid(token)) {
result = true;
break;
@ -167,7 +168,32 @@ public class OneTimeAuthManager {
}
/**
* Invalides (deletes) a {@link OneTimeAuthToken}.
* Verifies a submitted token against a token from the database.
*
*
* @param token The token against which the submitted token is
* verified.
* @param submittedToken The submitted token.
*
* @return {@code true} if the submitted token is valid and matches {@link token},
* {@code false} if not.
*/
public boolean verify(final OneTimeAuthToken token,
final String submittedToken) {
if (token == null || submittedToken == null) {
throw new IllegalArgumentException(
"token or submittedToken can't be null");
}
if (!isValid(token)) {
return false;
}
return token.getToken().equals(submittedToken);
}
/**
* Invaliades (deletes) a {@link OneTimeAuthToken}.
*
* @param token The token to invalidate.
*/

View File

@ -40,6 +40,25 @@ import javax.persistence.TemporalType;
import static org.libreccm.core.CoreConstants.*;
/**
* The {@code OneTimeAuthToken} is used as a one time authentication mechanism
* for several purposes. Usually a {@link OneTimeAuthToken} is used together
* with a challenge created by the {@link ChallengeManager}. The supported
* purposes at the moment are:
* <dl>
* <dt>account activation</dt>
* <dd>If user registration is enabled a new user will receive an email from the
* system with a {@link OneTimeAuthToken}. This token is used to verify the
* email address of the user and to complete the registration process.
* </dd>
* <dt>Email verification</dt>
* <dd>The systems sends a mail to an email address containing a
* {@link OneTimeAuthToken}. The user must confirm his/her email address by
* submitting the {@link OneTimeAuthToken} to the system.</dd>
* <dt>Password recover</dt>
* <dd>Sometimes users forget their password. To recover their password they
* must first request a {@link OneTimeAuthToken}. After they submitted this
* token to the system they can change their password.</dd>
* </dl>
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/

View File

@ -25,7 +25,6 @@ import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.Startup;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.TimerConfig;
import javax.ejb.TimerService;
@ -35,6 +34,9 @@ import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
/**
* This EJB uses the {@link TimerService} to run a cleanup task periodically to
* remove all expired {@link OneTimeAuthToken}s. The task period is the same
* as the time a {@link OneTimeAuthToken} is valid.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ -49,7 +51,7 @@ public class OneTimeAuthTokenCleaner {
private ConfigurationManager configurationManager;
@Inject
EntityManager entityManager;
private EntityManager entityManager;
@Inject
private OneTimeAuthManager oneTimeAuthManager;

View File

@ -19,6 +19,7 @@
package org.libreccm.security;
/**
* A enumeration used to described the purpose of {@link OneTimeAuthToken}.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/

View File

@ -64,3 +64,38 @@ login.changePasswortForm.greeting=Welcome {0}
login.changePasswortForm.introText=To change your passwort please fill out this form.
login.userForm.couldnt_load_user=Could not load User
login.userLoginForm.couldnt_create_timestamp=Could not create timestamp
login.userActiveActivateAccount.title=Activate your user account
login.resetPasswordPage.title=Reset your password
login.form.recover_password.email.label=Your email address
login.form.recover_password.email.hint=Please provide the email address associated with your user account. Please note: For security reason you will not receive an error message if there is no matching account.
login.form.recover_password.error.send_challenge_failed=Failed to send email with instructions for resetting your password. Please contact the administrator.
login.form.reset_password.email.label=Your email address
login.form.reset_password.email.hint=Please provide the email address associated with your user account.
login.form.account_activation.auth_token.label=One time authentication token
login.form.account_activation.auth_token.hint=Please provide the one time authentication token which you received (usually per email)
login.form.reset_password.password.label=New password
login.form.reset_password.password.hint=Your new password
login.form.reset_password.password_confirmation.label=Please repeat the new password to avoid typos.
login.form.reset_password.password_confirmation.hint=To avoid typos repeat the new password.
login.form.reset_password.scucess=Your password was changed successfully. You now login with your new password.
login.form.new_user.username.label=User name
login.form.new_user.username.hint=Please provide the user name you want to use for your new account.
login.form.new_user.givenname.label=Given name
login.form.new_user.givenname.hint=Please provide your given name.
login.form.new_user.familyname.label=Family name
login.form.new_user.familyname.hint=Please provide your family name.
login.form.new_user.email.label=Email
login.form.new_user.email.hint=Please provide an valid email address.
login.form.recover_password.finished_message=An email with further instructions for resetting your password has been send to the provided email address. If you don't receive an email please make sure that you provided the correct email address and try again. When receive the email follow the link below and follow the instructions.
login.form.recover_password.finished_message.link=Reset password
login.form.new_user.password.label=Password
login.form.new_user.password.hint=The password for your new account.
login.form.new_user.password_confirmation.label=Password confirmation
login.form.new_user.password_confirmation.hint=Please repeat the password to avoid typos.
login.form.new_user.finshed_message=Your new account has been created but is not active yet. An email with instructions how to activate your new account has been send to the provided email address. If you don't receive an email please contact the system administrator. Follow the following the activate your new account:
login.form.new_user.finished_message.activate_link=Activate user account
login.form.account_activation.email.label=Email
login.form.account_activation.email.hint=Please provide the email address you provided when creating your account.
login.form.reset_password.auth_token.label=One time authentication token
login.form.reset_password.auth_token.hint=Please provide the one time authentication token which you received (usually per email)
login.form.account_activation.success=Your account has been activated.

View File

@ -64,3 +64,38 @@ login.changePasswortForm.greeting=Willkommen {0}
login.changePasswortForm.introText=Um ihr Passwort zu \u00e4ndern, f\u00fcllen sie bitte das folgende Formular aus.
login.userForm.couldnt_load_user=User konnte nicht geladen werden
login.userLoginForm.couldnt_create_timestamp=Konnte den Timestamp nicht erstellen
login.userActiveActivateAccount.title=Aktivieren Sie Ihr Benutzerkonto
login.resetPasswordPage.title=Passwort zur\u00fccksetzen
login.form.recover_password.email.label=Ihre E-Mail-Adresse
login.form.recover_password.email.hint=Bitte geben Sie die E-Mail-Adresse, die in ihrem Benutzerkonto hinterlegt ist an. Bitte beachten Sie: Aus Sicherheitsgr\u00fcnden wird keine Fehlermeldung angezeigt, wenn es kein passenden Benutzerkonto gibt.
login.form.recover_password.error.send_challenge_failed=Senden der E-Mail mit weiteren Anweisungen zum Zur\u00fccksetzen ihres Passwortes fehlgeschlagen. Bitte kontaktieren Sie den Systemadministratior
login.form.reset_password.email.label=Ihre E-Mail-Adresse
login.form.reset_password.email.hint=Bitte geben Sie die E-Mail-Adresse, die in Ihrem Benutzerkonto hinterlegt ist, an.
login.form.account_activation.auth_token.label=Einmalpasswort
login.form.account_activation.auth_token.hint=Bitte geben Sie das Einmalpasswort, dass Sie erhalten haben an (\u00fcberlicherweise per E-Mail).
login.form.reset_password.password.label=Neues Passwort
login.form.reset_password.password.hint=Ihr neues Passwort
login.form.reset_password.password_confirmation.label=Best\u00e4tigen Sie das neue Passwort, um Tippfehler auszuschlie\u00dfen
login.form.reset_password.password_confirmation.hint=Bitte wiederholen
login.form.reset_password.scucess=Ihr Passwort wurde erfolgreich ge\u00e4ndert. Sie k\u00f6nnen sich jetzt mit Ihrem neuen Passwort angmelden.
login.form.new_user.username.label=Benutzername
login.form.new_user.username.hint=Bitte geben den Benutzername an, den Sie f\u00fcr Ihr neues Benutzerkonto verwenden m\u00f6chten.
login.form.new_user.givenname.label=Vorname
login.form.new_user.givenname.hint=Bitte geben Sie Ihren Vornamen an.
login.form.new_user.familyname.label=Familienname
login.form.new_user.familyname.hint=Bitte geben Sie Ihren Familiennamen an.
login.form.new_user.email.label=E-Mail-Adresse
login.form.new_user.email.hint=Bitte geben Sie eine g\u00fcltige E-Mail-Adresse an.
login.form.recover_password.finished_message=Eine E-Mail mit weiteren Anweisungen zum zur\u00fccksetzen des Passwortes wurde an die angebebene E-Mail-Adresse gesendet. Wenn Sie keine E-Mail erhalten, stellen Sie bitte sicher, dass Sie die korrekte E-Mail angegeben haben. Wenn Sie die E-Mail erhalten folgen Sie bitte dem folgenden Link:
login.form.recover_password.finished_message.link=Passwort zur\u00fccksetzen
login.form.new_user.password.label=Passwort
login.form.new_user.password.hint=Das Passwort f\u00fcr Ihr neues Benutzerkonto.
login.form.new_user.password_confirmation.label=Passwortbest\u00e4tigung
login.form.new_user.password_confirmation.hint=Bitte wiederholen Sie das Passwort um Tippfehler zu vermeiden.
login.form.new_user.finshed_message=Ihr Benutzerkonto wurde angelegt, ist aber noch nicht aktiviert. Eine E-Mail mit Anweisungen zur Aktivierung Ihres Benutzerkontos wurde an die angegebene E-Mail-Adresse gesendet. Falls Sie keine E-Mail erhalten, kontaktieren Sie bitte den Systemadministrator. Um Ihr Benutzerkonto zu aktivieren folgen Sie bitte dem folgenden Link:
login.form.new_user.finished_message.activate_link=Benutzerkonto aktivieren
login.form.account_activation.email.label=E-Mail
login.form.account_activation.email.hint=Bitte geben Sie die E-Mail-Adresse an, die Sie beim Anlegen Ihres Benutzerkontos verwendet haben.
login.form.reset_password.auth_token.label=Einmalpasswort
login.form.reset_password.auth_token.hint=Bitte geben Sie das Einmalpasswort, dass Sie erhalten haben an (\u00fcberlicherweise per E-Mail).
login.form.account_activation.success=Ihr Benutzerkonto wurde aktiviert.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -28,7 +28,6 @@ import org.jboss.arquillian.persistence.ShouldMatchDataSet;
import org.jboss.arquillian.persistence.UsingDataSet;
import org.jboss.arquillian.transaction.api.annotation.TransactionMode;
import org.jboss.arquillian.transaction.api.annotation.Transactional;
import org.jboss.shrinkwrap.api.IllegalArchivePathException;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
@ -318,7 +317,7 @@ public class ChallengeManagerTest {
@UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/data.xml")
@ShouldMatchDataSet(
value = "datasets/org/libreccm/security/ChallengeManagerTest/"
+ "after-create-password-recover.xml",
+ "after-create-password-recovery.xml",
excludeColumns = {"token_id", "token", "valid_until"})
@InSequence(3100)
public void createPasswordRecover() {
@ -352,10 +351,10 @@ public class ChallengeManagerTest {
@Test
@UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/"
+ "finish-password-recover.xml")
+ "finish-password-recovery.xml")
@ShouldMatchDataSet(
value = "datasets/org/libreccm/security/ChallengeManagerTest/"
+ "after-finish-password-recover.xml",
+ "after-finish-password-recovery.xml",
excludeColumns = "password")
@InSequence(3300)
public void finishPasswordRecover() throws ChallengeFailedException {
@ -371,10 +370,10 @@ public class ChallengeManagerTest {
@Test(expected = IllegalArgumentException.class)
@UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/"
+ "finish-password-recover.xml")
+ "finish-password-recovery.xml")
@ShouldMatchDataSet(
value = "datasets/org/libreccm/security/ChallengeManagerTest/"
+ "finish-password-recover.xml")
+ "finish-password-recovery.xml")
@ShouldThrowException(IllegalArgumentException.class)
@InSequence(3400)
public void finishPasswordRecoverNullUser() throws ChallengeFailedException {
@ -386,10 +385,10 @@ public class ChallengeManagerTest {
@Test(expected = IllegalArgumentException.class)
@UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/"
+ "finish-password-recover.xml")
+ "finish-password-recovery.xml")
@ShouldMatchDataSet(
value = "datasets/org/libreccm/security/ChallengeManagerTest/"
+ "finish-password-recover.xml")
+ "finish-password-recovery.xml")
@ShouldThrowException(IllegalArgumentException.class)
@InSequence(3400)
public void finishPasswordRecoverNullToken()
@ -401,10 +400,10 @@ public class ChallengeManagerTest {
@Test(expected = IllegalArgumentException.class)
@UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/"
+ "finish-password-recover.xml")
+ "finish-password-recovery.xml")
@ShouldMatchDataSet(
value = "datasets/org/libreccm/security/ChallengeManagerTest/"
+ "finish-password-recover.xml")
+ "finish-password-recovery.xml")
@ShouldThrowException(IllegalArgumentException.class)
@InSequence(3500)
public void finishPasswordRecoverNullPassword()
@ -418,10 +417,10 @@ public class ChallengeManagerTest {
@Test(expected = IllegalArgumentException.class)
@UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/"
+ "finish-password-recover.xml")
+ "finish-password-recovery.xml")
@ShouldMatchDataSet(
value = "datasets/org/libreccm/security/ChallengeManagerTest/"
+ "finish-password-recover.xml")
+ "finish-password-recovery.xml")
@ShouldThrowException(IllegalArgumentException.class)
@InSequence(3600)
public void finishPasswordRecoverEmptyPassword()

View File

@ -42,10 +42,15 @@ public class DatasetsXmlTest extends DatasetsVerifier {
public static Collection<String> data() {
return Arrays.asList(new String[]{
"/datasets/org/libreccm/security/ChallengeManagerTest/data.xml",
"/datasets/org/libreccm/security/ChallengeManagerTest/after-create-email-verification.xml",
"/datasets/org/libreccm/security/ChallengeManagerTest/after-create-account-activation.xml",
"/datasets/org/libreccm/security/ChallengeManagerTest/after-create-email-verification.xml",
"/datasets/org/libreccm/security/ChallengeManagerTest/after-create-password-recovery.xml",
"/datasets/org/libreccm/security/ChallengeManagerTest/after-finish-account-activation.xml",
"/datasets/org/libreccm/security/ChallengeManagerTest/after-finish-email-verification.xml",
"/datasets/org/libreccm/security/ChallengeManagerTest/after-finish-password-recovery.xml",
"/datasets/org/libreccm/security/OneTimeAuthManagerTest/data.xml",
"/datasets/org/libreccm/security/OneTimeAuthManagerTest/after-create.xml",
"/datasets/org/libreccm/security/OneTimeAuthManagerTest/after-invalidate.xml",});