[FEATURE] Send HTTP header 'Authorization' 35/46135/9
authorSascha Egerer <sascha@sascha-egerer.de>
Thu, 21 Jan 2016 14:55:30 +0000 (15:55 +0100)
committerChristian Futterlieb <christian@futterlieb.ch>
Mon, 25 Jan 2016 11:27:59 +0000 (12:27 +0100)
When a server requires an 'Authorization' HTTP header,
the request to fetch the 404/403 page must contain
the same header too to be succesful.
In case of using HTTP digest authentication, the credentials
must be configured in extension manager or sys_template
record. Additionally cURL must be available and enabled.

Change-Id: Iea4d5723b3fbac1186a5453f07cbcd0f77608799
Resolves: #72868
Releases: master
Reviewed-on: https://review.typo3.org/46135
Reviewed-by: Christian Futterlieb <christian@futterlieb.ch>
Tested-by: Christian Futterlieb <christian@futterlieb.ch>
Classes/Controller/PagenotfoundController.php
Configuration/TCA/Overrides/sys_template.php
Documentation/Configuration/ExtensionManager/Index.rst
ext_conf_template.txt
ext_tables.sql
locallang_db.xml

index bee2f11..57bd991 100644 (file)
@@ -182,6 +182,15 @@ class PagenotfoundController
      */
     protected $_absoluteReferencePrefix = '';
 
+    /**
+     * HTTP digest authentication
+     *
+     * Format: 'username:password'
+     *
+     * @var string
+     */
+    protected $_digestAuthentication = '';
+
        /**
         * Main method called through TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::pageErrorHandler()
         *
@@ -319,6 +328,7 @@ class PagenotfoundController
                     $this->_passthroughContentTypeHeader = (bool) $row['tx_pagenotfoundhandling_passthroughContentTypeHeader'];
                     $this->_sendXForwardedForHeader = (bool) $row['tx_pagenotfoundhandling_sendXForwardedForHeader'];
                     $this->_additionalHeaders = GeneralUtility::trimExplode('|', $row['tx_pagenotfoundhandling_additionalHeaders'], true);
+                    $this->_digestAuthentication = trim($row['tx_pagenotfoundhandling_digestAuthentication']);
 
                     // override 404 page with its 403 equivalent (if needed and configured so)
                     if($this->_isForbiddenError) {
@@ -409,6 +419,10 @@ class PagenotfoundController
             $this->_additionalHeaders = GeneralUtility::trimExplode('|', $conf['additionalHeaders'], true);
         }
 
+        if(isset($conf['digestAuthentication'])) {
+            $this->_digestAuthentication = trim($conf['digestAuthentication']);
+        }
+
         if(isset($conf['absoluteReferencePrefix'])) {
             // remove '/' and whitespaces
             $absoluteReferencePrefix = \trim(\trim($conf['absoluteReferencePrefix'], '/'));
@@ -472,7 +486,7 @@ class PagenotfoundController
                 }
 
                 $report = array();
-                $html = GeneralUtility::getURL($url, (int) $this->_passthroughContentTypeHeader, $headers, $report);
+                $html = $this->_getUrl($url, (int) $this->_passthroughContentTypeHeader, $headers, $report);
                 if ($this->_passthroughContentTypeHeader && $html !== null) {
                     // split response header and body
                     list ($responseHeaders, $html) = GeneralUtility::trimExplode(CRLF . CRLF, $html, false, 2);
@@ -524,6 +538,159 @@ class PagenotfoundController
     }
 
     /**
+     * Wrapper method for GeneralUtility::getURL();
+     *
+     * Additionally, this method adds a HTTP 'Authorization' header, when one is
+     * present in the current request.
+     *
+     * @throws \Exception
+     * @param string $url
+     * @param int $includeHeaders
+     * @param array $headers
+     * @param array $report
+     * @return mixed
+     * @see \TYPO3\CMS\Core\Utility\GeneralUtility::getURL()
+     */
+    protected function _getUrl($url, $includeHeaders = 0, array $headers = array(), &$report = null)
+    {
+        // handle http authorization
+        $digestAuthorization = false;
+        $requestHeaders = $this->_getAllHttpHeaders();
+        if (array_key_exists('Authorization', $requestHeaders)) {
+            $authorizationHeader = $requestHeaders['Authorization'];
+            // Authorization 'basic' support
+            if (strpos($authorizationHeader, 'Basic ') === 0) {
+                // check the header value for authentication basic,
+                // only base64 characters are allowed
+                if (preg_match('~[^a-zA-Z0-9+/=]~', substr($authorizationHeader, 6)) === 0) {
+                    $headers[] = 'Authorization: ' . $authorizationHeader;
+                }
+            } elseif (strpos($authorizationHeader, 'Digest ') === 0) {
+                $digestAuthorization = true;
+            }
+        }
+
+        if ($digestAuthorization === true) {
+            $return = $this->_getUrlWithDigestAuthentication($url, $includeHeaders, $headers, $report);
+        } else {
+            $return = GeneralUtility::getURL($url, $includeHeaders, $headers, $report);
+        }
+
+        if ($return === false) {
+            throw new \Exception('Fetching the 40' . ($this->_isForbiddenError ? '3' : '4') . ' page failed with error #' . $report['error'] . ': "' . $report['message'] . '"');
+        }
+        return $return;
+    }
+
+    /**
+     * Wrapper method for the cURL-enabled part of GeneralUtility::getURL()
+     *
+     * Additionally, this method configures cURL to use HTTP digest
+     * authentication.
+     *
+     * Note: other than GeneralUtility::getURL(), this method supports no
+     * redirection itself. Redirection won't work, when cURL option
+     * CURLOPT_FOLLOWLOCATION con't be applied!
+     *
+     * @throws \Exception
+     * @param string $url
+     * @param int $includeHeaders
+     * @param array $headers
+     * @param array $report
+     * @return mixed
+     * @see \TYPO3\CMS\Core\Utility\GeneralUtility::getURL()
+     */
+    protected function _getUrlWithDigestAuthentication($url, $includeHeaders = 0, array $headers = array(), &$report = null)
+    {
+        if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse'] != '1') {
+            throw new \Exception('cURL usage must be enabled ($GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'curlUse\']) when using HTTP digest authentication.');
+        }
+
+        // prepare username/password
+        list ($username, $password) = GeneralUtility::trimExplode(':', $this->_digestAuthentication, false, 2);
+
+        // do (almost) the same things as in GeneralUtility::getURL();
+        if (!function_exists('curl_init') || !($ch = curl_init())) {
+            if (isset($report)) {
+                $report['error'] = -1;
+                $report['message'] = 'Couldn\'t initialize cURL.';
+            }
+            return false;
+        }
+
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($ch, CURLOPT_HEADER, (intval($includeHeaders) > 0));
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_FAILONERROR, true);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, max(0, (int) $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlTimeout']));
+        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
+        curl_setopt($ch, CURLOPT_USERPWD, $username. ':' . $password);
+        if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyServer']) {
+            curl_setopt($ch, CURLOPT_PROXY, $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyServer']);
+            if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyNTLM']) {
+                curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_NTLM);
+            }
+            if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyTunnel']) {
+                curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyTunnel']);
+            }
+            if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyUserPass']) {
+                curl_setopt($ch, CURLOPT_PROXYUSERPWD, $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyUserPass']);
+            }
+        }
+
+        // apply the http headers
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+
+        // execute the request
+        $content = curl_exec($ch);
+        $curlInfo = curl_getinfo($ch);
+
+        // strip http headers
+        // @see GeneralUtility::stripHttpHeaders()
+        if ($includeHeaders < 1) {
+            $headersEndPos = strpos($content, CRLF . CRLF);
+            if ($headersEndPos !== false) {
+                $content = substr($content, $headersEndPos + 4);
+            }
+        }
+
+        if (isset($report)) {
+            if ($content === false) {
+                $report['error'] = curl_errno($ch);
+                $report['message'] = curl_error($ch);
+            } elseif ($includeHeader) {
+                $report['http_code'] = $curlInfo['http_code'];
+                $report['content_type'] = $curlInfo['content_type'];
+            }
+        }
+        curl_close($ch);
+        return $content;
+    }
+
+    /**
+     * Returns all HTTP headers from the current request
+     *
+     * @return array
+     */
+    protected function _getAllHttpHeaders()
+    {
+        if (function_exists('getallheaders')) {
+            return \getallheaders();
+        } else {
+            $headers = array();
+            foreach ($_SERVER as $key => $value) {
+                if (strpos($key, 'HTTP_') === 0) {
+                    $name = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
+                    $headers[$name] = $value;
+                }
+            }
+            return $headers;
+        }
+    }
+
+    /**
      * Sets the forbiddenHeader to the accurate value
      *
      * @param int $number
index a5b0a16..8ee0456 100644 (file)
@@ -179,6 +179,15 @@ if(!isset($conf['disableDomainConfig']) || empty($conf['disableDomainConfig']))
                 'default' => '',
             )
         ),
+        'tx_pagenotfoundhandling_digestAuthentication' => array(
+            'exclude' => 1,
+            'label' => 'LLL:EXT:pagenotfoundhandling/locallang_db.xml:pagenotfoundhandling.sys_domain.digestAuthentication',
+            'displayCond' => 'FIELD:tx_pagenotfoundhandling_enable:REQ:true',
+            'config' => array (
+                'type' => 'input',
+                'default' => '',
+            )
+        ),
     );
 
     // avoid deprecation messages
@@ -214,7 +223,7 @@ if(!isset($conf['disableDomainConfig']) || empty($conf['disableDomainConfig']))
         'canNotCollapse' => true,
     );
     $GLOBALS['TCA']['sys_domain']['palettes']['pagenotfoundhandling_palette_opts'] = array(
-        'showitem' => 'tx_pagenotfoundhandling_passthroughContentTypeHeader,tx_pagenotfoundhandling_sendXForwardedForHeader,--linebreak--,tx_pagenotfoundhandling_additionalHeaders',
+        'showitem' => 'tx_pagenotfoundhandling_passthroughContentTypeHeader,tx_pagenotfoundhandling_sendXForwardedForHeader,--linebreak--,tx_pagenotfoundhandling_additionalHeaders,--linebreak--,tx_pagenotfoundhandling_digestAuthentication',
         'canNotCollapse' => true,
     );
 
index 45b60b0..9b484be 100644 (file)
@@ -287,6 +287,29 @@ on the extension name to open the configuration interface.
    Default
          -
 
+.. container:: table-row
+
+   Property
+         digestAuthentication
+   
+   Data type
+         string
+   
+   Description
+         If the 404/403 page is access restricted with HTTP digest
+         authentication, you can configure the username and password here.
+         
+         Format: "username:password"
+         
+         HTTP digest authentication requires cURL to be installed and enabled in
+         $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse']
+         
+         Note: this is NOT required for HTTP basic authentication (this type of
+         authentication works out-of-the box)!
+   
+   Default
+         -
+
 
 .. ###### END~OF~TABLE ######
 
index b5f46e6..eb1bd3d 100644 (file)
@@ -34,6 +34,9 @@ sendXForwardedForHeader = 0
 # cat=basic/enable; type=string; label=LLL:EXT:pagenotfoundhandling/locallang_db.xml:pagenotfoundhandling.constants.additionalHeaders
 additionalHeaders = 
 
+# cat=basic/enable; type=string; label=LLL:EXT:pagenotfoundhandling/locallang_db.xml:pagenotfoundhandling.constants.digestAuthentication
+digestAuthentication = 
+
 # cat=basic/language; type=boolean; label=LLL:EXT:pagenotfoundhandling/locallang_db.xml:pagenotfoundhandling.constants.ignoreLanguage
 ignoreLanguage = 0
 
@@ -47,4 +50,4 @@ locallangFile =
 defaultLanguageKey = default
 
 # cat=basic/language; type=small; label=LLL:EXT:pagenotfoundhandling/locallang_db.xml:pagenotfoundhandling.constants.languageParam
-languageParam = L
\ No newline at end of file
+languageParam = L
index c9bef4c..723ab02 100644 (file)
@@ -13,4 +13,5 @@ CREATE TABLE sys_domain (
     tx_pagenotfoundhandling_passthroughContentTypeHeader tinyint(3) DEFAULT '0' NOT NULL,
     tx_pagenotfoundhandling_sendXForwardedForHeader tinyint(3) DEFAULT '0' NOT NULL,
     tx_pagenotfoundhandling_additionalHeaders text,
-);
\ No newline at end of file
+    tx_pagenotfoundhandling_digestAuthentication varchar(255) DEFAULT '' NOT NULL,
+);
index 9eb270d..6ef8a45 100644 (file)
@@ -19,8 +19,9 @@
             <label index="pagenotfoundhandling.sys_domain.languageParam">Language parameter</label>
             <label index="pagenotfoundhandling.sys_domain.passthroughContentTypeHeader">Passthrough content-type header</label>
             <label index="pagenotfoundhandling.sys_domain.sendXForwardedForHeader">Send X-Forwarded-For header</label>
-            <label index="pagenotfoundhandling.sys_domain.additionalHeaders">Additional HTTP headers</label>
-            
+            <label index="pagenotfoundhandling.sys_domain.additionalHeaders">Additional HTTP headers sent to the client</label>
+            <label index="pagenotfoundhandling.sys_domain.digestAuthentication">HTTP digest authentication</label>
+
             <label index="pagenotfoundhandling.constants.forceLanguage">Force language: This language is used to fetch default404Page and/or marker substitution in defaultTemplateFile. Default: ''</label>
             <label index="pagenotfoundhandling.constants.default404Page">Default 404 Page:An uid of a page out of the page tree that will be returned as 404 page. '0' disables this feature. Default: '0'.</label>
             <label index="pagenotfoundhandling.constants.additional404GetParams">Additional GET params:These will be appended to the URL when fetching default404Page. Default: ''</label>
@@ -38,6 +39,7 @@
             <label index="pagenotfoundhandling.constants.sendXForwardedForHeader">Send X-Forwarded-For header:Send the REMOTE_ADDR in the 'X-Forwarded-For' HTTP header. Default: '0'</label>
             <label index="pagenotfoundhandling.constants.additionalHeaders">Additional HTTP headers: Send additional HTTP headers with the 404/403 page. Separate headers with '|'. Default: ''</label>
             <label index="pagenotfoundhandling.constants.absoluteReferencePrefix">Additional reference prefix: If your TYPO3 installation runs in a subdir of the DOCUMENT_ROOT, add the relative path from DOCUMENT_ROOT to the installation here. Default: ''</label>
+            <label index="pagenotfoundhandling.constants.digestAuthentication">HTTP digest authentication:Username and password when using HTTP digest authentication (this is not required for HTTP basic authentication). Format: "username:password". Default: ''</label>
         </languageKey>
         <languageKey index="de" type="array">
             <label index="pagenotfoundhandling.sys_domain.tcasheet.title">404 Behandlung</label>
@@ -53,8 +55,9 @@
             <label index="pagenotfoundhandling.sys_domain.languageParam">Sprachparameter</label>
             <label index="pagenotfoundhandling.sys_domain.passthroughContentTypeHeader">Content-type header weitergeben</label>
             <label index="pagenotfoundhandling.sys_domain.sendXForwardedForHeader">X-Forwarded-For header senden</label>
-            <label index="pagenotfoundhandling.sys_domain.additionalHeaders">Zusätzliche HTTP header</label>
-            
+            <label index="pagenotfoundhandling.sys_domain.additionalHeaders">Zusätzliche HTTP header die zum Client gesendet werden</label>
+            <label index="pagenotfoundhandling.sys_domain.digestAuthentication">HTTP digest authentication</label>
+
             <label index="pagenotfoundhandling.constants.forceLanguage">Sprache erzwingen: Diese Sprache wird für die Ersetzung der Marker verwendet. Standard: ''</label>
             <label index="pagenotfoundhandling.constants.default404Page">Standard 404 Seite:Die Uid einer Seite im TYPO3 Seitenbaum, die als 404 Seite ausgegeben werden soll. '0' deaktiviert diese Funktion. Standard: '0'.</label>
             <label index="pagenotfoundhandling.constants.additional404GetParams">Zusätzliche GET Parameter:Diese werden beim Holen der default404Page verwendet. Standard: ''</label>
@@ -72,6 +75,7 @@
             <label index="pagenotfoundhandling.constants.sendXForwardedForHeader">X-Forwarded-For header senden:Sendet die REMOTE_ADDR im 'X-Forwarded-For' HTTP header. Standard: '0'</label>
             <label index="pagenotfoundhandling.constants.additionalHeaders">Zusätzliche HTTP header: Sendet die hier definierten HTTP headers mit der 404/403 Seite. Mehrere headers mit '|' trennen. Standard: ''</label>
             <label index="pagenotfoundhandling.constants.absoluteReferencePrefix">Absoluter Referenz-Präfix: Falls TYPO3 nicht direkt im DOCUMENT_ROOT installiert ist, muss der relative Pfad vom DOCUMENT_ROOT zur Installation hier angegeben werden. Standard: ''</label>
+            <label index="pagenotfoundhandling.constants.digestAuthentication">HTTP digest authentication:Benutzername und Passwort für HTTP digest authentication (nicht notwendig, wenn HTTP basic authentication verwendet wird). Format: "benutzername:passwort". Standard: ''</label>
         </languageKey>
     </data>
-</T3locallang>
\ No newline at end of file
+</T3locallang>