SecurityComponent.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. <?php
  2. /**
  3. * Security Component
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package cake
  16. * @subpackage cake.cake.libs.controller.components
  17. * @since CakePHP(tm) v 0.10.8.2156
  18. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  19. */
  20. App::uses('String', 'Core');
  21. App::uses('Security', 'Core');
  22. /**
  23. * SecurityComponent
  24. *
  25. * @package cake
  26. * @subpackage cake.cake.libs.controller.components
  27. * @link http://book.cakephp.org/view/1296/Security-Component
  28. */
  29. class SecurityComponent extends Component {
  30. /**
  31. * The controller method that will be called if this request is black-hole'd
  32. *
  33. * @var string
  34. * @access public
  35. */
  36. public $blackHoleCallback = null;
  37. /**
  38. * List of controller actions for which a POST request is required
  39. *
  40. * @var array
  41. * @access public
  42. * @see SecurityComponent::requirePost()
  43. */
  44. public $requirePost = array();
  45. /**
  46. * List of controller actions for which a GET request is required
  47. *
  48. * @var array
  49. * @access public
  50. * @see SecurityComponent::requireGet()
  51. */
  52. public $requireGet = array();
  53. /**
  54. * List of controller actions for which a PUT request is required
  55. *
  56. * @var array
  57. * @access public
  58. * @see SecurityComponent::requirePut()
  59. */
  60. public $requirePut = array();
  61. /**
  62. * List of controller actions for which a DELETE request is required
  63. *
  64. * @var array
  65. * @access public
  66. * @see SecurityComponent::requireDelete()
  67. */
  68. public $requireDelete = array();
  69. /**
  70. * List of actions that require an SSL-secured connection
  71. *
  72. * @var array
  73. * @access public
  74. * @see SecurityComponent::requireSecure()
  75. */
  76. public $requireSecure = array();
  77. /**
  78. * List of actions that require a valid authentication key
  79. *
  80. * @var array
  81. * @access public
  82. * @see SecurityComponent::requireAuth()
  83. */
  84. public $requireAuth = array();
  85. /**
  86. * List of actions that require an HTTP-authenticated login (basic or digest)
  87. *
  88. * @var array
  89. * @access public
  90. * @see SecurityComponent::requireLogin()
  91. */
  92. public $requireLogin = array();
  93. /**
  94. * Login options for SecurityComponent::requireLogin()
  95. *
  96. * @var array
  97. * @access public
  98. * @see SecurityComponent::requireLogin()
  99. */
  100. public $loginOptions = array('type' => '', 'prompt' => null);
  101. /**
  102. * An associative array of usernames/passwords used for HTTP-authenticated logins.
  103. *
  104. * @var array
  105. * @access public
  106. * @see SecurityComponent::requireLogin()
  107. */
  108. public $loginUsers = array();
  109. /**
  110. * Controllers from which actions of the current controller are allowed to receive
  111. * requests.
  112. *
  113. * @var array
  114. * @access public
  115. * @see SecurityComponent::requireAuth()
  116. */
  117. public $allowedControllers = array();
  118. /**
  119. * Actions from which actions of the current controller are allowed to receive
  120. * requests.
  121. *
  122. * @var array
  123. * @access public
  124. * @see SecurityComponent::requireAuth()
  125. */
  126. public $allowedActions = array();
  127. /**
  128. * Form fields to disable
  129. *
  130. * @var array
  131. * @access public
  132. */
  133. public $disabledFields = array();
  134. /**
  135. * Whether to validate POST data. Set to false to disable for data coming from 3rd party
  136. * services, etc.
  137. *
  138. * @var boolean
  139. * @access public
  140. */
  141. public $validatePost = true;
  142. /**
  143. * Whether to use CSRF protected forms. Set to false to disable CSRF protection on forms.
  144. *
  145. * @var boolean
  146. * @see http://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
  147. * @see SecurityComponent::$csrfExpires
  148. */
  149. public $csrfCheck = true;
  150. /**
  151. * The duration from when a CSRF token is created that it will expire on.
  152. * Each form/page request will generate a new token that can only be submitted once unless
  153. * it expires. Can be any value compatible with strtotime()
  154. *
  155. * @var string
  156. */
  157. public $csrfExpires = '+30 minutes';
  158. /**
  159. * Controls whether or not CSRF tokens are use and burn. Set to false to not generate
  160. * new tokens on each request. One token will be reused until it expires. This reduces
  161. * the chances of users getting invalid requests because of token consumption.
  162. * It has the side effect of making CSRF less secure, as tokens are reusable.
  163. *
  164. * @var boolean
  165. */
  166. public $csrfUseOnce = true;
  167. /**
  168. * Other components used by the Security component
  169. *
  170. * @var array
  171. * @access public
  172. */
  173. public $components = array('Session');
  174. /**
  175. * Holds the current action of the controller
  176. *
  177. * @var string
  178. */
  179. protected $_action = null;
  180. /**
  181. * Request object
  182. *
  183. * @var CakeRequest
  184. */
  185. public $request;
  186. /**
  187. * Component startup. All security checking happens here.
  188. *
  189. * @param object $controller Instantiating controller
  190. * @return void
  191. */
  192. public function startup(&$controller) {
  193. $this->request = $controller->request;
  194. $this->_action = strtolower($this->request->params['action']);
  195. $this->_methodsRequired($controller);
  196. $this->_secureRequired($controller);
  197. $this->_authRequired($controller);
  198. $this->_loginRequired($controller);
  199. $isPost = ($this->request->is('post') || $this->request->is('put'));
  200. $isRequestAction = (
  201. !isset($controller->request->params['requested']) ||
  202. $controller->request->params['requested'] != 1
  203. );
  204. if ($isPost && $isRequestAction && $this->validatePost) {
  205. if ($this->_validatePost($controller) === false) {
  206. return $this->blackHole($controller, 'auth');
  207. }
  208. }
  209. if ($isPost && $this->csrfCheck) {
  210. if ($this->_validateCsrf($controller) === false) {
  211. return $this->blackHole($controller, 'csrf');
  212. }
  213. }
  214. $this->_generateToken($controller);
  215. }
  216. /**
  217. * Sets the actions that require a POST request, or empty for all actions
  218. *
  219. * @return void
  220. * @link http://book.cakephp.org/view/1299/requirePost
  221. */
  222. public function requirePost() {
  223. $args = func_get_args();
  224. $this->_requireMethod('Post', $args);
  225. }
  226. /**
  227. * Sets the actions that require a GET request, or empty for all actions
  228. *
  229. * @return void
  230. */
  231. public function requireGet() {
  232. $args = func_get_args();
  233. $this->_requireMethod('Get', $args);
  234. }
  235. /**
  236. * Sets the actions that require a PUT request, or empty for all actions
  237. *
  238. * @return void
  239. */
  240. public function requirePut() {
  241. $args = func_get_args();
  242. $this->_requireMethod('Put', $args);
  243. }
  244. /**
  245. * Sets the actions that require a DELETE request, or empty for all actions
  246. *
  247. * @return void
  248. */
  249. public function requireDelete() {
  250. $args = func_get_args();
  251. $this->_requireMethod('Delete', $args);
  252. }
  253. /**
  254. * Sets the actions that require a request that is SSL-secured, or empty for all actions
  255. *
  256. * @return void
  257. * @link http://book.cakephp.org/view/1300/requireSecure
  258. */
  259. public function requireSecure() {
  260. $args = func_get_args();
  261. $this->_requireMethod('Secure', $args);
  262. }
  263. /**
  264. * Sets the actions that require an authenticated request, or empty for all actions
  265. *
  266. * @return void
  267. * @link http://book.cakephp.org/view/1301/requireAuth
  268. */
  269. public function requireAuth() {
  270. $args = func_get_args();
  271. $this->_requireMethod('Auth', $args);
  272. }
  273. /**
  274. * Sets the actions that require an HTTP-authenticated request, or empty for all actions
  275. *
  276. * @return void
  277. * @link http://book.cakephp.org/view/1302/requireLogin
  278. */
  279. public function requireLogin() {
  280. $args = func_get_args();
  281. $base = $this->loginOptions;
  282. foreach ($args as $i => $arg) {
  283. if (is_array($arg)) {
  284. $this->loginOptions = $arg;
  285. unset($args[$i]);
  286. }
  287. }
  288. $this->loginOptions = array_merge($base, $this->loginOptions);
  289. $this->_requireMethod('Login', $args);
  290. if (isset($this->loginOptions['users'])) {
  291. $this->loginUsers =& $this->loginOptions['users'];
  292. }
  293. }
  294. /**
  295. * Attempts to validate the login credentials for an HTTP-authenticated request
  296. *
  297. * @param string $type Either 'basic', 'digest', or null. If null/empty, will try both.
  298. * @return mixed If successful, returns an array with login name and password, otherwise null.
  299. * @link http://book.cakephp.org/view/1303/loginCredentials-string-type
  300. */
  301. public function loginCredentials($type = null) {
  302. switch (strtolower($type)) {
  303. case 'basic':
  304. $login = array('username' => env('PHP_AUTH_USER'), 'password' => env('PHP_AUTH_PW'));
  305. if (!empty($login['username'])) {
  306. return $login;
  307. }
  308. break;
  309. case 'digest':
  310. default:
  311. $digest = null;
  312. if (version_compare(PHP_VERSION, '5.1') != -1) {
  313. $digest = env('PHP_AUTH_DIGEST');
  314. } elseif (function_exists('apache_request_headers')) {
  315. $headers = apache_request_headers();
  316. if (isset($headers['Authorization']) && !empty($headers['Authorization']) && substr($headers['Authorization'], 0, 7) == 'Digest ') {
  317. $digest = substr($headers['Authorization'], 7);
  318. }
  319. } else {
  320. // Server doesn't support digest-auth headers
  321. trigger_error(__('SecurityComponent::loginCredentials() - Server does not support digest authentication'), E_USER_WARNING);
  322. }
  323. if (!empty($digest)) {
  324. return $this->parseDigestAuthData($digest);
  325. }
  326. break;
  327. }
  328. return null;
  329. }
  330. /**
  331. * Generates the text of an HTTP-authentication request header from an array of options.
  332. *
  333. * @param array $options Set of options for header
  334. * @return string HTTP-authentication request header
  335. * @link http://book.cakephp.org/view/1304/loginRequest-array-options
  336. */
  337. public function loginRequest($options = array()) {
  338. $options = array_merge($this->loginOptions, $options);
  339. $this->_setLoginDefaults($options);
  340. $auth = 'WWW-Authenticate: ' . ucfirst($options['type']);
  341. $out = array('realm="' . $options['realm'] . '"');
  342. if (strtolower($options['type']) == 'digest') {
  343. $out[] = 'qop="auth"';
  344. $out[] = 'nonce="' . uniqid("") . '"';
  345. $out[] = 'opaque="' . md5($options['realm']) . '"';
  346. }
  347. return $auth . ' ' . implode(',', $out);
  348. }
  349. /**
  350. * Parses an HTTP digest authentication response, and returns an array of the data, or null on failure.
  351. *
  352. * @param string $digest Digest authentication response
  353. * @return array Digest authentication parameters
  354. * @link http://book.cakephp.org/view/1305/parseDigestAuthData-string-digest
  355. */
  356. public function parseDigestAuthData($digest) {
  357. if (substr($digest, 0, 7) == 'Digest ') {
  358. $digest = substr($digest, 7);
  359. }
  360. $keys = array();
  361. $match = array();
  362. $req = array('nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1);
  363. preg_match_all('/(\w+)=([\'"]?)([a-zA-Z0-9@=.\/_-]+)\2/', $digest, $match, PREG_SET_ORDER);
  364. foreach ($match as $i) {
  365. $keys[$i[1]] = $i[3];
  366. unset($req[$i[1]]);
  367. }
  368. if (empty($req)) {
  369. return $keys;
  370. }
  371. return null;
  372. }
  373. /**
  374. * Generates a hash to be compared with an HTTP digest-authenticated response
  375. *
  376. * @param array $data HTTP digest response data, as parsed by SecurityComponent::parseDigestAuthData()
  377. * @return string Digest authentication hash
  378. * @access public
  379. * @see SecurityComponent::parseDigestAuthData()
  380. * @link http://book.cakephp.org/view/1306/generateDigestResponseHash-array-data
  381. */
  382. function generateDigestResponseHash($data) {
  383. return md5(
  384. md5($data['username'] . ':' . $this->loginOptions['realm'] . ':' . $this->loginUsers[$data['username']]) .
  385. ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' .
  386. md5(env('REQUEST_METHOD') . ':' . $data['uri'])
  387. );
  388. }
  389. /**
  390. * Black-hole an invalid request with a 404 error or custom callback. If SecurityComponent::$blackHoleCallback
  391. * is specified, it will use this callback by executing the method indicated in $error
  392. *
  393. * @param object $controller Instantiating controller
  394. * @param string $error Error method
  395. * @return mixed If specified, controller blackHoleCallback's response, or no return otherwise
  396. * @access public
  397. * @see SecurityComponent::$blackHoleCallback
  398. * @link http://book.cakephp.org/view/1307/blackHole-object-controller-string-error
  399. */
  400. function blackHole(&$controller, $error = '') {
  401. if ($this->blackHoleCallback == null) {
  402. $code = 404;
  403. if ($error == 'login') {
  404. $code = 401;
  405. $controller->header($this->loginRequest());
  406. }
  407. return $controller->redirect(null, $code, true);
  408. } else {
  409. return $this->_callback($controller, $this->blackHoleCallback, array($error));
  410. }
  411. }
  412. /**
  413. * Sets the actions that require a $method HTTP request, or empty for all actions
  414. *
  415. * @param string $method The HTTP method to assign controller actions to
  416. * @param array $actions Controller actions to set the required HTTP method to.
  417. * @return void
  418. */
  419. protected function _requireMethod($method, $actions = array()) {
  420. if (isset($actions[0]) && is_array($actions[0])) {
  421. $actions = $actions[0];
  422. }
  423. $this->{'require' . $method} = (empty($actions)) ? array('*'): $actions;
  424. }
  425. /**
  426. * Check if HTTP methods are required
  427. *
  428. * @param object $controller Instantiating controller
  429. * @return bool true if $method is required
  430. */
  431. protected function _methodsRequired(&$controller) {
  432. foreach (array('Post', 'Get', 'Put', 'Delete') as $method) {
  433. $property = 'require' . $method;
  434. if (is_array($this->$property) && !empty($this->$property)) {
  435. $require = array_map('strtolower', $this->$property);
  436. if (in_array($this->_action, $require) || $this->$property == array('*')) {
  437. if (!$this->request->is(strtolower($method))) {
  438. if (!$this->blackHole($controller, strtolower($method))) {
  439. return null;
  440. }
  441. }
  442. }
  443. }
  444. }
  445. return true;
  446. }
  447. /**
  448. * Check if access requires secure connection
  449. *
  450. * @param object $controller Instantiating controller
  451. * @return bool true if secure connection required
  452. */
  453. protected function _secureRequired(&$controller) {
  454. if (is_array($this->requireSecure) && !empty($this->requireSecure)) {
  455. $requireSecure = array_map('strtolower', $this->requireSecure);
  456. if (in_array($this->_action, $requireSecure) || $this->requireSecure == array('*')) {
  457. if (!$this->request->is('ssl')) {
  458. if (!$this->blackHole($controller, 'secure')) {
  459. return null;
  460. }
  461. }
  462. }
  463. }
  464. return true;
  465. }
  466. /**
  467. * Check if authentication is required
  468. *
  469. * @param object $controller Instantiating controller
  470. * @return bool true if authentication required
  471. */
  472. protected function _authRequired(&$controller) {
  473. if (is_array($this->requireAuth) && !empty($this->requireAuth) && !empty($this->request->data)) {
  474. $requireAuth = array_map('strtolower', $this->requireAuth);
  475. if (in_array($this->request->params['action'], $requireAuth) || $this->requireAuth == array('*')) {
  476. if (!isset($controller->request->data['_Token'] )) {
  477. if (!$this->blackHole($controller, 'auth')) {
  478. return null;
  479. }
  480. }
  481. if ($this->Session->check('_Token')) {
  482. $tData = $this->Session->read('_Token');
  483. if (!empty($tData['allowedControllers']) && !in_array($this->request->params['controller'], $tData['allowedControllers']) || !empty($tData['allowedActions']) && !in_array($this->request->params['action'], $tData['allowedActions'])) {
  484. if (!$this->blackHole($controller, 'auth')) {
  485. return null;
  486. }
  487. }
  488. } else {
  489. if (!$this->blackHole($controller, 'auth')) {
  490. return null;
  491. }
  492. }
  493. }
  494. }
  495. return true;
  496. }
  497. /**
  498. * Check if login is required
  499. *
  500. * @param object $controller Instantiating controller
  501. * @return bool true if login is required
  502. */
  503. protected function _loginRequired(&$controller) {
  504. if (is_array($this->requireLogin) && !empty($this->requireLogin)) {
  505. $requireLogin = array_map('strtolower', $this->requireLogin);
  506. if (in_array($this->_action, $requireLogin) || $this->requireLogin == array('*')) {
  507. $login = $this->loginCredentials($this->loginOptions['type']);
  508. if ($login == null) {
  509. $controller->header($this->loginRequest());
  510. if (!empty($this->loginOptions['prompt'])) {
  511. $this->_callback($controller, $this->loginOptions['prompt']);
  512. } else {
  513. $this->blackHole($controller, 'login');
  514. }
  515. } else {
  516. if (isset($this->loginOptions['login'])) {
  517. $this->_callback($controller, $this->loginOptions['login'], array($login));
  518. } else {
  519. if (strtolower($this->loginOptions['type']) == 'digest') {
  520. if ($login && isset($this->loginUsers[$login['username']])) {
  521. if ($login['response'] == $this->generateDigestResponseHash($login)) {
  522. return true;
  523. }
  524. }
  525. $this->blackHole($controller, 'login');
  526. } else {
  527. if (
  528. !(in_array($login['username'], array_keys($this->loginUsers)) &&
  529. $this->loginUsers[$login['username']] == $login['password'])
  530. ) {
  531. $this->blackHole($controller, 'login');
  532. }
  533. }
  534. }
  535. }
  536. }
  537. }
  538. return true;
  539. }
  540. /**
  541. * Validate submitted form
  542. *
  543. * @param object $controller Instantiating controller
  544. * @return bool true if submitted form is valid
  545. */
  546. protected function _validatePost(&$controller) {
  547. if (empty($controller->request->data)) {
  548. return true;
  549. }
  550. $data = $controller->request->data;
  551. if (!isset($data['_Token']) || !isset($data['_Token']['fields'])) {
  552. return false;
  553. }
  554. $locked = null;
  555. $check = $controller->request->data;
  556. $token = urldecode($check['_Token']['fields']);
  557. if (strpos($token, ':')) {
  558. list($token, $locked) = explode(':', $token, 2);
  559. }
  560. unset($check['_Token']);
  561. $locked = str_rot13($locked);
  562. if (preg_match('/(\A|;|{|})O\:[0-9]+/', $locked)) {
  563. return false;
  564. }
  565. $lockedFields = array();
  566. $fields = Set::flatten($check);
  567. $fieldList = array_keys($fields);
  568. $locked = unserialize($locked);
  569. $multi = array();
  570. foreach ($fieldList as $i => $key) {
  571. if (preg_match('/\.\d+$/', $key)) {
  572. $multi[$i] = preg_replace('/\.\d+$/', '', $key);
  573. unset($fieldList[$i]);
  574. }
  575. }
  576. if (!empty($multi)) {
  577. $fieldList += array_unique($multi);
  578. }
  579. foreach ($fieldList as $i => $key) {
  580. $isDisabled = false;
  581. $isLocked = (is_array($locked) && in_array($key, $locked));
  582. if (!empty($this->disabledFields)) {
  583. foreach ((array)$this->disabledFields as $disabled) {
  584. $disabled = explode('.', $disabled);
  585. $field = array_values(array_intersect(explode('.', $key), $disabled));
  586. $isDisabled = ($field === $disabled);
  587. if ($isDisabled) {
  588. break;
  589. }
  590. }
  591. }
  592. if ($isDisabled || $isLocked) {
  593. unset($fieldList[$i]);
  594. if ($isLocked) {
  595. $lockedFields[$key] = $fields[$key];
  596. }
  597. }
  598. }
  599. sort($fieldList, SORT_STRING);
  600. ksort($lockedFields, SORT_STRING);
  601. $fieldList += $lockedFields;
  602. $check = Security::hash(serialize($fieldList) . Configure::read('Security.salt'));
  603. return ($token === $check);
  604. }
  605. /**
  606. * Add authentication key for new form posts
  607. *
  608. * @param object $controller Instantiating controller
  609. * @return bool Success
  610. */
  611. protected function _generateToken(&$controller) {
  612. if (isset($controller->request->params['requested']) && $controller->request->params['requested'] === 1) {
  613. if ($this->Session->check('_Token')) {
  614. $tokenData = $this->Session->read('_Token');
  615. $controller->request->params['_Token'] = $tokenData;
  616. }
  617. return false;
  618. }
  619. $authKey = Security::generateAuthKey();
  620. $token = array(
  621. 'key' => $authKey,
  622. 'allowedControllers' => $this->allowedControllers,
  623. 'allowedActions' => $this->allowedActions,
  624. 'disabledFields' => $this->disabledFields,
  625. 'csrfTokens' => array()
  626. );
  627. $tokenData = array();
  628. if ($this->Session->check('_Token')) {
  629. $tokenData = $this->Session->read('_Token');
  630. if (!empty($tokenData['csrfTokens'])) {
  631. $token['csrfTokens'] = $this->_expireTokens($tokenData['csrfTokens']);
  632. }
  633. }
  634. if ($this->csrfCheck && ($this->csrfUseOnce || empty($tokenData['csrfTokens'])) ) {
  635. $token['csrfTokens'][$authKey] = strtotime($this->csrfExpires);
  636. }
  637. $controller->request->params['_Token'] = $token;
  638. $this->Session->write('_Token', $token);
  639. return true;
  640. }
  641. /**
  642. * Validate that the controller has a CSRF token in the POST data
  643. * and that the token is legit/not expired. If the token is valid
  644. * it will be removed from the list of valid tokens.
  645. *
  646. * @param Controller $controller A controller to check
  647. * @return boolean Valid csrf token.
  648. */
  649. protected function _validateCsrf($controller) {
  650. $token = $this->Session->read('_Token');
  651. $requestToken = $controller->request->data('_Token.key');
  652. if (isset($token['csrfTokens'][$requestToken]) && $token['csrfTokens'][$requestToken] >= time()) {
  653. if ($this->csrfUseOnce) {
  654. $this->Session->delete('_Token.csrfTokens.' . $requestToken);
  655. }
  656. return true;
  657. }
  658. return false;
  659. }
  660. /**
  661. * Expire CSRF nonces and remove them from the valid tokens.
  662. * Uses a simple timeout to expire the tokens.
  663. *
  664. * @param array $tokens An array of nonce => expires.
  665. * @return An array of nonce => expires.
  666. */
  667. protected function _expireTokens($tokens) {
  668. $now = time();
  669. foreach ($tokens as $nonce => $expires) {
  670. if ($expires < $now) {
  671. unset($tokens[$nonce]);
  672. }
  673. }
  674. return $tokens;
  675. }
  676. /**
  677. * Sets the default login options for an HTTP-authenticated request
  678. *
  679. * @param array $options Default login options
  680. * @return void
  681. */
  682. protected function _setLoginDefaults(&$options) {
  683. $options = array_merge(array(
  684. 'type' => 'basic',
  685. 'realm' => env('SERVER_NAME'),
  686. 'qop' => 'auth',
  687. 'nonce' => String::uuid()
  688. ), array_filter($options));
  689. $options = array_merge(array('opaque' => md5($options['realm'])), $options);
  690. }
  691. /**
  692. * Calls a controller callback method
  693. *
  694. * @param object $controller Controller to run callback on
  695. * @param string $method Method to execute
  696. * @param array $params Parameters to send to method
  697. * @return mixed Controller callback method's response
  698. */
  699. protected function _callback(&$controller, $method, $params = array()) {
  700. if (is_callable(array($controller, $method))) {
  701. return call_user_func_array(array(&$controller, $method), empty($params) ? null : $params);
  702. } else {
  703. return null;
  704. }
  705. }
  706. }