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