diff --git a/lib/Cake/Controller/Component/Auth/BaseAuthenticate.php b/lib/Cake/Controller/Component/Auth/BaseAuthenticate.php index d9dc7248f..827774db2 100644 --- a/lib/Cake/Controller/Component/Auth/BaseAuthenticate.php +++ b/lib/Cake/Controller/Component/Auth/BaseAuthenticate.php @@ -66,19 +66,28 @@ abstract class BaseAuthenticate { /** * Find a user record using the standard options. * - * @param string $username The username/identifier. - * @param string $password The unhashed password. + * The $conditions parameter can be a (string)username or an array containing conditions for Model::find('first'). If + * the password field is not included in the conditions the password will be returned. + * + * @param Mixed $conditions The username/identifier, or an array of find conditions. + * @param Mixed $password The password, only use if passing as $conditions = 'username'. * @return Mixed Either false on failure, or an array of user data. */ - protected function _findUser($username, $password) { + protected function _findUser($conditions, $password = null) { $userModel = $this->settings['userModel']; list($plugin, $model) = pluginSplit($userModel); $fields = $this->settings['fields']; - $conditions = array( - $model . '.' . $fields['username'] => $username, - $model . '.' . $fields['password'] => $this->_password($password), - ); + if (!is_array($conditions)) { + if (!$password) { + return false; + } + $username = $conditions; + $conditions = array( + $model . '.' . $fields['username'] => $username, + $model . '.' . $fields['password'] => $this->_password($password), + ); + } if (!empty($this->settings['scope'])) { $conditions = array_merge($conditions, $this->settings['scope']); } @@ -91,7 +100,12 @@ abstract class BaseAuthenticate { return false; } $user = $result[$model]; - unset($user[$fields['password']]); + if ( + isset($conditions[$model . '.' . $fields['password']]) || + isset($conditions[$fields['password']]) + ) { + unset($user[$fields['password']]); + } unset($result[$model]); return array_merge($user, $result); } diff --git a/lib/Cake/Controller/Component/Auth/BlowfishAuthenticate.php b/lib/Cake/Controller/Component/Auth/BlowfishAuthenticate.php new file mode 100644 index 000000000..d806c6ff6 --- /dev/null +++ b/lib/Cake/Controller/Component/Auth/BlowfishAuthenticate.php @@ -0,0 +1,75 @@ +Auth->authenticate = array( + * 'Blowfish' => array( + * 'scope' => array('User.active' => 1) + * ) + * ) + * }}} + * + * When configuring BlowfishAuthenticate you can pass in settings to which fields, model and additional conditions + * are used. See BlowfishAuthenticate::$settings for more information. + * + * @package Cake.Controller.Component.Auth + * @since CakePHP(tm) v 2.3 + * @see AuthComponent::$authenticate + */ +class BlowfishAuthenticate extends FormAuthenticate { + +/** + * Authenticates the identity contained in a request. Will use the `settings.userModel`, and `settings.fields` + * to find POST data that is used to find a matching record in the`settings.userModel`. Will return false if + * there is no post data, either username or password is missing, or if the scope conditions have not been met. + * + * @param CakeRequest $request The request that contains login information. + * @param CakeResponse $response Unused response object. + * @return mixed False on login failure. An array of User data on success. + */ + public function authenticate(CakeRequest $request, CakeResponse $response) { + $userModel = $this->settings['userModel']; + list($plugin, $model) = pluginSplit($userModel); + + $fields = $this->settings['fields']; + if (!$this->_checkFields($request, $model, $fields)) { + return false; + } + $user = $this->_findUser( + array( + $model . '.' . $fields['username'] => $request->data[$model][$fields['username']], + ) + ); + if (!$user) { + return false; + } + $password = Security::hash( + $request->data[$model][$fields['password']], + 'blowfish', + $user[$fields['password']] + ); + if ($password === $user[$fields['password']]) { + unset($user[$fields['password']]); + return $user; + } + return false; + } +} diff --git a/lib/Cake/Controller/Component/Auth/FormAuthenticate.php b/lib/Cake/Controller/Component/Auth/FormAuthenticate.php index 0a51f527e..c6bc5a8bf 100644 --- a/lib/Cake/Controller/Component/Auth/FormAuthenticate.php +++ b/lib/Cake/Controller/Component/Auth/FormAuthenticate.php @@ -36,6 +36,27 @@ App::uses('BaseAuthenticate', 'Controller/Component/Auth'); */ class FormAuthenticate extends BaseAuthenticate { +/** + * Checks the fields to ensure they are supplied. + * + * @param CakeRequest $request The request that contains login information. + * @param string $model The model used for login verification. + * @param array $fields The fields to be checked. + * @return boolean False if the fields have not been supplied. True if they exist. + */ + protected function _checkFields(CakeRequest $request, $model, $fields) { + if (empty($request->data[$model])) { + return false; + } + if ( + empty($request->data[$model][$fields['username']]) || + empty($request->data[$model][$fields['password']]) + ) { + return false; + } + return true; + } + /** * Authenticates the identity contained in a request. Will use the `settings.userModel`, and `settings.fields` * to find POST data that is used to find a matching record in the `settings.userModel`. Will return false if @@ -50,13 +71,7 @@ class FormAuthenticate extends BaseAuthenticate { list($plugin, $model) = pluginSplit($userModel); $fields = $this->settings['fields']; - if (empty($request->data[$model])) { - return false; - } - if ( - empty($request->data[$model][$fields['username']]) || - empty($request->data[$model][$fields['password']]) - ) { + if (!$this->_checkFields($request, $model, $fields)) { return false; } return $this->_findUser( diff --git a/lib/Cake/Test/Case/Controller/Component/Auth/BlowfishAuthenticateTest.php b/lib/Cake/Test/Case/Controller/Component/Auth/BlowfishAuthenticateTest.php new file mode 100644 index 000000000..59238199e --- /dev/null +++ b/lib/Cake/Test/Case/Controller/Component/Auth/BlowfishAuthenticateTest.php @@ -0,0 +1,206 @@ +Collection = $this->getMock('ComponentCollection'); + $this->auth = new BlowfishAuthenticate($this->Collection, array( + 'fields' => array('username' => 'user', 'password' => 'password'), + 'userModel' => 'User' + )); + $password = Security::hash('password', 'blowfish'); + $User = ClassRegistry::init('User'); + $User->updateAll(array('password' => $User->getDataSource()->value($password))); + $this->response = $this->getMock('CakeResponse'); + } + +/** + * test applying settings in the constructor + * + * @return void + */ + public function testConstructor() { + $Object = new BlowfishAuthenticate($this->Collection, array( + 'userModel' => 'AuthUser', + 'fields' => array('username' => 'user', 'password' => 'password') + )); + $this->assertEquals('AuthUser', $Object->settings['userModel']); + $this->assertEquals(array('username' => 'user', 'password' => 'password'), $Object->settings['fields']); + } + +/** + * testAuthenticateNoData method + * + * @return void + */ + public function testAuthenticateNoData() { + $request = new CakeRequest('posts/index', false); + $request->data = array(); + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + +/** + * testAuthenticateNoUsername method + * + * @return void + */ + public function testAuthenticateNoUsername() { + $request = new CakeRequest('posts/index', false); + $request->data = array('User' => array('password' => 'foobar')); + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + +/** + * testAuthenticateNoPassword method + * + * @return void + */ + public function testAuthenticateNoPassword() { + $request = new CakeRequest('posts/index', false); + $request->data = array('User' => array('user' => 'mariano')); + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + +/** + * testAuthenticatePasswordIsFalse method + * + * @return void + */ + public function testAuthenticatePasswordIsFalse() { + $request = new CakeRequest('posts/index', false); + $request->data = array( + 'User' => array( + 'user' => 'mariano', + 'password' => null + )); + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + +/** + * testAuthenticateInjection method + * + * @return void + */ + public function testAuthenticateInjection() { + $request = new CakeRequest('posts/index', false); + $request->data = array('User' => array( + 'user' => '> 1', + 'password' => "' OR 1 = 1" + )); + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + +/** + * testAuthenticateSuccess method + * + * @return void + */ + public function testAuthenticateSuccess() { + $request = new CakeRequest('posts/index', false); + $request->data = array('User' => array( + 'user' => 'mariano', + 'password' => 'password' + )); + $result = $this->auth->authenticate($request, $this->response); + $expected = array( + 'id' => 1, + 'user' => 'mariano', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31', + ); + $this->assertEquals($expected, $result); + } + +/** + * testAuthenticateScopeFail method + * + * @return void + */ + public function testAuthenticateScopeFail() { + $this->auth->settings['scope'] = array('user' => 'nate'); + $request = new CakeRequest('posts/index', false); + $request->data = array('User' => array( + 'user' => 'mariano', + 'password' => 'password' + )); + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + +/** + * testPluginModel method + * + * @return void + */ + public function testPluginModel() { + Cache::delete('object_map', '_cake_core_'); + App::build(array( + 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) + ), App::RESET); + CakePlugin::load('TestPlugin'); + + $PluginModel = ClassRegistry::init('TestPlugin.TestPluginAuthUser'); + $user['id'] = 1; + $user['username'] = 'gwoo'; + $user['password'] = Security::hash('password', 'blowfish'); + $PluginModel->save($user, false); + + $this->auth->settings['userModel'] = 'TestPlugin.TestPluginAuthUser'; + $this->auth->settings['fields']['username'] = 'username'; + + $request = new CakeRequest('posts/index', false); + $request->data = array('TestPluginAuthUser' => array( + 'username' => 'gwoo', + 'password' => 'password' + )); + + $result = $this->auth->authenticate($request, $this->response); + $expected = array( + 'id' => 1, + 'username' => 'gwoo', + 'created' => '2007-03-17 01:16:23' + ); + $this->assertEquals(self::date(), $result['updated']); + unset($result['updated']); + $this->assertEquals($expected, $result); + CakePlugin::unload(); + } +} diff --git a/lib/Cake/Test/Case/Controller/Component/Auth/FormAuthenticateTest.php b/lib/Cake/Test/Case/Controller/Component/Auth/FormAuthenticateTest.php index a65e04f28..4d2470a3b 100644 --- a/lib/Cake/Test/Case/Controller/Component/Auth/FormAuthenticateTest.php +++ b/lib/Cake/Test/Case/Controller/Component/Auth/FormAuthenticateTest.php @@ -99,6 +99,21 @@ class FormAuthenticateTest extends CakeTestCase { $this->assertFalse($this->auth->authenticate($request, $this->response)); } +/** + * test authenticate password is false method + * + * @return void + */ + public function testAuthenticatePasswordIsFalse() { + $request = new CakeRequest('posts/index', false); + $request->data = array( + 'User' => array( + 'user' => 'mariano', + 'password' => null + )); + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + /** * test the authenticate method *