Browse Source

HPCC-8767 ESP users should be able to reset expired passwords

Currently ESP (LDAP) users have to have an admin reset expired passwords.
This change allows a user to change it themselves. It is ensured that people
cannot change someone else's password by requiring the "old password."
When passing a valid but expired password to LDAP, the authentication
fails but returns a unique error string that is parsed to detect this scenario

Signed-off-by: William Whitehead <william.whitehead@lexisnexis.com>

Signed-off-by: Russ Whitehead <william.whitehead@lexisnexis.com>
William Whitehead 12 years ago
parent
commit
bdb20ec68c

+ 25 - 23
esp/bindings/http/platform/httpservice.cpp

@@ -153,18 +153,7 @@ bool CEspHttpServer::rootAuth(IEspContext* ctx)
         return true;
 
     bool ret=false;
-    EspHttpBinding* thebinding=NULL;
-    int ordinality=m_apport->getBindingCount();
-    if (ordinality==1)
-    {
-        CEspBindingEntry *entry = m_apport->queryBindingItem(0);
-        thebinding = (entry) ? dynamic_cast<EspHttpBinding*>(entry->queryBinding()) : NULL;
-    }
-    else if (m_defaultBinding)
-    {
-        thebinding=dynamic_cast<EspHttpBinding*>(m_defaultBinding.get());
-    }
-
+    EspHttpBinding* thebinding=getBinding();
     if (thebinding)
     {
         thebinding->populateRequest(m_request.get());
@@ -175,10 +164,8 @@ bool CEspHttpServer::rootAuth(IEspContext* ctx)
             ISecUser *user = ctx->queryUser();
             if (user && user->getAuthenticateStatus() == AS_PASSWORD_EXPIRED)
             {
-                DBGLOG("ESP password expired for %s", user->getName());
-                m_response->setContentType(HTTP_TYPE_TEXT_PLAIN);
-                m_response->setContent("Your ESP password has expired");
-                m_response->send();
+                m_response->redirect(*m_request.get(), "/esp/updatepasswordinput");
+                ret = true;
             }
             else
             {
@@ -246,6 +233,20 @@ static bool authenticateOptionalFailed(IEspContext& ctx, IEspHttpBinding* bindin
     return false;
 }
 
+EspHttpBinding* CEspHttpServer::getBinding()
+{
+    EspHttpBinding* thebinding=NULL;
+    int ordinality=m_apport->getBindingCount();
+    if (ordinality==1)
+    {
+        CEspBindingEntry *entry = m_apport->queryBindingItem(0);
+        thebinding = (entry) ? dynamic_cast<EspHttpBinding*>(entry->queryBinding()) : NULL;
+    }
+    else if (m_defaultBinding)
+        thebinding=dynamic_cast<EspHttpBinding*>(m_defaultBinding.get());
+    return thebinding;
+}
+
 int CEspHttpServer::processRequest()
 {
     try
@@ -333,7 +334,11 @@ int CEspHttpServer::processRequest()
             {
                 if (!methodName.length())
                     return 0;
-                if (!rootAuth(ctx))
+#ifdef _USE_OPENLDAP
+                if (strieq(methodName.str(), "updatepasswordinput"))//process before authentication check
+                    return onUpdatePasswordInput(m_request.get(), m_response.get());
+#endif
+                if (!rootAuth(ctx) )
                     return 0;
                 if (methodName.charAt(methodName.length()-1)=='_')
                     methodName.setCharAt(methodName.length()-1, 0);
@@ -355,17 +360,14 @@ 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;
+            EspHttpBinding* thebinding = getBinding();
+            if (thebinding)
+                thebinding->populateRequest(m_request.get());
             return onUpdatePassword(m_request.get(), m_response.get());
         }
 #endif

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

@@ -48,6 +48,7 @@ protected:
     int m_MaxRequestEntityLength;
 
     int unsupported();
+    EspHttpBinding* getBinding();
 public:
     IMPLEMENT_IINTERFACE;
 

+ 0 - 2
esp/bindings/http/platform/httptransport.cpp

@@ -2229,8 +2229,6 @@ void CHttpResponse::sendBasicChallenge(const char* realm, const char* content)
     send();
 }
 
-
-
 int CHttpResponse::processHeaders(IMultiException *me)
 {
     char oneline[MAX_HTTP_HEADER_LEN + 1];

+ 1 - 1
esp/platform/espprotocol.cpp

@@ -617,7 +617,7 @@ unsigned CEspApplicationPort::updatePassword(IEspContext &context, IHttpMessage*
     bool returnFlag = false;
     try
     {
-        returnFlag = secmgr->updateUser(*user, newpass1);
+        returnFlag = secmgr->updateUserPassword(*user, newpass1, oldpass);//provide the entered current password, not the cached one
     }
     catch(IException* e)
     {

+ 1 - 1
esp/services/ws_access/ws_accessService.cpp

@@ -2533,7 +2533,7 @@ bool Cws_accessEx::onUserResetPass(IEspContext &context, IEspUserResetPassReques
             return false;
         }
 
-        bool ret = ldapsecmgr->updateUser(username, req.getNewPassword());
+        bool ret = ldapsecmgr->updateUserPassword(username, req.getNewPassword());
         if(ret)
         {
             resp.setRetcode(0);

+ 1 - 1
esp/services/ws_account/ws_accountService.cpp

@@ -96,7 +96,7 @@ bool Cws_accountEx::onUpdateUser(IEspContext &context, IEspUpdateUserRequest & r
         bool ok = false;
         try
         {
-            ok = secmgr->updateUser(*user, newpass1);
+            ok = secmgr->updateUserPassword(*user, newpass1, oldpass);
         }
         catch(IException* e)
         {

+ 47 - 6
system/security/LdapSecurity/ldapconnection.cpp

@@ -1177,6 +1177,7 @@ public:
             StringBuffer hostbuf;
             m_ldapconfig->getLdapHost(hostbuf);
             int rc = LDAP_SERVER_DOWN;
+            char *ldap_errstring=NULL;
 
             for(int retries = 0; retries <= LDAPSEC_MAX_RETRIES; retries++)
             {
@@ -1192,6 +1193,8 @@ public:
 #endif
                     LDAP* user_ld = LdapUtils::LdapInit(m_ldapconfig->getProtocol(), hostbuf.str(), m_ldapconfig->getLdapPort(), m_ldapconfig->getLdapSecurePort());
                     rc = LdapUtils::LdapBind(user_ld, m_ldapconfig->getDomain(), username, password, userdnbuf.str(), m_ldapconfig->getServerType(), m_ldapconfig->getAuthMethod());
+                    if(rc != LDAP_SUCCESS)
+                        ldap_get_option(user_ld, LDAP_OPT_ERROR_STRING, &ldap_errstring);
                     ldap_unbind(user_ld);
                 }
                 DBGLOG("finished LdapBind for user %s, rc=%d", username, rc);
@@ -1214,12 +1217,14 @@ public:
                     WARNLOG("Using automatically obtained LDAP Server %s", dc.str());
                     LDAP* user_ld = LdapUtils::LdapInit(m_ldapconfig->getProtocol(), dc.str(), m_ldapconfig->getLdapPort(), m_ldapconfig->getLdapSecurePort());
                     rc = LdapUtils::LdapBind(user_ld, m_ldapconfig->getDomain(), username, password, userdnbuf.str(), m_ldapconfig->getServerType(), m_ldapconfig->getAuthMethod());
+                    if(rc != LDAP_SUCCESS)
+                        ldap_get_option(user_ld, LDAP_OPT_ERROR_STRING, &ldap_errstring);
                     ldap_unbind(user_ld);
                 }
             }
             if(rc != LDAP_SUCCESS)
             {
-                if (user.getPasswordDaysRemaining() == -1)
+                if (user.getPasswordDaysRemaining() == -1 || strstr(ldap_errstring, "data 532"))//80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 532, v1db0.
                 {
                     DBGLOG("ESP Password Expired for user %s", username);
                     user.setAuthenticateStatus(AS_PASSWORD_EXPIRED);
@@ -2132,7 +2137,7 @@ public:
 
         StringBuffer userdn;
         getUserDN(username, userdn);
-        
+
         int rc = LDAP_SUCCESS;
 
         if(!type || !*type || stricmp(type, "names") == 0)
@@ -2611,15 +2616,51 @@ public:
         return true;
     }
 
-    virtual bool updateUser(ISecUser& user, const char* newPassword)
+    virtual bool queryPasswordStatus(ISecUser& user, const char* password)
+    {
+        char *ldap_errstring = NULL;
+        const char * username = user.getName();
+
+        StringBuffer userdn;
+        getUserDN(user.getName(), userdn);
+
+        StringBuffer hostbuf;
+        m_ldapconfig->getLdapHost(hostbuf);
+
+        LDAP* user_ld = LdapUtils::LdapInit(m_ldapconfig->getProtocol(), hostbuf.str(), m_ldapconfig->getLdapPort(), m_ldapconfig->getLdapSecurePort());
+        int rc = LdapUtils::LdapBind(user_ld, m_ldapconfig->getDomain(), username, password, userdn, m_ldapconfig->getServerType(), m_ldapconfig->getAuthMethod());
+        if(rc != LDAP_SUCCESS)
+            ldap_get_option(user_ld, LDAP_OPT_ERROR_STRING, &ldap_errstring);
+        ldap_unbind(user_ld);
+
+        //Error string ""80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 532, v1db0."
+        //is returned if pw valid but expired
+        if(rc == LDAP_SUCCESS || strstr(ldap_errstring, "data 532"))//
+            return true;
+        else
+            return false;
+    }
+
+    virtual bool updateUserPassword(ISecUser& user, const char* newPassword, const char* currPassword)
     {
         const char* username = user.getName();
         if(!username || !*username)
             return false;
-        return updateUser(username, newPassword);
+
+        if (currPassword)
+        {
+            //User will not be authenticated if their password was expired,
+            //so check here that they provided a valid one in the "change
+            //password" form (use the one they type, not the one in the secuser)
+            bool validated = queryPasswordStatus(user, currPassword);
+            if (!validated)
+                throw MakeStringException(-1, "Password not changed, invalid credentials");
+        }
+
+        return updateUserPassword(username, newPassword);
     }
 
-    virtual bool updateUser(const char* username, const char* newPassword)
+    virtual bool updateUserPassword(const char* username, const char* newPassword)
     {
         if(!username || !*username)
             return false;
@@ -4898,7 +4939,7 @@ private:
         if(passwd == NULL || *passwd == '\0')
             passwd = "password";
 
-        updateUser(*tmpuser, passwd);
+        updateUserPassword(*tmpuser, passwd, NULL);
 
         //Add tempfile scope for this user (spill, paused and checkpoint
         //will be created under this user specific scope)

+ 2 - 2
system/security/LdapSecurity/ldapconnection.hpp

@@ -165,9 +165,9 @@ interface ILdapClient : extends IInterface
     virtual void setResourceBasedn(const char* rbasedn, SecResourceType rtype = RT_DEFAULT) = 0;
     virtual ILdapConfig* getLdapConfig() = 0;
     virtual bool userInGroup(const char* userdn, const char* groupdn) = 0;
-    virtual bool updateUser(ISecUser& user, const char* newPassword) = 0;
+    virtual bool updateUserPassword(ISecUser& user, const char* newPassword, const char* currPassword = 0) = 0;
     virtual bool updateUser(const char* type, ISecUser& user) = 0;
-    virtual bool updateUser(const char* username, const char* newPassword) = 0;
+    virtual bool updateUserPassword(const char* username, const char* newPassword) = 0;
     virtual bool getResources(SecResourceType rtype, const char * basedn, const char* prefix, IArrayOf<ISecResource>& resources) = 0;
     virtual bool getResourcesEx(SecResourceType rtype, const char * basedn, const char* prefix, const char* searchstr, IArrayOf<ISecResource>& resources) = 0;
     virtual bool getPermissionsArray(const char* basedn, SecResourceType rtype, const char* name, IArrayOf<CPermission>& permissions) = 0;

+ 5 - 5
system/security/LdapSecurity/ldapsecurity.cpp

@@ -1078,16 +1078,16 @@ IAuthMap * CLdapSecManager::createFeatureMap(IPropertyTree * authconfig)
     return feature_authmap;
 }
 
-bool CLdapSecManager::updateUser(ISecUser& user, const char* newPassword)
+bool CLdapSecManager::updateUserPassword(ISecUser& user, const char* newPassword, const char* currPassword)
 {
     // Authenticate User first
-    if(!authenticate(&user))
+    if(!authenticate(&user) && user.getAuthenticateStatus() != AS_PASSWORD_EXPIRED)
     {
         return false;
     }
 
     //Update password if authenticated
-    bool ok = m_ldap_client->updateUser(user, newPassword);
+    bool ok = m_ldap_client->updateUserPassword(user, newPassword, currPassword);
     if(ok && m_permissionsCache.isCacheEnabled() && !m_usercache_off)
     {
         m_permissionsCache.removeFromUserCache(user);
@@ -1104,9 +1104,9 @@ bool CLdapSecManager::updateUser(const char* type, ISecUser& user)
     return ok;
 }
 
-bool CLdapSecManager::updateUser(const char* username, const char* newPassword)
+bool CLdapSecManager::updateUserPassword(const char* username, const char* newPassword)
 {
-    return m_ldap_client->updateUser(username, newPassword);
+    return m_ldap_client->updateUserPassword(username, newPassword);
 }
 
 void CLdapSecManager::getAllGroups(StringArray & groups)

+ 2 - 2
system/security/LdapSecurity/ldapsecurity.ipp

@@ -378,9 +378,9 @@ public:
     virtual IAuthMap * createFeatureMap(IPropertyTree * authconfig);
     virtual IAuthMap * createSettingMap(struct IPropertyTree *){return 0;}
     virtual bool updateSettings(ISecUser & User,ISecPropertyList * settings){return false;}
-    virtual bool updateUser(ISecUser& user, const char* newPassword);
+    virtual bool updateUserPassword(ISecUser& user, const char* newPassword, const char* currPassword = 0);
     virtual bool updateUser(const char* type, ISecUser& user);
-    virtual bool updateUser(const char* username, const char* newPassword);
+    virtual bool updateUserPassword(const char* username, const char* newPassword);
     virtual bool initUser(ISecUser& user){return false;}
 
     virtual bool getResources(SecResourceType rtype, const char * basedn, IArrayOf<ISecResource>& resources);

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

@@ -548,7 +548,7 @@ bool CBaseSecurityManager::updateResources(ISecUser & user, ISecResourceList * r
     return true;
 }
 
-bool CBaseSecurityManager::updateUser(ISecUser& user, const char* newPassword)
+bool CBaseSecurityManager::updateUserPassword(ISecUser& user, const char* newPassword, const char* currPassword)
 {
     //("CBaseSecurityManager::updateUser");
     if(!newPassword)

+ 1 - 1
system/security/shared/basesecurity.hpp

@@ -242,7 +242,7 @@ public:
     virtual IAuthMap * createFeatureMap(IPropertyTree * authconfig) {return NULL;}
     virtual IAuthMap * createSettingMap(IPropertyTree * authconfig) {return NULL;}
 
-    virtual bool updateUser(ISecUser& user, const char* newPassword);
+    virtual bool updateUserPassword(ISecUser& user, const char* newPassword, const char* currPassword = NULL);
     virtual bool IsPasswordExpired(ISecUser& user){return false;}
     void getAllGroups(StringArray & groups) { UNIMPLEMENTED;}
     

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

@@ -283,7 +283,7 @@ interface ISecManager : extends IInterface
     virtual ISecUser * lookupUser(unsigned uid) = 0;
     virtual ISecUserIterator * getAllUsers() = 0;
     virtual void getAllGroups(StringArray & groups) = 0;
-    virtual bool updateUser(ISecUser & user, const char * newPassword) = 0;
+    virtual bool updateUserPassword(ISecUser & user, const char * newPassword, const char* currPassword = 0) = 0;
     virtual bool initUser(ISecUser & user) = 0;
     virtual void setExtraParam(const char * name, const char * value) = 0;
     virtual IAuthMap * createAuthMap(IPropertyTree * authconfig) = 0;

+ 1 - 1
system/security/test/ldapsecuritytest/ldapsecuritytest.cpp

@@ -377,7 +377,7 @@ int main(int argc, char* argv[])
 
             Owned<ISecUser> usr = secmgr->createUser(username);
             usr->credentials().setPassword(passwd);
-            bool ok = secmgr->updateUser(*usr, newpasswd);
+            bool ok = secmgr->updateUserPassword(*usr, newpasswd);
             if(ok)
                 printf("user password changed\n");
             else