La stratégie d'utilisation implique l'utilisation du concept de «logique métier dans la base de données», qui vient d'être décrit en détail ici - Une étude sur l'implémentation de la logique métier stockée chez PostgreSQL fonctionne
partie théorique parfaitement décrite dans la documentation des lignes Postgres Pro - Politiques de protection . Ci-dessous, nous examinons la mise en œuvre pratique d'une tâche commerciale spécifique - un modèle d'accès aux données basé sur les rôles.
L'article n'est pas nouveau, il n'y a pas de sens caché et de connaissance secrète. Juste une esquisse sur la mise en œuvre pratique d'une idée théorique. Si quelqu'un est intéressé, lisez la suite. Qui n'est pas intéressé - ne perdez pas votre temps.
Formulation du problème
Il est nécessaire de délimiter l'accès pour visualiser / insérer / modifier / supprimer un document en fonction du rôle de l'utilisateur de l'application. Un rôle signifie une entrée dans la table des rôles liée dans une relation plusieurs-à-plusieurs avec la table des utilisateurs . Les détails d'implémentation des tables sont omis en raison de leur trivialité. De plus, les détails d'implémentation spécifiques liés au domaine sont omis.
la mise en oeuvre
Nous créons des rôles, des schémas, des tables
Création d'objets de base de données
CREATE ROLE store;
CREATE SCHEMA store AUTHORIZATION store;
CREATE TABLE store.docs
(
id integer , --id
man_id integer , --id
stat_id integer , --id
...
is_del BOOLEAN DEFAULT FALSE
);
ALTER TABLE store.docs ADD CONSTRAINT doc_pk PRIMARY KEY (id);
ALTER TABLE store.docs OWNER TO store ;
Création de fonctions pour implémenter RLS
Vérifier si une ligne est SELECT
check_select
CREATE OR REPLACE FUNCTION store.check_select ( current_id store.docs.id%TYPE ) RETURNS boolean AS $$
DECLARE
result boolean ;
curr_pid integer ;
curr_stat_id integer ;
doc_man_id integer ;
BEGIN
-- DBA
IF SESSION_USER = 'curr_dba'
THEN
RETURN TRUE ;
END IF ;
--------------------------------
-- '' -
SELECT
is_del
INTO
result
FROM
store.docs
WHERE
id = current_id ;
IF result = TRUE
THEN
RETURN FALSE ;
END IF ;
--------------------------------
-- id
SELECT
service_function.get_curr_pid ()
INTO
curr_pid ;
--------------------------------
-- id
SELECT
man_id
INTO
doc_man_id
FROM
store.docs
WHERE
id = current_id ;
--------------------------------
--
--
IF doc_man_id != curr_pid OR doc_man_id IS NULL
THEN
RETURN TRUE ;
ELSE
--
SELECT
stat_id
INTO
curr_statid
FROM
store.docs
WHERE
id = current_id ;
-- -
IF curr_statid = 4 OR curr_statid = 9
THEN
RETURN TRUE ;
ELSE
-- -
RETURN FALSE ;
END IF ;
END IF ;
--------------------------------
RETURN FALSE ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.check_select( store.docs.id%TYPE ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_select( store.docs.id%TYPE ) FROM public;
GRANT EXECUTE ON FUNCTION store.check_select( store.docs.id%TYPE ) TO service_functions;
Vérification des lignes INSERT
check_insert
CREATE OR REPLACE FUNCTION store.check_insert ( current_id store.docs.id%TYPE ) RETURNS boolean AS $$
DECLARE
curr_role_id integer ;
BEGIN
--DBA
IF SESSION_USER = 'curr_dba'
THEN
RETURN TRUE ;
END IF ;
--------------------------------
-- id
SELECT
service_functions.current_rid()
INTO
curr_role_id ;
--------------------------------
--
--
IF curr_role_id = 3 OR curr_role_id = 5
THEN
RETURN TRUE ;
END IF ;
--------------------------------
RETURN FALSE ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.check_insert( store.docs.id%TYPE ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_insert( store.docs.id%TYPE ) FROM public;
GRANT EXECUTE ON FUNCTION store.check_insert( store.docs.id%TYPE ) TO service_functions;
Vérifier si une ligne peut être SUPPRIMÉE
check_delete
CREATE OR REPLACE FUNCTION store.check_delete ( current_id store.docs.id%TYPE )
RETURNS boolean AS $$
BEGIN
-- DBA
IF SESSION_USER = 'curr_dba'
THEN
RETURN TRUE ;
END IF ;
--------------------------------
RETURN FALSE ;
END
$$ LANGUAGE plpgsql
SECURITY DEFINER;
ALTER FUNCTION store.check_delete( store.docs.id%TYPE ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_delete( store.docs.id%TYPE ) FROM public;
Vérifier si la ligne peut être UPDATE.
update_using
CREATE OR REPLACE FUNCTION store.update_using ( current_id store.docs.id%TYPE , is_del boolean )
RETURNS boolean AS $$
BEGIN
-- '' -
IF is_del
THEN
RETURN FALSE ;
ELSE
RETURN TRUE ;
END IF ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.update_using( store.docs.id%TYPE , boolean ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.update_using( store.docs.id%TYPE , boolean ) FROM public;
GRANT EXECUTE ON FUNCTION store.update_using( store.docs.id%TYPE ) TO service_functions;
update_check
CREATE OR REPLACE FUNCTION store.update_with_check ( current_id store.docs.id%TYPE , is_del boolean )
RETURNS boolean AS $$
DECLARE
current_rid integer ;
current_statid integer ;
BEGIN
--DBA
IF SESSION_USER = 'curr_dba'
THEN
RETURN TRUE ;
END IF ;
--------------------------------
-- id
SELECT
service_functions.current_rid()
INTO
curr_role_id ;
--------------------------------
-- -
IF is_deleted
THEN
-- ***
IF current_role_id = 3
THEN
SELECT
stat_id
INTO
curr_statid
FROM
store.docs
WHERE
id = current_id ;
-- ***
IF current_status_id = 11
THEN
RETURN FALSE ;
ELSE
--
RETURN TRUE ;
END IF ;
-- , ***
ELSIF current_role_id = 5
THEN
--
RETURN TRUE ;
ELSE
--
RETURN FALSE ;
END IF ;
ELSE
--
RETURN TRUE ;
END IF ;
RETURN FALSE ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.update_with_check( storg.docs.id%TYPE , boolean ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.update_with_check( storg.docs.id%TYPE , boolean ) FROM public;
GRANT EXECUTE ON FUNCTION store.update_with_check( store.docs.id%TYPE ) TO service_functions;
Activation de la stratégie de sécurité au niveau de la ligne pour la table.
ACTIVER LA SÉCURITÉ AU NIVEAU DES RANGÉES
ALTER TABLE store.docs ENABLE ROW LEVEL SECURITY ;
CREATE POLICY doc_select ON store.docs FOR SELECT TO service_functions USING ( (SELECT store.check_select(id)) );
CREATE POLICY doc_insert ON store.docs FOR INSERT TO service_functions WITH CHECK ( (SELECT store.check_insert(id)) );
CREATE POLICY docs_delete ON store.docs FOR DELETE TO service_functions USING ( (SELECT store.check_delete(id)) );
CREATE POLICY doc_update_using ON store.docs FOR UPDATE TO service_functions USING ( (SELECT store.update_using(id , is_del )) );
CREATE POLICY doc_update_check ON store.docs FOR UPDATE TO service_functions WITH CHECK ( (SELECT store.update_with_check(id , is_del )) );
Résultat
Ça marche.
La stratégie proposée a permis de déplacer la mise en œuvre du modèle de rôle du niveau des fonctions métier au niveau du stockage des données.
Les fonctions peuvent être utilisées comme modèle pour implémenter des modèles de masquage de données plus sophistiqués si les exigences métier l'exigent.