* Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) * * Licensed under The MIT License * Redistributions of files must retain the above copyright notice * * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests * @package cake * @subpackage cake.tests.cases.libs.controller.components * @since CakePHP(tm) v 1.2.0.5435 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Controller', 'Controller', false); App::import('Component', 'Security'); /** * TestSecurityComponent * * @package cake * @subpackage cake.tests.cases.libs.controller.components */ class TestSecurityComponent extends SecurityComponent { /** * validatePost method * * @param Controller $controller * @return unknown */ function validatePost($controller) { return $this->_validatePost($controller); } } /** * SecurityTestController * * @package cake * @subpackage cake.tests.cases.libs.controller.components */ class SecurityTestController extends Controller { /** * name property * * @var string 'SecurityTest' * @access public */ public $name = 'SecurityTest'; /** * components property * * @var array * @access public */ public $components = array('Session', 'TestSecurity'); /** * failed property * * @var bool false * @access public */ public $failed = false; /** * Used for keeping track of headers in test * * @var array * @access public */ public $testHeaders = array(); /** * fail method * * @access public * @return void */ function fail() { $this->failed = true; } /** * redirect method * * @param mixed $option * @param mixed $code * @param mixed $exit * @access public * @return void */ function redirect($url, $status = null, $exit = true) { return $status; } /** * Conveinence method for header() * * @param string $status * @return void */ public function header($status) { $this->testHeaders[] = $status; } } /** * SecurityComponentTest class * * @package cake * @subpackage cake.tests.cases.libs.controller.components */ class SecurityComponentTest extends CakeTestCase { /** * Controller property * * @var SecurityTestController * @access public */ public $Controller; /** * oldSalt property * * @var string * @access public */ public $oldSalt; /** * setUp method * * @access public * @return void */ function setUp() { parent::setUp(); $request = new CakeRequest('posts/index', false); $request->addParams(array('controller' => 'posts', 'action' => 'index')); $this->Controller = new SecurityTestController($request); $this->Controller->Components->init($this->Controller); $this->Controller->Security = $this->Controller->TestSecurity; $this->Controller->Security->blackHoleCallback = 'fail'; $this->Security = $this->Controller->Security; $this->Security->csrfCheck = false; Configure::write('Security.salt', 'foo!'); } /** * Tear-down method. Resets environment state. * * @return void */ function tearDown() { parent::tearDown(); $this->Controller->Session->delete('_Token'); unset($this->Controller->Security); unset($this->Controller->Component); unset($this->Controller); } /** * test that initalize can set properties. * * @return void */ function testConstructorSettingProperties() { $settings = array( 'requirePost' => array('edit', 'update'), 'requireSecure' => array('update_account'), 'requireGet' => array('index'), 'validatePost' => false, 'loginUsers' => array( 'mark' => 'password' ), 'requireLogin' => array('login'), ); $Security = new SecurityComponent($this->Controller->Components, $settings); $this->Controller->Security->initialize($this->Controller, $settings); $this->assertEqual($Security->requirePost, $settings['requirePost']); $this->assertEqual($Security->requireSecure, $settings['requireSecure']); $this->assertEqual($Security->requireGet, $settings['requireGet']); $this->assertEqual($Security->validatePost, $settings['validatePost']); $this->assertEqual($Security->loginUsers, $settings['loginUsers']); $this->assertEqual($Security->requireLogin, $settings['requireLogin']); } /** * testStartup method * * @access public * @return void */ function testStartup() { $this->Controller->Security->startup($this->Controller); $result = $this->Controller->params['_Token']['key']; $this->assertNotNull($result); $this->assertTrue($this->Controller->Session->check('_Token')); } /** * testRequirePostFail method * * @access public * @return void */ function testRequirePostFail() { $_SERVER['REQUEST_METHOD'] = 'GET'; $this->Controller->request['action'] = 'posted'; $this->Controller->Security->requirePost(array('posted')); $this->Controller->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed); } /** * testRequirePostSucceed method * * @access public * @return void */ function testRequirePostSucceed() { $_SERVER['REQUEST_METHOD'] = 'POST'; $this->Controller->request['action'] = 'posted'; $this->Controller->Security->requirePost('posted'); $this->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); } /** * testRequireSecureFail method * * @access public * @return void */ function testRequireSecureFail() { $_SERVER['HTTPS'] = 'off'; $_SERVER['REQUEST_METHOD'] = 'POST'; $this->Controller->request['action'] = 'posted'; $this->Controller->Security->requireSecure(array('posted')); $this->Controller->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed); } /** * testRequireSecureSucceed method * * @access public * @return void */ function testRequireSecureSucceed() { $_SERVER['REQUEST_METHOD'] = 'Secure'; $this->Controller->request['action'] = 'posted'; $_SERVER['HTTPS'] = 'on'; $this->Controller->Security->requireSecure('posted'); $this->Controller->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); } /** * testRequireAuthFail method * * @access public * @return void */ function testRequireAuthFail() { $_SERVER['REQUEST_METHOD'] = 'AUTH'; $this->Controller->request['action'] = 'posted'; $this->Controller->request->data = array('username' => 'willy', 'password' => 'somePass'); $this->Controller->Security->requireAuth(array('posted')); $this->Controller->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed); $this->Controller->Session->write('_Token', array('allowedControllers' => array())); $this->Controller->request->data = array('username' => 'willy', 'password' => 'somePass'); $this->Controller->request['action'] = 'posted'; $this->Controller->Security->requireAuth('posted'); $this->Controller->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed); $this->Controller->Session->write('_Token', array( 'allowedControllers' => array('SecurityTest'), 'allowedActions' => array('posted2') )); $this->Controller->request->data = array('username' => 'willy', 'password' => 'somePass'); $this->Controller->request['action'] = 'posted'; $this->Controller->Security->requireAuth('posted'); $this->Controller->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed); } /** * testRequireAuthSucceed method * * @access public * @return void */ function testRequireAuthSucceed() { $_SERVER['REQUEST_METHOD'] = 'AUTH'; $this->Controller->request['action'] = 'posted'; $this->Controller->Security->requireAuth('posted'); $this->Controller->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); $this->Controller->Security->Session->write('_Token', array( 'allowedControllers' => array('SecurityTest'), 'allowedActions' => array('posted') )); $this->Controller->request['controller'] = 'SecurityTest'; $this->Controller->request['action'] = 'posted'; $this->Controller->request->data = array( 'username' => 'willy', 'password' => 'somePass', '_Token' => '' ); $this->Controller->action = 'posted'; $this->Controller->Security->requireAuth('posted'); $this->Controller->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); } /** * testRequirePostSucceedWrongMethod method * * @access public * @return void */ function testRequirePostSucceedWrongMethod() { $_SERVER['REQUEST_METHOD'] = 'GET'; $this->Controller->request['action'] = 'getted'; $this->Controller->Security->requirePost('posted'); $this->Controller->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); } /** * testRequireGetFail method * * @access public * @return void */ function testRequireGetFail() { $_SERVER['REQUEST_METHOD'] = 'POST'; $this->Controller->request['action'] = 'getted'; $this->Controller->Security->requireGet(array('getted')); $this->Controller->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed); } /** * testRequireGetSucceed method * * @access public * @return void */ function testRequireGetSucceed() { $_SERVER['REQUEST_METHOD'] = 'GET'; $this->Controller->request['action'] = 'getted'; $this->Controller->Security->requireGet('getted'); $this->Controller->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); } /** * testRequireLogin method * * @access public * @return void */ function testRequireLogin() { $this->Controller->request['action'] = 'posted'; $this->Controller->Security->requireLogin( 'posted', array('type' => 'basic', 'users' => array('admin' => 'password')) ); $_SERVER['PHP_AUTH_USER'] = 'admin'; $_SERVER['PHP_AUTH_PW'] = 'password'; $this->Controller->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); $this->Controller->request['action'] = 'posted'; $this->Controller->Security->requireLogin( array('posted'), array('type' => 'basic', 'users' => array('admin' => 'password')) ); $_SERVER['PHP_AUTH_USER'] = 'admin2'; $_SERVER['PHP_AUTH_PW'] = 'password'; $this->Controller->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed); $this->Controller->request['action'] = 'posted'; $this->Controller->Security->requireLogin( 'posted', array('type' => 'basic', 'users' => array('admin' => 'password')) ); $_SERVER['PHP_AUTH_USER'] = 'admin'; $_SERVER['PHP_AUTH_PW'] = 'password2'; $this->Controller->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed); } /** * testDigestAuth method * * @access public * @return void */ function testDigestAuth() { $skip = $this->skipIf((version_compare(PHP_VERSION, '5.1') == -1) XOR (!function_exists('apache_request_headers')), "%s Cannot run Digest Auth test for PHP versions < 5.1" ); if ($skip) { return; } $this->Controller->request['action'] = 'posted'; $_SERVER['PHP_AUTH_DIGEST'] = $digest = <<Controller->Security->requireLogin('posted', array( 'type' => 'digest', 'users' => array('Mufasa' => 'password'), 'realm' => 'testrealm@host.com' )); $this->Controller->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); } /** * testRequireGetSucceedWrongMethod method * * @access public * @return void */ function testRequireGetSucceedWrongMethod() { $_SERVER['REQUEST_METHOD'] = 'POST'; $this->Controller->request['action'] = 'posted'; $this->Security->requireGet('getted'); $this->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); } /** * testRequirePutFail method * * @access public * @return void */ function testRequirePutFail() { $_SERVER['REQUEST_METHOD'] = 'POST'; $this->Controller->request['action'] = 'putted'; $this->Controller->Security->requirePut(array('putted')); $this->Controller->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed); } /** * testRequirePutSucceed method * * @access public * @return void */ function testRequirePutSucceed() { $_SERVER['REQUEST_METHOD'] = 'PUT'; $this->Controller->request['action'] = 'putted'; $this->Controller->Security->requirePut('putted'); $this->Controller->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); } /** * testRequirePutSucceedWrongMethod method * * @access public * @return void */ function testRequirePutSucceedWrongMethod() { $_SERVER['REQUEST_METHOD'] = 'POST'; $this->Controller->request['action'] = 'posted'; $this->Controller->Security->requirePut('putted'); $this->Controller->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); } /** * testRequireDeleteFail method * * @access public * @return void */ function testRequireDeleteFail() { $_SERVER['REQUEST_METHOD'] = 'POST'; $this->Controller->request['action'] = 'deleted'; $this->Controller->Security->requireDelete(array('deleted', 'other_method')); $this->Controller->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed); } /** * testRequireDeleteSucceed method * * @access public * @return void */ function testRequireDeleteSucceed() { $_SERVER['REQUEST_METHOD'] = 'DELETE'; $this->Controller->request['action'] = 'deleted'; $this->Controller->Security->requireDelete('deleted'); $this->Controller->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); } /** * testRequireDeleteSucceedWrongMethod method * * @access public * @return void */ function testRequireDeleteSucceedWrongMethod() { $_SERVER['REQUEST_METHOD'] = 'POST'; $this->Controller->request['action'] = 'posted'; $this->Controller->Security->requireDelete('deleted'); $this->Controller->Security->startup($this->Controller); $this->assertFalse($this->Controller->failed); } /** * testRequireLoginSettings method * * @access public * @return void */ 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')); } /** * testRequireLoginAllActions method * * @access public * @return void */ 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')); } /** * Simple hash validation test * * @access public * @return void */ function testValidatePost() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid'; $this->Controller->request->data = array( 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'), '_Token' => compact('key', 'fields') ); $this->assertTrue($this->Controller->Security->validatePost($this->Controller)); } /** * test that validatePost fails if any of its required fields are missing. * * @return void */ function testValidatePostFormHacking() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->params['_Token']['key']; $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid'; $this->Controller->request->data = array( 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'), '_Token' => compact('key') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertFalse($result, 'validatePost passed when fields were missing. %s'); } /** * Test that objects can't be passed into the serialized string. This was a vector for RFI and LFI * attacks. Thanks to Felix Wilhelm * * @return void */ function testValidatePostObjectDeserialize() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877'; // a corrupted serialized object, so we can see if it ever gets to deserialize $attack = 'O:3:"App":1:{s:5:"__map";a:1:{s:3:"foo";s:7:"Hacked!";s:1:"fail"}}'; $fields .= urlencode(':' . str_rot13($attack)); $this->Controller->request->data = array( 'Model' => array('username' => 'mark', 'password' => 'foo', 'valid' => '0'), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertFalse($result, 'validatePost passed when key was missing. %s'); } /** * Tests validation of checkbox arrays * * @access public * @return void */ function testValidatePostArray() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = 'f7d573650a295b94e0938d32b323fde775e5f32b%3A'; $this->Controller->request->data = array( 'Model' => array('multi_field' => array('1', '3')), '_Token' => compact('key', 'fields') ); $this->assertTrue($this->Controller->Security->validatePost($this->Controller)); } /** * testValidatePostNoModel method * * @access public * @return void */ function testValidatePostNoModel() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = '540ac9c60d323c22bafe997b72c0790f39a8bdef%3A'; $this->Controller->request->data = array( 'anything' => 'some_data', '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * testValidatePostSimple method * * @access public * @return void */ function testValidatePostSimple() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = '69f493434187b867ea14b901fdf58b55d27c935d%3A'; $this->Controller->request->data = $data = array( 'Model' => array('username' => '', 'password' => ''), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * Tests hash validation for multiple records, including locked fields * * @access public * @return void */ function testValidatePostComplex() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = 'c9118120e680a7201b543f562e5301006ccfcbe2%3AAddresses.0.id%7CAddresses.1.id'; $this->Controller->request->data = array( 'Addresses' => array( '0' => array( 'id' => '123456', 'title' => '', 'first_name' => '', 'last_name' => '', 'address' => '', 'city' => '', 'phone' => '', 'primary' => '' ), '1' => array( 'id' => '654321', 'title' => '', 'first_name' => '', 'last_name' => '', 'address' => '', 'city' => '', 'phone' => '', 'primary' => '' ) ), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * test ValidatePost with multiple select elements. * * @return void */ function testValidatePostMultipleSelect() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = '422cde416475abc171568be690a98cad20e66079%3A'; $this->Controller->request->data = array( 'Tag' => array('Tag' => array(1, 2)), '_Token' => compact('key', 'fields'), ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); $this->Controller->request->data = array( 'Tag' => array('Tag' => array(1, 2, 3)), '_Token' => compact('key', 'fields'), ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); $this->Controller->request->data = array( 'Tag' => array('Tag' => array(1, 2, 3, 4)), '_Token' => compact('key', 'fields'), ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); $fields = '19464422eafe977ee729c59222af07f983010c5f%3A'; $this->Controller->request->data = array( 'User.password' => 'bar', 'User.name' => 'foo', 'User.is_valid' => '1', 'Tag' => array('Tag' => array(1)), '_Token' => compact('key', 'fields'), ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * testValidatePostCheckbox method * * First block tests un-checked checkbox * Second block tests checked checkbox * * @access public * @return void */ function testValidatePostCheckbox() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid'; $this->Controller->request->data = array( 'Model' => array('username' => '', 'password' => '', 'valid' => '0'), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); $fields = '874439ca69f89b4c4a5f50fb9c36ff56a28f5d42%3A'; $this->Controller->request->data = array( 'Model' => array('username' => '', 'password' => '', 'valid' => '0'), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); $this->Controller->request->data = array(); $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $this->Controller->request->data = $data = array( 'Model' => array('username' => '', 'password' => '', 'valid' => '0'), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * testValidatePostHidden method * * @access public * @return void */ function testValidatePostHidden() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = '51ccd8cb0997c7b3d4523ecde5a109318405ef8c%3AModel.hidden%7CModel.other_hidden'; $fields .= ''; $this->Controller->request->data = array( 'Model' => array( 'username' => '', 'password' => '', 'hidden' => '0', 'other_hidden' => 'some hidden value' ), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * testValidatePostWithDisabledFields method * * @access public * @return void */ function testValidatePostWithDisabledFields() { $this->Controller->Security->disabledFields = array('Model.username', 'Model.password'); $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = 'ef1082968c449397bcd849f963636864383278b1%3AModel.hidden'; $this->Controller->request->data = array( 'Model' => array( 'username' => '', 'password' => '', 'hidden' => '0' ), '_Token' => compact('fields', 'key') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * testValidateHiddenMultipleModel method * * @access public * @return void */ function testValidateHiddenMultipleModel() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = 'a2d01072dc4660eea9d15007025f35a7a5b58e18%3AModel.valid%7CModel2.valid%7CModel3.valid'; $this->Controller->request->data = array( 'Model' => array('username' => '', 'password' => '', 'valid' => '0'), 'Model2' => array('valid' => '0'), 'Model3' => array('valid' => '0'), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * testValidateHasManyModel method * * @access public * @return void */ function testValidateHasManyModel() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = '51e3b55a6edd82020b3f29c9ae200e14bbeb7ee5%3AModel.0.hidden%7CModel.0.valid'; $fields .= '%7CModel.1.hidden%7CModel.1.valid'; $this->Controller->request->data = array( 'Model' => array( array( 'username' => 'username', 'password' => 'password', 'hidden' => 'value', 'valid' => '0' ), array( 'username' => 'username', 'password' => 'password', 'hidden' => 'value', 'valid' => '0' ) ), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * testValidateHasManyRecordsPass method * * @access public * @return void */ function testValidateHasManyRecordsPass() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = '7a203edb3d345bbf38fe0dccae960da8842e11d7%3AAddress.0.id%7CAddress.0.primary%7C'; $fields .= 'Address.1.id%7CAddress.1.primary'; $this->Controller->request->data = array( 'Address' => array( 0 => array( 'id' => '123', 'title' => 'home', 'first_name' => 'Bilbo', 'last_name' => 'Baggins', 'address' => '23 Bag end way', 'city' => 'the shire', 'phone' => 'N/A', 'primary' => '1', ), 1 => array( 'id' => '124', 'title' => 'home', 'first_name' => 'Frodo', 'last_name' => 'Baggins', 'address' => '50 Bag end way', 'city' => 'the shire', 'phone' => 'N/A', 'primary' => '1' ) ), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * testValidateHasManyRecords method * * validatePost should fail, hidden fields have been changed. * * @access public * @return void */ function testValidateHasManyRecordsFail() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = '7a203edb3d345bbf38fe0dccae960da8842e11d7%3AAddress.0.id%7CAddress.0.primary%7C'; $fields .= 'Address.1.id%7CAddress.1.primary'; $this->Controller->request->data = array( 'Address' => array( 0 => array( 'id' => '123', 'title' => 'home', 'first_name' => 'Bilbo', 'last_name' => 'Baggins', 'address' => '23 Bag end way', 'city' => 'the shire', 'phone' => 'N/A', 'primary' => '5', ), 1 => array( 'id' => '124', 'title' => 'home', 'first_name' => 'Frodo', 'last_name' => 'Baggins', 'address' => '50 Bag end way', 'city' => 'the shire', 'phone' => 'N/A', 'primary' => '1' ) ), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertFalse($result); } /** * testLoginRequest method * * @access public * @return void */ function testLoginRequest() { $this->Controller->Security->startup($this->Controller); $realm = 'cakephp.org'; $options = array('realm' => $realm, 'type' => 'basic'); $result = $this->Controller->Security->loginRequest($options); $expected = 'WWW-Authenticate: Basic realm="'.$realm.'"'; $this->assertEqual($result, $expected); $this->Controller->Security->startup($this->Controller); $options = array('realm' => $realm, 'type' => 'digest'); $result = $this->Controller->Security->loginRequest($options); $this->assertPattern('/realm="'.$realm.'"/', $result); $this->assertPattern('/qop="auth"/', $result); } /** * testGenerateDigestResponseHash method * * @access public * @return void */ function testGenerateDigestResponseHash() { $this->Controller->Security->startup($this->Controller); $realm = 'cakephp.org'; $loginData = array('realm' => $realm, 'users' => array('Willy Smith' => 'password')); $this->Controller->Security->requireLogin($loginData); $data = array( 'username' => 'Willy Smith', 'password' => 'password', 'nonce' => String::uuid(), 'nc' => 1, 'cnonce' => 1, 'realm' => $realm, 'uri' => 'path_to_identifier', 'qop' => 'testme' ); $_SERVER['REQUEST_METHOD'] = 'POST'; $result = $this->Controller->Security->generateDigestResponseHash($data); $expected = md5( md5($data['username'] . ':' . $loginData['realm'] . ':' . $data['password']) . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . md5(env('REQUEST_METHOD') . ':' . $data['uri']) ); $this->assertIdentical($result, $expected); } /** * testLoginCredentials method * * @access public * @return void */ function testLoginCredentials() { $this->Controller->Security->startup($this->Controller); $_SERVER['PHP_AUTH_USER'] = $user = 'Willy Test'; $_SERVER['PHP_AUTH_PW'] = $pw = 'some password for the nice test'; $result = $this->Controller->Security->loginCredentials('basic'); $expected = array('username' => $user, 'password' => $pw); $this->assertIdentical($result, $expected); if (version_compare(PHP_VERSION, '5.1') != -1) { $_SERVER['PHP_AUTH_DIGEST'] = $digest = << 'Mufasa', 'realm' => 'testrealm@host.com', 'nonce' => 'dcd98b7102dd2f0e8b11d0f600bfb0c093', 'uri' => '/dir/index.html', 'qop' => 'auth', 'nc' => '00000001', 'cnonce' => '0a4f113b', 'response' => '6629fae49393a05397450978507c4ef1', 'opaque' => '5ccc069c403ebaf9f0171e9517f40e41' ); $result = $this->Controller->Security->loginCredentials('digest'); $this->assertIdentical($result, $expected); } } /** * testParseDigestAuthData method * * @access public * @return void */ function testParseDigestAuthData() { $this->Controller->Security->startup($this->Controller); $digest = << 'Mufasa', 'realm' => 'testrealm@host.com', 'nonce' => 'dcd98b7102dd2f0e8b11d0f600bfb0c093', 'uri' => '/dir/index.html', 'qop' => 'auth', 'nc' => '00000001', 'cnonce' => '0a4f113b', 'response' => '6629fae49393a05397450978507c4ef1', 'opaque' => '5ccc069c403ebaf9f0171e9517f40e41' ); $result = $this->Controller->Security->parseDigestAuthData($digest); $this->assertIdentical($result, $expected); $result = $this->Controller->Security->parseDigestAuthData(''); $this->assertNull($result); } /** * test parsing digest information with email addresses * * @return void */ function testParseDigestAuthEmailAddress() { $this->Controller->Security->startup($this->Controller); $digest = << 'mark@example.com', 'realm' => 'testrealm@host.com', 'nonce' => 'dcd98b7102dd2f0e8b11d0f600bfb0c093', 'uri' => '/dir/index.html', 'qop' => 'auth', 'nc' => '00000001', 'cnonce' => '0a4f113b', 'response' => '6629fae49393a05397450978507c4ef1', 'opaque' => '5ccc069c403ebaf9f0171e9517f40e41' ); $result = $this->Controller->Security->parseDigestAuthData($digest); $this->assertIdentical($result, $expected); } /** * testFormDisabledFields method * * @access public * @return void */ function testFormDisabledFields() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = '11842060341b9d0fc3808b90ba29fdea7054d6ad%3An%3A0%3A%7B%7D'; $this->Controller->request->data = array( 'MyModel' => array('name' => 'some data'), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertFalse($result); $this->Controller->Security->startup($this->Controller); $this->Controller->Security->disabledFields = array('MyModel.name'); $key = $this->Controller->request->params['_Token']['key']; $this->Controller->request->data = array( 'MyModel' => array('name' => 'some data'), '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * testRadio method * * @access public * @return void */ function testRadio() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $fields = '575ef54ca4fc8cab468d6d898e9acd3a9671c17e%3An%3A0%3A%7B%7D'; $this->Controller->request->data = array( '_Token' => compact('key', 'fields') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertFalse($result); $this->Controller->request->data = array( '_Token' => compact('key', 'fields'), 'Test' => array('test' => '') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); $this->Controller->request->data = array( '_Token' => compact('key', 'fields'), 'Test' => array('test' => '1') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); $this->Controller->request->data = array( '_Token' => compact('key', 'fields'), 'Test' => array('test' => '2') ); $result = $this->Controller->Security->validatePost($this->Controller); $this->assertTrue($result); } /** * testInvalidAuthHeaders method * * @access public * @return void */ function testInvalidAuthHeaders() { $this->Controller->Security->blackHoleCallback = null; $_SERVER['PHP_AUTH_USER'] = 'admin'; $_SERVER['PHP_AUTH_PW'] = 'password'; $realm = 'cakephp.org'; $loginData = array('type' => 'basic', 'realm' => $realm); $this->Controller->Security->requireLogin($loginData); $this->Controller->Security->startup($this->Controller); $expected = 'WWW-Authenticate: Basic realm="'.$realm.'"'; $this->assertEqual(count($this->Controller->testHeaders), 1); $this->assertEqual(current($this->Controller->testHeaders), $expected); } /** * test that a requestAction's controller will have the _Token appended to * the params. * * @return void * @see http://cakephp.lighthouseapp.com/projects/42648/tickets/68 */ function testSettingTokenForRequestAction() { $this->Controller->Security->startup($this->Controller); $key = $this->Controller->request->params['_Token']['key']; $this->Controller->params['requested'] = 1; unset($this->Controller->request->params['_Token']); $this->Controller->Security->startup($this->Controller); $this->assertEqual($this->Controller->request->params['_Token']['key'], $key); } /** * test that blackhole doesn't delete the _Token session key so repeat data submissions * stay blackholed. * * @link http://cakephp.lighthouseapp.com/projects/42648/tickets/214 * @return void */ function testBlackHoleNotDeletingSessionInformation() { $this->Controller->Security->startup($this->Controller); $this->Controller->Security->blackHole($this->Controller, 'auth'); $this->assertTrue($this->Controller->Security->Session->check('_Token'), '_Token was deleted by blackHole %s'); } /** * test setting * * @return void */ function testCsrfSettings() { $this->Security->validatePost = false; $this->Security->csrfCheck = true; $this->Security->csrfExpires = '+10 minutes'; $this->Security->startup($this->Controller); $token = $this->Security->Session->read('_Token'); $this->assertEquals(count($token['csrfTokens']), 1, 'Missing the csrf token.'); $this->assertEquals(strtotime('+10 minutes'), current($token['csrfTokens']), 'Token expiry does not match'); } /** * Test setting multiple nonces, when startup() is called more than once, (ie more than one request.) * * @return void */ function testCsrfSettingMultipleNonces() { $this->Security->validatePost = false; $this->Security->csrfCheck = true; $this->Security->csrfExpires = '+10 minutes'; $this->Security->startup($this->Controller); $this->Security->startup($this->Controller); $token = $this->Security->Session->read('_Token'); $this->assertEquals(count($token['csrfTokens']), 2, 'Missing the csrf token.'); foreach ($token['csrfTokens'] as $key => $expires) { $this->assertEquals(strtotime('+10 minutes'), $expires, 'Token expiry does not match'); } } /** * test that nonces are consumed by form submits. * * @return void */ function testCsrfNonceConsumption() { $this->Security->validatePost = false; $this->Security->csrfCheck = true; $this->Security->csrfExpires = '+10 minutes'; $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes'))); $this->Controller->request = $this->getMock('CakeRequest', array('is')); $this->Controller->request->expects($this->once())->method('is') ->with('post') ->will($this->returnValue(true)); $this->Controller->request->params['action'] = 'index'; $this->Controller->request->data = array( '_Token' => array( 'key' => 'nonce1' ), 'Post' => array( 'title' => 'Woot' ) ); $this->Security->startup($this->Controller); $token = $this->Security->Session->read('_Token'); $this->assertFalse(isset($token['csrfTokens']['nonce1']), 'Token was not consumed'); } /** * test that expired values in the csrfTokens are cleaned up. * * @return void */ function testCsrfNonceVacuum() { $this->Security->validatePost = false; $this->Security->csrfCheck = true; $this->Security->csrfExpires = '+10 minutes'; $this->Security->Session->write('_Token.csrfTokens', array( 'valid' => strtotime('+30 minutes'), 'poof' => strtotime('-11 minutes'), 'dust' => strtotime('-20 minutes') )); $this->Security->startup($this->Controller); $tokens = $this->Security->Session->read('_Token.csrfTokens'); $this->assertEquals(2, count($tokens), 'Too many tokens left behind'); $this->assertNotEmpty('valid', $tokens, 'Valid token was removed.'); } /** * test that when the key is missing the request is blackHoled * * @return void */ function testCsrfBlackHoleOnKeyMismatch() { $this->Security->validatePost = false; $this->Security->csrfCheck = true; $this->Security->csrfExpires = '+10 minutes'; $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes'))); $this->Controller->request = $this->getMock('CakeRequest', array('is')); $this->Controller->request->expects($this->once())->method('is') ->with('post') ->will($this->returnValue(true)); $this->Controller->request->params['action'] = 'index'; $this->Controller->request->data = array( '_Token' => array( 'key' => 'not the right value' ), 'Post' => array( 'title' => 'Woot' ) ); $this->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed, 'fail() was not called.'); } /** * test that when the key is missing the request is blackHoled * * @return void */ function testCsrfBlackHoleOnExpiredKey() { $this->Security->validatePost = false; $this->Security->csrfCheck = true; $this->Security->csrfExpires = '+10 minutes'; $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('-5 minutes'))); $this->Controller->request = $this->getMock('CakeRequest', array('is')); $this->Controller->request->expects($this->once())->method('is') ->with('post') ->will($this->returnValue(true)); $this->Controller->request->params['action'] = 'index'; $this->Controller->request->data = array( '_Token' => array( 'key' => 'nonce1' ), 'Post' => array( 'title' => 'Woot' ) ); $this->Security->startup($this->Controller); $this->assertTrue($this->Controller->failed, 'fail() was not called.'); } /** * test that csrfUseOnce = false works. * * @return void */ function testCsrfNotUseOnce() { $this->Security->validatePost = false; $this->Security->csrfCheck = true; $this->Security->csrfUseOnce = false; $this->Security->csrfExpires = '+10 minutes'; // Generate one token $this->Security->startup($this->Controller); $token = $this->Security->Session->read('_Token.csrfTokens'); $this->assertEquals(1, count($token), 'Should only be one token.'); $this->Security->startup($this->Controller); $token2 = $this->Security->Session->read('_Token.csrfTokens'); $this->assertEquals(1, count($token2), 'Should only be one token.'); $this->assertEquals($token, $token2, 'Tokens should not be different.'); } /** * ensure that longer session tokens are not consumed * * @return void */ function testCsrfNotUseOnceValidationLeavingToken() { $this->Security->validatePost = false; $this->Security->csrfCheck = true; $this->Security->csrfUseOnce = false; $this->Security->csrfExpires = '+10 minutes'; $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes'))); $this->Controller->request = $this->getMock('CakeRequest', array('is')); $this->Controller->request->expects($this->once())->method('is') ->with('post') ->will($this->returnValue(true)); $this->Controller->request->params['action'] = 'index'; $this->Controller->request->data = array( '_Token' => array( 'key' => 'nonce1' ), 'Post' => array( 'title' => 'Woot' ) ); $this->Security->startup($this->Controller); $token = $this->Security->Session->read('_Token'); $this->assertTrue(isset($token['csrfTokens']['nonce1']), 'Token was consumed'); } }