Module based ErrorPresenter for NForbiddenRequestException from checkRequirements()

Notice: This thread is very old.
petr.pavel
Member | 533
+
0
-

Hi there,
I'd like to use annotations for presenter access control but it seems that NForbiddenRequestException thrown by checkRequirements() can only be processed by default error presenter (defined in config.neon).

I see that NPresenter->run() calls checkRequirements() before startup() so I don't think I can change the error presenter in my module before the exception occurs.

Is there any other way for setting custom error presenter, different for each module, earlier than in startup()?

I could implement my own checkRequirements() just for this purpose (to set the error presenter and call its parent). But I'm wondering if some nicer way exists.

Nette Framework 2.0.8 (revision b7f6732 released on 2013–01–01)

/**
 * @Secured
 * @User(loggedIn)
 */
enumag
Member | 2118
+
0
-

I've already tried this path but had to abandon it. The buggy use-case was that I have thrown an exception (like FailedAuthorizationException) in the checkRequirements, didn't catch it and let it go into ErrorPresenter. There I used a redirect to somewhere else (homepage of admin module). That redirect however caused another exception (because of a bug – missing template maybe) which again went all the way into ErrorPresenter and caused an infinite loop of redirects.

Yeah it probably can be avoided if you are careful with the redirects but I just didn't like it. Eventually I rewrote my checkRequirements method to call another method like this:

	public function checkRequirements($reflection)
	{
		try {
			// call a service to check the $reflection
		} catch (BadRequestException $e) {
			$this->processException($e);
			throw $e;
		}
	}

I'm not completly sure about this solution but it works fine.

As for module-specific ErrorPresenters it is pretty easy to do in Application. I'm just lazy to write the necessary tests so I didn't send a PR.

Last edited by enumag (2013-12-17 20:54)

petr.pavel
Member | 533
+
0
-

In my case, I don't redirect because a log-in form pop-up button exists on every page, including the 403 template.

Re Application: Do you recommend to implement my own Application and set error presenters for each module there? Would you happen to have a code that I could look at?

BTW: This thread seems related but it's missing a closure. Also, I didn't find a way to define redirects via annotations, in Nette 2.0.8. By any chance, would you know why David dropped it?

enumag
Member | 2118
+
0
-

Re Application: Do you recommend to implement my own Application and set error presenters for each module there? Would you happen to have a code that I could look at?

Sure:

<?php

namespace Enumag\Application;

use Nette\Application\AbortException;
use Nette\Application\Application as BaseApplication;
use Nette\Application\BadRequestException;
use Nette\Application\InvalidPresenterException;
use Nette\Application\IPresenterFactory;
use Nette\Application\IRouter;
use Nette\Application\Request;
use Nette\Application\UI\Presenter;
use Nette\Http\IRequest;
use Nette\Http\IResponse;

class Application extends BaseApplication
{

	/** @var IPresenterFactory */
	private $presenterFactory;

	/** @var IResponse */
	private $httpResponse;

	public function __construct(IPresenterFactory $presenterFactory, IRouter $router, IRequest $httpRequest, IResponse $httpResponse)
	{
		parent::__construct($presenterFactory, $router, $httpRequest, $httpResponse);
		$this->presenterFactory = $presenterFactory;
		$this->httpResponse = $httpResponse;
	}

	public function processException(\Exception $e)
	{
		if (!$this->httpResponse->isSent()) {
			$this->httpResponse->setCode($e instanceof BadRequestException ? ($e->getCode() ?: 404) : 500);
		}

		$requests = $this->getRequests();
		$request = end($requests);
		$args = array('exception' => $e, 'request' => $request ?: NULL);

		$name = $request->getPresenterName();
		$pos = strrpos($name, ':');
		$module = $pos !== FALSE ? substr($name, 0, $pos) : '';
		$errorPresenter = "$module:$this->errorPresenter";

		try {
			$this->presenterFactory->getPresenterClass($errorPresenter);
		} catch (InvalidPresenterException $_) {
			$errorPresenter = $this->errorPresenter;
		}

		if ($this->presenter instanceof Presenter) {
			try {
				$this->presenter->forward(":$errorPresenter:", $args);
			} catch (AbortException $_) {
				$this->processRequest($this->presenter->getLastCreatedRequest());
			}
		} else {
			$this->processRequest(new Request($errorPresenter, Request::FORWARD, $args));
		}
	}

}

BTW: This thread seems related but it's missing a closure. Also, I didn't find a way to define redirects via annotations, in Nette 2.0.8. By any chance, would you know why David dropped it?

I don't know why he dropped it but I'd never use it because I want to add a flash message before every redirect so the user would know what happened and why.

petr.pavel
Member | 533
+
0
-

Thanks! Interesting concept. I understand that the main advantage is that the error presenter gets the original request.

Not sure though, how this prevents an infinite loop. This code redirects too, only internally instead of externally. I guess I'd have to keep track of all request forwards and break the loop when a repeated request (with the same arguments) occurs.

enumag
Member | 2118
+
0
-

This doesn't. The problem that caused the enfinite loop was that I had the redirect in ErrorPresenter. Moving the redirect away from ErrorPresenter into the processException method prevents the loop. Basically ErrorPresenter should never use redirects in my opinion.

The Application class above does the same as Nette\Application\Application. The fact that ErrorPresenter gets the original request was not my idea, Nette is already doing that. The only added logic is searching for module-specific ErrorPresenters.

EDIT: Well now I'm thinking that the redirects in ErrorPresenters might not be so bad if the Application would prevent calling ErrorPresenter multiple times.

Last edited by enumag (2013-12-19 16:32)