Skip to content

Commit

Permalink
Merge pull request UnionOfRAD#981 from davidpersson/http-auth-cgi
Browse files Browse the repository at this point in the history
Enable HTTP basic and digest auth for FCGI/CGI environments
  • Loading branch information
nateabele committed Jun 29, 2013
2 parents 411c69f + 87fcffc commit 81c873a
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 15 deletions.
21 changes: 21 additions & 0 deletions action/Request.php
Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions security/auth/adapter/Http.php
Expand Up @@ -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 {

Expand Down
86 changes: 86 additions & 0 deletions tests/cases/action/RequestTest.php
Expand Up @@ -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'));
Expand Down
81 changes: 66 additions & 15 deletions tests/cases/security/auth/adapter/HttpTest.php
Expand Up @@ -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'));
Expand All @@ -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')
));
Expand All @@ -46,22 +39,50 @@ 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]);
$this->assertPattern('/qop="auth",/', $http->headers[0]);
$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);
Expand All @@ -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);
}
}

?>

0 comments on commit 81c873a

Please sign in to comment.