From e83a86fd302dfaa3667894be2d734a9fcc854076 Mon Sep 17 00:00:00 2001 From: olegbaturin Date: Thu, 1 Jul 2021 17:06:38 +0700 Subject: [PATCH] Fix #18648: Fix `yii\web\Request` to properly handle HTTP Basic Auth headers --- framework/CHANGELOG.md | 1 + framework/web/Request.php | 21 +++++++++++++++------ tests/framework/filters/auth/BasicAuthTest.php | 13 +++++++++++-- tests/framework/web/RequestTest.php | 13 ++++++++----- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index fbe1ff7..6624066 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -12,6 +12,7 @@ Yii Framework 2 Change Log - Enh #18676: Added method `yii\helpers\BaseFileHelper::changeOwnership()` and properties `newFileMode`/`newFileOwnership` in `yii\console\controllers\BaseMigrateController` (rhertogh) - Bug #18678: Fix `yii\caching\DbCache` to use configured cache table name instead of the default one in case of MSSQL varbinary column type detection (aidanbek) - Enh #18695: Added `yii\web\Cookie::SAME_SITE_NONE` constant (rhertogh) +- Bug #18648: Fix `yii\web\Request` to properly handle HTTP Basic Auth headers (olegbaturin) - Enh #18726: Added `yii\helpers\Json::$prettyPrint` (rhertogh) diff --git a/framework/web/Request.php b/framework/web/Request.php index 70fa32d..51dd502 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -371,10 +371,16 @@ class Request extends \yii\base\Request $this->_headers->add($name, $value); } } else { + // ['prefix' => length] + $headerPrefixes = ['HTTP_' => 5, 'REDIRECT_HTTP_' => 14]; + foreach ($_SERVER as $name => $value) { - if (strncmp($name, 'HTTP_', 5) === 0) { - $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))); - $this->_headers->add($name, $value); + foreach ($headerPrefixes as $prefix => $length) { + if (strncmp($name, $prefix, $length) === 0) { + $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, $length))))); + $this->_headers->add($name, $value); + continue 2; + } } } } @@ -1321,13 +1327,16 @@ class Request extends \yii\base\Request return [$username, $password]; } - /* + /** * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default. - * To make it work, add the following line to to your .htaccess file: + * To make it work, add one of the following lines to to your .htaccess file: * + * SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0 + * --OR-- * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] */ - $auth_token = $this->getHeaders()->get('HTTP_AUTHORIZATION') ?: $this->getHeaders()->get('REDIRECT_HTTP_AUTHORIZATION'); + $auth_token = $this->getHeaders()->get('Authorization'); + if ($auth_token !== null && strncasecmp($auth_token, 'basic', 5) === 0) { $parts = array_map(function ($value) { return strlen($value) === 0 ? null : $value; diff --git a/tests/framework/filters/auth/BasicAuthTest.php b/tests/framework/filters/auth/BasicAuthTest.php index 55d8ec2..1bde8a8 100644 --- a/tests/framework/filters/auth/BasicAuthTest.php +++ b/tests/framework/filters/auth/BasicAuthTest.php @@ -27,10 +27,13 @@ class BasicAuthTest extends AuthTest */ public function testHttpBasicAuth($token, $login) { + $original = $_SERVER; + $_SERVER['PHP_AUTH_USER'] = $token; $_SERVER['PHP_AUTH_PW'] = 'whatever, we are testers'; $filter = ['class' => HttpBasicAuth::className()]; $this->ensureFilterApplies($token, $login, $filter); + $_SERVER = $original; } /** @@ -40,9 +43,12 @@ class BasicAuthTest extends AuthTest */ public function testHttpBasicAuthWithHttpAuthorizationHeader($token, $login) { - Yii::$app->request->headers->set('HTTP_AUTHORIZATION', 'Basic ' . base64_encode($token . ':' . 'mypw')); + $original = $_SERVER; + + $_SERVER['HTTP_AUTHORIZATION'] = 'Basic ' . base64_encode($token . ':' . 'mypw'); $filter = ['class' => HttpBasicAuth::className()]; $this->ensureFilterApplies($token, $login, $filter); + $_SERVER = $original; } /** @@ -52,9 +58,12 @@ class BasicAuthTest extends AuthTest */ public function testHttpBasicAuthWithRedirectHttpAuthorizationHeader($token, $login) { - Yii::$app->request->headers->set('REDIRECT_HTTP_AUTHORIZATION', 'Basic ' . base64_encode($token . ':' . 'mypw')); + $original = $_SERVER; + + $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] = 'Basic ' . base64_encode($token . ':' . 'mypw'); $filter = ['class' => HttpBasicAuth::className()]; $this->ensureFilterApplies($token, $login, $filter); + $_SERVER = $original; } /** diff --git a/tests/framework/web/RequestTest.php b/tests/framework/web/RequestTest.php index 934148b..551e137 100644 --- a/tests/framework/web/RequestTest.php +++ b/tests/framework/web/RequestTest.php @@ -991,18 +991,21 @@ class RequestTest extends TestCase */ public function testHttpAuthCredentialsFromHttpAuthorizationHeader($secret, $expected) { - $request = new Request(); + $original = $_SERVER; - $request->getHeaders()->set('HTTP_AUTHORIZATION', 'Basic ' . $secret); + $request = new Request(); + $_SERVER['HTTP_AUTHORIZATION'] = 'Basic ' . $secret; $this->assertSame($request->getAuthCredentials(), $expected); $this->assertSame($request->getAuthUser(), $expected[0]); $this->assertSame($request->getAuthPassword(), $expected[1]); - $request->getHeaders()->offsetUnset('HTTP_AUTHORIZATION'); + $_SERVER = $original; - $request->getHeaders()->set('REDIRECT_HTTP_AUTHORIZATION', 'Basic ' . $secret); + $request = new Request(); + $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] = 'Basic ' . $secret; $this->assertSame($request->getAuthCredentials(), $expected); $this->assertSame($request->getAuthUser(), $expected[0]); $this->assertSame($request->getAuthPassword(), $expected[1]); + $_SERVER = $original; } public function testHttpAuthCredentialsFromServerSuperglobal() @@ -1013,7 +1016,7 @@ class RequestTest extends TestCase $_SERVER['PHP_AUTH_PW'] = $pw; $request = new Request(); - $request->getHeaders()->set('HTTP_AUTHORIZATION', 'Basic ' . base64_encode('less-priority:than-PHP_AUTH_*')); + $request->getHeaders()->set('Authorization', 'Basic ' . base64_encode('less-priority:than-PHP_AUTH_*')); $this->assertSame($request->getAuthCredentials(), [$user, $pw]); $this->assertSame($request->getAuthUser(), $user);