PostgreSQL(または一般的なSQL)にビジネスロジックのアクセス許可を実装する方法は?


16

アイテムのテーブルがあると仮定しましょう:

CREATE TABLE items
(
    item serial PRIMARY KEY,
    ...
);

次に、各アイテムの「アクセス許可」の概念を紹介します(ここでは、データベースアクセス許可についてではなく、そのアイテムのビジネスロジック許可について説明していることに注意してください)。各アイテムにはデフォルトの許可があり、デフォルトの許可よりも優先されるユーザーごとの許可もあります。

私はこれを実装するいくつかの方法を考えて、次の解決策を考え出しましたが、どれが最良であり、なぜかについてはわかりません。

1)ブール解

各権限にブール列を使用します。

CREATE TABLE items
(
    item serial PRIMARY KEY,

    can_change_description boolean NOT NULL,
    can_change_price boolean NOT NULL,
    can_delete_item_from_store boolean NOT NULL,
    ...
);

CREATE TABLE item_per_user_permissions
(
    item int NOT NULL REFERENCES items(item),
    user int NOT NULL REFERENCES users(user),

    PRIMARY KEY(item, user),

    can_change_description boolean NOT NULL,
    can_change_price boolean NOT NULL,
    can_delete_item_from_store boolean NOT NULL,
    ...
);

利点:各許可には名前が付けられます。

短所:列の数を大幅に増やす許可が多数あり、それらを2回定義する必要があります(各テーブルで1回)。

2)整数ソリューション

整数を使用し、ビットフィールドとして扱います(つまり、ビット0はfor can_change_description、ビット1はforなどcan_change_price、ビット単位の操作を使用してアクセス許可を設定または読み取ります)。

CREATE DOMAIN permissions AS integer;

利点:非常に高速。

短所:データベースとフロントエンドインターフェイスの両方で、どのビットがどの許可を表しているかを追跡する必要があります。

3)ビットフィールドソリューション

2)と同じbit(n)ですが、を使用します。ほとんどの場合、同じ長所と短所がありますが、少し遅いかもしれません。

4)列挙ソリューション

許可に列挙型を使用します。

CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);

次に、デフォルトの許可用に追加のテーブルを作成します。

CREATE TABLE item_default_permissions
(
    item int NOT NULL REFERENCES items(item),
    perm permission NOT NULL,

    PRIMARY KEY(item, perm)
);

ユーザーごとの定義テーブルを次のように変更します。

CREATE TABLE item_per_user_permissions
(
    item int NOT NULL REFERENCES items(item),
    user int NOT NULL REFERENCES users(user),
    perm permission NOT NULL,

    PRIMARY KEY(item, user, perm)    
);

利点:個々のアクセス許可に名前を付けるのは簡単です(ビット位置を処理する必要はありません)。

短所:デフォルトのアクセス許可を取得するだけでも、2つの追加テーブルにアクセスする必要があります。1つ目はデフォルトのアクセス許可テーブル、2つ目は列挙値を格納するシステムカタログです。

特に、そのアイテムのすべての単一ページビューについてデフォルトのアクセス許可を取得する必要があるため、最後の選択肢のパフォーマンスへの影響が大きい可能性があります。

5)列挙配列ソリューション

4)と同じですが、配列を使用してすべての(既定の)アクセス許可を保持します。

CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);

CREATE TABLE items
(
    item serial PRIMARY KEY,

    granted_permissions permission ARRAY,
    ...
);

利点:個々のアクセス許可に名前を付けるのは簡単です(ビット位置を処理する必要はありません)。

短所:第1正規形を壊し、少しいです。許可の数が大きい場合(約50)、大量のバイトを連続して使用します。

他の選択肢を考えられますか?

どのアプローチを採用する必要があり、その理由は何ですか?

注:これは、以前にStackoverflowに投稿され質問の修正版です。


2
数十種類の異なるアクセス許可を使用して、1つ(または複数)のbigintフィールド(それぞれが64ビットに適している)またはビット文字列を選択する場合があります。SOに関連する回答をいくつか
アーウィンブランドステッター14

回答:


7

データベースセキュリティ自体について質問しているのではないことは知っていますが、データベースセキュリティを使用して、必要なことを実行できます。これはWebアプリでも使用できます。データベースセキュリティを使用したくない場合は、スキーマが引き続き適用されます。

列レベルのセキュリティ、行レベルのセキュリティ、およびおそらく階層的なロール管理が必要です。ロールベースのセキュリティは、ユーザーベースのセキュリティよりも管理がはるかに簡単です。

このサンプルコードはPostgreSQL 9.4用で、近日中に公開されます。9.3でできますが、より多くの手作業が必要です。

パフォーマンス†に関心がある場合は、すべてをインデックス可能にする必要があります。これは、ビットマスクと配列フィールドはおそらく良いアイデアではないことを意味します。

この例では、メインデータテーブルをdataスキーマに保持し、対応するビューをに保持しpublicます。

create schema data; --main data tables
create schema security; --acls, security triggers, default privileges

create table data.thing (
  thing_id int primary key,
  subject text not null, --or whatever
  owner name not null
);

所有者列がcurrent_userであることを強制する挿入および更新のためにdata.thingにトリガーを配置します。おそらく、所有者だけが自分のレコード(別のトリガー)を削除することを許可します。

WITH CHECK OPTIONユーザーが実際に使用するビューを作成します。更新できるように一生懸命試してください。そうしないと、トリガー/ルールが必要になります。これはより多くの作業です。

create view public.thing with(security_barrier) as 
select
thing_id,
subject,
owner,
from data.thing
where
pg_has_role(owner, 'member') --only owner or roles "above" him can view his rows. 
WITH CHECK OPTION;

次に、アクセス制御リストテーブルを作成します。

--privileges r=read, w=write

create table security.thing_acl (
  thing_id int,
  grantee name, --the role to whom your are granting the privilege
  privilege char(1) check (privilege in ('r','w') ),

  primary key (thing_id, grantee, privilege),

  foreign key (thing_id) references data.thing(thing_id) on delete cascade
);

ビューを変更してACLを説明します。

drop view public.thing;

create view public.thing with(security_barrier) as 
select
thing_id,
subject,
owner
from data.thing a
where
pg_has_role(owner, 'member')
or exists (select 1 from security.thing_acl b where b.thing_id = a.thing_id and pg_has_role(grantee, 'member') and privilege='r')
with check option;

デフォルトの行特権テーブルを作成します。

create table security.default_row_privileges (
  table_name name,
  role_name name,
  privilege char(1),

  primary key (table_name, role_name, privilege)
);

data.thingの挿入にトリガーを設定して、デフォルトの行特権をsecurity.thing_aclにコピーします。

  • テーブルレベルのセキュリティを適切に調整します(不要なユーザーからの挿入を防止します)。誰もデータまたはセキュリティスキーマを読み取ることができません。
  • 列レベルのセキュリティを適切に調整します(一部のユーザーが一部の列を表示/編集できないようにします)。has_column_privilege()を使用して、ユーザーが列を表示できることを確認できます。
  • おそらくビューにセキュリティ定義タグが必要です。
  • 追加grantorを検討してくださいadmin_optionACLテーブルに列列を、特権を付与されたユーザーと、その行の特権を被付与者が管理できるかどうかを追跡する。
  • テストロット

†この場合、pg_has_roleはおそらくインデックス化できません。current_userよりも上位のすべてのロールのリストを取得し、代わりに所有者/保証人の値と比較する必要があります。


ここでデータベースアクセス許可について話しているのではない」という部分を見ましたか?
a_horse_with_no_name 14

@a_horse_with_no_nameはい。彼は自分のRLS / ACLシステムを書くことができ、または彼は彼が求めているものを行うために、組み込みのデータベースのセキュリティを使用することができます。
ニールマクギガン14

詳細な回答ありがとうございます!ただし、スタッフだけでなくすべてのユーザーがアクセス許可を持っている可能性があるため、データベースロールを使用することは正しい答えだとは思いません。例は、「can_view_item」、「can_bulk_order_item」または「can_review_item」です。私の許可名の最初の選択は、スタッフの許可のみに関するものであると信じるようになったと思いますが、これらの名前はすべて、複雑さを抽象化するための単なる例にすぎません。最初の質問で述べたように、それはスタッフごとの許可ではなく、ユーザーごとの許可に関するものです。
JohnCand 14

とにかく、usersテーブル内のすべてのユーザー行に対して個別のデータベースロールを持たなければならないことは、やりすぎで管理しにくいようです。しかし、スタッフの許可だけを実装する開発者にとって、あなたの答えは価値があると思います。
JohnCand 14

1
@JohnCand他の場所でアクセス許可を管理する方が簡単かどうかはわかりませんが、見つかったらソリューションを教えてください!:)
ニールマクギガン14

4

アクセス制御リストの使用を検討しましたか PostgreSQL拡張機能のか?

ネイティブPostgreSQLデータタイプACEと、ユーザーがデータにアクセスする権限を持っているかどうかを確認できる一連の関数が含まれています。PostgreSQLロールシステム、またはアプリケーションのユーザー/ロールIDを表す抽象番号(またはUUID)で動作します。

この場合、データテーブルにACL列を追加し、acl_check_access関数の1つを使用してユーザーをACLに対してチェックするだけです。

CREATE TABLE items
(
    item serial PRIMARY KEY,
    acl ace[],
    ...
);

INSERT INTO items(acl, ...) VALUES ('{a//<user id>=r, a//<role id>=rwd, ...}');

SELECT * FROM items where acl_check_access(acl, 'r', <roles of the user>, false) = 'r'

ACLの使用は、ビジネスロジックのアクセス許可を扱うための非常に柔軟な方法です。さらに、非常に高速です。平均的なオーバーヘッドは、レコードの読み取りに必要な時間のわずか25%です。唯一の制限は、オブジェクトタイプごとに最大16のカスタム権限をサポートすることです。


1

これをエンコードする別の可能性、リレーショナルの可能性を考えることができます

あなたが必要としない場合permission_per_itemtableyouはそれをスキップして接続することができますPermissionsし、Items直接item_per_user_permissionsテーブル。

ここに画像の説明を入力してください

凡例

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.