PageRenderTime 58ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/DevApp/library/ServerLibraries/ZendFramework/trunk/tests/Zend/Auth/Adapter/Http/AuthTest.php

http://firephp.googlecode.com/
PHP | 494 lines | 244 code | 82 blank | 168 comment | 1 complexity | 5a95532b8fba9f18efa4bb5729ce520b MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, MIT, Apache-2.0
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Auth
  17. * @subpackage UnitTests
  18. * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id: AuthTest.php 24593 2012-01-05 20:35:02Z matthew $
  21. */
  22. /**
  23. * PHPUnit_Framework_TestCase
  24. */
  25. /**
  26. * @see Zend_Auth_Adapter_Http
  27. */
  28. require_once 'Zend/Auth/Adapter/Http.php';
  29. /**
  30. * @see Zend_Auth_Adapter_Http_Resolver_File
  31. */
  32. require_once 'Zend/Auth/Adapter/Http/Resolver/File.php';
  33. /**
  34. * @see Zend_Controller_Request_Http
  35. */
  36. require_once 'Zend/Controller/Request/Http.php';
  37. /**
  38. * @see Zend_Controller_Response_Http
  39. */
  40. require_once 'Zend/Controller/Response/Http.php';
  41. /**
  42. * @category Zend
  43. * @package Zend_Auth
  44. * @subpackage UnitTests
  45. * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  46. * @license http://framework.zend.com/license/new-bsd New BSD License
  47. * @group Zend_Auth
  48. */
  49. class Zend_Auth_Adapter_Http_AuthTest extends PHPUnit_Framework_TestCase
  50. {
  51. /**
  52. * Path to test files
  53. *
  54. * @var string
  55. */
  56. protected $_filesPath;
  57. /**
  58. * HTTP Basic configuration
  59. *
  60. * @var array
  61. */
  62. protected $_basicConfig;
  63. /**
  64. * HTTP Digest configuration
  65. *
  66. * @var array
  67. */
  68. protected $_digestConfig;
  69. /**
  70. * HTTP Basic Digest configuration
  71. *
  72. * @var array
  73. */
  74. protected $_bothConfig;
  75. /**
  76. * File resolver setup against with HTTP Basic auth file
  77. *
  78. * @var Zend_Auth_Adapter_Http_Resolver_File
  79. */
  80. protected $_basicResolver;
  81. /**
  82. * File resolver setup against with HTTP Digest auth file
  83. *
  84. * @var Zend_Auth_Adapter_Http_Resolver_File
  85. */
  86. protected $_digestResolver;
  87. /**
  88. * Set up test configuration
  89. *
  90. * @return void
  91. */
  92. public function __construct()
  93. {
  94. $this->_filesPath = dirname(__FILE__) . '/_files';
  95. $this->_basicResolver = new Zend_Auth_Adapter_Http_Resolver_File("{$this->_filesPath}/htbasic.1");
  96. $this->_digestResolver = new Zend_Auth_Adapter_Http_Resolver_File("{$this->_filesPath}/htdigest.3");
  97. $this->_basicConfig = array(
  98. 'accept_schemes' => 'basic',
  99. 'realm' => 'Test Realm'
  100. );
  101. $this->_digestConfig = array(
  102. 'accept_schemes' => 'digest',
  103. 'realm' => 'Test Realm',
  104. 'digest_domains' => '/ http://localhost/',
  105. 'nonce_timeout' => 300
  106. );
  107. $this->_bothConfig = array(
  108. 'accept_schemes' => 'basic digest',
  109. 'realm' => 'Test Realm',
  110. 'digest_domains' => '/ http://localhost/',
  111. 'nonce_timeout' => 300
  112. );
  113. }
  114. public function testBasicChallenge()
  115. {
  116. // Trying to authenticate without sending an Authorization header
  117. // should result in a 401 reply with a Www-Authenticate header, and a
  118. // false result.
  119. // The expected Basic Www-Authenticate header value
  120. $basic = 'Basic realm="' . $this->_bothConfig['realm'] . '"';
  121. $data = $this->_doAuth('', 'basic');
  122. $this->_checkUnauthorized($data, $basic);
  123. }
  124. public function testDigestChallenge()
  125. {
  126. // Trying to authenticate without sending an Authorization header
  127. // should result in a 401 reply with a Www-Authenticate header, and a
  128. // false result.
  129. // The expected Digest Www-Authenticate header value
  130. $digest = $this->_digestChallenge();
  131. $data = $this->_doAuth('', 'digest');
  132. $this->_checkUnauthorized($data, $digest);
  133. }
  134. public function testBothChallenges()
  135. {
  136. // Trying to authenticate without sending an Authorization header
  137. // should result in a 401 reply with at least one Www-Authenticate
  138. // header, and a false result.
  139. $data = $this->_doAuth('', 'both');
  140. extract($data); // $result, $status, $headers
  141. // The expected Www-Authenticate header values
  142. $basic = 'Basic realm="' . $this->_bothConfig['realm'] . '"';
  143. $digest = $this->_digestChallenge();
  144. // Make sure the result is false
  145. $this->assertType('Zend_Auth_Result', $result);
  146. $this->assertFalse($result->isValid());
  147. // Verify the status code and the presence of both challenges
  148. $this->assertEquals(401, $status);
  149. $this->assertEquals('Www-Authenticate', $headers[0]['name']);
  150. $this->assertEquals('Www-Authenticate', $headers[1]['name']);
  151. // Check to see if the expected challenges match the actual
  152. $this->assertEquals($basic, $headers[0]['value']);
  153. $this->assertEquals($digest, $headers[1]['value']);
  154. }
  155. public function testBasicAuthValidCreds()
  156. {
  157. // Attempt Basic Authentication with a valid username and password
  158. $data = $this->_doAuth('Basic ' . base64_encode('Bryce:ThisIsNotMyPassword'), 'basic');
  159. $this->_checkOK($data);
  160. }
  161. public function testBasicAuthBadCreds()
  162. {
  163. // Ensure that credentials containing invalid characters are treated as
  164. // a bad username or password.
  165. // The expected Basic Www-Authenticate header value
  166. $basic = 'Basic realm="' . $this->_basicConfig['realm'] . '"';
  167. $data = $this->_doAuth('Basic ' . base64_encode("Bad\tChars:In:Creds"), 'basic');
  168. $this->_checkUnauthorized($data, $basic);
  169. }
  170. public function testBasicAuthBadUser()
  171. {
  172. // Attempt Basic Authentication with a nonexistant username and
  173. // password
  174. // The expected Basic Www-Authenticate header value
  175. $basic = 'Basic realm="' . $this->_basicConfig['realm'] . '"';
  176. $data = $this->_doAuth('Basic ' . base64_encode('Nobody:NotValid'), 'basic');
  177. $this->_checkUnauthorized($data, $basic);
  178. }
  179. public function testBasicAuthBadPassword()
  180. {
  181. // Attempt Basic Authentication with a valid username, but invalid
  182. // password
  183. // The expected Basic Www-Authenticate header value
  184. $basic = 'Basic realm="' . $this->_basicConfig['realm'] . '"';
  185. $data = $this->_doAuth('Basic ' . base64_encode('Bryce:Invalid'), 'basic');
  186. $this->_checkUnauthorized($data, $basic);
  187. }
  188. public function testDigestAuthValidCreds()
  189. {
  190. // Attempt Digest Authentication with a valid username and password
  191. $data = $this->_doAuth($this->_digestReply('Bryce', 'ThisIsNotMyPassword'), 'digest');
  192. $this->_checkOK($data);
  193. }
  194. public function testDigestAuthDefaultAlgo()
  195. {
  196. // If the client omits the aglorithm argument, it should default to MD5,
  197. // and work just as above
  198. $cauth = $this->_digestReply('Bryce', 'ThisIsNotMyPassword');
  199. $cauth = preg_replace('/algorithm="MD5", /', '', $cauth);
  200. $data = $this->_doAuth($cauth, 'digest');
  201. $this->_checkOK($data);
  202. }
  203. public function testDigestAuthQuotedNC()
  204. {
  205. // The nonce count isn't supposed to be quoted, but apparently some
  206. // clients do anyway.
  207. $cauth = $this->_digestReply('Bryce', 'ThisIsNotMyPassword');
  208. $cauth = preg_replace('/nc=00000001/', 'nc="00000001"', $cauth);
  209. $data = $this->_doAuth($cauth, 'digest');
  210. $this->_checkOK($data);
  211. }
  212. public function testDigestAuthBadCreds()
  213. {
  214. // Attempt Digest Authentication with a bad username and password
  215. // The expected Digest Www-Authenticate header value
  216. $digest = $this->_digestChallenge();
  217. $data = $this->_doAuth($this->_digestReply('Nobody', 'NotValid'), 'digest');
  218. $this->_checkUnauthorized($data, $digest);
  219. }
  220. public function testDigestAuthBadCreds2()
  221. {
  222. // Formerly, a username with invalid characters would result in a 400
  223. // response, but now should result in 401 response.
  224. // The expected Digest Www-Authenticate header value
  225. $digest = $this->_digestChallenge();
  226. $data = $this->_doAuth($this->_digestReply('Bad:chars', 'NotValid'), 'digest');
  227. $this->_checkUnauthorized($data, $digest);
  228. }
  229. public function testDigestTampered()
  230. {
  231. // Create the tampered header value
  232. $tampered = $this->_digestReply('Bryce', 'ThisIsNotMyPassword');
  233. $tampered = preg_replace(
  234. '/ nonce="[a-fA-F0-9]{32}", /',
  235. ' nonce="'.str_repeat('0', 32).'", ',
  236. $tampered
  237. );
  238. // The expected Digest Www-Authenticate header value
  239. $digest = $this->_digestChallenge();
  240. $data = $this->_doAuth($tampered, 'digest');
  241. $this->_checkUnauthorized($data, $digest);
  242. }
  243. public function testBadSchemeRequest()
  244. {
  245. // Sending a request for an invalid authentication scheme should result
  246. // in a 400 Bad Request response.
  247. $data = $this->_doAuth('Invalid ' . base64_encode('Nobody:NotValid'), 'basic');
  248. $this->_checkBadRequest($data);
  249. }
  250. public function testBadDigestRequest()
  251. {
  252. // If any of the individual parts of the Digest Authorization header
  253. // are bad, it results in a 400 Bad Request. But that's a lot of
  254. // possibilities, so we're just going to pick one for now.
  255. $bad = $this->_digestReply('Bryce', 'ThisIsNotMyPassword');
  256. $bad = preg_replace(
  257. '/realm="([^"]+)"/', // cut out the realm
  258. '', $bad
  259. );
  260. $data = $this->_doAuth($bad, 'digest');
  261. $this->_checkBadRequest($data);
  262. }
  263. /**
  264. * Acts like a client sending the given Authenticate header value.
  265. *
  266. * @param string $clientHeader Authenticate header value
  267. * @param string $scheme Which authentication scheme to use
  268. * @return array Containing the result, response headers, and the status
  269. */
  270. protected function _doAuth($clientHeader, $scheme)
  271. {
  272. // Set up stub request and response objects
  273. $request = $this->getMock('Zend_Controller_Request_Http');
  274. $response = new Zend_Controller_Response_Http;
  275. $response->setHttpResponseCode(200);
  276. $response->headersSentThrowsException = false;
  277. // Set stub method return values
  278. $request->expects($this->any())
  279. ->method('getRequestUri')
  280. ->will($this->returnValue('/'));
  281. $request->expects($this->any())
  282. ->method('getMethod')
  283. ->will($this->returnValue('GET'));
  284. $request->expects($this->any())
  285. ->method('getServer')
  286. ->will($this->returnValue('PHPUnit'));
  287. $request->expects($this->any())
  288. ->method('getHeader')
  289. ->will($this->returnValue($clientHeader));
  290. // Select an Authentication scheme
  291. switch ($scheme) {
  292. case 'basic':
  293. $use = $this->_basicConfig;
  294. break;
  295. case 'digest':
  296. $use = $this->_digestConfig;
  297. break;
  298. case 'both':
  299. default:
  300. $use = $this->_bothConfig;
  301. }
  302. // Create the HTTP Auth adapter
  303. $a = new Zend_Auth_Adapter_Http($use);
  304. $a->setBasicResolver($this->_basicResolver);
  305. $a->setDigestResolver($this->_digestResolver);
  306. // Send the authentication request
  307. $a->setRequest($request);
  308. $a->setResponse($response);
  309. $result = $a->authenticate();
  310. $return = array(
  311. 'result' => $result,
  312. 'status' => $response->getHttpResponseCode(),
  313. 'headers' => $response->getHeaders()
  314. );
  315. return $return;
  316. }
  317. /**
  318. * Constructs a local version of the digest challenge we expect to receive
  319. *
  320. * @return string
  321. */
  322. protected function _digestChallenge()
  323. {
  324. $timeout = ceil(time() / 300) * 300;
  325. $nonce = md5($timeout . ':PHPUnit:Zend_Auth_Adapter_Http');
  326. $opaque = md5('Opaque Data:Zend_Auth_Adapter_Http');
  327. $wwwauth = 'Digest '
  328. . 'realm="' . $this->_digestConfig['realm'] . '", '
  329. . 'domain="' . $this->_digestConfig['digest_domains'] . '", '
  330. . 'nonce="' . $nonce . '", '
  331. . 'opaque="' . $opaque . '", '
  332. . 'algorithm="MD5", '
  333. . 'qop="auth"';
  334. return $wwwauth;
  335. }
  336. /**
  337. * Constructs a client digest Authorization header
  338. *
  339. * @return string
  340. */
  341. protected function _digestReply($user, $pass)
  342. {
  343. $nc = '00000001';
  344. $timeout = ceil(time() / 300) * 300;
  345. $nonce = md5($timeout . ':PHPUnit:Zend_Auth_Adapter_Http');
  346. $opaque = md5('Opaque Data:Zend_Auth_Adapter_Http');
  347. $cnonce = md5('cnonce');
  348. $response = md5(md5($user . ':' . $this->_digestConfig['realm'] . ':' . $pass) . ":$nonce:$nc:$cnonce:auth:"
  349. . md5('GET:/'));
  350. $cauth = 'Digest '
  351. . 'username="Bryce", '
  352. . 'realm="' . $this->_digestConfig['realm'] . '", '
  353. . 'nonce="' . $nonce . '", '
  354. . 'uri="/", '
  355. . 'response="' . $response . '", '
  356. . 'algorithm="MD5", '
  357. . 'cnonce="' . $cnonce . '", '
  358. . 'opaque="' . $opaque . '", '
  359. . 'qop="auth", '
  360. . 'nc=' . $nc;
  361. return $cauth;
  362. }
  363. /**
  364. * Checks for an expected 401 Unauthorized response
  365. *
  366. * @param array $data Authentication results
  367. * @param string $expected Expected Www-Authenticate header value
  368. * @return void
  369. */
  370. protected function _checkUnauthorized($data, $expected)
  371. {
  372. extract($data); // $result, $status, $headers
  373. // Make sure the result is false
  374. $this->assertType('Zend_Auth_Result', $result);
  375. $this->assertFalse($result->isValid());
  376. // Verify the status code and the presence of the challenge
  377. $this->assertEquals(401, $status);
  378. $this->assertEquals('Www-Authenticate', $headers[0]['name']);
  379. // Check to see if the expected challenge matches the actual
  380. $this->assertEquals($expected, $headers[0]['value']);
  381. }
  382. /**
  383. * Checks for an expected 200 OK response
  384. *
  385. * @param array $data Authentication results
  386. * @return void
  387. */
  388. protected function _checkOK($data)
  389. {
  390. extract($data); // $result, $status, $headers
  391. // Make sure the result is true
  392. $this->assertType('Zend_Auth_Result', $result);
  393. $this->assertTrue($result->isValid());
  394. // Verify we got a 200 response
  395. $this->assertEquals(200, $status);
  396. }
  397. /**
  398. * Checks for an expected 400 Bad Request response
  399. *
  400. * @param array $data Authentication results
  401. * @return void
  402. */
  403. protected function _checkBadRequest($data)
  404. {
  405. extract($data); // $result, $status, $headers
  406. // Make sure the result is false
  407. $this->assertType('Zend_Auth_Result', $result);
  408. $this->assertFalse($result->isValid());
  409. // Make sure it set the right HTTP code
  410. $this->assertEquals(400, $status);
  411. }
  412. }