シムノート

PHPフレームワークSymfonyの学習帳

ユーザ用ツール

サイト用ツール


サイドバー

メニュー



このエントリーをはてなブックマークに追加

blog:2015-12-22:symfony入門_todoリストチュートリアル_ユーザー承認

Symfony3入門 TODOリストチュートリアル : (6) ユーザー承認

前回、ユーザーとタスクの間に1対多のリレーションを持たせました。これにより、タスク一覧画面にはログインしているユーザーが所有するタスクのみが表示されるようになりました。しかし、まだ問題があります。タスク編集用のURLで他のユーザが所有するタスクのIDを直接入力すると編集が出来てしまいます。

http://localhost:8000/task/{id}/edit

現在、{id}を変えれば、誰のタスクでも編集可能

この問題を解決する為に、タスクの編集処理と削除処理にユーザー承認(権限チェック)を追加します。

ハードコーディングでの承認

最初にコントローラー内に権限チェックのロジックをハードコーディングして承認してみます。

    // ...

    use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

    // ...
    public function editAction(Request $request, Task $task)
    {
        // タスクの所有者以外はアクセス拒否をする
        if ($this->getUser() !== $task->getOwner()) {
            throw new AccessDeniedHttpException();
        }
        // ...
    }
    
    public function deleteAction(Request $request, Task $task)
    {
        // タスクの所有者以外はアクセス拒否をする
        if ($this->getUser() !== $task->getOwner()) {
            throw new AccessDeniedHttpException();
        }
        // ...
    }    

これで、所有者だけがタスクを編集/削除できるように承認することができました。しかし、今回のような単純なチェックロジックならよいのですが、もう少し条件が複雑になった場合、そのロジックを何箇所にもコピ&ペーストして使うことになります。これでは保守性が落ちてしまし、スマートな方法とは言えません。そこで、次にSymfonyが提供するVoters(有権者)という機能を使って、承認を行うよう修正します。

Votersを使った承認

Controllerの修正

コントローラ内での承認を以下のように修正します。

    // ...
    public function editAction(Request $request, Task $task)
    {
        // 現在ログインしているユーザーが Taskに対して edit権限があるかチェック
        $this->denyAccessUnlessGranted('edit', $task);

        // ...
    }
    
    public function deleteAction(Request $request, Task $task)
    {
        // 現在ログインしているユーザーが Taskに対して delete権限があるかチェック
        $this->denyAccessUnlessGranted('delete', $task);

        // ...
    }    

Voterクラスの作成

Voterクラスを以下のように作成します。

<?php
namespace AppBundle\Security;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use AppBundle\Entity\Task;
use AppBundle\Entity\User;

class TaskVoter extends Voter
{
    const EDIT = 'edit';
    const DELETE = 'delete';

    /**
     *
     * {@inheritDoc}
     *
     * @see \Symfony\Component\Security\Core\Authorization\Voter\Voter::supports()
     */
    protected function supports($attribute, $subject)
    {
        if (!in_array($attribute, [self::EDIT, self::DELETE])) {
            return false;
        }
        
        if (!$subject instanceof Task) {
            return false;
        }
        
        return true;
    }

    /**
     *
     * {@inheritDoc}
     *
     * @see \Symfony\Component\Security\Core\Authorization\Voter\Voter::voteOnAttribute()
     */
    protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
    {
        $user = $token->getUser();
        
        if (!$user instanceof User) {
            return false;
        }
        
        /** @var Task $task */
        $task = $subject;
        
        switch ($attribute) {
            case self::EDIT:
                return $this->canEdit($task, $user);
            case self::DELETE:
                return $this->canDelete($task, $user);
        }
        
        throw new \LogicException('This code should not be reached!');
    }
    
    private function canEdit(Task $task, User $user) {
        return $user === $task->getOwner();
    }
    
    private function canDelete(Task $task, User $user) {
        return $user === $task->getOwner();
    }
}

Voterでは以下の2つのメソッドを実装する必要があります。

  • supports() サポートする属性のチェック
  • voteOnAttribute() 権限のチェック

コントローラ内でisGranted()denyAccessUnlessGranted()が実行される時、第1引数には$attribute(例、ROLE_ADMIN, edit)、第2引数には$subject(例、null, Taskオブジェクト)が渡されます。

最初にsupports()メソッドが実行されて$attribute$subjectがサポート対象かどうかをチェックします。supports()trueを返すと、voteOnAttribute()が実行されて、権限のチェックを行います。trueを返せばアクセス許可、falseを返せばアクセス拒否となります。

denyAccessUnlessGranted()内では$token->getUser()を使用することで、現在のユーザーを取得することができます。

Votersの詳細に関しては以下のURLを参照してください。

http://symfony.com/doc/current/cookbook/security/voters.html

設定

作成したTaskVoterクラスをSymfonyのサービスコンテナに登録します。

services:
    app.post_voter:
        class: AppBundle\Security\TaskVoter
        tags:
            - { name: security.voter }
        # small performance boost
        public: false

動作確認

http://localhost:8000/task/{id}/edit にアクセスして動作確認を行います。{id}をログインユーザーが所有するタスクのidに変更したり、他のユーザーのタスクidに変更して、アクセスの可否を確認してみてください。

まとめ

TODOリストチュートリアルは今回で終了となります。CRUD系のアプリケーションはCRUDジェネレータを使うことで簡単に作成出来たことと思います。Symfonyはジェネレータを使用しなくても、開発はしていけますが、最初の頃はジェネレータが生成したコードはとても参考になると思います。


Comments



180 -9​ = ?
blog/2015-12-22/symfony入門_todoリストチュートリアル_ユーザー承認.txt · 最終更新: 2015/12/22 22:51 by tsubo