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

/vendor/cakephp/cakephp/src/Controller/Component/SecurityComponent.php

https://gitlab.com/alexandresgv/siteentec
PHP | 405 lines | 229 code | 36 blank | 140 comment | 42 complexity | 21e5ef9827fcb937511a8ff127dc70f2 MD5 | raw file
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 0.10.8
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Controller\Component;
  16. use Cake\Controller\Component;
  17. use Cake\Controller\Controller;
  18. use Cake\Event\Event;
  19. use Cake\Network\Exception\BadRequestException;
  20. use Cake\Network\Request;
  21. use Cake\Utility\Hash;
  22. use Cake\Utility\Security;
  23. /**
  24. * The Security Component creates an easy way to integrate tighter security in
  25. * your application. It provides methods for various tasks like:
  26. *
  27. * - Restricting which HTTP methods your application accepts.
  28. * - Form tampering protection
  29. * - Requiring that SSL be used.
  30. * - Limiting cross controller communication.
  31. *
  32. * @link http://book.cakephp.org/3.0/en/controllers/components/security.html
  33. */
  34. class SecurityComponent extends Component
  35. {
  36. /**
  37. * Default config
  38. *
  39. * - `blackHoleCallback` - The controller method that will be called if this
  40. * request is black-hole'd.
  41. * - `requireSecure` - List of actions that require an SSL-secured connection.
  42. * - `requireAuth` - List of actions that require a valid authentication key.
  43. * - `allowedControllers` - Controllers from which actions of the current
  44. * controller are allowed to receive requests.
  45. * - `allowedActions` - Actions from which actions of the current controller
  46. * are allowed to receive requests.
  47. * - `unlockedFields` - Form fields to exclude from POST validation. Fields can
  48. * be unlocked either in the Component, or with FormHelper::unlockField().
  49. * Fields that have been unlocked are not required to be part of the POST
  50. * and hidden unlocked fields do not have their values checked.
  51. * - `unlockedActions` - Actions to exclude from POST validation checks.
  52. * Other checks like requireAuth(), requireSecure() etc. will still be applied.
  53. * - `validatePost` - Whether to validate POST data. Set to false to disable
  54. * for data coming from 3rd party services, etc.
  55. *
  56. * @var array
  57. */
  58. protected $_defaultConfig = [
  59. 'blackHoleCallback' => null,
  60. 'requireSecure' => [],
  61. 'requireAuth' => [],
  62. 'allowedControllers' => [],
  63. 'allowedActions' => [],
  64. 'unlockedFields' => [],
  65. 'unlockedActions' => [],
  66. 'validatePost' => true
  67. ];
  68. /**
  69. * Holds the current action of the controller
  70. *
  71. * @var string
  72. */
  73. protected $_action = null;
  74. /**
  75. * Request object
  76. *
  77. * @var \Cake\Network\Request
  78. */
  79. public $request;
  80. /**
  81. * The Session object
  82. *
  83. * @var \Cake\Network\Session
  84. */
  85. public $session;
  86. /**
  87. * Component startup. All security checking happens here.
  88. *
  89. * @param Event $event An Event instance
  90. * @return mixed
  91. */
  92. public function startup(Event $event)
  93. {
  94. $controller = $event->subject();
  95. $this->session = $this->request->session();
  96. $this->_action = $this->request->params['action'];
  97. $this->_secureRequired($controller);
  98. $this->_authRequired($controller);
  99. $isPost = $this->request->is(['post', 'put']);
  100. $isNotRequestAction = (
  101. !isset($controller->request->params['requested']) ||
  102. $controller->request->params['requested'] != 1
  103. );
  104. if ($this->_action === $this->_config['blackHoleCallback']) {
  105. return $this->blackHole($controller, 'auth');
  106. }
  107. if (!in_array($this->_action, (array)$this->_config['unlockedActions']) &&
  108. $isPost && $isNotRequestAction
  109. ) {
  110. if ($this->_config['validatePost'] &&
  111. $this->_validatePost($controller) === false
  112. ) {
  113. return $this->blackHole($controller, 'auth');
  114. }
  115. }
  116. $this->generateToken($controller->request);
  117. if ($isPost && is_array($controller->request->data)) {
  118. unset($controller->request->data['_Token']);
  119. }
  120. }
  121. /**
  122. * Events supported by this component.
  123. *
  124. * @return array
  125. */
  126. public function implementedEvents()
  127. {
  128. return [
  129. 'Controller.startup' => 'startup',
  130. ];
  131. }
  132. /**
  133. * Sets the actions that require a request that is SSL-secured, or empty for all actions
  134. *
  135. * @param string|array $actions Actions list
  136. * @return void
  137. */
  138. public function requireSecure($actions = null)
  139. {
  140. $this->_requireMethod('Secure', (array)$actions);
  141. }
  142. /**
  143. * Sets the actions that require whitelisted form submissions.
  144. *
  145. * Adding actions with this method will enforce the restrictions
  146. * set in SecurityComponent::$allowedControllers and
  147. * SecurityComponent::$allowedActions.
  148. *
  149. * @param string|array $actions Actions list
  150. * @return void
  151. */
  152. public function requireAuth($actions)
  153. {
  154. $this->_requireMethod('Auth', (array)$actions);
  155. }
  156. /**
  157. * Black-hole an invalid request with a 400 error or custom callback. If SecurityComponent::$blackHoleCallback
  158. * is specified, it will use this callback by executing the method indicated in $error
  159. *
  160. * @param Controller $controller Instantiating controller
  161. * @param string $error Error method
  162. * @return mixed If specified, controller blackHoleCallback's response, or no return otherwise
  163. * @see SecurityComponent::$blackHoleCallback
  164. * @link http://book.cakephp.org/3.0/en/controllers/components/security.html#handling-blackhole-callbacks
  165. * @throws \Cake\Network\Exception\BadRequestException
  166. */
  167. public function blackHole(Controller $controller, $error = '')
  168. {
  169. if (!$this->_config['blackHoleCallback']) {
  170. throw new BadRequestException('The request has been black-holed');
  171. }
  172. return $this->_callback($controller, $this->_config['blackHoleCallback'], [$error]);
  173. }
  174. /**
  175. * Sets the actions that require a $method HTTP request, or empty for all actions
  176. *
  177. * @param string $method The HTTP method to assign controller actions to
  178. * @param array $actions Controller actions to set the required HTTP method to.
  179. * @return void
  180. */
  181. protected function _requireMethod($method, $actions = [])
  182. {
  183. if (isset($actions[0]) && is_array($actions[0])) {
  184. $actions = $actions[0];
  185. }
  186. $this->config('require' . $method, (empty($actions)) ? ['*'] : $actions);
  187. }
  188. /**
  189. * Check if access requires secure connection
  190. *
  191. * @param Controller $controller Instantiating controller
  192. * @return bool true if secure connection required
  193. */
  194. protected function _secureRequired(Controller $controller)
  195. {
  196. if (is_array($this->_config['requireSecure']) &&
  197. !empty($this->_config['requireSecure'])
  198. ) {
  199. $requireSecure = $this->_config['requireSecure'];
  200. if (in_array($this->_action, $requireSecure) || $requireSecure === ['*']) {
  201. if (!$this->request->is('ssl')) {
  202. if (!$this->blackHole($controller, 'secure')) {
  203. return null;
  204. }
  205. }
  206. }
  207. }
  208. return true;
  209. }
  210. /**
  211. * Check if authentication is required
  212. *
  213. * @param Controller $controller Instantiating controller
  214. * @return bool true if authentication required
  215. */
  216. protected function _authRequired(Controller $controller)
  217. {
  218. if (is_array($this->_config['requireAuth']) &&
  219. !empty($this->_config['requireAuth']) &&
  220. !empty($this->request->data)
  221. ) {
  222. $requireAuth = $this->_config['requireAuth'];
  223. if (in_array($this->request->params['action'], $requireAuth) || $requireAuth == ['*']) {
  224. if (!isset($controller->request->data['_Token'])) {
  225. if (!$this->blackHole($controller, 'auth')) {
  226. return false;
  227. }
  228. }
  229. if ($this->session->check('_Token')) {
  230. $tData = $this->session->read('_Token');
  231. if (!empty($tData['allowedControllers']) &&
  232. !in_array($this->request->params['controller'], $tData['allowedControllers']) ||
  233. !empty($tData['allowedActions']) &&
  234. !in_array($this->request->params['action'], $tData['allowedActions'])
  235. ) {
  236. if (!$this->blackHole($controller, 'auth')) {
  237. return false;
  238. }
  239. }
  240. } else {
  241. if (!$this->blackHole($controller, 'auth')) {
  242. return false;
  243. }
  244. }
  245. }
  246. }
  247. return true;
  248. }
  249. /**
  250. * Validate submitted form
  251. *
  252. * @param Controller $controller Instantiating controller
  253. * @return bool true if submitted form is valid
  254. */
  255. protected function _validatePost(Controller $controller)
  256. {
  257. if (empty($controller->request->data)) {
  258. return true;
  259. }
  260. $check = $controller->request->data;
  261. if (!isset($check['_Token']) ||
  262. !isset($check['_Token']['fields']) ||
  263. !isset($check['_Token']['unlocked'])
  264. ) {
  265. return false;
  266. }
  267. $locked = '';
  268. $token = urldecode($check['_Token']['fields']);
  269. $unlocked = urldecode($check['_Token']['unlocked']);
  270. if (strpos($token, ':')) {
  271. list($token, $locked) = explode(':', $token, 2);
  272. }
  273. unset($check['_Token'], $check['_csrfToken']);
  274. $locked = explode('|', $locked);
  275. $unlocked = explode('|', $unlocked);
  276. $fields = Hash::flatten($check);
  277. $fieldList = array_keys($fields);
  278. $multi = $lockedFields = [];
  279. $isUnlocked = false;
  280. foreach ($fieldList as $i => $key) {
  281. if (preg_match('/(\.\d+){1,10}$/', $key)) {
  282. $multi[$i] = preg_replace('/(\.\d+){1,10}$/', '', $key);
  283. unset($fieldList[$i]);
  284. } else {
  285. $fieldList[$i] = (string)$key;
  286. }
  287. }
  288. if (!empty($multi)) {
  289. $fieldList += array_unique($multi);
  290. }
  291. $unlockedFields = array_unique(
  292. array_merge((array)$this->config('disabledFields'), (array)$this->_config['unlockedFields'], $unlocked)
  293. );
  294. foreach ($fieldList as $i => $key) {
  295. $isLocked = (is_array($locked) && in_array($key, $locked));
  296. if (!empty($unlockedFields)) {
  297. foreach ($unlockedFields as $off) {
  298. $off = explode('.', $off);
  299. $field = array_values(array_intersect(explode('.', $key), $off));
  300. $isUnlocked = ($field === $off);
  301. if ($isUnlocked) {
  302. break;
  303. }
  304. }
  305. }
  306. if ($isUnlocked || $isLocked) {
  307. unset($fieldList[$i]);
  308. if ($isLocked) {
  309. $lockedFields[$key] = $fields[$key];
  310. }
  311. }
  312. }
  313. sort($unlocked, SORT_STRING);
  314. sort($fieldList, SORT_STRING);
  315. ksort($lockedFields, SORT_STRING);
  316. $fieldList += $lockedFields;
  317. $unlocked = implode('|', $unlocked);
  318. $hashParts = [
  319. $controller->request->here(),
  320. serialize($fieldList),
  321. $unlocked,
  322. Security::salt()
  323. ];
  324. $check = Security::hash(implode('', $hashParts), 'sha1');
  325. return ($token === $check);
  326. }
  327. /**
  328. * Manually add form tampering prevention token information into the provided
  329. * request object.
  330. *
  331. * @param \Cake\Network\Request $request The request object to add into.
  332. * @return bool
  333. */
  334. public function generateToken(Request $request)
  335. {
  336. if (isset($request->params['requested']) && $request->params['requested'] === 1) {
  337. if ($this->session->check('_Token')) {
  338. $request->params['_Token'] = $this->session->read('_Token');
  339. }
  340. return false;
  341. }
  342. $token = [
  343. 'allowedControllers' => $this->_config['allowedControllers'],
  344. 'allowedActions' => $this->_config['allowedActions'],
  345. 'unlockedFields' => $this->_config['unlockedFields'],
  346. ];
  347. $this->session->write('_Token', $token);
  348. $request->params['_Token'] = [
  349. 'unlockedFields' => $token['unlockedFields']
  350. ];
  351. return true;
  352. }
  353. /**
  354. * Calls a controller callback method
  355. *
  356. * @param Controller $controller Controller to run callback on
  357. * @param string $method Method to execute
  358. * @param array $params Parameters to send to method
  359. * @return mixed Controller callback method's response
  360. * @throws \Cake\Network\Exception\BadRequestException When a the blackholeCallback is not callable.
  361. */
  362. protected function _callback(Controller $controller, $method, $params = [])
  363. {
  364. if (!is_callable([$controller, $method])) {
  365. throw new BadRequestException('The request has been black-holed');
  366. }
  367. return call_user_func_array([&$controller, $method], empty($params) ? null : $params);
  368. }
  369. }