diff --git a/cake/libs/controller/components/auth.php b/cake/libs/controller/components/auth.php index c6429a419..a7993c249 100644 --- a/cake/libs/controller/components/auth.php +++ b/cake/libs/controller/components/auth.php @@ -48,20 +48,36 @@ class AuthComponent extends Component { public $components = array('Session', 'RequestHandler'); /** - * A reference to the object used for authentication + * An array of authentication objects to use for authenticating users. You can configure + * multiple adapters and they will be checked sequentially when users are identified. * * @var object * @link http://book.cakephp.org/view/1278/authenticate */ - public $authenticate = null; + public $authenticate = array('Form'); /** - * The name of the component to use for Authorization or set this to - * 'controller' will validate against Controller::isAuthorized() - * 'actions' will validate Controller::action against an AclComponent::check() - * 'crud' will validate mapActions against an AclComponent::check() - * array('model'=> 'name'); will validate mapActions against model $name::isAuthorized(user, controller, mapAction) - * 'object' will validate Controller::action against object::isAuthorized(user, controller, action) + * Objects that will be used for authentication checks. + * + * @var array + */ + protected $_authenticateObjects = array(); + +/** + * A hash mapping legacy properties => to settings passed into Authenticate objects. + * + * @var string + * @deprecated Will be removed in 2.1+ + */ + protected $_authenticateLegacyMap = array( + 'userModel' => 'userModel', + 'userScope' => 'scope', + 'fields' => 'fields' + ); + +/** + * An array of authorization objects to use for authorizing users. You can configure + * multiple adapters and they will be checked sequentially when authorization checks are done. * * @var mixed * @link http://book.cakephp.org/view/1275/authorize @@ -363,15 +379,7 @@ class AuthComponent extends Component { !empty($request->data[$model->alias][$this->fields['password']]); if ($isValid) { - $username = $request->data[$model->alias][$this->fields['username']]; - $password = $request->data[$model->alias][$this->fields['password']]; - - $data = array( - $model->alias . '.' . $this->fields['username'] => $username, - $model->alias . '.' . $this->fields['password'] => $password - ); - - if ($this->login($data)) { + if ($this->login()) { if ($this->autoRedirect) { $controller->redirect($this->redirect(), null, true); } @@ -456,18 +464,24 @@ class AuthComponent extends Component { * Each adapter will be checked in sequence, if any of them return true, then the user will * be authorized for the request. * - * @param mixed $user The user to check the authorization of, if empty the user in the session will be used. + * @param mixed $user The user to check the authorization of. If empty the user in the session will be used. + * @param CakeRequest $request The request to authenticate for. If empty, the current request will be used. * @return boolean True if $user is authorized, otherwise false */ - public function isAuthorized($user = null) { + public function isAuthorized($user = null, $request = null) { if (empty($user) && !$this->user()) { return false; } elseif (empty($user)) { $user = $this->user(); } - $this->loadAuthorizeObjects(); + if (empty($request)) { + $request = $this->request; + } + if (empty($this->_authorizeObjects)) { + $this->loadAuthorizeObjects(); + } foreach ($this->_authorizeObjects as $authorizer) { - if ($authorizer->authorize($user, $this->request) === true) { + if ($authorizer->authorize($user, $request) === true) { return true; } } @@ -483,6 +497,7 @@ class AuthComponent extends Component { if (empty($this->authorize)) { return; } + $this->_authorizeObjects = array(); foreach (Set::normalize($this->authorize) as $class => $settings) { $className = $class . 'Authorize'; if (!class_exists($className) && !App::import('Component', 'auth/' . $class . '_authorize')) { @@ -596,15 +611,14 @@ class AuthComponent extends Component { * @return boolean True on login success, false on failure * @link http://book.cakephp.org/view/1261/login */ - public function login($data = null) { + public function login($request = null) { $this->__setDefaults(); $this->_loggedIn = false; - if (empty($data)) { - $data = $this->data; + if (empty($request)) { + $request = $this->request; } - - if ($user = $this->identify($data)) { + if ($user = $this->identify($request)) { $this->Session->write($this->sessionKey, $user); $this->_loggedIn = true; } @@ -739,80 +753,51 @@ class AuthComponent extends Component { } /** - * Identifies a user based on specific criteria. + * Use the configured authentication adapters, and attempt to identify the user + * by credentials contained in $request. * - * @param mixed $user Optional. The identity of the user to be validated. - * Uses the current user session if none specified. - * @param array $conditions Optional. Additional conditions to a find. - * @return array User record data, or null, if the user could not be identified. + * @param CakeRequest $request The request that contains authentication data. + * @return array User record data, or false, if the user could not be identified. */ - public function identify($user = null, $conditions = null) { - if ($conditions === false) { - $conditions = null; - } elseif (is_array($conditions)) { - $conditions = array_merge((array)$this->userScope, $conditions); - } else { - $conditions = $this->userScope; + public function identify(CakeRequest $request) { + if (empty($this->_authenticateObjects)) { + $this->loadAuthenticateObjects(); } - $model = $this->getModel(); - if (empty($user)) { - $user = $this->user(); - if (empty($user)) { - return null; + foreach ($this->_authenticateObjects as $auth) { + $result = $auth->authenticate($request); + if (!empty($result) && is_array($result)) { + return $result; } - } elseif (is_object($user) && is_a($user, 'Model')) { - if (!$user->exists()) { - return null; - } - $user = $user->read(); - $user = $user[$model->alias]; - } elseif (is_array($user) && isset($user[$model->alias])) { - $user = $user[$model->alias]; } + return false; + } - if (is_array($user) && (isset($user[$this->fields['username']]) || isset($user[$model->alias . '.' . $this->fields['username']]))) { - if (isset($user[$this->fields['username']]) && !empty($user[$this->fields['username']]) && !empty($user[$this->fields['password']])) { - if (trim($user[$this->fields['username']]) == '=' || trim($user[$this->fields['password']]) == '=') { - return false; - } - $find = array( - $model->alias.'.'.$this->fields['username'] => $user[$this->fields['username']], - $model->alias.'.'.$this->fields['password'] => $user[$this->fields['password']] - ); - } elseif (isset($user[$model->alias . '.' . $this->fields['username']]) && !empty($user[$model->alias . '.' . $this->fields['username']])) { - if (trim($user[$model->alias . '.' . $this->fields['username']]) == '=' || trim($user[$model->alias . '.' . $this->fields['password']]) == '=') { - return false; - } - $find = array( - $model->alias.'.'.$this->fields['username'] => $user[$model->alias . '.' . $this->fields['username']], - $model->alias.'.'.$this->fields['password'] => $user[$model->alias . '.' . $this->fields['password']] - ); - } else { - return false; - } - $data = $model->find('first', array( - 'conditions' => array_merge($find, $conditions), - 'recursive' => 0 - )); - if (empty($data) || empty($data[$model->alias])) { - return null; - } - } elseif (!empty($user) && is_string($user)) { - $data = $model->find('first', array( - 'conditions' => array_merge(array($model->escapeField() => $user), $conditions), - )); - if (empty($data) || empty($data[$model->alias])) { - return null; - } +/** + * loads the configured authentication objects. + * + * @return mixed either null on empty authenticate value, or an array of loaded objects. + */ + public function loadAuthenticateObjects() { + if (empty($this->authenticate)) { + return; } - - if (!empty($data)) { - if (!empty($data[$model->alias][$this->fields['password']])) { - unset($data[$model->alias][$this->fields['password']]); + $this->_authenticateObjects = array(); + foreach (Set::normalize($this->authenticate) as $class => $settings) { + $className = $class . 'Authenticate'; + if (!class_exists($className) && !App::import('Component', 'auth/' . $class . '_authenticate')) { + throw new CakeException(__('Authentication adapter "%s" was not found.', $class)); } - return $data[$model->alias]; + if (!method_exists($className, 'authenticate')) { + throw new CakeException(__('Authentication objects must implement an authenticate method.')); + } + foreach ($this->_authenticateLegacyMap as $old => $new) { + if (empty($settings[$new]) && !empty($this->{$old})) { + $settings[$new] = $this->{$old}; + } + } + $this->_authenticateObjects[] = new $className($settings); } - return null; + return $this->_authenticateObjects; } /** diff --git a/cake/tests/cases/libs/controller/components/auth.test.php b/cake/tests/cases/libs/controller/components/auth.test.php index 54689c841..27f9b6c10 100644 --- a/cake/tests/cases/libs/controller/components/auth.test.php +++ b/cake/tests/cases/libs/controller/components/auth.test.php @@ -582,13 +582,17 @@ class AuthTest extends CakeTestCase { $this->Controller->Auth->startup($this->Controller); $user = $this->Controller->Auth->user(); $expected = array('AuthUser' => array( - 'id' => 1, 'username' => 'mariano', 'created' => '2007-03-17 01:16:23', 'updated' => date('Y-m-d H:i:s') + 'id' => 1, + 'username' => 'mariano', + 'created' => '2007-03-17 01:16:23', + 'updated' => date('Y-m-d H:i:s') )); $this->assertEqual($user, $expected); $this->Controller->Session->delete('Auth'); $this->Controller->request->data['AuthUser'] = array( - 'username' => 'blah', 'password' => '' + 'username' => 'blah', + 'password' => '' ); $this->Controller->Auth->startup($this->Controller); @@ -598,7 +602,8 @@ class AuthTest extends CakeTestCase { $this->Controller->Session->delete('Auth'); $this->Controller->request->data['AuthUser'] = array( - 'username' => 'now() or 1=1 --', 'password' => '' + 'username' => 'now() or 1=1 --', + 'password' => '' ); $this->Controller->Auth->startup($this->Controller); @@ -608,7 +613,8 @@ class AuthTest extends CakeTestCase { $this->Controller->Session->delete('Auth'); $this->Controller->request->data['AuthUser'] = array( - 'username' => 'now() or 1=1 #something', 'password' => '' + 'username' => 'now() or 1=1 #something', + 'password' => '' ); $this->Controller->Auth->startup($this->Controller); @@ -617,14 +623,6 @@ class AuthTest extends CakeTestCase { $this->assertNull($user); $this->Controller->Session->delete('Auth'); - $this->Controller->Auth->userModel = 'UuidUser'; - $this->Controller->Auth->login('47c36f9c-bc00-4d17-9626-4e183ca6822b'); - - $user = $this->Controller->Auth->user(); - $expected = array('UuidUser' => array( - 'id' => '47c36f9c-bc00-4d17-9626-4e183ca6822b', 'title' => 'Unique record 1', 'count' => 2, 'created' => '2008-03-13 01:16:23', 'updated' => '2008-03-13 01:18:31' - )); - $this->assertEqual($user, $expected); $this->Controller->Session->delete('Auth'); } @@ -850,22 +848,80 @@ class AuthTest extends CakeTestCase { 'AuthMockThree' ); $mocks = $this->Controller->Auth->loadAuthorizeObjects(); + $request = $this->Controller->request; $this->assertEquals(3, count($mocks)); $mocks[0]->expects($this->once()) ->method('authorize') - ->with(array('User')) + ->with(array('User'), $request) ->will($this->returnValue(false)); - + $mocks[1]->expects($this->once()) ->method('authorize') - ->with(array('User')) + ->with(array('User'), $request) ->will($this->returnValue(true)); - + $mocks[2]->expects($this->never()) ->method('authorize'); - - $this->assertTrue($this->Controller->Auth->isAuthorized(array('User'))); + + $this->assertTrue($this->Controller->Auth->isAuthorized(array('User'), $request)); + } + +/** + * test that loadAuthorize resets the loaded objects each time. + * + * @return void + */ + function testLoadAuthorizeResets() { + $this->Controller->Auth->authorize = array( + 'Controller' + ); + $result = $this->Controller->Auth->loadAuthorizeObjects(); + $this->assertEquals(1, count($result)); + + $result = $this->Controller->Auth->loadAuthorizeObjects(); + $this->assertEquals(1, count($result)); + } + +/** + * @expectedException CakeException + * @return void + */ + function testLoadAuthenticateNoFile() { + $this->Controller->Auth->authenticate = 'Missing'; + $this->Controller->Auth->identify($this->Controller->request); + } + +/** + * test that loadAuthorize resets the loaded objects each time. + * + * @return void + */ + function testLoadAuthenticateResets() { + $this->Controller->Auth->authenticate = array( + 'Form' + ); + $result = $this->Controller->Auth->loadAuthenticateObjects(); + $this->assertEquals(1, count($result)); + + $result = $this->Controller->Auth->loadAuthenticateObjects(); + $this->assertEquals(1, count($result)); + } + +/** + * test that loadAuthenticate merges in legacy authentication settings. + * + * @return void + */ + function testLoadAuthenticateSettingsPass() { + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->userScope = array('AuthUser.active' => 1); + $this->Controller->Auth->fields = array('username' => 'user', 'password' => 'passwd'); + + $this->Controller->Auth->authenticate = array('Form'); + $objects = $this->Controller->Auth->loadAuthenticateObjects(); + $result = $objects[0]; + $this->assertEquals($result->settings['userModel'], 'AuthUser'); } /** @@ -1463,7 +1519,8 @@ class AuthTest extends CakeTestCase { $authUser = $PluginModel->find(); $this->Controller->request->data['TestPluginAuthUser'] = array( - 'username' => $authUser['TestPluginAuthUser']['username'], 'password' => 'cake' + 'username' => $authUser['TestPluginAuthUser']['username'], + 'password' => 'cake' ); $this->Controller->request->addParams(Router::parse('auth_test/login')); @@ -1476,8 +1533,12 @@ class AuthTest extends CakeTestCase { $this->Controller->Auth->startup($this->Controller); $user = $this->Controller->Auth->user(); - $expected = array('TestPluginAuthUser' => array( - 'id' => 1, 'username' => 'gwoo', 'created' => '2007-03-17 01:16:23', 'updated' => date('Y-m-d H:i:s') + $expected = array( + 'TestPluginAuthUser' => array( + 'id' => 1, + 'username' => 'gwoo', + 'created' => '2007-03-17 01:16:23', + 'updated' => date('Y-m-d H:i:s') )); $this->assertEqual($user, $expected); $sessionKey = $this->Controller->Auth->sessionKey;