diff --git a/cake/libs/controller/components/security.php b/cake/libs/controller/components/security.php index 0ff493751..87fe4719b 100644 --- a/cake/libs/controller/components/security.php +++ b/cake/libs/controller/components/security.php @@ -50,6 +50,30 @@ class SecurityComponent extends Object { * @see SecurityComponent::requirePost() */ var $requirePost = array(); +/** + * List of controller actions for which a GET request is required + * + * @var array + * @access public + * @see SecurityComponent::requireGet() + */ + var $requireGet = array(); +/** + * List of controller actions for which a PUT request is required + * + * @var array + * @access public + * @see SecurityComponent::requirePut() + */ + var $requirePut = array(); +/** + * List of controller actions for which a DELETE request is required + * + * @var array + * @access public + * @see SecurityComponent::requireDelete() + */ + var $requireDelete = array(); /** * List of actions that require an SSL-secured connection * @@ -137,7 +161,7 @@ class SecurityComponent extends Object { */ function startup(&$controller) { $this->__action = strtolower($controller->action); - $this->__postRequired($controller); + $this->__methodsRequired($controller); $this->__secureRequired($controller); $this->__authRequired($controller); $this->__loginRequired($controller); @@ -154,10 +178,35 @@ class SecurityComponent extends Object { * @access public */ function requirePost() { - $this->requirePost = func_get_args(); - if (empty($this->requirePost)) { - $this->requirePost = array('*'); - } + $args = func_get_args(); + $this->__requireMethod('Post', $args); + } +/** + * Sets the actions that require a GET request, or empty for all actions + * + * @access public + */ + function requireGet() { + $args = func_get_args(); + $this->__requireMethod('Get', $args); + } +/** + * Sets the actions that require a PUT request, or empty for all actions + * + * @access public + */ + function requirePut() { + $args = func_get_args(); + $this->__requireMethod('Put', $args); + } +/** + * Sets the actions that require a DELETE request, or empty for all actions + * + * @access public + */ + function requireDelete() { + $args = func_get_args(); + $this->__requireMethod('Delete', $args); } /** * Sets the actions that require a request that is SSL-secured, or empty for all actions @@ -165,10 +214,8 @@ class SecurityComponent extends Object { * @access public */ function requireSecure() { - $this->requireSecure = func_get_args(); - if (empty($this->requireSecure)) { - $this->requireSecure = array('*'); - } + $args = func_get_args(); + $this->__requireMethod('Secure', $args); } /** * Sets the actions that require an authenticated request, or empty for all actions @@ -176,10 +223,8 @@ class SecurityComponent extends Object { * @access public */ function requireAuth() { - $this->requireAuth = func_get_args(); - if (empty($this->requireAuth)) { - $this->requireAuth = array('*'); - } + $args = func_get_args(); + $this->__requireMethod('Auth', $args); } /** * Sets the actions that require an HTTP-authenticated request, or empty for all actions @@ -190,18 +235,14 @@ class SecurityComponent extends Object { $args = func_get_args(); $base = $this->loginOptions; - foreach ($args as $arg) { + foreach ($args as $i => $arg) { if (is_array($arg)) { $this->loginOptions = $arg; - } else { - $this->requireLogin[] = $arg; + unset($args[$i]); } } $this->loginOptions = array_merge($base, $this->loginOptions); - - if (empty($this->requireLogin)) { - $this->requireLogin = array('*'); - } + $this->__requireMethod('Login', $args); if (isset($this->loginOptions['users'])) { $this->loginUsers =& $this->loginOptions['users']; @@ -332,20 +373,33 @@ class SecurityComponent extends Object { } } /** - * Check if post is required + * Sets the actions that require a $method HTTP request, or empty for all actions * - * @param object $controller Instantiating controller - * @return bool true if post is requred + * @param string $method The HTTP method to assign controller actions to + * @param array $actions Controller actions to set the required HTTP method to. * @access private */ - function __postRequired(&$controller) { - if (is_array($this->requirePost) && !empty($this->requirePost)) { - $requirePost = array_map('strtolower', $this->requirePost); + function __requireMethod($method, $actions = array()) { + $this->{'require' . $method} = ife(empty($actions), array('*'), $actions); + } +/** + * Check if HTTP methods are required + * + * @param object $controller Instantiating controller + * @return bool true if $method is required + * @access private + */ + function __methodsRequired(&$controller) { + foreach (array('Post', 'Get', 'Put', 'Delete') as $method) { + $property = 'require' . $method; + if (is_array($this->$property) && !empty($this->$property)) { + $require = array_map('strtolower', $this->$property); - if (in_array($this->__action, $requirePost) || $this->requirePost == array('*')) { - if (!$this->RequestHandler->isPost()) { - if (!$this->blackHole($controller, 'post')) { - return null; + if (in_array($this->__action, $require) || $this->$property == array('*')) { + if (!$this->RequestHandler->{'is' . $method}()) { + if (!$this->blackHole($controller, strtolower($method))) { + return null; + } } } } diff --git a/cake/tests/cases/libs/controller/components/security.test.php b/cake/tests/cases/libs/controller/components/security.test.php index 2b64470d9..7bed3e69a 100644 --- a/cake/tests/cases/libs/controller/components/security.test.php +++ b/cake/tests/cases/libs/controller/components/security.test.php @@ -38,6 +38,12 @@ class SecurityTestController extends Controller { var $name = 'SecurityTest'; var $components = array('Security'); + var $failed = false; + + function fail() { + $this->failed = true; + } + function redirect($option, $code, $exit) { return $code; } @@ -53,9 +59,8 @@ class SecurityComponentTest extends CakeTestCase { function setUp() { $this->Controller =& new SecurityTestController(); - restore_error_handler(); - @$this->Controller->_initComponents(); - set_error_handler('simpleTestErrorHandler'); + $this->Controller->_initComponents(); + $this->Controller->Security->blackHoleCallback = 'fail'; } function testStartup() { @@ -64,65 +69,134 @@ class SecurityComponentTest extends CakeTestCase { $this->assertNotNull($result); $this->assertTrue($this->Controller->Session->check('_Token')); } - - function testRequirePost() - { - $this->Controller->action = 'posted'; - $this->Controller->Security->startup($this->Controller); - $this->Controller->Security->requirePost('posted'); - $this->assertNull($this->Controller->Security->__methodsRequired($this->Controller)); - $_SERVER['REQUEST_METHOD'] = 'POST'; - $this->assertTrue($this->Controller->Security->__methodsRequired($this->Controller)); - } - - function testRequireGet() - { - $this->Controller->action = 'getted'; - $this->Controller->Security->startup($this->Controller); - $this->Controller->Security->requireGet('getted'); - $this->assertNull($this->Controller->Security->__methodsRequired($this->Controller)); + + function testRequirePostFail() { $_SERVER['REQUEST_METHOD'] = 'GET'; - $this->assertTrue($this->Controller->Security->__methodsRequired($this->Controller)); + $this->Controller->action = 'posted'; + $this->Controller->Security->requirePost('posted'); + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); } - - function testRequirePut() - { + + function testRequirePostSucceed() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'posted'; + $this->Controller->Security->requirePost('posted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + + function testRequirePostSucceedWrongMethod() { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $this->Controller->action = 'getted'; + $this->Controller->Security->requirePost('posted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + + function testRequireGetFail() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'getted'; + $this->Controller->Security->requireGet('getted'); + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + } + + function testRequireGetSucceed() { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $this->Controller->action = 'getted'; + $this->Controller->Security->requireGet('getted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + + function testRequireGetSucceedWrongMethod() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'posted'; + $this->Controller->Security->requireGet('getted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + + function testRequirePutFail() { + $_SERVER['REQUEST_METHOD'] = 'POST'; $this->Controller->action = 'putted'; - $this->Controller->Security->startup($this->Controller); $this->Controller->Security->requirePut('putted'); - $this->assertNull($this->Controller->Security->__methodsRequired($this->Controller)); - $_SERVER['REQUEST_METHOD'] = 'PUT'; - $this->assertTrue($this->Controller->Security->__methodsRequired($this->Controller)); - } - - function testRequireDelete() - { - $this->Controller->action = 'deleted'; $this->Controller->Security->startup($this->Controller); - $this->Controller->Security->requireDelete('deleted'); - $this->assertNull($this->Controller->Security->__methodsRequired($this->Controller)); - $_SERVER['REQUEST_METHOD'] = 'DELETE'; - $this->assertTrue($this->Controller->Security->__methodsRequired($this->Controller)); + $this->assertTrue($this->Controller->failed); } - + + function testRequirePutSucceed() { + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $this->Controller->action = 'putted'; + $this->Controller->Security->requirePut('putted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + + function testRequirePutSucceedWrongMethod() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'posted'; + $this->Controller->Security->requirePut('putted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + + function testRequireDeleteFail() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'deleted'; + $this->Controller->Security->requireDelete('deleted'); + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + } + + function testRequireDeleteSucceed() { + $_SERVER['REQUEST_METHOD'] = 'DELETE'; + $this->Controller->action = 'deleted'; + $this->Controller->Security->requireDelete('deleted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + + function testRequireDeleteSucceedWrongMethod() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'posted'; + $this->Controller->Security->requireDelete('deleted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + + function testRequireLoginSettings() { + $this->Controller->Security->requireLogin( + 'add', 'edit', + array('type' => 'basic', 'users' => array('admin' => 'password')) + ); + $this->assertEqual($this->Controller->Security->requireLogin, array('add', 'edit')); + $this->assertEqual($this->Controller->Security->loginUsers, array('admin' => 'password')); + } + + function testRequireLoginAllActions() { + $this->Controller->Security->requireLogin( + array('type' => 'basic', 'users' => array('admin' => 'password')) + ); + $this->assertEqual($this->Controller->Security->requireLogin, array('*')); + $this->assertEqual($this->Controller->Security->loginUsers, array('admin' => 'password')); + } + function testValidatePostNoModel() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->params['_Token']['key']; $data['anything'] = 'some_data'; $data['__Token']['key'] = $key; - - $fields = array('anything', - '__Token' => array('key' => $key)); - - $fields = $this->__sortFields($fields); + $fields = $this->__sortFields(array('anything', '__Token' => array('key' => $key))); $fields = urlencode(Security::hash(serialize($fields) . Configure::read('Security.salt'))); $data['__Token']['fields'] = $fields; $this->Controller->data = $data; $result = $this->Controller->Security->__validatePost($this->Controller); $this->assertTrue($result); - $this->assertTrue($this->Controller->data == $data); + $this->assertEqual($this->Controller->data, $data); } function testValidatePostSimple() { @@ -133,9 +207,7 @@ class SecurityComponentTest extends CakeTestCase { $data['Model']['password'] = ''; $data['__Token']['key'] = $key; - $fields = array('Model' => array('username','password'), - '__Token' => array('key' => $key)); - + $fields = array('Model' => array('username','password'), '__Token' => array('key' => $key)); $fields = $this->__sortFields($fields); $fields = urlencode(Security::hash(serialize($fields) . Configure::read('Security.salt'))); @@ -143,11 +215,10 @@ class SecurityComponentTest extends CakeTestCase { $this->Controller->data = $data; $result = $this->Controller->Security->__validatePost($this->Controller); $this->assertTrue($result); - $this->assertTrue($this->Controller->data == $data); + $this->assertEqual($this->Controller->data, $data); } function testValidatePostCheckbox() { - $this->Controller->Security->startup($this->Controller); $key = $this->Controller->params['_Token']['key']; @@ -156,10 +227,11 @@ class SecurityComponentTest extends CakeTestCase { $data['_Model']['valid'] = '0'; $data['__Token']['key'] = $key; - $fields = array('Model' => array('username', 'password', 'valid'), - '_Model' => array('valid' => '0'), - '__Token' => array('key' => $key)); - + $fields = array( + 'Model' => array('username', 'password', 'valid'), + '_Model' => array('valid' => '0'), + '__Token' => array('key' => $key) + ); $fields = $this->__sortFields($fields); $fields = urlencode(Security::hash(serialize($fields) . Configure::read('Security.salt'))); @@ -171,7 +243,7 @@ class SecurityComponentTest extends CakeTestCase { unset($data['_Model']); $data['Model']['valid'] = '0'; - $this->assertTrue($this->Controller->data == $data); + $this->assertEqual($this->Controller->data, $data); } function testValidatePostHidden() { @@ -183,10 +255,11 @@ class SecurityComponentTest extends CakeTestCase { $data['_Model']['hidden'] = '0'; $data['__Token']['key'] = $key; - $fields = array('Model' => array('username', 'password', 'hidden'), - '_Model' => array('hidden' => '0'), - '__Token' => array('key' => $key)); - + $fields = array( + 'Model' => array('username', 'password', 'hidden'), + '_Model' => array('hidden' => '0'), + '__Token' => array('key' => $key) + ); $fields = $this->__sortFields($fields); $fields = urlencode(Security::hash(serialize($fields) . Configure::read('Security.salt'))); @@ -212,17 +285,17 @@ class SecurityComponentTest extends CakeTestCase { $data['_Model3']['valid'] = '0'; $data['__Token']['key'] = $key; - $fields = array('Model' => array('username', 'password', 'valid'), - 'Model2'=> array('valid'), - 'Model3'=> array('valid'), - '_Model2'=> array('valid' => '0'), - '_Model3'=> array('valid' => '0'), - '_Model' => array('valid' => '0'), - '__Token' => array('key' => $key)); + $fields = array( + 'Model' => array('username', 'password', 'valid'), + 'Model2'=> array('valid'), + 'Model3'=> array('valid'), + '_Model2'=> array('valid' => '0'), + '_Model3'=> array('valid' => '0'), + '_Model' => array('valid' => '0'), + '__Token' => array('key' => $key) + ); - $fields = $this->__sortFields($fields); - - $fields = urlencode(Security::hash(serialize($fields) . Configure::read('Security.salt'))); + $fields = urlencode(Security::hash(serialize($this->__sortFields($fields)) . Configure::read('Security.salt'))); $data['__Token']['fields'] = $fields; $this->Controller->data = $data; @@ -236,6 +309,10 @@ class SecurityComponentTest extends CakeTestCase { $this->assertTrue($this->Controller->data == $data); } + function testLoginValidation() { + + } + function testValidateHasManyModel() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->params['_Token']['key']; @@ -275,7 +352,7 @@ class SecurityComponentTest extends CakeTestCase { $data['Model'][1]['valid'] = '0'; $this->assertTrue($this->Controller->data == $data); - } + } function __sortFields($fields) { foreach ($fields as $key => $value) {