Geçerli kullanıcıya bağlı olarak kayıtlara erişimi sınırlayan ve kullanıcı tarafından yapılan değişiklikleri izleyen bir PostgreSQL (9.4) veritabanı var. Bu, görünümler ve tetikleyiciler aracılığıyla elde edilir ve çoğunlukla bu iyi çalışır, ancak INSTEAD OF
tetikleyici gerektiren görünümlerde sorun yaşıyorum . Sorunu azaltmaya çalıştım, ama bunun hala uzun olduğu için özür dilerim.
Durum
Veritabanına tüm bağlantılar, tek bir hesap aracılığıyla web ön ucundan yapılır dbweb
. Bağlantı kurulduktan sonra rol, SET ROLE
web arayüzünü kullanan kişiye karşılık gelecek şekilde değiştirilir ve bu tür tüm roller grup rolüne aittir dbuser
. (Ayrıntılar için bu cevaba bakınız). Kullanıcının olduğunu varsayalım alice
.
Tablolarımın çoğu burada arayacağım private
ve ait olacağım bir şemaya yerleştirildi dbowner
. Bu tablolara doğrudan erişilemez dbuser
, ancak başka bir rol için erişilebilir dbview
. Örneğin:
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;
Geçerli satırda belirli satırların kullanılabilirliği alice
diğer görünümler tarafından belirlenir. Basitleştirilmiş bir örnek (azaltılabilir, ancak daha genel vakaları desteklemek için bu şekilde yapılması gerekir):
-- 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;
Daha sonra satırlara erişim, aşağıdaki dbuser
rollere erişilebilen bir görünümle sağlanır 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
Bu maddede yalnızca bir ilişki göründüğünden , bu tür bir görünümün ek tetikleyiciler olmadan güncellenebilir olduğunu unutmayın.
Günlüğe kaydetme için, hangi tablonun değiştirildiğini ve kimin değiştirildiğini kaydetmek için başka bir tablo vardır. İndirgenmiş sürüm:
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;
Bu, izlemek istediğim ilişkilerin her birine yerleştirilen tetikleyicilerle doldurulur. Örneğin, private.incident
yalnızca kesici uçlarla sınırlı bir örnek :
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();
Yani şimdi eğer alice
içine ekler public.incident
, rekor ('incident','alice')
denetim görünür.
Sorun
Bu yaklaşım, görünümler daha karmaşık hale geldiğinde ve INSTEAD OF
ekleri desteklemek için tetikleyicilere ihtiyaç duyulduğunda sorunlara neden olur .
Diyelim ki iki ilişkim var, örneğin bire bir ilişkide yer alan varlıkları temsil eden:
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;
Adından başka ayrıntıları göstermek istemediğimizi varsayalım private.driver
ve bu yüzden tablolara katılan ve ortaya çıkarmak istediğim bitleri yansıtan bir görünüme sahibim:
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;
İçin için alice
bir tetikleyici sağlanacak olan bu görüş, örneğin içine yerleştirmek mümkün:
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();
Bununla ilgili sorun SECURITY DEFINER
, tetikleyici işlevindeki seçeneğin current_user
ayarlanmış olarak çalıştırılmasına neden olmasıdır dbowner
, bu nedenle alice
görünüme yeni bir kayıt private.audit
eklerse, yazarın kayıtlarında karşılık gelen girdi olur dbowner
.
Peki, gruba şemadaki ilişkilere doğrudan erişim current_user
vermeden bunu korumanın bir yolu var mı? dbuser
private
Kısmi Çözüm
Craig'in önerdiği gibi, tetikleyiciler yerine kurallar kullanmak current_user
. Yukarıdaki örnek kullanılarak, güncelleme tetikleyicisi yerine aşağıdakiler kullanılabilir:
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));
)
Bu korur current_user
. RETURNING
Yine de destekleyici hükümler biraz kıllı olabilir. Ayrıca, bir dizinin kullanımını işlemek için her iki tabloya aynı anda eklemek için kuralları kullanmanın güvenli bir yolunu bulamadım driver_id
. En kolay yolu kullanmak olurdu WITH
bir in maddesi INSERT
(CTE), ancak bunlar ile bağlantılı olarak izin verilmez NEW
(hata: rules cannot refer to NEW within WITH query
) başvurmak ettirecek şekilde, lastval()
hangi kesinlikle önerilmez .
SET SESSION
daha iyi olabilir ama ilk giriş kullanıcısının tehlikeli kokulu süper kullanıcı ayrıcalıklarına sahip olması gerektiğini düşünüyorum.