PageRenderTime 53ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/zendframework/zendframework/tests/ZendTest/Authentication/Adapter/Http/AuthTest.php

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