Tôi có cơ sở dữ liệu PostgreSQL (9.4) giới hạn quyền truy cập vào các bản ghi tùy thuộc vào người dùng hiện tại và theo dõi các thay đổi được thực hiện bởi người dùng. Điều này đạt được thông qua các lượt xem và trình kích hoạt, và đối với hầu hết các phần này hoạt động tốt, nhưng tôi gặp vấn đề với các lượt xem yêu cầu INSTEAD OF
trình kích hoạt. Tôi đã cố gắng giảm vấn đề xuống, nhưng tôi xin lỗi trước rằng điều này vẫn còn khá dài.
Tình huống
Tất cả các kết nối đến cơ sở dữ liệu được thực hiện từ một giao diện người dùng web thông qua một tài khoản dbweb
. Sau khi kết nối, vai trò được thay đổi thông qua SET ROLE
để tương ứng với người sử dụng giao diện web và tất cả các vai trò đó thuộc về vai trò nhóm dbuser
. (Xem câu trả lời này để biết chi tiết). Giả sử người dùng là alice
.
Hầu hết các bảng của tôi được đặt trong một lược đồ mà ở đây tôi sẽ gọi private
và thuộc về dbowner
. Các bảng này không thể truy cập trực tiếp dbuser
, nhưng là một vai trò khác dbview
. Ví dụ:
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;
Tính khả dụng của các hàng cụ thể cho người dùng hiện tại alice
được xác định bởi các chế độ xem khác. Một ví dụ đơn giản hóa (có thể giảm bớt, nhưng cần được thực hiện theo cách này để hỗ trợ các trường hợp tổng quát hơn) sẽ là:
-- 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;
Truy cập vào các hàng sau đó được cung cấp thông qua chế độ xem có thể truy cập được với các dbuser
vai trò như 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;
Lưu ý rằng vì chỉ có một quan hệ xuất hiện trong FROM
mệnh đề, nên loại quan điểm này có thể cập nhật mà không cần bất kỳ trình kích hoạt bổ sung nào.
Để ghi nhật ký, một bảng khác tồn tại để ghi lại bảng nào được thay đổi và ai đã thay đổi bảng đó. Một phiên bản rút gọn là:
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;
Điều này được phổ biến thông qua các kích hoạt được đặt trên mỗi mối quan hệ mà tôi muốn theo dõi. Chẳng hạn, một ví dụ cho private.incident
giới hạn chỉ chèn là:
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();
Vì vậy, bây giờ nếu alice
chèn vào public.incident
, một bản ghi ('incident','alice')
xuất hiện trong kiểm toán.
Vấn đề
Cách tiếp cận này gặp vấn đề khi các chế độ xem trở nên phức tạp hơn và cần INSTEAD OF
kích hoạt để hỗ trợ chèn.
Giả sử tôi có hai mối quan hệ, ví dụ đại diện cho các thực thể liên quan đến một số mối quan hệ nhiều-một:
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;
Giả sử rằng tôi không muốn để lộ các chi tiết khác so với tên private.driver
, và do đó có một cái nhìn mà tham gia các bảng và các dự án các bit Tôi muốn để lộ:
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
có thể chèn vào chế độ xem này, một bộ kích hoạt phải được cung cấp, ví dụ:
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();
Vấn đề với điều này là SECURITY DEFINER
tùy chọn trong chức năng kích hoạt khiến nó được chạy với current_user
cài đặt dbowner
, vì vậy nếu alice
chèn một bản ghi mới vào chế độ xem mục tương ứng trong private.audit
bản ghi của tác giả dbowner
.
Vì vậy, có cách nào để bảo tồn current_user
, mà không cho dbuser
vai trò nhóm truy cập trực tiếp vào các mối quan hệ trong lược đồ private
?
Giải pháp từng phần
Theo đề xuất của Craig, sử dụng các quy tắc thay vì kích hoạt sẽ tránh thay đổi current_user
. Sử dụng ví dụ trên, có thể sử dụng các mục sau thay cho trình kích hoạt cập nhật:
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));
)
Điều này bảo tồn current_user
. RETURNING
Mệnh đề hỗ trợ có thể là một chút lông, mặc dù. Hơn nữa, tôi không thể tìm thấy cách an toàn nào để sử dụng các quy tắc để chèn đồng thời vào cả hai bảng để xử lý việc sử dụng chuỗi cho driver_id
. Cách dễ nhất có thể là sử dụng một WITH
mệnh đề trong INSERT
(CTE), nhưng những điều này không được phép kết hợp với NEW
(lỗi rules cannot refer to NEW within WITH query
:), khiến cho một điều khoản phải lastval()
được khuyến khích mạnh mẽ .
SET SESSION
có thể tốt hơn nữa nhưng tôi nghĩ người dùng đăng nhập ban đầu sẽ cần phải có đặc quyền siêu người dùng, có mùi nguy hiểm.