diff --git a/cake/libs/controller/components/auth.php b/cake/libs/controller/components/auth.php index 001bdeef1b0..41b3c514674 100644 --- a/cake/libs/controller/components/auth.php +++ b/cake/libs/controller/components/auth.php @@ -312,7 +312,7 @@ public function startup($controller) { } } } else { - if (!$this->user()) { + if (!$this->_getUser()) { if (!$request->is('ajax')) { $this->flash($this->authError); $this->Session->write('Auth.redirect', Router::reverse($request)); @@ -558,6 +558,29 @@ public static function user($key = null) { } } +/** + * Similar to AuthComponent::user() except if the session user cannot be found, connected authentication + * objects will have their getUser() methods called. This lets stateless authentication methods function correctly. + * + * @return boolean true if a user can be found, false if one cannot. + */ + protected function _getUser() { + $user = $this->user(); + if ($user) { + return true; + } + if (empty($this->_authenticateObjects)) { + $this->constructAuthenticate(); + } + foreach ($this->_authenticateObjects as $auth) { + $result = $auth->getUser($this->request); + if (!empty($result) && is_array($result)) { + return true; + } + } + return false; + } + /** * If no parameter is passed, gets the authentication redirect URL. Pass a url in to * set the destination a user should be redirected to upon logging in. Will fallback to diff --git a/cake/libs/controller/components/auth/base_authenticate.php b/cake/libs/controller/components/auth/base_authenticate.php index d34789729b9..575b7544ec0 100644 --- a/cake/libs/controller/components/auth/base_authenticate.php +++ b/cake/libs/controller/components/auth/base_authenticate.php @@ -87,4 +87,15 @@ protected function _findUser($username, $password) { * @return mixed Either false on failure, or an array of user data on success. */ abstract public function authenticate(CakeRequest $request, CakeResponse $response); + +/** + * Get a user based on information in the request. Primarily used by stateless authentication + * systems like basic and digest auth. + * + * @param CakeRequest $request Request object. + * @return mixed Either false or an array of user information + */ + public function getUser($request) { + return false; + } } \ No newline at end of file diff --git a/cake/libs/controller/components/auth/basic_authenticate.php b/cake/libs/controller/components/auth/basic_authenticate.php index 08a35846361..9bf68d38675 100644 --- a/cake/libs/controller/components/auth/basic_authenticate.php +++ b/cake/libs/controller/components/auth/basic_authenticate.php @@ -83,20 +83,10 @@ public function __construct($settings) { * @return mixed Either false on failure, or an array of user data on success. */ public function authenticate(CakeRequest $request, CakeResponse $response) { - $username = env('PHP_AUTH_USER'); - $pass = env('PHP_AUTH_PW'); - - if (empty($username) || empty($pass)) { - $response->header($this->loginHeaders()); - $response->send(); - return false; - } - - $result = $this->_findUser($username, $pass); + $result = $this->getUser($request); if (empty($result)) { $response->header($this->loginHeaders()); - $response->header('Location', Router::reverse($request)); $response->statusCode(401); $response->send(); return false; @@ -104,6 +94,23 @@ public function authenticate(CakeRequest $request, CakeResponse $response) { return $result; } +/** + * Get a user based on information in the request. Primarily used by stateless authentication + * systems like basic and digest auth. + * + * @param CakeRequest $request Request object. + * @return mixed Either false or an array of user information + */ + public function getUser($request) { + $username = env('PHP_AUTH_USER'); + $pass = env('PHP_AUTH_PW'); + + if (empty($username) || empty($pass)) { + return false; + } + return $this->_findUser($username, $pass); + } + /** * Generate the login headers * diff --git a/cake/libs/controller/components/auth/digest_authenticate.php b/cake/libs/controller/components/auth/digest_authenticate.php index 91d8ed2702c..0e506824678 100644 --- a/cake/libs/controller/components/auth/digest_authenticate.php +++ b/cake/libs/controller/components/auth/digest_authenticate.php @@ -108,28 +108,40 @@ public function __construct($settings) { * @return mixed Either false on failure, or an array of user data on success. */ public function authenticate(CakeRequest $request, CakeResponse $response) { - $digest = $this->_getDigest(); + $user = $this->getUser($request); - if (empty($digest)) { + if (empty($user)) { $response->header($this->loginHeaders()); + $response->statusCode(401); $response->send(); return false; } + return $user; + } - $result = $this->_findUser($digest['username'], null); - $password = $result[$this->settings['fields']['password']]; - unset($result[$this->settings['fields']['password']]); - - if (empty($result) || $digest['response'] !== $this->generateResponseHash($digest, $password)) { - $response->header($this->loginHeaders()); - $response->header('Location', Router::reverse($request)); - $response->statusCode(401); - $response->send(); +/** + * Get a user based on information in the request. Primarily used by stateless authentication + * systems like basic and digest auth. + * + * @param CakeRequest $request Request object. + * @return mixed Either false or an array of user information + */ + public function getUser($request) { + $digest = $this->_getDigest(); + if (empty($digest)) { return false; } - return $result; + $user = $this->_findUser($digest['username'], null); + if (empty($user)) { + return false; + } + $password = $user[$this->settings['fields']['password']]; + unset($user[$this->settings['fields']['password']]); + if ($digest['response'] === $this->generateResponseHash($digest, $password)) { + return $user; + } + return false; } - /** * Find a user record using the standard options. * diff --git a/cake/tests/cases/libs/controller/components/auth/basic_authenticate.test.php b/cake/tests/cases/libs/controller/components/auth/basic_authenticate.test.php index 656ca0fbb1a..b1b2fc2e4b8 100644 --- a/cake/tests/cases/libs/controller/components/auth/basic_authenticate.test.php +++ b/cake/tests/cases/libs/controller/components/auth/basic_authenticate.test.php @@ -14,6 +14,7 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ +App::import('Component', 'Auth'); App::import('Component', 'auth/basic_authenticate'); App::import('Model', 'AppModel'); App::import('Core', 'CakeRequest'); @@ -114,6 +115,7 @@ function testAuthenticateNoUsername() { function testAuthenticateNoPassword() { $request = new CakeRequest('posts/index', false); $_SERVER['PHP_AUTH_USER'] = 'mariano'; + $_SERVER['PHP_AUTH_PW'] = null; $this->response->expects($this->once()) ->method('header') @@ -196,14 +198,10 @@ function testAuthenticateFailReChallenge() { ->with('WWW-Authenticate: Basic realm="localhost"'); $this->response->expects($this->at(1)) - ->method('header') - ->with('Location', Router::reverse($request)); - - $this->response->expects($this->at(2)) ->method('statusCode') ->with(401); - $this->response->expects($this->at(3)) + $this->response->expects($this->at(2)) ->method('send'); $this->assertFalse($this->auth->authenticate($request, $this->response)); diff --git a/cake/tests/cases/libs/controller/components/auth/digest_authenticate.test.php b/cake/tests/cases/libs/controller/components/auth/digest_authenticate.test.php index 40b8ef4c136..1ef4ccc5f8b 100644 --- a/cake/tests/cases/libs/controller/components/auth/digest_authenticate.test.php +++ b/cake/tests/cases/libs/controller/components/auth/digest_authenticate.test.php @@ -122,14 +122,10 @@ function testAuthenticateWrongUsername() { ->with('WWW-Authenticate: Digest realm="localhost",qop="auth",nonce="123",opaque="123abc"'); $this->response->expects($this->at(1)) - ->method('header') - ->with('Location', Router::reverse($request)); - - $this->response->expects($this->at(2)) ->method('statusCode') ->with(401); - $this->response->expects($this->at(3)) + $this->response->expects($this->at(2)) ->method('send'); $this->assertFalse($this->auth->authenticate($request, $this->response)); @@ -149,6 +145,10 @@ function testAuthenticateChallenge() { ->with('WWW-Authenticate: Digest realm="localhost",qop="auth",nonce="123",opaque="123abc"'); $this->response->expects($this->at(1)) + ->method('statusCode') + ->with(401); + + $this->response->expects($this->at(2)) ->method('send'); $result = $this->auth->authenticate($request, $this->response); @@ -211,16 +211,12 @@ function testAuthenticateFailReChallenge() { $this->response->expects($this->at(0)) ->method('header') ->with('WWW-Authenticate: Digest realm="localhost",qop="auth",nonce="123",opaque="123abc"'); - - $this->response->expects($this->at(1)) - ->method('header') - ->with('Location', Router::reverse($request)); - $this->response->expects($this->at(2)) + $this->response->expects($this->at(1)) ->method('statusCode') ->with(401); - $this->response->expects($this->at(3)) + $this->response->expects($this->at(2)) ->method('send'); $this->assertFalse($this->auth->authenticate($request, $this->response));