Browse Source

Merge pull request #12169 from wangkx/h20896

HPCC-20896 Support ESP session cookie verification

Reviewed-By: Russ Whitehead <william.whitehead@lexisnexis.com>
Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 6 năm trước cách đây
mục cha
commit
387b9ccd6a

+ 206 - 4
esp/bindings/http/platform/httpservice.cpp

@@ -1059,7 +1059,7 @@ EspAuthState CEspHttpServer::preCheckAuth(EspAuthRequest& authReq)
                         sendMessage(errMsg.str(), "text/html; charset=UTF-8");
                         return authTaskDone;
                     }
-                    else if (strieq(method, "get_session_timeout") || strieq(method, "reset_session_timeout"))
+                    else if (strieq(method, "verify") || strieq(method, "get_session_timeout") || strieq(method, "reset_session_timeout"))
                     {
                         VStringBuffer errMsg("Action not supported: %s", method);
                         ESPSerializationFormat respFormat = m_request->queryContext()->getResponseFormat();
@@ -1080,10 +1080,15 @@ EspAuthState CEspHttpServer::preCheckAuth(EspAuthRequest& authReq)
         return authSucceeded;
 
     if (!authReq.httpMethod.isEmpty() && !authReq.serviceName.isEmpty() && !authReq.methodName.isEmpty()
-        && strieq(authReq.httpMethod.str(), GET_METHOD) && strieq(authReq.serviceName.str(), "esp") && strieq(authReq.methodName.str(), "getauthtype"))
+        && strieq(authReq.httpMethod.str(), GET_METHOD) && strieq(authReq.serviceName.str(), "esp"))
     {
-        sendGetAuthTypeResponse(authReq, nullptr);
-        return authTaskDone;
+        if (strieq(authReq.methodName.str(), "getauthtype"))
+        {
+            sendGetAuthTypeResponse(authReq, nullptr);
+            return authTaskDone;
+        }
+        if (strieq(authReq.methodName.str(), "verify"))
+            return verifyCookies(authReq);
     }
 #ifdef _USE_OPENLDAP
     if (!authReq.httpMethod.isEmpty() && !authReq.serviceName.isEmpty() && !authReq.methodName.isEmpty() && strieq(authReq.serviceName.str(), "esp"))
@@ -1120,6 +1125,203 @@ EspAuthState CEspHttpServer::preCheckAuth(EspAuthRequest& authReq)
     return authUnknown;
 }
 
+EspAuthState CEspHttpServer::verifyCookies(EspAuthRequest& authReq)
+{
+    CIArrayOf<CESPCookieVerification> cookies;
+    //Check whether the ESP request has specified which cookie to be verified or not.
+    IProperties* parms = m_request->queryParameters();
+    if (parms)
+    {
+        Owned<IPropertyIterator> iter = parms->getIterator();
+        ForEach(*iter)
+        {
+            const char* name = iter->getPropKey();
+            if (strieq(name, "__querystring"))
+                continue; //skip the property created by ESP.
+
+            cookies.append(*(new CESPCookieVerification(name, "")));
+        }
+    }
+
+    //If the ESP request has not specified which cookie to be verified,
+    //we will verify the cookies in HTTP Cookie header.
+    if (cookies.ordinality() == 0)
+    {
+        IArrayOf<CEspCookie>& cookiesUsed = m_request->queryCookies();
+        ForEachItemIn(i, cookiesUsed)
+        {
+            CEspCookie& r = cookiesUsed.item(i);
+            cookies.append(*(new CESPCookieVerification(r.getName(), r.getValue())));
+        }
+    }
+
+    ForEachItemIn(ii, cookies)
+        verifyCookie(authReq, cookies.item(ii));
+
+    sendVerifyCookieResponse(authReq, cookies);
+    return authTaskDone;
+}
+
+void CEspHttpServer::verifyCookie(EspAuthRequest& authReq, CESPCookieVerification& cookie)
+{
+    const char* name = cookie.cookieName.get();
+    if (strieq(name, "ESPSessionID") || strieq(name, authReq.authBinding->querySessionIDCookieName()))
+    {
+        if (verifyESPSessionIDCookie(authReq))
+            cookie.valid.set("true");
+    }
+    else if (strieq(name, SESSION_AUTH_OK_COOKIE))
+        verifyESPAuthenticatedCookie(authReq, cookie);
+    else if (strieq(name, USER_NAME_COOKIE))
+        verifyESPUserNameCookie(authReq, cookie);
+    else if (strieq(name, SESSION_TIMEOUT_COOKIE) || strieq(name, SESSION_AUTH_MSG_COOKIE))
+    {
+        //SESSION_TIMEOUT_COOKIE: used to pass timeout settings to a client.
+        //SESSION_AUTH_MSG_COOKIE: used to pass authentication message to a client.
+        //A client should clean it as soon as received. ESP always returns invalid if it is asked.
+        cookie.verificationDetails.set("ESP cannot verify this cookie. It is one-time use only.");
+    }
+    else if (strieq(name, SESSION_START_URL_COOKIE))
+    {   
+        //SESSION_START_URL_COOKIE: created as soon as a login process is started and used to redirect at the end of the login process.
+        //After the login process, ESP should remove this cookie if possible. This verify function is designed to verify cookies before 
+        //a login process is started. At that time, any SESSION_START_URL_COOKIE should be invalid.
+        cookie.verificationDetails.set("ESP cannot verify this cookie. This cookie is only valid within a login process.");
+    }
+    else if (strieq(name, SESSION_ID_TEMP_COOKIE))
+    {   
+        //SESSION_ID_TEMP_COOKIE: used for remembering UserID when a user is in AS_PASSWORD_VALID_BUT_EXPIRED state and updating password.
+        //It is created before the updatepasswordinput is started. After the update process, ESP should remove this cookie if possible.
+        //This verify function is designed to verify cookies before a login process is started. At that time, any SESSION_ID_TEMP_COOKIE
+        //should be invalid.
+        cookie.verificationDetails.set("ESP cannot verify this cookie. This cookie is only valid when updating an expired password.");
+    }
+    else
+        cookie.verificationDetails.set("ESP does not know this cookie");
+}
+
+void CEspHttpServer::verifyESPUserNameCookie(EspAuthRequest& authReq, CESPCookieVerification& cookie)
+{
+    //USER_NAME_COOKIE: used in UserNameOnlyMode for remembering the user name. Right now it is
+    //always valid if ESP is in the UserNameOnlyMode.
+    if (authReq.authBinding->getDomainAuthType() != AuthUserNameOnly)
+        return;
+
+    if (!cookie.cookieValue.get())
+        cookie.cookieValue.set(m_request->queryCookie(USER_NAME_COOKIE));
+    if (cookie.cookieValue.get())
+        cookie.valid.set("true");
+}
+
+bool CEspHttpServer::verifyESPSessionIDCookie(EspAuthRequest& authReq)
+{
+    unsigned sessionID = readCookie(authReq.authBinding->querySessionIDCookieName());
+    if (sessionID == 0) //No valid SessionIDCookie found
+        return false;
+
+    //Timeout old sessions.
+    Owned<IRemoteConnection> conn = getSDSConnection(authReq.authBinding->queryESPSessionSDSPath(), RTM_LOCK_WRITE, SESSION_SDS_LOCK_TIMEOUT);
+    IPropertyTree* espSessions = conn->queryRoot();
+    if (authReq.authBinding->getServerSessionTimeoutSeconds() >= 0)
+    {
+        CDateTime now;
+        now.setNow();
+        time_t timeNow = now.getSimple();
+        if ((timeNow - lastSessionCleanUpTime) >= authReq.authBinding->getCheckSessionTimeoutSeconds())
+        {
+            lastSessionCleanUpTime = timeNow;
+            timeoutESPSessions(authReq.authBinding, espSessions);
+        }
+    }
+
+    //Now, check whether the session ID is valid or not.
+    VStringBuffer xpath("%s[@port=\"%d\"]/%s%u", PathSessionApplication, authReq.authBinding->getPort(),
+        PathSessionSession, sessionID);
+    IPropertyTree* sessionTree = espSessions->queryBranch(xpath.str());
+    if (!sessionTree)
+        return false;
+
+    StringBuffer peer;
+    return streq(m_request->getPeer(peer).str(), sessionTree->queryProp(PropSessionNetworkAddress));
+}
+
+void CEspHttpServer::verifyESPAuthenticatedCookie(EspAuthRequest& authReq, CESPCookieVerification& cookie)
+{
+    if (!cookie.cookieValue.get())
+        cookie.cookieValue.set(m_request->queryCookie(SESSION_AUTH_OK_COOKIE));
+    if (!cookie.cookieValue.get())
+    {
+        cookie.verificationDetails.set("Cookie value not found.");
+        return;
+    }
+
+    bool checkValid = strieq(cookie.cookieValue.get(), "true") ? true : false;
+    if (authReq.authBinding->getDomainAuthType() == AuthUserNameOnly)
+    {//For AuthUserNameOnly, a user is 'authenticated' if there is a USER_NAME_COOKIE (which contains a user name).
+        if (checkValid && m_request->queryCookie(USER_NAME_COOKIE))
+            cookie.valid.set("true");
+        if (!checkValid && !m_request->queryCookie(USER_NAME_COOKIE))
+            cookie.valid.set("true");
+        return;
+    }
+
+    bool isSessionIDCookieValid = verifyESPSessionIDCookie(authReq);
+    if (checkValid && isSessionIDCookieValid)
+        cookie.valid.set("true");
+    else if (!checkValid && !isSessionIDCookieValid)
+        cookie.valid.set("true");
+}
+
+void CEspHttpServer::sendVerifyCookieResponse(EspAuthRequest& authReq, CIArrayOf<CESPCookieVerification>& cookies)
+{
+    StringBuffer resp;
+    ESPSerializationFormat format = m_request->queryContext()->getResponseFormat();
+    if (format == ESPSerializationJSON)
+    {
+        resp.set("{ ");
+        resp.append(" \"VerifyResponse\": { ");
+        ForEachItemIn(i, cookies)
+        {
+            CESPCookieVerification& cookie = cookies.item(i);
+            const char* name = cookie.cookieName.get();
+            if (i > 0)
+                resp.append(",");
+            resp.appendf(" \"%s\": { ", name);
+            if (strieq(name, "ESPSessionID") || strieq(name, authReq.authBinding->querySessionIDCookieName()))
+                resp.append(" \"Value\": \"(hidden)\"");
+            else if (cookie.cookieValue.get())
+                resp.appendf(" \"Value\": \"%s\"", cookie.cookieValue.get());
+            resp.appendf(", \"Valid\": %s", cookie.valid.get());
+
+            if (cookie.verificationDetails.get())
+                resp.appendf(", \"Details\": \"%s\"", cookie.verificationDetails.get());
+            resp.append(" }");
+        }
+        resp.append(" }");
+        resp.append(" }");
+    }
+    else
+    {
+        resp.set("<VerifyResponse>");
+        ForEachItemIn(i, cookies)
+        {
+            CESPCookieVerification& cookie = cookies.item(i);
+            const char* name = cookie.cookieName.get();
+            resp.append("<").append(name).append(">");
+            if (strieq(name, "ESPSessionID") || strieq(name, authReq.authBinding->querySessionIDCookieName()))
+                resp.append("<Value>(hidden)</Value>");
+            else if (cookie.cookieValue.get())
+                resp.append("<Value>").append(cookie.cookieValue.get()).append("</Value>");
+            resp.append("<Valid>").append(cookie.valid.get()).append("</Valid>");
+            if (cookie.verificationDetails.get())
+                resp.append("<Details>").append(cookie.verificationDetails.get()).append("</Details>");
+            resp.append("</").append(name).append(">");
+        }
+        resp.append("</VerifyResponse>");
+    }
+    sendMessage(resp.str(), (format == ESPSerializationJSON) ? "application/json" : "text/xml");
+}
+
 EspAuthState CEspHttpServer::handleUserNameOnlyMode(EspAuthRequest& authReq)
 {
     if (authReq.authBinding->isDomainAuthResources(authReq.httpPath.str()))

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

@@ -56,6 +56,14 @@ struct EspAuthRequest
     bool isSoapPost;
 };
 
+class CESPCookieVerification : public CInterface
+{
+public:
+    StringAttr cookieName, cookieValue, valid, verificationDetails;
+
+    CESPCookieVerification(const char* name, const char* value) : cookieName(name), cookieValue(value), valid("false") { };
+};
+
 interface IRemoteConnection;
 class CEspHttpServer : implements IHttpServerService, public CInterface
 {
@@ -77,6 +85,13 @@ protected:
     EspAuthState checkUserAuth();
     void readAuthRequest(EspAuthRequest& req);
     EspAuthState preCheckAuth(EspAuthRequest& authReq);
+    EspAuthState verifyCookies(EspAuthRequest& authReq);
+    void verifyCookie(EspAuthRequest& authReq, CESPCookieVerification& cookie);
+    bool verifyESPSessionIDCookie(EspAuthRequest& authReq);
+    void verifyESPUserNameCookie(EspAuthRequest& authReq, CESPCookieVerification& cookie);
+    void verifyESPAuthenticatedCookie(EspAuthRequest& authReq, CESPCookieVerification& cookie);
+    void sendVerifyCookieResponse(EspAuthRequest& authReq, CIArrayOf<CESPCookieVerification>& cookies);
+
     EspAuthState checkUserAuthPerRequest(EspAuthRequest& authReq);
     EspAuthState checkUserAuthPerSession(EspAuthRequest& authReq, StringBuffer& authorizationHeader);
     EspAuthState authNewSession(EspAuthRequest& authReq, const char* _userName, const char* _password, const char* sessionStartURL, bool unlock);