diff --git a/action/Request.php b/action/Request.php index b63ab0005e..511a440261 100644 --- a/action/Request.php +++ b/action/Request.php @@ -397,6 +397,27 @@ public function env($key) { case 'HTTP_BASE': $val = preg_replace('/^([^.])*/i', null, $this->env('HTTP_HOST')); break; + case 'PHP_AUTH_USER': + case 'PHP_AUTH_PW': + case 'PHP_AUTH_DIGEST': + if (!$header = $this->env('HTTP_AUTHORIZATION')) { + if (!$header = $this->env('REDIRECT_HTTP_AUTHORIZATION')) { + return $this->_computed[$key] = $val; + } + } + if (stripos($header, 'basic') === 0) { + $decoded = base64_decode(substr($header, strlen('basic '))); + + if (strpos($decoded, ':') !== false) { + list($user, $password) = explode(':', $decoded, 2); + + $this->_computed['PHP_AUTH_USER'] = $user; + $this->_computed['PHP_AUTH_PW'] = $password; + return $this->_computed[$key]; + } + } elseif (stripos($header, 'digest') === 0) { + return $this->_computed[$key] = substr($header, strlen('digest ')); + } default: $val = array_key_exists($key, $this->_env) ? $this->_env[$key] : $val; break; diff --git a/security/auth/adapter/Http.php b/security/auth/adapter/Http.php index 04c7d70650..50d10db116 100644 --- a/security/auth/adapter/Http.php +++ b/security/auth/adapter/Http.php @@ -25,8 +25,18 @@ * ))) * }}} * + * When running PHP as a CGI/FCGI PHP doesn't automatically parse the authorization + * header into `PHP_AUTH_*` headers. Lithium will work arround this issue by looking for + * a `HTTP_AUTHORIZATION` header instead. When using PHP as a CGI/FCGI in combination + * with Apache you must additionally add the following rewrite rule to your configuration + * in order to make the header available so Lithium can pick it up: + * {{{ + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * }}} + * * @link http://tools.ietf.org/html/rfc2068#section-14.8 * @see lithium\action\Request + * @see lithium\action\Request::env */ class Http extends \lithium\core\Object { diff --git a/tests/cases/action/RequestTest.php b/tests/cases/action/RequestTest.php index e84d2bce4f..0c30543492 100644 --- a/tests/cases/action/RequestTest.php +++ b/tests/cases/action/RequestTest.php @@ -246,6 +246,92 @@ public function testRemoteAddrFromHttpPcRemoteAddr() { $this->assertEqual('123.456.789.000', $request->env('REMOTE_ADDR')); } + public function testPhpAuthBasic() { + $request = new Request(array('env' => array( + 'PHP_AUTH_USER' => 'test-user', + 'PHP_AUTH_PW' => 'test-password' + ))); + $this->assertEqual('test-user', $request->env('PHP_AUTH_USER')); + $this->assertEqual('test-password', $request->env('PHP_AUTH_PW')); + + $request = new Request(array('env' => array( + 'PHP_AUTH_USER' => 'test-user', + 'PHP_AUTH_PW' => '' + ))); + $this->assertEqual('test-user', $request->env('PHP_AUTH_USER')); + $this->assertNull($request->env('PHP_AUTH_PW')); + + $request = new Request(array('env' => array( + 'PHP_AUTH_USER' => '', + 'PHP_AUTH_PW' => 'test-password' + ))); + $this->assertNull($request->env('PHP_AUTH_USER')); + $this->assertEqual('test-password', $request->env('PHP_AUTH_PW')); + } + + public function testCgiPhpAuthBasic() { + $request = new Request(array('env' => array( + 'HTTP_AUTHORIZATION' => 'Basic dGVzdC11c2VyOnRlc3QtcGFzc3dvcmQ=', + ))); + $this->assertEqual('test-user', $request->env('PHP_AUTH_USER')); + $this->assertEqual('test-password', $request->env('PHP_AUTH_PW')); + + $request = new Request(array('env' => array( + 'REDIRECT_HTTP_AUTHORIZATION' => 'Basic dGVzdC11c2VyOnRlc3QtcGFzc3dvcmQ=', + ))); + $this->assertEqual('test-user', $request->env('PHP_AUTH_USER')); + $this->assertEqual('test-password', $request->env('PHP_AUTH_PW')); + } + + public function testCgiPhpAuthBasicFailMissingPassword() { + $request = new Request(array('env' => array( + 'HTTP_AUTHORIZATION' => 'Basic dGVzdC11c2VyOg==', + ))); + $this->assertEqual('test-user', $request->env('PHP_AUTH_USER')); + $this->assertIdentical('', $request->env('PHP_AUTH_PW')); + + $request = new Request(array('env' => array( + 'HTTP_AUTHORIZATION' => 'Basic nRlc3QtcGFzc3dvcmQ=', + ))); + $this->assertNull($request->env('PHP_AUTH_USER')); + $this->assertNull($request->env('PHP_AUTH_PW')); + } + + public function testCgiPhpAuthBasicFailMissingPasswordAndColon() { + $request = new Request(array('env' => array( + 'HTTP_AUTHORIZATION' => 'Basic dGVzdC11c2Vy', + ))); + $this->assertNull($request->env('PHP_AUTH_USER')); + $this->assertNull($request->env('PHP_AUTH_PW')); + } + + public function testCgiPhpAuthBasicFailMissingUser() { + $request = new Request(array('env' => array( + 'HTTP_AUTHORIZATION' => 'Basic nRlc3QtcGFzc3dvcmQ=', + ))); + $this->assertNull($request->env('PHP_AUTH_USER')); + $this->assertNull($request->env('PHP_AUTH_PW')); + } + + public function testPhpAuthDigest() { + $request = new Request(array('env' => array( + 'PHP_AUTH_DIGEST' => 'test-digest' + ))); + $this->assertEqual('test-digest', $request->env('PHP_AUTH_DIGEST')); + } + + public function testCgiPhpAuthDigest() { + $request = new Request(array('env' => array( + 'HTTP_AUTHORIZATION' => 'Digest test-digest' + ))); + $this->assertEqual('test-digest', $request->env('PHP_AUTH_DIGEST')); + + $request = new Request(array('env' => array( + 'REDIRECT_HTTP_AUTHORIZATION' => 'Digest test-digest' + ))); + $this->assertEqual('test-digest', $request->env('PHP_AUTH_DIGEST')); + } + public function testBase() { $request = new Request(array('env' => array('PHP_SELF' => '/index.php'))); $this->assertEmpty($request->env('base')); diff --git a/tests/cases/security/auth/adapter/HttpTest.php b/tests/cases/security/auth/adapter/HttpTest.php index f111d045ec..cbabf167d8 100644 --- a/tests/cases/security/auth/adapter/HttpTest.php +++ b/tests/cases/security/auth/adapter/HttpTest.php @@ -14,17 +14,10 @@ class HttpTest extends \lithium\test\Unit { - public $request; - - public function setUp() { - $this->request = new Request(); - } - - public function tearDown() {} - - public function testCheckBasicIsFalse() { + public function testCheckBasicIsFalseRequestsAuth() { + $request = new Request(); $http = new MockHttp(array('method' => 'basic', 'users' => array('gwoo' => 'li3'))); - $result = $http->check($this->request); + $result = $http->check($request); $this->assertEmpty($result); $basic = basename(Libraries::get(true, 'path')); @@ -33,7 +26,7 @@ public function testCheckBasicIsFalse() { $this->assertEqual($expected, $result); } - public function testCheckBasicIsTrue() { + public function testCheckBasicIsTrueProcessesAuthAndSucceeds() { $request = new Request(array( 'env' => array('PHP_AUTH_USER' => 'gwoo', 'PHP_AUTH_PW' => 'li3') )); @@ -46,9 +39,36 @@ public function testCheckBasicIsTrue() { $this->assertEqual($expected, $result); } - public function testCheckDigestIsFalse() { + public function testCheckBasicIsTrueProcessesAuthAndSucceedsCgi() { + $basic = 'Z3dvbzpsaTM='; + + $request = new Request(array( + 'env' => array('HTTP_AUTHORIZATION' => "Basic {$basic}") + )); + $http = new MockHttp(array('method' => 'basic', 'users' => array('gwoo' => 'li3'))); + $result = $http->check($request); + $this->assertNotEmpty($result); + + $expected = array(); + $result = $http->headers; + $this->assertEqual($expected, $result); + + $request = new Request(array( + 'env' => array('REDIRECT_HTTP_AUTHORIZATION' => "Basic {$basic}") + )); + $http = new MockHttp(array('method' => 'basic', 'users' => array('gwoo' => 'li3'))); + $result = $http->check($request); + $this->assertNotEmpty($result); + + $expected = array(); + $result = $http->headers; + $this->assertEqual($expected, $result); + } + + public function testCheckDigestIsFalseRequestsAuth() { + $request = new Request(); $http = new MockHttp(array('realm' => 'app', 'users' => array('gwoo' => 'li3'))); - $result = $http->check($this->request); + $result = $http->check($request); $this->assertFalse($result); $this->assertPattern('/Digest/', $http->headers[0]); $this->assertPattern('/realm="app",/', $http->headers[0]); @@ -56,12 +76,13 @@ public function testCheckDigestIsFalse() { $this->assertPattern('/nonce=/', $http->headers[0]); } - public function testCheckDigestIsTrue() { - $digest = 'qop="auth",nonce="4bca0fbca7bd0",'; + public function testCheckDigestIsTrueProcessesAuthAndSucceeds() { + $digest = 'qop="auth",nonce="4bca0fbca7bd0",'; $digest .= 'nc="00000001",cnonce="95b2cd1e179bf5414e52ed62811481cf",'; $digest .= 'uri="/http_auth",realm="app",'; $digest .= 'opaque="d3fb67a7aa4d887ec4bf83040a820a46",username="gwoo",'; $digest .= 'response="04d7d878c67f289f37e553d2025e3a52"'; + $request = new Request(array('env' => array('PHP_AUTH_DIGEST' => $digest))); $http = new MockHttp(array('realm' => 'app', 'users' => array('gwoo' => 'li3'))); $result = $http->check($request); @@ -71,6 +92,36 @@ public function testCheckDigestIsTrue() { $result = $http->headers; $this->assertEqual($expected, $result); } + + public function testCheckDigestIsTrueProcessesAuthAndSucceedsCgi() { + $digest = 'qop="auth",nonce="4bca0fbca7bd0",'; + $digest .= 'nc="00000001",cnonce="95b2cd1e179bf5414e52ed62811481cf",'; + $digest .= 'uri="/http_auth",realm="app",'; + $digest .= 'opaque="d3fb67a7aa4d887ec4bf83040a820a46",username="gwoo",'; + $digest .= 'response="04d7d878c67f289f37e553d2025e3a52"'; + + $request = new Request(array( + 'env' => array('HTTP_AUTHORIZATION' => "Digest {$digest}") + )); + $http = new MockHttp(array('realm' => 'app', 'users' => array('gwoo' => 'li3'))); + $result = $http->check($request); + $this->assertNotEmpty($result); + + $expected = array(); + $result = $http->headers; + $this->assertEqual($expected, $result); + + $request = new Request(array( + 'env' => array('REDIRECT_HTTP_AUTHORIZATION' => "Digest {$digest}") + )); + $http = new MockHttp(array('realm' => 'app', 'users' => array('gwoo' => 'li3'))); + $result = $http->check($request); + $this->assertNotEmpty($result); + + $expected = array(); + $result = $http->headers; + $this->assertEqual($expected, $result); + } } ?> \ No newline at end of file