Browse Source

Merge pull request #3401 from ADmad/3.0-controller-redirect

3.0 controller redirect
José Lorenzo Rodríguez 12 years ago
parent
commit
33dea130e1

+ 6 - 3
src/Controller/Component/Auth/BaseAuthenticate.php

@@ -201,9 +201,12 @@ abstract class BaseAuthenticate {
 	}
 
 /**
- * Handle unauthenticated access attempt. In implementation, will return true to indicate
- * the unauthenticated request has been dealt with and no more action is required by
- * AuthComponent or void (default).
+ * Handle unauthenticated access attempt. In implementation valid return values
+ * can be:
+ *
+ * - Null - No action taken, AuthComponent should return appropriate response.
+ * - Cake\Network\Response - A response object, which will cause AuthComponent to
+ *   simply return that response.
  *
  * @param \Cake\Network\Request $request A request object.
  * @param \Cake\Network\Response $response A response object.

+ 27 - 25
src/Controller/Component/AuthComponent.php

@@ -1,9 +1,5 @@
 <?php
 /**
- * Authentication component
- *
- * Manages user logins and permissions.
- *
  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  *
@@ -240,7 +236,7 @@ class AuthComponent extends Component {
  * of login form data.
  *
  * @param Event $event The startup event.
- * @return bool
+ * @return void|\Cake\Network\Response
  */
 	public function startup(Event $event) {
 		$controller = $event->subject();
@@ -248,26 +244,31 @@ class AuthComponent extends Component {
 		$action = strtolower($controller->request->params['action']);
 
 		if (!isset($methods[$action])) {
-			return true;
+			return;
 		}
 
 		$this->_setDefaults();
 
 		if ($this->_isAllowed($controller)) {
-			return true;
+			return;
 		}
 
 		if (!$this->_getUser()) {
-			return $this->_unauthenticated($controller);
+			$result = $this->_unauthenticated($controller);
+			if ($result instanceof Response) {
+				$event->stopPropagation();
+			}
+			return $result;
 		}
 
 		if ($this->_isLoginAction($controller) ||
 			empty($this->_config['authorize']) ||
 			$this->isAuthorized($this->user())
 		) {
-			return true;
+			return;
 		}
 
+		$event->stopPropagation();
 		return $this->_unauthorized($controller);
 	}
 
@@ -295,15 +296,17 @@ class AuthComponent extends Component {
  * is returned.
  *
  * @param Controller $controller A reference to the controller object.
- * @return bool True if current action is login action else false.
+ * @return void|\Cake\Network\Response Null if current action is login action
+ *   else response object returned by authenticate object or Controller::redirect().
  */
 	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;
+		$result = $auth->unauthenticated($this->request, $this->response);
+		if ($result !== null) {
+			return $result;
 		}
 
 		if ($this->_isLoginAction($controller)) {
@@ -313,25 +316,25 @@ class AuthComponent extends Component {
 			) {
 				$this->Session->write('Auth.redirect', $controller->referer(null, true));
 			}
-			return true;
+			return;
 		}
 
 		if (!$controller->request->is('ajax')) {
 			$this->flash($this->_config['authError']);
 			$this->Session->write('Auth.redirect', $controller->request->here(false));
-			$controller->redirect($this->_config['loginAction']);
-			return false;
+			return $controller->redirect($this->_config['loginAction']);
 		}
 
 		if (!empty($this->_config['ajaxLogin'])) {
-			$controller->response->statusCode(403);
 			$controller->viewPath = 'Element';
-			echo $controller->render($this->_config['ajaxLogin'], $this->RequestHandler->ajaxLayout);
-			$controller->response->stop();
-			return false;
-		}
-		$controller->redirect(null, 403);
-		return false;
+			$response = $controller->render(
+				$this->_config['ajaxLogin'],
+				$this->RequestHandler->ajaxLayout
+			);
+			$response->statusCode(403);
+			return $response;
+		}
+		return $controller->redirect(null, 403);
 	}
 
 /**
@@ -355,7 +358,7 @@ class AuthComponent extends Component {
  * Handle unauthorized access attempt
  *
  * @param Controller $controller A reference to the controller object
- * @return bool Returns false
+ * @return \Cake\Network\Response
  * @throws \Cake\Error\ForbiddenException
  */
 	protected function _unauthorized(Controller $controller) {
@@ -373,8 +376,7 @@ class AuthComponent extends Component {
 		} else {
 			$url = $this->_config['unauthorizedRedirect'];
 		}
-		$controller->redirect($url, null, true);
-		return false;
+		return $controller->redirect($url);
 	}
 
 /**

+ 27 - 15
src/Controller/Controller.php

@@ -511,11 +511,17 @@ class Controller implements EventListener {
  * - Calls the controller `beforeFilter`.
  * - triggers Component `startup` methods.
  *
- * @return void
+ * @return void|\Cake\Network\Response
  */
 	public function startupProcess() {
-		$this->getEventManager()->dispatch(new Event('Controller.initialize', $this));
-		$this->getEventManager()->dispatch(new Event('Controller.startup', $this));
+		$event = $this->getEventManager()->dispatch(new Event('Controller.initialize', $this));
+		if ($event->result instanceof Response) {
+			return $event->result;
+		}
+		$event = $this->getEventManager()->dispatch(new Event('Controller.startup', $this));
+		if ($event->result instanceof Response) {
+			return $event->result;
+		}
 	}
 
 /**
@@ -525,10 +531,13 @@ class Controller implements EventListener {
  * - triggers the component `shutdown` callback.
  * - calls the Controller's `afterFilter` method.
  *
- * @return void
+ * @return void|\Cake\Network\Response
  */
 	public function shutdownProcess() {
-		$this->getEventManager()->dispatch(new Event('Controller.shutdown', $this));
+		$event = $this->getEventManager()->dispatch(new Event('Controller.shutdown', $this));
+		if ($event->result instanceof Response) {
+			return $event->result;
+		}
 	}
 
 /**
@@ -538,11 +547,10 @@ class Controller implements EventListener {
  * @param string|array $url A string or array-based URL pointing to another location within the app,
  *     or an absolute URL
  * @param int $status Optional HTTP status code (eg: 404)
- * @param bool $exit If true, exit() will be called after the redirect
- * @return void
- * @link http://book.cakephp.org/2.0/en/controllers.html#Controller::redirect
+ * @return void|\Cake\Network\Response
+ * @link http://book.cakephp.org/3.0/en/controllers.html#Controller::redirect
  */
-	public function redirect($url, $status = null, $exit = true) {
+	public function redirect($url, $status = null) {
 		$this->autoRender = false;
 
 		$response = $this->response;
@@ -551,7 +559,10 @@ class Controller implements EventListener {
 		}
 
 		$event = new Event('Controller.beforeRedirect', $this, [$response, $url, $status]);
-		$this->getEventManager()->dispatch($event);
+		$event = $this->getEventManager()->dispatch($event);
+		if ($event->result instanceof Response) {
+			return $event->result;
+		}
 		if ($event->isStopped()) {
 			return;
 		}
@@ -560,10 +571,7 @@ class Controller implements EventListener {
 			$response->location(Router::url($url, true));
 		}
 
-		if ($exit) {
-			$response->send();
-			$response->stop();
-		}
+		return $response;
 	}
 
 /**
@@ -598,7 +606,11 @@ class Controller implements EventListener {
  */
 	public function render($view = null, $layout = null) {
 		$event = new Event('Controller.beforeRender', $this);
-		$this->getEventManager()->dispatch($event);
+		$event = $this->getEventManager()->dispatch($event);
+		if ($event->result instanceof Response) {
+			$this->autoRender = false;
+			return $event->result;
+		}
 		if ($event->isStopped()) {
 			$this->autoRender = false;
 			return $this->response;

+ 9 - 2
src/Routing/Dispatcher.php

@@ -187,7 +187,10 @@ class Dispatcher implements EventListener {
  */
 	protected function _invoke(Controller $controller) {
 		$controller->constructClasses();
-		$controller->startupProcess();
+		$result = $controller->startupProcess();
+		if ($result instanceof Response) {
+			return $result;
+		}
 
 		$response = $controller->invokeAction();
 		if ($response !== null && !($response instanceof Response)) {
@@ -200,7 +203,11 @@ class Dispatcher implements EventListener {
 			$response = $controller->response;
 		}
 
-		$controller->shutdownProcess();
+		$result = $controller->shutdownProcess();
+		if ($result instanceof Response) {
+			return $result;
+		}
+
 		return $response;
 	}
 

+ 35 - 35
tests/TestCase/Controller/Component/AuthComponentTest.php

@@ -123,11 +123,11 @@ class AuthComponentTest extends TestCase {
 		$this->Controller->Auth->initialize($event);
 
 		$this->Controller->name = 'Error';
-		$this->assertTrue($this->Controller->Auth->startup($event));
+		$this->assertNull($this->Controller->Auth->startup($event));
 
 		$this->Controller->name = 'Post';
 		$this->Controller->request['action'] = 'thisdoesnotexist';
-		$this->assertTrue($this->Controller->Auth->startup($event));
+		$this->assertNull($this->Controller->Auth->startup($event));
 	}
 
 /**
@@ -215,16 +215,17 @@ class AuthComponentTest extends TestCase {
 		$this->Controller->request->addParams(Router::parse('auth_test/add'));
 		$this->Controller->Auth->initialize($event);
 		$result = $this->Controller->Auth->startup($event);
-		$this->assertTrue($result);
+		$this->assertNull($result);
 
 		$this->Auth->Session->delete('Auth');
 		$result = $this->Controller->Auth->startup($event);
-		$this->assertFalse($result);
+		$this->assertTrue($event->isStopped());
+		$this->assertInstanceOf('Cake\Network\Response', $result);
 		$this->assertTrue($this->Auth->Session->check('Message.auth'));
 
 		$this->Controller->request->addParams(Router::parse('auth_test/camelCase'));
 		$result = $this->Controller->Auth->startup($event);
-		$this->assertFalse($result);
+		$this->assertInstanceOf('Cake\Network\Response', $result);
 	}
 
 /**
@@ -381,52 +382,52 @@ class AuthComponentTest extends TestCase {
 		$this->Controller->Auth->deny('add', 'camelCase');
 
 		$this->Controller->request['action'] = 'delete';
-		$this->assertTrue($this->Controller->Auth->startup($event));
+		$this->assertNull($this->Controller->Auth->startup($event));
 
 		$this->Controller->request['action'] = 'add';
-		$this->assertFalse($this->Controller->Auth->startup($event));
+		$this->assertInstanceOf('Cake\Network\Response', $this->Controller->Auth->startup($event));
 
 		$this->Controller->request['action'] = 'camelCase';
-		$this->assertFalse($this->Controller->Auth->startup($event));
+		$this->assertInstanceOf('Cake\Network\Response', $this->Controller->Auth->startup($event));
 
 		$this->Controller->Auth->allow();
 		$this->Controller->Auth->deny(array('add', 'camelCase'));
 
 		$this->Controller->request['action'] = 'delete';
-		$this->assertTrue($this->Controller->Auth->startup($event));
+		$this->assertNull($this->Controller->Auth->startup($event));
 
 		$this->Controller->request['action'] = 'camelCase';
-		$this->assertFalse($this->Controller->Auth->startup($event));
+		$this->assertInstanceOf('Cake\Network\Response', $this->Controller->Auth->startup($event));
 
-		$this->Controller->Auth->allow('*');
+		$this->Controller->Auth->allow();
 		$this->Controller->Auth->deny();
 
 		$this->Controller->request['action'] = 'camelCase';
-		$this->assertFalse($this->Controller->Auth->startup($event));
+		$this->assertInstanceOf('Cake\Network\Response', $this->Controller->Auth->startup($event));
 
 		$this->Controller->request['action'] = 'add';
-		$this->assertFalse($this->Controller->Auth->startup($event));
+		$this->assertInstanceOf('Cake\Network\Response', $this->Controller->Auth->startup($event));
 
 		$this->Controller->Auth->allow('camelCase');
 		$this->Controller->Auth->deny();
 
 		$this->Controller->request['action'] = 'camelCase';
-		$this->assertFalse($this->Controller->Auth->startup($event));
+		$this->assertInstanceOf('Cake\Network\Response', $this->Controller->Auth->startup($event));
 
 		$this->Controller->request['action'] = 'login';
-		$this->assertFalse($this->Controller->Auth->startup($event));
+		$this->assertInstanceOf('Cake\Network\Response', $this->Controller->Auth->startup($event));
 
 		$this->Controller->Auth->deny();
 		$this->Controller->Auth->allow(null);
 
 		$this->Controller->request['action'] = 'camelCase';
-		$this->assertTrue($this->Controller->Auth->startup($event));
+		$this->assertNull($this->Controller->Auth->startup($event));
 
 		$this->Controller->Auth->allow();
 		$this->Controller->Auth->deny(null);
 
 		$this->Controller->request['action'] = 'camelCase';
-		$this->assertFalse($this->Controller->Auth->startup($event));
+		$this->assertInstanceOf('Cake\Network\Response', $this->Controller->Auth->startup($event));
 	}
 
 /**
@@ -444,12 +445,12 @@ class AuthComponentTest extends TestCase {
 		$this->Controller->request->addParams(Router::parse($url));
 		$this->Controller->request->query['url'] = Router::normalize($url);
 
-		$this->assertFalse($this->Controller->Auth->startup($event));
+		$this->assertInstanceOf('Cake\Network\Response', $this->Controller->Auth->startup($event));
 
 		$url = '/auth_test/CamelCase';
 		$this->Controller->request->addParams(Router::parse($url));
 		$this->Controller->request->query['url'] = Router::normalize($url);
-		$this->assertFalse($this->Controller->Auth->startup($event));
+		$this->assertInstanceOf('Cake\Network\Response', $this->Controller->Auth->startup($event));
 	}
 
 /**
@@ -467,7 +468,7 @@ class AuthComponentTest extends TestCase {
 		$this->Controller->Auth->userModel = 'AuthUsers';
 		$this->Controller->Auth->allow();
 		$result = $this->Controller->Auth->startup($event);
-		$this->assertTrue($result, 'startup() should return true, as action is allowed. %s');
+		$this->assertNull($result, 'startup() should return null, as action is allowed. %s');
 
 		$url = '/auth_test/camelCase';
 		$this->Controller->request->addParams(Router::parse($url));
@@ -477,11 +478,10 @@ class AuthComponentTest extends TestCase {
 		$this->Controller->Auth->userModel = 'AuthUsers';
 		$this->Controller->Auth->allowedActions = array('delete', 'camelCase', 'add');
 		$result = $this->Controller->Auth->startup($event);
-		$this->assertTrue($result, 'startup() should return true, as action is allowed. %s');
+		$this->assertNull($result, 'startup() should return null, as action is allowed. %s');
 
 		$this->Controller->Auth->allowedActions = array('delete', 'add');
-		$result = $this->Controller->Auth->startup($event);
-		$this->assertFalse($result, 'startup() should return false, as action is not allowed. %s');
+		$this->assertInstanceOf('Cake\Network\Response', $this->Controller->Auth->startup($event));
 
 		$url = '/auth_test/delete';
 		$this->Controller->request->addParams(Router::parse($url));
@@ -492,7 +492,7 @@ class AuthComponentTest extends TestCase {
 
 		$this->Controller->Auth->allow(array('delete', 'add'));
 		$result = $this->Controller->Auth->startup($event);
-		$this->assertTrue($result, 'startup() should return true, as action is allowed. %s');
+		$this->assertNull($result, 'startup() should return null, as action is allowed. %s');
 	}
 
 	public function testAllowedActionsSetWithAllowMethod() {
@@ -687,7 +687,7 @@ class AuthComponentTest extends TestCase {
 
 		$event = new Event('Controller.startup', $this->Controller);
 		$return = $this->Auth->startup($event);
-		$this->assertTrue($return);
+		$this->assertNull($return);
 		$this->assertNull($this->Controller->testUrl);
 	}
 
@@ -860,7 +860,7 @@ class AuthComponentTest extends TestCase {
 		$this->Auth->initialize($event);
 		$this->Auth->request->addParams(Router::parse('auth_test/something_totally_wrong'));
 		$result = $this->Auth->startup($event);
-		$this->assertTrue($result, 'Auth redirected a missing action %s');
+		$this->assertNull($result, 'Auth redirected a missing action %s');
 	}
 
 /**
@@ -904,19 +904,20 @@ class AuthComponentTest extends TestCase {
 			'environment' => ['HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest']
 		]);
 		$this->Controller->request->params['action'] = 'add';
-		$this->Controller->response->expects($this->once())->method('stop');
 
 		$event = new Event('Controller.startup', $this->Controller);
 		$this->Auth->config('ajaxLogin', 'test_element');
 		$this->Auth->RequestHandler->ajaxLayout = 'ajax2';
 		$this->Auth->initialize($event);
 
-		ob_start();
-		$this->Auth->startup($event);
-		$result = ob_get_clean();
+		$response = $this->Auth->startup($event);
 
-		$this->assertEquals(403, $this->Controller->response->statusCode());
-		$this->assertEquals("Ajax!\nthis is the test element", str_replace("\r\n", "\n", $result));
+		$this->assertTrue($event->isStopped());
+		$this->assertEquals(403, $response->statusCode());
+		$this->assertEquals(
+			"Ajax!\nthis is the test element",
+			str_replace("\r\n", "\n", $response->body())
+		);
 	}
 
 /**
@@ -1313,7 +1314,7 @@ class AuthComponentTest extends TestCase {
 		$this->Controller->request->env('PHP_AUTH_PW', 'cake');
 
 		$result = $this->Auth->startup($event);
-		$this->assertTrue($result);
+		$this->assertNull($result);
 
 		$this->assertNull(Session::id());
 	}
@@ -1331,8 +1332,7 @@ class AuthComponentTest extends TestCase {
 		$this->Auth->response->expects($this->never())->method('statusCode');
 		$this->Auth->response->expects($this->never())->method('send');
 
-		$result = $this->Auth->startup($event);
-		$this->assertFalse($result);
+		$this->assertInstanceOf('Cake\Network\Response', $this->Auth->startup($event));
 
 		$this->assertEquals('/users/login', $this->Controller->testUrl);
 	}

+ 22 - 13
tests/TestCase/Controller/ControllerTest.php

@@ -429,9 +429,9 @@ class ControllerTest extends TestCase {
 		$Controller = new Controller(null);
 		$Controller->response = new Response();
 
-		$Controller->redirect('http://cakephp.org', (int)$code, false);
-		$this->assertEquals($code, $Controller->response->statusCode());
-		$this->assertEquals('http://cakephp.org', $Controller->response->header()['Location']);
+		$response = $Controller->redirect('http://cakephp.org', (int)$code, false);
+		$this->assertEquals($code, $response->statusCode());
+		$this->assertEquals('http://cakephp.org', $response->header()['Location']);
 		$this->assertFalse($Controller->autoRender);
 	}
 
@@ -448,9 +448,9 @@ class ControllerTest extends TestCase {
 			$response->location('http://book.cakephp.org');
 		}, 'Controller.beforeRedirect');
 
-		$Controller->redirect('http://cakephp.org', 301, false);
-		$this->assertEquals('http://book.cakephp.org', $Controller->response->header()['Location']);
-		$this->assertEquals(301, $Controller->response->statusCode());
+		$response = $Controller->redirect('http://cakephp.org', 301, false);
+		$this->assertEquals('http://book.cakephp.org', $response->header()['Location']);
+		$this->assertEquals(301, $response->statusCode());
 	}
 
 /**
@@ -466,10 +466,10 @@ class ControllerTest extends TestCase {
 			$response->statusCode(302);
 		}, 'Controller.beforeRedirect');
 
-		$Controller->redirect('http://cakephp.org', 301, false);
+		$response = $Controller->redirect('http://cakephp.org', 301, false);
 
-		$this->assertEquals('http://cakephp.org', $Controller->response->header()['Location']);
-		$this->assertEquals(302, $Controller->response->statusCode());
+		$this->assertEquals('http://cakephp.org', $response->header()['Location']);
+		$this->assertEquals(302, $response->statusCode());
 	}
 
 /**
@@ -492,7 +492,8 @@ class ControllerTest extends TestCase {
 		$Controller->response->expects($this->never())
 			->method('statusCode');
 
-		$Controller->redirect('http://cakephp.org');
+		$result = $Controller->redirect('http://cakephp.org');
+		$this->assertNull($result);
 	}
 
 /**
@@ -631,7 +632,9 @@ class ControllerTest extends TestCase {
 					$this->attributeEqualTo('_name', 'Controller.initialize'),
 					$this->attributeEqualTo('_subject', $Controller)
 				)
-			);
+			)
+			->will($this->returnValue($this->getMock('Cake\Event\Event', null, [], '', false)));
+
 		$eventManager->expects($this->at(1))->method('dispatch')
 			->with(
 				$this->logicalAnd(
@@ -639,9 +642,12 @@ class ControllerTest extends TestCase {
 					$this->attributeEqualTo('_name', 'Controller.startup'),
 					$this->attributeEqualTo('_subject', $Controller)
 				)
-			);
+			)
+			->will($this->returnValue($this->getMock('Cake\Event\Event', null, [], '', false)));
+
 		$Controller->expects($this->exactly(2))->method('getEventManager')
 			->will($this->returnValue($eventManager));
+
 		$Controller->startupProcess();
 	}
 
@@ -661,9 +667,12 @@ class ControllerTest extends TestCase {
 					$this->attributeEqualTo('_name', 'Controller.shutdown'),
 					$this->attributeEqualTo('_subject', $Controller)
 				)
-			);
+			)
+			->will($this->returnValue($this->getMock('Cake\Event\Event', null, [], '', false)));
+
 		$Controller->expects($this->once())->method('getEventManager')
 			->will($this->returnValue($eventManager));
+
 		$Controller->shutdownProcess();
 	}
 

+ 3 - 11
tests/test_app/TestApp/Controller/AuthTestController.php

@@ -23,13 +23,6 @@ use Cake\Routing\Router;
 class AuthTestController extends Controller {
 
 /**
- * uses property
- *
- * @var array
- */
-	public $uses = array('Users');
-
-/**
  * components property
  *
  * @var array
@@ -109,12 +102,11 @@ class AuthTestController extends Controller {
  *
  * @param mixed $url
  * @param mixed $status
- * @param mixed $exit
- * @return void
+ * @return void|\Cake\Network\Response
  */
-	public function redirect($url, $status = null, $exit = true) {
+	public function redirect($url, $status = null) {
 		$this->testUrl = Router::url($url);
-		return false;
+		return parent::redirect($url, $status);
 	}
 
 /**