CS-Cart:自前コントローラをアクセス権限制御する

ここでは、CS-Cartのアドオン開発時に追加したコントローラへのアクセス権限をどうやって制御するか説明します。

## CS-Cartにはアカウントタイプが3つある

CS-Cartには管理者(A)、出品者(V)、お客様(C)の3種類のアカウントタイプがあり、何もしなくても最低限、以下のアクセス制御をしてくれます。

* A: index.php, admin.phpのアクセス許可, vendor.phpは拒否
* V: index.php, vendor.phpのアクセス許可, admin.phpは拒否
* C: index.phpのみアクセス許可

## コントローラ単位でのアクセス制御

CS-Cartでは管理画面のユーザグループで権限を設定できることからわかりますが、コントローラ単位でのアクセス制御も行うことができます。

ただ、サードパーティのアドオン側が「ユーザーグループ」の「権限」に権限を足す方法や、アクセス権限のロジックについては一切ドキュメント化されておらず、ソースコードから手探りで仕様を確認することになりました…。冗談きついなぁ。

下記が、アクセス制御フローの概要です。ご覧のとおり、分岐が複雑ですが、

* 原則、Aならデフォルトでなんでもできる(図で常に左を選ぶパターン)
* 原則、Vはデフォルトでなんにもできない(図で常に右を選ぶパターン)
* 宣言すると、例外的にできる・できないを変えられる

という風に覚えておけば差し支えないかと思います。

<img src="https://qiita-image-store.s3.amazonaws.com/0/889/64e607e3-b9ca-8a82-f46c-ad6a11e6660b.png" class="img-thumbnail">

## サードパーティアドオンの権限項目を追加する方法

まず、CS-Cartがどのように「ユーザグループ」の「権限」に権限項目を出しているかですが、単純に、`cscart_privileges`テーブルのデータを見ているだけです。

`cscart_privileges`テーブルの`privilege`は権限名。`is_default`はデフォルトで権限を付与するかどうか。`section_id`は、「権限」のUIのどのセクションに出すかの指定です。

つまり、自分が作っているアドオンで権限項目を追加するには、このテーブルに行を足せばいいわけですが、インストール時に権限項目が追加されるよう、addon.xmlにSQLを書いておきます。以下は`manage_hogehoge`という権限を追加する例です。なお、`manage_hogehoge`などの文字列はなんでもいいですが、他の権限項目を見てみると`manage_{コントローラ名}`といった命名規則になっているようです。

```xml:addon.xml
<?xml version="1.0"?>
<addon scheme="3.0">
    ...
    <queries>
        <item>REPLACE INTO ?:privileges (privilege, is_default, section_id) VALUES ('manage_hogehoge', 'Y', 'addons');</item>
        <item for="uninstall">DELETE FROM ?:privileges WHERE privilege = 'manage_hogehoge'</item>
    </queries>
    ...
</addon>
```

配布を考えていないアドオンや、既にインストール済みのアドオンの場合は、率直に`cscart_privileges`テーブルに`INSERT`をかましてOKです。

`cscart_privileges`テーブルに権限項目を増やすと、すぐに「権限」に追加した項目が現れます。まだ翻訳がないので、翻訳を追加しておきます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/889/a41b9af1-cc08-4ac7-4f69-53c2d68b05f2.png" class="img-thumbnail">

## 管理者(A)向けのコントローラ権限を宣言する

前述のとおり、管理者は「デフォルトでなんでもできる」というポリシーなので、グループの権限設定によって管理者のアクセスを拒否するには、アドオン側で宣言する必要があります。

宣言は、`app/addons/[addon-id]/schemas/permissions/admin.post.php`ファイルで行います。下記はその例で、`hogehoge`コントローラへの権限は、権限項目`manage_hogehoge`を参照しますよ、という意味になります。コントローラ名はURLの`dispatch`パラメータで渡ってくる文字列のドット以前のものです。

```php:app/addons/[addon-id]/schemas/permissions/admin.post.php
<?php

$schema['hogehoge'] = [
    'permissions' => 'manage_hogehoge',
];

return $schema;
```

上の例は、`hogehoge`コントローラ全体の権限になりますが、コントローラにはmode(add, update, delete, manageなど)が生えている場合があり、モードごとに権限制御することもできます。下のコードがその例です。

```php:app/addons/[addon-id]/schemas/permissions/admin.post.php
<?php

$schema['hogehoge'] = [
    'permissions' => 'manage_hogehoge', // add, update以外のとき
    'modes'       => [
        'add'    => ['permissions' => 'add_hogehoge'], // addのとき
        'update' => ['permissions' => 'update_hogehoge'], // updateのとき
    ],
];

return $schema;
```

## 出品者(V)向けのコントローラ権限を宣言する

前述のとおり、出品者はデフォルトで「なにもできない」がポリシーです。従って、出品者は一切アクセスできなくてよい場合は、何も宣言しなくていいです。一方で、出品者もユーザグループの権限設定でアクセスを制御したい場合は、宣言が必要です。

宣言方法は、管理者と似ていますが、ファイル名が違います。`app/addons/[addon-id]/schemas/permissions/vendor.post.php`が出品者向け権限宣言の場所です。また、宣言のschemaの構造も違います。(調査時に、微妙な違いに時間をとられて「なんでや!」と突っ込みたくなりました…)

下記の例は、`hogehoge`コントローラへのアクセスをすべて許可するという宣言です。一見すると、グループの権限設定を無視ししてアクセスできそうですが、CS-Cartは`vendor.post.php`の宣言で制御して、アクセス可能とわかったら次に`admin.post.php`での宣言も考慮するので心配はいりません。逆をいえば、admin.post.phpで宣言をしていない場合、アクセスし放題になります。

```php:app/addons/[addon-id]/schemas/permissions/vendor.post.php
<?php

$schema['controllers']['hogehoge'] = [
    'permissions' => true,
];

return $schema;
```

モード単位のアクセス制御もできます。

```php:app/addons/[addon-id]/schemas/permissions/vendor.post.php
<?php

$schema['controllers']['hogehoge'] = [
    'permissions' => true, // add, update以外のときは許可
    'modes'       => [
        'add'    => false, // addのときは拒否
        'update' => false, // updateのときは拒否
    ],
];

return $schema;
```

## schemasを追加したらキャッシュのクリアが必要

admin.post.phpもvendor.post.phpも、すべてRegistryのキャッシュに乗るので、ファイルを追加した場合はキャッシュのクリアが必要です。管理画面の「一般設定」→「ストレージ」→「キャッシュのクリア」もしくはコマンドで消します。

```
rm -rf var/cache/registry/*
```