このユーザーはこの機能が使える、みたいなあれをどう実現するか

3 min read読了の目安(約3200字 2

このユーザーはこの機能が使える、みたいなあれをどう実現するか

結論

DBで実現するかアプリケーション側でライブラリで実現する。casbinが強そうだぞ。

https://casbin.org/en/

はじめに

ユーザーテーブルにisAdminというカラムを使って、アドミンかそうでないかで、アクセスコントロールを分けていたが、サービスが大きくなり、もう少し細かくコントロールする必要になった。そこで、情報収集してえた見識をまとめておく。

アクセスコントロールという考え方を知る

だれがこのファイルを操作できるのかみたいなやつ。おおきく4つのパターンがあるみたい。

  1. DAC
  2. RBAC
  3. MAC
  4. ABAC

DACとRBACとMAC

この3つはまとめて図で理解したほうが覚えやすいと思う。
DACとRBACとMACの図

DAC(任意アクセス制御)はユーザー側が任意にこの機能を誰が使えるか設定する。例えばgoogleでファイル共有するときに誰までが閲覧可能かどうか設定する、あれのこと。ちなみに、Linuxのファイルシステムなどに利用される。反対にMAC(強制アクセス制御)は完全にシステムからレベルごとに使える機能を限定されている。軍隊などでよく利用される。例えば、めちゃくちゃ下っ端だとここまでの情報しか取得できないみたいなやつ。DACはユーザーに自由度を与え過ぎるのが欠点。MACは、純粋にレベルがあがると利用できる機能が増えていくモデルなので、例えば作成はできるけど閲覧はできないみたいな表現ができないのが欠点。

RBAC(ロールベースアクセス制御)は2つに比べたら比較的新し目。ロールという中間層を設ける。ユーザー側からしたら割り振られたロールが可能な機能しか使えない。システム側からしたらユーザーに自由度は与えずに、かつ機能のわりふりが比較的表現度たかく行える。

ABAC

ABACの図

ちょっとトリッキー。最終進化系かもしれない。こちらは条件を複雑にしたもの。例えば、

  • 本店勤務である、店長クラスであれば削除ができる(閲覧と作成と更新もできる)
  • 店長クラスであれば閲覧と作成と更新ができる
  • 支店勤務であれば閲覧ができる
    みたいに、その人の属性によって細かく権限を変える考え方。めっちゃ複雑。もう少しシンプルにしてRBACでも対応できると思う。
  • 本店の店長ロールは全権限がある
  • 支店店長ロールは閲覧と作成と更新ができる。
  • 支店バイトロールは閲覧しかできない。
    みたいな感じ。

DACとRBACとMACをアプリの権限管理に活かして考えてみる

上記はどちらかというとシステムレベルでコンテンツにアクセスするさいのふるい分けでよく言われる話で、webサービスの権限的な部分での話とは少し違う気がする(ユーザーが自由に権限管理できたら権限の意味がない)。ただ上記の図は権限管理に活かせそうな気がする。ここは上記の図をヒントに、少し違った考えをしてみる。

下記はDBを利用しているが、動的に変化することがなければテキストファイル(yml的なものとか)でも良いと思う。

ユーザーidに直接できることを紐付けるパターン

DBで次のように権限テーブルを作成する。

userId view create edit delete
1 true true true true
2 true false false false

アプリケーション側ではユーザー取得に権限テーブルをjoinし、各操作の真偽値を保持しておく。

const create = () => {
  // さすがにuser.createという取得は嫌なので
  if(!user.create){
    throw new Error('create is not permitted.');
  }
  
  // create something.
} 

ロールにできることを紐付けるパターン

DBではuserテーブルにroleカラムができ、権限テーブルにroleIdと各操作がひも付く。
userテーブル

userId name roleId
1 bob 1
2 alice 2

権限テーブル

roleId name view create edit delete
1 ADMIN true true true true
2 GUEST true false false false

アプリケーション側のコード

const create = () => {
  if(!user.role.create){
    throw new Error('create is not permitted.');
  }
  
  // create something.
} 

もしくは、userテーブルにroleカラムだけおいておき、権限テーブルは作らないという選択もある。その場合はアプリケーション側で分岐する。

const create = () => {
  // 対応ロールが増えるとここの分岐が増える。
  // const permissions = ['ADMIN', 'LEADER']として、if(permissions.includes(user.role)) とするのもあり。
  if(user.role !== 'ADMIN){
    throw new Error(`role need admin. you role: ${user.role}`);
  }
  
  // create something.
} 

もしくはRBACを実現できるライブラリを導入して、権限テーブルの部分をアプリケーションがわのどこかであらかじめ宣言しておくという方法もある。

ユーザーにレベルをつけるパターン

ユーザーテーブルにレベルカラムをつけて、アプリケーション側でレベルが閾値以上かチェックする。どのレベル権限でどの操作ができるのかという情報はドキュメントにちゃんとまとめておかないと、あとで地獄をみそう。

userテーブル

userId name level
1 bob 2
2 alice 1

アプリケーション側のコード

const create = () => {
  if(!user.level <= 1){
    throw new Error('create is not permitted.');
  }
  
  // create something.
} 

specification パターン

デザインパターンのひとつにspecificationパターンというものがある。DDD本のどこかででてくる。複雑な条件のビジネスロジックを扱うパターン。これを利用する手もあるかなと思った。

おわりに

この分野初心者なので、もし詳しい方いましたら教えていただけるとありがたいです^^