Last active
August 29, 2015 14:04
-
-
Save J7mbo/64a02d00d5439a8ac8f1 to your computer and use it in GitHub Desktop.
An example DigestRequestHandler
This file contains hidden or 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 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