Jak używać atrybutu #[Requires] Atrybut

Podczas pisania aplikacji internetowej często pojawia się potrzeba ograniczenia dostępu do niektórych jej części. Być może chcesz, aby niektóre żądania mogły wysyłać dane tylko za pośrednictwem formularza (a więc przy użyciu metody POST) lub aby były dostępne tylko dla wywołań AJAX. W Nette Framework 3.2 wprowadzono nowe narzędzie, które pozwala ustawić takie ograniczenia w elegancki i przejrzysty sposób: atrybut #[Requires] atrybut.

Atrybut jest specjalnym znacznikiem w PHP, który dodaje się przed definicją klasy lub metody. Ponieważ jest to zasadniczo klasa, musisz dołączyć klauzulę use, aby poniższe przykłady działały:

use Nette\Application\Attributes\Requires;

Atrybutu #[Requires] z samą klasą prezentera i tymi metodami:

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

Dwie ostatnie metody również dotyczą komponentów, więc można użyć atrybutu również z nimi.

Jeśli warunki określone przez atrybut nie są spełnione, wywoływany jest błąd HTTP 4xx.

Metody HTTP

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

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

Dlaczego należy używać POST zamiast GET do akcji zmieniających stan i jak to zrobić? Przeczytaj przewodnik.

Można określić metodę lub tablicę metod. Szczególnym przypadkiem jest wartość '*', która włącza wszystkie metody, na które prezentery nie pozwalają domyślnie ze względów bezpieczeństwa.

Wywołania AJAX

Jeśli chcesz, aby prezenter lub metoda były dostępne tylko dla żądań AJAX, użyj:

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

To samo pochodzenie

Aby zwiększyć bezpieczeństwo, można wymagać, aby żądanie zostało wykonane z tej samej domeny. Zapobiega to podatności na CSRF:

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

Dla handle<Signal>() automatycznie wymagany jest dostęp z tej samej domeny. Jeśli więc chcesz zezwolić na dostęp z dowolnej domeny, określ:

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

Dostęp przez Forward

Czasami przydatne jest ograniczenie dostępu do prezentera, tak aby był on dostępny tylko pośrednio, na przykład przy użyciu metod forward() lub switch() z innego prezentera. W ten sposób chronione są prezentery błędów, aby uniemożliwić ich wywołanie z adresu URL:

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

W praktyce często konieczne jest oznaczenie pewnych widoków, do których można uzyskać dostęp tylko w oparciu o logikę w prezenterze. 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 działania

Można również ograniczyć dostęp do określonego kodu, takiego jak tworzenie komponentu, tylko dla określonych akcji w prezenterze:

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

Dla pojedynczej akcji nie ma potrzeby pisania tablicy: #[Requires(actions: 'default')]

Atrybuty niestandardowe

Jeśli chcesz używać atrybutu #[Requires] z tymi samymi ustawieniami, można utworzyć własny atrybut, który będzie dziedziczył #[Requires] i ustawić go zgodnie z własnymi potrzebami.

Na przykład, #[SingleAction] zezwala na dostęp tylko poprzez 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 za pośrednictwem wszystkich metod HTTP używanych w interfejsie 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
{
}

Wnioski

Atrybut #[Requires] zapewnia dużą elastyczność i kontrolę nad sposobem dostępu do stron internetowych. Korzystając z prostych, ale potężnych reguł, można zwiększyć bezpieczeństwo i prawidłowe funkcjonowanie aplikacji. Jak widać, korzystanie z atrybutów w Nette może nie tylko uprościć pracę, ale także ją zabezpieczyć.