ฉันมีฐานข้อมูล PostgreSQL (9.4) ที่ จำกัด การเข้าถึงระเบียนขึ้นอยู่กับผู้ใช้ปัจจุบันและติดตามการเปลี่ยนแปลงที่ทำโดยผู้ใช้ สิ่งนี้ทำได้โดยการดูและทริกเกอร์และส่วนใหญ่ทำงานได้ดี แต่ฉันมีปัญหากับมุมมองที่ต้องมีINSTEAD OF
ทริกเกอร์ ฉันพยายามลดปัญหาลง แต่ฉันต้องขออภัยล่วงหน้าว่านี่ยังค่อนข้างนาน
สถานการณ์
การเชื่อมต่อกับฐานข้อมูลทั้งหมดที่ทำจากเว็บ front-end dbweb
ผ่านบัญชีเดียว เมื่อเชื่อมต่อบทบาทที่มีการเปลี่ยนแปลงทางเพื่อให้สอดคล้องกับคนที่ใช้อินเตอร์เฟซเว็บและทุกบทบาทดังกล่าวอยู่ในบทบาทของกลุ่มSET ROLE
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
คำสั่งมุมมองประเภทนี้สามารถอัปเดตได้โดยไม่มีทริกเกอร์เพิ่มเติม
สำหรับการบันทึกจะมีตารางอื่นที่จะบันทึกว่ามีการเปลี่ยนแปลงตารางใดและใครเป็นผู้เปลี่ยนแปลง รุ่นที่ลดลงคือ:
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
ทริกเกอร์เพื่อรองรับส่วนแทรก
สมมติว่าฉันมีความสัมพันธ์สองอย่างเช่นเป็นตัวแทนของหน่วยงานที่เกี่ยวข้องกับความสัมพันธ์แบบตัวต่อตัว:
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
ดังนั้นมีวิธีการรักษาcurrent_user
โดยไม่ให้dbuser
บทบาทกลุ่มโดยตรงเข้าถึงความสัมพันธ์ในสคีมาprivate
หรือไม่
โซลูชันบางส่วน
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
ประโยคในINSERT
(CTE) แต่เหล่านี้จะไม่ได้รับอนุญาตในการร่วมกับNEW
(ข้อผิดพลาด: rules cannot refer to NEW within WITH query
) ออกจากที่หนึ่งไปยังรีสอร์ทเพื่อlastval()
ซึ่งเป็นกำลังใจอย่างมาก
SET SESSION
อาจดีกว่านี้ แต่ฉันคิดว่าผู้ใช้ที่เข้าสู่ระบบครั้งแรกจะต้องมีสิทธิ์ผู้ใช้ระดับสูงซึ่งมีกลิ่นอันตราย