Created
July 27, 2012 04:12
-
-
Save emarref/3186139 to your computer and use it in GitHub Desktop.
Class that handles HTTP Digest Authentication
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 | |
class HttpDigestAuthentication | |
{ | |
private $realm = 'Restricted area'; | |
private $logins = array(); | |
private $digestProperties = array(); | |
private $authenticatedAs; | |
public function __construct(array $logins) | |
{ | |
$this->logins = $logins; | |
} | |
public function isAuthenticated() | |
{ | |
return $this->hasProvidedCredentials() && $this->isValidCredentials(); | |
} | |
public function isAnonymous() | |
{ | |
return !$this->isAuthenticated(); | |
} | |
protected function hasProvidedCredentials() | |
{ | |
return isset($_SERVER['PHP_AUTH_DIGEST']); | |
} | |
public function authenticate() | |
{ | |
try { | |
$this->authenticatedAs = $this->isValidCredentials(); | |
} catch (HttpDigestAuthenticationException $ex) { | |
$this->requireCredentials(); | |
} | |
} | |
protected function isValidCredentials() | |
{ | |
if (!$this->hasProvidedCredentials()) { | |
throw new HttpDigestAuthenticationException('No credentials provided'); | |
} | |
if (false === $this->_parseHttpDigest()) { | |
throw new HttpDigestAuthenticationException('Could not parse http digest'); | |
} | |
$username = $this->getDigestProperty('username'); | |
error_log('Authenticating '.$username); | |
if (!isset($this->logins[$username])) { | |
// Attempted username does not exist | |
throw new HttpDigestAuthenticationException('Incorrect username'); | |
} | |
$password = $this->logins[$username]; | |
$validResponse = md5(sprintf( | |
'%s:%s:%s:%s:%s:%s', | |
md5(sprintf('%s:%s:%s', $username, $this->realm, $password)), | |
$this->getDigestProperty('nonce'), | |
$this->getDigestProperty('nc'), | |
$this->getDigestProperty('cnonce'), | |
$this->getDigestProperty('qop'), | |
md5(sprintf('%s:%s', $_SERVER['REQUEST_METHOD'], $this->getDigestProperty('uri'))) | |
)); | |
if ($this->getDigestProperty('response') != $validResponse) { | |
// Username is valid, but password is incorrect | |
throw new HttpDigestAuthenticationException('Incorrect password'); | |
} | |
return $username; | |
} | |
public function requireCredentials($message = 'Restricted area') | |
{ | |
ob_end_clean(); | |
header('HTTP/1.1 401 Unauthorized'); | |
header( | |
sprintf('WWW-Authenticate: Digest realm="%s",qop="auth",nonce="%s",opaque="%s"', | |
$this->getRealm(), | |
$this->_generateNonce(), | |
$this->_generateOpaque() | |
)); | |
exit($message); | |
} | |
public function setRealm($realm) | |
{ | |
$this->realm = $realm; | |
} | |
public function getRealm() | |
{ | |
return $this->realm; | |
} | |
protected function _generateNonce() | |
{ | |
return uniqid(); | |
} | |
protected function _generateOpaque() | |
{ | |
return md5($this->realm); | |
} | |
public function getCredentials() | |
{ | |
if ($this->isAuthenticated()) { | |
return array('username' => $this->authenticatedAs, 'password' => $this->logins[$this->authenticatedAs]); | |
} else { | |
return false; | |
} | |
} | |
protected function getDigestProperty($key, $default = null) | |
{ | |
return isset($this->digestProperties[$key]) ? $this->digestProperties[$key] : $default; | |
} | |
private function _parseHttpDigest() | |
{ | |
$neededParts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1); | |
$data = array(); | |
$keys = implode('|', array_keys($neededParts)); | |
$matches = array(); | |
preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $_SERVER['PHP_AUTH_DIGEST'], $matches, PREG_SET_ORDER); | |
foreach ($matches as $m) { | |
$data[$m[1]] = $m[3] ? $m[3] : $m[4]; | |
unset($neededParts[$m[1]]); | |
} | |
$this->digestProperties = $neededParts ? false : $data; | |
} | |
} | |
class HttpDigestAuthenticationException extends Exception { | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment