From 4bd63b1b458a3c3c371383fc0ecd97263aef3150 Mon Sep 17 00:00:00 2001 From: jensp Date: Wed, 6 Apr 2016 13:44:27 +0000 Subject: [PATCH] CCM NG: - 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 --- .../ui/admin/usersgroupsroles/UserAdmin.java | 177 ++++---- .../ui/login/ChangePasswordForm.java | 16 +- .../arsdigita/ui/login/LoginConstants.java | 97 +++-- .../com/arsdigita/ui/login/LoginServlet.java | 109 +++-- .../ui/login/RecoverPasswordForm.java | 174 ++++++++ .../arsdigita/ui/login/ResetPasswordForm.java | 256 +++++++++++ .../ui/login/UserAccountActivationForm.java | 209 +++++++++ .../com/arsdigita/ui/login/UserEditForm.java | 15 +- .../com/arsdigita/ui/login/UserNewForm.java | 398 +++++++++++------- .../libreccm/security/ChallengeManager.java | 40 +- .../libreccm/security/OneTimeAuthConfig.java | 3 +- .../libreccm/security/OneTimeAuthManager.java | 30 +- .../libreccm/security/OneTimeAuthToken.java | 35 +- .../security/OneTimeAuthTokenCleaner.java | 8 +- .../security/OneTimeAuthTokenPurpose.java | 3 +- .../ui/login/LoginResources.properties | 35 ++ .../ui/login/LoginResources_de.properties | 35 ++ .../ui/login/LoginResources_en.properties | 35 ++ .../ui/login/LoginResources_fr.properties | 35 ++ .../security/ChallengeManagerTest.java | 23 +- .../libreccm/security/DatasetsXmlTest.java | 7 +- ...xml => after-create-password-recovery.xml} | 0 ...xml => after-finish-password-recovery.xml} | 0 ...cover.xml => finish-password-recovery.xml} | 0 24 files changed, 1361 insertions(+), 379 deletions(-) create mode 100644 ccm-core/src/main/java/com/arsdigita/ui/login/RecoverPasswordForm.java create mode 100644 ccm-core/src/main/java/com/arsdigita/ui/login/ResetPasswordForm.java create mode 100644 ccm-core/src/main/java/com/arsdigita/ui/login/UserAccountActivationForm.java rename ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/{after-create-password-recover.xml => after-create-password-recovery.xml} (100%) rename ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/{after-finish-password-recover.xml => after-finish-password-recovery.xml} (100%) rename ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/{finish-password-recover.xml => finish-password-recovery.xml} (100%) diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserAdmin.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserAdmin.java index a12bb9eb3..ba4829bb4 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserAdmin.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserAdmin.java @@ -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; @@ -68,9 +67,9 @@ import static com.arsdigita.ui.admin.AdminUiConstants.*; * @author Jens Pelzetter */ public class UserAdmin extends BoxPanel { - + private static final Logger LOGGER = LogManager.getLogger(UserAdmin.class); - + private final StringParameter userIdParameter; private final StringParameter emailParameter; private final ParameterSingleSelectionModel selectedUserId; @@ -86,15 +85,15 @@ public class UserAdmin extends BoxPanel { // private final UserDetails userDetails; private final BoxPanel userDetails; private final Form emailForm; - + public UserAdmin() { super(); - + setIdAttr("userAdmin"); - + usersTablePanel = new BoxPanel(); usersTablePanel.setIdAttr("usersTablePanel"); - + final Form filterForm = new Form("usersTableFilterForm"); usersTableFilter = new TextField("usersTableFilter"); usersTableFilter.setLabel(new GlobalizedMessage( @@ -110,7 +109,7 @@ public class UserAdmin extends BoxPanel { }); filterForm.add(clearLink); usersTablePanel.add(filterForm); - + userIdParameter = new StringParameter("selected_user_id"); selectedUserId = new ParameterSingleSelectionModel<>(userIdParameter); //selectedUserId = new ParameterSingleSelectionModel<>(USER_ID_PARAM); @@ -118,10 +117,10 @@ public class UserAdmin extends BoxPanel { emailParameter = new StringParameter("selected_email_address"); selectedEmailAddress = new ParameterSingleSelectionModel<>( emailParameter); - + usersTable = new UsersTable(this, usersTableFilter, selectedUserId); usersTablePanel.add(usersTable); - + add(usersTablePanel); // final Text text = new Text(); @@ -137,19 +136,19 @@ public class UserAdmin extends BoxPanel { // add(new UserDetails(this, selectedUserId)); userDetails = new BoxPanel(); userDetails.setIdAttr("userDetails"); - + backToUsersTable = new ActionLink(new GlobalizedMessage( "ui.admin.user_details.back", ADMIN_BUNDLE)); backToUsersTable.setIdAttr("userDetailsBackLink"); backToUsersTable.addActionListener( e -> closeUserDetails(e.getPageState())); userDetails.add(backToUsersTable); - + userProperties = new PropertySheet(new UserPropertySheetModelBuilder( this, selectedUserId)); userProperties.setIdAttr("userProperties"); userDetails.add(userProperties); - + userEditForm = new Form("userEditForm"); final TextField username = new TextField("username"); username.setLabel(new GlobalizedMessage( @@ -193,12 +192,12 @@ public class UserAdmin extends BoxPanel { userEditForm.add(userEditFormSaveCancel); userEditForm.addInitListener(e -> { final PageState state = e.getPageState(); - + final String userIdStr = selectedUserId.getSelectedKey(state); final UserRepository userRepository = CdiUtil.createCdiUtil() .findBean(UserRepository.class); final User user = userRepository.findById(Long.parseLong(userIdStr)); - + username.setValue(state, user.getName()); familyName.setValue(state, user.getFamilyName()); givenName.setValue(state, user.getGivenName()); @@ -212,14 +211,14 @@ public class UserAdmin extends BoxPanel { }); userEditForm.addProcessListener(e -> { final PageState state = e.getPageState(); - + if (userEditFormSaveCancel.getSaveButton().isSelected(state)) { final String userIdStr = selectedUserId.getSelectedKey(state); final UserRepository userRepository = CdiUtil.createCdiUtil() .findBean(UserRepository.class); final User user = userRepository.findById(Long.parseLong( userIdStr)); - + if (!user.getName().equals(username.getValue(state))) { user.setName((String) username.getValue(state)); } @@ -229,13 +228,13 @@ public class UserAdmin extends BoxPanel { if (!user.getGivenName().equals(givenName.getValue(state))) { user.setGivenName((String) familyName.getValue(state)); } - + if ("banned".equals(banned.getValue(state)) && !user.isBanned()) { user.setBanned(true); } else { user.setBanned(false); } - + if ("password_reset_required".equals(passwordResetRequired .getValue( state)) @@ -244,13 +243,13 @@ public class UserAdmin extends BoxPanel { } else { user.setPasswordResetRequired(false); } - + userRepository.save(user); } closeUserEditForm(state); }); add(userEditForm); - + passwordSetForm = new Form("password_set_form"); final Password newPassword = new Password("new_password"); newPassword.setLabel(new GlobalizedMessage( @@ -276,13 +275,13 @@ public class UserAdmin extends BoxPanel { passwordSetForm.add(passwordSetFormSaveCancel); passwordSetForm.addValidationListener(e -> { final PageState state = e.getPageState(); - + if (passwordSetFormSaveCancel.getSaveButton().isSelected(state)) { final FormData formData = e.getFormData(); - + final String password = (String) newPassword.getValue(state); final String confirm = (String) passwordConfirm.getValue(state); - + if (!password.equals(confirm)) { formData.addError(new GlobalizedMessage( "ui.admin.user_set_password.error.do_not_match", @@ -292,16 +291,16 @@ public class UserAdmin extends BoxPanel { }); passwordSetForm.addProcessListener(e -> { final PageState state = e.getPageState(); - + if (passwordSetFormSaveCancel.getSaveButton().isSelected(state)) { final String userIdStr = selectedUserId.getSelectedKey(state); final String password = (String) newPassword.getValue(state); - + final UserRepository userRepository = CdiUtil.createCdiUtil() .findBean(UserRepository.class); final User user = userRepository.findById(Long.parseLong( userIdStr)); - + final UserManager userManager = CdiUtil.createCdiUtil() .findBean( UserManager.class); @@ -310,7 +309,7 @@ public class UserAdmin extends BoxPanel { closePasswordSetForm(state); }); add(passwordSetForm); - + actionLinks = new BoxPanel(BoxPanel.HORIZONTAL); actionLinks.setIdAttr("userDetailsActionLinks"); final ActionLink editUserDetailsLink = new ActionLink( @@ -320,7 +319,7 @@ public class UserAdmin extends BoxPanel { }); actionLinks.add(editUserDetailsLink); actionLinks.add(new Text(" | ")); - + final ActionLink setPasswordLink = new ActionLink( new GlobalizedMessage("ui.admin.user_details.set_password", ADMIN_BUNDLE)); @@ -329,7 +328,7 @@ public class UserAdmin extends BoxPanel { }); actionLinks.add(setPasswordLink); actionLinks.add(new Text(" | ")); - + final ActionLink generatePasswordLink = new ActionLink( new GlobalizedMessage("ui.admin.user_details.generate_password", ADMIN_BUNDLE)); @@ -337,26 +336,22 @@ 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); } }); actionLinks.add(generatePasswordLink); userDetails.add(actionLinks); - + final Table primaryEmailTable = new Table(); primaryEmailTable.setModelBuilder( new UserPrimaryEmailTableModelBuilder(selectedUserId)); @@ -385,7 +380,7 @@ public class UserAdmin extends BoxPanel { primaryEmailTableColModel.get( UserPrimaryEmailTableModel.COL_ACTION).setCellRenderer( new TableCellRenderer() { - + @Override public Component getComponent(final Table table, final PageState state, @@ -394,29 +389,29 @@ public class UserAdmin extends BoxPanel { final Object key, final int row, final int column) { - return new ControlLink((Label) value); + return new ControlLink((Component) value); } - + }); - + primaryEmailTable.addTableActionListener(new TableActionListener() { - + @Override public void cellSelected(final TableActionEvent event) { final String key = (String) event.getRowKey(); selectedEmailAddress.setSelectedKey(event.getPageState(), key); showEmailForm(event.getPageState()); } - + @Override public void headSelected(final TableActionEvent event) { //Nothing } - + }); - + userDetails.add(primaryEmailTable); - + final Table emailTable = new Table(); emailTable.setModelBuilder( new UserEmailTableModelBuilder(selectedUserId)); @@ -449,7 +444,7 @@ public class UserAdmin extends BoxPanel { ADMIN_BUNDLE)))); emailTableColumnModel.get(UserEmailTableModel.COL_EDIT).setCellRenderer( new TableCellRenderer() { - + @Override public Component getComponent(final Table table, final PageState state, @@ -460,12 +455,12 @@ public class UserAdmin extends BoxPanel { final int column) { return new ControlLink((Component) value); } - + }); emailTableColumnModel.get(UserEmailTableModel.COL_DELETE) .setCellRenderer( new TableCellRenderer() { - + @Override public Component getComponent(final Table table, final PageState state, @@ -482,16 +477,16 @@ public class UserAdmin extends BoxPanel { } return link; } - + }); emailTable.addTableActionListener(new TableActionListener() { - + @Override public void cellSelected(final TableActionEvent event) { final PageState state = event.getPageState(); - + final String key = (String) event.getRowKey(); - + switch (event.getColumn()) { case UserEmailTableModel.COL_EDIT: selectedEmailAddress.setSelectedKey(state, key); @@ -511,32 +506,32 @@ public class UserAdmin extends BoxPanel { break; } } - + if (email != null) { user.removeEmailAddress(email); userRepository.save(user); } } } - + @Override public void headSelected(final TableActionEvent event) { //Nothing } - + }); emailTable.setEmptyView(new Label(new GlobalizedMessage( "ui.admin.user.email_addresses.none", ADMIN_BUNDLE))); - + userDetails.add(emailTable); - + final ActionLink addEmailLink = new ActionLink(new GlobalizedMessage( "ui.admin.user.email_addresses.add", ADMIN_BUNDLE)); addEmailLink.addActionListener(e -> { showEmailForm(e.getPageState()); }); userDetails.add(addEmailLink); - + emailForm = new Form("email_form"); // emailForm.add(new Label(new GlobalizedMessage( // "ui.admin.user.email_form.address", @@ -564,12 +559,12 @@ public class UserAdmin extends BoxPanel { "ui.admin.user.email_form.bouncing", ADMIN_BUNDLE)))); emailForm.add(emailFormBouncing); - + emailForm.add(new SaveCancelSection()); - + emailForm.addInitListener(e -> { final PageState state = e.getPageState(); - + final String selected = selectedEmailAddress.getSelectedKey(state); final String userIdStr = selectedUserId.getSelectedKey(state); if (selected != null && !selected.isEmpty()) { @@ -588,7 +583,7 @@ public class UserAdmin extends BoxPanel { } } } - + if (email != null) { emailFormAddress.setValue(state, email.getAddress()); if (email.isVerified()) { @@ -600,13 +595,13 @@ public class UserAdmin extends BoxPanel { } } }); - + emailForm.addProcessListener(e -> { final PageState state = e.getPageState(); - + final String selected = selectedEmailAddress.getSelectedKey(state); final String userIdStr = selectedUserId.getSelectedKey(state); - + final UserRepository userRepository = CdiUtil.createCdiUtil() .findBean(UserRepository.class); final User user = userRepository.findById(Long.parseLong( @@ -626,10 +621,10 @@ public class UserAdmin extends BoxPanel { } } } - + if (email != null) { email.setAddress((String) emailFormAddress.getValue(state)); - + final String[] verifiedValues = (String[]) emailFormVerified .getValue(state); if (verifiedValues != null && verifiedValues.length > 0) { @@ -641,7 +636,7 @@ public class UserAdmin extends BoxPanel { } else { email.setVerified(false); } - + final String[] bouncingValues = (String[]) emailFormBouncing .getValue(state); if (bouncingValues != null && bouncingValues.length > 0) { @@ -654,33 +649,33 @@ public class UserAdmin extends BoxPanel { email.setBouncing(false); } } - + userRepository.save(user); closeEmailForm(e.getPageState()); }); - + emailForm.addCancelListener(e -> closeEmailForm(e.getPageState())); - + add(emailForm); - + add(userDetails); - + } - + @Override public void register(final Page page) { super.register(page); - + page.addGlobalStateParam(userIdParameter); page.addGlobalStateParam(emailParameter); - + page.setVisibleDefault(usersTablePanel, true); page.setVisibleDefault(userDetails, false); page.setVisibleDefault(userEditForm, false); page.setVisibleDefault(passwordSetForm, false); page.setVisibleDefault(emailForm, false); } - + protected void showUserDetails(final PageState state) { usersTablePanel.setVisible(state, false); userDetails.setVisible(state, true); @@ -688,7 +683,7 @@ public class UserAdmin extends BoxPanel { passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); } - + protected void closeUserDetails(final PageState state) { selectedUserId.clearSelection(state); usersTablePanel.setVisible(state, true); @@ -697,7 +692,7 @@ public class UserAdmin extends BoxPanel { passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); } - + protected void showUserEditForm(final PageState state) { usersTablePanel.setVisible(state, false); userDetails.setVisible(state, false); @@ -705,7 +700,7 @@ public class UserAdmin extends BoxPanel { passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); } - + protected void closeUserEditForm(final PageState state) { usersTablePanel.setVisible(state, false); userDetails.setVisible(state, true); @@ -713,7 +708,7 @@ public class UserAdmin extends BoxPanel { passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); } - + protected void showPasswordSetForm(final PageState state) { usersTablePanel.setVisible(state, false); userDetails.setVisible(state, false); @@ -721,7 +716,7 @@ public class UserAdmin extends BoxPanel { passwordSetForm.setVisible(state, true); emailForm.setVisible(state, false); } - + protected void closePasswordSetForm(final PageState state) { usersTablePanel.setVisible(state, false); userDetails.setVisible(state, true); @@ -729,7 +724,7 @@ public class UserAdmin extends BoxPanel { passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); } - + protected void showEmailForm(final PageState state) { usersTablePanel.setVisible(state, false); userDetails.setVisible(state, false); @@ -737,7 +732,7 @@ public class UserAdmin extends BoxPanel { passwordSetForm.setVisible(state, false); emailForm.setVisible(state, true); } - + protected void closeEmailForm(final PageState state) { selectedEmailAddress.clearSelection(state); usersTablePanel.setVisible(state, false); @@ -746,5 +741,5 @@ public class UserAdmin extends BoxPanel { passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); } - + } diff --git a/ccm-core/src/main/java/com/arsdigita/ui/login/ChangePasswordForm.java b/ccm-core/src/main/java/com/arsdigita/ui/login/ChangePasswordForm.java index 38be90fa0..8b824c84c 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/login/ChangePasswordForm.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/ChangePasswordForm.java @@ -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) { - PageState state = event.getPageState(); - m_oldPasswordLabel.setVisible(state, true); - m_oldPassword.setVisible(state, true); - } - + 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; diff --git a/ccm-core/src/main/java/com/arsdigita/ui/login/LoginConstants.java b/ccm-core/src/main/java/com/arsdigita/ui/login/LoginConstants.java index 41b366063..c1676c831 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/login/LoginConstants.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/LoginConstants.java @@ -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 - = "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_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 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; - - /** URL_MSG stub of Login page in ServletPath format (with leading slash and - without trailing slash */ + 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 + */ // 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/*"; } diff --git a/ccm-core/src/main/java/com/arsdigita/ui/login/LoginServlet.java b/ccm-core/src/main/java/com/arsdigita/ui/login/LoginServlet.java index 354b369c6..f21b0119f 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/login/LoginServlet.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/LoginServlet.java @@ -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 edit profile * 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) new + * user * 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 change + * password 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 recover + * password page. Ends with "/" because it is a servlet/directory. */ public static final String RECOVER_USER_PASSWORD_PATH_INFO = "/recover-password/"; - public static final String VERIFY_EMAIL = "/verify-email/"; - /** - * PathInfo into the Login application to access the (optional) newUser + * PathInfo into the Login application to access the password reset + * 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 verify email * 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) explain + * persistent 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 login + * expired-page * 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 logout * 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"; @@ -132,7 +153,7 @@ public class LoginServlet extends BebopApplicationServlet { @Inject private ConfigurationManager confManager; - + @Inject private UserRepository userRepository; @@ -146,7 +167,7 @@ public class LoginServlet extends BebopApplicationServlet { public void doInit() throws ServletException { final SecurityConfig securityConfig = confManager.findConfiguration( SecurityConfig.class); - + if (userRepository == null) { throw new IllegalStateException("User repository is not available."); } @@ -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", @@ -167,23 +189,31 @@ public class LoginServlet extends BebopApplicationServlet { "login")); disableClientCaching("/"); - /* Create and add userEditPage to the page map. */ + /* Create and add userEditPage to the page map. */ put(EDIT_USER_PROFILE_PATH_INFO, buildSimplePage("login.userEditPage.title", new UserEditForm(), "edit")); 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; } } diff --git a/ccm-core/src/main/java/com/arsdigita/ui/login/RecoverPasswordForm.java b/ccm-core/src/main/java/com/arsdigita/ui/login/RecoverPasswordForm.java new file mode 100644 index 000000000..a7c9f46d0 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/RecoverPasswordForm.java @@ -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 Jens Pelzetter + */ +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); + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/ui/login/ResetPasswordForm.java b/ccm-core/src/main/java/com/arsdigita/ui/login/ResetPasswordForm.java new file mode 100644 index 000000000..c8ca37920 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/ResetPasswordForm.java @@ -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 Jens Pelzetter + */ +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 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); + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/ui/login/UserAccountActivationForm.java b/ccm-core/src/main/java/com/arsdigita/ui/login/UserAccountActivationForm.java new file mode 100644 index 000000000..6579c98dd --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/UserAccountActivationForm.java @@ -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 Jens Pelzetter + */ +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 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); + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/ui/login/UserEditForm.java b/ccm-core/src/main/java/com/arsdigita/ui/login/UserEditForm.java index e4af46f44..74088494f 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/login/UserEditForm.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/UserEditForm.java @@ -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 EmailAddress newAddress = new EmailAddress(); - newAddress.setAddress(data.get(FORM_EMAIL).toString()); - user.setPrimaryEmailAddress(newAddress); + 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 diff --git a/ccm-core/src/main/java/com/arsdigita/ui/login/UserNewForm.java b/ccm-core/src/main/java/com/arsdigita/ui/login/UserNewForm.java index 04acac9e8..d1fdaf990 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/login/UserNewForm.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/UserNewForm.java @@ -1,204 +1,284 @@ /* - * 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 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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., 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 Jens Pelzetter */ -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); + if (saveCancelSection.getSaveButton().isSelected(state)) { + final FormData data = e.getFormData(); - // save return URL_MSG - m_returnURL = new Hidden(new URLParameter( - LoginHelper.RETURN_URL_PARAM_NAME)); - m_returnURL.setPassIn(true); - add(m_returnURL); + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + 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; + } - // save email address or screen name - m_loginName = new Hidden(new StringParameter(FORM_LOGIN)); - m_loginName.setPassIn(true); - add(m_loginName); + 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; + } - // save persistent flag - ArrayParameter cookieP = new ArrayParameter(FORM_PERSISTENT_LOGIN_P); - m_persistent = new Hidden(cookieP); - m_persistent.setPassIn(true); - add(m_persistent); - } + //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)); + } - @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); + //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)); + } } - } - } - - @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}; - - final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); - final Shiro shiro = cdiUtil.findBean(Shiro.class); - - shiro.getSystemUser().execute(new Callable() { - - @Override - public Void call() throws Exception { - - final UserManager userManager = CdiUtil.createCdiUtil() - .findBean(UserManager.class); - userManager.createUser(firstName, - lastName, - screenName, - email, - password); - - return null; - } - }); - try { - final String loginName; - if (KernelConfig.getConfig().emailIsPrimaryIdentifier()) { - loginName = email; - } else { - loginName = screenName; + 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 { + 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); + } + + formPanel.setVisible(state, false); + finishedMessagePanel.setVisible(state, true); } + }); + } - final Subject subject = cdiUtil.findBean(Subject.class); + @Override + public void register(final Page page) { + super.register(page); - if (subject.isAuthenticated()) { - subject.logout(); - } - - 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); - } - - // 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); } } diff --git a/ccm-core/src/main/java/org/libreccm/security/ChallengeManager.java b/ccm-core/src/main/java/org/libreccm/security/ChallengeManager.java index 2b002c65f..c6e24c6fc 100644 --- a/ccm-core/src/main/java/org/libreccm/security/ChallengeManager.java +++ b/ccm-core/src/main/java/org/libreccm/security/ChallengeManager.java @@ -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: + * + *
    + *
  • a {@code create} method returning a string with the text to be send to + * the user
  • + *
  • a {@code send} method which creates a challenge using the create method + * and sends it to the user per email
  • + *
  • a {@code finish} method which accepts a {@link OneTimeAuthToken} and + * executes the final action of the challenge
  • + *
+ * + * 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: + * + *
+ *
link
+ *
The link to the page for submitting the {@link OneTimeAuthToken}
+ *
expires_date
+ *
The time on which the {@link OneTimeAuthToken} expires.
+ *
* * @author Jens Pelzetter */ @@ -146,8 +174,8 @@ public class ChallengeManager { throws MessagingException { final String text = createEmailVerification(user); sendMessage( - user, - retrieveEmailSubject(OneTimeAuthTokenPurpose.RECOVER_PASSWORD), + user, + retrieveEmailSubject(OneTimeAuthTokenPurpose.RECOVER_PASSWORD), text); } @@ -297,8 +325,12 @@ public class ChallengeManager { for (OneTimeAuthToken token : tokens) { if (oneTimeAuthManager.isValid(token)) { - oneTimeAuthManager.invalidate(token); - return true; + //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); } diff --git a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthConfig.java b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthConfig.java index 81f353932..9e5114e97 100644 --- a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthConfig.java +++ b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthConfig.java @@ -24,7 +24,8 @@ import org.libreccm.configuration.ConfigurationManager; import org.libreccm.configuration.Setting; /** - * + * Configuration for the one time authentication system. + * * @author Jens Pelzetter */ @Configuration diff --git a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthManager.java b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthManager.java index 9cc5cc09b..ecb7bed27 100644 --- a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthManager.java +++ b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthManager.java @@ -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 Jens Pelzetter */ @@ -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. */ diff --git a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthToken.java b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthToken.java index e0e2e8528..de655c134 100644 --- a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthToken.java +++ b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthToken.java @@ -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: + *
+ *
account activation
+ *
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. + *
+ *
Email verification
+ *
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.
+ *
Password recover
+ *
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.
+ *
* * @author Jens Pelzetter */ @@ -47,10 +66,10 @@ import static org.libreccm.core.CoreConstants.*; @Table(name = "ONE_TIME_AUTH_TOKENS", schema = DB_SCHEMA) @NamedQueries({ @NamedQuery( - name = "OneTimeAuthToken.findByUserAndPurpose", - query = "SELECT t FROM OneTimeAuthToken t " - + "WHERE t.user = :user AND t.purpose = :purpose " - + "ORDER BY t.validUntil")}) + name = "OneTimeAuthToken.findByUserAndPurpose", + query = "SELECT t FROM OneTimeAuthToken t " + + "WHERE t.user = :user AND t.purpose = :purpose " + + "ORDER BY t.validUntil")}) public class OneTimeAuthToken implements Serializable { private static final long serialVersionUID = -9088185274208292873L; @@ -168,10 +187,10 @@ public class OneTimeAuthToken implements Serializable { public String toString(final String data) { return String.format("%s{ " - + "tokenId = %d, " - + "user = { %s }, " - + "validUnit = %tF %Jens Pelzetter */ @Singleton @@ -49,7 +51,7 @@ public class OneTimeAuthTokenCleaner { private ConfigurationManager configurationManager; @Inject - EntityManager entityManager; + private EntityManager entityManager; @Inject private OneTimeAuthManager oneTimeAuthManager; diff --git a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenPurpose.java b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenPurpose.java index b3763356a..7a2e4fbff 100644 --- a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenPurpose.java +++ b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenPurpose.java @@ -19,7 +19,8 @@ package org.libreccm.security; /** - * + * A enumeration used to described the purpose of {@link OneTimeAuthToken}. + * * @author Jens Pelzetter */ public enum OneTimeAuthTokenPurpose { diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources.properties b/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources.properties index d9978c8e4..fcbff2ba8 100644 --- a/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources.properties @@ -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. diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_de.properties b/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_de.properties index 691f2376b..a4753d683 100644 --- a/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_de.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_de.properties @@ -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. diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_en.properties b/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_en.properties index d9978c8e4..b3a0e782f 100644 --- a/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_en.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_en.properties @@ -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=To avoid typos repeat the new password. +login.form.reset_password.password_confirmation.hint=To avoid typos repeat the new password. +login.form.reset_password.scucess=login.form.reset_password.scucess\tYour password was changed successfully. You now login with your new password.\t\t\t +login.form.new_user.username.label=User name +login.form.new_user.username.hint=login.form.new_user.username.hint +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=login.form.new_user.familyname.hint +login.form.new_user.email.label=Email +login.form.new_user.email.hint=login.form.new_user.email.hint +login.form.recover_password.finished_message=login.form.recover_password.finished_messag +login.form.recover_password.finished_message.link=Reset password +login.form.new_user.password.label=Password +login.form.new_user.password.hint=login.additionalEmail\tAdditional Email:\tAdresse de messagerie suppl\u00e9mentaire\tWeitere E-Mail:\tAdditional Email:\nlogin.bio\tBiography:\tBiographie:\tBiographie:\tBiography:\nlogin.changePasswordForm.badPasswordError\tIncorrect Password\tMot de passe erron\u00e9\tFalsches Passwort\tIncorrect Password\nlogin.changePasswordForm.confirmPasswordLabel\tConfirm Password:\tConfirmation du nouveau mot de passe\tPasswort best\u00e4tigen:\tConfirm Password:\nlogin.changePasswordForm.mailBody\tDear {0},\n\nYour password has been changed successfully.\nIf you did not intend to change your password,\nplease reply to this mail and report this problem.\tCher {0}, \n\n Votre mot de passe a \u00e9t\u00e9 modifi\u00e9 avec succ\u00e8s.\nSi vous n'avez pas souhait\u00e9 changer votre mot de passe,\nmerci de r\u00e9pondre \u00e0 ce message en faisant part de ce probl\u00e8me.\tHallo {0},\n\nIhr Passwort wurde erfolgreich ge\u00e4ndert.\nWenn Sie Ihr Passwort nicht \u00e4ndern wollten,\nmelden Sie bitte diese Mail an uns.\tDear {0},\n\nYour password has been changed successfully.\nIf you did not intend to change your password,\nplease reply to this mail and report this problem.\nlogin.changePasswordForm.mailSubject\tYour password has been changed\tVotre mot de passe a \u00e9t\u00e9 modifi\u00e9\tIhr Passwort wurde ge\u00e4ndert.\tYour password has been changed\nlogin.changePasswordForm.mustDifferError\tNew password must differ from old\tLe nouveau mot de passe doit \u00eatre diff\u00e9rent de l'ancien\tDas neue Passwort muss sich vom bisherigen unterscheiden\tNew password must differ from old\nlogin.changePasswordForm.mustMatchError\tNew passwords must match\tLe nouveau mot de passe doit \u00eatre identique dans la zone de confirmation\tDie neuen Passw\u00f6rter m\u00fcssen gleich sein\tNew passwords must match\nlogin.changePasswordForm.newPasswordLabel\tNew Password (at least {0} characters, no whitespace):\tNouveau mot de passe (au moins {0} caract\u00e8re et pas d'espace):\tNeues Passwort (mindestens {0} Zeichen, keine Leertaste):\tNew Password (at least {0} characters, no whitespace):\nlogin.changePasswordForm.noUserError\tUser is not logged in\tL'utilisateur n'est pas connect\u00e9\tBenutzer ist nicht angemeldet\tUser is not logged in\nlogin.changePasswordForm.oldPasswordLabel\tOld Password:\tAncien mot de passe :\tAltes Passwort:\tOld Password:\nlogin.changePasswordForm.submit\tSubmit\tSoumettre\tAusf\u00fchren\tSubmit\nlogin.changePasswordPage.title\tChange Password\tModifier le mot de passe\tPasswort \u00c4nderung\tChange Password\nlogin.changePasswortForm.greeting\tWelcome {0}\tWelcome {0}\tWillkommen {0}\tWelcome {0}\nlogin.changePasswortForm.introText\tTo change your passwort please fill out this form.\tTo change your passwort please fill out this form.\tUm ihr Passwort zu \u00e4ndern, f\u00fcllen sie bitte das folgende Formular aus.\tTo change your passwort please fill out this form.\nlogin.error.badAnswer\tIncorrect answer\tR\u00e9ponse erron\u00e9e\tFalsche Antwort\tIncorrect answer\nlogin.error.badEmail\tUnrecognized email address {0}\tCette adresse de messagerie est inconnue\tUng\u00fcltige E-Mail Adresse: {0}\tUnrecognized email address {0}\nlogin.error.badPassword\tIncorrect password\tMot de passe erron\u00e9\tFalsches Passwort\tIncorrect password\nlogin.error.bannedEmail\tUser cannot currently access system\tUser cannot currently access system\tBenutzer kann zur Zeit das System nicht nutzen\tUser cannot currently access system\nlogin.error.duplicateEmail\tSome other user has this email address\tUn autre utilisateur nous a d\u00e9j\u00e0 donner cette adresse de messagerie\tEin anderer Nutzer verwendet diese E-Mail Adresse\tSome other user has this email address\nlogin.error.duplicateScreenName\tSome other user has this screen name\tUn autre utilisateur utilise d\u00e9j\u00e0 ce pseudonyme\tEin anderer Benutzer verwendet diesen Namen\tSome other user has this screen name\nlogin.error.loginFail\tLogin failed\tUser cannot currently access system\tAnmeldung nicht erfolgreich\tLogin failed\nlogin.error.mismatchPassword\tNew passwords must match\tLe nouveau mot de passe doit \u00eatre identique dans la zone de confirmation\tDie neuen Passw\u00f6rter m\u00fcssen gleich sein\tNew passwords must match\nlogin.explainCookiesPage.title\tSaving email address and password\tEnregistrement de l'adresse de messagerie et du mot de passe\tE-Mail Adresse und Passwort werden gesichert\tSaving email address and password\nlogin.firstName\tFirst (Given) Name:\tPr\u00e9nom:\tVorname:\tFirst (Given) Name:\nlogin.form.account_activation.auth_token.hint\tPlease provide the one time authentication token which you received (usually per email)\tPlease provide the one time authentication token which you received (usually per email)\tBitte geben Sie das Einmalpasswort, dass Sie erhalten haben an (\u00fcberlicherweise per E-Mail).\tPlease provide the one time authentication token which you received (usually per email)\nlogin.form.account_activation.auth_token.label\tOne time authentication token\tOne time authentication token\tEinmalpasswort\tOne time authentication token\nlogin.form.new_user.email.hint\tPlease provide an valid email address.\tlogin.form.new_user.email.hint\tBitte geben Sie eine g\u00fcltige E-Mail-Adresse an.\tlogin.form.new_user.email.hint\nlogin.form.new_user.email.label\tEmail\tEmail\tE-Mail-Adresse\tEmail\nlogin.form.new_user.familyname.hint\tPlease provide your family name.\tlogin.form.new_user.familyname.hint\tBitte geben Sie Ihren Familiennamen an.\tlogin.form.new_user.familyname.hint\nlogin.form.new_user.familyname.label\tFamily name\tFamily name\tFamilienname\tFamily name\nlogin.form.new_user.givenname.hint\tPlease provide your given name.\tPlease provide your given name.\tBitte geben Sie Ihren Vornamen an.\tPlease provide your given name.\nlogin.form.new_user.givenname.label\tGiven name\tGiven name\tVorname\tGiven name\nlogin.form.new_user.password.hint\tThe password for your new account.\t\t\t +login.form.new_user.password_confirmation.label=Password confirmation +login.form.new_user.password_confirmation.hint=login.form.new_user.password_confirmation.hint +login.form.new_user.finshed_message=login.form.new_user.finshed_messageYour 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. diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_fr.properties b/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_fr.properties index be53f1a31..6f465d74d 100755 --- a/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_fr.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/login/LoginResources_fr.properties @@ -64,3 +64,38 @@ login.error.bannedEmail=User cannot currently access system login.error.loginFail=User cannot currently access system login.userForm.couldnt_load_user=Impossible de charger l'utilisateur login.userLoginForm.couldnt_create_timestamp=Impossible de cr\u00e9er 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=To avoid typos repeat the new password. +login.form.reset_password.password_confirmation.hint=To avoid typos repeat the new password. +login.form.reset_password.scucess=login.form.reset_password.scucess\tYour password was changed successfully. You now login with your new password.\t\t\t +login.form.new_user.username.label=User name +login.form.new_user.username.hint=login.form.new_user.username.hint +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=login.form.new_user.familyname.hint +login.form.new_user.email.label=Email +login.form.new_user.email.hint=login.form.new_user.email.hint +login.form.recover_password.finished_message=login.form.recover_password.finished_messag +login.form.recover_password.finished_message.link=Reset password +login.form.new_user.password.label=Password +login.form.new_user.password.hint=login.additionalEmail\tAdditional Email:\tAdresse de messagerie suppl\u00e9mentaire\tWeitere E-Mail:\tAdditional Email:\nlogin.bio\tBiography:\tBiographie:\tBiographie:\tBiography:\nlogin.changePasswordForm.badPasswordError\tIncorrect Password\tMot de passe erron\u00e9\tFalsches Passwort\tIncorrect Password\nlogin.changePasswordForm.confirmPasswordLabel\tConfirm Password:\tConfirmation du nouveau mot de passe\tPasswort best\u00e4tigen:\tConfirm Password:\nlogin.changePasswordForm.mailBody\tDear {0},\n\nYour password has been changed successfully.\nIf you did not intend to change your password,\nplease reply to this mail and report this problem.\tCher {0}, \n\n Votre mot de passe a \u00e9t\u00e9 modifi\u00e9 avec succ\u00e8s.\nSi vous n'avez pas souhait\u00e9 changer votre mot de passe,\nmerci de r\u00e9pondre \u00e0 ce message en faisant part de ce probl\u00e8me.\tHallo {0},\n\nIhr Passwort wurde erfolgreich ge\u00e4ndert.\nWenn Sie Ihr Passwort nicht \u00e4ndern wollten,\nmelden Sie bitte diese Mail an uns.\tDear {0},\n\nYour password has been changed successfully.\nIf you did not intend to change your password,\nplease reply to this mail and report this problem.\nlogin.changePasswordForm.mailSubject\tYour password has been changed\tVotre mot de passe a \u00e9t\u00e9 modifi\u00e9\tIhr Passwort wurde ge\u00e4ndert.\tYour password has been changed\nlogin.changePasswordForm.mustDifferError\tNew password must differ from old\tLe nouveau mot de passe doit \u00eatre diff\u00e9rent de l'ancien\tDas neue Passwort muss sich vom bisherigen unterscheiden\tNew password must differ from old\nlogin.changePasswordForm.mustMatchError\tNew passwords must match\tLe nouveau mot de passe doit \u00eatre identique dans la zone de confirmation\tDie neuen Passw\u00f6rter m\u00fcssen gleich sein\tNew passwords must match\nlogin.changePasswordForm.newPasswordLabel\tNew Password (at least {0} characters, no whitespace):\tNouveau mot de passe (au moins {0} caract\u00e8re et pas d'espace):\tNeues Passwort (mindestens {0} Zeichen, keine Leertaste):\tNew Password (at least {0} characters, no whitespace):\nlogin.changePasswordForm.noUserError\tUser is not logged in\tL'utilisateur n'est pas connect\u00e9\tBenutzer ist nicht angemeldet\tUser is not logged in\nlogin.changePasswordForm.oldPasswordLabel\tOld Password:\tAncien mot de passe :\tAltes Passwort:\tOld Password:\nlogin.changePasswordForm.submit\tSubmit\tSoumettre\tAusf\u00fchren\tSubmit\nlogin.changePasswordPage.title\tChange Password\tModifier le mot de passe\tPasswort \u00c4nderung\tChange Password\nlogin.changePasswortForm.greeting\tWelcome {0}\tWelcome {0}\tWillkommen {0}\tWelcome {0}\nlogin.changePasswortForm.introText\tTo change your passwort please fill out this form.\tTo change your passwort please fill out this form.\tUm ihr Passwort zu \u00e4ndern, f\u00fcllen sie bitte das folgende Formular aus.\tTo change your passwort please fill out this form.\nlogin.error.badAnswer\tIncorrect answer\tR\u00e9ponse erron\u00e9e\tFalsche Antwort\tIncorrect answer\nlogin.error.badEmail\tUnrecognized email address {0}\tCette adresse de messagerie est inconnue\tUng\u00fcltige E-Mail Adresse: {0}\tUnrecognized email address {0}\nlogin.error.badPassword\tIncorrect password\tMot de passe erron\u00e9\tFalsches Passwort\tIncorrect password\nlogin.error.bannedEmail\tUser cannot currently access system\tUser cannot currently access system\tBenutzer kann zur Zeit das System nicht nutzen\tUser cannot currently access system\nlogin.error.duplicateEmail\tSome other user has this email address\tUn autre utilisateur nous a d\u00e9j\u00e0 donner cette adresse de messagerie\tEin anderer Nutzer verwendet diese E-Mail Adresse\tSome other user has this email address\nlogin.error.duplicateScreenName\tSome other user has this screen name\tUn autre utilisateur utilise d\u00e9j\u00e0 ce pseudonyme\tEin anderer Benutzer verwendet diesen Namen\tSome other user has this screen name\nlogin.error.loginFail\tLogin failed\tUser cannot currently access system\tAnmeldung nicht erfolgreich\tLogin failed\nlogin.error.mismatchPassword\tNew passwords must match\tLe nouveau mot de passe doit \u00eatre identique dans la zone de confirmation\tDie neuen Passw\u00f6rter m\u00fcssen gleich sein\tNew passwords must match\nlogin.explainCookiesPage.title\tSaving email address and password\tEnregistrement de l'adresse de messagerie et du mot de passe\tE-Mail Adresse und Passwort werden gesichert\tSaving email address and password\nlogin.firstName\tFirst (Given) Name:\tPr\u00e9nom:\tVorname:\tFirst (Given) Name:\nlogin.form.account_activation.auth_token.hint\tPlease provide the one time authentication token which you received (usually per email)\tPlease provide the one time authentication token which you received (usually per email)\tBitte geben Sie das Einmalpasswort, dass Sie erhalten haben an (\u00fcberlicherweise per E-Mail).\tPlease provide the one time authentication token which you received (usually per email)\nlogin.form.account_activation.auth_token.label\tOne time authentication token\tOne time authentication token\tEinmalpasswort\tOne time authentication token\nlogin.form.new_user.email.hint\tPlease provide an valid email address.\tlogin.form.new_user.email.hint\tBitte geben Sie eine g\u00fcltige E-Mail-Adresse an.\tlogin.form.new_user.email.hint\nlogin.form.new_user.email.label\tEmail\tEmail\tE-Mail-Adresse\tEmail\nlogin.form.new_user.familyname.hint\tPlease provide your family name.\tlogin.form.new_user.familyname.hint\tBitte geben Sie Ihren Familiennamen an.\tlogin.form.new_user.familyname.hint\nlogin.form.new_user.familyname.label\tFamily name\tFamily name\tFamilienname\tFamily name\nlogin.form.new_user.givenname.hint\tPlease provide your given name.\tPlease provide your given name.\tBitte geben Sie Ihren Vornamen an.\tPlease provide your given name.\nlogin.form.new_user.givenname.label\tGiven name\tGiven name\tVorname\tGiven name\nlogin.form.new_user.password.hint\tThe password for your new account.\t\t\t +login.form.new_user.password_confirmation.label=Password confirmation +login.form.new_user.password_confirmation.hint=login.form.new_user.password_confirmation.hint +login.form.new_user.finshed_message=login.form.new_user.finshed_messageYour 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. diff --git a/ccm-core/src/test/java/org/libreccm/security/ChallengeManagerTest.java b/ccm-core/src/test/java/org/libreccm/security/ChallengeManagerTest.java index 904540918..254a558da 100644 --- a/ccm-core/src/test/java/org/libreccm/security/ChallengeManagerTest.java +++ b/ccm-core/src/test/java/org/libreccm/security/ChallengeManagerTest.java @@ -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() diff --git a/ccm-core/src/test/java/org/libreccm/security/DatasetsXmlTest.java b/ccm-core/src/test/java/org/libreccm/security/DatasetsXmlTest.java index 7a2624e8d..ccf99d489 100644 --- a/ccm-core/src/test/java/org/libreccm/security/DatasetsXmlTest.java +++ b/ccm-core/src/test/java/org/libreccm/security/DatasetsXmlTest.java @@ -42,10 +42,15 @@ public class DatasetsXmlTest extends DatasetsVerifier { public static Collection 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",}); diff --git a/ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/after-create-password-recover.xml b/ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/after-create-password-recovery.xml similarity index 100% rename from ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/after-create-password-recover.xml rename to ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/after-create-password-recovery.xml diff --git a/ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/after-finish-password-recover.xml b/ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/after-finish-password-recovery.xml similarity index 100% rename from ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/after-finish-password-recover.xml rename to ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/after-finish-password-recovery.xml diff --git a/ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/finish-password-recover.xml b/ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/finish-password-recovery.xml similarity index 100% rename from ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/finish-password-recover.xml rename to ccm-core/src/test/resources/datasets/org/libreccm/security/ChallengeManagerTest/finish-password-recovery.xml