현재 사용자에 따라 레코드에 대한 액세스를 제한하고 사용자가 변경 한 내용을 추적하는 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
부분 솔루션
Craig가 제안한 것처럼 트리거 대신 규칙을 사용하면을 변경하지 않아도 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
더 좋을 수도 있지만 초기 로그인 사용자에게는 수퍼 유저 권한이 있어야한다고 생각합니다.