6.3.6. ExtensibleFilter mechanism

Warning

You are looking at documentation for an older release. Not what you want? See the current release documentation.

eXo Platform provides you with the ExtensibleFilter mechanism that allows you to insert more filters from your own extension without touching the web.xml file.

In this section, you will be introduced how to create a filter that requires users to change password at the first login and when it is expired. The source code used in this tutorial is available here so that you can clone.

Our general project will be structured as below:

In which, the sub-projects include:

Now, follow the detailed steps:

Under pom.xml

Add the following dependencies to the pom.xml file:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <artifactId>change-password-extension</artifactId>
  <groupId>org.exoplatform.addons.change-password</groupId>
  <version>1.1.x-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>Change Password Extension</name>
  <description>Change Password Extension</description>
  <modules>
    <module>config</module>
    <module>war</module>
    <module>services</module>
  </modules>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.exoplatform.platform</groupId>
        <artifactId>platform</artifactId>
        <version>4.2.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

Under config folder

  1. Create a pom.xml and a configuration.xml file as below:

  2. Add the following information to config/pom.xml:

    
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <parent>
        <artifactId>change-password-extension</artifactId>
        <groupId>org.exoplatform.addons.change-password</groupId>
        <version>1.1.x-SNAPSHOT</version>
      </parent>
      <artifactId>change-password-extension-config</artifactId>
      <packaging>jar</packaging>
      <name>Change Password Extension Configuration</name>
      <description>Change Password Extension Configuration</description>
    </project>
  3. Add the below configuration to conf/configuration.xml:

    
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"
        xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd">

        <external-component-plugins>
          <!-- The full qualified name of the PortalContainerConfig -->
          <target-component>org.exoplatform.container.definition.PortalContainerConfig</target-component>
          <component-plugin>
            <!-- The name of the plugin -->
            <name>Change PortalContainer Definitions</name>
            <!-- The name of the method to call on the PortalContainerConfig in order to register the changes on the PortalContainerDefinitions -->
            <set-method>registerChangePlugin</set-method>
            <!-- The full qualified name of the PortalContainerDefinitionChangePlugin -->
            <type>org.exoplatform.container.definition.PortalContainerDefinitionChangePlugin</type>
            <priority>102</priority>
            <init-params>
              <value-param>
                <name>apply.default</name>
                <value>true</value>
              </value-param>
              <object-param>
                <name>change</name>
                <object type="org.exoplatform.container.definition.PortalContainerDefinitionChange$AddDependenciesAfter">
                  <!-- The list of name of the dependencies to add -->
                  <field name="dependencies">
                    <collection type="java.util.ArrayList">
                      <value>
                        <string>change-password-extension</string>
                      </value>
                    </collection>
                  </field>
                  <!-- The name of the target dependency -->
                  <field name="target">
                    <string>welcome-screens</string>
                  </field>
                </object>
              </object-param>     
            </init-params>
          </component-plugin>
        </external-component-plugins>   
    </configuration>

Under services folder

This project structure is as follows:

  1. Implement the class ChangePasswordFilter.java as follows:

    package org.exoplatform.changePassword;
    
    
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import javax.servlet.FilterChain;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.exoplatform.container.ExoContainerContext;
    import org.exoplatform.container.PortalContainer;
    import org.exoplatform.services.log.ExoLogger;
    import org.exoplatform.services.log.Log;
    import org.exoplatform.services.organization.OrganizationService;
    import org.exoplatform.services.organization.UserProfile;
    import org.exoplatform.services.organization.UserProfileHandler;
    import org.exoplatform.services.security.ConversationState;
    import org.exoplatform.services.security.Identity;
    import org.exoplatform.web.filter.Filter;
    public class ChangePasswordFilter implements Filter {
        private static Log logger = ExoLogger.getLogger(ChangePasswordFilter.class);
        private static final String CHANGE_PASSWORD_SERVLET_CTX = "/change-password-extension";
        private static final String CHANGE_PASSWORD_SERVLET_URL = "/changePasswordView";
        private static final String INITIAL_URI_PARAM_NAME = "initialURI";
        private static final String REST_URI = ExoContainerContext.getCurrentContainer().getContext().getRestContextName();
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
            HttpServletResponse httpServletResponse = (HttpServletResponse)servletResponse;
            OrganizationService organizationService = (OrganizationService)PortalContainer.getInstance().getComponentInstanceOfType(OrganizationService.class);
            //get current user
            Identity identity = ConversationState.getCurrent().getIdentity();
            String userId = identity.getUserId();
            boolean logged = false;
            boolean passwordChanged = false;
            boolean passwordExpired = false;
            if (!userId.equals("__anonim")) {
                logged = true;
                UserProfileHandler userProfileHandler = organizationService.getUserProfileHandler();
                try {
                    //get current user profile
                    UserProfile userProfile = userProfileHandler.findUserProfileByName(userId);
                    //get password changing status
                    String changePassword = userProfile.getAttribute("changePassword");
                    //get expire password date
                    String expirePasswordDate = userProfile.getAttribute("expirePasswordDate");
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MMM/yyyy");
                    Date today = new Date();
                    //check if the password has been changed
                    if (changePassword != null && changePassword.equals("true")) {
                        passwordChanged = true;
                    }
                    //check if the password has expired
                    passwordExpired = today.after(simpleDateFormat.parse(expirePasswordDate));
                } catch (Exception exception) {
                    logger.error("User profile not found");
                }
            }    
            String requestUri = httpServletRequest.getRequestURI();
            boolean isRestUri = requestUri.contains(REST_URI);
            if (!isRestUri && logged && (!passwordChanged || passwordExpired)) {
                String requestURI = httpServletRequest.getRequestURI();
                String queryString = httpServletRequest.getQueryString();
                if (queryString != null) {
                    requestURI += "?" + queryString;
                }
                //get context for changing password and forward to password changing view servlet
                ServletContext servletContext = httpServletRequest.getSession().getServletContext().getContext(CHANGE_PASSWORD_SERVLET_CTX);
                String targetURI = (new StringBuilder()).append(CHANGE_PASSWORD_SERVLET_URL + "?" + INITIAL_URI_PARAM_NAME + "=").append(requestURI).toString();
                servletContext.getRequestDispatcher(targetURI).forward(httpServletRequest, httpServletResponse);
                return;
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    This filter checks the changePassword attribute from the current user profile as well as the date when his password will expire. If one of these conditions is met, this user will be forwarded to the password changing view servlet in the next step.

  2. Implement the class ChangePasswordViewServlet.java as below:

    package org.exoplatform.changePassword;
    
    
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    public class ChangePasswordViewServlet extends HttpServlet {
        private static final String CHANGE_PASSWORD_JSP_RESOURCE = "/WEB-INF/jsp/changePassword.jsp";
        private static final long serialVersionUID = 1L;
        @Override
        protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
            doPost(httpServletRequest, httpServletResponse);
        }
        
        @Override
        protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
            getServletContext().getRequestDispatcher(CHANGE_PASSWORD_JSP_RESOURCE).include(httpServletRequest, httpServletResponse);
        }
    }

    This servlet simply calls the interface for changing password which is created in this step. After that, when user sends a post request from that interface, the ChangePasswordActionServlet servlet will be initialized. Go to next step to implement this servlet.

  3. Implement the class ChangePasswordActionServlet.java as below:

    package org.exoplatform.changePassword;
    
    
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.exoplatform.container.PortalContainer;
    import org.exoplatform.container.component.RequestLifeCycle;
    import org.exoplatform.services.log.ExoLogger;
    import org.exoplatform.services.log.Log;
    import org.exoplatform.services.organization.OrganizationService;
    import org.exoplatform.services.organization.User;
    import org.exoplatform.services.organization.UserProfile;
    import org.exoplatform.services.organization.UserProfileHandler;
    public class ChangePasswordActionServlet extends HttpServlet {
        private static Log logger = ExoLogger.getLogger(ChangePasswordActionServlet.class);
        private static final long serialVersionUID = 1L;
        private static final String CHANGE_PASSWORD_JSP_RESOURCE = "/WEB-INF/jsp/changePassword.jsp";
        //define the duration (in month) when user password will expire
        private static final int PASSWORD_EXPIRATION_MONTHS_NUMBER = 6;
        
        @Override
        protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
            //get the new password entered
            String newPassword = httpServletRequest.getParameter("newPassword");
            //get the confirmation password
            String reNewPassword = httpServletRequest.getParameter("reNewPassword");
            OrganizationService organizationService = (OrganizationService)PortalContainer.getInstance().getComponentInstanceOfType(OrganizationService.class);
            String userId = httpServletRequest.getRemoteUser();
            try {
                RequestLifeCycle.begin(PortalContainer.getInstance());
                User user = organizationService.getUserHandler().findUserByName(userId);
                //check if the two passwords entered are the same, if not redirect the current user to the password changing view
                if (!newPassword.equals(reNewPassword)) {
                    httpServletRequest.setAttribute("notValidNewPassword", "true");
                    getServletContext().getRequestDispatcher(CHANGE_PASSWORD_JSP_RESOURCE).include(httpServletRequest, httpServletResponse);
                }
                else if (newPassword.length() < 6 || newPassword.length() > 30) {
                    //check if the new password does not meet the requirement
                    httpServletRequest.setAttribute("notCorrectNewPassword", "true");
                    getServletContext().getRequestDispatcher(CHANGE_PASSWORD_JSP_RESOURCE).include(httpServletRequest, httpServletResponse);
                }
                else {
                    //do changing the current password into the new one and reset the related attributes
                    UserProfileHandler userProfileHandler = organizationService.getUserProfileHandler();
                    UserProfile userProfile = userProfileHandler.findUserProfileByName(userId);
                    userProfile.setAttribute("changePassword", "true");
                    Calendar calendar = Calendar.getInstance();
                    String passwordExpirationMonthsNumber = System.getProperty("password.expiration.months.number");
                    calendar.add(Calendar.MONTH, passwordExpirationMonthsNumber != null ? Integer.parseInt(passwordExpirationMonthsNumber) : PASSWORD_EXPIRATION_MONTHS_NUMBER);
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MMM/yyyy");
                    userProfile.setAttribute("expirePasswordDate", simpleDateFormat.format(calendar.getTime()));
                    userProfileHandler.saveUserProfile(userProfile, true);
                    user.setPassword(newPassword);
                    organizationService.getUserHandler().saveUser(user, true);
                    //Redirect to the home page
                    String redirectURI = "/portal/";
                    httpServletResponse.sendRedirect(redirectURI);
                }
            } 
            catch (Exception exception) {
                logger.error("Password not changed");
            } finally {
                RequestLifeCycle.end();
            }
        }
       
        @Override
        protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
            doGet(httpServletRequest, httpServletResponse);
        }
    }

    This servlet verifies the new password whether it meets the minimum and maximum length or not. If yes, the current password will be updated and the related attributes including changePassword and expirePasswordDate will also be reset. Note that the expirePasswordDate attribute will be calculated based on the PASSWORD_EXPIRATION_MONTHS_NUMBER constant.

Under war folder

This war folder will have the following structure:

In which, you will have locale resources in the resources/locale/portal folder, css rules for password changing view in the css/changePassword.css file and other configuration files. In this section, you are going to look at the filter-configuration.xml and changePassword.jsp files. For the other files, you can check from the cloned source code.

  1. Add the below configuration to the filter-configuration.xml file:

    
    <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"
                   xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd">
        <external-component-plugins>
            <target-component>org.exoplatform.web.filter.ExtensibleFilter</target-component>
            <component-plugin profiles="all">
                <name>ChangePassword Filter</name>
                <set-method>addFilterDefinitions</set-method>
                <type>org.exoplatform.web.filter.FilterDefinitionPlugin</type>
                <init-params>
                    <object-param>
                        <name>Change Password Filter</name>
                        <object type="org.exoplatform.web.filter.FilterDefinition">
                            <field name="filter">
                                <object type="org.exoplatform.changePassword.ChangePasswordFilter"/>
                            </field>
                            <field name="patterns">
                                <collection type="java.util.ArrayList" item-type="java.lang.String">
                                    <value>
                                        <string>/*</string>
                                    </value>
                                </collection>
                            </field>
                        </object>
                    </object-param>
                </init-params>
            </component-plugin>
        </external-component-plugins>
    </configuration>

    In which, the patterns field defines which URLs will be passed through this filter, in this case /* means that all URLs are counted.

  2. Add the following code to the changePassword.jsp file:

    <%@ page import="org.exoplatform.container.PortalContainer"%>
    
    <%@ page import="org.exoplatform.services.resources.ResourceBundleService"%>
    <%@ page import="java.util.ResourceBundle"%>
    <%@ page language="java" %>
    <%
      String contextPath = request.getContextPath() ;
      //get locale properties from the locale resource
      ResourceBundleService service = (ResourceBundleService) PortalContainer.getCurrentInstance(session.getServletContext())
                                                            .getComponentInstanceOfType(ResourceBundleService.class);
      ResourceBundle resourceBundle = service.getResourceBundle(service.getSharedResourceBundleNames(), request.getLocale()) ;
      String changePassword = resourceBundle.getString("changePassword.title");
      String newPassword = resourceBundle.getString("changePassword.newPassword");
      String reNewPassword = resourceBundle.getString("changePassword.reNewPassword");
      String send = resourceBundle.getString("changePassword.send");
      String notValidNewPasswordError  = resourceBundle.getString("changePassword.notValidNewPasswordError");
      String notCorrectNewPasswordError  = resourceBundle.getString("changePassword.notCorrectNewPasswordError");
      //get the password validation status
      String notValidNewPassword = (String) request.getAttribute("notValidNewPassword");
      String notCorrectNewPassword = (String) request.getAttribute("notCorrectNewPassword");
      response.setCharacterEncoding("UTF-8"); 
      response.setContentType("text/html; charset=UTF-8");
    %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
            <title>Change password</title>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
            <link href="<%=contextPath%>/css/changePassword.css" rel="stylesheet" type="text/css"/>
        </head>
        <body class="change-password">
            <div class="bg-light"><span></span></div>
            <div class="ui-change-password">
                <div class="change-password-container">
                    <div class="change-password-header intro-box">
                        <div class="change-password-icon"><%=changePassword%></div>
                    </div>
                    <div class="change-password-content">
                        <div class="change-password-title">
                            <%
                                    //check if the new password is not valid
                                    if(notValidNewPassword == "true") {
                            %>
                                        <div class="new-password-error"><class="change-password-icon-error"></i><%=notValidNewPasswordError%></div>
                            <%
                                    }
                                    //check if the password confirmation is not successful
                                    else if(notCorrectNewPassword == "true") {
                            %>
                                    <div class="new-password-error"><class="change-password-icon-error"></i><%=notCorrectNewPasswordError%></div>
                            <%
                                    }
                            %> 
                        </div>
                        <div class="center-change-password-content">
                            <form id="changePasswordForm" name="changePasswordForm" action="<%=contextPath%>/changePassword" method="post">
                                <input  id="newPassword" name="newPassword" type="password" placeholder="<%=newPassword%>" onblur="this.placeholder = <%=newPassword%>" onfocus="this.placeholder = ''"/>
                                <input  id="reNewPassword" name="reNewPassword" type="password" placeholder="<%=reNewPassword%>" onblur="this.placeholder = <%=reNewPassword%>" onfocus="this.placeholder = ''"/>
                                <div id="changePasswordFormAction" class="change-password-button" onclick="submit();">
                                    <button class="button" href="#"><%=send%></button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </body>
    </html>

Testing

  1. Build your project with the mvn clean install command.

  2. Copy the generated jar and war files into the corresponding deployment folders and start eXo Platform.

  3. Sign in with the root account and create a new user such as john.

  4. Sign in with the john account, you will be required to change password at the first login.

  5. Enter a new password and validate it, then click Send. If your new password is valid, you will be automatically redirected to the homepage, otherwise a message that says "The new password is not valid" will appear.

Copyright ©. All rights reserved. eXo Platform SAS
blog comments powered byDisqus