浏览代码

Merge pull request #2055 from wangkx/password_expiration

Add password expiration reminder to ESP

Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 13 年之前
父节点
当前提交
992ba5b9ad

+ 1 - 0
esp/bindings/SOAP/soaplib/CMakeLists.txt

@@ -51,6 +51,7 @@ include_directories (
          ./../../../../system/include 
          ./../../../../system/security/shared
          ./../../../../system/security/securesocket 
+         ./../../../../system/security/LdapSecurity
          ./../../../../system/jlib 
          ./../../../bindings 
          ./../../../platform 

+ 39 - 0
esp/bindings/http/platform/httpservice.cpp

@@ -346,8 +346,20 @@ int CEspHttpServer::processRequest()
                     return onGetNavEvent(m_request.get(), m_response.get());
                 else if (!stricmp(methodName.str(), "soapreq"))
                     return onGetBuildSoapRequest(m_request.get(), m_response.get());
+#ifdef _USE_OPENLDAP
+                else if (strieq(methodName.str(), "updatepasswordinput"))
+                    return onUpdatePasswordInput(m_request.get(), m_response.get());
+#endif
             }
         }
+#ifdef _USE_OPENLDAP
+        else if (strieq(method.str(), POST_METHOD) && strieq(serviceName.str(), "esp") && (methodName.length() > 0) && strieq(methodName.str(), "updatepassword"))
+        {
+            if (!rootAuth(ctx))
+                return 0;
+            return onUpdatePassword(m_request.get(), m_response.get());
+        }
+#endif
 
         if(m_apport != NULL)
         {
@@ -571,6 +583,33 @@ int CEspHttpServer::onGetBuildSoapRequest(CHttpRequest* request, CHttpResponse*
     return 0;
 }
 
+#ifdef _USE_OPENLDAP
+int CEspHttpServer::onUpdatePasswordInput(CHttpRequest* request, CHttpResponse* response)
+{
+    StringBuffer html;
+    m_apport->onUpdatePasswordInput(*request->queryContext(), html);
+    response->setContent(html.length(), html.str());
+    response->setContentType("text/html; charset=UTF-8");
+    response->setStatus(HTTP_STATUS_OK);
+
+    response->send();
+
+    return 0;
+}
+
+int CEspHttpServer::onUpdatePassword(CHttpRequest* request, CHttpResponse* response)
+{
+    StringBuffer html;
+    m_apport->onUpdatePassword(*request->queryContext(), request, html);
+    response->setContent(html.length(), html.str());
+    response->setContentType("text/html; charset=UTF-8");
+    response->setStatus(HTTP_STATUS_OK);
+
+    response->send();
+    return 0;
+}
+#endif
+
 int CEspHttpServer::onGetMainWindow(CHttpRequest* request, CHttpResponse* response)
 {
     StringBuffer url("../?main");

+ 4 - 1
esp/bindings/http/platform/httpservice.hpp

@@ -79,7 +79,10 @@ public:
     virtual int onGetNavEvent(CHttpRequest* request, CHttpResponse* response);
     virtual int onGetMainWindow(CHttpRequest* request, CHttpResponse* response);
     virtual int onRunCGI(CHttpRequest* request, CHttpResponse* response, const char *path);
-
+#ifdef _USE_OPENLDAP
+    virtual int onUpdatePasswordInput(CHttpRequest* request, CHttpResponse* response);
+    virtual int onUpdatePassword(CHttpRequest* request, CHttpResponse* response);
+#endif
 
 
     virtual const char * getServiceType() {return "HttpServer";};

+ 131 - 3
esp/platform/espprotocol.cpp

@@ -27,6 +27,9 @@
 #include "platform.h"
 #include "espprotocol.hpp"
 #include "espbinding.hpp"
+#ifdef _USE_OPENLDAP
+#include "ldapsecurity.ipp"
+#endif
 
 typedef IXslProcessor * (*getXslProcessor_func)();
 
@@ -126,6 +129,18 @@ const StringBuffer &CEspApplicationPort::getAppFrameHtml(time_t &modified, const
 
     if (needRefresh || embedded_url || !appFrameHtml.length())
     {
+        int passwordDaysRemaining = -1;
+#ifdef _USE_OPENLDAP
+        ISecUser* user = ctx->queryUser();
+        ISecManager* secmgr = ctx->querySecManager();
+        if(user && secmgr)
+        {
+            passwordDaysRemaining = user->getPasswordDaysRemaining();
+            unsigned passwordExpirationDays = secmgr->getPasswordExpirationWarningDays();
+            if ((unsigned) passwordDaysRemaining > passwordExpirationDays)
+                passwordDaysRemaining = -1;
+        }
+#endif
         StringBuffer xml;
         StringBuffer encoded_inner;
         if(inner && *inner)
@@ -134,8 +149,9 @@ const StringBuffer &CEspApplicationPort::getAppFrameHtml(time_t &modified, const
         // replace & with &amps;
         params.replaceString("&","&amp;");
 
-        xml.appendf("<EspApplicationFrame title=\"%s\" navWidth=\"%d\" navResize=\"%d\" navScroll=\"%d\" inner=\"%s\" params=\"%s\"/>", 
-            getESPContainer()->getFrameTitle(), navWidth, navResize, navScroll, (inner&&*inner) ? encoded_inner.str() : "?main", params.str());
+        xml.appendf("<EspApplicationFrame title=\"%s\" navWidth=\"%d\" navResize=\"%d\" navScroll=\"%d\" inner=\"%s\" params=\"%s\" passwordDays=\"%d\"/>",
+            getESPContainer()->getFrameTitle(), navWidth, navResize, navScroll, (inner&&*inner) ? encoded_inner.str() : "?main", params.str(), passwordDaysRemaining);
+
         Owned<IXslTransform> xform = xslp->createXslTransform();
         xform->loadXslFromFile(StringBuffer(getCFD()).append("./xslt/appframe.xsl").str());
         xform->setXmlSource(xml.str(), xml.length()+1);
@@ -149,7 +165,6 @@ const StringBuffer &CEspApplicationPort::getAppFrameHtml(time_t &modified, const
     modified = startup_time;
     return html;
 }
-    
 
 const StringBuffer &CEspApplicationPort::getTitleBarHtml(IEspContext& ctx, bool rawXml)
 {
@@ -511,6 +526,119 @@ void CEspBinding::getDynNavData(IEspContext &context, IProperties *params, IProp
 {
 }
 
+#ifdef _USE_OPENLDAP
+void CEspApplicationPort::onUpdatePasswordInput(IEspContext &context, StringBuffer& html)
+{
+    StringBuffer xml;
+    if (context.queryUserId())
+        xml.appendf("<UpdatePassword><username>%s</username><Code>-1</Code></UpdatePassword>", context.queryUserId());
+    else
+        xml.appendf("<UpdatePassword><Code>2</Code><Massage>Can't find user in esp context. Please check if the user was properly logged in.</Massage></UpdatePassword>");
+    Owned<IXslTransform> xform = xslp->createXslTransform();
+    xform->loadXslFromFile(StringBuffer(getCFD()).append("./xslt/passwordupdate.xsl").str());
+    xform->setXmlSource(xml.str(), xml.length()+1);
+    xform->transform( html);
+    return;
+}
+
+void CEspApplicationPort::onUpdatePassword(IEspContext &context, IHttpMessage* request, StringBuffer& html)
+{
+    StringBuffer xml, message;
+    unsigned returnCode = updatePassword(context, request, message);
+
+    if (context.queryUserId())
+        xml.appendf("<UpdatePassword><username>%s</username>", context.queryUserId());
+    else
+        xml.appendf("<UpdatePassword><username/>");
+    xml.appendf("<Code>%d</Code><Message>%s</Message></UpdatePassword>", returnCode, message.str());
+
+    Owned<IXslTransform> xform = xslp->createXslTransform();
+    xform->loadXslFromFile(StringBuffer(getCFD()).append("./xslt/passwordupdate.xsl").str());
+    xform->setXmlSource(xml.str(), xml.length()+1);
+    xform->transform( html);
+    return;
+}
+
+unsigned CEspApplicationPort::updatePassword(IEspContext &context, IHttpMessage* request, StringBuffer& message)
+{
+    ISecManager* secmgr = context.querySecManager();
+    if(!secmgr)
+    {
+        message.append("Security manager is not found. Please check if the system authentication is set up correctly.");
+        return 2;
+    }
+
+    ISecUser* user = context.queryUser();
+    if(!user)
+    {
+        message.append("Can't find user in esp context. Please check if the user was properly logged in.");
+        return 2;
+    }
+
+    const char* oldpass1 = context.queryPassword();
+    if (!oldpass1)
+    {
+        message.append("Existing password missing from request.");
+        return 2;
+    }
+
+    CHttpRequest *httpRequest=dynamic_cast<CHttpRequest*>(request);
+    IProperties *params = httpRequest->getParameters();
+    if (!params)
+    {
+        message.append("No parameter is received. Please check user input.");
+        return 1;
+    }
+
+    const char* username = params->queryProp("username");
+    const char* oldpass = params->queryProp("oldpass");
+    const char* newpass1 = params->queryProp("newpass1");
+    const char* newpass2 = params->queryProp("newpass2");
+    if(!username || !streq(username, user->getName()))
+    {
+        message.append("Incorrect username has been received.");
+        return 1;
+    }
+    if(!oldpass || !streq(oldpass, oldpass1))
+    {
+        message.append("Old password doesn't match credentials in use.");
+        return 1;
+    }
+    if(!streq(newpass1, newpass2))
+    {
+        message.append("Password re-entry doesn't match.");
+        return 1;
+    }
+    if(streq(oldpass, newpass1))
+    {
+        message.append("New password can't be the same as current password.");
+        return 1;
+    }
+
+    bool returnFlag = false;
+    try
+    {
+        returnFlag = secmgr->updateUser(*user, newpass1);
+    }
+    catch(IException* e)
+    {
+        StringBuffer emsg;
+        e->errorMessage(emsg);
+        message.append(emsg.str());
+        return 2;
+    }
+
+    if(!returnFlag)
+    {
+        message.append("Failed in changing password.");
+        return 2;
+    }
+
+    message.append("Your password has been changed successfully.");
+    return 0;
+}
+#endif
+
 CEspProtocol::CEspProtocol() 
 {
    m_viewConfig=false;

+ 5 - 0
esp/platform/espprotocol.hpp

@@ -126,6 +126,11 @@ public:
 
     CEspBindingEntry* queryBindingItem(int item){return (item<bindingCount) ? bindings[item] : NULL;}
     CEspBindingEntry* getDefaultBinding(){return bindings[(defBinding>=0) ? defBinding : 0];}
+#ifdef _USE_OPENLDAP
+    unsigned updatePassword(IEspContext &context, IHttpMessage* request, StringBuffer& message);
+    void onUpdatePasswordInput(IEspContext &context, StringBuffer &html);
+    void onUpdatePassword(IEspContext &context, IHttpMessage* request, StringBuffer& html);
+#endif
 };
 
 typedef map<int, CEspApplicationPort*> CApplicationPortMap;

+ 4 - 0
esp/protocols/http/CMakeLists.txt

@@ -59,6 +59,7 @@ include_directories (
          ./../../../system/jlib 
          ./../../platform 
          ./../../../system/security/shared 
+         ./../../../system/security/LdapSecurity
     )
 
 ADD_DEFINITIONS( -DESPHTTP_EXPORTS -DESP_TIMING -D_USRDLL -DESP_PLUGIN )
@@ -71,5 +72,8 @@ target_link_libraries ( esphttp
          jlib
          xmllib 
     )
+IF (USE_OPENLDAP)
+target_link_libraries ( esphttp LdapSecurity )
+ENDIF(USE_OPENLDAP)
 
 

+ 1 - 0
esp/xslt/CMakeLists.txt

@@ -43,6 +43,7 @@ FOREACH( iFILES
     ${CMAKE_CURRENT_SOURCE_DIR}/wsecl3_xmltest.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/wsecl3_jsontest.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/wsecl3_result.xslt
+    ${CMAKE_CURRENT_SOURCE_DIR}/passwordupdate.xsl
 )
     Install ( FILES ${iFILES} DESTINATION ${OSSDIR}/componentfiles/xslt COMPONENT Runtime )
 ENDFOREACH ( iFILES )

+ 35 - 1
esp/xslt/appframe.xsl

@@ -28,8 +28,42 @@
         <link rel="stylesheet" type="text/css" href="/esp/files/yui/build/fonts/fonts-min.css" />
         <link rel="stylesheet" type="text/css" href="/esp/files/css/espdefault.css" />
         <link rel="shortcut icon" href="/esp/files/img/favicon.ico" />
+        <script language="JavaScript1.2" id="menuhandlers">
+            var passwordDays='<xsl:value-of select="@passwordDays"/>';
+            <xsl:text disable-output-escaping="yes"><![CDATA[
+                var passwordCookie = "ESP Password Cookie";
+                function areCookiesEnabled() {
+	                var cookieEnabled = (navigator.cookieEnabled) ? true : false;
+	                if (typeof navigator.cookieEnabled == "undefined" && !cookieEnabled) {
+		                document.cookie="testcookie";
+		                cookieEnabled = (document.cookie.indexOf("testcookie") != -1) ? true : false;
+	                }
+	                return (cookieEnabled);
+                }
+                function updatePassword() {
+                    var dt = new Date();
+                    dt.setDate(dt.getDate() + 1);
+                    var exdate = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
+                    document.cookie = passwordCookie + "=1; expires=" + exdate.toUTCString() + "; path=/";
+
+                    var msg = 'Your password will be expired in ' + passwordDays + ' day(s). Do you want to change it now?'
+                    if (confirm(msg)) {
+                        var mywindow = window.open('/esp/updatepasswordinput', 'UpdatePassword', 'toolbar=0,location=no,titlebar=0,status=0,directories=0,menubar=0', true);
+                        if (mywindow.opener == null)
+                            mywindow.opener = window;
+                        mywindow.focus();
+                    }
+                    return true;
+                }
+                function onLoad() {
+                    if ((passwordDays > -1) && areCookiesEnabled() && (document.cookie == '' || (document.cookie.indexOf(passwordCookie) == -1))) {
+                        updatePassword();
+                    }
+                }
+            ]]></xsl:text>
+        </script>
       </head>
-      <frameset rows="62,*" FRAMEPADDING="0" PADDING="0" SPACING="0" FRAMEBORDER="0">
+      <frameset rows="62,*" FRAMEPADDING="0" PADDING="0" SPACING="0" FRAMEBORDER="0" onload="onLoad()">
                 <frame src="esp/titlebar" name="header" target="main" scrolling="no"/>
                 <frameset FRAMEPADDING="0" PADDING="0" SPACING="0" FRAMEBORDER="{@navResize}" BORDERCOLOR="black" FRAMESPACING="1">
                     <xsl:attribute name="cols"><xsl:value-of select="@navWidth"/>,*</xsl:attribute>

+ 152 - 0
esp/xslt/passwordupdate.xsl

@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (C) <2010>  <LexisNexis Risk Data Management Inc.>
+
+    All rights reserved. This program is NOT PRESENTLY free software: you can NOT 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/>.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
+    <xsl:output method="html"/>
+    <xsl:template match="/">
+    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+        <title>Change Password</title>
+        <link rel="stylesheet" type="text/css" href="/esp/files/yui/build/fonts/fonts-min.css" />
+        <link rel="stylesheet" type="text/css" href="/esp/files/css/espdefault.css" />
+        <script type="text/javascript" src="/esp/files/scripts/espdefault.js">&#160;</script>
+        <script type="text/javascript" language="javascript">
+            <![CDATA[
+                function checkInput()
+                {
+                    document.getElementById("submit").disabled = (document.getElementById("oldpass").value=='') || (document.getElementById("newpass1").value=='') || (document.getElementById("newpass2").value=='');
+                }
+                function beforeSubmit()
+                {
+                    var ploc = top.location;
+                    var url = ploc.protocol + "//" + ploc.host + "/esp/updatepassword";
+                    document.getElementById("user_input_form").action = url;
+                }
+        ]]></script>
+    </head>
+    <body class="yui-skin-sam">
+         <p align="left" />
+            <xsl:apply-templates/>
+        </body>
+        </html>
+    </xsl:template>
+    <xsl:template match="UpdatePassword">
+        <xsl:choose>
+            <xsl:when test="(number(Code) = 0) or (number(Code) = 2)">
+                <b>
+                    <xsl:choose>
+                        <xsl:when test="number(Code) = 0">
+                            <xsl:choose>
+                                <xsl:when test="string-length(Message)">
+                                    <xsl:value-of select="Message"/>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    The password has been successfully updated.
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <xsl:choose>
+                                <xsl:when test="string-length(Message)">
+                                    <xsl:value-of select="Message"/>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    This operation is not available for this system configuration.
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:otherwise>
+                    </xsl:choose>
+                </b>
+                <form>
+                    <table>
+                        <tr>
+                            <td>
+                                <input type="button" class="sbutton" value="Close this window" onClick="window.close()"/>
+                            </td>
+                        </tr>
+                    </table>
+                </form>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:if test="number(Code) = 1">
+                    <b>
+                        <xsl:if test="string-length(Message)">
+                            <xsl:value-of select="Message"/>
+                            <br/>
+                        </xsl:if>
+                        Please check your input and try again.
+                        <br/>
+                        <br/>
+                    </b>
+                </xsl:if>
+                <form id="user_input_form" name="user_input_form" method="POST">
+                    <input type="hidden" id="username" name="username" value="{username}"/>
+                    <table>
+                        <tr>
+                            <th colspan="2">
+                                <h3>Change Password</h3>
+                            </th>
+                        </tr>
+                        <tr>
+                            <td>
+                                <b>User Name: </b>
+                            </td>
+                            <td>
+                                <input type="text" id="username2" name="username2" size="20" value="{username}" disabled="disabled"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>
+                                <b>Old Password: </b>
+                            </td>
+                            <td>
+                                <input id="oldpass" type="password" name="oldpass" size="20" value=""  onchange="checkInput()" onblur="checkInput()" onkeypress="checkInput()"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>
+                                <b>New password: </b>
+                            </td>
+                            <td>
+                                <input id="newpass1" type="password" name="newpass1" size="20" value=""  onchange="checkInput()" onblur="checkInput()" onkeypress="checkInput()"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>
+                                <b>Retype new password: </b>
+                            </td>
+                            <td>
+                                <input id="newpass2" type="password" name="newpass2" size="20" value=""  onchange="checkInput()" onblur="checkInput()" onkeypress="checkInput()"/>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td></td>
+                            <td>
+                                <input type="submit" value="Submit" id="submit" name="S1" disabled="true" onclick="return beforeSubmit();"/>
+                                <xsl:text disable-output-escaping="yes"> </xsl:text>
+                                <input type="reset" value="Clear" onClick="S1.disabled=true"/>
+                            </td>
+                        </tr>
+                    </table>
+                </form>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+</xsl:stylesheet>

+ 7 - 0
initfiles/componentfiles/configxml/esp.xsd.in

@@ -271,6 +271,13 @@
                                 </xs:appinfo>
                             </xs:annotation>
                         </xs:attribute>
+                        <xs:attribute name="passwordExpirationWarningDays" use="optional" default="10">
+                            <xs:annotation>
+                                <xs:appinfo>
+                                    <tooltip>In this time period, ESP displays a warning about password expiration.</tooltip>
+                                </xs:appinfo>
+                            </xs:annotation>
+                        </xs:attribute>
                     </xs:complexType>
                 </xs:element>
                 <xs:element name="HTTPS" minOccurs="0">

+ 10 - 0
initfiles/componentfiles/configxml/esp.xsl

@@ -81,6 +81,7 @@
                         <xsl:with-param name="ldapServer" select="@ldapServer"/>
                         <xsl:with-param name="ldapAuthMethod" select="@ldapAuthMethod"/>
                         <xsl:with-param name="ldapConnections" select="@ldapConnections"/>
+                        <xsl:with-param name="passwordExpirationWarningDays" select="@passwordExpirationWarningDays"/>
                         <xsl:with-param name="localDomain" select="/Environment/Hardware/Computer[@name=$computerName]/@domain"/>
                     </xsl:call-template>
                 </xsl:if>
@@ -300,6 +301,7 @@
         <xsl:param name="ldapAuthMethod"/>
         <xsl:param name="ldapConnections"/>
         <xsl:param name="localDomain"/>
+        <xsl:param name="passwordExpirationWarningDays"/>
         <xsl:variable name="ldapServerNode" select="/Environment/Software/LDAPServerProcess[@name=$ldapServer]"/>
         <xsl:if test="not($ldapServerNode)">
            <xsl:message terminate="yes">LDAP server is either not specified or is invalid!</xsl:message>
@@ -323,6 +325,14 @@
                       <xsl:otherwise><xsl:value-of select="@maxConnections"/></xsl:otherwise>
                    </xsl:choose>
                 </xsl:attribute>
+                <xsl:attribute name="passwordExpirationWarningDays">
+                    <xsl:choose>
+                        <xsl:when test="string($passwordExpirationWarningDays) != ''">
+                            <xsl:value-of select="$passwordExpirationWarningDays"/>
+                        </xsl:when>
+                        <xsl:otherwise>10</xsl:otherwise>
+                    </xsl:choose>
+                </xsl:attribute>
                 <xsl:variable name="ldapAddress">
                    <xsl:for-each select="Instance[@name]">
                  <xsl:variable name="netAddress" select="/Environment/Hardware/Computer[@name=current()/@computer]/@netAddress"/>

+ 1 - 0
system/security/LdapSecurity/ldapsecurity.cpp

@@ -541,6 +541,7 @@ void CLdapSecManager::init(const char *serviceName, IPropertyTree* cfg)
     int cachetimeout = cfg->getPropInt("@cacheTimeout", 5);
     m_permissionsCache.setCacheTimeout( 60 * cachetimeout);
     m_permissionsCache.setTransactionalEnabled(true);
+    m_passwordExpirationWarningDays = cfg->getPropInt(".//@passwordExpirationWarningDays", 10); //Default to 10 days
 };
 
 

+ 6 - 0
system/security/LdapSecurity/ldapsecurity.ipp

@@ -342,6 +342,7 @@ private:
     bool m_usercache_off;
     bool authenticate(ISecUser* user);
     StringBuffer m_description;
+    unsigned m_passwordExpirationWarningDays;
 
 public:
     IMPLEMENT_IINTERFACE
@@ -433,6 +434,11 @@ public:
     {
         return m_description.str();
     }
+
+    virtual unsigned getPasswordExpirationWarningDays()
+    {
+        return m_passwordExpirationWarningDays;
+    }
 };
 
 #endif

+ 1 - 0
system/security/shared/basesecurity.cpp

@@ -94,6 +94,7 @@ void CBaseSecurityManager::init(const char *serviceName, IPropertyTree *config)
 
     m_enableIPRoaming = config->getPropBool("@enableIPRoaming");
     m_enableOTP = config->getPropBool("@enableOTP",false);
+    m_passwordExpirationWarningDays = config->getPropInt(".//@passwordExpirationWarningDays", 10); //Default to 10 days
 }
 
 CBaseSecurityManager::~CBaseSecurityManager()

+ 6 - 0
system/security/shared/basesecurity.hpp

@@ -120,6 +120,7 @@ private:
     MapStrToUsers              m_userList;  
     Owned<IProperties>         m_extraparams;
     CPermissionsCache          m_permissionsCache;
+    unsigned                   m_passwordExpirationWarningDays;
 
 protected:
     CriticalSection             crit;
@@ -279,6 +280,11 @@ public:
         return NULL;
     }
 
+    virtual unsigned getPasswordExpirationWarningDays()
+    {
+        return m_passwordExpirationWarningDays;
+    }
+
 protected:
     const char* getServer(){return m_dbserver.toCharArray();}
     const char* getUser(){return m_dbuser.toCharArray();}

+ 1 - 0
system/security/shared/seclib.hpp

@@ -288,6 +288,7 @@ interface ISecManager : extends IInterface
     virtual int authorizeWorkunitScope(ISecUser & user, const char * filescope) = 0;
     virtual bool authorizeWorkunitScope(ISecUser & user, ISecResourceList * resources) = 0;
     virtual const char * getDescription() = 0;
+    virtual unsigned getPasswordExpirationWarningDays() = 0;
 };
 
 interface IExtSecurityManager