- rewrote fetching context parameter with protected methods to have this code encapsulated
- used this for feature controller
- added new user password-change controller (+ interface) to change user's password
- used it in template to avoid monolithic super controllers ...
- added missing i18n strings
- used password-change event for updating user's password history
- added new context parameter for "change_user_password" feature
- added new context parameter for maximum checked passwords
- fixed redirect outcomes
Signed-off-by: Roland Häder <roland@mxchange.org>
<Scene Scope="Project" version="2">
<Scope Scope="Faces Configuration Only"/>
<Scope Scope="Project">
- <Node id="admin/cellphone/admin_contact_cellphone_list.xhtml" x="1150" y="900" zoom="true"/>
- <Node id="admin/contact/admin_contact_show.xhtml" x="900" y="750" zoom="true"/>
- <Node id="privacy.xhtml" x="900" y="150" zoom="true"/>
- <Node id="guest/user/show_addressbook.xhtml" x="1900" y="300" zoom="true"/>
- <Node id="admin/mobile_provider/admin_mobile_provider_show.xhtml" x="900" y="900" zoom="true"/>
- <Node id="admin/user/admin_user_show.xhtml" x="400" y="600" zoom="true"/>
- <Node id="admin/index.xhtml" x="400" y="150" zoom="true"/>
- <Node id="user/login_own_addressbooks.xhtml" x="650" y="750" zoom="true"/>
- <Node id="user/login_user_data_saved.xhtml" x="650" y="1050" zoom="true"/>
- <Node id="admin/admin_logout.xhtml" x="1400" y="150" zoom="true"/>
- <Node id="user/login_data_saved.xhtml" x="2150" y="150" zoom="true"/>
- <Node id="admin/user/admin_user_unlock.xhtml" x="150" y="750" zoom="true"/>
- <Node id="guest/user/user_list.xhtml" x="650" y="300" zoom="true"/>
- <Node id="index.xhtml" x="400" y="1050" zoom="true"/>
- <Node id="user/login_edit_user_data.xhtml" x="1900" y="600" zoom="true"/>
- <Node id="*" x="1900" y="150" zoom="true"/>
- <Node id="admin/admin_category_delete.xhtml" x="1150" y="600" zoom="true"/>
- <Node id="user/login_index.xhtml" x="1150" y="150" zoom="true"/>
- <Node id="admin/cellphone/admin_contact_cellphone_edit.xhtml" x="1650" y="600" zoom="true"/>
- <Node id="guest/user/lost_passwd.xhtml" x="650" y="1200" zoom="true"/>
- <Node id="guest/user/register_done.xhtml" x="1150" y="300" zoom="true"/>
- <Node id="guest/user/show_addressbook_entries.xhtml" x="1650" y="450" zoom="true"/>
- <Node id="user/login.xhtml" x="900" y="300" zoom="true"/>
- <Node id="admin/cellphone/admin_contact_cellphone_unlink.xhtml" x="1650" y="300" zoom="true"/>
- <Node id="admin/country/admin_country_list.xhtml" x="650" y="900" zoom="true"/>
- <Node id="login/login_edit_user_data.xhtml" x="2400" y="150" zoom="true"/>
- <Node id="guest/user/login_error.xhtml" x="400" y="1350" zoom="true"/>
- <Node id="admin/user/admin_user_export.xhtml" x="400" y="900" zoom="true"/>
- <Node id="admin/mobile_provider/admin_mobile_provider_delete.xhtml" x="1650" y="150" zoom="true"/>
- <Node id="guest/user/resend_done.xhtml" x="900" y="1050" zoom="true"/>
- <Node id="user/login_change_password.xhtml" x="650" y="450" zoom="true"/>
- <Node id="admin/mobile_provider/admin_mobile_provider_list.xhtml" x="2150" y="450" zoom="true"/>
- <Node id="admin/contact/admin_contact_export.xhtml" x="1400" y="300" zoom="true"/>
- <Node id="guest/user/user_profile.xhtml" x="650" y="150" zoom="true"/>
- <Node id="guest/user/confirm_account.xhtml" x="1400" y="900" zoom="true"/>
- <Node id="admin/user/admin_user_delete.xhtml" x="1400" y="450" zoom="true"/>
- <Node id="admin/cellphone/admin_contact_cellphone_delete.xhtml" x="1150" y="750" zoom="true"/>
- <Node id="exception.xhtml" x="900" y="1200" zoom="true"/>
- <Node id="login/login_index.xhtml" x="150" y="450" zoom="true"/>
- <Node id="guest/user/register_page2.xhtml" x="150" y="150" zoom="true"/>
- <Node id="admin/user/admin_user_edit.xhtml" x="900" y="600" zoom="true"/>
- <Node id="terms.xhtml" x="2400" y="300" zoom="true"/>
- <Node id="admin/contact/admin_contact_edit.xhtml" x="1150" y="450" zoom="true"/>
- <Node id="admin/contact/admin_contact_list.xhtml" x="400" y="1200" zoom="true"/>
- <Node id="admin/user/admin_user_list.xhtml" x="1900" y="450" zoom="true"/>
- <Node id="admin/admin_user_add.xhtml" x="1150" y="1050" zoom="true"/>
- <Node id="admin/country/admin_country_delete.xhtml" x="150" y="300" zoom="true"/>
- <Node id="user/login_change_personal_data.xhtml" x="150" y="1050" zoom="true"/>
- <Node id="logout.xhtml" x="400" y="450" zoom="true"/>
- <Node id="admin/country/admin_country_edit.xhtml" x="150" y="1200" zoom="true"/>
- <Node id="user/login_add_addressbook.xhtml" x="150" y="900" zoom="true"/>
- <Node id="guest/user/resend_link.xhtml" x="150" y="1350" zoom="true"/>
- <Node id="user/login_contact_data_saved.xhtml" x="400" y="300" zoom="true"/>
- <Node id="user/user_profile.xhtml" x="650" y="600" zoom="true"/>
- <Node id="admin/contact/admin_contact_delete.xhtml" x="2150" y="300" zoom="true"/>
- <Node id="imprint.xhtml" x="900" y="450" zoom="true"/>
- <Node id="admin/mobile_provider/admin_mobile_provider_edit.xhtml" x="2650" y="150" zoom="true"/>
- <Node id="admin/cellphone/admin_contact_cellphone_show.xhtml" x="150" y="1500" zoom="true"/>
- <Node id="admin/admin_product_delete.xhtml" x="1400" y="600" zoom="true"/>
- <Node id="guest/user/login.xhtml" x="1650" y="750" zoom="true"/>
- <Node id="guest/user/register.xhtml" x="1400" y="750" zoom="true"/>
- <Node id="user/login_change_email_address.xhtml" x="400" y="750" zoom="true"/>
- <Node id="user/login/login_data_saved.xhtml" x="150" y="600" zoom="true"/>
+ <Node id="admin/cellphone/admin_contact_cellphone_list.xhtml" x="150" y="1050" zoom="true"/>
+ <Node id="user/login_logout.xhtml" x="1650" y="300" zoom="true"/>
+ <Node id="admin/contact/admin_contact_show.xhtml" x="400" y="750" zoom="true"/>
+ <Node id="privacy.xhtml" x="650" y="900" zoom="true"/>
+ <Node id="admin/mobile_provider/admin_mobile_provider_show.xhtml" x="650" y="750" zoom="true"/>
+ <Node id="guest/user/show_addressbook.xhtml" x="650" y="1350" zoom="true"/>
+ <Node id="admin/user/admin_user_show.xhtml" x="900" y="600" zoom="true"/>
+ <Node id="admin/index.xhtml" x="1150" y="450" zoom="true"/>
+ <Node id="user/login_own_addressbooks.xhtml" x="900" y="300" zoom="true"/>
+ <Node id="user/login_user_data_saved.xhtml" x="400" y="900" zoom="true"/>
+ <Node id="admin/admin_logout.xhtml" x="400" y="1200" zoom="true"/>
+ <Node id="user/login_data_saved.xhtml" x="150" y="450" zoom="true"/>
+ <Node id="admin/user/admin_user_unlock.xhtml" x="1400" y="750" zoom="true"/>
+ <Node id="guest/user/user_list.xhtml" x="1150" y="750" zoom="true"/>
+ <Node id="index.xhtml" x="1150" y="900" zoom="true"/>
+ <Node id="user/login_edit_user_data.xhtml" x="1900" y="150" zoom="true"/>
+ <Node id="*" x="650" y="450" zoom="true"/>
+ <Node id="admin/admin_category_delete.xhtml" x="900" y="750" zoom="true"/>
+ <Node id="admin/cellphone/admin_contact_cellphone_edit.xhtml" x="1150" y="600" zoom="true"/>
+ <Node id="user/login_index.xhtml" x="1650" y="450" zoom="true"/>
+ <Node id="guest/user/lost_passwd.xhtml" x="1900" y="450" zoom="true"/>
+ <Node id="guest/user/register_done.xhtml" x="1650" y="150" zoom="true"/>
+ <Node id="guest/user/show_addressbook_entries.xhtml" x="400" y="1350" zoom="true"/>
+ <Node id="user/login.xhtml" x="150" y="1350" zoom="true"/>
+ <Node id="login/login_edit_user_data.xhtml" x="150" y="300" zoom="true"/>
+ <Node id="admin/cellphone/admin_contact_cellphone_unlink.xhtml" x="2400" y="150" zoom="true"/>
+ <Node id="admin/country/admin_country_list.xhtml" x="1900" y="600" zoom="true"/>
+ <Node id="guest/user/login_error.xhtml" x="650" y="150" zoom="true"/>
+ <Node id="admin/user/admin_user_export.xhtml" x="900" y="1050" zoom="true"/>
+ <Node id="admin/mobile_provider/admin_mobile_provider_delete.xhtml" x="400" y="300" zoom="true"/>
+ <Node id="guest/user/resend_done.xhtml" x="1150" y="300" zoom="true"/>
+ <Node id="user/login_change_password.xhtml" x="1400" y="600" zoom="true"/>
+ <Node id="admin/mobile_provider/admin_mobile_provider_list.xhtml" x="150" y="750" zoom="true"/>
+ <Node id="admin/contact/admin_contact_export.xhtml" x="1900" y="300" zoom="true"/>
+ <Node id="guest/user/user_profile.xhtml" x="1150" y="1050" zoom="true"/>
+ <Node id="guest/user/confirm_account.xhtml" x="2150" y="150" zoom="true"/>
+ <Node id="exception.xhtml" x="650" y="600" zoom="true"/>
+ <Node id="admin/user/admin_user_delete.xhtml" x="1650" y="750" zoom="true"/>
+ <Node id="admin/cellphone/admin_contact_cellphone_delete.xhtml" x="900" y="1200" zoom="true"/>
+ <Node id="login/login_index.xhtml" x="2150" y="300" zoom="true"/>
+ <Node id="guest/user/register_page2.xhtml" x="1150" y="150" zoom="true"/>
+ <Node id="admin/user/admin_user_edit.xhtml" x="900" y="900" zoom="true"/>
+ <Node id="terms.xhtml" x="900" y="150" zoom="true"/>
+ <Node id="admin/contact/admin_contact_list.xhtml" x="400" y="450" zoom="true"/>
+ <Node id="admin/contact/admin_contact_edit.xhtml" x="150" y="600" zoom="true"/>
+ <Node id="admin/user/admin_user_list.xhtml" x="1400" y="450" zoom="true"/>
+ <Node id="admin/admin_user_add.xhtml" x="650" y="1050" zoom="true"/>
+ <Node id="admin/country/admin_country_delete.xhtml" x="2400" y="300" zoom="true"/>
+ <Node id="user/login_change_personal_data.xhtml" x="1400" y="300" zoom="true"/>
+ <Node id="logout.xhtml" x="400" y="150" zoom="true"/>
+ <Node id="admin/country/admin_country_edit.xhtml" x="400" y="1050" zoom="true"/>
+ <Node id="guest/user/resend_link.xhtml" x="150" y="150" zoom="true"/>
+ <Node id="user/login_add_addressbook.xhtml" x="1400" y="150" zoom="true"/>
+ <Node id="user/login_contact_data_saved.xhtml" x="400" y="600" zoom="true"/>
+ <Node id="user/user_profile.xhtml" x="150" y="1200" zoom="true"/>
+ <Node id="admin/contact/admin_contact_delete.xhtml" x="1400" y="900" zoom="true"/>
+ <Node id="imprint.xhtml" x="1650" y="600" zoom="true"/>
+ <Node id="admin/mobile_provider/admin_mobile_provider_edit.xhtml" x="2150" y="450" zoom="true"/>
+ <Node id="admin/cellphone/admin_contact_cellphone_show.xhtml" x="2650" y="150" zoom="true"/>
+ <Node id="admin/admin_product_delete.xhtml" x="650" y="1200" zoom="true"/>
+ <Node id="guest/user/login.xhtml" x="150" y="1500" zoom="true"/>
+ <Node id="guest/user/register.xhtml" x="900" y="450" zoom="true"/>
+ <Node id="user/login_change_email_address.xhtml" x="150" y="900" zoom="true"/>
+ <Node id="user/login/login_data_saved.xhtml" x="650" y="300" zoom="true"/>
</Scope>
<Scope Scope="All Faces Configurations"/>
</Scene>
FacesContext.getCurrentInstance().addMessage(clientId, new FacesMessage(cause.getMessage()));
}
+ /**
+ * Returns given property key or throws an exception if not found.
+ * <p>
+ * @param parameterKey Property key
+ * <p>
+ * @return Property value
+ * <p>
+ * @throws NullPointerException If given key is not found
+ * @throws NumberFormatException If no number is given in context parameter
+ */
+ protected int getIntegerContextParameter (final String parameterKey) throws NullPointerException, NumberFormatException {
+ // Get context parameter
+ Integer contextValue = Integer.parseInt(this.getStringContextParameter(parameterKey));
+
+ // Return it
+ return contextValue;
+ }
+
+ /**
+ * Returns given property key or throws an exception if not found.
+ * <p>
+ * @param parameterKey Property key
+ * <p>
+ * @return Property value
+ * <p>
+ * @throws NullPointerException If given key is not found
+ */
+ protected String getStringContextParameter (final String parameterKey) throws NullPointerException {
+ // Get context parameter
+ String contextValue = FacesContext.getCurrentInstance().getExternalContext().getInitParameter(parameterKey);
+
+ // Is it null?
+ if (null == contextValue) {
+ // Throw NPE
+ throw new NullPointerException("parameterKey=" + parameterKey + " is not set.");
+ }
+
+ // Return it
+ return contextValue;
+ }
+
}
--- /dev/null
+/*
+ * Copyright (C) 2016 Roland Haeder
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.mxchange.jjobs.beans.features;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Named;
+import org.mxchange.jjobs.beans.BaseJobsController;
+
+/**
+ * A feature bean
+ * <p>
+ * @author Roland Haeder<rhaeder@cho-time.de>
+ */
+@Named ("featureController")
+@ApplicationScoped
+public class JobsFeatureWebApplicationBean extends BaseJobsController implements JobsFeaturesWebApplicationController {
+
+ /**
+ * Serial number
+ */
+ private static final long serialVersionUID = 64_237_512_690_168_674L;
+
+ @Override
+ public boolean isFeatureEnabled (final String feature) {
+ // The parameter must be set
+ if (null == feature) {
+ // Throw NPE
+ throw new NullPointerException("feature is null"); //NOI18N
+ } else if (feature.isEmpty()) {
+ // Is empty
+ throw new IllegalArgumentException("feature is empty"); //NOI18N
+ }
+
+ // Default is not enabled
+ boolean isEnabled = false;
+
+ // Try it as an NPE may come
+ try {
+ // Get value from property
+ String value = this.getStringContextParameter(String.format("is_feature_%s_enabled", feature)); //NOI18N
+
+ // Is it set?
+ isEnabled = (value.toLowerCase().equals("true")); //NOI18N
+ } catch (final NullPointerException ex) {
+ // Ignored
+ }
+
+ // Return value
+ return isEnabled;
+ }
+
+}
*/
package org.mxchange.jjobs.beans.login;
+import java.text.MessageFormat;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.event.Event;
+import javax.enterprise.event.Observes;
import javax.enterprise.inject.Any;
+import javax.faces.context.FacesContext;
import javax.faces.view.facelets.FaceletException;
import javax.inject.Inject;
import javax.inject.Named;
import org.mxchange.jusercore.container.login.UserLoginContainer;
import org.mxchange.jusercore.events.login.UserLoggedInEvent;
import org.mxchange.jusercore.events.login.UserLoginEvent;
+import org.mxchange.jusercore.events.user.password_change.UpdatedUserPasswordEvent;
import org.mxchange.jusercore.exceptions.UserNotFoundException;
import org.mxchange.jusercore.exceptions.UserPasswordMismatchException;
import org.mxchange.jusercore.exceptions.UserStatusLockedException;
}
@Override
- public String doLogin () {
+ public void afterUserUpdatedPasswordEvent (@Observes final UpdatedUserPasswordEvent event) {
+ // Check parameter
+ if (null == event) {
+ // Throw NPE
+ throw new NullPointerException("event is null"); //NOI18N
+ } else if (event.getPasswordHistory() == null) {
+ // Throw NPE again
+ throw new NullPointerException("event.passwordHistory is null"); //NOI18N
+ } else if (event.getPasswordHistory().getUserPasswordHistoryId() == null) {
+ // ... and again
+ throw new NullPointerException("event.passwordHistory.userPasswordHistoryId is null"); //NOI18N
+ } else if (event.getPasswordHistory().getUserPasswordHistoryId() < 1) {
+ // Invalid value
+ throw new IllegalArgumentException(MessageFormat.format("event.passwordHistory.userPasswordHistoryId={0} is in valid", event.getPasswordHistory().getUserPasswordHistoryId())); //NOI18N
+ }
+
+ // All fine, so update list
+ this.updatePasswordHistory(event.getPasswordHistory());
+ }
+
+ @Override
+ public String doAdminLogout () {
+ // Is a user logged-in?
+ if (this.isUserLoggedIn()) {
+ // Call other logout
+ return this.doUserLogout();
+ }
+
+ // Invalidate session
+ FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
+
+ // Set template type to guest
+ this.setBaseTemplatePathName(GUEST_BASE_TEMPLATE_NAME); //NOI18N
+
+ // Redirect to index
+ return "index?faces-redirect=true"; //NOI18N
+ }
+
+ @Override
+ public String doUserLogin () {
// Get user instance
User user = this.userController.createUserLogin();
return this.getLoggedInUser().getUserProfileMode().equals(ProfileMode.INVISIBLE);
}
+ @Override
+ public boolean isPasswordInHistory (final String userPassword) {
+ // Default is not found
+ boolean isPasswordInHistory = false;
+
+ // Init variables
+ int count = 1;
+ int maxEntries = this.getIntegerContextParameter("max_user_password_history"); //NOI18N
+
+ // Check all passwords
+ for (final PasswordHistory entry : this.getUserPasswordHistory()) {
+ // Is password the same?
+ if (UserUtils.ifPasswordMatches(userPassword, entry.getUserPasswordHistoryUser())) {
+ // Yes, found it
+ isPasswordInHistory = true;
+ break;
+ } else if (count == maxEntries) {
+ // Maximum reached
+ break;
+ }
+
+ // Count up
+ count++;
+ }
+
+ // Return status
+ return isPasswordInHistory;
+ }
+
@Override
public boolean isUserLoggedIn () {
// Trace message
return this.userLoggedIn;
}
+ /**
+ * Clears this bean
+ */
+ private void clear () {
+ // Clear all fields
+ this.setCurrentPassword(null);
+ }
+
+ /**
+ * Updates password history by adding given entry to it as long as it is not
+ * there.
+ * <p>
+ * @param passwordHistory Password history entry
+ */
+ private void updatePasswordHistory (final PasswordHistory passwordHistory) {
+ if (null == passwordHistory) {
+ // Throw NPE
+ throw new NullPointerException("passwordHistory is null"); //NOI18N
+ } else if (passwordHistory.getUserPasswordHistoryId() == null) {
+ // Throw NPE again
+ throw new NullPointerException("passwordHistory.userPasswordHistoryId is null"); //NOI18N
+ } else if (passwordHistory.getUserPasswordHistoryId() < 1) {
+ // Invalid id
+ throw new IllegalArgumentException(MessageFormat.format("passwordHistory.userPasswordHistoryId={0} is not valid.", passwordHistory.getUserPasswordHistoryId())); //NOI18N
+ }
+
+ // Is it there?
+ if (this.userPasswordHistory.contains(passwordHistory)) {
+ // Excact copy found
+ return;
+ }
+
+ // Check all entries
+ for (final PasswordHistory entry : this.userPasswordHistory) {
+ // Is same id number?
+ if (Objects.equals(entry.getUserPasswordHistoryId(), passwordHistory.getUserPasswordHistoryId())) {
+ // Found it
+ return;
+ }
+ }
+
+ // Not found, so add it
+ this.userPasswordHistory.add(passwordHistory);
+ }
+
}
import java.io.Serializable;
import java.util.List;
import javax.ejb.Local;
+import org.mxchange.jusercore.events.user.password_change.UpdatedUserPasswordEvent;
import org.mxchange.jusercore.model.user.User;
import org.mxchange.jusercore.model.user.password_history.PasswordHistory;
@Local
public interface JobsUserLoginWebSessionController extends Serializable {
+ /**
+ * Method being call after user's password has been updated (and history
+ * entry has been created).
+ * <p>
+ * @param event Event being observed
+ */
+ void afterUserUpdatedPasswordEvent (final UpdatedUserPasswordEvent event);
+
+ /**
+ * Checks whether given clear-text password is in user's password history.
+ * <p>
+ * @param userPassword Clear-text password
+ * <p>
+ * @return Whether clear-text password is in user's password history
+ */
+ boolean isPasswordInHistory (final String userPassword);
+
/**
* Getter for template type
* <p>
import org.mxchange.jusercore.events.login.UserLoggedInEvent;
import org.mxchange.jusercore.events.registration.UserRegisteredEvent;
import org.mxchange.jusercore.events.user.add.AdminAddedUserEvent;
+import org.mxchange.jusercore.events.user.password_change.UpdatedUserPasswordEvent;
import org.mxchange.jusercore.events.user.update.AdminUpdatedUserDataEvent;
import org.mxchange.jusercore.events.user.update.UpdatedUserPersonalDataEvent;
import org.mxchange.jusercore.events.user.update.UserUpdatedPersonalDataEvent;
}
}
+ @Override
+ public void afterUserUpdatedPasswordEvent (@Observes final UpdatedUserPasswordEvent event) {
+ // Check parameter
+ if (null == event) {
+ // Throw NPE
+ throw new NullPointerException("event is null"); //NOI18N
+ } else if (event.getPasswordHistory() == null) {
+ // Throw NPE again
+ throw new NullPointerException("event.passwordHistory is null"); //NOI18N
+ } else if (event.getPasswordHistory().getUserPasswordHistoryId() == null) {
+ // ... and again
+ throw new NullPointerException("event.passwordHistory.userPasswordHistoryId is null"); //NOI18N
+ } else if (event.getPasswordHistory().getUserPasswordHistoryId() < 1) {
+ // Invalid value
+ throw new IllegalArgumentException(MessageFormat.format("event.passwordHistory.userPasswordHistoryId={0} is in valid", event.getPasswordHistory().getUserPasswordHistoryId())); //NOI18N
+ }
+
+ // All fine, so update list
+ this.updateList(event.getPasswordHistory().getUserPasswordHistoryUser());
+ }
+
@Override
public void afterUserUpdatedPersonalData (@Observes final UpdatedUserPersonalDataEvent event) {
// Check parameter
} else if (!this.userLoginController.ifCurrentPasswordMatches()) {
// Password not matching
throw new FaceletException(new UserPasswordMismatchException(this.userLoginController.getLoggedInUser()));
+ } else if (!this.featureController.isFeatureEnabled("edit_user_data")) {
+ // Editing is not allowed
+ throw new IllegalStateException("User tried to edit personal data."); //NOI18N
}
// Get user instance
this.updatedPersonalDataEvent.fire(new UserUpdatedPersonalDataEvent(updatedUser));
// All fine
- return "user_data_saved"; //NOI18N
+ return "contact_data_saved"; //NOI18N
}
@Override
import org.mxchange.jusercore.events.login.UserLoggedInEvent;
import org.mxchange.jusercore.events.registration.UserRegisteredEvent;
import org.mxchange.jusercore.events.user.add.AdminAddedUserEvent;
+import org.mxchange.jusercore.events.user.password_change.UpdatedUserPasswordEvent;
import org.mxchange.jusercore.events.user.update.AdminUpdatedUserDataEvent;
import org.mxchange.jusercore.events.user.update.UpdatedUserPersonalDataEvent;
import org.mxchange.jusercore.exceptions.UserEmailAddressNotFoundException;
*/
void afterUserConfirmedAccount (final UserConfirmedAccountEvent event);
+ /**
+ * Method being call after user's password has been updated (and history
+ * entry has been created).
+ * <p>
+ * @param event Event being observed
+ */
+ void afterUserUpdatedPasswordEvent (final UpdatedUserPasswordEvent event);
+
/**
* Listens to fired event when user updated personal data
* <p>
--- /dev/null
+/*
+ * Copyright (C) 2016 Cho-Time GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.mxchange.jjobs.beans.user.password;
+
+import java.util.Objects;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.event.Event;
+import javax.enterprise.inject.Any;
+import javax.faces.view.facelets.FaceletException;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import org.mxchange.addressbook.beans.BaseAddressbookController;
+import org.mxchange.addressbook.beans.features.AddressbookFeaturesWebApplicationController;
+import org.mxchange.addressbook.beans.login.AddressbookUserLoginWebSessionController;
+import org.mxchange.jusercore.events.user.password_change.UpdatedUserPasswordEvent;
+import org.mxchange.jusercore.events.user.password_change.UserUpdatedPasswordEvent;
+import org.mxchange.jusercore.exceptions.UserNotFoundException;
+import org.mxchange.jusercore.exceptions.UserPasswordMismatchException;
+import org.mxchange.jusercore.exceptions.UserStatusLockedException;
+import org.mxchange.jusercore.exceptions.UserStatusUnconfirmedException;
+import org.mxchange.jusercore.model.user.User;
+import org.mxchange.jusercore.model.user.UserSessionBeanRemote;
+import org.mxchange.jusercore.model.user.UserUtils;
+import org.mxchange.jusercore.model.user.password_history.PasswordHistory;
+
+/**
+ * A user password (change) bean (controller)
+ * <p>
+ * @author Roland Haeder<rhaeder@cho-time.de>
+ */
+@Named ("userPasswordController")
+@RequestScoped
+public class JobsUserPasswordWebRequestBean extends BaseAddressbookController implements JobsUserPasswordWebRequestController {
+
+ /**
+ * Serial number
+ */
+ private static final long serialVersionUID = 15_267_867_367_501L;
+
+ /**
+ * Features controller
+ */
+ @Inject
+ private AddressbookFeaturesWebApplicationController featureController;
+
+ /**
+ * Remote user bean
+ */
+ private final UserSessionBeanRemote userBean;
+
+ /**
+ * Current password (for confirmation of password change)
+ */
+ private String userCurrentPassword;
+
+ /**
+ * Login bean (controller)
+ */
+ @Inject
+ private AddressbookUserLoginWebSessionController userLoginController;
+
+ /**
+ * User password (unencrypted from web form)
+ */
+ private String userPassword;
+
+ /**
+ * User password repeated (unencrypted from web form)
+ */
+ private String userPasswordRepeat;
+
+ /**
+ * Event being fired when user's password has been updated
+ */
+ @Any
+ @Inject
+ private Event<UpdatedUserPasswordEvent> userUpdatedPasswordEvent;
+
+ /**
+ * Default constructor
+ */
+ public JobsUserPasswordWebRequestBean () {
+ // Try it
+ try {
+ // Get initial context
+ Context context = new InitialContext();
+
+ // Try to lookup
+ this.userBean = (UserSessionBeanRemote) context.lookup("java:global/jlandingpage-ejb/user!org.mxchange.jusercore.model.user.UserSessionBeanRemote"); //NOI18N
+ } catch (final NamingException e) {
+ // Throw again
+ throw new FaceletException(e);
+ }
+ }
+
+ @Override
+ public String doChangePassword () {
+ // This method shall only be called if the user is logged-in
+ if (!this.userLoginController.isUserLoggedIn()) {
+ // Not logged-in
+ throw new IllegalStateException("User is not logged-in"); //NOI18N
+ } else if (!this.isRequiredChangePasswordSet()) {
+ // Not all required fields are set
+ throw new FaceletException("Not all required fields are set."); //NOI18N
+ } else if (!this.userLoginController.ifCurrentPasswordMatches()) {
+ // Password not matching
+ throw new FaceletException(new UserPasswordMismatchException(this.userLoginController.getLoggedInUser()));
+ } else if (!this.featureController.isFeatureEnabled("change_user_password")) { //NOI18N
+ // Editing is not allowed
+ throw new IllegalStateException("User tried to change password."); //NOI18N
+ } else if (!UserUtils.ifPasswordMatches(this.getUserCurrentPassword(), this.userLoginController.getLoggedInUser())) {
+ // Password mismatches
+ this.showFacesMessage("form_user_change_password:userCurrentPassword", "Entered current password does not matched stored password."); //NOI18N
+
+ // Clear bean
+ this.clear();
+
+ // No redirect
+ return ""; //NOI18N
+ } else if (!Objects.equals(this.getUserPassword(), this.getUserPasswordRepeat())) {
+ // Both entered passwords don't match
+ this.showFacesMessage("form_user_change_password:userPasswordRepeat", "Entered new passwords mismatch."); //NOI18N
+
+ // Clear bean
+ this.clear();
+
+ // No redirect
+ return ""; //NOI18N
+ } else if (Objects.equals(this.getUserCurrentPassword(), this.getUserPassword())) {
+ // New password matches current
+ this.showFacesMessage("form_user_change_password:userPassword", "Entered new password is same as current password."); //NOI18N
+
+ // Clear bean
+ this.clear();
+
+ // No redirect
+ return ""; //NOI18N
+ } else if (this.userLoginController.isPasswordInHistory(this.getUserPassword())) {
+ // Is already in list (to old passwords are ignored)
+ this.showFacesMessage("form_user_change_password:userPassword", "Entered new password is has already been used some time ago."); //NOI18N
+
+ // Clear bean
+ this.clear();
+
+ // No redirect
+ return ""; //NOI18N
+ }
+
+ // Get user instance
+ User user = this.userLoginController.getLoggedInUser();
+
+ // Encrypt password
+ String encryptedPassword = UserUtils.encryptPassword(this.getUserPassword());
+
+ // Set it in user
+ user.setUserEncryptedPassword(encryptedPassword);
+
+ try {
+ // All is set, then update password
+ PasswordHistory passwordHistory = this.userBean.updateUserPassword(user);
+
+ // Fire event
+ this.userUpdatedPasswordEvent.fire(new UserUpdatedPasswordEvent(passwordHistory));
+ } catch (final UserNotFoundException | UserStatusUnconfirmedException | UserStatusLockedException ex) {
+ // Clear bean
+ this.clear();
+
+ // Throw again
+ throw new FaceletException(ex);
+ }
+
+ // Clear bean
+ this.clear();
+
+ // Return outcome
+ return "login_data_saved"; //NOI18N
+ }
+
+ @Override
+ public String getUserCurrentPassword () {
+ return this.userCurrentPassword;
+ }
+
+ @Override
+ public void setUserCurrentPassword (final String userCurrentPassword) {
+ this.userCurrentPassword = userCurrentPassword;
+ }
+
+ @Override
+ public String getUserPassword () {
+ return this.userPassword;
+ }
+
+ @Override
+ public void setUserPassword (final String userPassword) {
+ this.userPassword = userPassword;
+ }
+
+ @Override
+ public String getUserPasswordRepeat () {
+ return this.userPasswordRepeat;
+ }
+
+ @Override
+ public void setUserPasswordRepeat (final String userPasswordRepeat) {
+ this.userPasswordRepeat = userPasswordRepeat;
+ }
+
+ public boolean isRequiredChangePasswordSet () {
+ // Is all data set?
+ return ((this.getUserCurrentPassword() != null) &&
+ (!this.getUserCurrentPassword().isEmpty()) &&
+ (this.getUserPassword() != null) &&
+ (!this.getUserPassword().isEmpty()) &&
+ (this.getUserPasswordRepeat() != null) &&
+ (!this.getUserPasswordRepeat().isEmpty()));
+ }
+
+ /**
+ * Clears this bean
+ */
+ private void clear () {
+ // Clear all data
+ this.setUserPassword(null);
+ this.setUserPasswordRepeat(null);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 Cho-Time GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.mxchange.jjobs.beans.user.password;
+
+import java.io.Serializable;
+import javax.ejb.Local;
+
+/**
+ * An interface for user beans
+ * <p>
+ * @author Roland Haeder<rhaeder@cho-time.de>
+ */
+@Local
+public interface JobsUserPasswordWebRequestController extends Serializable {
+
+ /**
+ * Getter for unencrypted user password
+ * <p>
+ * @return Unencrypted user password
+ */
+ String getUserPassword ();
+
+ /**
+ * Setter for unencrypted user password
+ * <p>
+ * @param userPassword Unencrypted user password
+ */
+ void setUserPassword (final String userPassword);
+
+ /**
+ * Getter for current unencrypted user password
+ * <p>
+ * @return Current unencrypted user password
+ */
+ String getUserCurrentPassword ();
+
+ /**
+ * Setter for current unencrypted user password
+ * <p>
+ * @param userCurrentPassword Current unencrypted user password
+ */
+ void setUserCurrentPassword (final String userCurrentPassword);
+
+ /**
+ * Getter for unencrypted user password repeated
+ * <p>
+ * @return Unencrypted user password repeated
+ */
+ String getUserPasswordRepeat ();
+
+ /**
+ * Setter for unencrypted user password repeated
+ * <p>
+ * @param userPasswordRepeat Unencrypted user password repeated
+ */
+ void setUserPasswordRepeat (final String userPasswordRepeat);
+
+ /**
+ * Changes logged-in user's password. It must not match with current password and should not appear in password history list for X (configurable) entries.
+ * <p>
+ * @return Redirect outcome
+ */
+ String doChangePassword ();
+
+}
USER_LOGIN_MUST_CHANGE_PASSWORD=Sie m\u00fcssen Ihr Passwort \u00e4ndern. Dies darf nicht mit dem aktuellen \u00fcbereinstimmen.
ADMIN_LOGOUT_TITLE=Aus dem Administrationsbereich ausloggen
ADMIN_LOGOUT_NOTICE=Bitte loggen Sie sich immer aus, damit die Sitzung sauber beendet ist. Andere M\u00f6gliichkeit ist, den Browser zu schhlie\u00dfen, dann wird die Sitzung auch beendet.
+USER_CURRENT_PASSWORD_REQUIRED=Bitte geben Sie Ihr derzeit verwendetes Passwort zur \u00c4nderungsbest\u00e4tigung ein.
+USER_NEW_PASSWORD_REQUIRED=Bitte geben Sie ein neues Passwort zweimal ein. Dieses muss anders als Ihr aktuelles sein.
+USER_NEW_PASSWORD_REPEAT_REQUIRED=Bitte wiederholen Sie das neue Passwort. Dies dient der Reduzierung von Tippfehlern.
USER_LOGIN_MUST_CHANGE_PASSWORD=Please change your password. It must not match with your current one.
ADMIN_LOGOUT_TITLE=Logout from administration area
ADMIN_LOGOUT_NOTICE=Please always logout from administration area that the session is cleanly closed. An other option is to close the browser, then the session is also closed.
+USER_CURRENT_PASSWORD_REQUIRED=Please enter your currently used password for confirming password change.
+USER_NEW_PASSWORD_REQUIRED=Please a new password twice. This must be different than the current one.
+USER_NEW_PASSWORD_REPEAT_REQUIRED=Please repeat your new password. This is for preventing type mistakes.
<param-name>is_feature_user_must_change_password_enabled</param-name>
<param-value>true</param-value>
</context-param>
+ <context-param>
+ <description>Whether users are allowed to change their login password.</description>
+ <param-name>is_feature_change_user_password_enabled</param-name>
+ <param-value>true</param-value>
+ </context-param>
+ <context-param>
+ <description>Maximum passwords that must be different.</description>
+ <param-name>max_user_password_history</param-name>
+ <param-value>5</param-value>
+ </context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<ui:define name="content">
<ui:fragment rendered="#{userLoginController.isUserLoggedIn()}">
- <div class="table">
- <div class="table_header">
- <h:outputText value="#{msg.LOGIN_CHANGE_PASSWORD_TITLE}" />
- </div>
+ <ui:fragment rendered="#{featureController.isFeatureEnabled('change_user_password')}">
+ <div class="table">
+ <div class="table_header">
+ <h:outputText value="#{msg.LOGIN_CHANGE_PASSWORD_TITLE}" />
+ </div>
+
+ <h:form id="form_user_change_password">
+ <div class="para">
+ <fieldset id="change_password">
+ <legend title="#{msg.LOGIN_CHANGE_PASSWORD_LEGEND_TITLE}">
+ <h:outputText value="#{msg.LOGIN_CHANGE_PASSWORD_LEGEND}" />
+ </legend>
+
+ <div class="table_row">
+ <div class="table_left">
+ <h:outputLabel for="userCurrentPassword" value="#{msg.GUEST_REGISTRATION_ENTER_CURRENT_PASSWORD}" />
+ </div>
- <h:form id="login_form">
- <div class="para">
- <fieldset id="change_password">
- <legend title="#{msg.LOGIN_CHANGE_PASSWORD_LEGEND_TITLE}">
- <h:outputText value="#{msg.LOGIN_CHANGE_PASSWORD_LEGEND}" />
- </legend>
+ <div class="table_right">
+ <h:inputSecret styleClass="input" id="userCurrentPassword" size="10" maxlength="255" value="#{userPasswordController.userCurrentPassword}" required="true" requiredMessage="#{msg.USER_CURRENT_PASSWORD_REQUIRED}" />
+ </div>
- <div class="table_row">
- <div class="table_left">
- <h:outputLabel for="userPassword" value="#{msg.GUEST_REGISTRATION_ENTER_PASSWORD}" />
+ <div class="clear"></div>
</div>
- <div class="table_right">
- <h:inputSecret styleClass="input" id="userPassword" size="10" maxlength="255" value="#{userLoginController.userPassword}" required="true" />
+ <div class="error_container">
+ <h:message for="userCurrentPassword" errorClass="errors" fatalClass="errors" warnClass="errors" />
</div>
- <div class="clear"></div>
- </div>
+ <div class="table_row">
+ <div class="table_left">
+ <h:outputLabel for="userPassword" value="#{msg.GUEST_REGISTRATION_ENTER_PASSWORD}" />
+ </div>
- <div class="table_row">
- <div class="table_left">
- <h:outputLabel for="userPasswordRepeat" value="#{msg.GUEST_REGISTRATION_ENTER_PASSWORD_REPEAT}" />
+ <div class="table_right">
+ <h:inputSecret styleClass="input" id="userPassword" size="10" maxlength="255" value="#{userPasswordController.userPassword}" required="true" requiredMessage="#{msg.USER_NEW_PASSWORD_REQUIRED}" />
+ </div>
</div>
- <div class="table_right">
- <h:inputSecret styleClass="input" id="userPasswordRepeat" size="10" maxlength="255" value="#{userLoginController.userPasswordRepeat}" required="true" />
+ <div class="error_container">
+ <h:message for="userPassword" errorClass="errors" fatalClass="errors" warnClass="errors" />
</div>
- <div class="clear"></div>
- </div>
- </fieldset>
- </div>
+ <div class="table_row">
+ <div class="table_left">
+ <h:outputLabel for="userPasswordRepeat" value="#{msg.GUEST_REGISTRATION_ENTER_PASSWORD_REPEAT}" />
+ </div>
- <ui:include src="/WEB-INF/templates/login/user/user_enter_current_password.tpl" />
+ <div class="table_right">
+ <h:inputSecret styleClass="input" id="userPasswordRepeat" size="10" maxlength="255" value="#{userPasswordController.userPasswordRepeat}" required="true" requiredMessage="#{msg.USER_NEW_PASSWORD_REPEAT_REQUIRED}" />
+ </div>
+ </div>
- <div class="table_footer">
- <h:commandButton styleClass="reset" type="reset" value="#{msg.BUTTON_RESET_FORM}" />
- <h:commandButton styleClass="submit" type="submit" id="change_password" value="#{msg.BUTTON_CHANGE_PASSWORD}" action="#{userController.changePassword()}" />
- </div>
- </h:form>
- </div>
- </ui:fragment>
+ <div class="error_container">
+ <h:message for="userPasswordRepeat" errorClass="errors" fatalClass="errors" warnClass="errors" />
+ </div>
+ </fieldset>
+ </div>
+
+ <div class="table_footer">
+ <h:commandButton styleClass="reset" type="reset" value="#{msg.BUTTON_RESET_FORM}" />
+ <h:commandButton styleClass="submit" type="submit" id="change_password" value="#{msg.BUTTON_CHANGE_PASSWORD}" action="#{userPasswordController.doChangePassword()}" />
+ </div>
+ </h:form>
+ </div>
+
+ <ui:include src="/WEB-INF/templates/login/user/user_enter_current_password.tpl" />
+
+ <h:outputText styleClass="errors" value="#{msg.ERROR_LOGIN_USER_EDIT_DATA_DISABLED}" rendered="#{not featureController.isFeatureEnabled('change_user_password')}" />
+ </ui:fragment>
- <ui:fragment rendered="#{not userLoginController.isUserLoggedIn()}">
- <ui:include src="/WEB-INF/templates/user/user_not_logged_in.tpl" />
+ <ui:fragment rendered="#{not userLoginController.isUserLoggedIn()}">
+ <ui:include src="/WEB-INF/templates/user/user_not_logged_in.tpl" />
+ </ui:fragment>
</ui:fragment>
</ui:define>
</ui:composition>