認可(アクセスコントロール)が難しい
自分の関わってきたシステムは、何らか、「同一テナント(企業)であっても、ユーザーごとに許可されていること・拒否されていることを決めたい」という要件があったように思います。 これがひたすら難しいという話。
「【あるユーザー】は【あるリソース】に対して【このアクション】ができるか」に対して True/False を返すという話ではあるのですが、色々と苦しいところがあります。
このカギ括弧内をこう書くのが大変なので、P(user: User, resource: Resource, action: Action, result: Boolean) と書かせてください。型のつもりで書いている : User 等も以後省略します。
例えば、以下でP("User:001", "Resource:001", "read", True) は、「【ユーザー1】は【リソース1】の【読み取り】を【許可】されている」と読みます。
いずれかが変数のままであるときは、この述語 P を成立させる変数は何か?を解こうとしています。
難しくない問題
シンプルな、 P("User:001", "Resource:001", "read", result) であれば、単に、 result が True か False かというだけです。
アクセスコントロールを実装するとなると、まずはこれを実装することになると思います。
私が関わってきたシステムだと、「ユーザーが所属する組織のリソースは閲覧できる」というのがよくあるものなので、
user.organization IN resource.organizations 的なもので実現できますね。
UI/UX への反映
画面上で、ユーザーが自分に何ができて、何ができないのかがわからないのはあまりにも不便ですから、P("User:001", "Resource:001", action, True) の問題を解くことになるはずです。
ユーザーとリソースがわかっているので、先ほどと一緒の話なんですけど、さて、「画面」を描画するたびに、「あるリソースに対して、自分は何ができるのか」を毎回問い合わせなければいけないのだとすると、
結構な権限チェックAPI呼び出しを走らせることになって辛い。
あまり User と Resource 間の複雑なルールがないのなら、認証終わった時にjsonか何か渡して、クライアントサイドでボタンの非活性化等対応するのが良いでしょうね。
ページネーション
P("User:001", resource, "read", True) の問題。つまり、「読み取り可能なリソースを列挙」する問題。
ルールが難しくなければ、これそのものを表現するような以下のようなクエリが書くというのも一つの手。 ただ、ルールが複雑になるとおよそ間違いの恐れもあって苦しい。
SELECT * FROM resources
INNER JOIN resource_organizations ON resource_organizations.resource_id = resources.id
INNER JOIN organizations ON resource_organizations.organization_id = organizations.id
INNER JOIN users ON users.organization_id = organizations.id
WHERE users.id = $1
一方で、データベースからデータを取得するタイミングでなく、取得した後に、そのリソースがアクセスできるかというのをチェックするという方法もあります。 これは単に擬似的には以下のようなコードに対応して、最初の「難しくない問題」に帰着します。列挙用にクエリをあれこれしなくても同じポリシーを使いまわせる。
for resource in resources:
P("User:001", resource, "read", result)
さて、問題はこの、result を得る方法が低コストなのか?ということ、そして、「全件数を知りたい」とか、「ちょうど20件取得してこなければならない」のような、ページネーション系の条件がないか?というところが懸念点になります。
「全件数を知る」はそれこそ全部取得してきて上のチェックしないといけないので現実的でないです。
「ちょうど20件」ってやつは、上のチェックで落とされるかもしれないので、多めに取得するとか、それでも足りなかったら繰り返し、20件を埋められるまでクエリを実行するという話になります。 この場合、リソース1000件中2件くらいしか許可されていないユーザーが、1ページ目開くために50回クエリを投げるという恐ろしい話があります。 あと、歯抜けがあるときページ数とオフセットの対応どうするのという問題があります。
まあ妥当なページネーションはまず無理、無限スクロールなどでカバーしても、リソースアクセスがほとんど許されていないユーザーが大量クエリを発行してしまう問題は残り続けます。
検証機能?
ロールをきめ細かに作れることをウリにしているシステムに携わっていました。ユーザーの属性から、ロールが付与されて、そのロールでリソースの閲覧ができるかどうかが決まるといった仕組みです。
さて、あまりにきめ細かすぎて中々設定が難しく、「結局このリソースって誰が見れるの?」という問い合わせがありました。P(user, "Resouce:001", "read", True) 問題
これはほぼ、ページネーションで書いたところと同じですかね。
まとめ
もちろん、システムの要件によるんですが、できる限り簡単にした方がいいのは確かです。簡単に組み合わせが膨れあがって崩壊します。
(マイクロサービスにすると P(user, resource, action, result) に対して、「どこがユーザーを持ってるの?」「誰がリソース持ってるの?」「ルールどこ?」「判定どこ?」みたいな
もっと面倒な話があります。恐ろしいですね。考えたくないです。)
以上!