CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 2.0 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.0
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Packages

  • Cake
    • Cache
      • Engine
    • Configure
    • Console
      • Command
        • Task
    • Controller
      • Component
        • Auth
    • Core
    • Error
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
    • Network
      • Email
      • Http
    • Routing
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper

Classes

  • AclComponent
  • AuthComponent
  • CookieComponent
  • DbAcl
  • EmailComponent
  • IniAcl
  • PaginatorComponent
  • RequestHandlerComponent
  • SecurityComponent
  • SessionComponent

Interfaces

  • AclInterface
  1: <?php
  2: /**
  3:  * Security Component
  4:  *
  5:  * PHP 5
  6:  *
  7:  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8:  * Copyright 2005-2011, 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-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
 14:  * @link          http://cakephp.org CakePHP(tm) Project
 15:  * @package       Cake.Controller.Component
 16:  * @since         CakePHP(tm) v 0.10.8.2156
 17:  * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
 18:  */
 19: 
 20: App::uses('Component', 'Controller');
 21: App::uses('String', 'Utility');
 22: App::uses('Security', 'Utility');
 23: 
 24: /**
 25:  * The Security Component creates an easy way to integrate tighter security in 
 26:  * your application. It provides methods for various tasks like:
 27:  *
 28:  * - Restricting which HTTP methods your application accepts.
 29:  * - CSRF protection.
 30:  * - Form tampering protection
 31:  * - Requiring that SSL be used.
 32:  * - Limiting cross controller communication.
 33:  *
 34:  * @package       Cake.Controller.Component
 35:  * @link http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html
 36:  */
 37: class SecurityComponent extends Component {
 38: 
 39: /**
 40:  * The controller method that will be called if this request is black-hole'd
 41:  *
 42:  * @var string
 43:  */
 44:     public $blackHoleCallback = null;
 45: 
 46: /**
 47:  * List of controller actions for which a POST request is required
 48:  *
 49:  * @var array
 50:  * @see SecurityComponent::requirePost()
 51:  */
 52:     public $requirePost = array();
 53: 
 54: /**
 55:  * List of controller actions for which a GET request is required
 56:  *
 57:  * @var array
 58:  * @see SecurityComponent::requireGet()
 59:  */
 60:     public $requireGet = array();
 61: 
 62: /**
 63:  * List of controller actions for which a PUT request is required
 64:  *
 65:  * @var array
 66:  * @see SecurityComponent::requirePut()
 67:  */
 68:     public $requirePut = array();
 69: 
 70: /**
 71:  * List of controller actions for which a DELETE request is required
 72:  *
 73:  * @var array
 74:  * @see SecurityComponent::requireDelete()
 75:  */
 76:     public $requireDelete = array();
 77: 
 78: /**
 79:  * List of actions that require an SSL-secured connection
 80:  *
 81:  * @var array
 82:  * @see SecurityComponent::requireSecure()
 83:  */
 84:     public $requireSecure = array();
 85: 
 86: /**
 87:  * List of actions that require a valid authentication key
 88:  *
 89:  * @var array
 90:  * @see SecurityComponent::requireAuth()
 91:  */
 92:     public $requireAuth = array();
 93: 
 94: /**
 95:  * Controllers from which actions of the current controller are allowed to receive
 96:  * requests.
 97:  *
 98:  * @var array
 99:  * @see SecurityComponent::requireAuth()
100:  */
101:     public $allowedControllers = array();
102: 
103: /**
104:  * Actions from which actions of the current controller are allowed to receive
105:  * requests.
106:  *
107:  * @var array
108:  * @see SecurityComponent::requireAuth()
109:  */
110:     public $allowedActions = array();
111: 
112: /**
113:  * Deprecated property, superseded by unlockedFields.
114:  *
115:  * @var array
116:  * @deprecated
117:  * @see SecurityComponent::$unlockedFields
118:  */
119:     public $disabledFields = array();
120: 
121: /**
122:  * Form fields to exclude from POST validation. Fields can be unlocked
123:  * either in the Component, or with FormHelper::unlockField().
124:  * Fields that have been unlocked are not required to be part of the POST
125:  * and hidden unlocked fields do not have their values checked.
126:  *
127:  * @var array
128:  */
129:     public $unlockedFields = array();
130: 
131: /**
132:  * Whether to validate POST data.  Set to false to disable for data coming from 3rd party
133:  * services, etc.
134:  *
135:  * @var boolean
136:  */
137:     public $validatePost = true;
138: 
139: /**
140:  * Whether to use CSRF protected forms.  Set to false to disable CSRF protection on forms.
141:  *
142:  * @var boolean
143:  * @see http://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
144:  * @see SecurityComponent::$csrfExpires
145:  */
146:     public $csrfCheck = true;
147: 
148: /**
149:  * The duration from when a CSRF token is created that it will expire on.
150:  * Each form/page request will generate a new token that can only be submitted once unless
151:  * it expires.  Can be any value compatible with strtotime()
152:  *
153:  * @var string
154:  */
155:     public $csrfExpires = '+30 minutes';
156: 
157: /**
158:  * Controls whether or not CSRF tokens are use and burn.  Set to false to not generate
159:  * new tokens on each request.  One token will be reused until it expires. This reduces
160:  * the chances of users getting invalid requests because of token consumption.
161:  * It has the side effect of making CSRF less secure, as tokens are reusable.
162:  *
163:  * @var boolean
164:  */
165:     public $csrfUseOnce = true;
166: 
167: /**
168:  * Other components used by the Security component
169:  *
170:  * @var array
171:  */
172:     public $components = array('Session');
173: 
174: /**
175:  * Holds the current action of the controller
176:  *
177:  * @var string
178:  */
179:     protected $_action = null;
180: 
181: /**
182:  * Request object
183:  *
184:  * @var CakeRequest
185:  */
186:     public $request;
187: 
188: /**
189:  * Component startup. All security checking happens here.
190:  *
191:  * @param Controller $controller Instantiating controller
192:  * @return void
193:  */
194:     public function startup($controller) {
195:         $this->request = $controller->request;
196:         $this->_action = $this->request->params['action'];
197:         $this->_methodsRequired($controller);
198:         $this->_secureRequired($controller);
199:         $this->_authRequired($controller);
200: 
201:         $isPost = ($this->request->is('post') || $this->request->is('put'));
202:         $isNotRequestAction = (
203:             !isset($controller->request->params['requested']) ||
204:             $controller->request->params['requested'] != 1
205:         );
206: 
207:         if ($isPost && $isNotRequestAction && $this->validatePost) {
208:             if ($this->_validatePost($controller) === false) {
209:                 return $this->blackHole($controller, 'auth');
210:             }
211:         }
212:         if ($isPost && $isNotRequestAction && $this->csrfCheck) {
213:             if ($this->_validateCsrf($controller) === false) {
214:                 return $this->blackHole($controller, 'csrf');
215:             }
216:         }
217:         $this->_generateToken($controller);
218:         if ($isPost) {
219:             unset($controller->request->data['_Token']);
220:         }
221:     }
222: 
223: /**
224:  * Sets the actions that require a POST request, or empty for all actions
225:  *
226:  * @return void
227:  * @link http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html#SecurityComponent::requirePost
228:  */
229:     public function requirePost() {
230:         $args = func_get_args();
231:         $this->_requireMethod('Post', $args);
232:     }
233: 
234: /**
235:  * Sets the actions that require a GET request, or empty for all actions
236:  *
237:  * @return void
238:  */
239:     public function requireGet() {
240:         $args = func_get_args();
241:         $this->_requireMethod('Get', $args);
242:     }
243: 
244: /**
245:  * Sets the actions that require a PUT request, or empty for all actions
246:  *
247:  * @return void
248:  */
249:     public function requirePut() {
250:         $args = func_get_args();
251:         $this->_requireMethod('Put', $args);
252:     }
253: 
254: /**
255:  * Sets the actions that require a DELETE request, or empty for all actions
256:  *
257:  * @return void
258:  */
259:     public function requireDelete() {
260:         $args = func_get_args();
261:         $this->_requireMethod('Delete', $args);
262:     }
263: 
264: /**
265:  * Sets the actions that require a request that is SSL-secured, or empty for all actions
266:  *
267:  * @return void
268:  * @link http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html#SecurityComponent::requireSecure
269:  */
270:     public function requireSecure() {
271:         $args = func_get_args();
272:         $this->_requireMethod('Secure', $args);
273:     }
274: 
275: /**
276:  * Sets the actions that require an authenticated request, or empty for all actions
277:  *
278:  * @return void
279:  * @link http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html#SecurityComponent::requireAuth
280:  */
281:     public function requireAuth() {
282:         $args = func_get_args();
283:         $this->_requireMethod('Auth', $args);
284:     }
285: 
286: /**
287:  * Black-hole an invalid request with a 400 error or custom callback. If SecurityComponent::$blackHoleCallback
288:  * is specified, it will use this callback by executing the method indicated in $error
289:  *
290:  * @param Controller $controller Instantiating controller
291:  * @param string $error Error method
292:  * @return mixed If specified, controller blackHoleCallback's response, or no return otherwise
293:  * @see SecurityComponent::$blackHoleCallback
294:  * @link http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html#handling-blackhole-callbacks
295:  * @throws BadRequestException
296:  */
297:     public function blackHole($controller, $error = '') {
298:         if ($this->blackHoleCallback == null) {
299:             throw new BadRequestException(__d('cake_dev', 'The request has been black-holed'));
300:         } else {
301:             return $this->_callback($controller, $this->blackHoleCallback, array($error));
302:         }
303:     }
304: 
305: /**
306:  * Sets the actions that require a $method HTTP request, or empty for all actions
307:  *
308:  * @param string $method The HTTP method to assign controller actions to
309:  * @param array $actions Controller actions to set the required HTTP method to.
310:  * @return void
311:  */
312:     protected function _requireMethod($method, $actions = array()) {
313:         if (isset($actions[0]) && is_array($actions[0])) {
314:             $actions = $actions[0];
315:         }
316:         $this->{'require' . $method} = (empty($actions)) ? array('*'): $actions;
317:     }
318: 
319: /**
320:  * Check if HTTP methods are required
321:  *
322:  * @param Controller $controller Instantiating controller
323:  * @return boolean true if $method is required
324:  */
325:     protected function _methodsRequired($controller) {
326:         foreach (array('Post', 'Get', 'Put', 'Delete') as $method) {
327:             $property = 'require' . $method;
328:             if (is_array($this->$property) && !empty($this->$property)) {
329:                 $require = $this->$property;
330:                 if (in_array($this->_action, $require) || $this->$property == array('*')) {
331:                     if (!$this->request->is($method)) {
332:                         if (!$this->blackHole($controller, $method)) {
333:                             return null;
334:                         }
335:                     }
336:                 }
337:             }
338:         }
339:         return true;
340:     }
341: 
342: /**
343:  * Check if access requires secure connection
344:  *
345:  * @param Controller $controller Instantiating controller
346:  * @return boolean true if secure connection required
347:  */
348:     protected function _secureRequired($controller) {
349:         if (is_array($this->requireSecure) && !empty($this->requireSecure)) {
350:             $requireSecure = $this->requireSecure;
351: 
352:             if (in_array($this->_action, $requireSecure) || $this->requireSecure == array('*')) {
353:                 if (!$this->request->is('ssl')) {
354:                     if (!$this->blackHole($controller, 'secure')) {
355:                         return null;
356:                     }
357:                 }
358:             }
359:         }
360:         return true;
361:     }
362: 
363: /**
364:  * Check if authentication is required
365:  *
366:  * @param Controller $controller Instantiating controller
367:  * @return boolean true if authentication required
368:  */
369:     protected function _authRequired($controller) {
370:         if (is_array($this->requireAuth) && !empty($this->requireAuth) && !empty($this->request->data)) {
371:             $requireAuth = $this->requireAuth;
372: 
373:             if (in_array($this->request->params['action'], $requireAuth) || $this->requireAuth == array('*')) {
374:                 if (!isset($controller->request->data['_Token'] )) {
375:                     if (!$this->blackHole($controller, 'auth')) {
376:                         return null;
377:                     }
378:                 }
379: 
380:                 if ($this->Session->check('_Token')) {
381:                     $tData = $this->Session->read('_Token');
382: 
383:                     if (
384:                         !empty($tData['allowedControllers']) && 
385:                         !in_array($this->request->params['controller'], $tData['allowedControllers']) || 
386:                         !empty($tData['allowedActions']) && 
387:                         !in_array($this->request->params['action'], $tData['allowedActions'])
388:                     ) {
389:                         if (!$this->blackHole($controller, 'auth')) {
390:                             return null;
391:                         }
392:                     }
393:                 } else {
394:                     if (!$this->blackHole($controller, 'auth')) {
395:                         return null;
396:                     }
397:                 }
398:             }
399:         }
400:         return true;
401:     }
402: 
403: /**
404:  * Validate submitted form
405:  *
406:  * @param Controller $controller Instantiating controller
407:  * @return boolean true if submitted form is valid
408:  */
409:     protected function _validatePost($controller) {
410:         if (empty($controller->request->data)) {
411:             return true;
412:         }
413:         $data = $controller->request->data;
414: 
415:         if (!isset($data['_Token']) || !isset($data['_Token']['fields']) || !isset($data['_Token']['unlocked'])) {
416:             return false;
417:         }
418: 
419:         $locked = '';
420:         $check = $controller->request->data;
421:         $token = urldecode($check['_Token']['fields']);
422:         $unlocked = urldecode($check['_Token']['unlocked']);
423: 
424:         if (strpos($token, ':')) {
425:             list($token, $locked) = explode(':', $token, 2);
426:         }
427:         unset($check['_Token']);
428: 
429:         $locked = explode('|', $locked);
430:         $unlocked = explode('|', $unlocked);
431: 
432:         $lockedFields = array();
433:         $fields = Set::flatten($check);
434:         $fieldList = array_keys($fields);
435:         $multi = array();
436: 
437:         foreach ($fieldList as $i => $key) {
438:             if (preg_match('/(\.\d+)+$/', $key)) {
439:                 $multi[$i] = preg_replace('/(\.\d+)+$/', '', $key);
440:                 unset($fieldList[$i]);
441:             }
442:         }
443:         if (!empty($multi)) {
444:             $fieldList += array_unique($multi);
445:         }
446: 
447:         $unlockedFields = array_unique(
448:             array_merge((array)$this->disabledFields, (array)$this->unlockedFields, $unlocked)
449:         );
450: 
451:         foreach ($fieldList as $i => $key) {
452:             $isDisabled = false;
453:             $isLocked = (is_array($locked) && in_array($key, $locked));
454: 
455:             if (!empty($unlockedFields)) {
456:                 foreach ($unlockedFields as $off) {
457:                     $off = explode('.', $off);
458:                     $field = array_values(array_intersect(explode('.', $key), $off));
459:                     $isUnlocked = ($field === $off);
460:                     if ($isUnlocked) {
461:                         break;
462:                     }
463:                 }
464:             }
465: 
466:             if ($isUnlocked || $isLocked) {
467:                 unset($fieldList[$i]);
468:                 if ($isLocked) {
469:                     $lockedFields[$key] = $fields[$key];
470:                 }
471:             }
472:         }
473:         sort($unlocked, SORT_STRING);
474:         sort($fieldList, SORT_STRING);
475:         ksort($lockedFields, SORT_STRING);
476: 
477:         $fieldList += $lockedFields;
478:         $unlocked = implode('|', $unlocked);
479:         $check = Security::hash(serialize($fieldList) . $unlocked . Configure::read('Security.salt'));
480:         return ($token === $check);
481:     }
482: 
483: /**
484:  * Add authentication key for new form posts
485:  *
486:  * @param Controller $controller Instantiating controller
487:  * @return boolean Success
488:  */
489:     protected function _generateToken($controller) {
490:         if (isset($controller->request->params['requested']) && $controller->request->params['requested'] === 1) {
491:             if ($this->Session->check('_Token')) {
492:                 $tokenData = $this->Session->read('_Token');
493:                 $controller->request->params['_Token'] = $tokenData;
494:             }
495:             return false;
496:         }
497:         $authKey = Security::generateAuthKey();
498:         $token = array(
499:             'key' => $authKey,
500:             'allowedControllers' => $this->allowedControllers,
501:             'allowedActions' => $this->allowedActions,
502:             'unlockedFields' => array_merge($this->disabledFields, $this->unlockedFields),
503:             'csrfTokens' => array()
504:         );
505: 
506:         $tokenData = array();
507:         if ($this->Session->check('_Token')) {
508:             $tokenData = $this->Session->read('_Token');
509:             if (!empty($tokenData['csrfTokens']) && is_array($tokenData['csrfTokens'])) {
510:                 $token['csrfTokens'] = $this->_expireTokens($tokenData['csrfTokens']);
511:             }
512:         }
513:         if ($this->csrfCheck && ($this->csrfUseOnce || empty($token['csrfTokens'])) ) {
514:             $token['csrfTokens'][$authKey] = strtotime($this->csrfExpires);
515:         }
516:         if ($this->csrfCheck && $this->csrfUseOnce == false) {
517:             $csrfTokens = array_keys($token['csrfTokens']);
518:             $token['key'] = $csrfTokens[0];
519:         }
520:         $this->Session->write('_Token', $token);
521:         $controller->request->params['_Token'] = array(
522:             'key' => $token['key'],
523:             'unlockedFields' => $token['unlockedFields']
524:         );
525:         return true;
526:     }
527: 
528: /**
529:  * Validate that the controller has a CSRF token in the POST data
530:  * and that the token is legit/not expired.  If the token is valid
531:  * it will be removed from the list of valid tokens.
532:  *
533:  * @param Controller $controller A controller to check
534:  * @return boolean Valid csrf token.
535:  */
536:     protected function _validateCsrf($controller) {
537:         $token = $this->Session->read('_Token');
538:         $requestToken = $controller->request->data('_Token.key');
539:         if (isset($token['csrfTokens'][$requestToken]) && $token['csrfTokens'][$requestToken] >= time()) {
540:             if ($this->csrfUseOnce) {
541:                 $this->Session->delete('_Token.csrfTokens.' . $requestToken);
542:             }
543:             return true;
544:         }
545:         return false;
546:     }
547: 
548: /**
549:  * Expire CSRF nonces and remove them from the valid tokens.
550:  * Uses a simple timeout to expire the tokens.
551:  *
552:  * @param array $tokens An array of nonce => expires.
553:  * @return array An array of nonce => expires.
554:  */
555:     protected function _expireTokens($tokens) {
556:         $now = time();
557:         foreach ($tokens as $nonce => $expires) {
558:             if ($expires < $now) {
559:                 unset($tokens[$nonce]);
560:             }
561:         }
562:         return $tokens;
563:     }
564: 
565: /**
566:  * Calls a controller callback method
567:  *
568:  * @param Controller $controller Controller to run callback on
569:  * @param string $method Method to execute
570:  * @param array $params Parameters to send to method
571:  * @return mixed Controller callback method's response
572:  */
573:     protected function _callback($controller, $method, $params = array()) {
574:         if (is_callable(array($controller, $method))) {
575:             return call_user_func_array(array(&$controller, $method), empty($params) ? null : $params);
576:         } else {
577:             return null;
578:         }
579:     }
580: }
581: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs