web-dev-qa-db-ja.com

ACLのデータベーススキーマ

ACLのスキーマを作成したい。しかし、私はそれを実装するいくつかの方法の間で引き裂かれています。

バックエンドとサイト管理者にとって多くの混乱を招くので、カスケードアクセス許可を処理したくないと確信しています。

一度に1つの役割だけをしているユーザーと一緒に生活することもできると思います。このような設定では、既存のロール/ルールに影響を与えることなく、サイトの拡大に​​応じてロールと権限を必要に応じて追加できます。

最初に、データを正規化し、関係を表す3つのテーブルを用意しました。

ROLES { id, name }
RESOURCES { id, name }
PERMISSIONS { id, role_id, resource_id }

ユーザーがどこかで許可されているかどうかを確認するクエリは、次のようになります。

SELECT id FROM resources WHERE name = ?
SELECT * FROM permissions WHERE role_id = ? AND resource_id = ? ($user_role_id, $resource->id)

その後、私は約20のリソースしか持たないことに気づきました。それぞれに最大5つのアクション(作成、更新、表示など)と、おそらく別の8つの役割があります。これは、可能なレコードが数百を超えることはないため、データの正規化をあからさまに無視できることを意味します。

したがって、おそらくこのようなスキーマの方が理にかなっています。

ROLES { id, name }
PERMISSIONS { id, role_id, resource_name }

単一のクエリでレコードを検索できます

SELECT * FROM permissions WHERE role_id = ? AND permission  = ? ($user_role_id, 'post.update')

これらのどちらがより正しいですか?ACLの他のスキーマレイアウトはありますか?

28
Xeoncross

私の経験では、本当の問題は、ユーザー固有のアクセス制限が発生するかどうかに大別されます。

たとえば、コミュニティのスキーマを設計していて、ユーザーがプロファイルの表示を切り替えることを許可しているとします。

1つのオプションは、パブリック/プライベートプロファイルフラグに固執し、広範なプリエンプティブな権限チェックに固執することです。たとえば、「users.view」(パブリックユーザーを表示)または「users.view_all」(モデレーターはすべてのユーザーを表示)などです。 。

もう1つは、より洗練されたアクセス許可を含み、(a)すべてのユーザーが表示できるように、(b)厳選されたバディが表示できるように、(c)完全に非公開に、そしておそらく(d) )厳選されたbozosを除くすべてのユーザーが表示できます。この場合、個々の行の所有者/アクセス関連のデータを格納する必要があり、密で方向性のあるグラフの推移的な閉包の具体化を回避するために、これらのもののいくつかを大幅に抽象化する必要があります。

どちらのアプローチでも、ロールの編集/割り当てに追加された複雑さが、個々のデータへの割り当てアクセス許可の結果の容易さ/柔軟性によって相殺され、次の方法が最も効果的であることがわかりました。

  1. ユーザーは複数の役割を持つことができます
  2. 役割と権限が同じテーブルにマージされ、2つを区別するためのフラグが付いています(役割/権限を編集するときに役立ちます)
  3. 同じテーブル内から、役割は他の役割を割り当てることができ、役割と権限は権限を割り当てることができます(ただし、権限は役割を割り当てることができません)。

結果の指向グラフは、2つのクエリでプルされ、使用している言語を使用して妥当な時間内に一度だけ構築され、その後の使用のためにMemcacheなどにキャッシュされます。

そこから、ユーザーの権限を取得することは、ユーザーが持っている役割を確認し、権限グラフを使用してそれらを処理して最終的な権限を取得することです。ユーザーが指定した役割/権限を持っているかどうかを確認して、権限を確認します。次に、クエリを実行するか、その権限チェックに基づいてエラーを発行します。

必要に応じて、個々のノードのチェックを拡張できます(つまり、「このノードを編集できます」のcheck_perms($user, 'users.edit', $node)と「ノードを編集できます」のcheck_perms($user, 'users.edit'))。エンドユーザーにとって非常に柔軟で使いやすいもの。

冒頭の例で説明されているように、行レベルの権限への過度の誘導に注意してください。個々のノードの権限をチェックする場合、有効なノード(つまり、ユーザーが表示または編集できるもののみ)のリストを取得する場合よりも、パフォーマンスのボトルネックは少なくなります。クエリの最適化に(非常に)精通していない場合は、行自体のフラグとuser_idフィールド以外のものは使用しないことをお勧めします。

29

これは、可能なレコードが数百を超えることはないため、データの正規化をあからさまに無視できることを意味します。

予想される行数は、どの正規形を対象にするかを選択するための基準ではありません。正規化はデータの整合性に関係しています。通常、冗長性を減らすことでデータの整合性を高めます。

実際に尋ねる質問は、「行数はいくつですか?」ではなく、「データベースが常に正しい答えを与えることがどれほど重要か」です。 ACLを実装するために使用されるデータベースの場合、「かなり重要だ」と言います。

どちらかといえば、行数が少ない場合は、パフォーマンスを気にする必要がないことを示しているため、5NFを選択するのは簡単です。 ID番号を追加する前に5NFを押してください。

ユーザーがどこかで許可されているかどうかを判断するクエリは、次のようになります。

SELECT id FROM resources WHERE name = ?
SELECT * FROM permissions 
WHERE role_id = ? AND resource_id = ? ($user_role_id, $resource->id)

内部結合を使用する代わりに2つのクエリを使用すると、頭の中にいる可能性があることを示唆していると書いたこと。 (それは観察であり、批判ではありません。)

SELECT p.* 
FROM permissions p
INNER JOIN resources r ON (r.id = p.resource_id AND 
                           r.name = ?)

SETを使用してロールを割り当てることができます。

CREATE TABLE permission (
  id integer primary key autoincrement
  ,name varchar
  ,perm SET('create', 'edit', 'delete', 'view')
  ,resource_id integer );
1
Johan