Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DirectAuthenticate adapter #949

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions lib/Cake/Controller/Component/Auth/DirectAuthenticate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php
App::uses('BaseAuthenticate', 'Controller/Component/Auth');

/**
* An authentication adapter for AuthComponent to directly log in a user by username, id or
* any other distinct identification.
*
* Inside a controller(/component):
*
* $this->request->data = array('User' => array('id' => $userId));
* $this->Auth->authenticate = array('Direct' => array('contain' => array('Role.id'), 'fields'=>array('username' => 'id')));
* $result = $this->Auth->login();
*
* This has several advantages over using Auth->login($data) directly:
* - You keep it dry, especially when using contain ($data would have to have the exact same data).
* - No overhead - retrieving the data prior to the login is not necessary. It's short and easy.
* - You keep it centralized, only one single mechanism to login (using your Authentication adapters
* and its common _findUser() method). It also respects the scope and contain settings specified
* in your AppController just as any other adapter.
*
*/
class DirectAuthenticate extends BaseAuthenticate {

/**
* 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, username is missing, of 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;
}
$conditions = array(
$model . '.' . $fields['username'] => $request->data[$model][$fields['username']]
);
return $this->_findUser($conditions);
}

/**
* 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']])) {
return false;
}
return true;
}

/**
* Find a user record using the standard options.
*
* The $conditions parameter can be a (string)username or an array containing conditions for Model::find('first').
*
* @param array $conditions An array of find conditions.
* @return Mixed Either false on failure, or an array of user data.
*/
protected function _findUser($conditions, $password = null) {
$userModel = $this->settings['userModel'];
list($plugin, $model) = pluginSplit($userModel);
$fields = $this->settings['fields'];

$user = parent::_findUser($conditions);
if (isset($user[$fields['password']])) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesnt the parent already unset the pw?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only if its passed - which it isnt here, of course. we pass the conditions array directly, hence no unset triggered.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, you are right about that, i think we should always unset it in BaseAuthenticate if present in the userdata, not only if its in the conditions.

unset($user[$fields['password']]);
}
return $user;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php
/**
* DirectAuthenticateTest file
*
*/

App::uses('AuthComponent', 'Controller/Component');
App::uses('DirectAuthenticate', 'Controller/Component/Auth');
App::uses('AppModel', 'Model');
App::uses('CakeRequest', 'Network');
App::uses('CakeResponse', 'Network');

require_once CAKE . 'Test' . DS . 'Case' . DS . 'Model' . DS . 'models.php';

/**
* Test case for DirectAuthentication
*
* @package Cake.Test.Case.Controller.Component.Auth
*/
class DirectAuthenticateTest extends CakeTestCase {

public $fixtures = array('core.user', 'core.auth_user');

/**
* setup
*
* @return void
*/
public function setUp() {
parent::setUp();
$this->Collection = $this->getMock('ComponentCollection');
$this->auth = new DirectAuthenticate($this->Collection, array(
'fields' => array('username' => 'user'),
'userModel' => 'User'
));
$User = ClassRegistry::init('User');
$this->response = $this->getMock('CakeResponse');
}

/**
* test applying settings in the constructor
*
* @return void
*/
public function testConstructor() {
$object = new DirectAuthenticate($this->Collection, array(
'userModel' => 'AuthUser',
'fields' => array('username' => 'user')
));
$this->assertEquals('AuthUser', $object->settings['userModel']);
$this->assertEquals(array('username' => 'user', 'password' => 'password'), $object->settings['fields']);
}

/**
* test the authenticate method
*
* @return void
*/
public function testAuthenticateNoData() {
$request = new CakeRequest('posts/index', false);
$request->data = array();
$this->assertFalse($this->auth->authenticate($request, $this->response));
}

/**
* test the authenticate method
*
* @return void
*/
public function testAuthenticateNoUsername() {
$request = new CakeRequest('posts/index', false);
$request->data = array('User' => array('x' => 'foobar'));
$this->assertFalse($this->auth->authenticate($request, $this->response));
}

/**
* test authenticate password is false method
*
* @return void
*/
public function testAuthenticateUsernameDoesNotExist() {
$request = new CakeRequest('posts/index', false);
$request->data = array(
'User' => array(
'user' => 'foo',
));
$this->assertFalse($this->auth->authenticate($request, $this->response));
}

/**
* test the authenticate method
*
* @return void
*/
public function testAuthenticateInjection() {
$request = new CakeRequest('posts/index', false);
$request->data = array(
'User' => array(
'user' => "> 1 ' OR 1 = 1",
));
$this->assertFalse($this->auth->authenticate($request, $this->response));
}

/**
* test authenticate success
*
* @return void
*/
public function testAuthenticateSuccess() {
$request = new CakeRequest('posts/index', false);
$request->data = array('User' => array(
'user' => 'mariano',
));
$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);
}

/**
* test scope failure.
*
* @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',
));

$this->assertFalse($this->auth->authenticate($request, $this->response));
}

/**
* test a model in a plugin.
*
* @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';
$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',

));

$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();
}

}