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:
config: creates a jar file that declares the extension (war) as a portal dependency.
services: creates services that check if this is the first login of the current user or their current password has expired, and update their new password.
war: creates a war file that provides filter configuration files, locale resources as well as a form for changing password.
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
Create a pom.xml
and a configuration.xml
file as below:
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>
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:
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.
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.
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.
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.
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"><i 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"><i 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
Build your project with the mvn clean install command.
Copy the generated jar and war files into the corresponding deployment folders and start eXo Platform.
Sign in with the root account and create a new user such as john.
Sign in with the john account, you will be required to change password at the first login.
Enter a new password and validate it, then click The new password is not valid" will appear.
. If your new password is valid, you will be automatically redirected to the homepage, otherwise a message that says "