現在のユーザーに応じてレコードへのアクセスを制限し、ユーザーが行った変更を追跡するPostgreSQL(9.4)データベースがあります。これはビューとトリガーによって実現され、ほとんどの場合これで十分に機能しますが、_INSTEAD OF
_トリガーを必要とするビューで問題が発生しています。私は問題を減らすように努めましたが、これがまだかなり長いことを前もってお詫びします。
データベースへのすべての接続は、単一のアカウントdbweb
を介してWebフロントエンドから行われます。接続されると、ロールは_SET ROLE
_を介してWebインターフェイスを使用するユーザーに対応するように変更され、そのようなロールはすべてグループロールdbuser
に属します。 (詳細は この答え を参照してください)。ユーザーがalice
であるとします。
ほとんどのテーブルは、ここでprivate
と呼び、dbowner
に属するスキーマに配置されています。これらのテーブルにはdbuser
から直接アクセスできませんが、別のロールdbview
からアクセスできます。例えば:
_SET SESSION AUTHORIZATION dbowner;
CREATE TABLE private.incident
(
incident_id serial PRIMARY KEY,
incident_name character varying NOT NULL,
incident_owner character varying NOT NULL
);
GRANT ALL ON TABLE private.incident TO dbview;
_
現在のユーザーalice
が特定の行を利用できるかどうかは、他のビューによって決定されます。簡略化した例(減らすことはできますが、より一般的なケースをサポートするには、この方法で行う必要があります)は次のようになります。
_-- Simplified case, but in principle could join multiple tables to determine allowed ids
CREATE OR REPLACE VIEW usr_incident AS
SELECT incident_id
FROM private.incident
WHERE incident_owner = current_user;
ALTER TABLE usr_incident
OWNER TO dbview;
_
行へのアクセスは、dbuser
などのalice
ロールにアクセスできるビューを介して提供されます。
_CREATE OR REPLACE VIEW public.incident AS
SELECT incident.*
FROM private.incident
WHERE (incident_id IN ( SELECT incident_id
FROM usr_incident));
ALTER TABLE public.incident
OWNER TO dbview;
GRANT ALL ON TABLE public.incident TO dbuser;
_
FROM
句には1つのリレーションのみが表示されるため、この種のビューはトリガーを追加しなくても更新できます。
ロギングの場合、どのテーブルが変更され、誰が変更したかを記録する別のテーブルが存在します。削減バージョンは次のとおりです。
_CREATE TABLE private.audit
(
audit_id serial PRIMATE KEY,
table_name text NOT NULL,
user_name text NOT NULL
);
GRANT INSERT ON TABLE private.audit TO dbuser;
_
これは、追跡する各関係に配置されたトリガーを介して入力されます。たとえば、挿入のみに制限された_private.incident
_の例は次のとおりです。
_CREATE OR REPLACE FUNCTION private.if_modified_func()
RETURNS trigger AS
$BODY$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO private.audit (table_name, user_name)
VALUES (tg_table_name::text, current_user::text);
RETURN NEW;
END IF;
END;
$BODY$
LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION private.if_modified_func() TO dbuser;
CREATE TRIGGER log_incident
AFTER INSERT ON private.incident
FOR EACH ROW
EXECUTE PROCEDURE private.if_modified_func();
_
したがって、alice
が_public.incident
_に挿入された場合、レコード_('incident','alice')
_が監査に表示されます。
このアプローチは、ビューがより複雑になり、挿入をサポートするために_INSTEAD OF
_トリガーが必要な場合に問題に直面します。
たとえば、多対1の関係に関与するエンティティを表す、2つの関係があるとします。
_CREATE TABLE private.driver
(
driver_id serial PRIMARY KEY,
driver_name text NOT NULL
);
GRANT ALL ON TABLE private.driver TO dbview;
CREATE TABLE private.vehicle
(
vehicle_id serial PRIMARY KEY,
incident_id integer REFERENCES private.incident,
make text NOT NULL,
model text NOT NULL,
driver_id integer NOT NULL REFERENCES private.driver
);
GRANT ALL ON TABLE private.vehicle TO dbview;
_
_private.driver
_の名前以外の詳細を公開したくないので、テーブルを結合し、公開するビットを投影するビューがあるとします。
_CREATE OR REPLACE VIEW public.vehicle AS
SELECT vehicle_id, make, model, driver_name
FROM private.driver
JOIN private.vehicle USING (driver_id)
WHERE (incident_id IN ( SELECT incident_id
FROM usr_incident));
ALTER TABLE public.vehicle OWNER TO dbview;
GRANT ALL ON TABLE public.vehicle TO dbuser;
_
alice
がこのビューに挿入できるようにするには、トリガーを指定する必要があります。例:
_CREATE OR REPLACE FUNCTION vehicle_vw_insert()
RETURNS trigger AS
$BODY$
DECLARE did INTEGER;
BEGIN
INSERT INTO private.driver(driver_name) VALUES(NEW.driver_name) RETURNING driver_id INTO did;
INSERT INTO private.vehicle(make, model, driver_id) VALUES(NEW.make_id,NEW.model, did) RETURNING vehicle_id INTO NEW.vehicle_id;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION vehicle_vw_insert()
OWNER TO dbowner;
GRANT EXECUTE ON FUNCTION vehicle_vw_insert() TO dbuser;
CREATE TRIGGER vehicle_vw_insert_trig
INSTEAD OF INSERT ON public.vehicle
FOR EACH ROW
EXECUTE PROCEDURE vehicle_vw_insert();
_
この問題は、トリガー関数の_SECURITY DEFINER
_オプションにより、_current_user
_をdbowner
に設定して実行されるため、alice
が新しいレコードを_private.audit
_の対応するエントリのビューは、作成者をdbowner
として記録します。
では、dbuser
グループロールにスキーマprivate
の関係への直接アクセス権を付与せずに、_current_user
_を保持する方法はありますか?
Craigが提案したように、トリガーではなくルールを使用すると、_current_user
_の変更を回避できます。上記の例を使用すると、更新トリガーの代わりに以下を使用できます。
_CREATE OR REPLACE RULE update_vehicle_view AS
ON UPDATE TO vehicle
DO INSTEAD
(
UPDATE private.vehicle
SET make = NEW.make,
model = NEW.model
WHERE vehicle_id = OLD.vehicle_id
AND (NEW.incident_id IN ( SELECT incident_id
FROM usr_incident));
UPDATE private.driver
SET driver_name = NEW.driver_name
FROM private.vehicle v
WHERE driver_id = v.driver_id
AND vehicle_id = OLD.vehicle_id
AND (NEW.incident_id IN ( SELECT incident_id
FROM usr_incident));
)
_
これにより、_current_user
_が保持されます。ただし、RETURNING
句のサポートは少し厄介です。さらに、_driver_id
_のシーケンスの使用を処理するために、両方のテーブルに同時に挿入するルールを使用する安全な方法を見つけることができませんでした。 WITH
(CTE)でINSERT
句を使用するのが最も簡単な方法ですが、NEW
と組み合わせて使用することはできません(エラー:_rules cannot refer to NEW within WITH query
_)、1つをlastval()
に頼るようにします。これは 強く非推奨 です。
では、スキーマプライベートの関係への直接アクセスをdbuserグループの役割に与えずに、
current_user
を保持する方法はありますか?
INSTEAD OF
トリガーではなく、ルールを使用して、ビューを介した書き込みアクセスを提供できる場合があります。ビューは常に、クエリを実行するユーザーではなく、ビュー作成者のセキュリティ権限で動作しますが、私はthinkcurrent_user
の変更を行いません。
アプリケーションがユーザーとして直接接続する場合は、session_user
ではなくcurrent_user
を確認できます。これは、一般ユーザーに接続してからSET SESSION AUTHORIZATION
に接続した場合にも機能します。ただし、一般的なユーザーとして接続し、目的のユーザーにSET ROLE
を接続すると機能しません。
SECURITY DEFINER
関数内から直前のユーザーを取得する方法はありません。 current_user
とsession_user
のみを取得できます。 last_user
またはユーザーIDのスタックを取得する方法はいいでしょうが、現在サポートされていません。
完全な答えではありませんが、コメントには収まりません。
lastval()
&currval()
lastval()
が推奨されない理由は何ですか?誤解のようです。
参考回答 では、Craigはコメントでルールの代わりにトリガーを使用することを強くお勧めします。そして、私は同意します-あなたの特別な場合を除いて、明らかに。
answerはcurrval()
の使用を強くお勧めしません-しかし、それは誤解のようです。 lastval()
またはcurrval()
に問題はありません。私は参照された回答でコメントを残しました。
currval
現在のセッションでこのシーケンスの
nextval
が最後に取得した値を返します。 (このセッションでこのシーケンスに対してnextval
が呼び出されなかった場合、エラーが報告されます。)これはセッションローカル値を返すため、他のセッションがnextval
現在のセッション以降。
したがって、これは並行トランザクションで安全です。起こり得る唯一の複雑さは、同じトリガーを誤って呼び出す可能性のある他のトリガーまたはルールから発生する可能性があります。これは非常にまれなシナリオであり、インストールするトリガー/ルールを完全に制御できます。
ただし、コマンドのシーケンスがルール内に保持されているかどうかは不明です( currval()
は揮発性関数として )ですがまた、複数行INSERT
を使用すると、同期が取れなくなる可能性があります。ルールを2つのルールに分割できます。2番目のルールはINSTEAD
のみです。覚えておいてください ドキュメントごと:
同じテーブルと同じイベントタイプの複数のルールは、名前のアルファベット順に適用されます。
私は時間をかけて、それ以上調査しませんでした。
DEFAULT PRIVILEGES
はどうかと言うと:
SET SESSION AUTHORIZATION dbowner;
...
GRANT ALL ON TABLE private.incident TO dbview;
代わりに興味があるかもしれません:
ALTER DEFAULT PRIVILEGES FOR ROLE dbowner IN SCHEMA private
GRANT ALL ON TABLES TO dbview;
関連: