Como usar o atributo #[Requires]

Ao escrever uma aplicação web, você frequentemente encontrará a necessidade de restringir o acesso a certas partes da sua aplicação. Talvez você queira que algumas requisições possam enviar dados apenas através de um formulário (ou seja, pelo método POST), ou que sejam acessíveis apenas para chamadas AJAX. No Nette Framework 3.2, surgiu uma nova ferramenta que permite definir tais restrições de forma muito elegante e clara: o atributo #[Requires].

Um atributo é uma marca especial em PHP que você adiciona antes da definição de uma classe ou método. Como na verdade é uma classe, para que os exemplos a seguir funcionem, é necessário incluir a cláusula use:

use Nette\Application\Attributes\Requires;

Você pode usar o atributo #[Requires] na própria classe do presenter e também nestes métodos:

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

Os dois últimos métodos também se aplicam a componentes, ou seja, você também pode usar o atributo neles.

Se as condições especificadas pelo atributo não forem atendidas, um erro HTTP 4xx será lançado.

Métodos HTTP

Você pode especificar quais métodos HTTP (como GET, POST, etc.) são permitidos para acesso. Por exemplo, se você quiser permitir o acesso apenas enviando um formulário, defina:

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

Por que você deve usar POST em vez de GET para ações que alteram o estado e como fazer isso? Leia o tutorial.

Você pode especificar um método ou um array de métodos. Um caso especial é o valor '*', que permite todos os métodos, o que os presenters normalmente não permitem por razões de segurança.

Chamada AJAX

Se você quiser que o presenter ou método esteja disponível apenas para requisições AJAX, use:

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

Mesma origem

Para aumentar a segurança, você pode exigir que a requisição seja feita do mesmo domínio. Isso evita a vulnerabilidade CSRF:

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

Para os métodos handle<Signal>(), o acesso do mesmo domínio é exigido automaticamente. Portanto, se, pelo contrário, você quiser permitir o acesso de qualquer domínio, especifique:

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

Acesso via forward

Às vezes, é útil restringir o acesso a um presenter para que ele esteja disponível apenas indiretamente, por exemplo, usando o método forward() ou switch() de outro presenter. Assim, por exemplo, protegem-se os error-presenters para que não possam ser chamados a partir da URL:

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

Na prática, muitas vezes é necessário marcar certas views às quais só se pode chegar com base na lógica do presenter. Ou seja, novamente, para que não possam ser abertas diretamente:

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

Ações específicas

Você também pode restringir que um determinado código, como a criação de um componente, esteja disponível apenas para ações específicas no presenter:

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

No caso de uma única ação, não é necessário escrever um array: #[Requires(actions: 'default')]

Atributos personalizados

Se você quiser usar o atributo #[Requires] repetidamente com as mesmas configurações, pode criar seu próprio atributo que herdará #[Requires] e o configurará de acordo com as necessidades.

Por exemplo, #[SingleAction] permitirá o acesso apenas através da ação default:

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

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

Ou #[RestMethods] permitirá o acesso através de todos os métodos HTTP usados para APIs 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
{
}

Conclusão

O atributo #[Requires] oferece grande flexibilidade e controle sobre como suas páginas web são acessíveis. Usando regras simples, mas poderosas, você pode aumentar a segurança e o funcionamento correto da sua aplicação. Como você pode ver, o uso de atributos no Nette pode não apenas facilitar seu trabalho, mas também torná-lo mais seguro.