Как использовать #[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() из другого презентатора. Так защищаются презентаторы ошибок, чтобы их нельзя было вызвать с URL:

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

На практике часто возникает необходимость пометить определенные представления, доступ к которым возможен только на основе логики в презентере. Опять же, чтобы их нельзя было открыть напрямую:

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 может не только упростить вашу работу, но и обезопасить ее.