|
|
|
[
Permlink
| « Hide
]
Shahar Evron - 23/Mar/07 02:02 PM
note: look at the implementation of Zend_Auth_Http_Digest - I don't remember ever looking at it - might be useful
This is the sample script that contains all the functions needed for digest authentication, it currently runs externally from Zend_Http_Client, and is not intended to be Zend Framework ready code (comments are missing and the structure will change when integrated with Zend_Http_Client):
<?php // configure error reporting error_reporting(E_ALL | E_STRICT); // set include paths for zend framework set_include_path(PATH_SEPARATOR . get_include_path() . PATH_SEPARATOR . './library/'); // load loader and set autoload function require_once 'Zend/Loader.php'; spl_autoload_register(array('Zend_Loader', 'autoload')); /*-----------------------------------------------------------*/ $client = new Zend_Http_Client('http://services.msn.com/svcs/hotmail/httpmail.asp', array( 'useragent' => 'Outlook-Express/6.0', 'maxredirects' => 0)); $client->setMethod('PROPFIND'); $digest = new Digest($client, 'PROPFIND', 'zftest@hotmail.co.uk', 'password123'); $digest->request(); /*-----------------------------------------------------------*/ class Digest { private $client; private $method; // we shouldnt have to specify this, it should be possible to get it from the client private $username; private $password; private $nonce; private $nc = 1; private $a1; public function __construct(Zend_Http_Client $client, $method, $username, $password) { $this->client = $client; $this->method = $method; $this->username = $username; $this->password = $password; } public function request() { // make initial request $response = $this->client->request(); Zend_Debug::dump($response->getHeaders(), 'Initial Request Response'); // check the status if ($response->getStatus() != 401) { return $response; } // get the authenticate header (we might also want to try for proxy-authenticate) $resHeader = $response->getHeader('www-authenticate'); // check that authentication digest has been requested if (!preg_match('/^Digest/i', $resHeader)) { throw new Zend_Exception('server does not require digest authentication'); } // take the response header params and create a request header $resParams = $this->splitHeader($resHeader); $reqParams = $this->calculateParams($resParams, $response); $reqHeader = $this->joinHeader($reqParams); Zend_Debug::dump($reqHeader, 'Authorization Request Header'); // set header and re-request $this->client->setHeaders('Authorization', $reqHeader); $response = $this->client->request(); Zend_Debug::dump($response->getHeaders(), 'Authorization Request Response'); return $response; } private function calculateParams($params, $response) { // generate a random client nonce value $cnonce = md5(microtime(true)); // check we have the minumum requirements if (!isset($params['realm'])) { throw new Zend_Exception('authentication realm parameter missing'); } if (!isset($params['nonce'])) { throw new Zend_Exception('authentication nonce parameter missing'); } // check if we are retrying the nonce value if (isset($this->nonce)) { if ($this->nonce == $params['nonce']) { $this->nc++; } else { $this->nonce = $params['nonce']; $this->nc = 1; } } else { $this->nonce = $params['nonce']; } // convert decimal nc to hex $nc = dechex($this->nc); // set required values $result = array( 'username' => '"' . $this->username . '"', 'realm' => '"' . $params['realm'] . '"', 'nonce' => '"' . $params['nonce'] . '"', 'uri' => '"' . $this->client->getUri()->getPath() . '"', ); // check for a qop value if (isset($params['qop'])) { $qops = preg_split('/;\s+/', $params['qop']); if (in_array('auth', $qops)) { $qop = 'auth'; } /* // todo elseif (in_array('auth-int', $qops)) { $qop = 'auth-int'; } */ } // check for an algorithm value if (isset($params['algorithm'])) { if ($params['algorithm'] == 'MD5' || $params['algorithm'] == 'MD5-sess') { $algorithm = $params['algorithm']; } } // if qop is specified add parameters to result if(isset($qop)) { $result['qop'] = $qop; $result['nc'] = $nc; $result['cnonce'] = '"' . $cnonce . '"'; } // if an algorithm is specified add parameter to result if(isset($algorithm)) { $result['algorithm'] = $algorithm; } // generate the A1 string based on the algorithm value if (!isset($algorithm) || $algorithm == 'MD5') { $a1 = $this->username . ':' . $params['realm'] . ':' . $this->password; $this->a1 = null; } elseif ($algorithm == 'MD5-sess') { if (isset($this->a1)) { $a1 = $this->a1; } else { $a1 = $this->h($this->username . ':' . $params['realm'] . ':' . $this->password) . ':' . $params['nonce'] . ':' . $cnonce; $this->a1 = $a1; } } // generate secret value $secret = $this->h($a1); // generate the A2 string based on the qop value if (!isset($qop) || $qop == 'auth') { $a2 = $this->method . ':' . $this->client->getUri()->getPath(); } /* //todo elseif($qop == 'auth-int') { $a2 = $this->method . ':' . $uri . ':' . *entity-body*; } */ // generate the response hash based on the qop value if(!isset($qop)) { $response = $this->kd($secret, $params['nonce'] . ':' . $a2); } elseif($qop == 'auth' || $qop == 'auth-int') { $response = $this->kd($secret, $params['nonce'] . ':' . $nc . ':' . $cnonce . ':' . $qop . ':' . $a2); } // add response value to result $result['response'] = '"' . $response . '"'; // check if an opaque value was sent if (isset($params['opaque'])) { $result['opaque'] = '"' . $params['opaque'] . '"'; } return $result; } private function h($data) { return md5($data); } private function kd($secret, $data) { return $this->h($secret . ':' . $data); } private function splitHeader($header) { $params = array(); // remove the leading Digest string $header = preg_replace('/^Digest\s+(.*)$/i', '$1', $header); // match all individual parts of the header preg_match_all('/([^=]+)=("[^"]+"|[^,]+)(?:,\s*|$)/', $header, $matches); // loop through matches remove quotes and add to array foreach ($matches[1] as $key => $name) { $params[$name] = trim($matches[2][$key], '"'); } return $params; } private function joinHeader($params) { $header = 'Digest '; // loop through params and add to header foreach ($params as $name => $value) { $header .= $name . '=' . $value . ', '; } // trim trailing comma-space $header = rtrim($header, ', '); return $header; } } As is probably obvious, the __construct() and request() functions in the class above are only there for the purposes of the test script, and don't play any part in the actual calculation of the digest response.
Assigning to
Modified description to include a proposal for changing the authentication behaviour of Zend_Http_Client. Also, in the code above, the only functions actually relevant to receiving and responding to a digest authentication challenge are the calculateParams(), h() and kd() functions. The rest are merely there to allow the script to function, and some (splitHeader()) already exist in one form or another in the Zend_Http_Client (from what I remember).
Can this issue be closed in relation with
|
||||||||||||||||||||||||||||||||||||||||||||||||