CookieTest.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <?php
  2. /**
  3. * Tests the cookie class
  4. *
  5. * @group kohana
  6. * @group kohana.core
  7. * @group kohana.core.cookie
  8. *
  9. * @package Kohana
  10. * @category Tests
  11. * @author Kohana Team
  12. * @author Jeremy Bush <contractfrombelow@gmail.com>
  13. * @author Andrew Coulton <andrew@ingenerator.com>
  14. * @copyright (c) Kohana Team
  15. * @license https://koseven.ga/LICENSE.md
  16. */
  17. class Kohana_CookieTest extends Unittest_TestCase
  18. {
  19. const UNIX_TIMESTAMP = 1411040141;
  20. const COOKIE_EXPIRATION = 60;
  21. /**
  22. * Sets up the environment
  23. */
  24. // @codingStandardsIgnoreStart
  25. public function setUp(): void
  26. // @codingStandardsIgnoreEnd
  27. {
  28. parent::setUp();
  29. Kohana_CookieTest_TestableCookie::$_mock_cookies_set = [];
  30. $this->setEnvironment([
  31. 'Cookie::$salt' => 'some-random-salt',
  32. 'HTTP_USER_AGENT' => 'cli'
  33. ]);
  34. }
  35. /**
  36. * Tests that cookies are set with the global path, domain, etc options.
  37. *
  38. * @covers Cookie::set
  39. */
  40. public function test_set_creates_cookie_with_configured_cookie_options()
  41. {
  42. $this->setEnvironment([
  43. 'Cookie::$path' => '/path',
  44. 'Cookie::$domain' => 'my.domain',
  45. 'Cookie::$secure' => TRUE,
  46. 'Cookie::$httponly' => FALSE,
  47. ]);
  48. Kohana_CookieTest_TestableCookie::set('cookie', 'value');
  49. $this->assertSetCookieWith([
  50. 'path' => '/path',
  51. 'domain' => 'my.domain',
  52. 'secure' => TRUE,
  53. 'httponly' => FALSE
  54. ]);
  55. }
  56. /**
  57. * Provider for test_set_calculates_expiry_from_lifetime
  58. *
  59. * @return array of $lifetime, $expect_expiry
  60. */
  61. public function provider_set_calculates_expiry_from_lifetime()
  62. {
  63. return [
  64. [NULL, self::COOKIE_EXPIRATION + self::UNIX_TIMESTAMP],
  65. [0, 0],
  66. [10, 10 + self::UNIX_TIMESTAMP],
  67. ];
  68. }
  69. /**
  70. * @param int $expiration
  71. * @param int $expect_expiry
  72. *
  73. * @dataProvider provider_set_calculates_expiry_from_lifetime
  74. * @covers Cookie::set
  75. */
  76. public function test_set_calculates_expiry_from_lifetime($expiration, $expect_expiry)
  77. {
  78. $this->setEnvironment(['Cookie::$expiration' => self::COOKIE_EXPIRATION]);
  79. Kohana_CookieTest_TestableCookie::set('foo', 'bar', $expiration);
  80. $this->assertSetCookieWith(['expire' => $expect_expiry]);
  81. }
  82. /**
  83. * @covers Cookie::get
  84. */
  85. public function test_get_returns_default_if_cookie_missing()
  86. {
  87. unset($_COOKIE['missing_cookie']);
  88. $this->assertEquals('default', Cookie::get('missing_cookie', 'default'));
  89. }
  90. /**
  91. * @covers Cookie::get
  92. */
  93. public function test_get_returns_value_if_cookie_present_and_signed()
  94. {
  95. Kohana_CookieTest_TestableCookie::set('cookie', 'value');
  96. $cookie = Kohana_CookieTest_TestableCookie::$_mock_cookies_set[0];
  97. $_COOKIE[$cookie['name']] = $cookie['value'];
  98. $this->assertEquals('value', Cookie::get('cookie', 'default'));
  99. }
  100. /**
  101. * Provider for test_get_returns_default_without_deleting_if_cookie_unsigned
  102. *
  103. * @return array
  104. */
  105. public function provider_get_returns_default_without_deleting_if_cookie_unsigned()
  106. {
  107. return [
  108. ['unsalted'],
  109. ['un~salted'],
  110. ];
  111. }
  112. /**
  113. * Verifies that unsigned cookies are not available to the kohana application, but are not affected for other
  114. * consumers.
  115. *
  116. * @param string $unsigned_value
  117. *
  118. * @dataProvider provider_get_returns_default_without_deleting_if_cookie_unsigned
  119. * @covers Cookie::get
  120. */
  121. public function test_get_returns_default_without_deleting_if_cookie_unsigned($unsigned_value)
  122. {
  123. $_COOKIE['cookie'] = $unsigned_value;
  124. $this->assertEquals('default', Kohana_CookieTest_TestableCookie::get('cookie', 'default'));
  125. $this->assertEquals($unsigned_value, $_COOKIE['cookie'], '$_COOKIE not affected');
  126. $this->assertEmpty(Kohana_CookieTest_TestableCookie::$_mock_cookies_set, 'No cookies set or changed');
  127. }
  128. /**
  129. * If a cookie looks like a signed cookie but the signature no longer matches, it should be deleted.
  130. *
  131. * @covers Cookie::get
  132. */
  133. public function test_get_returns_default_and_deletes_tampered_signed_cookie()
  134. {
  135. $_COOKIE['cookie'] = Cookie::salt('cookie', 'value').'~tampered';
  136. $this->assertEquals('default', Kohana_CookieTest_TestableCookie::get('cookie', 'default'));
  137. $this->assertDeletedCookie('cookie');
  138. }
  139. /**
  140. * @covers Cookie::delete
  141. */
  142. public function test_delete_removes_cookie_from_globals_and_expires_cookie()
  143. {
  144. $_COOKIE['cookie'] = Cookie::salt('cookie', 'value').'~tampered';
  145. $this->assertTrue(Kohana_CookieTest_TestableCookie::delete('cookie'));
  146. $this->assertDeletedCookie('cookie');
  147. }
  148. /**
  149. * @covers Cookie::delete
  150. * @link http://dev.kohanaframework.org/issues/3501
  151. * @link http://dev.kohanaframework.org/issues/3020
  152. */
  153. public function test_delete_does_not_require_configured_salt()
  154. {
  155. Cookie::$salt = NULL;
  156. $this->assertTrue(Kohana_CookieTest_TestableCookie::delete('cookie'));
  157. $this->assertDeletedCookie('cookie');
  158. }
  159. /**
  160. * @covers Cookie::salt
  161. * @expectedException Kohana_Exception
  162. */
  163. public function test_salt_throws_with_no_configured_salt()
  164. {
  165. Cookie::$salt = NULL;
  166. Cookie::salt('key', 'value');
  167. }
  168. /**
  169. * @covers Cookie::salt
  170. */
  171. public function test_salt_creates_same_hash_for_same_values_and_state()
  172. {
  173. $name = 'cookie';
  174. $value = 'value';
  175. $this->assertEquals(Cookie::salt($name, $value), Cookie::salt($name, $value));
  176. }
  177. /**
  178. * Provider for test_salt_creates_different_hash_for_different_data
  179. *
  180. * @return array
  181. */
  182. public function provider_salt_creates_different_hash_for_different_data()
  183. {
  184. return [
  185. [['name' => 'foo', 'value' => 'bar', 'salt' => 'our-salt', 'user-agent' => 'Chrome'], ['name' => 'changed']],
  186. [['name' => 'foo', 'value' => 'bar', 'salt' => 'our-salt', 'user-agent' => 'Chrome'], ['value' => 'changed']],
  187. [['name' => 'foo', 'value' => 'bar', 'salt' => 'our-salt', 'user-agent' => 'Chrome'], ['salt' => 'changed-salt']],
  188. [['name' => 'foo', 'value' => 'bar', 'salt' => 'our-salt', 'user-agent' => 'Chrome'], ['user-agent' => 'Firefox']],
  189. [['name' => 'foo', 'value' => 'bar', 'salt' => 'our-salt', 'user-agent' => 'Chrome'], ['user-agent' => NULL]],
  190. ];
  191. }
  192. /**
  193. * @param array $first_args
  194. * @param array $changed_args
  195. *
  196. * @dataProvider provider_salt_creates_different_hash_for_different_data
  197. * @covers Cookie::salt
  198. */
  199. public function test_salt_creates_different_hash_for_different_data($first_args, $changed_args)
  200. {
  201. $second_args = array_merge($first_args, $changed_args);
  202. $hashes = [];
  203. foreach ([$first_args, $second_args] as $args)
  204. {
  205. Cookie::$salt = $args['salt'];
  206. $this->set_or_remove_http_user_agent($args['user-agent']);
  207. $hashes[] = Cookie::salt($args['name'], $args['value']);
  208. }
  209. $this->assertNotEquals($hashes[0], $hashes[1]);
  210. }
  211. /**
  212. * Verify that a cookie was deleted from the global $_COOKIE array, and that a setcookie call was made to remove it
  213. * from the client.
  214. *
  215. * @param string $name
  216. */
  217. // @codingStandardsIgnoreStart
  218. protected function assertDeletedCookie($name)
  219. // @codingStandardsIgnoreEnd
  220. {
  221. $this->assertArrayNotHasKey($name, $_COOKIE);
  222. // To delete the client-side cookie, Cookie::delete should send a new cookie with value NULL and expiry in the past
  223. $this->assertSetCookieWith([
  224. 'name' => $name,
  225. 'value' => NULL,
  226. 'expire' => -86400,
  227. 'path' => Cookie::$path,
  228. 'domain' => Cookie::$domain,
  229. 'secure' => Cookie::$secure,
  230. 'httponly' => Cookie::$httponly
  231. ]);
  232. }
  233. /**
  234. * Verify that there was a single call to setcookie including the provided named arguments
  235. *
  236. * @param array $expected
  237. */
  238. // @codingStandardsIgnoreStart
  239. protected function assertSetCookieWith($expected)
  240. // @codingStandardsIgnoreEnd
  241. {
  242. $this->assertCount(1, Kohana_CookieTest_TestableCookie::$_mock_cookies_set);
  243. $relevant_values = array_intersect_key(Kohana_CookieTest_TestableCookie::$_mock_cookies_set[0], $expected);
  244. $this->assertEquals($expected, $relevant_values);
  245. }
  246. /**
  247. * Configure the $_SERVER[HTTP_USER_AGENT] environment variable for the test
  248. *
  249. * @param string $user_agent
  250. */
  251. protected function set_or_remove_http_user_agent($user_agent)
  252. {
  253. if ($user_agent === NULL)
  254. {
  255. unset($_SERVER['HTTP_USER_AGENT']);
  256. }
  257. else
  258. {
  259. $_SERVER['HTTP_USER_AGENT'] = $user_agent;
  260. }
  261. }
  262. }
  263. /**
  264. * Class Kohana_CookieTest_TestableCookie wraps the cookie class to mock out the actual setcookie and time calls for
  265. * unit testing.
  266. */
  267. class Kohana_CookieTest_TestableCookie extends Cookie {
  268. /**
  269. * @var array setcookie calls that were made
  270. */
  271. public static $_mock_cookies_set = [];
  272. /**
  273. * {@inheritdoc}
  274. */
  275. protected static function _setcookie($name, $value, $expire, $path, $domain, $secure, $httponly)
  276. {
  277. self::$_mock_cookies_set[] = [
  278. 'name' => $name,
  279. 'value' => $value,
  280. 'expire' => $expire,
  281. 'path' => $path,
  282. 'domain' => $domain,
  283. 'secure' => $secure,
  284. 'httponly' => $httponly
  285. ];
  286. return TRUE;
  287. }
  288. /**
  289. * @return int
  290. */
  291. protected static function _time()
  292. {
  293. return Kohana_CookieTest::UNIX_TIMESTAMP;
  294. }
  295. }