Saya memiliki database PostgreSQL (9.4) yang membatasi akses ke catatan tergantung pada pengguna saat ini, dan melacak perubahan yang dibuat oleh pengguna. Ini dicapai melalui tampilan dan pemicu, dan sebagian besar ini berfungsi dengan baik, tapi saya mengalami masalah dengan tampilan yang memerlukan INSTEAD OF
pemicu. Saya telah mencoba untuk mengurangi masalah, tetapi saya minta maaf sebelumnya bahwa ini masih cukup lama.
Situasi
Semua koneksi ke database dibuat dari front-end web melalui satu akun dbweb
. Setelah tersambung, peran diubah melalui SET ROLE
agar sesuai dengan orang yang menggunakan antarmuka web, dan semua peran tersebut menjadi milik peran grup dbuser
. (Lihat jawaban ini untuk detailnya). Anggap pengguna itu alice
.
Sebagian besar meja saya ditempatkan dalam skema yang di sini saya akan menelepon private
dan milik dbowner
. Tabel ini tidak dapat diakses secara langsung dbuser
, tetapi ke peran lain dbview
. Misalnya:
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;
Ketersediaan baris tertentu untuk pengguna saat alice
ini ditentukan oleh tampilan lain. Contoh yang disederhanakan (yang dapat dikurangi, tetapi perlu dilakukan dengan cara ini untuk mendukung kasus yang lebih umum) adalah:
-- 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;
Akses ke baris kemudian diberikan melalui tampilan yang dapat diakses oleh dbuser
peran seperti 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;
Perhatikan bahwa karena hanya satu relasi yang muncul dalam FROM
klausa, tampilan seperti ini dapat diperbarui tanpa pemicu tambahan.
Untuk masuk, ada tabel lain untuk merekam tabel mana yang diubah dan siapa yang mengubahnya. Versi yang dikurangi adalah:
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;
Ini diisi melalui pemicu yang ditempatkan pada masing-masing hubungan yang ingin saya lacak. Misalnya, contoh untuk private.incident
sisipan terbatas hanya:
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();
Jadi sekarang jika alice
memasukkan public.incident
, catatan ('incident','alice')
muncul di audit.
Masalah
Pendekatan ini menimbulkan masalah ketika pandangan menjadi lebih rumit dan perlu INSTEAD OF
pemicu untuk mendukung sisipan.
Katakanlah saya memiliki dua hubungan, misalnya mewakili entitas yang terlibat dalam beberapa hubungan banyak-ke-satu:
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;
Asumsikan bahwa saya tidak ingin mengekspos detail selain nama private.driver
, dan juga memiliki pandangan yang bergabung dengan tabel dan memproyeksikan bit yang ingin saya paparkan:
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;
Agar alice
dapat memasukkan ke dalam tampilan ini pemicu harus disediakan, misalnya:
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();
Masalah dengan ini adalah bahwa SECURITY DEFINER
opsi dalam fungsi pemicu menyebabkannya dijalankan dengan current_user
diatur ke dbowner
, jadi jika alice
memasukkan catatan baru ke tampilan entri yang sesuai dalam private.audit
catatan penulis menjadi dbowner
.
Jadi, apakah ada cara untuk melestarikan current_user
, tanpa memberi dbuser
peran langsung akses kelompok kepada hubungan dalam skema private
?
Solusi Parsial
Seperti yang disarankan oleh Craig, menggunakan aturan dan bukannya pemicu menghindari mengubah current_user
. Menggunakan contoh di atas, berikut ini dapat digunakan sebagai pengganti pemicu pembaruan:
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));
)
Ini mempertahankan current_user
. RETURNING
Klausa pendukung bisa jadi sedikit berbulu. Selain itu, saya tidak menemukan cara aman untuk menggunakan aturan untuk secara bersamaan memasukkan ke dalam kedua tabel untuk menangani penggunaan urutan driver_id
. Cara termudah adalah dengan menggunakan WITH
klausa dalam INSERT
(CTE), tetapi ini tidak diizinkan bersamaan dengan NEW
(kesalahan rules cannot refer to NEW within WITH query
:), meninggalkan satu untuk resor lastval()
yang sangat tidak dianjurkan .
SET SESSION
bisa lebih baik tapi saya pikir pengguna login awal harus memiliki hak superuser, yang baunya berbahaya.