Created
April 22, 2016 20:12
-
-
Save rodmcnew/e7dedfc047c04b2f5cdda5c43c856ba5 to your computer and use it in GitHub Desktop.
HTTP Basic Auth Implementing Zend\Stratigility\MiddlewareInterface
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Controller; | |
use Psr\Http\Message\ResponseInterface as Response; | |
use Psr\Http\Message\ServerRequestInterface as Request; | |
use Zend\Stratigility\MiddlewareInterface; | |
/** | |
* Class HttpBasicAuth | |
* | |
* @package App\Controller | |
*/ | |
class HttpBasicAuth implements MiddlewareInterface | |
{ | |
protected $users; | |
protected $ignoreUrls; | |
/** | |
* Constructor | |
* | |
* @param array $users ['username' => 'password'] | |
* @param array $ignoreUrls ['/ignored/path'] | |
*/ | |
public function __construct(array $users = [], array $ignoreUrls = []) | |
{ | |
$this->users = $users; | |
$this->ignoreUrls = $ignoreUrls; | |
} | |
/** | |
* Process an incoming request and/or response. | |
* | |
* Accepts a server-side request and a response instance, and does | |
* something with them. | |
* | |
* If the response is not complete and/or further processing would not | |
* interfere with the work done in the middleware, or if the middleware | |
* wants to delegate to another process, it can use the `$next` callable | |
* if present. | |
* | |
* If the middleware does not return a value, execution of the current | |
* request is considered complete, and the response instance provided will | |
* be considered the response to return. | |
* | |
* Alternately, the middleware may return a response instance. | |
* | |
* Often, middleware will `return $next();`, with the assumption that a | |
* later middleware will return a response. | |
* | |
* @param Request $request | |
* @param Response $response | |
* @param null|callable $next | |
* @return null|Response | |
*/ | |
public function __invoke(Request $request, Response $response, callable $next = null) | |
{ | |
$basicAuthIgnoreUrls = $this->ignoreUrls; | |
if (!is_array($this->users) | |
|| count($this->users) < 1 | |
|| !isset($_SERVER['REQUEST_URI']) | |
|| in_array($_SERVER['REQUEST_URI'], $basicAuthIgnoreUrls) | |
) { | |
return $next($request, $response); | |
} | |
$realm = 'Restricted area'; | |
if (empty($_SERVER['PHP_AUTH_DIGEST'])) { | |
return $response->withStatus(401) | |
->withAddedHeader('WWW-Authenticate', 'Digest realm="' . $realm . | |
'",qop="auth",nonce="' . uniqid() . '",opaque="' . md5($realm) . '"'); | |
} | |
// analyze the PHP_AUTH_DIGEST variable | |
if (!($data = $this->httpParseDigest($_SERVER['PHP_AUTH_DIGEST'])) | |
|| !isset($this->users[$data['username']]) | |
) { | |
return $response->withStatus(401) | |
->withAddedHeader('WWW-Authenticate', 'Basic realm="My Realm"'); | |
} | |
// generate the valid response | |
$A1 = md5( | |
$data['username'] . ':' . $realm . ':' . $this->users[$data['username']] | |
); | |
$A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']); | |
$valid_response = md5( | |
$A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] | |
. ':' . $data['qop'] . ':' . $A2 | |
); | |
if ($data['response'] != $valid_response) { | |
return $response->withStatus(401) | |
->withAddedHeader('WWW-Authenticate', 'Basic realm="My Realm"'); | |
} | |
return $next($request, $response); | |
} | |
/** | |
* parse the http auth header | |
* | |
* @param $txt | |
* | |
* @return array|bool | |
*/ | |
protected function httpParseDigest($txt) | |
{ | |
// protect against missing data | |
$needed_parts = array( | |
'nonce' => 1, | |
'nc' => 1, | |
'cnonce' => 1, | |
'qop' => 1, | |
'username' => 1, | |
'uri' => 1, | |
'response' => 1 | |
); | |
$data = array(); | |
$keys = implode('|', array_keys($needed_parts)); | |
preg_match_all( | |
'@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', | |
$txt, | |
$matches, | |
PREG_SET_ORDER | |
); | |
foreach ($matches as $m) { | |
$data[$m[1]] = $m[3] ? $m[3] : $m[4]; | |
unset($needed_parts[$m[1]]); | |
} | |
return $needed_parts ? false : $data; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment