मेरे पास एक PostgreSQL (9.4) डेटाबेस है जो वर्तमान उपयोगकर्ता के आधार पर रिकॉर्ड तक पहुंच को सीमित करता है, और उपयोगकर्ता द्वारा किए गए परिवर्तनों को ट्रैक करता है। यह दृश्य और ट्रिगर के माध्यम से प्राप्त किया जाता है, और अधिकांश भाग के लिए यह अच्छी तरह से काम करता है, लेकिन मुझे उन विचारों के साथ समस्या हो रही है जिनके लिए INSTEAD OF
ट्रिगर की आवश्यकता होती है। मैंने समस्या को कम करने की कोशिश की है, लेकिन मैं पहले से माफी मांगता हूं कि यह अभी भी काफी लंबा है।
स्थिति
डेटाबेस के सभी कनेक्शन एक ही खाते के माध्यम से वेब फ्रंट-एंड से बने हैं 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
और भी बेहतर हो सकता है, लेकिन मुझे लगता है कि प्रारंभिक लॉगिन उपयोगकर्ता को सुपरसुसर विशेषाधिकारों की आवश्यकता होगी, जो खतरनाक खुशबू आ रही है।