瀏覽代碼

HPCC-19581 Add redirect url check, new CORS check, etc.

The code is added to avoid invalid url being used to redirect.
In the existing code, a request is treated as a CORS request if
an Origin header exists. But, some request is not CORS request
even if there is an Origin header. The code is added to check
if the request comes from the same ESP service. If yes, the
request is not a CORS request. A new ESPAuthencated cookie is
added for an ESP client to check whether the client has been
authenticated or not.

Signed-off-by: wangkx <kevin.wang@lexisnexis.com>
wangkx 7 年之前
父節點
當前提交
2143f70425

+ 76 - 0
esp/bindings/http/platform/httpbinding.cpp

@@ -49,6 +49,7 @@
 #include "daclient.hpp"
 
 #define FILE_UPLOAD     "FileUploadAccess"
+#define DEFAULT_HTTP_PORT 80
 
 static HINSTANCE getXmlLib()
 {
@@ -312,6 +313,8 @@ EspHttpBinding::EspHttpBinding(IPropertyTree* tree, const char *bindname, const
         setSDSSession();
         checkSessionTimeoutSeconds = proc_cfg->getPropInt("@checkSessionTimeoutSeconds", ESP_CHECK_SESSION_TIMEOUT);
     }
+
+    setABoolHash(proc_cfg->queryProp("@urlAlias"), serverAlias);
 }
 
 void EspHttpBinding::setSDSSession()
@@ -386,6 +389,12 @@ void EspHttpBinding::readAuthDomainCfg(IPropertyTree* procCfg)
             logoutURL.set(_logoutURL);
             domainAuthResources.setValue(logoutURL.get(), true);
         }
+
+        //Read pre-configured 'invalidURLsAfterAuth'. Separate the comma separated string to a
+        //list. Store them into BoolHash for quick lookup.
+        setABoolHash(authDomainTree->queryProp("@invalidURLsAfterAuth"), invalidURLsAfterAuth);
+        if (!loginURL.isEmpty())
+            invalidURLsAfterAuth.setValue(loginURL.get(), true);
     }
     else
     {//old environment.xml
@@ -412,6 +421,73 @@ void EspHttpBinding::readUnrestrictedResources(const char* resources)
     }
 }
 
+//Check whether the url is valid or not for redirect after authentication.
+bool EspHttpBinding::canRedirectAfterAuth(const char* url) const
+{
+    if (isEmptyString(url))
+        return false;
+
+    bool* found = invalidURLsAfterAuth.getValue(url);
+    return (!found || !*found);
+}
+
+//Use the origin header to check whether the request is a CORS request or not.
+bool EspHttpBinding::isCORSRequest(const char* originHeader)
+{
+    if (isEmptyString(originHeader))
+        return false;
+
+    const char* ptr = nullptr;
+    if (strnicmp(originHeader, "http://", 7) == 0)
+        ptr = originHeader + 7;
+    else if (strnicmp(originHeader, "https://", 8) == 0)
+        ptr = originHeader + 8;
+    else
+        return true;
+
+    StringBuffer ipStr; //ip or network alias
+    while(*ptr && *ptr != ':')
+    {
+        ipStr.append(*ptr);
+        ptr++;
+    }
+
+    IpAddress ip(ipStr.str());
+    if (ip.ipequals(queryHostIP()))
+        return false;
+
+    int port = 0;
+    if (*ptr && *ptr == ':')
+    {
+        ptr++;
+        while(*ptr && isdigit(*ptr))
+        {
+            port = 10*port + (*ptr-'0');
+            ptr++;
+        }
+    }
+    if (port == 0)
+        port = DEFAULT_HTTP_PORT;
+
+    if (port != getPort())
+        return true;
+
+
+    bool* found = serverAlias.getValue(ipStr.str());
+    return (!found || !*found);
+}
+
+void EspHttpBinding::setABoolHash(const char* csv, BoolHash& hash) const
+{
+    if (isEmptyString(csv))
+        return;
+
+    StringArray aList;
+    aList.appendListUniq(csv, ",");
+    ForEachItemIn(i, aList)
+        hash.setValue(aList.item(i), true);
+}
+
 StringBuffer &EspHttpBinding::generateNamespace(IEspContext &context, CHttpRequest* request, const char *serv, const char *method, StringBuffer &ns)
 {
     ns.append("urn:hpccsystems:ws:");

+ 5 - 0
esp/bindings/http/platform/httpbinding.hpp

@@ -167,6 +167,8 @@ private:
     int                     clientSessionTimeoutSeconds = 60 * ESP_SESSION_TIMEOUT;
     int                     serverSessionTimeoutSeconds = 120 * ESP_SESSION_TIMEOUT;
     int                     checkSessionTimeoutSeconds = ESP_CHECK_SESSION_TIMEOUT; //the duration to clean timed out sesssions
+    BoolHash                serverAlias;  //like www.microsoft.com, www.yahoo.com
+    BoolHash                invalidURLsAfterAuth; //Those URLs should not be used for redirect after authenticated, such as /SMC/, /esp/login
     BoolHash                domainAuthResources;
     StringArray             domainAuthResourcesWildMatch;
 
@@ -366,6 +368,9 @@ public:
     void readAuthDomainCfg(IPropertyTree* procCfg);
     void readUnrestrictedResources(const char* resources);
     void setSDSSession();
+    void setABoolHash(const char* csv, BoolHash& hash) const;
+    bool isCORSRequest(const char* originHeader);
+    bool canRedirectAfterAuth(const char* url) const;
 
     static void escapeSingleQuote(StringBuffer& src, StringBuffer& escaped);
 

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

@@ -547,6 +547,7 @@ int CEspHttpServer::onUpdatePassword(CHttpRequest* request, CHttpResponse* respo
                 m_request->queryContext()->setSessionToken(sessionID);
                 VStringBuffer cookieStr("%u", sessionID);
                 addCookie(binding->querySessionIDCookieName(), cookieStr.str(), 0, true);
+                addCookie(SESSION_AUTH_OK_COOKIE, "true", 0, false); //client can access this cookie.
                 cookieStr.setf("%u", binding->getClientSessionTimeoutSeconds());
                 addCookie(SESSION_TIMEOUT_COOKIE, cookieStr.str(), 0, false);
                 clearCookie(SESSION_START_URL_COOKIE);
@@ -956,9 +957,10 @@ EspAuthState CEspHttpServer::checkUserAuth()
     //The following 3 rules are used to detect  a REST type call.
     //   Any HTTP POST request;
     //   Any request which has a BasicAuthentication header;
-    //   Any CORS calls (any request which has an Origin header).
+    //   Any CORS calls.
     bool authSession = (domainAuthType == AuthPerSessionOnly) || ((domainAuthType == AuthTypeMixed) &&
-        authorizationHeader.isEmpty() && originHeader.isEmpty() && !strieq(authReq.httpMethod.str(), POST_METHOD));
+        authorizationHeader.isEmpty() && !authReq.authBinding->isCORSRequest(originHeader.str()) &&
+        !strieq(authReq.httpMethod.str(), POST_METHOD));
     return handleAuthFailed(authSession, authReq, false, nullptr);
 }
 
@@ -1092,11 +1094,13 @@ EspAuthState CEspHttpServer::handleUserNameOnlyMode(EspAuthRequest& authReq)
 
     //We just got the user name. Let's add it into cookie for future use.
     addCookie(USER_NAME_COOKIE, userNameIn, 0, false);
+    addCookie(SESSION_AUTH_OK_COOKIE, "true", 0, false); //client can access this cookie.
 
     StringBuffer urlCookie;
     readCookie(SESSION_START_URL_COOKIE, urlCookie);
     clearCookie(SESSION_START_URL_COOKIE);
-    m_response->redirect(*m_request, urlCookie.isEmpty() ? "/" : urlCookie.str());
+    bool canRedirect = authReq.authBinding->canRedirectAfterAuth(urlCookie.str());
+    m_response->redirect(*m_request, canRedirect ? urlCookie.str() : "/");
     return authSucceeded;
 }
 
@@ -1224,6 +1228,7 @@ EspAuthState CEspHttpServer::authNewSession(EspAuthRequest& authReq, const char*
     VStringBuffer cookieStr("%u", sessionID);
     addCookie(authReq.authBinding->querySessionIDCookieName(), cookieStr.str(), 0, true);
     cookieStr.setf("%u", authReq.authBinding->getClientSessionTimeoutSeconds());
+    addCookie(SESSION_AUTH_OK_COOKIE, "true", 0, false); //client can access this cookie.
     addCookie(SESSION_TIMEOUT_COOKIE, cookieStr.str(), 0, false);
     clearCookie(SESSION_AUTH_MSG_COOKIE);
     clearCookie(SESSION_START_URL_COOKIE);
@@ -1238,7 +1243,8 @@ EspAuthState CEspHttpServer::authNewSession(EspAuthRequest& authReq, const char*
         return authTaskDone;
     }
 
-    m_response->redirect(*m_request, sessionStartURL);
+    bool canRedirect = authReq.authBinding->canRedirectAfterAuth(sessionStartURL);
+    m_response->redirect(*m_request, canRedirect ? sessionStartURL : "/");
     return authSucceeded;
 }
 
@@ -1356,6 +1362,7 @@ void CEspHttpServer::resetSessionTimeout(EspAuthRequest& authReq, unsigned sessi
 
         VStringBuffer sessionIDStr("%u", sessionID);
         addCookie(authReq.authBinding->querySessionIDCookieName(), sessionIDStr.str(), 0, true);
+        addCookie(SESSION_AUTH_OK_COOKIE, "true", 0, false); //client can access this cookie.
 
         if (getEspLogLevel()>=LogMax)
         {
@@ -1437,6 +1444,7 @@ EspAuthState CEspHttpServer::authExistingSession(EspAuthRequest& authReq, unsign
         ESPLOG(LogMin, "Authentication failed: session:<%u> not found", sessionID);
 
         clearCookie(authReq.authBinding->querySessionIDCookieName());
+        clearCookie(SESSION_AUTH_OK_COOKIE);
         ESPLOG(LogMin, "clearCookie() called for session <%u> ID cookie", sessionID);
 
         if (authReq.isSoapPost) //from SOAP Test page
@@ -1493,8 +1501,11 @@ EspAuthState CEspHttpServer::authExistingSession(EspAuthRequest& authReq, unsign
         ///authReq.ctx->setAuthorized(true);
         VStringBuffer sessionIDStr("%u", sessionID);
         addCookie(authReq.authBinding->querySessionIDCookieName(), sessionIDStr.str(), 0, true);
+        addCookie(SESSION_AUTH_OK_COOKIE, "true", 0, false); //client can access this cookie.
         if (getLoginPage)
             m_response->redirect(*m_request, "/");
+        if (!authReq.authBinding->canRedirectAfterAuth(authReq.httpPath.str()))
+            m_response->redirect(*m_request, "/");
     }
 
     return authSucceeded;

+ 1 - 0
esp/platform/espcontext.hpp

@@ -41,6 +41,7 @@ static const char* const SESSION_ID_COOKIE = "ESPSessionID";
 static const char* const SESSION_START_URL_COOKIE = "ESPAuthURL";
 static const char* const SESSION_TIMEOUT_COOKIE = "ESPSessionTimeoutSeconds";
 static const char* const SESSION_ID_TEMP_COOKIE = "ESPAuthIDTemp";
+static const char* const SESSION_AUTH_OK_COOKIE = "ESPAuthenticated";
 static const char* const SESSION_AUTH_MSG_COOKIE = "ESPAuthenticationMSG";
 static const char* const DEFAULT_LOGIN_URL = "/esp/files/Login.html";
 static const char* const DEFAULT_GET_USER_NAME_URL = "/esp/files/GetUserName.html";

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

@@ -440,6 +440,15 @@
 	                </xs:appinfo>
 	              </xs:annotation>
 	            </xs:attribute>
+	            <xs:attribute name="invalidURLsAfterAuth" type="xs:string" use="optional" default="/esp/login">
+	              <xs:annotation>
+	                <xs:appinfo>
+	                  <width>50</width>
+	                  <tooltip>After authentication, requests are redirected to the home page.</tooltip>
+	                  <colIndex>8</colIndex>
+	                </xs:appinfo>
+	              </xs:annotation>
+	            </xs:attribute>
 	          </xs:complexType>
 	        </xs:element>
                 <xs:element name="HTTPS" minOccurs="0">
@@ -907,6 +916,13 @@
                     </xs:appinfo>
                 </xs:annotation>
             </xs:attribute>
+            <xs:attribute name="urlAlias" type="xs:string" use="optional">
+                <xs:annotation>
+                    <xs:appinfo>
+                        <tooltip>The URL alias for this ESP. This can be used to detect Cross-Origin Resource Sharing (CORS) access.</tooltip>
+                    </xs:appinfo>
+                </xs:annotation>
+            </xs:attribute>
             <xs:attribute name="PageCacheTimeoutSeconds" type="xs:nonNegativeInteger" use="optional" default="600">
                 <xs:annotation>
                     <xs:appinfo>