浏览代码

merged branch kepten/ticket_1813 (PR #3551)

Commits
-------

a450d00 [HttpFoundation] HTTP Basic authentication is broken with PHP as cgi/fastCGI under Apache

Discussion
----------

[HttpFoundation] HTTP Basic authentication is broken with php-cgi under Apache

Bug fix: yes
Feature addition: no
Backwards compatibility break: no
Symfony2 tests pass: yes
Fixes the following tickets: #1813
Todo: -

In order to work, add this to the .htaccess:

RewriteEngine on
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ app.php [QSA,L]

---------------------------------------------------------------------------

by stof at 2012-03-10T17:34:26Z

you should also add a unit test for this

---------------------------------------------------------------------------

by kepten at 2012-03-11T15:34:04Z

Thanks for the feedback, I committed the changes.

---------------------------------------------------------------------------

by stof at 2012-04-04T01:59:53Z

@fabpot could you review it ?

---------------------------------------------------------------------------

by fabpot at 2012-04-04T07:15:34Z

My comments:

 * `ServerBag` represents what we have in the `$_SERVER` global variables. As such, the code should be moved to the `getHeaders()` method instead like the other tweaks we do for the HTTP headers.

 * A comment must be added explaining why this is needed and the configuration the user must have to make it work (then remove the Github URLs).

 * The code should only be executed when `PHP_AUTH_USER` is not available (to not have any overhead when not needed).

---------------------------------------------------------------------------

by danielholmes at 2012-04-14T13:27:09Z

A quick note on that .htaccess/apache configuration required, if adding to the Symfony SE htaccess file, then it will need to look like this:

```
<IfModule mod_rewrite.c>
    RewriteEngine On

    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ app.php [QSA,L]
</IfModule>
```

NOTE: No **,L** in the Authorization Rewrite as in the original example - it prevents the front controller rewrite from happening

---------------------------------------------------------------------------

by towards at 2012-04-20T16:12:49Z

@kepten you were faster than me applying @fabpot's comments :) nevertheless part of the bug hunt day I also modified the ServerBag class and tested them on a productive LAMP hosting server using Apache and FastCGI

---------------------------------------------------------------------------

by kepten at 2012-04-20T16:15:57Z

ok, so is my PR is useless or should I still fix problems?

---------------------------------------------------------------------------

by towards at 2012-04-20T16:20:26Z

your PR is fine for sure and I don't want to interfere, just wanted to mention that part of the bug hunt day of Symfony I had a go at this PR as an "exercise" but just saw later on that you already fixed the problem, so you can ignore my pushes

---------------------------------------------------------------------------

by vicb at 2012-04-20T16:20:36Z

I have been working with @towards: your PR is useful, please implement his comments and squash your PR.

---------------------------------------------------------------------------

by kepten at 2012-04-20T16:59:07Z

never squashed before, is it okay now? :)

---------------------------------------------------------------------------

by stof at 2012-04-20T17:21:07Z

it is

---------------------------------------------------------------------------

by vicb at 2012-05-20T19:57:51Z

@fabpot this should be ready to be merged
Fabien Potencier 13 年之前
父节点
当前提交
87bb3661fc

+ 35 - 3
src/Symfony/Component/HttpFoundation/ServerBag.php

@@ -16,6 +16,7 @@ namespace Symfony\Component\HttpFoundation;
  *
  * @author Fabien Potencier <fabien@symfony.com>
  * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
+ * @author Robert Kiss <kepten@gmail.com>
  */
 class ServerBag extends ParameterBag
 {
@@ -32,10 +33,41 @@ class ServerBag extends ParameterBag
             }
         }
 
-        // PHP_AUTH_USER/PHP_AUTH_PW
         if (isset($this->parameters['PHP_AUTH_USER'])) {
-            $pass = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : '';
-            $headers['AUTHORIZATION'] = 'Basic '.base64_encode($this->parameters['PHP_AUTH_USER'].':'.$pass);
+            $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER'];
+            $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : '';
+        } else {
+            /*
+             * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
+             * For this workaround to work, add this line to your .htaccess file:
+             * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+             *
+             * A sample .htaccess file:
+             * RewriteEngine On
+             * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+             * RewriteCond %{REQUEST_FILENAME} !-f
+             * RewriteRule ^(.*)$ app.php [QSA,L]
+             */
+
+            $authorizationHeader = null;
+            if (isset($this->parameters['HTTP_AUTHORIZATION'])) {
+                $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION'];
+            } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) {
+                $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION'];
+            }
+
+            // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW
+            if (null !== $authorizationHeader) {
+                $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)));
+                if (count($exploded) == 2) {
+                    list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
+                }
+            }
+        }
+
+        // PHP_AUTH_USER/PHP_AUTH_PW
+        if (isset($headers['PHP_AUTH_USER'])) {
+            $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
         }
 
         return $headers;

+ 2 - 2
src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php

@@ -56,7 +56,7 @@ class BasicAuthenticationListener implements ListenerInterface
     {
         $request = $event->getRequest();
 
-        if (false === $username = $request->server->get('PHP_AUTH_USER', false)) {
+        if (false === $username = $request->headers->get('PHP_AUTH_USER', false)) {
             return;
         }
 
@@ -71,7 +71,7 @@ class BasicAuthenticationListener implements ListenerInterface
         }
 
         try {
-            $token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->server->get('PHP_AUTH_PW'), $this->providerKey));
+            $token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->headers->get('PHP_AUTH_PW'), $this->providerKey));
             $this->securityContext->setToken($token);
         } catch (AuthenticationException $failed) {
             $this->securityContext->setToken(null);

+ 40 - 1
tests/Symfony/Tests/Component/HttpFoundation/ServerBagTest.php

@@ -40,6 +40,8 @@ class ServerBagTest extends \PHPUnit_Framework_TestCase
             'CONTENT_LENGTH' => '0',
             'ETAG' => 'asdf',
             'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'),
+            'PHP_AUTH_USER' => 'foo',
+            'PHP_AUTH_PW' => 'bar',
         ), $bag->getHeaders());
     }
 
@@ -47,6 +49,43 @@ class ServerBagTest extends \PHPUnit_Framework_TestCase
     {
         $bag = new ServerBag(array('PHP_AUTH_USER' => 'foo'));
 
-        $this->assertEquals(array('AUTHORIZATION' => 'Basic '.base64_encode('foo:')), $bag->getHeaders());
+        $this->assertEquals(array(
+            'AUTHORIZATION' => 'Basic '.base64_encode('foo:'),
+            'PHP_AUTH_USER' => 'foo',
+            'PHP_AUTH_PW' => ''
+        ), $bag->getHeaders());
+    }
+
+    public function testHttpBasicAuthWithPhpCgi()
+    {
+        $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:bar')));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'),
+            'PHP_AUTH_USER' => 'foo',
+            'PHP_AUTH_PW' => 'bar'
+        ), $bag->getHeaders());
+    }
+
+    public function testHttpBasicAuthWithPhpCgiRedirect()
+    {
+        $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:bar')));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'),
+            'PHP_AUTH_USER' => 'foo',
+            'PHP_AUTH_PW' => 'bar'
+        ), $bag->getHeaders());
+    }
+
+    public function testHttpBasicAuthWithPhpCgiEmptyPassword()
+    {
+        $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:')));
+
+        $this->assertEquals(array(
+            'AUTHORIZATION' => 'Basic '.base64_encode('foo:'),
+            'PHP_AUTH_USER' => 'foo',
+            'PHP_AUTH_PW' => ''
+        ), $bag->getHeaders());
     }
 }