Как да използваме атрибута #[Requires]

Когато пишете уеб приложение, често се сблъсквате с необходимостта да ограничите достъпа до определени части от вашето приложение. Може би искате някои заявки да могат да изпращат данни само чрез формуляр (т.е. с метод POST), или да бъдат достъпни само за AJAX извиквания. В Nette Framework 3.2 се появи нов инструмент, който ви позволява да настроите такива ограничения много елегантно и прегледно: атрибутът #[Requires].

Атрибутът е специална маркировка в PHP, която добавяте преди дефиницията на клас или метод. Тъй като всъщност е клас, за да работят следващите примери, е необходимо да се посочи клаузата use:

use Nette\Application\Attributes\Requires;

Атрибутът #[Requires] можете да използвате при самия клас на презентера, както и на тези методи:

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

Последните два метода се отнасят и до компоненти, т.е. атрибутът можете да използвате и при тях.

Ако не са изпълнени условията, които атрибутът посочва, ще се предизвика HTTP грешка 4xx.

Методи HTTP

Можете да специфицирате кои HTTP методи (като GET, POST и т.н.) са разрешени за достъп. Например, ако искате да разрешите достъп само чрез изпращане на формуляр, настройте:

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

Защо трябва да използвате POST вместо GET за действия, променящи състоянието, и как да го направите? Прочетете ръководството.

Можете да посочите метод или масив от методи. Специален случай е стойността '*', която разрешава всички методи, което стандартно презентерите от съображения за сигурност не позволяват.

AJAX извикване

Ако искате презентерът или методът да бъдат достъпни само за AJAX заявки, използвайте:

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

Същият произход

За повишаване на сигурността можете да изисквате заявката да бъде направена от същия домейн. С това предотвратявате уязвимостта CSRF:

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

При методите handle<Signal>() достъпът от същия домейн се изисква автоматично. Така че, ако обратно искате да разрешите достъп от всеки домейн, посочете:

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

Достъп чрез forward

Понякога е полезно да се ограничи достъпът до презентера така, че да бъде достъпен само непряко, например с използването на метода forward() или switch() от друг презентер. Така например се защитават error-presenter-ите, за да не могат да бъдат извикани от URL:

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

На практика често е необходимо да се маркират определени views, до които може да се стигне едва въз основа на логиката в презентера. Тоест отново, за да не могат да бъдат отворени директно:

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

Конкретни действия

Можете също така да ограничите, че определен код, например създаване на компонент, ще бъде достъпен само за специфични действия в презентера:

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

В случай на едно действие не е необходимо да се записва масив: #[Requires(actions: 'default')]

Собствени атрибути

Ако искате да използвате атрибута #[Requires] многократно със същите настройки, можете да си създадете собствен атрибут, който ще наследява #[Requires] и ще го настрои според нуждите.

Например #[SingleAction] ще позволи достъп само чрез действието default:

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

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

Или #[RestMethods] ще позволи достъп чрез всички HTTP методи, използвани за 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
{
}

Заключение

Атрибутът #[Requires] ви дава голяма гъвкавост и контрол върху това как са достъпни вашите уеб страници. С помощта на прости, но мощни правила можете да повишите сигурността и правилното функциониране на вашето приложение. Както виждате, използването на атрибути в Nette може не само да улесни вашата работа, но и да я обезопаси.