Comment utiliser l'attribut #[Requires]

Quand vous écrivez une application web, vous rencontrez souvent le besoin de restreindre l'accès à certaines parties de votre application. Vous voulez peut-être que certaines requêtes ne puissent envoyer des données que par un formulaire (donc par la méthode POST), ou qu'elles ne soient accessibles qu'aux appels AJAX. Nette Framework 3.2 a introduit un nouvel outil qui vous permet de poser de telles restrictions de façon élégante et claire : l'attribut #[Requires].

Un attribut est un marqueur particulier en PHP que vous ajoutez avant la définition d'une classe ou d'une méthode. Comme il s'agit au fond d'une classe, vous devez ajouter la clause use pour que les exemples suivants fonctionnent :

use Nette\Application\Attributes\Requires;

Vous pouvez utiliser l'attribut #[Requires] sur la classe du presenter elle-même et sur ces méthodes :

  • action<Action>()
  • render<View>()
  • handle<Signal>()
  • createComponent<Name>()

Les deux dernières méthodes valent aussi pour les composants, vous pouvez donc utiliser l'attribut avec eux également.

Si les conditions posées par l'attribut ne sont pas remplies, une erreur HTTP 4xx est déclenchée.

Méthodes HTTP

Vous pouvez indiquer quelles méthodes HTTP (comme GET, POST, etc.) sont autorisées pour l'accès. Par exemple, si vous voulez n'autoriser l'accès que par l'envoi d'un formulaire, écrivez :

class AdminPresenter extends Nette\Application\UI\Presenter
{
	#[Requires(methods: 'POST')]
	public function actionDelete(int $id): void
	{
	}
}

Pourquoi utiliser POST plutôt que GET pour les actions qui changent l'état, et comment faire ? Lisez le guide.

Vous pouvez indiquer une méthode ou un tableau de méthodes. Un cas particulier est la valeur '*', qui autorise toutes les méthodes, ce que les presenters n'autorisent pas par défaut pour des raisons de sécurité.

Appels AJAX

Si vous voulez qu'un presenter ou une méthode ne soit accessible qu'aux requêtes AJAX, utilisez :

#[Requires(ajax: true)]
class AjaxPresenter extends Nette\Application\UI\Presenter
{
}

Même origine

Pour renforcer la sécurité, vous pouvez exiger que la requête provienne du même domaine. Cela évite la faille CSRF :

#[Requires(sameOrigin: true)]
class SecurePresenter extends Nette\Application\UI\Presenter
{
}

Pour les méthodes handle<Signal>(), l'accès depuis le même domaine est exigé automatiquement. Si vous voulez donc autoriser l'accès depuis n'importe quel domaine, indiquez :

#[Requires(sameOrigin: false)]
public function handleList(): void
{
}

Accès par forward

Il est parfois utile de restreindre l'accès à un presenter de sorte qu'il ne soit accessible qu'indirectement, par exemple à l'aide des méthodes forward() ou switch() depuis un autre presenter. C'est ainsi que sont protégés, par exemple, les presenters d'erreur, afin qu'ils ne puissent pas être déclenchés depuis une URL :

#[Requires(forward: true)]
class ForwardedPresenter extends Nette\Application\UI\Presenter
{
}

En pratique, il est souvent nécessaire de marquer certaines vues qui ne sont accessibles que sur la base d'une logique dans le presenter. Là encore, pour qu'elles ne puissent pas être ouvertes directement :

class ProductPresenter extends Nette\Application\UI\Presenter
{

	public function actionDefault(int $id): void
	{
		$product = $this->facade->getProduct($id);
		if (!$product) {
			$this->setView('notfound');
		}
	}

	#[Requires(forward: true)]
	public function renderNotFound(): void
	{
	}
}

Actions précises

Vous pouvez aussi restreindre certain code, comme la création d'un composant, aux seules actions précises du presenter :

class EditDeletePresenter extends Nette\Application\UI\Presenter
{
	#[Requires(actions: ['add', 'edit'])]
	public function createComponentPostForm()
	{
	}
}

Dans le cas d'une seule action, il n'est pas nécessaire d'écrire un tableau : #[Requires(actions: 'default')]

Attributs personnalisés

Si vous voulez utiliser l'attribut #[Requires] à plusieurs reprises avec les mêmes réglages, vous pouvez créer votre propre attribut qui hérite de #[Requires] et le configure selon vos besoins.

Par exemple, #[SingleAction] n'autorise l'accès que par l'action default :

#[\Attribute]
class SingleAction extends Nette\Application\Attributes\Requires
{
	public function __construct()
	{
		parent::__construct(actions: 'default');
	}
}

#[SingleAction]
class SingleActionPresenter extends Nette\Application\UI\Presenter
{
}

Ou #[RestMethods] autorisera l'accès par toutes les méthodes HTTP utilisées pour une API REST :

#[\Attribute]
class RestMethods extends Nette\Application\Attributes\Requires
{
	public function __construct()
	{
		parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
	}
}

#[RestMethods]
class ApiPresenter extends Nette\Application\UI\Presenter
{
}

Conclusion

L'attribut #[Requires] vous donne une grande souplesse et un vrai contrôle sur la façon dont vos pages web sont accessibles. À l'aide de règles simples mais puissantes, vous pouvez renforcer la sécurité et le bon fonctionnement de votre application. Comme vous le voyez, l'usage des attributs dans Nette peut non seulement simplifier votre travail, mais aussi le sécuriser.