ソースを参照

Implemented stateless login for Auth

ADmad 13 年 前
コミット
b7834a2b16

+ 11 - 0
lib/Cake/Controller/Component/Auth/BaseAuthenticate.php

@@ -155,4 +155,15 @@ abstract class BaseAuthenticate {
 		return false;
 	}
 
+/**
+ * Handle unauthenticated access attempt.
+ *
+ * @param CakeRequest $request A request object.
+ * @param CakeResponse $response A response object.
+ * @return mixed Either true to indicate the unauthenticated request has been
+ *  dealt with and no more action is required by AuthComponent or void (default).
+ */
+	public function unauthenticated(CakeRequest $request, CakeResponse $response) {
+	}
+
 }

+ 17 - 11
lib/Cake/Controller/Component/Auth/BasicAuthenticate.php

@@ -82,23 +82,15 @@ class BasicAuthenticate extends BaseAuthenticate {
 	}
 
 /**
- * Authenticate a user using basic HTTP auth. Will use the configured User model and attempt a
- * login using basic HTTP auth.
+ * Authenticate a user using HTTP auth. Will use the configured User model and attempt a
+ * login using HTTP auth.
  *
  * @param CakeRequest $request The request to authenticate with.
  * @param CakeResponse $response The response to add headers to.
  * @return mixed Either false on failure, or an array of user data on success.
  */
 	public function authenticate(CakeRequest $request, CakeResponse $response) {
-		$result = $this->getUser($request);
-
-		if (empty($result)) {
-			$response->header($this->loginHeaders());
-			$response->statusCode(401);
-			$response->send();
-			return false;
-		}
-		return $result;
+		return $this->getUser($request);
 	}
 
 /**
@@ -118,6 +110,20 @@ class BasicAuthenticate extends BaseAuthenticate {
 	}
 
 /**
+ * Handles an unauthenticated access attempt by sending appropriate login headers
+ *
+ * @param CakeRequest $request A request object.
+ * @param CakeResponse $response A response object.
+ * @return boolean True
+ */
+	public function unauthenticated(CakeRequest $request, CakeResponse $response) {
+		$response->header($this->loginHeaders());
+		$response->statusCode(401);
+		$response->send();
+		return true;
+	}
+
+/**
  * Generate the login headers
  *
  * @return string Headers for logging in.

+ 2 - 25
lib/Cake/Controller/Component/Auth/DigestAuthenticate.php

@@ -14,7 +14,7 @@
  * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
  */
 
-App::uses('BaseAuthenticate', 'Controller/Component/Auth');
+App::uses('BasicAuthenticate', 'Controller/Component/Auth');
 
 /**
  * Digest Authentication adapter for AuthComponent.
@@ -55,7 +55,7 @@ App::uses('BaseAuthenticate', 'Controller/Component/Auth');
  * @package       Cake.Controller.Component.Auth
  * @since 2.0
  */
-class DigestAuthenticate extends BaseAuthenticate {
+class DigestAuthenticate extends BasicAuthenticate {
 
 /**
  * Settings for this object.
@@ -97,9 +97,6 @@ class DigestAuthenticate extends BaseAuthenticate {
  */
 	public function __construct(ComponentCollection $collection, $settings) {
 		parent::__construct($collection, $settings);
-		if (empty($this->settings['realm'])) {
-			$this->settings['realm'] = env('SERVER_NAME');
-		}
 		if (empty($this->settings['nonce'])) {
 			$this->settings['nonce'] = uniqid('');
 		}
@@ -109,26 +106,6 @@ class DigestAuthenticate extends BaseAuthenticate {
 	}
 
 /**
- * Authenticate a user using Digest HTTP auth. Will use the configured User model and attempt a
- * login using Digest HTTP auth.
- *
- * @param CakeRequest $request The request to authenticate with.
- * @param CakeResponse $response The response to add headers to.
- * @return mixed Either false on failure, or an array of user data on success.
- */
-	public function authenticate(CakeRequest $request, CakeResponse $response) {
-		$user = $this->getUser($request);
-
-		if (empty($user)) {
-			$response->header($this->loginHeaders());
-			$response->statusCode(401);
-			$response->send();
-			return false;
-		}
-		return $user;
-	}
-
-/**
  * Get a user based on information in the request. Used by cookie-less auth for stateless clients.
  *
  * @param CakeRequest $request Request object.

+ 63 - 35
lib/Cake/Controller/Component/AuthComponent.php

@@ -157,8 +157,9 @@ class AuthComponent extends Component {
 	);
 
 /**
- * The session key name where the record of the current user is stored. If
- * unspecified, it will be "Auth.User".
+ * The session key name where the record of the current user is stored. Default
+ * key is "Auth.User". If you are using only stateless authenticators set this
+ * to false to ensure session is not started.
  *
  * @var string
  */
@@ -188,7 +189,7 @@ class AuthComponent extends Component {
  * Normally, if a user is redirected to the $loginAction page, the location they
  * were redirected from will be stored in the session so that they can be
  * redirected back after a successful login. If this session value is not
- * set, the user will be redirected to the page specified in $loginRedirect.
+ * set, redirectUrl() method will return the url specified in $loginRedirect.
  *
  * @var mixed
  * @link http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#AuthComponent::$loginRedirect
@@ -312,44 +313,43 @@ class AuthComponent extends Component {
 
 /**
  * Checks whether current action is accessible without authentication.
- * If current action is login action referrer url is saved in session which is
- * later accessible using AuthComponent::redirectUrl().
  *
  * @param Controller $controller A reference to the instantiating controller object
  * @return boolean True if action is accessible without authentication else false
  */
 	protected function _isAllowed(Controller $controller) {
 		$action = strtolower($controller->request->params['action']);
-
-		$url = '';
-		if (isset($controller->request->url)) {
-			$url = $controller->request->url;
-		}
-		$url = Router::normalize($url);
-		$loginAction = Router::normalize($this->loginAction);
-
-		if ($loginAction != $url && in_array($action, array_map('strtolower', $this->allowedActions))) {
-			return true;
-		}
-
-		if ($loginAction == $url) {
-			if (empty($controller->request->data)) {
-				if (!$this->Session->check('Auth.redirect') && !$this->loginRedirect && env('HTTP_REFERER')) {
-					$this->Session->write('Auth.redirect', $controller->referer(null, true));
-				}
-			}
+		if (in_array($action, array_map('strtolower', $this->allowedActions))) {
 			return true;
 		}
 		return false;
 	}
 
 /**
- * Handle unauthenticated access attempt.
+ * Handles unauthenticated access attempt. First the `unathenticated()` method
+ * of the last authenticator in the chain will be called. The authenticator can
+ * handle sending response or redirection as appropriate and return `true` to
+ * indicate no furthur action is necessary. If authenticator returns null this
+ * method redirects user to login action. If it's an ajax request and
+ * $ajaxLogin is specified that element is rendered else a 403 http status code
+ * is returned.
  *
- * @param Controller $controller A reference to the controller object
- * @return boolean Returns false
+ * @param Controller $controller A reference to the controller object.
+ * @return boolean True if current action is login action else false.
  */
 	protected function _unauthenticated(Controller $controller) {
+		if (empty($this->_authenticateObjects)) {
+			$this->constructAuthenticate();
+		}
+		$auth = $this->_authenticateObjects[count($this->_authenticateObjects) - 1];
+		if ($auth->unauthenticated($this->request, $this->response)) {
+			return false;
+		}
+
+		if ($this->_isLoginAction($controller)) {
+			return true;
+		}
+
 		if (!$controller->request->is('ajax')) {
 			$this->flash($this->authError);
 			$this->Session->write('Auth.redirect', $controller->request->here());
@@ -367,11 +367,39 @@ class AuthComponent extends Component {
 	}
 
 /**
+ * Normalizes $loginAction and checks if current request url is same as login
+ * action. If current url is same as login action, referrer url is saved in session
+ * which is later accessible using redirectUrl().
+ *
+ * @param Controller $controller A reference to the controller object.
+ * @return boolean True if current action is login action else false.
+ */
+	protected function _isLoginAction(Controller $controller) {
+		$url = '';
+		if (isset($controller->request->url)) {
+			$url = $controller->request->url;
+		}
+		$url = Router::normalize($url);
+		$loginAction = Router::normalize($this->loginAction);
+
+		if ($loginAction == $url) {
+			if (empty($controller->request->data)) {
+				if (!$this->Session->check('Auth.redirect') && !$this->loginRedirect && env('HTTP_REFERER')) {
+					$this->Session->write('Auth.redirect', $controller->referer(null, true));
+				}
+			}
+			return true;
+		}
+		return false;
+	}
+
+/**
  * Handle unauthorized access attempt
  *
  * @param Controller $controller A reference to the controller object
  * @return boolean Returns false
  * @throws ForbiddenException
+ * @see AuthComponent::$unauthorizedRedirect
  */
 	protected function _unauthorized(Controller $controller) {
 		if ($this->unauthorizedRedirect === false) {
@@ -395,7 +423,7 @@ class AuthComponent extends Component {
 /**
  * Attempts to introspect the correct values for object properties.
  *
- * @return boolean
+ * @return boolean True
  */
 	protected function _setDefaults() {
 		$defaults = array(
@@ -619,13 +647,12 @@ class AuthComponent extends Component {
  * @link http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#accessing-the-logged-in-user
  */
 	public static function user($key = null) {
-		if (empty(self::$_user) && !CakeSession::check(self::$sessionKey)) {
-			return null;
-		}
 		if (!empty(self::$_user)) {
 			$user = self::$_user;
-		} else {
+		} elseif (self::$sessionKey && CakeSession::check(self::$sessionKey)) {
 			$user = CakeSession::read(self::$sessionKey);
+		} else {
+			return null;
 		}
 		if ($key === null) {
 			return $user;
@@ -640,10 +667,6 @@ class AuthComponent extends Component {
  * @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();
 		}
@@ -654,6 +677,11 @@ class AuthComponent extends Component {
 				return true;
 			}
 		}
+
+		$user = $this->user();
+		if ($user) {
+			return true;
+		}
 		return false;
 	}
 

+ 8 - 15
lib/Cake/Test/Case/Controller/Component/Auth/BasicAuthenticateTest.php

@@ -80,11 +80,10 @@ class BasicAuthenticateTest extends CakeTestCase {
 	public function testAuthenticateNoData() {
 		$request = new CakeRequest('posts/index', false);
 
-		$this->response->expects($this->once())
-			->method('header')
-			->with('WWW-Authenticate: Basic realm="localhost"');
+		$this->response->expects($this->never())
+			->method('header');
 
-		$this->assertFalse($this->auth->authenticate($request, $this->response));
+		$this->assertFalse($this->auth->getUser($request));
 	}
 
 /**
@@ -96,10 +95,6 @@ class BasicAuthenticateTest extends CakeTestCase {
 		$request = new CakeRequest('posts/index', false);
 		$_SERVER['PHP_AUTH_PW'] = 'foobar';
 
-		$this->response->expects($this->once())
-			->method('header')
-			->with('WWW-Authenticate: Basic realm="localhost"');
-
 		$this->assertFalse($this->auth->authenticate($request, $this->response));
 	}
 
@@ -113,10 +108,6 @@ class BasicAuthenticateTest extends CakeTestCase {
 		$_SERVER['PHP_AUTH_USER'] = 'mariano';
 		$_SERVER['PHP_AUTH_PW'] = null;
 
-		$this->response->expects($this->once())
-			->method('header')
-			->with('WWW-Authenticate: Basic realm="localhost"');
-
 		$this->assertFalse($this->auth->authenticate($request, $this->response));
 	}
 
@@ -132,6 +123,8 @@ class BasicAuthenticateTest extends CakeTestCase {
 		$_SERVER['PHP_AUTH_USER'] = '> 1';
 		$_SERVER['PHP_AUTH_PW'] = "' OR 1 = 1";
 
+		$this->assertFalse($this->auth->getUser($request));
+
 		$this->assertFalse($this->auth->authenticate($request, $this->response));
 	}
 
@@ -151,8 +144,8 @@ class BasicAuthenticateTest extends CakeTestCase {
 		$this->response->expects($this->at(1))
 			->method('send');
 
-		$result = $this->auth->authenticate($request, $this->response);
-		$this->assertFalse($result);
+		$result = $this->auth->unauthenticated($request, $this->response);
+		$this->assertTrue($result);
 	}
 
 /**
@@ -201,7 +194,7 @@ class BasicAuthenticateTest extends CakeTestCase {
 		$this->response->expects($this->at(2))
 			->method('send');
 
-		$this->assertFalse($this->auth->authenticate($request, $this->response));
+		$this->assertTrue($this->auth->unauthenticated($request, $this->response));
 	}
 
 }

+ 7 - 8
lib/Cake/Test/Case/Controller/Component/Auth/DigestAuthenticateTest.php

@@ -94,11 +94,10 @@ class DigestAuthenticateTest extends CakeTestCase {
 	public function testAuthenticateNoData() {
 		$request = new CakeRequest('posts/index', false);
 
-		$this->response->expects($this->once())
-			->method('header')
-			->with('WWW-Authenticate: Digest realm="localhost",qop="auth",nonce="123",opaque="123abc"');
+		$this->response->expects($this->never())
+			->method('header');
 
-		$this->assertFalse($this->auth->authenticate($request, $this->response));
+		$this->assertFalse($this->auth->getUser($request, $this->response));
 	}
 
 /**
@@ -133,7 +132,7 @@ DIGEST;
 		$this->response->expects($this->at(2))
 			->method('send');
 
-		$this->assertFalse($this->auth->authenticate($request, $this->response));
+		$this->assertTrue($this->auth->unauthenticated($request, $this->response));
 	}
 
 /**
@@ -156,8 +155,8 @@ DIGEST;
 		$this->response->expects($this->at(2))
 			->method('send');
 
-		$result = $this->auth->authenticate($request, $this->response);
-		$this->assertFalse($result);
+		$result = $this->auth->unauthenticated($request, $this->response);
+		$this->assertTrue($result);
 	}
 
 /**
@@ -224,7 +223,7 @@ DIGEST;
 		$this->response->expects($this->at(2))
 			->method('send');
 
-		$this->assertFalse($this->auth->authenticate($request, $this->response));
+		$this->assertTrue($this->auth->unauthenticated($request, $this->response));
 	}
 
 /**

+ 74 - 0
lib/Cake/Test/Case/Controller/Component/AuthComponentTest.php

@@ -1348,4 +1348,78 @@ class AuthComponentTest extends CakeTestCase {
 		$result = $this->Auth->user('is_admin');
 		$this->assertFalse($result);
 	}
+
+/**
+ * testStatelessAuthNoRedirect method
+ *
+ * @return void
+ */
+	public function testStatelessAuthNoRedirect() {
+		if (CakeSession::id()) {
+			session_destroy();
+			CakeSession::$id = null;
+		}
+		$_SESSION = null;
+
+		AuthComponent::$sessionKey = false;
+		$this->Auth->authenticate = array('Basic');
+		$this->Controller->request['action'] = 'admin_add';
+
+		$this->Auth->response->expects($this->once())
+			->method('statusCode')
+			->with(401);
+
+		$this->Auth->response->expects($this->once())
+			->method('send');
+
+		$result = $this->Auth->startup($this->Controller);
+		$this->assertFalse($result);
+
+		$this->assertNull($this->Controller->testUrl);
+		$this->assertNull(CakeSession::id());
+	}
+
+/**
+ * testStatelessAuthNoSessionStart method
+ *
+ * @return void
+ */
+	public function testStatelessAuthNoSessionStart() {
+		if (CakeSession::id()) {
+			session_destroy();
+			CakeSession::$id = null;
+		}
+		$_SESSION = null;
+
+		$_SERVER['PHP_AUTH_USER'] = 'mariano';
+		$_SERVER['PHP_AUTH_PW'] = 'cake';
+
+		$this->Auth->authenticate = array(
+			'Basic' => array('userModel' => 'AuthUser')
+		);
+		$this->Controller->request['action'] = 'admin_add';
+
+		$result = $this->Auth->startup($this->Controller);
+		$this->assertTrue($result);
+
+		$this->assertNull(CakeSession::id());
+	}
+
+/**
+ * testStatelessAuthRedirect method
+ *
+ * @return void
+ */
+	public function testStatelessFollowedByStatefulAuth() {
+		$this->Auth->authenticate = array('Basic', 'Form');
+		$this->Controller->request['action'] = 'admin_add';
+
+		$this->Auth->response->expects($this->never())->method('statusCode');
+		$this->Auth->response->expects($this->never())->method('send');
+
+		$result = $this->Auth->startup($this->Controller);
+		$this->assertFalse($result);
+
+		$this->assertEquals('/users/login', $this->Controller->testUrl);
+	}
 }