CMSチュートリアル-承認

ユーザーがCMSにログインできるようになったため、承認ルールを適用して、各ユーザーが自分の所有する投稿のみを編集するようにします。 これには承認プラグインを使用します。

With users now able to login to our CMS, we want to apply authorization rules to ensure that each user only edits the posts they own. We’ll use the authorization plugin to do this.

Authorizationプラグインのインストール

composerを使用して、承認プラグインをインストールします:

Use composer to install the Auhorization Plugin:
composer require cakephp/authorization:^2.0

src/Application.phpbootstrap()メソッドに次のステートメントを追加してプラグインをロードします:

Load the plugin by adding the following statement to the bootstrap() method in src/Application.php:
$this->addPlugin('Authorization');

Authorizationプラグインを有効にする

Authorizationプラグインは、ミドルウェアレイヤーとしてアプリケーションに統合され、オプションでコンポーネントを統合して、認証のチェックを容易にします。 まず、ミドルウェアを適用しましょう。 src/Application.phpで、クラスimportsに以下を追加します:

The Authorization plugin integrates into your application as a middleware layer and optionally a component to make checking authorization easier. First, lets apply the middleware. In src/Application.php add the following to the class imports:
use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Middleware\AuthorizationMiddleware;
use Authorization\Policy\OrmResolver;
use Psr\Http\Message\ResponseInterface;

AuthorizationProviderInterfaceをアプリケーションの実装済みインターフェースに追加します:

Add the AuthorizationProviderInterface to the implemented interfaces on your application:
class Application extends BaseApplication
    implements AuthenticationServiceProviderInterface,
    AuthorizationServiceProviderInterface

次に、以下をmiddleware()メソッドに追加します:

Then add the following to your middleware() method:
// Add authorization **after** authentication
$middlewareQueue->add(new AuthorizationMiddleware($this));

AuthorizationMiddlewareは、リクエストの処理を開始すると、アプリケーションのフックメソッドを呼び出します。 このフックメソッドを使用すると、アプリケーションで使用するAuthorizationServiceを定義できます。 次のメソッドをsrc/Application.phpに追加します:

The AuthorizationMiddleware will call a hook method on your application when it starts handling the request. This hook method allows your application to define the AuthorizationService it wants to use. Add the following method your src/Application.php:
public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
{
    $resolver = new OrmResolver();

    return new AuthorizationService($resolver);
}

OrmResolverを使用すると、承認プラグインでORMエンティティとクエリのポリシークラスを検索できます。 他のリゾルバーを使用して、他のリソースタイプのポリシーを見つけることができます。

The OrmResolver lets the authorization plugin find policy classes for ORM entities and queries. Other resolvers can be used to find policies for other resources types.

次に、AuthorizationComponentAppControllerに追加します。 src/Controller/AppController.phpで、次のコードをinitialize()メソッドに追加します:

Next, lets add the AuthorizationComponent to AppController. In src/Controller/AppController.php add the following to the initialize() method:
$this->loadComponent('Authorization.Authorization');

最後に、次のコードをsrc/Controller/UsersController.phpに追加して、追加、ログイン、ログアウトのアクションに認証が不要であることを示します:

Lastly we’ll mark the add, login, and logout actions as not requiring authorization by adding the following to src/Controller/UsersController.php:
// In the add, login, and logout methods
$this->Authorization->skipAuthorization();

skipAuthorization()メソッドは、まだログインしていないユーザーも含め、すべてのユーザーがアクセスできるコントローラーアクションで呼び出す必要があります。

The skipAuthorization() method should be called in any controller action that should be accessible to all users even those who have not logged in yet.

最初のポリシーの作成

Authorizationプラグインは、認可と権限をポリシークラスとしてモデル化します。 これらのクラスは、特定のリソースIDアクションを実行できるかどうかを確認するロジックを実装します。 私たちのアイデンティティはログインしたユーザーであり、私たちのリソースはORMエンティティとクエリです。 ベイクを使用して基本的なポリシーを生成してみましょう:

The Authorization plugin models authorization and permissions as Policy classes. These classes implement the logic to check whether or not a identity is allowed to perform an action on a given resource. Our identity is going to be our logged in user, and our resources are our ORM entities and queries. Lets use bake to generate a basic policy:
bin/cake bake policy --type entity Article

これにより、Articleエンティティの空のポリシークラスが生成されます。 生成されたポリシーは、src/Policy/ArticlePolicy.phpにあります。 次に、ポリシーを次のように更新します:

This will generate an empty policy class for our Article entity. You can find the generated policy in src/Policy/ArticlePolicy.php. Next update the policy to look like the following:
<?php
namespace App\Policy;

use App\Model\Entity\Article;
use Authorization\IdentityInterface;

class ArticlePolicy
{
    public function canAdd(IdentityInterface $user, Article $article)
    {
        // All logged in users can create articles.
        return true;
    }

    public function canEdit(IdentityInterface $user, Article $article)
    {
        // logged in users can edit their own articles.
        return $this->isAuthor($user, $article);
    }

    public function canDelete(IdentityInterface $user, Article $article)
    {
        // logged in users can delete their own articles.
        return $this->isAuthor($user, $article);
    }

    protected function isAuthor(IdentityInterface $user, Article $article)
    {
        return $article->user_id === $user->getIdentifier();
    }
}

いくつかの非常に単純なルールを定義しましたが、アプリケーションがポリシーで要求するのと同じくらい複雑なロジックを使用できます。

While we’ve defined some very simple rules, you can use as complex logic as your application requires in your policies.

ArticlesControllerで承認を確認する

ポリシーを作成したら、コントローラーのアクションごとに承認の確認を開始できます。 コントローラーアクションで承認の確認またはスキップを忘れた場合、承認プラグインは例外を発生させ、承認の適用を忘れたことを知らせます。 src/Controller/ArticlesController.phpで、addeditdeleteメソッドに以下を追加します:

With our policy created we can start checking authorization in each controller action. If we forget to check or skip authorization in an controller action the Authorization plugin will raise an exception letting us know we forgot to apply authorization. In src/Controller/ArticlesController.php add the following to the add, edit and delete methods:
public function add()
{
    $article = $this->Articles->newEmptyEntity();
    $this->Authorization->authorize($article);
    // Rest of the method
}

public function edit($slug)
{
    $article = $this->Articles
        ->findBySlug($slug)
        ->contain('Tags') // load associated Tags
        ->firstOrFail();
    $this->Authorization->authorize($article);
    // Rest of the method.
}

public function delete($slug)
{
    $this->request->allowMethod(['post', 'delete']);

    $article = $this->Articles->findBySlug($slug)->firstOrFail();
    $this->Authorization->authorize($article);
    // Rest of the method.
}

AuthorizationComponent::authorize()メソッドは、現在のコントローラーアクション名を使用して、呼び出すポリシーメソッドを生成します。 別のポリシーメソッドを呼び出す場合は、オペレーション名を指定してauthorizeを呼び出すことができます:

The AuthorizationComponent::authorize() method will use the current controller action name to generate the policy method to call. If you’d like to call a different policy method you can call authorize with the operation name:
$this->Authorization->authorize($article, 'update');

最後に、ArticlesControllerタグビュー、およびインデックスメソッドに以下を追加します。

Lastly add the following to the tags, view, and index methods on the ArticlesController:
// View, index and tags actions are public methods
// and don't require authorization checks.
$this->Authorization->skipAuthorization();

追加および編集アクションの修正

編集アクションへのアクセスはブロックされていますが、編集中に記事のuser_id属性を変更するユーザーは引き続き利用できます。 次にこれらの問題を解決します。 最初は追加アクションです。

While we’ve blocked access to the edit action, we’re still open to users changing the user_id attribute of articles during edit. We will solve these problems next. First up is the add action.

記事を作成するとき、user_idを現在ログインしているユーザーに修正します。 追加アクションを次のように置き換えます:

When creating articles, we want to fix the user_id to be the currently logged in user. Replace your add action with the following:
// in src/Controller/ArticlesController.php

public function add()
{
    $article = $this->Articles->newEmptyEntity();
    $this->Authorization->authorize($article);

    if ($this->request->is('post')) {
        $article = $this->Articles->patchEntity($article, $this->request->getData());

        // Changed: Set the user_id from the current user.
        $article->user_id = $this->request->getAttribute('identity')->getIdentifier();

        if ($this->Articles->save($article)) {
            $this->Flash->success(__('Your article has been saved.'));
            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error(__('Unable to add your article.'));
    }
    $tags = $this->Articles->Tags->find('list');
    $this->set(compact('article', 'tags'));
}

次に、編集アクションを更新します。 編集メソッドを次のものに置き換えます。

Next we’ll update the edit action. Replace the edit method with the following:
// in src/Controller/ArticlesController.php

public function edit($slug)
{
    $article = $this->Articles
        ->findBySlug($slug)
        ->contain('Tags') // load associated Tags
        ->firstOrFail();
    $this->Authorization->authorize($article);

    if ($this->request->is(['post', 'put'])) {
        $this->Articles->patchEntity($article, $this->request->getData(), [
            // Added: Disable modification of user_id.
            'accessibleFields' => ['user_id' => false]
        ]);
        if ($this->Articles->save($article)) {
            $this->Flash->success(__('Your article has been updated.'));
            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error(__('Unable to update your article.'));
    }
    $tags = $this->Articles->Tags->find('list');
    $this->set(compact('article', 'tags'));
}

ここでは、patchEntity()のオプションを使用して、どのプロパティを一括割り当てできるかを変更しています。 詳細については、「アクセス可能なフィールドの変更」セクションを参照してください。 不要になったため、templates/Articles/edit.phpからuser_idコントロールを削除することを忘れないでください。

Here we’re modifying which properties can be mass-assigned, via the options for patchEntity(). See the Changing Accessible Fields section for more information. Remember to remove the user_id control from templates/Articles/edit.php as we no longer need it.

まとめ

ユーザーがログインし、記事を投稿し、タグを付け、投稿した記事をタグで探索できるようにするシンプルなCMSアプリケーションを構築し、基本的なアクセス制御を記事に適用しました。 また、FormHelperとORMの機能を活用することで、UXを改善しました。

We’ve built a simple CMS application that allows users to login, post articles, tag them, explore posted articles by tag, and applied basic access control to articles. We’ve also added some nice UX improvements by leveraging the FormHelper and ORM capabilities.

CakePHPを探求していただきありがとうございます。 次に、データベースアクセスとORMについてさらに学習するか、CakePHPの使用を熟読する必要があります。

Thank you for taking the time to explore CakePHP. Next, you should learn more about the Database Access & ORM, or you peruse the Using CakePHP.