From 81fd867e31b880dd1e7fa8e0ab223e107df3bb75 Mon Sep 17 00:00:00 2001 From: jensp Date: Fri, 8 Apr 2016 17:54:17 +0000 Subject: [PATCH] CCM NG: - User registration and password recover now work - Improvements for ChallengeManager - Groups and roles assigned to a user are now shown in in the user details view - Groups assigned to a user can be edited git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3981 8810af33-2d31-482b-a856-94f89814c4df --- .../ui/admin/usersgroupsroles/UserAdmin.java | 335 +++++++++++++++--- .../UserGroupsRolesTableModel.java | 170 +++++++++ .../UserGroupsRolesTableModelBuilder.java | 63 ++++ .../UserPropertySheetModel.java | 5 +- .../ui/admin/usersgroupsroles/UsersTable.java | 2 +- .../arsdigita/ui/login/LoginConstants.java | 2 + .../com/arsdigita/ui/login/LoginServlet.java | 21 +- .../ui/login/RecoverPasswordForm.java | 13 +- .../arsdigita/ui/login/ResetPasswordForm.java | 33 +- .../ui/login/UserAccountActivationForm.java | 26 +- .../core/AbstractEntityRepository.java | 18 +- .../libreccm/l10n/GlobalizationHelper.java | 20 +- .../libreccm/security/ChallengeManager.java | 42 ++- .../org/libreccm/security/EmailTemplates.java | 81 ++++- .../java/org/libreccm/security/Group.java | 39 +- .../org/libreccm/security/GroupManager.java | 1 + .../libreccm/security/OneTimeAuthManager.java | 6 +- .../security/OneTimeAuthTokenCleaner.java | 11 +- .../main/java/org/libreccm/security/User.java | 9 +- .../org/libreccm/security/UserManager.java | 38 +- .../org/libreccm/security/UserRepository.java | 8 +- .../ui/admin/AdminResources.properties | 9 + .../ui/admin/AdminResources_de.properties | 9 + .../ui/admin/AdminResources_en.properties | 9 + .../ui/admin/AdminResources_fr.properties | 9 + .../ui/login/LoginResources.properties | 6 + .../ui/login/LoginResources_de.properties | 6 + .../ui/login/LoginResources_en.properties | 6 + .../ui/login/LoginResources_fr.properties | 6 + 29 files changed, 868 insertions(+), 135 deletions(-) create mode 100644 ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserGroupsRolesTableModel.java create mode 100644 ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserGroupsRolesTableModelBuilder.java 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 ef8d433f0..c59326c3b 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 @@ -20,12 +20,12 @@ package com.arsdigita.ui.admin.usersgroupsroles; import com.arsdigita.bebop.ActionLink; import com.arsdigita.bebop.BoxPanel; +import com.arsdigita.bebop.ColumnPanel; import com.arsdigita.bebop.Component; import com.arsdigita.bebop.ControlLink; import com.arsdigita.bebop.Form; import com.arsdigita.bebop.FormData; import com.arsdigita.bebop.FormProcessException; -import com.arsdigita.bebop.FormSection; import com.arsdigita.bebop.Label; import com.arsdigita.bebop.Page; import com.arsdigita.bebop.PageState; @@ -49,16 +49,30 @@ import com.arsdigita.bebop.table.TableCellRenderer; import com.arsdigita.bebop.table.TableColumn; import com.arsdigita.bebop.table.TableColumnModel; import com.arsdigita.globalization.GlobalizedMessage; +import com.arsdigita.util.UncheckedWrapperException; 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.Group; +import org.libreccm.security.GroupManager; +import org.libreccm.security.GroupRepository; import org.libreccm.security.User; import org.libreccm.security.UserManager; import org.libreccm.security.UserRepository; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TooManyListenersException; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.stream.IntStream; +import java.util.stream.StreamSupport; + import javax.mail.MessagingException; import static com.arsdigita.ui.admin.AdminUiConstants.*; @@ -86,6 +100,7 @@ public class UserAdmin extends BoxPanel { // private final UserDetails userDetails; private final BoxPanel userDetails; private final Form emailForm; + private final Form editGroupMembershipsForm; private final Form newUserForm; public UserAdmin() { @@ -132,17 +147,6 @@ public class UserAdmin extends BoxPanel { add(usersTablePanel); -// final Text text = new Text(); -// text.setPrintListener((final PrintEvent e) -> { -// final Text target = (Text) e.getTarget(); -// final PageState state = e.getPageState(); -// if (selectedUserId.isSelected(state)) { -// target.setText(selectedUserId.getSelectedKey(state)); -// } -// }); -// add(text); -// userDetails = new UserDetails(this, selectedUserId); -// add(new UserDetails(this, selectedUserId)); userDetails = new BoxPanel(); userDetails.setIdAttr("userDetails"); @@ -533,6 +537,71 @@ public class UserAdmin extends BoxPanel { userDetails.add(emailTable); + final Table groupsRolesTable = new Table(); + groupsRolesTable.setModelBuilder(new UserGroupsRolesTableModelBuilder( + selectedUserId)); + final TableColumnModel groupsRolesColModel = groupsRolesTable + .getColumnModel(); + groupsRolesColModel.add(new TableColumn( + UserGroupsRolesTableModel.COL_LABEL)); + groupsRolesColModel + .add(new TableColumn(UserGroupsRolesTableModel.COL_VALUE)); + groupsRolesColModel.add( + new TableColumn(UserGroupsRolesTableModel.COL_ACTION)); + groupsRolesColModel.get(UserGroupsRolesTableModel.COL_ACTION) + .setCellRenderer(new TableCellRenderer() { + + @Override + public Component getComponent(final Table table, + final PageState state, + final Object value, + final boolean isSelected, + final Object key, + final int row, + final int column) { + switch (row) { + case UserGroupsRolesTableModel.ROW_GROUPS: { + return new ControlLink((Component) value); + } + case UserGroupsRolesTableModel.ROW_ROLES: { + return new ControlLink((Component) value); + } + case UserGroupsRolesTableModel.ROW_ALL_ROLES: + return new Text(""); + default: + throw new IllegalArgumentException(); + } + } + + }); + groupsRolesTable.addTableActionListener(new TableActionListener() { + + @Override + public void cellSelected(final TableActionEvent event) { + final int selectedRow = Integer.parseInt((String) event + .getRowKey()); + final PageState state = event.getPageState(); + + switch (selectedRow) { + case UserGroupsRolesTableModel.ROW_GROUPS: + showEditGroupMembershipsForm(state); + break; + case UserGroupsRolesTableModel.ROW_ROLES: + //ToDo + break; + } + } + + @Override + public void headSelected(final TableActionEvent event) { + //Nothing + } + + } + ); + + userDetails.add(groupsRolesTable); + final ActionLink addEmailLink = new ActionLink(new GlobalizedMessage( "ui.admin.user.email_addresses.add", ADMIN_BUNDLE)); addEmailLink.addActionListener(e -> { @@ -541,9 +610,6 @@ public class UserAdmin extends BoxPanel { userDetails.add(addEmailLink); emailForm = new Form("email_form"); -// emailForm.add(new Label(new GlobalizedMessage( -// "ui.admin.user.email_form.address", -// ADMIN_BUNDLE))); final TextField emailFormAddress = new TextField("email_form_address"); emailFormAddress.setLabel(new GlobalizedMessage( "ui.admin.user.email_form.address", ADMIN_BUNDLE)); @@ -668,13 +734,17 @@ public class UserAdmin extends BoxPanel { add(userDetails); + editGroupMembershipsForm = buildEditGroupMembershipsForm(); + add(editGroupMembershipsForm); + newUserForm = buildNewUserForm(); + add(newUserForm); } - + private void setBasicProperties() { - setIdAttr("userAdmin"); + setIdAttr("userAdmin"); } - + private Form buildNewUserForm() { final Form form = new Form("new_user_form"); @@ -722,11 +792,24 @@ public class UserAdmin extends BoxPanel { .addValidationListener(new StringLengthValidationListener(256)); form.add(emailField); - final FormSection setPasswordSection = new FormSection(new BoxPanel( - BoxPanel.VERTICAL)); - setPasswordSection.setLabel(new GlobalizedMessage( - "ui.admin.new_user_form.password_options.set_password.label", - ADMIN_BUNDLE)); + final String passwordOptions = "passwordOptions"; + final String optionSetPassword = "setPassword"; + final String optionSendPassword = "sendPassword"; + final RadioGroup passwordOptionsGroup = new RadioGroup(passwordOptions); + final Option sendPasswordOption = new Option( + optionSendPassword, + new Label(new GlobalizedMessage( + "ui.admin.new_user_form.password_options.send_password.label", + ADMIN_BUNDLE))); + passwordOptionsGroup.addOption(sendPasswordOption); + final Option setPasswordOption = new Option( + optionSetPassword, + new Label(new GlobalizedMessage( + "ui.admin.new_user_form.password_options.set_password", + ADMIN_BUNDLE))); + passwordOptionsGroup.addOption(setPasswordOption); + form.add(passwordOptionsGroup); + final String password = "password"; final String passwordConfirmation = "passwordConfirmation"; final Password passwordField = new Password(password); @@ -738,7 +821,7 @@ public class UserAdmin extends BoxPanel { passwordField.addValidationListener(new NotEmptyValidationListener()); passwordField.addValidationListener(new StringLengthValidationListener( 256)); - setPasswordSection.add(passwordField); + form.add(passwordField); final Password passwordConfirmationField = new Password( passwordConfirmation); passwordConfirmationField.setLabel(new GlobalizedMessage( @@ -750,27 +833,7 @@ public class UserAdmin extends BoxPanel { new NotEmptyValidationListener()); passwordConfirmationField.addValidationListener( new StringLengthValidationListener(256)); - setPasswordSection.add(passwordConfirmationField); - - final String passwordOptions = "passwordOptions"; - final String optionSetPassword = "setPassword"; - final String optionSendPassword = "sendPassword"; - final RadioGroup passwordOptionsGroup = new RadioGroup(passwordOptions); -// final Option setPasswordOption = new Option( -// optionSetPassword, -// new Label(new GlobalizedMessage( -// "ui.admin.new_user_form.password_options.set_password", -// ADMIN_BUNDLE))); - final Option setPasswordOption = new Option( - optionSetPassword, setPasswordSection); - passwordOptionsGroup.addOption(setPasswordOption); - final Option sendPasswordOption = new Option( - optionSendPassword, - new Label(new GlobalizedMessage( - "ui.admin.new_user_form.password_options.send_password.label", - ADMIN_BUNDLE))); - passwordOptionsGroup.addOption(sendPasswordOption); - form.add(passwordOptionsGroup); + form.add(passwordConfirmationField); final SaveCancelSection saveCancelSection = new SaveCancelSection(); form.add(saveCancelSection); @@ -871,6 +934,156 @@ public class UserAdmin extends BoxPanel { return form; } + private Form buildEditGroupMembershipsForm() { + final Form form = new Form("edit-usergroupmemberships-form"); + + final BoxPanel links = new BoxPanel(BoxPanel.VERTICAL); + final Label header = new Label(e -> { + final PageState state = e.getPageState(); + final Label target = (Label) e.getTarget(); + + final String userIdStr = selectedUserId.getSelectedKey(state); + final UserRepository userRepository = CdiUtil.createCdiUtil() + .findBean(UserRepository.class); + final User user = userRepository.findById(Long.parseLong(userIdStr)); + + target.setLabel(new GlobalizedMessage( + "ui.admin.user.edit_group_memberships", ADMIN_BUNDLE, + new String[]{user.getName()})); + }); + links.add(header); + + final ActionLink backLink = new ActionLink(new GlobalizedMessage( + "ui.admin.user.edit_group_memberships.back_to_user_details", + ADMIN_BUNDLE)); + backLink.addActionListener(e -> { + closeEditGroupMembershipsForm(e.getPageState()); + }); + links.add(backLink); + + form.add(links); + + final String groupsSelector = "groupsselector"; + final CheckboxGroup groups = new CheckboxGroup(groupsSelector); + try { + groups.addPrintListener(e -> { +// final PageState state = e.getPageState(); + final CheckboxGroup target = (CheckboxGroup) e.getTarget(); + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); +// final UserRepository userRepository = cdiUtil.findBean( +// UserRepository.class); + final GroupRepository groupRepository = cdiUtil.findBean( + GroupRepository.class); + + target.clearOptions(); + + final SortedSet allGroups = new TreeSet<>( + (g1, g2) -> { + return g1.getName().compareTo(g2.getName()); + }); + allGroups.addAll(groupRepository.findAll()); +// final List assignedGroups = new ArrayList<>(); +// final User user = userRepository.findById(Long.parseLong( +// selectedUserId.getSelectedKey(state))); +// user.getGroupMemberships().forEach(m -> { +// assignedGroups.add(m.getGroup()); +// }); + + allGroups.forEach(g -> { + final Option option = new Option( + Long.toString(g.getPartyId()), new Text(g.getName())); + target.addOption(option); +// if (assignedGroups.contains(g)) { +// target.setOptionSelected(option); +// } + }); + }); + } catch (TooManyListenersException ex) { + throw new UncheckedWrapperException(ex); + } + form.add(groups); + + final SaveCancelSection saveCancelSection = new SaveCancelSection(); + form.add(saveCancelSection); + + form.addInitListener(e -> { + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final UserRepository userRepository = cdiUtil.findBean( + UserRepository.class); + + final PageState state = e.getPageState(); + + final User user = userRepository.findById(Long.parseLong( + selectedUserId.getSelectedKey(state))); + final List assignedGroups = new ArrayList<>(); + user.getGroupMemberships().forEach(m -> { + assignedGroups.add(m.getGroup()); + }); + + final String[] selectedGroups = new String[assignedGroups.size()]; + IntStream.range(0, assignedGroups.size()).forEach(i -> { + selectedGroups[i] = Long.toString(assignedGroups.get(i) + .getPartyId()); + }); + + groups.setValue(state, selectedGroups); + }); + + form.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 GroupRepository groupRepository = cdiUtil.findBean( + GroupRepository.class); + final GroupManager groupManager = cdiUtil.findBean( + GroupManager.class); + + final String[] selectedGroupIds = (String[]) data.get( + groupsSelector); + + final User user = userRepository.findById(Long.parseLong( + selectedUserId.getSelectedKey(state))); + final List selectedGroups = new ArrayList<>(); + if (selectedGroupIds != null) { + for (String selectedGroupId : selectedGroupIds) { + final Group group = groupRepository.findById(Long + .parseLong( + selectedGroupId)); + selectedGroups.add(group); + } + } + final List assignedGroups = new ArrayList<>(); + user.getGroupMemberships().forEach(m -> { + assignedGroups.add(m.getGroup()); + }); + + //First check for newly added groups + selectedGroups.forEach(g -> { + if (!assignedGroups.contains(g)) { + groupManager.addMemberToGroup(user, g); + } + }); + + //Than check for removed groups + assignedGroups.forEach(g -> { + if (!selectedGroups.contains(g)) { + final Group group = groupRepository.findById( + g.getPartyId()); + groupManager.removeMemberFromGroup(user, group); + } + }); + } + + closeEditGroupMembershipsForm(state); + }); + + return form; + } + @Override public void register(final Page page) { super.register(page); @@ -883,6 +1096,7 @@ public class UserAdmin extends BoxPanel { page.setVisibleDefault(userEditForm, false); page.setVisibleDefault(passwordSetForm, false); page.setVisibleDefault(emailForm, false); + page.setVisibleDefault(editGroupMembershipsForm, false); page.setVisibleDefault(newUserForm, false); } @@ -892,6 +1106,7 @@ public class UserAdmin extends BoxPanel { userEditForm.setVisible(state, false); passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); + editGroupMembershipsForm.setVisible(state, false); newUserForm.setVisible(state, false); } @@ -902,6 +1117,7 @@ public class UserAdmin extends BoxPanel { userEditForm.setVisible(state, false); passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); + editGroupMembershipsForm.setVisible(state, false); newUserForm.setVisible(state, false); } @@ -911,6 +1127,7 @@ public class UserAdmin extends BoxPanel { userEditForm.setVisible(state, true); passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); + editGroupMembershipsForm.setVisible(state, false); newUserForm.setVisible(state, false); } @@ -920,6 +1137,7 @@ public class UserAdmin extends BoxPanel { userEditForm.setVisible(state, false); passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); + editGroupMembershipsForm.setVisible(state, false); newUserForm.setVisible(state, false); } @@ -929,6 +1147,7 @@ public class UserAdmin extends BoxPanel { userEditForm.setVisible(state, false); passwordSetForm.setVisible(state, true); emailForm.setVisible(state, false); + editGroupMembershipsForm.setVisible(state, false); newUserForm.setVisible(state, false); } @@ -938,6 +1157,7 @@ public class UserAdmin extends BoxPanel { userEditForm.setVisible(state, false); passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); + editGroupMembershipsForm.setVisible(state, false); newUserForm.setVisible(state, false); } @@ -947,6 +1167,7 @@ public class UserAdmin extends BoxPanel { userEditForm.setVisible(state, false); passwordSetForm.setVisible(state, false); emailForm.setVisible(state, true); + editGroupMembershipsForm.setVisible(state, false); newUserForm.setVisible(state, false); } @@ -957,6 +1178,28 @@ public class UserAdmin extends BoxPanel { userEditForm.setVisible(state, false); passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); + editGroupMembershipsForm.setVisible(state, false); + newUserForm.setVisible(state, false); + } + + protected void showEditGroupMembershipsForm(final PageState state) { + usersTablePanel.setVisible(state, false); + userDetails.setVisible(state, false); + userEditForm.setVisible(state, false); + passwordSetForm.setVisible(state, false); + emailForm.setVisible(state, false); + editGroupMembershipsForm.setVisible(state, true); + newUserForm.setVisible(state, false); + } + + protected void closeEditGroupMembershipsForm(final PageState state) { + selectedEmailAddress.clearSelection(state); + usersTablePanel.setVisible(state, false); + userDetails.setVisible(state, true); + userEditForm.setVisible(state, false); + passwordSetForm.setVisible(state, false); + emailForm.setVisible(state, false); + editGroupMembershipsForm.setVisible(state, false); newUserForm.setVisible(state, false); } @@ -967,6 +1210,7 @@ public class UserAdmin extends BoxPanel { userEditForm.setVisible(state, false); passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); + editGroupMembershipsForm.setVisible(state, false); newUserForm.setVisible(state, true); } @@ -977,6 +1221,7 @@ public class UserAdmin extends BoxPanel { userEditForm.setVisible(state, false); passwordSetForm.setVisible(state, false); emailForm.setVisible(state, false); + editGroupMembershipsForm.setVisible(state, false); newUserForm.setVisible(state, false); } diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserGroupsRolesTableModel.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserGroupsRolesTableModel.java new file mode 100644 index 000000000..f317258c3 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserGroupsRolesTableModel.java @@ -0,0 +1,170 @@ +/* + * 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.admin.usersgroupsroles; + +import com.arsdigita.bebop.Label; +import com.arsdigita.bebop.table.TableModel; +import com.arsdigita.globalization.GlobalizedMessage; + +import org.libreccm.security.User; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static com.arsdigita.ui.admin.AdminUiConstants.*; + +/** + * + * @author Jens Pelzetter + */ +public class UserGroupsRolesTableModel implements TableModel { + + protected static final int COL_LABEL = 0; + protected static final int COL_VALUE = 1; + protected static final int COL_ACTION = 2; + + protected static final int ROW_GROUPS = 0; + protected static final int ROW_ROLES = 1; + protected static final int ROW_ALL_ROLES = 2; + + private int row = -1; + + private final User user; + + public UserGroupsRolesTableModel(final User user) { + this.user = user; + } + + @Override + public int getColumnCount() { + return 3; + } + + @Override + public boolean nextRow() { + row++; + return row < 3; + } + + @Override + public Object getElementAt(final int columnIndex) { + switch (row) { + case ROW_GROUPS: + return buildGroupRow(columnIndex); + case ROW_ROLES: + return buildRolesRow(columnIndex); + case ROW_ALL_ROLES: + return buildAllRolesRow(columnIndex); + default: + throw new IllegalArgumentException(); + } + } + + @Override + public Object getKeyAt(final int columnIndex) { + return row; + } + + private Object buildGroupRow(final int columnIndex) { + switch (columnIndex) { + case COL_LABEL: + return new Label(new GlobalizedMessage("ui.admin.user.groups", + ADMIN_BUNDLE)); + case COL_VALUE: + final List groupNames = new ArrayList<>(); + user.getGroupMemberships().forEach(m -> { + groupNames.add(m.getGroup().getName()); + }); + + groupNames.sort((name1, name2) -> { + return name1.compareTo(name2); + }); + + return String.join( + ", ", groupNames.toArray(new String[groupNames.size()])); + + case COL_ACTION: + return new Label(new GlobalizedMessage( + "ui.admin.user.groups.edit", ADMIN_BUNDLE)); + default: + throw new IllegalArgumentException(); + } + } + + private Object buildRolesRow(final int columnIndex) { + switch (columnIndex) { + case COL_LABEL: + return new Label(new GlobalizedMessage("ui.admin.user.roles", + ADMIN_BUNDLE)); + case COL_VALUE: + final List roleNames = new ArrayList<>(); + user.getRoleMemberships().forEach(m -> { + roleNames.add(m.getRole().getName()); + }); + + roleNames.sort((name1, name2) -> { + return name1.compareTo(name2); + }); + + return String.join( + ", ", roleNames.toArray(new String[roleNames.size()])); + + case COL_ACTION: + return new Label(new GlobalizedMessage( + "ui.admin.user.roles.edit", ADMIN_BUNDLE)); + default: + throw new IllegalArgumentException(); + } + } + + private Object buildAllRolesRow(final int columnIndex) { + switch (columnIndex) { + case COL_LABEL: + return new Label(new GlobalizedMessage( + "ui.admin.user.all_roles", ADMIN_BUNDLE)); + case COL_VALUE: + final Set roleNames = new HashSet<>(); + user.getRoleMemberships().forEach(m -> { + roleNames.add(m.getRole().getName()); + }); + + user.getGroupMemberships().forEach(m -> { + m.getGroup().getRoleMemberships().forEach(r -> { + roleNames.add(r.getRole().getName()); + }); + }); + + final List allRoleNames = new ArrayList<>(roleNames); + allRoleNames.sort((name1, name2) -> { + return name1.compareTo(name2); + }); + + return String.join(", ", allRoleNames.toArray( + new String[allRoleNames.size()])); + + case COL_ACTION: + return ""; + default: + throw new IllegalArgumentException(); + } + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserGroupsRolesTableModelBuilder.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserGroupsRolesTableModelBuilder.java new file mode 100644 index 000000000..67ada7462 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserGroupsRolesTableModelBuilder.java @@ -0,0 +1,63 @@ +/* + * 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.admin.usersgroupsroles; + +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.ParameterSingleSelectionModel; +import com.arsdigita.bebop.Table; +import com.arsdigita.bebop.table.TableModel; +import com.arsdigita.bebop.table.TableModelBuilder; +import com.arsdigita.util.LockableImpl; + +import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.security.User; +import org.libreccm.security.UserRepository; + +/** + * + * @author Jens Pelzetter + */ +public class UserGroupsRolesTableModelBuilder extends LockableImpl + implements TableModelBuilder{ + + private final ParameterSingleSelectionModel selectedUserId; + + public UserGroupsRolesTableModelBuilder( + final ParameterSingleSelectionModel selectedUserId) { + this.selectedUserId = selectedUserId; + } + + @Override + public TableModel makeModel(final Table table, final PageState state) { + final String userIdStr = selectedUserId.getSelectedKey(state); + final User selectedUser ; + if (userIdStr == null || userIdStr.isEmpty()) { + selectedUser = null; + } else { + final UserRepository userRepository = CdiUtil.createCdiUtil() + .findBean(UserRepository.class); + final long userId = Long.parseLong(userIdStr); + selectedUser = userRepository.findById(userId); + } + + return new UserGroupsRolesTableModel(selectedUser); + } + + +} diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserPropertySheetModel.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserPropertySheetModel.java index de8423889..8da989c14 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserPropertySheetModel.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UserPropertySheetModel.java @@ -20,8 +20,10 @@ package com.arsdigita.ui.admin.usersgroupsroles; import com.arsdigita.bebop.PropertySheetModel; import com.arsdigita.globalization.GlobalizedMessage; + import java.util.Arrays; import java.util.Iterator; + import org.libreccm.security.User; import static com.arsdigita.ui.admin.AdminUiConstants.*; @@ -38,7 +40,7 @@ public class UserPropertySheetModel implements PropertySheetModel { GIVEN_NAME, PASSWORD_SET, BANNED, - PASSWORD_RESET_REQUIRED, + PASSWORD_RESET_REQUIRED } private final User selectedUser; @@ -102,5 +104,4 @@ public class UserPropertySheetModel implements PropertySheetModel { return ""; } } - } diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UsersTable.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UsersTable.java index f0c0531df..9a46b7f17 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UsersTable.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/usersgroupsroles/UsersTable.java @@ -159,7 +159,7 @@ public class UsersTable extends Table { final UserRepository userRepository = CdiUtil.createCdiUtil() .findBean(UserRepository.class); if (filterTerm == null || filterTerm.isEmpty()) { - users = userRepository.findAll(); + users = userRepository.findAllOrderdByUsername(); LOGGER.debug("Found {} users in database.", users.size()); } else { users = userRepository.filtered(filterTerm); 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 c1676c831..f85e8e78e 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 @@ -109,6 +109,8 @@ public interface LoginConstants { // Don't modify without adapting instantiation in Loader class and // updating existing databases (table applications)! public static final String LOGIN_PAGE_URL = "/register/"; + + public static final String LOGIN_PATH = "/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 f21b0119f..c8c394ac0 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 @@ -108,16 +108,25 @@ public class LoginServlet extends BebopApplicationServlet { /** * 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 + * (using a previously requested one time authentication token). Ends with + * "/" because it is a servlet/directory */ - public static final String RESET_USER_PASSWORD_PATH_INFO = "/reset-password"; + 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 + * page (not implemted yet!). Ends with "/" because it is a + * servlet/directory */ - public static final String VERIFY_EMAIL = "/verify-email/"; + public static final String VERIFY_EMAIL_PATH_INFO = "/verify-email/"; + + /** + * PathInfo into the Login application to access the confirm email + * page which allows the user to confirm his/her email address by submitting + * a previously requested one time authentication token (not implemented!). + * Ends with "/" because it is a servlet/directory + */ + public static final String CONFIRM_EMAIL_PATH_INFO = "/verify-email/"; /** * PathInfo into the Login application to access the (optional) explain @@ -239,7 +248,7 @@ public class LoginServlet extends BebopApplicationServlet { 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()); 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 index a7c9f46d0..1b66dc710 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/login/RecoverPasswordForm.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/RecoverPasswordForm.java @@ -32,6 +32,8 @@ import com.arsdigita.bebop.parameters.NotEmptyValidationListener; import com.arsdigita.bebop.parameters.StringLengthValidationListener; import com.arsdigita.globalization.GlobalizedMessage; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.libreccm.cdi.utils.CdiUtil; import org.libreccm.security.ChallengeManager; import org.libreccm.security.User; @@ -48,6 +50,9 @@ import static com.arsdigita.ui.login.LoginServlet.*; */ public class RecoverPasswordForm extends Form { + private static final Logger LOGGER = LogManager.getLogger( + RecoverPasswordForm.class); + private static final String EMAIL = "email"; private BoxPanel formPanel; @@ -93,6 +98,7 @@ public class RecoverPasswordForm extends Form { LOGIN_BUNDLE)), LOGIN_PAGE_URL + RESET_USER_PASSWORD_PATH_INFO); finishedMessagePanel.add(link); + add(finishedMessagePanel); } private void addListeners() { @@ -155,9 +161,14 @@ public class RecoverPasswordForm extends Form { } } + if (user == null) { + LOGGER.warn( + "Password recover requested for not existing user {}.", + data.get(EMAIL)); + } + formPanel.setVisible(state, false); finishedMessagePanel.setVisible(state, true); - data.clear(); } } ); 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 index c8ca37920..9a04b1988 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/login/ResetPasswordForm.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/ResetPasswordForm.java @@ -23,6 +23,7 @@ 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; @@ -31,6 +32,7 @@ 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.web.URL; import org.libreccm.cdi.utils.CdiUtil; import org.libreccm.configuration.ConfigurationManager; @@ -45,6 +47,8 @@ import org.libreccm.security.UserRepository; import java.util.List; +import javax.servlet.http.HttpServletRequest; + import static com.arsdigita.ui.login.LoginConstants.*; /** @@ -115,10 +119,10 @@ public class ResetPasswordForm extends Form { passwordConfirmation = new Password(PASSWORD_CONFIRMATION); passwordConfirmation.setLabel(new GlobalizedMessage( - "login.form.reset_password.password_confirmation.label", + "login.form.reset_password.password_confirmation.label", LOGIN_BUNDLE)); passwordConfirmation.setHint(new GlobalizedMessage( - "login.form.reset_password.password_confirmation.hint", + "login.form.reset_password.password_confirmation.hint", LOGIN_BUNDLE)); passwordConfirmation.setMaxLength(256); passwordConfirmation.setSize(32); @@ -136,12 +140,32 @@ public class ResetPasswordForm extends Form { successPanel = new BoxPanel(BoxPanel.VERTICAL); successPanel.add(new Label(new GlobalizedMessage( "login.form.reset_password.scucess", LOGIN_BUNDLE))); + successPanel.add(new Link(new Label(new GlobalizedMessage( + "login.form.reset_password.scucess.login", + LOGIN_BUNDLE)), + URL.there(LOGIN_PAGE_URL, null).getURL())); add(successPanel); } private void addListeners() { + addInitListener(e -> { + final PageState state = e.getPageState(); + final HttpServletRequest request = state.getRequest(); + + final String paramEmail = request.getParameter("email"); + final String paramToken = request.getParameter("token"); + + if (paramEmail != null) { + email.setValue(state, paramEmail); + } + + if (paramToken != null) { + authToken.setValue(state, paramToken); + } + }); + addValidationListener(e -> { final PageState state = e.getPageState(); @@ -177,7 +201,7 @@ public class ResetPasswordForm extends Form { final List tokens = oneTimeAuthManager .retrieveForUser( - user, OneTimeAuthTokenPurpose.ACCOUNT_ACTIVATION); + user, OneTimeAuthTokenPurpose.RECOVER_PASSWORD); boolean result = false; for (OneTimeAuthToken token : tokens) { @@ -234,13 +258,12 @@ public class ResetPasswordForm extends Form { throw new FormProcessException( "Failed to finish password recovery.", new GlobalizedMessage( - "login.form.account_activation.error.failed"), + "login.form.password_reset.error.failed"), ex); } formPanel.setVisible(state, false); successPanel.setVisible(state, true); - data.clear(); } }); } 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 index 6579c98dd..e36fd500d 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/login/UserAccountActivationForm.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/UserAccountActivationForm.java @@ -23,6 +23,7 @@ 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; @@ -30,6 +31,7 @@ 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.web.URL; import org.libreccm.cdi.utils.CdiUtil; import org.libreccm.configuration.ConfigurationManager; @@ -44,6 +46,8 @@ import org.libreccm.security.UserRepository; import java.util.List; +import javax.servlet.http.HttpServletRequest; + import static com.arsdigita.ui.login.LoginConstants.*; /** @@ -102,11 +106,30 @@ public class UserAccountActivationForm extends Form { successPanel = new BoxPanel(BoxPanel.VERTICAL); successPanel.add(new Label(new GlobalizedMessage( "login.form.account_activation.success", LOGIN_BUNDLE))); - + successPanel.add(new Link(new Label( + new GlobalizedMessage("login.form.account_activation.success.login", + LOGIN_BUNDLE)), + URL.there(LOGIN_PAGE_URL, null).getURL())); add(successPanel); } private void addListeners() { + addInitListener(e -> { + final PageState state = e.getPageState(); + final HttpServletRequest request = state.getRequest(); + + final String paramEmail = request.getParameter("email"); + final String paramToken = request.getParameter("token"); + + if (paramEmail != null) { + email.setValue(state, paramEmail); + } + + if (paramToken != null) { + authToken.setValue(state, paramToken); + } + }); + addValidationListener(e -> { final PageState state = e.getPageState(); @@ -193,7 +216,6 @@ public class UserAccountActivationForm extends Form { formPanel.setVisible(state, false); successPanel.setVisible(state, true); - data.clear(); } }); } diff --git a/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java b/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java index 49ef0b30f..a78af5860 100644 --- a/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java +++ b/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java @@ -32,6 +32,7 @@ import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Order; import javax.persistence.criteria.Root; import javax.transaction.Transactional; @@ -208,8 +209,7 @@ public abstract class AbstractEntityRepository { public List findAll(final String entityGraphName) { @SuppressWarnings("unchecked") final EntityGraph entityGraph = (EntityGraph) entityManager - .getEntityGraph( - entityGraphName); + .getEntityGraph(entityGraphName); return findAll(entityGraph); } @@ -237,8 +237,8 @@ public abstract class AbstractEntityRepository { if (hasDefaultEntityGraph()) { return executeCriteriaQuery(criteriaQuery, getDefaultEntityGraph()); } else { - final TypedQuery query = entityManager.createQuery(criteriaQuery); - return query.getResultList(); + final TypedQuery query = entityManager.createQuery(criteriaQuery); + return query.getResultList(); } } @@ -286,16 +286,16 @@ public abstract class AbstractEntityRepository { } /** - * Overwrite this method to initialise new entities with default values. - * One example is assigning a (random) UUID to new entity which implements - * the {@link Identifiable} interface. - * + * Overwrite this method to initialise new entities with default values. One + * example is assigning a (random) UUID to new entity which implements the + * {@link Identifiable} interface. + * * @param entity The entity to init. */ public void initNewEntity(final E entity) { //Empty default implementation } - + /** * Deletes an entity from the database. * diff --git a/ccm-core/src/main/java/org/libreccm/l10n/GlobalizationHelper.java b/ccm-core/src/main/java/org/libreccm/l10n/GlobalizationHelper.java index d51c1cb87..f216636dc 100644 --- a/ccm-core/src/main/java/org/libreccm/l10n/GlobalizationHelper.java +++ b/ccm-core/src/main/java/org/libreccm/l10n/GlobalizationHelper.java @@ -44,7 +44,7 @@ import javax.servlet.http.HttpSession; * language. * *
  • - * If there is a parameter {@code lang} for the current request, use that + * If there is a parameter {@code lang} for the current request, use that * language and store the selected language in the session. *
  • *
  • @@ -54,10 +54,11 @@ import javax.servlet.http.HttpSession; *
  • * * - * A historic note: This CDI bean replaces the old {@code GlobalizationHelper} class which used - * static methods and relied on the old {@code DispatcherHelper} for getting the - * current request. In a CDI environment we can simply inject the current request - * and don't need to bother with static methods etc. + * A historic note: This CDI bean replaces the old {@code GlobalizationHelper} + * class which used static methods and relied on the old + * {@code DispatcherHelper} for getting the current request. In a CDI + * environment we can simply inject the current request and don't need to bother + * with static methods etc. * * @author Jens Pelzetter */ @@ -73,14 +74,13 @@ public class GlobalizationHelper { private ConfigurationManager confManager; // private final KernelConfig kernelConfig; - // public GlobalizationHelper() { // kernelConfig = confManager.findConfiguration(KernelConfig.class); // } - public Locale getNegotiatedLocale() { - final KernelConfig kernelConfig = confManager.findConfiguration(KernelConfig.class); - + final KernelConfig kernelConfig = confManager.findConfiguration( + KernelConfig.class); + Locale preferred = new Locale(kernelConfig.getDefaultLanguage()); final Locale selectedLocale = getSelectedLocale(); @@ -91,7 +91,7 @@ public class GlobalizationHelper { while (acceptedLocales.hasMoreElements()) { final Locale current = acceptedLocales.nextElement(); if (kernelConfig.hasLanguage(current.getLanguage())) { - preferred = current; + preferred = new Locale(current.getLanguage()); break; } } 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 c87a47bad..6a16608fc 100644 --- a/ccm-core/src/main/java/org/libreccm/security/ChallengeManager.java +++ b/ccm-core/src/main/java/org/libreccm/security/ChallengeManager.java @@ -20,8 +20,13 @@ package org.libreccm.security; import com.arsdigita.kernel.KernelConfig; import com.arsdigita.mail.Mail; +import com.arsdigita.ui.login.LoginConstants; +import com.arsdigita.web.ParameterMap; +import com.arsdigita.web.URL; import org.apache.commons.lang.text.StrSubstitutor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.libreccm.configuration.ConfigurationManager; import org.libreccm.configuration.LocalizedStringSetting; import org.libreccm.l10n.GlobalizationHelper; @@ -37,6 +42,9 @@ import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.mail.MessagingException; import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; + +import static com.arsdigita.ui.login.LoginServlet.*; /** * A service class for managing several so called challenges. These challenges @@ -73,6 +81,9 @@ import javax.servlet.ServletContext; @RequestScoped public class ChallengeManager { + private static final Logger LOGGER = LogManager.getLogger( + ChallengeManager.class); + @Inject private GlobalizationHelper globalizationHelper; @@ -89,7 +100,7 @@ public class ChallengeManager { private UserManager userManager; @Inject - private ServletContext servletContext; + private HttpServletRequest request; public String createEmailVerification(final User user) { if (user == null) { @@ -137,7 +148,7 @@ public class ChallengeManager { public void sendAccountActivation(final User user) throws MessagingException { - final String text = createEmailVerification(user); + final String text = createAccountActivation(user); sendMessage( user, retrieveEmailSubject(OneTimeAuthTokenPurpose.ACCOUNT_ACTIVATION), @@ -172,7 +183,7 @@ public class ChallengeManager { public void sendPasswordRecover(final User user) throws MessagingException { - final String text = createEmailVerification(user); + final String text = createPasswordRecover(user); sendMessage( user, retrieveEmailSubject(OneTimeAuthTokenPurpose.RECOVER_PASSWORD), @@ -211,13 +222,13 @@ public class ChallengeManager { final String path; switch (purpose) { case ACCOUNT_ACTIVATION: - path = "activate-account"; + path = ACTIVATE_ACCOUNT_PATH_INFO; break; case EMAIL_VERIFICATION: - path = "verify-email"; + path = VERIFY_EMAIL_PATH_INFO; break; case RECOVER_PASSWORD: - path = "recover-password"; + path = RESET_USER_PASSWORD_PATH_INFO; break; default: throw new IllegalArgumentException(String.format( @@ -225,17 +236,28 @@ public class ChallengeManager { purpose.toString())); } values.put("link", - String.format("%s/%s/register/%s", - servletContext.getVirtualServerName(), - servletContext.getContextPath(), - path)); + URL.there(request, + LoginConstants.LOGIN_PATH + path, null) + .getURL()); + + final ParameterMap params = new ParameterMap(); + params.setParameter("email", user.getPrimaryEmailAddress().getAddress()); + params.setParameter("token", token.getToken()); + values.put("full_link", + URL.there(request, + LoginConstants.LOGIN_PATH + path, params) + .getURL()); + + values.put("token", token.getToken()); final StrSubstitutor substitutor = new StrSubstitutor(values); return substitutor.replace(template); } private String retrieveEmailSubject(final OneTimeAuthTokenPurpose purpose) { + LOGGER.debug("Retreving email subject..."); final Locale locale = globalizationHelper.getNegotiatedLocale(); + LOGGER.debug("Negoiated locale is {}.", locale.toString()); final EmailTemplates emailTemplates = configurationManager .findConfiguration(EmailTemplates.class); diff --git a/ccm-core/src/main/java/org/libreccm/security/EmailTemplates.java b/ccm-core/src/main/java/org/libreccm/security/EmailTemplates.java index c3ba9f9eb..bc7a2e3d2 100644 --- a/ccm-core/src/main/java/org/libreccm/security/EmailTemplates.java +++ b/ccm-core/src/main/java/org/libreccm/security/EmailTemplates.java @@ -67,17 +67,35 @@ public final class EmailTemplates { "Please follow the following link to finish the email verfication " + "process:\n" + "\n" - + "${link}" - + "\n\n" - + "Please be aware that your verification token expires" + + "${full_link}\n" + + "\n" + + "or go to\n" + + "\n" + + "${link}\n" + + "\n" + + "and enter your email address and the following verification " + + "token:\n" + + "\n" + + "${token}\n" + + "\n" + + "Please be aware that your verification token expires " + "at ${expires_date}."); emailVerificationMail.getValue().addValue( Locale.GERMAN, "Bitte folgen Sie dem folgenden Link, um die Überprüfung ihrer E-" + "Mail-Adresse abzuschließen:\n" + "\n" - + "${link}" - + "\n\n" + + "${full_link}\n" + + "\n" + + "oder rufen Sie\n" + + "\n" + + "${link}\n" + + "\n" + + "auf und geben Sie Ihre E-Mail-Adresse und das folgende Token " + + "ein:\n" + + "\n" + + "${token}\n" + + "\n" + "Bitte beachten Sie, dass Sie den Prozess bis zu folgendem " + "Zeitpunkt abschließen müssen: ${expires_date}"); @@ -95,17 +113,33 @@ public final class EmailTemplates { "Please follow the following link to complete the password recover " + "process:\n" + "\n" - + "${link}" - + "\n\n" + + "${full_link}\n" + + "\n" + + "or go to\n" + + "${link}\n" + + "\n" + + "and enter your email address and the following token:\n" + + "\n" + + "${token}\n" + + "\n" + "Please be aware that you must complete the process until " - + "${expires_date}"); + + "${expires_date}."); passwordRecoverMail.getValue().addValue( Locale.GERMAN, "Bitte folgen Sie dem folgenden Link um ein neues Passwort " + "einzugeben:\n" + "\n" - + "${link}" - + "\n\n" + + "${full_link}\n" + + "\n" + + "oder rufen Sie\n" + + "\n" + + "${link}\n" + + "\n" + + "auf und geben Sie Ihre E-Mail-adresse und der folgende Token" + + "ein:\n" + + "\n" + + "${token}\n" + + "\n" + "Bitte beachten Sie, dass den den Prozess bis zu folgenden " + "Zeitpunkt abschließen müsssen: ${expires_date}"); @@ -122,8 +156,16 @@ public final class EmailTemplates { Locale.ENGLISH, "Please follow the following link to enable your new account:\n" + "\n" - + "${link}" - + "\n\n" + + "${full_link}\n" + + "\n" + + "or got to\n" + + "\n" + + "${link}\n" + + "\n" + + "and enter your email address and the following token:\n" + + "\n" + + "${token}\n" + + "\n" + "Please be aware that you must activate your account before " + "${expires_date}."); accountActivationMail.getValue().addValue( @@ -131,9 +173,18 @@ public final class EmailTemplates { "Bitte folgen Sie den folgendem Link, um ihr Benutzerkonto zu " + "aktivieren:\n" + "\n" - + "${link}" - + "\n\n" - + "Bitte beachten Sie, dass Sie ihr Benutzerkonto spätestens" + + "${full_link}\n" + + "\n" + + "oder rufen Sie\n" + + "\n" + + "${link}\n" + + "\n" + + "auf und geben Sie Ihre E-Mail-Adresse und das folgende Token " + + "ein:\n" + + "\n" + + "${token}\n" + + "\n" + + "Bitte beachten Sie, dass Sie ihr Benutzerkonto spätestens " + "bis zu folgendem Zeitpunkt aktivieren müssen: ${expires_date}"); } diff --git a/ccm-core/src/main/java/org/libreccm/security/Group.java b/ccm-core/src/main/java/org/libreccm/security/Group.java index 286a8f6a1..b181911e0 100644 --- a/ccm-core/src/main/java/org/libreccm/security/Group.java +++ b/ccm-core/src/main/java/org/libreccm/security/Group.java @@ -20,6 +20,8 @@ package org.libreccm.security; import static org.libreccm.core.CoreConstants.*; +import org.libreccm.core.DefaultEntityGraph; + import java.io.Serializable; import java.util.Collections; import java.util.HashSet; @@ -27,8 +29,12 @@ import java.util.Objects; import java.util.Set; import javax.persistence.Entity; +import javax.persistence.NamedAttributeNode; +import javax.persistence.NamedEntityGraph; +import javax.persistence.NamedEntityGraphs; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; +import javax.persistence.NamedSubgraph; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.xml.bind.annotation.XmlElement; @@ -36,12 +42,12 @@ import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; /** - * A group is basically a collection of users. - * - * Group extends the {@link Party} class. Therefore {@link Role}s can be + * A group is basically a collection of users. + * + * Group extends the {@link Party} class. Therefore {@link Role}s can be * assigned to a group. When a {@link Role} is assigned to a group each member * of the group gets the role and the permissions associated with that role. - * + * * @author Jens Pelzetter */ @Entity @@ -53,6 +59,29 @@ import javax.xml.bind.annotation.XmlRootElement; query = "SELECT g FROM Group g " + "WHERE LOWER(g.name) LIKE '%:name%'") }) +@NamedEntityGraphs({ + @NamedEntityGraph( + name = "Group.withMembersAndRoleMemberships", + attributeNodes = { + @NamedAttributeNode( + value = "memberships"), + @NamedAttributeNode( + value = "roleMemberships", + subgraph = "role")}, + subgraphs = { + @NamedSubgraph( + name = "role", + attributeNodes = { + @NamedAttributeNode(value = "role", + subgraph = "permissions") + }), + @NamedSubgraph( + name = "permissions", + attributeNodes = { + @NamedAttributeNode(value = "permissions")}) + }) +}) +@DefaultEntityGraph("Group.withMembersAndRoleMemberships") @XmlRootElement(name = "user-group", namespace = CORE_XML_NS) public class Group extends Party implements Serializable { @@ -118,8 +147,6 @@ public class Group extends Party implements Serializable { public int hashCode() { return super.hashCode(); } - - @Override public String toString(final String data) { diff --git a/ccm-core/src/main/java/org/libreccm/security/GroupManager.java b/ccm-core/src/main/java/org/libreccm/security/GroupManager.java index a90cc6ea9..e4adf3d1a 100644 --- a/ccm-core/src/main/java/org/libreccm/security/GroupManager.java +++ b/ccm-core/src/main/java/org/libreccm/security/GroupManager.java @@ -53,6 +53,7 @@ public class GroupManager { * @param user The user to add to a group. * @param group The group to which the user is added. */ + @Transactional(Transactional.TxType.REQUIRED) public void addMemberToGroup(final User user, final Group group) { if (user == null) { throw new IllegalArgumentException( 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 a25def25b..2d8440fe5 100644 --- a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthManager.java +++ b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthManager.java @@ -205,7 +205,11 @@ public class OneTimeAuthManager { throw new IllegalArgumentException("Can't invalidate a token null"); } - entityManager.remove(token); + //Ensure that we have a none detached instance + final OneTimeAuthToken delete = entityManager.find( + OneTimeAuthToken.class, token.getTokenId()); + + entityManager.remove(delete); } } diff --git a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenCleaner.java b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenCleaner.java index 87bf24fec..dc9857ed6 100644 --- a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenCleaner.java +++ b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenCleaner.java @@ -74,7 +74,10 @@ public class OneTimeAuthTokenCleaner { // final long interval = 60 * 60 * 1000; LOGGER.debug("Creating interval for {} s.", interval / 1000); - timerService.createIntervalTimer(interval, interval, new TimerConfig()); + LOGGER.debug("First run cleaning process will be executed in 5 min."); + timerService.createIntervalTimer(5 * 60 * 1000, + interval, + new TimerConfig()); } @Timeout @@ -88,7 +91,7 @@ public class OneTimeAuthTokenCleaner { LOGGER.debug("Found {} one time auth tokens.", tokens.size()); if (LOGGER.isDebugEnabled()) { final LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC); - LOGGER.debug("Current time is: {}", now); + LOGGER.debug("Current time (UTC) is: {}", now); tokens.forEach(t -> { if (oneTimeAuthManager.isValid(t)) { LOGGER.debug("OneTimeAuthToken with id {} is still valid. " @@ -97,7 +100,7 @@ public class OneTimeAuthTokenCleaner { t.getValidUntil()); } else { LOGGER.debug("OneTimeAuthToken with id {} is invalid. " - + "Expires at {}.", + + "Expires at {} UTC.", t.getTokenId(), t.getValidUntil()); } @@ -107,7 +110,7 @@ public class OneTimeAuthTokenCleaner { tokens.stream() .filter((token) -> (!oneTimeAuthManager.isValid(token))) .forEach((token) -> { - LOGGER.debug("Token with id {} expired at {}. " + LOGGER.debug("Token with id {} expired at {} UTC. " + "Invalidating token.", token.getTokenId(), token.getValidUntil()); oneTimeAuthManager.invalidate(token); diff --git a/ccm-core/src/main/java/org/libreccm/security/User.java b/ccm-core/src/main/java/org/libreccm/security/User.java index a87f4f006..587e23058 100644 --- a/ccm-core/src/main/java/org/libreccm/security/User.java +++ b/ccm-core/src/main/java/org/libreccm/security/User.java @@ -74,7 +74,13 @@ import javax.xml.bind.annotation.XmlTransient; + "LOWER(u.name) LIKE CONCAT(LOWER(:term), '%') " + "OR LOWER(u.givenName) LIKE CONCAT(LOWER(:term), '%') " + "OR LOWER(u.familyName) LIKE CONCAT(LOWER(:term), '%') " - + "OR LOWER(u.primaryEmailAddress.address) LIKE CONCAT('%', LOWER(:term), '%')") + + "OR LOWER(u.primaryEmailAddress.address) LIKE CONCAT('%', LOWER(:term), '%')"), + @NamedQuery( + name = "User.findAllOrderedByUsername", + query = "SELECT u FROM User u ORDER BY u.name, " + + " u.familyName, " + + " u.givenName, " + + " u.primaryEmailAddress.address") }) @NamedEntityGraphs({ @NamedEntityGraph( @@ -98,7 +104,6 @@ import javax.xml.bind.annotation.XmlTransient; @NamedAttributeNode(value = "permissions")} ) }) - }) @DefaultEntityGraph("User.withGroupAndRoleMemberships") @XmlRootElement(name = "user", namespace = CORE_XML_NS) diff --git a/ccm-core/src/main/java/org/libreccm/security/UserManager.java b/ccm-core/src/main/java/org/libreccm/security/UserManager.java index ddb0c3f4b..54c61c4ee 100644 --- a/ccm-core/src/main/java/org/libreccm/security/UserManager.java +++ b/ccm-core/src/main/java/org/libreccm/security/UserManager.java @@ -53,12 +53,13 @@ public class UserManager { * Creates a new user and saves the user in the database. The method also * creates the password hash. * - * @param givenName The given name of the new user. - * @param familyName The family name of the new user. - * @param name The name of the new user. + * @param givenName The given name of the new user. + * @param familyName The family name of the new user. + * @param name The name of the new user. * @param emailAddress The email address of the new user. - * @param password The password of the new user. The password is hashed - * using the algorithm configured in the {@link SecurityConfig}. + * @param password The password of the new user. The password is hashed + * using the algorithm configured in the + * {@link SecurityConfig}. * * @return The new user. */ @@ -77,7 +78,11 @@ public class UserManager { email.setAddress(emailAddress); user.setPrimaryEmailAddress(email); email.setVerified(true); - user.setPassword(hashPassword(password)); + if (password == null) { + user.setPassword(null); + } else { + user.setPassword(hashPassword(password)); + } userRepository.save(user); @@ -90,11 +95,11 @@ public class UserManager { * the user can't login or that the authentication for this user is done by * an external system. * - * @param user The user which password should be upgraded. + * @param user The user which password should be upgraded. * @param newPassword The new password. The password is hashed using the - * algorithm configured in the {@link SecurityConfig}. + * algorithm configured in the {@link SecurityConfig}. */ - public void updatePassword(@NotNull final User user, + public void updatePassword(@NotNull final User user, final String newPassword) { user.setPassword(hashPassword(newPassword)); @@ -105,11 +110,11 @@ public class UserManager { * Verifies the password of a user. This can be useful if you want to verify * the password of a user already logged in again. * - * @param user The user against which the password is verified. + * @param user The user against which the password is verified. * @param password The password to verify. * * @return {@code true} if the provided passworda matches the password from - * the database, {@code false} otherwise. + * the database, {@code false} otherwise. */ public boolean verifyPassword(final User user, final String password) { //Create a new Shiro PasswordMatcher instance @@ -142,9 +147,10 @@ public class UserManager { //We want to use the Shiro1 format for storing the password. This //format includes the algorithm used, the salt and the number of //iterations used and the hashed password in special formatted string. - final HashFormatFactory hashFormatFactory = new DefaultHashFormatFactory(); + final HashFormatFactory hashFormatFactory + = new DefaultHashFormatFactory(); final HashFormat hashFormat = hashFormatFactory.getInstance( - Shiro1CryptFormat.class.getName()); + Shiro1CryptFormat.class.getName()); return hashFormat.format(hash); } @@ -160,11 +166,13 @@ public class UserManager { if (generatedSaltSize % 8 != 0) { throw new IllegalArgumentException( - "Salt length is not a multipe of 8"); + "Salt length is not a multipe of 8"); } - final SecureRandomNumberGenerator generator = new SecureRandomNumberGenerator(); + final SecureRandomNumberGenerator generator + = new SecureRandomNumberGenerator(); final int byteSize = generatedSaltSize / 8; //generatedSaltSize is in *bits* - convert to byte size: return generator.nextBytes(byteSize); } + } diff --git a/ccm-core/src/main/java/org/libreccm/security/UserRepository.java b/ccm-core/src/main/java/org/libreccm/security/UserRepository.java index bd0385597..e917b44d3 100644 --- a/ccm-core/src/main/java/org/libreccm/security/UserRepository.java +++ b/ccm-core/src/main/java/org/libreccm/security/UserRepository.java @@ -149,7 +149,13 @@ public class UserRepository extends AbstractEntityRepository { final TypedQuery query = getEntityManager().createNamedQuery( "User.filterByNameAndEmail", User.class); query.setParameter("term", term); - + + return query.getResultList(); + } + + public List findAllOrderdByUsername() { + final TypedQuery query = getEntityManager().createNamedQuery( + "User.findAllOrderedByUsername", User.class); return query.getResultList(); } diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties index d67baa270..3e8b3b022 100644 --- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties @@ -228,3 +228,12 @@ ui.admin.new_user_form.error.username_already_in_use=The provided user name is a ui.admin.new_user_form.error.email_already_in_use=The provided email address is already assigned with an user account. ui.admin.new_user_form.error.password_do_not_match=Passwords do not match. ui.admin.new_user_form.error.failed_to_send_password=Failed to send password to new user. +ui.admin.new_user_form.password_options.set_password=Set password +ui.admin.user_details.groups=Groups +ui.admin.user.groups.edit=Edit +ui.admin.user.roles=Directly assigned Roles +ui.admin.user.roles.edit=Edit +ui.admin.user.all_roles=All roles +ui.admin.user.groups=Groups +ui.admin.user.edit_group_memberships=Edit group memberships for user {0} +ui.admin.user.edit_group_memberships.back_to_user_details=Back to user details diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties index 577022e51..7f30f14be 100644 --- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties @@ -228,3 +228,12 @@ ui.admin.new_user_form.error.username_already_in_use=Der angegebene Benutzername ui.admin.new_user_form.error.email_already_in_use=Die angegebene E-Mail-Adresse ist bereits mit einem Benutzerkonto verbunden. ui.admin.new_user_form.error.password_do_not_match=Die eingegebenen Passw\u00f6rter stimmen nicht \u00fcberein. ui.admin.new_user_form.error.failed_to_send_password=Fehler beim Senden des Passworts an den neuen Benutzer. +ui.admin.new_user_form.password_options.set_password=Passwert setzen +ui.admin.user_details.groups=Gruppen +ui.admin.user.groups.edit=Bearbeiten +ui.admin.user.roles=Zugewiesene Rollen +ui.admin.user.roles.edit=Bearbeiten +ui.admin.user.all_roles=Alle Rollen +ui.admin.user.groups=Gruppen +ui.admin.user.edit_group_memberships=Gruppen f\u00fcr Benutzer {0} bearbeiten +ui.admin.user.edit_group_memberships.back_to_user_details=Zur\u00fcck zu den Eigenschaften des Benutzers diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties index 0ea07151d..1c12ae11f 100755 --- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties @@ -201,3 +201,12 @@ ui.admin.new_user_form.error.username_already_in_use=The provided user name is a ui.admin.new_user_form.error.email_already_in_use=The provided email address is already assigned with an user account. ui.admin.new_user_form.error.password_do_not_match=Passwords do not match. ui.admin.new_user_form.error.failed_to_send_password=Failed to send password to new user. +ui.admin.new_user_form.password_options.set_password=Set password +ui.admin.user_details.groups=Groups +ui.admin.user.groups.edit=Edit +ui.admin.user.roles=Directly assigned Roles +ui.admin.user.roles.edit=Edit +ui.admin.user.all_roles=All roles +ui.admin.user.groups=Groups +ui.admin.user.edit_group_memberships=Edit group memberships for user {0} +ui.admin.user.edit_group_memberships.back_to_user_details=Back to user details diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties index 6819537fe..5350281e0 100755 --- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties @@ -192,3 +192,12 @@ ui.admin.new_user_form.error.username_already_in_use=The provided user name is a ui.admin.new_user_form.error.email_already_in_use=The provided email address is already assigned with an user account. ui.admin.new_user_form.error.password_do_not_match=Passwords do not match. ui.admin.new_user_form.error.failed_to_send_password=Failed to send password to new user. +ui.admin.new_user_form.password_options.set_password=Set password +ui.admin.user_details.groups=Groups +ui.admin.user.groups.edit=Edit +ui.admin.user.roles=Directly assigned Roles +ui.admin.user.roles.edit=Edit +ui.admin.user.all_roles=All roles +ui.admin.user.groups=Groups +ui.admin.user.edit_group_memberships=Edit group memberships for user {0} +ui.admin.user.edit_group_memberships.back_to_user_details=Back to user details 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 8ba385b79..349d8181c 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 @@ -101,3 +101,9 @@ login.form.reset_password.auth_token.hint=Please provide the one time authentica login.form.account_activation.success=Your account has been activated. login.form.new_user.error.passwords_do_not_match=Password and confirmation do not match. login.form_new_user.error.creating_challenge_failed=Failed to send registration confirmation. Please contact the administrator. +login.form.account_activation.success.login=Click here to login. +login.form.reset_password.error=Error while resetting password. +login.form.reset_password.error.password_mismatch=Password and confirmation do not match. +login.form.new_user.error.username_already_in_use=The provided user name is already in use. Please choose another user name. +login.form.new_user.error.email_already_registered=There is an account already registered for the provided email address. +login.form.reset_password.scucess.login=Click here to login 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 0b0b74afa..b57dbb6d3 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 @@ -101,3 +101,9 @@ login.form.reset_password.auth_token.hint=Bitte geben Sie das Einmalpasswort, da login.form.account_activation.success=Ihr Benutzerkonto wurde aktiviert. login.form.new_user.error.passwords_do_not_match=Passwort und Best\u00e4tigung sind nicht gleich login.form_new_user.error.creating_challenge_failed=Fehler beim Senden der Anmeldebest\u00e4tigung. Bitte kontaktieren Sie den Systemadministrator. +login.form.account_activation.success.login=Klicken Sie hier, um sich anzumelden. +login.form.reset_password.error=Beim \u00c4ndern des Passwortes ist ein Fehler aufgetreten. +login.form.reset_password.error.password_mismatch=Passwort und Best\u00e4tigung stimmen nicht \u00fcberein. +login.form.new_user.error.username_already_in_use=Der eingegeben Benutzername ist bereits vergeben. Bitte w\u00e4hlen Sie einen anderen Benutzernamen. +login.form.new_user.error.email_already_registered=Es gibt bereits ein Benutzerkonto f\u00fcr die eingebene E-Mail-Adresse +login.form.reset_password.scucess.login=Zur Anmeldung 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 b226f84d3..ac0991b3b 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 @@ -101,3 +101,9 @@ login.form.reset_password.auth_token.hint=Please provide the one time authentica login.form.account_activation.success=Your account has been activated. login.form.new_user.error.passwords_do_not_match=Password and confirmation do not match. login.form_new_user.error.creating_challenge_failed=Failed to send registration confirmation. Please contact the administrator. +login.form.account_activation.success.login=Click here to login. +login.form.reset_password.error=Error while resetting password. +login.form.reset_password.error.password_mismatch=Password and confirmation do not match. +login.form.new_user.error.username_already_in_use=The provided user name is already in use. Please choose another user name. +login.form.new_user.error.email_already_registered=There is an account already registered for the provided email address. +login.form.reset_password.scucess.login=Click here to login 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 e1b838915..6a3394a83 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 @@ -101,3 +101,9 @@ login.form.reset_password.auth_token.hint=Please provide the one time authentica login.form.account_activation.success=Your account has been activated. login.form.new_user.error.passwords_do_not_match=Password and confirmation do not match. login.form_new_user.error.creating_challenge_failed=Failed to send registration confirmation. Please contact the administrator. +login.form.account_activation.success.login=Click here to login. +login.form.reset_password.error=Error while resetting password. +login.form.reset_password.error.password_mismatch=Password and confirmation do not match. +login.form.new_user.error.username_already_in_use=The provided user name is already in use. Please choose another user name. +login.form.new_user.error.email_already_registered=There is an account already registered for the provided email address. +login.form.reset_password.scucess.login=Click here to login