AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 110747
Accepted
beldaz
beldaz
Asked: 2015-08-12 17:57:25 +0800 CST2015-08-12 17:57:25 +0800 CST 2015-08-12 17:57:25 +0800 CST

Rastreando o usuário atual por meio de exibições e gatilhos no PostgreSQL

  • 772

Eu tenho um banco de dados PostgreSQL (9.4) que limita o acesso aos registros dependendo do usuário atual e rastreia as alterações feitas pelo usuário. Isso é obtido por meio de exibições e acionadores e, na maioria das vezes, funciona bem, mas estou tendo problemas com exibições que exigem INSTEAD OFacionadores. Tentei reduzir o problema, mas peço desculpas antecipadamente por isso ainda ser muito longo.

A situação

Todas as conexões com o banco de dados são feitas a partir de um front-end da web por meio de uma única conta dbweb. Uma vez conectado, o papel é alterado SET ROLEpara corresponder à pessoa que está usando a interface da web, e todos esses papéis pertencem ao grupo role dbuser. (Veja esta resposta para detalhes). Vamos supor que o usuário seja alice.

A maioria das minhas tabelas são colocadas em um esquema que aqui vou chamar privatee pertencer dbowner. Essas tabelas não são acessíveis diretamente para dbuser, mas para outra função dbview. Por exemplo:

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;

A disponibilidade de linhas específicas para o usuário atual aliceé determinada por outras exibições. Um exemplo simplificado (que poderia ser reduzido, mas precisa ser feito dessa forma para dar suporte a casos mais gerais) seria:

-- 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;

O acesso às linhas é fornecido por meio de uma visualização acessível a dbuserfunções como 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;

Observe que, como apenas uma relação aparece na FROMcláusula, esse tipo de exibição é atualizável sem nenhum acionador adicional.

Para criação de log, existe outra tabela para registrar qual tabela foi alterada e quem a alterou. Uma versão reduzida é:

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;

Isso é preenchido por meio de gatilhos colocados em cada uma das relações que desejo rastrear. Por exemplo, um exemplo private.incidentlimitado a apenas inserções é:

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();

Agora, se aliceinsere em public.incident, um registro ('incident','alice')aparece na auditoria.

O problema

Essa abordagem atinge problemas quando as exibições se tornam mais complicadas e precisam INSTEAD OFde gatilhos para suportar inserções.

Digamos que eu tenha duas relações, por exemplo, representando entidades envolvidas em algum relacionamento muitos-para-um:

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;

Suponha que eu não queira expor os detalhes além do nome de private.driver, e assim ter uma visão que una as tabelas e projete os bits que desejo expor:

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;

Para alicepoder inserir nesta visão, um gatilho deve ser fornecido, por exemplo:

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();

O problema disso é que a SECURITY DEFINERopção na função trigger faz com que ela seja executada com current_userset to dbowner, então se aliceinsere um novo registro na view a entrada correspondente nos private.auditregistros o autor a ser dbowner.

Então, existe uma maneira de preservar current_user, sem dar ao dbusergrupo acesso direto às relações no esquema private?

Solução Parcial

Conforme sugerido por Craig, usar regras em vez de gatilhos evita alterar o arquivo current_user. Usando o exemplo acima, o seguinte pode ser usado no lugar do acionador de atualização:

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));               
   )

Isso preserva current_user. RETURNINGCláusulas de apoio podem ser um pouco complicadas, no entanto. Além disso, não consegui encontrar uma maneira segura de usar regras para inserir simultaneamente em ambas as tabelas para lidar com o uso de uma sequência para arquivos driver_id. A maneira mais fácil seria usar uma WITHcláusula em um INSERT(CTE), mas estes não são permitidos em conjunto com NEW(error: rules cannot refer to NEW within WITH query), deixando um para recorrer ao lastval()qual é fortemente desencorajado .

postgresql trigger
  • 2 2 respostas
  • 5947 Views

2 respostas

  • Voted
  1. Best Answer
    Craig Ringer
    2015-08-12T19:23:13+08:002015-08-12T19:23:13+08:00

    Então, existe uma maneira de preservar current_user, sem dar ao grupo dbuser acesso direto às relações no esquema private?

    Você pode usar uma regra, em vez de um INSTEAD OFgatilho, para fornecer acesso de gravação por meio da exibição. As exibições sempre agem com os direitos de segurança do criador da exibição, e não do usuário que faz a consulta, mas não acho que current_user mude.

    Se seu aplicativo se conecta diretamente como usuário, você pode verificar session_userem vez de current_user. Isso também funciona se você se conectar com um usuário genérico SET SESSION AUTHORIZATION. Não funcionará se você se conectar como um usuário genérico e, em seguida, SET ROLEpara o usuário desejado.

    Não há como obter o usuário imediatamente anterior de dentro de uma SECURITY DEFINERfunção. Você só pode obter o current_usere session_user. Uma maneira de obter last_userou uma pilha de identidades de usuário seria legal, mas não é suportada atualmente.

    • 6
  2. Erwin Brandstetter
    2015-08-14T06:50:00+08:002015-08-14T06:50:00+08:00

    Não é uma resposta completa, mas não caberia em um comentário.

    lastval()&currval()

    O que te faz pensar que lastval()está desanimado? Parece um mal-entendido.

    Na resposta referenciada , Craig recomenda fortemente o uso de um gatilho em vez da regra em um comentário . E eu concordo - exceto pelo seu caso especial, obviamente.

    A resposta desencoraja fortemente o uso de currval()- mas isso parece ser um mal-entendido. Não há nada de errado com lastval()ou melhor currval(). Deixei um comentário com a resposta referenciada.

    Citando o manual:

    currval

    Retorna o valor obtido mais recentemente por nextvalpara esta sequência na sessão atual. (Um erro é relatado se nextvalnunca foi chamado para esta sequência nesta sessão.) Como isso está retornando um valor local da sessão, ele fornece uma resposta previsível, independentemente de outras sessões terem sido executadas nextvaldesde a sessão atual.

    Portanto, isso é seguro com transações simultâneas. A única complicação possível pode surgir de outros gatilhos ou regras que podem chamar o mesmo gatilho inadvertidamente - o que seria um cenário muito improvável e você tem controle total sobre quais gatilhos/regras você instala.

    No entanto , não tenho certeza se a sequência de comandos é preservada nas regras (mesmo que currval()seja uma função volátil ). Além disso, uma linha múltipla INSERTpode deixar você fora de sincronia. Você pode dividir sua REGRA em duas regras, sendo apenas a segunda INSTEAD. Lembre-se, por documentação:

    Várias regras na mesma tabela e no mesmo tipo de evento são aplicadas em ordem alfabética de nomes.

    Não investiguei mais, fora de tempo.

    DEFAULT PRIVILEGES

    Quanto a:

    SET SESSION AUTHORIZATION dbowner;
    ...
    GRANT ALL ON TABLE private.incident TO dbview;
    

    Você pode estar interessado em vez disso:

    ALTER DEFAULT PRIVILEGES FOR ROLE dbowner IN SCHEMA private
       GRANT ALL ON TABLES TO dbview;
    

    Relacionado:

    • Conceda tudo em um esquema específico no banco de dados para uma função de grupo no PostgreSQL
    • Permissão negada para relação <table>
    • Permissões de usuário do PostgreSQL
    • 2

relate perguntas

  • Posso ativar o PITR depois que o banco de dados foi usado

  • Práticas recomendadas para executar a replicação atrasada do deslocamento de tempo

  • Os procedimentos armazenados impedem a injeção de SQL?

  • Sequências Biológicas do UniProt no PostgreSQL

  • Qual é a diferença entre a replicação do PostgreSQL 9.0 e o Slony-I?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve