Jak używać atrybutu #[Requires]

Kiedy piszesz aplikację internetową, często spotykasz się z potrzebą ograniczenia dostępu do określonych części Twojej aplikacji. Być może chcesz, aby niektóre żądania mogły wysyłać dane tylko za pomocą formularza (czyli metodą POST), lub aby były dostępne tylko dla wywołań AJAX. W Nette Framework 3.2 pojawiło się nowe narzędzie, które pozwoli Ci ustawić takie ograniczenia bardzo elegancko i przejrzyście: atrybut #[Requires].

Atrybut to specjalny znacznik w PHP, który dodajesz przed definicją klasy lub metody. Ponieważ jest to właściwie klasa, aby poniższe przykłady działały, konieczne jest podanie klauzuli use:

use Nette\Application\Attributes\Requires;

Atrybut #[Requires] możesz użyć przy samej klasie presentera, a także przy tych metodach:

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

Ostatnie dwie metody dotyczą również komponentów, więc atrybut możesz używać również przy nich.

Jeśli nie są spełnione warunki, które atrybut podaje, dojdzie do wywołania błędu HTTP 4xx.

Metody HTTP

Możesz określić, które metody HTTP (jak GET, POST itp.) są dozwolone dla dostępu. Na przykład, jeśli chcesz zezwolić na dostęp tylko przez wysłanie formularza, ustawisz:

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

Dlaczego powinieneś używać POST zamiast GET dla akcji zmieniających stan i jak to zrobić? Przeczytaj poradnik.

Możesz podać metodę lub tablicę metod. Specjalnym przypadkiem jest wartość '*', która zezwoli na wszystkie metody, czego standardowo presentery z powodów bezpieczeństwa nie pozwalają.

Wywołanie AJAX

Jeśli chcesz, aby presenter lub metoda była dostępna tylko dla żądań AJAX, użyj:

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

To samo pochodzenie

Dla zwiększenia bezpieczeństwa możesz wymagać, aby żądanie zostało wykonane z tej samej domeny. Tym samym zapobiegniesz podatności CSRF:

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

W przypadku metod handle<Signal>() dostęp z tej samej domeny jest wymagany automatycznie. Więc jeśli odwrotnie chcesz zezwolić na dostęp z dowolnej domeny, podaj:

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

Dostęp przez forward

Czasami przydatne jest ograniczenie dostępu do presentera tak, aby był dostępny tylko pośrednio, na przykład używając metody forward() lub switch() z innego presentera. W ten sposób na przykład chroni się error-presentery, aby nie można było ich wywołać z URL:

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

W praktyce często bywa potrzeba oznaczenia określonych widoków, do których można dostać się dopiero na podstawie logiki w presenterze. Czyli ponownie, aby nie można było ich otworzyć bezpośrednio:

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
	{
	}
}

Konkretne akcje

Możesz również ograniczyć, że określony kod, na przykład utworzenie komponentu, będzie dostępny tylko dla specyficznych akcji w presenterze:

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

W przypadku jednej akcji nie trzeba zapisywać tablicy: #[Requires(actions: 'default')]

Własne atrybuty

Jeśli chcesz użyć atrybutu #[Requires] wielokrotnie z tym samym ustawieniem, możesz stworzyć własny atrybut, który będzie dziedziczył #[Requires] i ustawi go według potrzeb.

Na przykład #[SingleAction] umożliwi dostęp tylko przez akcję default:

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

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

Lub #[RestMethods] umożliwi dostęp przez wszystkie metody HTTP używane dla REST API:

#[\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
{
}

Zakończenie

Atrybut #[Requires] daje Ci dużą elastyczność i kontrolę nad tym, jak dostępne są Twoje strony internetowe. Za pomocą prostych, ale potężnych reguł możesz zwiększyć bezpieczeństwo i prawidłowe funkcjonowanie Twojej aplikacji. Jak widzisz, użycie atrybutów w Nette może Twoją pracę nie tylko ułatwić, ale i zabezpieczyć.