Skip to content

Instantly share code, notes, and snippets.

@J7mbo
Last active August 29, 2015 14:04
Show Gist options
  • Select an option

  • Save J7mbo/64a02d00d5439a8ac8f1 to your computer and use it in GitHub Desktop.

Select an option

Save J7mbo/64a02d00d5439a8ac8f1 to your computer and use it in GitHub Desktop.
An example DigestRequestHandler
<?php
namespace SomeEngineName\Api\DigestRequestHandler;
use SomeEngineName\Api\UserRepository\UserNotFoundException,
SomeEngineName\Api\UserRepository\UserRepository;
/**
* Class DigestRequestHandler
*
* Handles digest requests on the server-side and throws exceptions which should be used to return valid http responses
*
* @package SomeEngineName\Api\DigestRequestHandler
*/
class DigestRequestHandler
{
/**
* @var UserRepository
*/
protected $userRepository;
/**
* @var DigestRequestChallengeHeader
*/
protected $challengeHeader;
/**
* @constructor
*
* @param UserRepository $userRepository
* @param DigestRequestChallengeHeader $challengeHeader
*/
public function __construct(UserRepository $userRepository, DigestRequestChallengeHeader $challengeHeader)
{
$this->challengeHeader = $challengeHeader;
$this->userRepository = $userRepository;
}
/**
* Handle the request header
*
* @param string $digestHeader This should be the contents of the $_SERVER['PHP_AUTH_DIGEST'] header
* @param string $requestMethod This should be either 'GET' or 'POST'
*
* @throws DigestRequestException When given an incorrectly constructed request (via parseDigestHeader())
* @throws DigestInvalidCredentialsException When given an invalid username / password
*
* @return DigestRequestChallengeHeader|true Header is for WWW-Authenticate, true means authentication successful
*/
public function handleRequest($digestHeader, $requestMethod)
{
/** If the user has sent a request without authentication - this should be used for a http 401 **/
if (empty($digestHeader))
{
return $this->challengeHeader;
}
$data = $this->parseDigestHeader($digestHeader);
try
{
$dbUser = $this->userRepository->findByEmail($data['username']);
$expectedHash = $this->calculateResponseHash($data, $requestMethod, $dbUser['HTTPDigestPassword']);
/** Invalid username or password - this should be used for a http 400 with an auth message **/
if ($data['response'] !== $expectedHash)
{
throw new DigestInvalidCredentialsException(
sprintf('Response hash did not match expected hash (invalid username / password)')
);
}
}
catch (UserNotFoundException $e)
{
/** If the user has sent a request with invalid credentials - this should be used for a http 400 **/
throw new DigestInvalidCredentialsException($e->getMessage());
}
return true;
}
/**
* Parse digest header and check for all required parameters
*
* @link http://php.net/manual/en/features.http-auth.php
*
* @param string $digestHeader
*
* @throws DigestRequestException Telling the consumer to return a http 400 Bad Request
*
* @return array
*/
protected function parseDigestHeader($digestHeader)
{
$neededParts = array('nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1);
$data = array();
$keys = implode('|', array_keys($neededParts));
preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $digestHeader, $matches, PREG_SET_ORDER);
foreach ($matches as $m)
{
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($neededParts[$m[1]]);
}
if ($neededParts)
{
throw new DigestRequestException(sprintf("Digest Header did not contain: %s", print_r($neededParts, true)));
}
return $data;
}
/**
* Generates the expected response hash that should match the one provided
*
* @param array $data The pre-validated data array provided by the client
* @param string $requestMethod The request method (GET|POST)
* @param string $digestPassword The (hopefully hashed) digest password from the database
*
* @return string The expected response hash
*/
protected function calculateResponseHash($data, $requestMethod, $digestPassword)
{
$A2 = md5(sprintf('%s:%s', $requestMethod, $data['uri']));
$expectedResponseHash = md5(sprintf('%s:%s:%s:%s:%s:%s', $digestPassword, $data['nonce'], $data['nc'], $data['cnonce'], $data['qop'], $A2));
return $expectedResponseHash;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment