Skip to content

Commit d24bbcb

Browse files
committed
Add BlowfishAuthenticate adapter.
1 parent 8e93c47 commit d24bbcb

5 files changed

Lines changed: 340 additions & 15 deletions

File tree

lib/Cake/Controller/Component/Auth/BaseAuthenticate.php

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,28 @@ public function __construct(ComponentCollection $collection, $settings) {
6666
/**
6767
* Find a user record using the standard options.
6868
*
69-
* @param string $username The username/identifier.
70-
* @param string $password The unhashed password.
69+
* The $conditions parameter can be a (string)username or an array containing conditions for Model::find('first'). If
70+
* the password field is not included in the conditions the password will be returned.
71+
*
72+
* @param Mixed $conditions The username/identifier, or an array of find conditions.
73+
* @param Mixed $password The password, only use if passing as $conditions = 'username'.
7174
* @return Mixed Either false on failure, or an array of user data.
7275
*/
73-
protected function _findUser($username, $password) {
76+
protected function _findUser($conditions, $password = null) {
7477
$userModel = $this->settings['userModel'];
7578
list($plugin, $model) = pluginSplit($userModel);
7679
$fields = $this->settings['fields'];
7780

78-
$conditions = array(
79-
$model . '.' . $fields['username'] => $username,
80-
$model . '.' . $fields['password'] => $this->_password($password),
81-
);
81+
if (!is_array($conditions)) {
82+
if (!$password) {
83+
return false;
84+
}
85+
$username = $conditions;
86+
$conditions = array(
87+
$model . '.' . $fields['username'] => $username,
88+
$model . '.' . $fields['password'] => $this->_password($password),
89+
);
90+
}
8291
if (!empty($this->settings['scope'])) {
8392
$conditions = array_merge($conditions, $this->settings['scope']);
8493
}
@@ -91,7 +100,12 @@ protected function _findUser($username, $password) {
91100
return false;
92101
}
93102
$user = $result[$model];
94-
unset($user[$fields['password']]);
103+
if (
104+
isset($conditions[$model . '.' . $fields['password']]) ||
105+
isset($conditions[$fields['password']])
106+
) {
107+
unset($user[$fields['password']]);
108+
}
95109
unset($result[$model]);
96110
return array_merge($user, $result);
97111
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
/**
3+
* PHP 5
4+
*
5+
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6+
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
7+
*
8+
* Licensed under The MIT License
9+
* Redistributions of the files must retain the above copyright notice.
10+
*
11+
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
12+
* @link http://cakephp.org CakePHP(tm) Project
13+
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
14+
*/
15+
16+
App::uses('FormAuthenticate', 'Controller/Component/Auth');
17+
18+
/**
19+
* An authentication adapter for AuthComponent. Provides the ability to authenticate using POST data using Blowfish
20+
* hashing. Can be used by configuring AuthComponent to use it via the AuthComponent::$authenticate setting.
21+
*
22+
* {{{
23+
* $this->Auth->authenticate = array(
24+
* 'Blowfish' => array(
25+
* 'scope' => array('User.active' => 1)
26+
* )
27+
* )
28+
* }}}
29+
*
30+
* When configuring BlowfishAuthenticate you can pass in settings to which fields, model and additional conditions
31+
* are used. See BlowfishAuthenticate::$settings for more information.
32+
*
33+
* @package Cake.Controller.Component.Auth
34+
* @since CakePHP(tm) v 2.3
35+
* @see AuthComponent::$authenticate
36+
*/
37+
class BlowfishAuthenticate extends FormAuthenticate {
38+
39+
/**
40+
* Authenticates the identity contained in a request. Will use the `settings.userModel`, and `settings.fields`
41+
* to find POST data that is used to find a matching record in the`settings.userModel`. Will return false if
42+
* there is no post data, either username or password is missing, or if the scope conditions have not been met.
43+
*
44+
* @param CakeRequest $request The request that contains login information.
45+
* @param CakeResponse $response Unused response object.
46+
* @return mixed False on login failure. An array of User data on success.
47+
*/
48+
public function authenticate(CakeRequest $request, CakeResponse $response) {
49+
$userModel = $this->settings['userModel'];
50+
list($plugin, $model) = pluginSplit($userModel);
51+
52+
$fields = $this->settings['fields'];
53+
if (!$this->_checkFields($request, $model, $fields)) {
54+
return false;
55+
}
56+
$user = $this->_findUser(
57+
array(
58+
$model . '.' . $fields['username'] => $request->data[$model][$fields['username']],
59+
)
60+
);
61+
if (!$user) {
62+
return false;
63+
}
64+
$password = Security::hash(
65+
$request->data[$model][$fields['password']],
66+
'blowfish',
67+
$user[$fields['password']]
68+
);
69+
if ($password === $user[$fields['password']]) {
70+
unset($user[$fields['password']]);
71+
return $user;
72+
}
73+
return false;
74+
}
75+
}

lib/Cake/Controller/Component/Auth/FormAuthenticate.php

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,27 @@
3636
*/
3737
class FormAuthenticate extends BaseAuthenticate {
3838

39+
/**
40+
* Checks the fields to ensure they are supplied.
41+
*
42+
* @param CakeRequest $request The request that contains login information.
43+
* @param string $model The model used for login verification.
44+
* @param array $fields The fields to be checked.
45+
* @return boolean False if the fields have not been supplied. True if they exist.
46+
*/
47+
protected function _checkFields(CakeRequest $request, $model, $fields) {
48+
if (empty($request->data[$model])) {
49+
return false;
50+
}
51+
if (
52+
empty($request->data[$model][$fields['username']]) ||
53+
empty($request->data[$model][$fields['password']])
54+
) {
55+
return false;
56+
}
57+
return true;
58+
}
59+
3960
/**
4061
* Authenticates the identity contained in a request. Will use the `settings.userModel`, and `settings.fields`
4162
* to find POST data that is used to find a matching record in the `settings.userModel`. Will return false if
@@ -50,13 +71,7 @@ public function authenticate(CakeRequest $request, CakeResponse $response) {
5071
list($plugin, $model) = pluginSplit($userModel);
5172

5273
$fields = $this->settings['fields'];
53-
if (empty($request->data[$model])) {
54-
return false;
55-
}
56-
if (
57-
empty($request->data[$model][$fields['username']]) ||
58-
empty($request->data[$model][$fields['password']])
59-
) {
74+
if (!$this->_checkFields($request, $model, $fields)) {
6075
return false;
6176
}
6277
return $this->_findUser(
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
<?php
2+
/**
3+
* BlowfishAuthenticateTest file
4+
*
5+
* PHP 5
6+
*
7+
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8+
* Copyright 2005-2012, 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-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
14+
* @link http://cakephp.org CakePHP(tm) Project
15+
* @package Cake.Test.Case.Controller.Component.Auth
16+
* @since CakePHP(tm) v 2.3
17+
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
18+
*/
19+
20+
App::uses('AuthComponent', 'Controller/Component');
21+
App::uses('BlowfishAuthenticate', 'Controller/Component/Auth');
22+
App::uses('AppModel', 'Model');
23+
App::uses('CakeRequest', 'Network');
24+
App::uses('CakeResponse', 'Network');
25+
App::uses('Security', 'Utility');
26+
27+
require_once CAKE . 'Test' . DS . 'Case' . DS . 'Model' . DS . 'models.php';
28+
29+
/**
30+
* Test case for BlowfishAuthentication
31+
*
32+
* @package Cake.Test.Case.Controller.Component.Auth
33+
*/
34+
class BlowfishAuthenticateTest extends CakeTestCase {
35+
36+
public $fixtures = array('core.user', 'core.auth_user');
37+
38+
/**
39+
* setup
40+
*
41+
* @return void
42+
*/
43+
public function setUp() {
44+
parent::setUp();
45+
$this->Collection = $this->getMock('ComponentCollection');
46+
$this->auth = new BlowfishAuthenticate($this->Collection, array(
47+
'fields' => array('username' => 'user', 'password' => 'password'),
48+
'userModel' => 'User'
49+
));
50+
$password = Security::hash('password', 'blowfish');
51+
$User = ClassRegistry::init('User');
52+
$User->updateAll(array('password' => $User->getDataSource()->value($password)));
53+
$this->response = $this->getMock('CakeResponse');
54+
}
55+
56+
/**
57+
* test applying settings in the constructor
58+
*
59+
* @return void
60+
*/
61+
public function testConstructor() {
62+
$Object = new BlowfishAuthenticate($this->Collection, array(
63+
'userModel' => 'AuthUser',
64+
'fields' => array('username' => 'user', 'password' => 'password')
65+
));
66+
$this->assertEquals('AuthUser', $Object->settings['userModel']);
67+
$this->assertEquals(array('username' => 'user', 'password' => 'password'), $Object->settings['fields']);
68+
}
69+
70+
/**
71+
* testAuthenticateNoData method
72+
*
73+
* @return void
74+
*/
75+
public function testAuthenticateNoData() {
76+
$request = new CakeRequest('posts/index', false);
77+
$request->data = array();
78+
$this->assertFalse($this->auth->authenticate($request, $this->response));
79+
}
80+
81+
/**
82+
* testAuthenticateNoUsername method
83+
*
84+
* @return void
85+
*/
86+
public function testAuthenticateNoUsername() {
87+
$request = new CakeRequest('posts/index', false);
88+
$request->data = array('User' => array('password' => 'foobar'));
89+
$this->assertFalse($this->auth->authenticate($request, $this->response));
90+
}
91+
92+
/**
93+
* testAuthenticateNoPassword method
94+
*
95+
* @return void
96+
*/
97+
public function testAuthenticateNoPassword() {
98+
$request = new CakeRequest('posts/index', false);
99+
$request->data = array('User' => array('user' => 'mariano'));
100+
$this->assertFalse($this->auth->authenticate($request, $this->response));
101+
}
102+
103+
/**
104+
* testAuthenticatePasswordIsFalse method
105+
*
106+
* @return void
107+
*/
108+
public function testAuthenticatePasswordIsFalse() {
109+
$request = new CakeRequest('posts/index', false);
110+
$request->data = array(
111+
'User' => array(
112+
'user' => 'mariano',
113+
'password' => null
114+
));
115+
$this->assertFalse($this->auth->authenticate($request, $this->response));
116+
}
117+
118+
/**
119+
* testAuthenticateInjection method
120+
*
121+
* @return void
122+
*/
123+
public function testAuthenticateInjection() {
124+
$request = new CakeRequest('posts/index', false);
125+
$request->data = array('User' => array(
126+
'user' => '> 1',
127+
'password' => "' OR 1 = 1"
128+
));
129+
$this->assertFalse($this->auth->authenticate($request, $this->response));
130+
}
131+
132+
/**
133+
* testAuthenticateSuccess method
134+
*
135+
* @return void
136+
*/
137+
public function testAuthenticateSuccess() {
138+
$request = new CakeRequest('posts/index', false);
139+
$request->data = array('User' => array(
140+
'user' => 'mariano',
141+
'password' => 'password'
142+
));
143+
$result = $this->auth->authenticate($request, $this->response);
144+
$expected = array(
145+
'id' => 1,
146+
'user' => 'mariano',
147+
'created' => '2007-03-17 01:16:23',
148+
'updated' => '2007-03-17 01:18:31',
149+
);
150+
$this->assertEquals($expected, $result);
151+
}
152+
153+
/**
154+
* testAuthenticateScopeFail method
155+
*
156+
* @return void
157+
*/
158+
public function testAuthenticateScopeFail() {
159+
$this->auth->settings['scope'] = array('user' => 'nate');
160+
$request = new CakeRequest('posts/index', false);
161+
$request->data = array('User' => array(
162+
'user' => 'mariano',
163+
'password' => 'password'
164+
));
165+
$this->assertFalse($this->auth->authenticate($request, $this->response));
166+
}
167+
168+
/**
169+
* testPluginModel method
170+
*
171+
* @return void
172+
*/
173+
public function testPluginModel() {
174+
Cache::delete('object_map', '_cake_core_');
175+
App::build(array(
176+
'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS)
177+
), App::RESET);
178+
CakePlugin::load('TestPlugin');
179+
180+
$PluginModel = ClassRegistry::init('TestPlugin.TestPluginAuthUser');
181+
$user['id'] = 1;
182+
$user['username'] = 'gwoo';
183+
$user['password'] = Security::hash('password', 'blowfish');
184+
$PluginModel->save($user, false);
185+
186+
$this->auth->settings['userModel'] = 'TestPlugin.TestPluginAuthUser';
187+
$this->auth->settings['fields']['username'] = 'username';
188+
189+
$request = new CakeRequest('posts/index', false);
190+
$request->data = array('TestPluginAuthUser' => array(
191+
'username' => 'gwoo',
192+
'password' => 'password'
193+
));
194+
195+
$result = $this->auth->authenticate($request, $this->response);
196+
$expected = array(
197+
'id' => 1,
198+
'username' => 'gwoo',
199+
'created' => '2007-03-17 01:16:23'
200+
);
201+
$this->assertEquals(self::date(), $result['updated']);
202+
unset($result['updated']);
203+
$this->assertEquals($expected, $result);
204+
CakePlugin::unload();
205+
}
206+
}

lib/Cake/Test/Case/Controller/Component/Auth/FormAuthenticateTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,21 @@ public function testAuthenticateNoPassword() {
9999
$this->assertFalse($this->auth->authenticate($request, $this->response));
100100
}
101101

102+
/**
103+
* test authenticate password is false method
104+
*
105+
* @return void
106+
*/
107+
public function testAuthenticatePasswordIsFalse() {
108+
$request = new CakeRequest('posts/index', false);
109+
$request->data = array(
110+
'User' => array(
111+
'user' => 'mariano',
112+
'password' => null
113+
));
114+
$this->assertFalse($this->auth->authenticate($request, $this->response));
115+
}
116+
102117
/**
103118
* test the authenticate method
104119
*

0 commit comments

Comments
 (0)