Bläddra i källkod

Merge pull request #11098 from wangkx/esp_login_page_not_found

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

Reviewed-By: Russ Whitehead <william.whitehead@lexisnexis.com>
Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 7 år sedan
förälder
incheckning
5e65c22f8a

+ 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>