]> git.mxchange.org Git - addressbook-war.git/commitdiff
Continued: (please cherry-pick)
authorRoland Häder <roland@mxchange.org>
Wed, 3 Aug 2016 14:39:23 +0000 (16:39 +0200)
committerRoland Haeder <roland@mxchange.org>
Sat, 6 Aug 2016 21:30:47 +0000 (23:30 +0200)
- 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>
13 files changed:
nbproject/faces-config.NavData
src/java/org/mxchange/addressbook/beans/BaseAddressbookController.java
src/java/org/mxchange/addressbook/beans/features/AddressbookFeatureWebApplicationBean.java
src/java/org/mxchange/addressbook/beans/login/AddressbookUserLoginWebSessionBean.java
src/java/org/mxchange/addressbook/beans/login/AddressbookUserLoginWebSessionController.java
src/java/org/mxchange/addressbook/beans/user/AddressbookUserWebSessionBean.java
src/java/org/mxchange/addressbook/beans/user/AddressbookUserWebSessionController.java
src/java/org/mxchange/addressbook/beans/user/password/AddressbookUserPasswordWebRequestBean.java [new file with mode: 0644]
src/java/org/mxchange/addressbook/beans/user/password/AddressbookUserPasswordWebRequestController.java [new file with mode: 0644]
src/java/org/mxchange/localization/bundle_de_DE.properties
src/java/org/mxchange/localization/bundle_en_US.properties
web/WEB-INF/web.xml
web/user/login_change_password.xhtml

index 4efcbdfbcae8b247765c6ea5d85fb458e64beeb8..ea458f2c7eeb5ea33f3fc910d7090d2988a86257 100644 (file)
@@ -2,72 +2,73 @@
 <Scene Scope="Project" version="2">
     <Scope Scope="Faces Configuration Only"/>
     <Scope Scope="Project">
-        <Node id="admin/cellphone/admin_contact_cellphone_list.xhtml" x="1400" y="450" zoom="true"/>
-        <Node id="admin/contact/admin_contact_show.xhtml" x="1150" y="300" zoom="true"/>
-        <Node id="privacy.xhtml" x="1650" y="750" zoom="true"/>
-        <Node id="guest/user/show_addressbook.xhtml" x="150" y="1200" zoom="true"/>
-        <Node id="admin/mobile_provider/admin_mobile_provider_show.xhtml" x="1650" y="600" zoom="true"/>
-        <Node id="admin/user/admin_user_show.xhtml" x="150" y="600" zoom="true"/>
-        <Node id="admin/index.xhtml" x="1150" y="450" zoom="true"/>
-        <Node id="user/login_own_addressbooks.xhtml" x="650" y="1050" zoom="true"/>
-        <Node id="user/login_user_data_saved.xhtml" x="900" y="150" zoom="true"/>
-        <Node id="user/index.xhtml" x="150" y="900" zoom="true"/>
-        <Node id="admin/admin_logout.xhtml" x="1150" y="1050" zoom="true"/>
-        <Node id="user/login_data_saved.xhtml" x="1150" y="750" zoom="true"/>
-        <Node id="admin/user/admin_user_unlock.xhtml" x="400" y="900" zoom="true"/>
-        <Node id="user/login_other_addressbooks.xhtml" x="150" y="1350" zoom="true"/>
-        <Node id="user/login_start_sharing_addressbook.xhtml" x="2400" y="300" zoom="true"/>
-        <Node id="index.xhtml" x="400" y="300" zoom="true"/>
-        <Node id="guest/user/user_list.xhtml" x="1400" y="600" zoom="true"/>
-        <Node id="user/login_edit_user_data.xhtml" x="900" y="900" zoom="true"/>
-        <Node id="*" x="150" y="300" zoom="true"/>
-        <Node id="admin/admin_category_delete.xhtml" x="2150" y="450" zoom="true"/>
-        <Node id="admin/cellphone/admin_contact_cellphone_edit.xhtml" x="400" y="450" zoom="true"/>
-        <Node id="user/login_index.xhtml" x="1650" y="150" zoom="true"/>
-        <Node id="guest/user/lost_passwd.xhtml" x="400" y="600" zoom="true"/>
-        <Node id="guest/user/register_done.xhtml" x="150" y="750" zoom="true"/>
-        <Node id="guest/user/show_addressbook_entries.xhtml" x="1150" y="150" zoom="true"/>
-        <Node id="user/login.xhtml" x="650" y="1200" zoom="true"/>
-        <Node id="admin/cellphone/admin_contact_cellphone_unlink.xhtml" x="1150" y="600" zoom="true"/>
-        <Node id="login/login_edit_user_data.xhtml" x="1900" y="300" zoom="true"/>
-        <Node id="admin/country/admin_country_list.xhtml" x="400" y="1500" zoom="true"/>
-        <Node id="guest/user/login_error.xhtml" x="400" y="1200" zoom="true"/>
-        <Node id="admin/user/admin_user_export.xhtml" x="1150" y="900" zoom="true"/>
-        <Node id="admin/mobile_provider/admin_mobile_provider_delete.xhtml" x="1900" y="150" zoom="true"/>
-        <Node id="guest/user/resend_done.xhtml" x="2150" y="150" zoom="true"/>
-        <Node id="user/login_change_password.xhtml" x="150" y="1650" zoom="true"/>
-        <Node id="admin/mobile_provider/admin_mobile_provider_list.xhtml" x="150" y="150" zoom="true"/>
-        <Node id="admin/contact/admin_contact_export.xhtml" x="650" y="150" zoom="true"/>
-        <Node id="guest/user/user_profile.xhtml" x="400" y="750" zoom="true"/>
-        <Node id="guest/user/confirm_account.xhtml" x="650" y="1350" zoom="true"/>
-        <Node id="admin/user/admin_user_delete.xhtml" x="900" y="750" zoom="true"/>
-        <Node id="admin/cellphone/admin_contact_cellphone_delete.xhtml" x="2150" y="300" zoom="true"/>
-        <Node id="user/login_list_sharing_addressbooks.xhtml" x="1400" y="750" zoom="true"/>
-        <Node id="exception.xhtml" x="900" y="1050" zoom="true"/>
-        <Node id="login/login_index.xhtml" x="400" y="1050" zoom="true"/>
-        <Node id="guest/user/register_page2.xhtml" x="650" y="600" zoom="true"/>
-        <Node id="admin/user/admin_user_edit.xhtml" x="1900" y="600" zoom="true"/>
-        <Node id="terms.xhtml" x="900" y="600" zoom="true"/>
-        <Node id="admin/contact/admin_contact_list.xhtml" x="150" y="450" zoom="true"/>
-        <Node id="admin/contact/admin_contact_edit.xhtml" x="150" y="1500" zoom="true"/>
-        <Node id="admin/user/admin_user_list.xhtml" x="1400" y="300" zoom="true"/>
-        <Node id="admin/country/admin_country_delete.xhtml" x="400" y="150" zoom="true"/>
-        <Node id="user/login_change_personal_data.xhtml" x="1900" y="450" zoom="true"/>
-        <Node id="logout.xhtml" x="900" y="450" zoom="true"/>
-        <Node id="admin/country/admin_country_edit.xhtml" x="650" y="900" zoom="true"/>
-        <Node id="guest/user/resend_link.xhtml" x="650" y="450" zoom="true"/>
-        <Node id="user/login_add_addressbook.xhtml" x="1400" y="900" zoom="true"/>
-        <Node id="user/login_contact_data_saved.xhtml" x="400" y="1350" zoom="true"/>
-        <Node id="user/user_profile.xhtml" x="900" y="300" zoom="true"/>
-        <Node id="admin/contact/admin_contact_delete.xhtml" x="650" y="750" zoom="true"/>
-        <Node id="imprint.xhtml" x="1650" y="300" zoom="true"/>
-        <Node id="user/login_shared_addressbooks.xhtml" x="650" y="300" zoom="true"/>
-        <Node id="admin/mobile_provider/admin_mobile_provider_edit.xhtml" x="1400" y="150" zoom="true"/>
-        <Node id="admin/cellphone/admin_contact_cellphone_show.xhtml" x="900" y="1200" zoom="true"/>
-        <Node id="admin/admin_product_delete.xhtml" x="150" y="1050" zoom="true"/>
-        <Node id="guest/user/login.xhtml" x="1650" y="450" zoom="true"/>
-        <Node id="guest/user/register.xhtml" x="2650" y="150" zoom="true"/>
-        <Node id="user/login_change_email_address.xhtml" x="2400" y="150" zoom="true"/>
+        <Node id="admin/cellphone/admin_contact_cellphone_list.xhtml" x="400" y="600" zoom="true"/>
+        <Node id="user/login_logout.xhtml" x="1150" y="900" zoom="true"/>
+        <Node id="admin/contact/admin_contact_show.xhtml" x="400" y="1050" zoom="true"/>
+        <Node id="privacy.xhtml" x="400" y="450" zoom="true"/>
+        <Node id="admin/mobile_provider/admin_mobile_provider_show.xhtml" x="150" y="1200" zoom="true"/>
+        <Node id="guest/user/show_addressbook.xhtml" x="400" y="1350" zoom="true"/>
+        <Node id="admin/user/admin_user_show.xhtml" x="900" y="1200" zoom="true"/>
+        <Node id="admin/index.xhtml" x="650" y="450" zoom="true"/>
+        <Node id="user/login_own_addressbooks.xhtml" x="1400" y="600" zoom="true"/>
+        <Node id="user/login_user_data_saved.xhtml" x="1900" y="300" zoom="true"/>
+        <Node id="user/index.xhtml" x="900" y="900" zoom="true"/>
+        <Node id="admin/admin_logout.xhtml" x="650" y="600" zoom="true"/>
+        <Node id="user/login_data_saved.xhtml" x="900" y="600" zoom="true"/>
+        <Node id="admin/user/admin_user_unlock.xhtml" x="150" y="150" zoom="true"/>
+        <Node id="user/login_other_addressbooks.xhtml" x="150" y="750" zoom="true"/>
+        <Node id="user/login_start_sharing_addressbook.xhtml" x="900" y="750" zoom="true"/>
+        <Node id="index.xhtml" x="2650" y="150" zoom="true"/>
+        <Node id="guest/user/user_list.xhtml" x="400" y="1500" zoom="true"/>
+        <Node id="user/login_edit_user_data.xhtml" x="2400" y="300" zoom="true"/>
+        <Node id="admin/admin_category_delete.xhtml" x="1650" y="150" zoom="true"/>
+        <Node id="*" x="1650" y="600" zoom="true"/>
+        <Node id="user/login_index.xhtml" x="2400" y="150" zoom="true"/>
+        <Node id="admin/cellphone/admin_contact_cellphone_edit.xhtml" x="1900" y="450" zoom="true"/>
+        <Node id="guest/user/lost_passwd.xhtml" x="150" y="1650" zoom="true"/>
+        <Node id="guest/user/register_done.xhtml" x="650" y="1050" zoom="true"/>
+        <Node id="guest/user/show_addressbook_entries.xhtml" x="1900" y="150" zoom="true"/>
+        <Node id="user/login.xhtml" x="150" y="450" 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="2150" y="150" zoom="true"/>
+        <Node id="admin/country/admin_country_list.xhtml" x="1650" y="750" zoom="true"/>
+        <Node id="guest/user/login_error.xhtml" x="2150" y="450" zoom="true"/>
+        <Node id="admin/user/admin_user_export.xhtml" x="650" y="1350" zoom="true"/>
+        <Node id="admin/mobile_provider/admin_mobile_provider_delete.xhtml" x="1150" y="450" zoom="true"/>
+        <Node id="guest/user/resend_done.xhtml" x="1150" y="750" zoom="true"/>
+        <Node id="user/login_change_password.xhtml" x="400" y="750" zoom="true"/>
+        <Node id="admin/mobile_provider/admin_mobile_provider_list.xhtml" x="150" y="900" zoom="true"/>
+        <Node id="admin/contact/admin_contact_export.xhtml" x="1650" y="450" zoom="true"/>
+        <Node id="guest/user/user_profile.xhtml" x="2900" y="150" zoom="true"/>
+        <Node id="guest/user/confirm_account.xhtml" x="1150" y="600" zoom="true"/>
+        <Node id="admin/cellphone/admin_contact_cellphone_delete.xhtml" x="650" y="300" zoom="true"/>
+        <Node id="admin/user/admin_user_delete.xhtml" x="900" y="300" zoom="true"/>
+        <Node id="exception.xhtml" x="2150" y="300" zoom="true"/>
+        <Node id="user/login_list_sharing_addressbooks.xhtml" x="900" y="1050" zoom="true"/>
+        <Node id="login/login_index.xhtml" x="150" y="1050" zoom="true"/>
+        <Node id="admin/user/admin_user_edit.xhtml" x="400" y="300" zoom="true"/>
+        <Node id="guest/user/register_page2.xhtml" x="650" y="750" zoom="true"/>
+        <Node id="terms.xhtml" x="400" y="150" zoom="true"/>
+        <Node id="admin/contact/admin_contact_edit.xhtml" x="650" y="1200" zoom="true"/>
+        <Node id="admin/contact/admin_contact_list.xhtml" x="1400" y="900" zoom="true"/>
+        <Node id="admin/user/admin_user_list.xhtml" x="150" y="1500" zoom="true"/>
+        <Node id="admin/country/admin_country_delete.xhtml" x="1150" y="300" zoom="true"/>
+        <Node id="user/login_change_personal_data.xhtml" x="1150" y="150" zoom="true"/>
+        <Node id="logout.xhtml" x="150" y="600" zoom="true"/>
+        <Node id="admin/country/admin_country_edit.xhtml" x="1400" y="750" zoom="true"/>
+        <Node id="user/login_add_addressbook.xhtml" x="1400" y="150" zoom="true"/>
+        <Node id="guest/user/resend_link.xhtml" x="400" y="1200" zoom="true"/>
+        <Node id="user/login_contact_data_saved.xhtml" x="900" y="450" zoom="true"/>
+        <Node id="admin/contact/admin_contact_delete.xhtml" x="1400" y="300" zoom="true"/>
+        <Node id="user/user_profile.xhtml" x="1400" y="450" zoom="true"/>
+        <Node id="imprint.xhtml" x="900" y="150" zoom="true"/>
+        <Node id="user/login_shared_addressbooks.xhtml" x="650" y="150" zoom="true"/>
+        <Node id="admin/mobile_provider/admin_mobile_provider_edit.xhtml" x="650" y="900" zoom="true"/>
+        <Node id="admin/cellphone/admin_contact_cellphone_show.xhtml" x="1650" y="300" zoom="true"/>
+        <Node id="guest/user/login.xhtml" x="150" y="1350" zoom="true"/>
+        <Node id="admin/admin_product_delete.xhtml" x="1900" y="600" zoom="true"/>
+        <Node id="guest/user/register.xhtml" x="1150" y="1050" zoom="true"/>
+        <Node id="user/login_change_email_address.xhtml" x="400" y="900" zoom="true"/>
     </Scope>
     <Scope Scope="All Faces Configurations"/>
 </Scene>
index 4b9c3ffc355d3f200496a08c57dcd019692d92ad..f2f07ba62dff6e71a313926744772b59c37b58a3 100644 (file)
@@ -75,4 +75,45 @@ public abstract class BaseAddressbookController implements Serializable {
                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;
+       }
+
 }
index a4171f1256c97069b4cff80f8ec7118727595764..ec9d6eb4b8d3815d63e3ae8b26556d678fe526c2 100644 (file)
@@ -17,7 +17,6 @@
 package org.mxchange.addressbook.beans.features;
 
 import javax.enterprise.context.ApplicationScoped;
-import javax.faces.context.FacesContext;
 import javax.inject.Named;
 import org.mxchange.addressbook.beans.BaseAddressbookController;
 
@@ -46,11 +45,19 @@ public class AddressbookFeatureWebApplicationBean extends BaseAddressbookControl
                        throw new IllegalArgumentException("feature is empty"); //NOI18N
                }
 
-               // Get context parameter
-               String contextParameter = FacesContext.getCurrentInstance().getExternalContext().getInitParameter(String.format("is_feature_%s_enabled", feature)); //NOI18N
+               // Default is not enabled
+               boolean isEnabled = false;
 
-               // Is it set?
-               boolean isEnabled = ((contextParameter instanceof String) && (contextParameter.toLowerCase().equals("true"))); //NOI18N
+               // 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;
index 7b348adab7f19f36ada4f9266331c266b0f7b793..bcd5ba28850f075cc5e51980ec8eff43a5af764c 100644 (file)
@@ -22,6 +22,7 @@ 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;
@@ -38,6 +39,7 @@ import org.mxchange.jusercore.events.login.UserLoggedInEvent;
 import org.mxchange.jusercore.events.login.UserLoginEvent;
 import org.mxchange.jusercore.events.logout.ObserveableUserLogoutEvent;
 import org.mxchange.jusercore.events.logout.UserLogoutEvent;
+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;
@@ -152,6 +154,27 @@ public class AddressbookUserLoginWebSessionBean extends BaseAddressbookControlle
                }
        }
 
+       @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.updatePasswordHistory(event.getPasswordHistory());
+       }
+
        @Override
        public String doAdminLogout () {
                // Is a user logged-in?
@@ -308,6 +331,35 @@ public class AddressbookUserLoginWebSessionBean extends BaseAddressbookControlle
                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 () {
                this.userLoggedIn = ((this.getLoggedInUser() instanceof User) && (Objects.equals(this.getLoggedInUser().getUserAccountStatus(), UserAccountStatus.CONFIRMED)));
@@ -324,4 +376,41 @@ public class AddressbookUserLoginWebSessionBean extends BaseAddressbookControlle
                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);
+       }
+
 }
index ff4b487a5ba0d2a2af4e193bc881d54355fc3688..afcf0d26b242230928514860c2bf3a0ad111e7aa 100644 (file)
@@ -19,6 +19,7 @@ package org.mxchange.addressbook.beans.login;
 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;
 
@@ -30,6 +31,23 @@ import org.mxchange.jusercore.model.user.password_history.PasswordHistory;
 @Local
 public interface AddressbookUserLoginWebSessionController 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>
index 7a380cfa8d42d34ac06b0bc432cfc0b30d997106..bcf49084bfcc7592604b23cf0db06d4c351c23dd 100644 (file)
@@ -44,6 +44,7 @@ import org.mxchange.jusercore.events.confirmation.UserConfirmedAccountEvent;
 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;
@@ -345,6 +346,27 @@ public class AddressbookUserWebSessionBean extends BaseAddressbookController imp
                }
        }
 
+       @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
@@ -471,7 +493,7 @@ public class AddressbookUserWebSessionBean extends BaseAddressbookController imp
                        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
+                       throw new IllegalStateException("User tried to edit personal data."); //NOI18N
                }
 
                // Get user instance
@@ -498,7 +520,7 @@ public class AddressbookUserWebSessionBean extends BaseAddressbookController imp
                this.updatedPersonalDataEvent.fire(new UserUpdatedPersonalDataEvent(updatedUser));
 
                // All fine
-               return "user_data_saved"; //NOI18N
+               return "contact_data_saved"; //NOI18N
        }
 
        @Override
index e7813b3cc458927e4f995a134bce9967bdbe29a5..68537e34ae7f73bd7e1c8f5df2d235ac0e01b759 100644 (file)
@@ -23,6 +23,7 @@ import org.mxchange.jusercore.events.confirmation.UserConfirmedAccountEvent;
 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;
@@ -63,6 +64,14 @@ public interface AddressbookUserWebSessionController extends Serializable {
         */
        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>
diff --git a/src/java/org/mxchange/addressbook/beans/user/password/AddressbookUserPasswordWebRequestBean.java b/src/java/org/mxchange/addressbook/beans/user/password/AddressbookUserPasswordWebRequestBean.java
new file mode 100644 (file)
index 0000000..5ebe15d
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * 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.addressbook.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 AddressbookUserPasswordWebRequestBean extends BaseAddressbookController implements AddressbookUserPasswordWebRequestController {
+
+       /**
+        * 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 AddressbookUserPasswordWebRequestBean () {
+               // 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);
+       }
+
+}
diff --git a/src/java/org/mxchange/addressbook/beans/user/password/AddressbookUserPasswordWebRequestController.java b/src/java/org/mxchange/addressbook/beans/user/password/AddressbookUserPasswordWebRequestController.java
new file mode 100644 (file)
index 0000000..ca3881d
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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.addressbook.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 AddressbookUserPasswordWebRequestController 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 ();
+
+}
index 95ac576319433175374de5aaa277d35d11a12ea2..141e5617438c81e6d66a6e96d7b57c3646fed035 100644 (file)
@@ -617,3 +617,6 @@ ADMIN_MOBILE_PROVIDER_DIAL_PREFIX_REQUIRED=Bitte geben Sie die Vorwahl ohne f\u0
 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.
index 84f48d801c9cca0689cfa19fc49b8610dd30a77e..13a9712e517411dd970fbc338290623ff8d0c1a1 100644 (file)
@@ -614,3 +614,6 @@ ADMIN_MOBILE_PROVIDER_DIAL_PREFIX_REQUIRED=Please enter dial prefix for mobile p
 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.
index 4a5a199786bbd554c1f925304014dfce65e6ef9d..536cec40729df8b4a7ed46d1b33f9543ad83c724 100644 (file)
         <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>
index fe0e40369bc8f39fa87504466cbf47857ffca77f..847c662334024ff2ab811d0b9d644da3fa7a8785 100644 (file)
@@ -16,7 +16,7 @@
 
                <ui:define name="content">
                        <ui:fragment rendered="#{userLoginController.isUserLoggedIn()}">
-                               <ui:fragment rendered="#{featureController.isFeatureEnabled('edit_user_data')}">
+                               <ui:fragment rendered="#{featureController.isFeatureEnabled('change_user_password')}">
                                        <div class="table">
                                                <div class="table_header">
                                                        <h:outputText value="#{msg.LOGIN_CHANGE_PASSWORD_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>
+
+                                                                               <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="clear"></div>
+                                                                       </div>
+
+                                                                       <div class="error_container">
+                                                                               <h:message for="userCurrentPassword" errorClass="errors" fatalClass="errors" warnClass="errors" />
+                                                                       </div>
+
                                                                        <div class="table_row">
                                                                                <div class="table_left">
                                                                                        <h:outputLabel for="userPassword" value="#{msg.GUEST_REGISTRATION_ENTER_PASSWORD}" />
                                                                                </div>
 
                                                                                <div class="table_right">
-                                                                                       <h:inputSecret styleClass="input" id="userPassword" size="10" maxlength="255" value="#{userLoginController.userPassword}" required="true" />
+                                                                                       <h:inputSecret styleClass="input" id="userPassword" size="10" maxlength="255" value="#{userPasswordController.userPassword}" required="true" requiredMessage="#{msg.USER_NEW_PASSWORD_REQUIRED}" />
                                                                                </div>
 
                                                                                <div class="clear"></div>
                                                                        </div>
 
+                                                                       <div class="error_container">
+                                                                               <h:message for="userPassword" errorClass="errors" fatalClass="errors" warnClass="errors" />
+                                                                       </div>
+
                                                                        <div class="table_row">
                                                                                <div class="table_left">
                                                                                        <h:outputLabel for="userPasswordRepeat" value="#{msg.GUEST_REGISTRATION_ENTER_PASSWORD_REPEAT}" />
                                                                                </div>
 
                                                                                <div class="table_right">
-                                                                                       <h:inputSecret styleClass="input" id="userPasswordRepeat" size="10" maxlength="255" value="#{userLoginController.userPasswordRepeat}" required="true" />
+                                                                                       <h:inputSecret styleClass="input" id="userPasswordRepeat" size="10" maxlength="255" value="#{userPasswordController.userPasswordRepeat}" required="true" requiredMessage="#{msg.USER_NEW_PASSWORD_REPEAT_REQUIRED}" />
                                                                                </div>
 
                                                                                <div class="clear"></div>
                                                                        </div>
+
+                                                                       <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="#{userController.doChangePassword()}" />
+                                                               <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('edit_user_data')}" />
+                                       <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()}">