#[Requires] アトリビュートの使い方

ウェブアプリケーションを書いていると、アプリケーションの一部へのアクセスを制限したくなる場面によく出会います。あるリクエストはフォーム経由(つまり POST メソッド)でしかデータを送れないようにしたい、あるいは AJAX の呼び出しからだけアクセスできるようにしたい、といった具合です。Nette Framework 3.2 では、そうした制限をきれいに分かりやすく設定できる新しい道具が導入されました。#[Requires] アトリビュートです。

アトリビュートとは、クラスやメソッドの定義の前に書く PHP の特別な印です。実体はクラスなので、以下の例を動かすには use 句が必要です。

use Nette\Application\Attributes\Requires;

#[Requires] アトリビュートは、プレゼンターのクラス自体と、次のメソッドに使えます。

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

最後の 2 つはコンポーネントにも当てはまるので、コンポーネントでもこのアトリビュートを使えます。

アトリビュートが定める条件が満たされない場合、HTTP 4xx のエラーが発生します。

HTTP メソッド

アクセスを許す HTTP メソッド(GET、POST など)を指定できます。たとえばフォームの送信でのみアクセスを許したいなら、次のようにします。

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

状態を変える操作になぜ GET ではなく POST を使うべきなのか、そしてどう使うのかは、ガイドをお読みください

メソッドはひとつでも、配列でも指定できます。特別なのは値 '*' で、すべてのメソッドを許します。プレゼンターは安全のため既定ではそれを許しません

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] は、REST API で使うすべての HTTP メソッドでのアクセスを許します。

#[\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 でアトリビュートを使うことは作業を簡単にするだけでなく、安全にもしてくれるのです。