PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/pcelta/zf2
PHP | 470 lines | 264 code | 70 blank | 136 comment | 4 complexity | 0609ef46d2d4a37d296be51c0194533e 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 ProxyTest 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 Zend_Auth_Adapter_Http_Resolver_File
  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. * Sets 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. 'proxy_auth' => true
  73. );
  74. $this->_digestConfig = array(
  75. 'accept_schemes' => 'digest',
  76. 'realm' => 'Test Realm',
  77. 'digest_domains' => '/ http://localhost/',
  78. 'nonce_timeout' => 300,
  79. 'proxy_auth' => true
  80. );
  81. $this->_bothConfig = array(
  82. 'accept_schemes' => 'basic digest',
  83. 'realm' => 'Test Realm',
  84. 'digest_domains' => '/ http://localhost/',
  85. 'nonce_timeout' => 300,
  86. 'proxy_auth' => true
  87. );
  88. }
  89. public function testBasicChallenge()
  90. {
  91. // Trying to authenticate without sending an Proxy-Authorization header
  92. // should result in a 407 reply with a Proxy-Authenticate header, and a
  93. // false result.
  94. // The expected Basic Proxy-Authenticate header value
  95. $basic = array(
  96. 'type' => 'Basic ',
  97. 'realm' => 'realm="' . $this->_bothConfig['realm'] . '"',
  98. );
  99. $data = $this->_doAuth('', 'basic');
  100. $this->_checkUnauthorized($data, $basic);
  101. }
  102. public function testDigestChallenge()
  103. {
  104. // Trying to authenticate without sending an Proxy-Authorization header
  105. // should result in a 407 reply with a Proxy-Authenticate header, and a
  106. // false result.
  107. // The expected Digest Proxy-Authenticate header value
  108. $digest = $this->_digestChallenge();
  109. $data = $this->_doAuth('', 'digest');
  110. $this->_checkUnauthorized($data, $digest);
  111. }
  112. public function testBothChallenges()
  113. {
  114. // Trying to authenticate without sending an Proxy-Authorization header
  115. // should result in a 407 reply with at least one Proxy-Authenticate
  116. // header, and a false result.
  117. $data = $this->_doAuth('', 'both');
  118. extract($data); // $result, $status, $headers
  119. // The expected Proxy-Authenticate header values
  120. $basic = 'Basic realm="' . $this->_bothConfig['realm'] . '"';
  121. $digest = $this->_digestChallenge();
  122. // Make sure the result is false
  123. $this->assertInstanceOf('Zend\\Authentication\\Result', $result);
  124. $this->assertFalse($result->isValid());
  125. // Verify the status code and the presence of both challenges
  126. $this->assertEquals(407, $status);
  127. $this->assertTrue($headers->has('Proxy-Authenticate'));
  128. $authHeader = $headers->get('Proxy-Authenticate');
  129. $this->assertEquals(2, count($authHeader), var_export($authHeader, 1));
  130. // Check to see if the expected challenges match the actual
  131. $basicFound = $digestFound = false;
  132. foreach ($authHeader as $header) {
  133. $value = $header->getFieldValue();
  134. if (preg_match('/^Basic/', $value)) {
  135. $basicFound = true;
  136. }
  137. if (preg_match('/^Digest/', $value)) {
  138. $digestFound = true;
  139. }
  140. }
  141. $this->assertTrue($basicFound);
  142. $this->assertTrue($digestFound);
  143. }
  144. public function testBasicAuthValidCreds()
  145. {
  146. // Attempt Basic Authentication with a valid username and password
  147. $data = $this->_doAuth('Basic ' . base64_encode('Bryce:ThisIsNotMyPassword'), 'basic');
  148. $this->_checkOK($data);
  149. }
  150. public function testBasicAuthBadCreds()
  151. {
  152. // Ensure that credentials containing invalid characters are treated as
  153. // a bad username or password.
  154. // The expected Basic WWW-Authenticate header value
  155. $basic = array(
  156. 'type' => 'Basic ',
  157. 'realm' => 'realm="' . $this->_basicConfig['realm'] . '"',
  158. );
  159. $data = $this->_doAuth('Basic ' . base64_encode("Bad\tChars:In:Creds"), 'basic');
  160. $this->_checkUnauthorized($data, $basic);
  161. }
  162. public function testBasicAuthBadUser()
  163. {
  164. // Attempt Basic Authentication with a bad username and password
  165. // The expected Basic Proxy-Authenticate header value
  166. $basic = array(
  167. 'type' => 'Basic ',
  168. 'realm' => 'realm="' . $this->_basicConfig['realm'] . '"',
  169. );
  170. $data = $this->_doAuth('Basic ' . base64_encode('Nobody:NotValid'), 'basic');
  171. $this->_checkUnauthorized($data, $basic);
  172. }
  173. public function testBasicAuthBadPassword()
  174. {
  175. // Attempt Basic Authentication with a valid username, but invalid
  176. // password
  177. // The expected Basic WWW-Authenticate header value
  178. $basic = array(
  179. 'type' => 'Basic ',
  180. 'realm' => 'realm="' . $this->_basicConfig['realm'] . '"',
  181. );
  182. $data = $this->_doAuth('Basic ' . base64_encode('Bryce:Invalid'), 'basic');
  183. $this->_checkUnauthorized($data, $basic);
  184. }
  185. public function testDigestAuthValidCreds()
  186. {
  187. // Attempt Digest Authentication with a valid username and password
  188. $data = $this->_doAuth($this->_digestReply('Bryce', 'ThisIsNotMyPassword'), 'digest');
  189. $this->_checkOK($data);
  190. }
  191. public function testDigestAuthDefaultAlgo()
  192. {
  193. // If the client omits the aglorithm argument, it should default to MD5,
  194. // and work just as above
  195. $cauth = $this->_digestReply('Bryce', 'ThisIsNotMyPassword');
  196. $cauth = preg_replace('/algorithm="MD5", /', '', $cauth);
  197. $data = $this->_doAuth($cauth, 'digest');
  198. $this->_checkOK($data);
  199. }
  200. public function testDigestAuthQuotedNC()
  201. {
  202. // The nonce count isn't supposed to be quoted, but apparently some
  203. // clients do anyway.
  204. $cauth = $this->_digestReply('Bryce', 'ThisIsNotMyPassword');
  205. $cauth = preg_replace('/nc=00000001/', 'nc="00000001"', $cauth);
  206. $data = $this->_doAuth($cauth, 'digest');
  207. $this->_checkOK($data);
  208. }
  209. public function testDigestAuthBadCreds()
  210. {
  211. // Attempt Digest Authentication with a bad username and password
  212. // The expected Digest Proxy-Authenticate header value
  213. $digest = $this->_digestChallenge();
  214. $data = $this->_doAuth($this->_digestReply('Nobody', 'NotValid'), 'digest');
  215. $this->_checkUnauthorized($data, $digest);
  216. }
  217. public function testDigestTampered()
  218. {
  219. // Create the tampered header value
  220. $tampered = $this->_digestReply('Bryce', 'ThisIsNotMyPassword');
  221. $tampered = preg_replace(
  222. '/ nonce="[a-fA-F0-9]{32}", /',
  223. ' nonce="' . str_repeat('0', 32).'", ',
  224. $tampered
  225. );
  226. // The expected Digest Proxy-Authenticate header value
  227. $digest = $this->_digestChallenge();
  228. $data = $this->_doAuth($tampered, 'digest');
  229. $this->_checkUnauthorized($data, $digest);
  230. }
  231. public function testBadSchemeRequest()
  232. {
  233. // Sending a request for an invalid authentication scheme should result
  234. // in a 400 Bad Request response.
  235. $data = $this->_doAuth('Invalid ' . base64_encode('Nobody:NotValid'), 'basic');
  236. $this->_checkBadRequest($data);
  237. }
  238. public function testBadDigestRequest()
  239. {
  240. // If any of the individual parts of the Digest Proxy-Authorization header
  241. // are bad, it results in a 400 Bad Request. But that's a lot of
  242. // possibilities, so we're just going to pick one for now.
  243. $bad = $this->_digestReply('Bryce', 'ThisIsNotMyPassword');
  244. $bad = preg_replace(
  245. '/realm="([^"]+)"/', // cut out the realm
  246. '', $bad
  247. );
  248. $data = $this->_doAuth($bad, 'digest');
  249. $this->_checkBadRequest($data);
  250. }
  251. /**
  252. * Acts like a client sending the given Authenticate header value.
  253. *
  254. * @param string $clientHeader Authenticate header value
  255. * @param string $scheme Which authentication scheme to use
  256. * @return array Containing the result, the response headers, and the status
  257. */
  258. public function _doAuth($clientHeader, $scheme)
  259. {
  260. // Set up stub request and response objects
  261. $response = new Response;
  262. $response->setStatusCode(200);
  263. $headers = new Headers();
  264. $headers->addHeaderLine('Proxy-Authorization', $clientHeader);
  265. $headers->addHeaderLine('User-Agent', 'PHPUnit');
  266. $request = new Request();
  267. $request->setUri('http://localhost/');
  268. $request->setMethod('GET');
  269. $request->setHeaders($headers);
  270. // Select an Authentication scheme
  271. switch ($scheme) {
  272. case 'basic':
  273. $use = $this->_basicConfig;
  274. break;
  275. case 'digest':
  276. $use = $this->_digestConfig;
  277. break;
  278. case 'both':
  279. default:
  280. $use = $this->_bothConfig;
  281. }
  282. // Create the HTTP Auth adapter
  283. $a = new \Zend\Authentication\Adapter\Http($use);
  284. $a->setBasicResolver($this->_basicResolver);
  285. $a->setDigestResolver($this->_digestResolver);
  286. // Send the authentication request
  287. $a->setRequest($request);
  288. $a->setResponse($response);
  289. $result = $a->authenticate();
  290. $return = array(
  291. 'result' => $result,
  292. 'status' => $response->getStatusCode(),
  293. 'headers' => $response->getHeaders(),
  294. );
  295. return $return;
  296. }
  297. /**
  298. * Constructs a local version of the digest challenge we expect to receive
  299. *
  300. * @return string
  301. */
  302. protected function _digestChallenge()
  303. {
  304. return array(
  305. 'type' => 'Digest ',
  306. 'realm' => 'realm="' . $this->_digestConfig['realm'] . '"',
  307. 'domain' => 'domain="' . $this->_bothConfig['digest_domains'] . '"',
  308. );
  309. }
  310. /**
  311. * Constructs a client digest Proxy-Authorization header
  312. *
  313. * @param string $user
  314. * @param string $pass
  315. * @return string
  316. */
  317. protected function _digestReply($user, $pass)
  318. {
  319. $nc = '00000001';
  320. $timeout = ceil(time() / 300) * 300;
  321. $nonce = md5($timeout . ':PHPUnit:Zend\\Authentication\\Adapter\\Http');
  322. $opaque = md5('Opaque Data:Zend\\Authentication\\Adapter\\Http');
  323. $cnonce = md5('cnonce');
  324. $response = md5(md5($user . ':' . $this->_digestConfig['realm'] . ':' . $pass) . ":$nonce:$nc:$cnonce:auth:"
  325. . md5('GET:/'));
  326. $cauth = 'Digest '
  327. . 'username="Bryce", '
  328. . 'realm="' . $this->_digestConfig['realm'] . '", '
  329. . 'nonce="' . $nonce . '", '
  330. . 'uri="/", '
  331. . 'response="' . $response . '", '
  332. . 'algorithm="MD5", '
  333. . 'cnonce="' . $cnonce . '", '
  334. . 'opaque="' . $opaque . '", '
  335. . 'qop="auth", '
  336. . 'nc=' . $nc;
  337. return $cauth;
  338. }
  339. /**
  340. * Checks for an expected 407 Proxy-Unauthorized response
  341. *
  342. * @param array $data Authentication results
  343. * @param string $expected Expected Proxy-Authenticate header value
  344. * @return void
  345. */
  346. protected function _checkUnauthorized($data, $expected)
  347. {
  348. extract($data); // $result, $status, $headers
  349. // Make sure the result is false
  350. $this->assertInstanceOf('Zend\\Authentication\\Result', $result);
  351. $this->assertFalse($result->isValid());
  352. // Verify the status code and the presence of the challenge
  353. $this->assertEquals(407, $status);
  354. $this->assertTrue($headers->has('Proxy-Authenticate'));
  355. // Check to see if the expected challenge matches the actual
  356. $headers = $headers->get('Proxy-Authenticate');
  357. $this->assertTrue($headers instanceof \ArrayIterator);
  358. $this->assertEquals(1, count($headers));
  359. $header = $headers[0]->getFieldValue();
  360. $this->assertContains($expected['type'], $header, $header);
  361. $this->assertContains($expected['realm'], $header, $header);
  362. if (isset($expected['domain'])) {
  363. $this->assertContains($expected['domain'], $header, $header);
  364. $this->assertContains('algorithm="MD5"', $header, $header);
  365. $this->assertContains('qop="auth"', $header, $header);
  366. $this->assertRegExp('/nonce="[a-fA-F0-9]{32}"/', $header, $header);
  367. $this->assertRegExp('/opaque="[a-fA-F0-9]{32}"/', $header, $header);
  368. }
  369. }
  370. /**
  371. * Checks for an expected 200 OK response
  372. *
  373. * @param array $data Authentication results
  374. * @return void
  375. */
  376. protected function _checkOK($data)
  377. {
  378. extract($data); // $result, $status, $headers
  379. // Make sure the result is true
  380. $this->assertInstanceOf('Zend\\Authentication\\Result', $result);
  381. $this->assertTrue($result->isValid(), var_export($result->getMessages(), 1));
  382. // Verify we got a 200 response
  383. $this->assertEquals(200, $status);
  384. }
  385. /**
  386. * Checks for an expected 400 Bad Request response
  387. *
  388. * @param array $data Authentication results
  389. * @return void
  390. */
  391. protected function _checkBadRequest($data)
  392. {
  393. extract($data); // $result, $status, $headers
  394. // Make sure the result is false
  395. $this->assertInstanceOf('Zend\\Authentication\\Result', $result);
  396. $this->assertFalse($result->isValid());
  397. // Make sure it set the right HTTP code
  398. $this->assertEquals(400, $status);
  399. }
  400. }