Projet

Général

Profil

Anomalie #329 » asgard_5_triggers_v15.sql

Leslie Lemaire, 31/08/2020 13:35

 

-- v0 2020.05.07 AF
-- ...
-- v7 2020.05.29 LL
-- v8 2020.05.29 LL
-- v9 2020.05.30 LL
-- v10 2020.06.02 LL
-- v11 2020.06.11 LL (asgard 0.7.0)
-- v12 2020.06.25 LL (asgard 0.7.1)
-- v13 2020.08.03 LL (asgard 1.0.0)
-- v14 2020.08.27 LL (asgard 1.0.0)
-- v15 2020.08.31 LL (asgard 1.0.0)
/*
trigger AFTER :
- meilleure prise en compte des valeurs NULL comme éléments
des array de ctrl, qui auraient pu fausser certains tests -
"= ANY(array_remove(NEW.ctrl, NULL))"
au lieu de "= ANY(NEW.ctrl)" ;
- simplification de la structuration des remontées d'erreurs ;
- le trigger n'ignore plus les INSERT pour l'application des
droits du producteur, ce qui empêchait la mise en cohérence des
propriétaires lors du référencement.

trigger BEFORE :
- meilleure prise en compte des valeurs NULL comme éléments
des array de ctrl, qui auraient pu fausser certains tests -
"= ANY(array_remove(NEW.ctrl, NULL))" au lieu de "= ANY(NEW.ctrl)" ;
- ajout de contrôles pour les modifications par g_admin sur des
schémas dont un super-utilisateur est producteur, y compris référencement ;
- amélioration du message d'erreur en cas de tentative de déférencement d'un
schéma de la nomenclature ;
- quelques HINT remplacés par DETAIL, qui paraissait plus approprié.
*/


-- MOT DE PASSE DE CONTRÔLE : 'x7-A;#rzo'
-- peut être modifié sans risque par rechercher/remplacer


---------------------------------------------
------ 5 - TRIGGERS SUR GESTION_SCHEMA ------
---------------------------------------------
/* 5.1 - TRIGGER BEFORE
5.2 - TRIGGER AFTER */

------ 5.1 - TRIGGER BEFORE ------

-- FUNCTION: z_asgard_admin.asgard_on_modify_gestion_schema_before()

CREATE OR REPLACE FUNCTION z_asgard_admin.asgard_on_modify_gestion_schema_before() RETURNS trigger
LANGUAGE plpgsql
AS $BODY$
/* OBJET : Fonction exécutée par le trigger asgard_on_modify_gestion_schema_before,
qui valide les informations saisies dans la table de gestion.
CIBLES : z_asgard_admin.gestion_schema.
PORTEE : FOR EACH ROW.
DECLENCHEMENT : BEFORE INSERT, UPDATE, DELETE.*/
DECLARE
n_role text ;
BEGIN
------ INSERT PAR UN UTILISATEUR NON HABILITE ------
IF TG_OP = 'INSERT' AND NOT has_database_privilege(current_database(), 'CREATE')
-- même si creation vaut faux, seul un rôle habilité à créer des
-- schéma peut ajouter des lignes dans la table de gestion
THEN
RAISE EXCEPTION 'TB1. Vous devez être habilité à créer des schémas pour réaliser cette opération.' ;
END IF ;
------ APPLICATION DES VALEURS PAR DEFAUT ------
-- au tout début car de nombreux tests sont faits par la
-- suite sur "NOT NEW.creation"
IF TG_OP IN ('INSERT', 'UPDATE')
THEN
NEW.creation := coalesce(NEW.creation, False) ;
NEW.nomenclature := coalesce(NEW.nomenclature, False) ;
END IF ;
------ EFFACEMENT D'UN ENREGISTREMENT ------
IF TG_OP = 'DELETE'
THEN
-- on n'autorise pas l'effacement si creation vaut True
-- avec une exception pour les commandes envoyées par la fonction
-- de maintenance asgard_sortie_gestion_schema
IF OLD.creation AND (OLD.ctrl[1] IS NULL OR NOT OLD.ctrl[1] = 'EXIT')
THEN
RAISE EXCEPTION 'TB2. Opération interdite (schéma %). L''effacement n''est autorisé que si creation vaut False.', OLD.nom_schema
USING HINT = 'Pour déréférencer un schéma sans le supprimer, vous pouvez utiliser la fonction z_asgard_admin.asgard_sortie_gestion_schema.' ;
END IF;
-- on n'autorise pas l'effacement pour les schémas de la nomenclature
IF OLD.nomenclature
THEN
IF OLD.ctrl[1] = 'EXIT'
THEN
RAISE EXCEPTION 'TB26. Opération interdite (schéma %). Le déréférencement n''est pas autorisé pour les schémas de la nomenclature nationale.', OLD.nom_schema
USING HINT = 'Si vous tenez à déréférencer cet enregistrement, basculez préalablement nomenclature sur False.' ;
ELSE
RAISE EXCEPTION 'TB3. Opération interdite (schéma %). L''effacement n''est pas autorisé pour les schémas de la nomenclature nationale.', OLD.nom_schema
USING HINT = 'Si vous tenez à supprimer définitivement cet enregistrement, basculez préalablement nomenclature sur False.' ;
END IF ;
END IF ;
END IF;

------ DE-CREATION D'UN SCHEMA ------
IF TG_OP = 'UPDATE'
THEN
-- si bloc valait déjà d (schéma "mis à la corbeille")
-- on exécute une commande de suppression du schéma. Toute autre modification sur
-- la ligne est ignorée.
IF OLD.bloc = 'd' AND OLD.creation AND NOT NEW.creation AND NEW.ctrl[2] IS NULL
AND OLD.nom_schema IN (SELECT nspname FROM pg_catalog.pg_namespace)
THEN
-- on bloque tout de même les tentatives de suppression
-- par un utilisateur qui n'aurait pas des droits suffisants (a priori
-- uniquement dans le cas de g_admin avec un schéma appartenant à un
-- super-utilisateur).
IF NOT pg_has_role(OLD.producteur, 'USAGE')
THEN
RAISE EXCEPTION 'TB23. Opération interdite (schéma %).', OLD.nom_schema
USING DETAIL = 'Seul les membres du rôle producteur ' || OLD.producteur || ' peuvent supprimer ce schéma.' ;
ELSE
EXECUTE 'DROP SCHEMA ' || quote_ident(OLD.nom_schema) || ' CASCADE' ;
RAISE NOTICE '... Le schéma % a été supprimé.', OLD.nom_schema ;
RETURN NULL ;
END IF ;
-- sinon, on n'autorise creation à passer de true à false que si le schéma
-- n'existe plus (permet notamment à l'event trigger qui gère les
-- suppressions de mettre creation à false)
ELSIF OLD.creation and NOT NEW.creation
AND NEW.nom_schema IN (SELECT nspname FROM pg_catalog.pg_namespace)
THEN
RAISE EXCEPTION 'TB4. Opération interdite (schéma %). Le champ creation ne peut passer de True à False si le schéma existe.', NEW.nom_schema
USING HINT = 'Si vous supprimez physiquement le schéma avec la commande DROP SCHEMA, creation basculera sur False automatiquement.' ;
END IF ;
END IF ;
IF TG_OP <> 'DELETE'
THEN
------ PROHIBITION DE LA SAISIE MANUELLE DES OID ------
-- vérifié grâce au champ ctrl
IF NEW.ctrl[2] IS NULL
OR NOT array_length(NEW.ctrl, 1) >= 2
OR NEW.ctrl[1] IS NULL
OR NOT NEW.ctrl[1] IN ('CREATE', 'RENAME', 'OWNER', 'DROP', 'SELF', 'EXIT')
OR NOT NEW.ctrl[2] = 'x7-A;#rzo'
-- ctrl NULL ou invalide
THEN

IF NEW.ctrl[1] = 'EXIT'
THEN
RAISE EXCEPTION 'TB17. Opération interdite (schéma %).', coalesce(NEW.nom_schema, '?')
USING HINT = 'Pour déréférencer un schéma, veuillez utiliser la fonction z_asgard_admin.asgard_sortie_gestion_schema.' ;
END IF ;
-- réinitialisation du champ ctrl, qui peut contenir des informations
-- issues de commandes antérieures (dans ctrl[1])
NEW.ctrl := ARRAY['MANUEL', NULL]::text[] ;
IF TG_OP = 'INSERT' AND (
NEW.oid_producteur IS NOT NULL
OR NEW.oid_lecteur IS NOT NULL
OR NEW.oid_editeur IS NOT NULL
OR NEW.oid_schema IS NOT NULL
)
-- cas d'un INSERT manuel pour lequel des OID ont été saisis
-- on les remet à NULL
THEN
NEW.oid_producteur = NULL ;
NEW.oid_editeur = NULL ;
NEW.oid_lecteur = NULL ;
NEW.oid_schema = NULL ;
ELSIF TG_OP = 'UPDATE'
THEN
IF NOT coalesce(NEW.oid_producteur, -1) = coalesce(OLD.oid_producteur, -1)
OR NOT coalesce(NEW.oid_editeur, -1) = coalesce(OLD.oid_editeur, -1)
OR NOT coalesce(NEW.oid_lecteur, -1) = coalesce(OLD.oid_lecteur, -1)
OR NOT coalesce(NEW.oid_schema, -1) = coalesce(OLD.oid_schema, -1)
-- cas d'un UPDATE avec modification des OID
-- on les remet à OLD
THEN
NEW.oid_producteur = OLD.oid_producteur ;
NEW.oid_editeur = OLD.oid_editeur ;
NEW.oid_lecteur = OLD.oid_lecteur ;
NEW.oid_schema = OLD.oid_schema ;
END IF ;
END IF ;
ELSE
-- suppression du mot de passe de contrôle.
-- ctrl[1] est par contre conservé - il sera utilisé
-- par le trigger AFTER pour connaître l'opération
-- à l'origine de son déclenchement.
NEW.ctrl[2] := NULL ;
END IF ;
------ REQUETES AUTO A IGNORER ------
-- les remontées du trigger AFTER (SELF)
-- sont exclues, car les contraintes ont déjà
-- été validées (et pose problèmes avec les
-- contrôles d'OID sur les UPDATE, car ceux-ci
-- ne seront pas nécessairement déjà remplis) ;
-- les requêtes EXIT de même, car c'est un
-- pré-requis à la suppression qui ne fait
-- que modifier le champ ctrl
IF NEW.ctrl[1] IN ('SELF', 'EXIT')
THEN
-- aucune action
RETURN NEW ;
END IF ;
------ VERROUILLAGE DES CHAMPS LIES A LA NOMENCLATURE ------
-- modifiables uniquement par l'ADL
IF TG_OP = 'UPDATE'
THEN
IF (OLD.nomenclature OR NEW.nomenclature) AND NOT pg_has_role('g_admin', 'MEMBER') AND (
NOT coalesce(OLD.nomenclature, False) = coalesce(NEW.nomenclature, False)
OR NOT coalesce(OLD.niv1, '') = coalesce(NEW.niv1, '')
OR NOT coalesce(OLD.niv1_abr, '') = coalesce(NEW.niv1_abr, '')
OR NOT coalesce(OLD.niv2, '') = coalesce(NEW.niv2, '')
OR NOT coalesce(OLD.niv2_abr, '') = coalesce(NEW.niv2_abr, '')
OR NOT coalesce(OLD.nom_schema, '') = coalesce(NEW.nom_schema, '')
OR NOT coalesce(OLD.bloc, '') = coalesce(NEW.bloc, '')
)
THEN
RAISE EXCEPTION 'TB18. Opération interdite (schéma %).', NEW.nom_schema
USING DETAIL = 'Seuls les membres de g_admin sont habilités à modifier les champs nomenclature et - pour les schémas de la nomenclature - bloc, niv1, niv1_abr, niv2, niv2_abr et nom_schema.' ;
END IF ;
ELSIF TG_OP = 'INSERT'
THEN
IF NEW.nomenclature AND NOT pg_has_role('g_admin', 'MEMBER')
THEN
RAISE EXCEPTION 'TB19. Opération interdite (schéma %).', NEW.nom_schema
USING DETAIL = 'Seuls les membres de g_admin sont autorisés à ajouter des schémas à la nomenclature (nomenclature = True).' ;
END IF ;
END IF ;
------ NETTOYAGE DES CHAÎNES VIDES ------
-- si l'utilisateur a entré des chaînes vides on met des NULL
NEW.editeur := nullif(NEW.editeur, '') ;
NEW.lecteur := nullif(NEW.lecteur, '') ;
NEW.bloc := nullif(NEW.bloc, '') ;
NEW.niv1 := nullif(NEW.niv1, '') ;
NEW.niv1_abr := nullif(NEW.niv1_abr, '') ;
NEW.niv2 := nullif(NEW.niv2, '') ;
NEW.niv2_abr := nullif(NEW.niv2_abr, '') ;
NEW.nom_schema := nullif(NEW.nom_schema, '') ;
-- si producteur est vide on met par défaut g_admin
NEW.producteur := coalesce(nullif(NEW.producteur, ''), 'g_admin') ;
------ NETTOYAGE DES CHAMPS OID ------
-- pour les rôles de lecteur et éditeur,
-- si le champ de nom est vidé par l'utilisateur,
-- on vide en conséquence l'OID
IF NEW.editeur IS NULL
THEN
NEW.oid_editeur := NULL ;
END IF ;
IF NEW.lecteur IS NULL
THEN
NEW.oid_lecteur := NULL ;
END IF ;
-- si le schéma n'est pas créé, on s'assure que les champs
-- d'OID restent vides
-- à noter que l'event trigger sur DROP SCHEMA vide
-- déjà le champ oid_schema
IF NOT NEW.creation
THEN
NEW.oid_schema := NULL ;
NEW.oid_lecteur := NULL ;
NEW.oid_editeur := NULL ;
NEW.oid_producteur := NULL ;
END IF ;
------ VALIDITE DES NOMS DE ROLES ------
-- dans le cas d'un schéma pré-existant, on s'assure que les rôles qui
-- ne changent pas sont toujours valides (qu'ils existent et que le nom
-- n'a pas été modifié entre temps)
-- si tel est le cas, on les met à jour et on le note dans
-- ctrl, pour que le trigger AFTER sache qu'il ne s'agit
-- pas réellement de nouveaux rôles sur lesquels les droits
-- devraient être réappliqués
IF TG_OP = 'UPDATE' AND NEW.creation
THEN
-- producteur
IF OLD.creation AND OLD.producteur = NEW.producteur
THEN
SELECT rolname INTO n_role
FROM pg_catalog.pg_roles
WHERE pg_roles.oid = NEW.oid_producteur ;
IF NOT FOUND
-- le rôle producteur n'existe pas
THEN
-- cas invraisemblable, car un rôle ne peut pas être
-- supprimé alors qu'il est propriétaire d'un schéma, et la
-- commande ALTER SCHEMA OWNER TO aurait été interceptée
-- mais, s'il advient, on repart du propriétaire
-- renseigné dans pg_namespace
SELECT replace(nspowner::regrole::text, '"', ''), nspowner
INTO NEW.producteur, NEW.oid_producteur
FROM pg_catalog.pg_namespace
WHERE pg_namespace.oid = NEW.oid_schema ;
RAISE NOTICE '[table de gestion] ANOMALIE. Schéma %. L''OID actuellement renseigné pour le producteur est invalide. Poursuite avec l''OID du propriétaire courant du schéma.', NEW.nom_schema ;
NEW.ctrl := array_append(NEW.ctrl, 'CLEAN producteur') ;
ELSIF NOT n_role = NEW.producteur
-- libellé obsolète du producteur
THEN
NEW.producteur := n_role ;
RAISE NOTICE '[table de gestion] Schéma %. Mise à jour du libellé du rôle producteur, renommé entre temps.', NEW.nom_schema
USING DETAIL = 'Ancien nom "' || OLD.producteur || '", nouveau nom "' || NEW.producteur || '".' ;
NEW.ctrl := array_append(NEW.ctrl, 'CLEAN producteur') ;
END IF ;
END IF ;
-- éditeur
IF OLD.creation AND OLD.editeur = NEW.editeur
AND NOT NEW.editeur = 'public'
THEN
SELECT rolname INTO n_role
FROM pg_catalog.pg_roles
WHERE pg_roles.oid = NEW.oid_editeur ;
IF NOT FOUND
-- le rôle éditeur n'existe pas
THEN
NEW.editeur := NULL ;
NEW.oid_editeur := NULL ;
RAISE NOTICE '[table de gestion] Schéma %. Le rôle éditeur n''existant plus, il est déréférencé.', NEW.nom_schema
USING DETAIL = 'Ancien nom "' || OLD.editeur || '".' ;
NEW.ctrl := array_append(NEW.ctrl, 'CLEAN editeur') ;
ELSIF NOT n_role = NEW.editeur
-- libellé obsolète de l'éditeur
THEN
NEW.editeur := n_role ;
RAISE NOTICE '[table de gestion] Schéma %. Mise à jour du libellé du rôle éditeur, renommé entre temps.', NEW.nom_schema
USING DETAIL = 'Ancien nom "' || OLD.editeur || '", nouveau nom "' || NEW.editeur || '".' ;
NEW.ctrl := array_append(NEW.ctrl, 'CLEAN editeur') ;
END IF ;
END IF ;
-- lecteur
IF OLD.creation AND OLD.lecteur = NEW.lecteur
AND NOT NEW.lecteur = 'public'
THEN
SELECT rolname INTO n_role
FROM pg_catalog.pg_roles
WHERE pg_roles.oid = NEW.oid_lecteur ;
IF NOT FOUND
-- le rôle lecteur n'existe pas
THEN
NEW.lecteur := NULL ;
NEW.oid_lecteur := NULL ;
RAISE NOTICE '[table de gestion] Schéma %. Le rôle lecteur n''existant plus, il est déréférencé.', NEW.nom_schema
USING DETAIL = 'Ancien nom "' || OLD.lecteur || '".' ;
NEW.ctrl := array_append(NEW.ctrl, 'CLEAN lecteur') ;
ELSIF NOT n_role = NEW.lecteur
-- libellé obsolète du lecteur
THEN
NEW.lecteur := n_role ;
RAISE NOTICE '[table de gestion] Schéma %. Mise à jour du libellé du rôle lecteur, renommé entre temps.', NEW.nom_schema
USING DETAIL = 'Ancien nom "' || OLD.lecteur || '", nouveau nom "' || NEW.lecteur || '".' ;
NEW.ctrl := array_append(NEW.ctrl, 'CLEAN lecteur') ;
END IF ;
END IF ;
END IF ;

------ NON RESPECT DES CONTRAINTES ------
-- non nullité de nom_schema
IF NEW.nom_schema IS NULL
THEN
RAISE EXCEPTION 'TB8. Saisie incorrecte. Le nom du schéma doit être renseigné (champ nom_schema).' ;
END IF ;
-- unicité de nom_schema
-- -> contrôlé après les manipulations sur les blocs de
-- la partie suivante.
-- unicité de oid_schema
IF TG_OP = 'INSERT' AND NEW.oid_schema IN (SELECT gestion_schema_etr.oid_schema FROM z_asgard.gestion_schema_etr
WHERE gestion_schema_etr.oid_schema IS NOT NULL)
THEN
RAISE EXCEPTION 'TB11. Saisie incorrecte (schéma %). Un schéma de même OID est déjà répertorié dans la table de gestion.', NEW.nom_schema ;
ELSIF TG_OP = 'UPDATE'
THEN
-- cas (très hypothétique) d'une modification d'OID
IF NOT coalesce(NEW.oid_schema, -1) = coalesce(OLD.oid_schema, -1)
AND NEW.oid_schema IN (SELECT gestion_schema_etr.oid_schema FROM z_asgard.gestion_schema_etr
WHERE gestion_schema_etr.oid_schema IS NOT NULL)
THEN
RAISE EXCEPTION 'TB12. Saisie incorrecte (schéma %). Un schéma de même OID est déjà répertorié dans la table de gestion.', NEW.nom_schema ;
END IF ;
END IF ;
-- non répétition des rôles
IF NOT ((NEW.oid_lecteur IS NULL OR NOT NEW.oid_lecteur = NEW.oid_producteur)
AND (NEW.oid_editeur IS NULL OR NOT NEW.oid_editeur = NEW.oid_producteur)
AND (NEW.oid_lecteur IS NULL OR NEW.oid_editeur IS NULL OR NOT NEW.oid_lecteur = NEW.oid_editeur))
THEN
RAISE EXCEPTION 'TB13. Saisie incorrecte (schéma %). Les rôles producteur, lecteur et éditeur doivent être distincts.', NEW.nom_schema ;
END IF ;
END IF ;
------ COHERENCE BLOC/NOM DU SCHEMA ------
IF TG_OP IN ('INSERT', 'UPDATE')
THEN
IF NEW.bloc IS NULL AND NEW.nom_schema ~ '^[a-z]_'
-- si bloc est NULL, mais que le nom du schéma
-- comporte un préfixe, on met ce préfixe dans bloc
THEN
NEW.bloc := substring(NEW.nom_schema, '^([a-z])_') ;
RAISE NOTICE '[table de gestion] Mise à jour du bloc pour le schéma %.', NEW.nom_schema || ' (' || NEW.bloc || ')' ;
ELSIF NEW.bloc IS NULL
-- si bloc est NULL, et que (sous-entendu) le nom du schéma ne
-- respecte pas la nomenclature, on avertit l'utilisateur
THEN
RAISE NOTICE '[table de gestion] Le nom du schéma % ne respecte pas la nomenclature.', NEW.nom_schema
USING HINT = 'Si vous saisissez un préfixe dans le champ bloc, il sera automatiquement ajouté au nom du schéma.' ;
ELSIF NOT NEW.nom_schema ~ ('^'|| NEW.bloc || '_') AND NOT NEW.bloc = 'd'
-- le bloc est renseigné mais le nom du schéma ne correspond pas
-- (et il ne s'agit pas d'un schéma mis à la corbeille) :
-- si le nom est de la forme 'a_...', alors :
-- - dans le cas d'un UPDATE avec modification du nom
-- du schéma et pas du bloc, on se fie au nom du schéma
-- et on change le bloc ;
-- - si bloc n'est pas une lettre, on renvoie une erreur ;
-- - dans les autres cas, on se fie au bloc et change le
-- préfixe.
-- Si le nom ne comporte pas de préfixe :
-- - si le bloc est une lettre, on l'ajoute au début du
-- nom (sans doubler l'underscore, si le nom commençait par
-- un underscore) ;
-- - sinon on renvoie une erreur.
THEN
IF NEW.nom_schema ~ '^([a-z])?_'
THEN
IF TG_OP = 'UPDATE'
THEN
IF NOT NEW.nom_schema = OLD.nom_schema AND NEW.bloc = OLD.bloc
THEN
NEW.bloc := substring(NEW.nom_schema, '^([a-z])_') ;
RAISE NOTICE '[table de gestion] Mise à jour du bloc pour le schéma %.', NEW.nom_schema || ' (' || NEW.bloc || ')' ;
ELSIF NOT NEW.bloc ~ '^[a-z]$'
THEN
RAISE EXCEPTION 'TB14. Saisie invalide (schéma %). Le bloc doit être une lettre minuscule ou rien.', NEW.nom_schema ;
ELSE
NEW.nom_schema := regexp_replace(NEW.nom_schema, '^([a-z])?_', NEW.bloc || '_') ;
RAISE NOTICE '[table de gestion] Mise à jour du préfixe du schéma %.', NEW.nom_schema || ' d''après son bloc (' || NEW.bloc || ')' ;
END IF ;
ELSIF NOT NEW.bloc ~ '^[a-z]$'
THEN
RAISE EXCEPTION 'TB15. Saisie invalide (schéma %). Le bloc doit être une lettre minuscule ou rien.', NEW.nom_schema ;
ELSE
NEW.nom_schema := regexp_replace(NEW.nom_schema, '^([a-z])?_', NEW.bloc || '_') ;
RAISE NOTICE '[table de gestion] Mise à jour du préfixe du schéma %.', NEW.nom_schema || ' d''après son bloc (' || NEW.bloc || ')' ;
END IF ;
ELSIF NOT NEW.bloc ~ '^[a-z]$'
THEN
RAISE EXCEPTION 'TB16. Saisie invalide (schéma %). Le bloc doit être une lettre minuscule ou rien.', NEW.nom_schema ;
ELSE
NEW.nom_schema := NEW.bloc || '_' || NEW.nom_schema ;
RAISE NOTICE '[table de gestion] Mise à jour du préfixe du schéma %.', NEW.nom_schema || ' d''après son bloc (' || NEW.bloc || ')' ;
END IF ;
-- le trigger AFTER se chargera de renommer physiquement le
-- schéma d'autant que de besoin
END IF ;
END IF ;
------ NON RESPECT DES CONTRAINTES (SUITE) ------
-- unicité de nom_schema
IF TG_OP IN ('INSERT', 'UPDATE')
THEN
IF TG_OP = 'INSERT' AND NEW.nom_schema IN (SELECT gestion_schema_etr.nom_schema FROM z_asgard.gestion_schema_etr)
THEN
RAISE EXCEPTION 'TB9. Saisie incorrecte (schéma %). Un schéma de même nom est déjà répertorié dans la table de gestion.', NEW.nom_schema ;
ELSIF TG_OP = 'UPDATE'
THEN
-- cas d'un changement de nom
IF NOT NEW.nom_schema = OLD.nom_schema
AND NEW.nom_schema IN (SELECT gestion_schema_etr.nom_schema FROM z_asgard.gestion_schema_etr)
THEN
RAISE EXCEPTION 'TB10. Saisie incorrecte (schéma %). Un schéma de même nom est déjà répertorié dans la table de gestion.', NEW.nom_schema ;
END IF ;
END IF ;
END IF ;
------ MISE À LA CORBEILLE ------
-- notification de l'utilisateur
IF TG_OP = 'UPDATE'
THEN
-- schéma existant dont bloc bascule sur 'd'
-- ou schéma créé par bascule de creation sur True dans bloc vaut 'd'
IF NEW.creation AND NEW.bloc = 'd' AND (NOT OLD.bloc = 'd' OR OLD.bloc IS NULL)
OR NEW.creation AND NOT OLD.creation AND NEW.bloc = 'd'
THEN
RAISE NOTICE '[table de gestion] Le schéma % a été mis à la corbeille (bloc = ''d'').', NEW.nom_schema
USING HINT = 'Si vous basculez creation sur False, le schéma et son contenu seront automatiquement supprimés.' ;
-- restauration
ELSIF NEW.creation AND OLD.bloc = 'd' AND (NOT NEW.bloc = 'd' OR NEW.bloc IS NULL)
THEN
RAISE NOTICE '[table de gestion] Le schéma % a été retiré de la corbeille (bloc ne vaut plus ''d'').', NEW.nom_schema ;
END IF ;
ELSIF TG_OP = 'INSERT'
THEN
-- nouveau schéma dont bloc vaut 'd'
IF NEW.creation AND NEW.bloc = 'd'
THEN
RAISE NOTICE '[table de gestion] Le schéma % a été mis à la corbeille (bloc = ''d'').', NEW.nom_schema
USING HINT = 'Si vous basculez creation sur False, le schéma et son contenu seront automatiquement supprimés.' ;
END IF ;
END IF ;
------ SCHEMAS DES SUPER-UTILISATEURS ------
-- concerne uniquement les membres de g_admin, qui voient tous
-- les schémas, y compris ceux des super-utilisateurs dont ils
-- ne sont pas membres. Les contrôles suivants bloquent dans ce
-- cas les tentatives de mise à jour des champs nom_schema,
-- producteur, editeur et lecteur, ainsi que les création de schéma
-- via un INSERT ou un UPDATE.
IF TG_OP = 'UPDATE'
THEN
IF NOT pg_has_role(OLD.producteur, 'USAGE')
AND OLD.producteur IN (SELECT rolname FROM pg_catalog.pg_roles WHERE rolsuper)
AND OLD.creation
AND (
NOT OLD.nom_schema = NEW.nom_schema
OR NOT OLD.producteur = NEW.producteur AND (NEW.ctrl IS NULL OR NOT 'CLEAN producteur' = ANY(array_remove(NEW.ctrl, NULL)))
OR NOT coalesce(OLD.editeur, '') = coalesce(NEW.editeur, '') AND (NEW.ctrl IS NULL OR NOT 'CLEAN editeur' = ANY(array_remove(NEW.ctrl, NULL)))
OR NOT coalesce(OLD.lecteur, '') = coalesce(NEW.lecteur, '') AND (NEW.ctrl IS NULL OR NOT 'CLEAN lecteur' = ANY(array_remove(NEW.ctrl, NULL)))
)
THEN
RAISE EXCEPTION 'TB20. Opération interdite (schéma %).', OLD.nom_schema
USING DETAIL = 'Seul le rôle producteur ' || OLD.producteur || ' (super-utilisateur) peut modifier ce schéma.' ;
ELSIF NOT pg_has_role(NEW.producteur, 'USAGE')
AND NEW.producteur IN (SELECT rolname FROM pg_catalog.pg_roles WHERE rolsuper)
AND NEW.creation AND NOT OLD.creation
THEN
RAISE EXCEPTION 'TB21. Opération interdite (schéma %).', NEW.nom_schema
USING DETAIL = 'Seul le super-utilisateur ' || NEW.producteur || ' peut créer un schéma dont il est identifié comme producteur.' ;
ELSIF NOT pg_has_role(NEW.producteur, 'USAGE')
AND NEW.producteur IN (SELECT rolname FROM pg_catalog.pg_roles WHERE rolsuper)
AND NEW.creation
AND NOT OLD.producteur = NEW.producteur AND (NEW.ctrl IS NULL OR NOT 'CLEAN producteur' = ANY(array_remove(NEW.ctrl, NULL)))
THEN
RAISE EXCEPTION 'TB24. Opération interdite (schéma %).', NEW.nom_schema
USING DETAIL = 'Seul le super-utilisateur ' || NEW.producteur || ' peut se désigner comme producteur d''un schéma.' ;
END IF ;
ELSIF TG_OP = 'INSERT'
THEN
IF NOT pg_has_role(NEW.producteur, 'USAGE')
AND NEW.producteur IN (SELECT rolname FROM pg_catalog.pg_roles WHERE rolsuper)
AND NEW.creation
AND NOT NEW.nom_schema IN (SELECT nspname FROM pg_catalog.pg_namespace)
-- on exclut les schémas en cours de référencement, qui sont gérés
-- juste après, avec leur propre message d'erreur
THEN
RAISE EXCEPTION 'TB22. Opération interdite (schéma %).', NEW.nom_schema
USING DETAIL = 'Seul le super-utilisateur ' || NEW.producteur || ' peut créer un schéma dont il est identifié comme producteur.' ;
ELSIF NOT pg_has_role(NEW.producteur, 'USAGE')
AND NEW.producteur IN (SELECT rolname FROM pg_catalog.pg_roles WHERE rolsuper)
AND NEW.nom_schema IN (SELECT nspname FROM pg_catalog.pg_namespace)
-- schéma pré-existant en cours de référencement
THEN
RAISE EXCEPTION 'TB25. Opération interdite (schéma %).', NEW.nom_schema
USING DETAIL = 'Seul le super-utilisateur ' || NEW.producteur || ' peut référencer dans ASGARD un schéma dont il est identifié comme producteur.' ;
END IF ;
END IF ;
------ RETURN ------
IF TG_OP IN ('UPDATE', 'INSERT')
THEN
RETURN NEW ;
ELSIF TG_OP = 'DELETE'
THEN
RETURN OLD ;
END IF ;
END
$BODY$ ;

ALTER FUNCTION z_asgard_admin.asgard_on_modify_gestion_schema_before()
OWNER TO g_admin ;

COMMENT ON FUNCTION z_asgard_admin.asgard_on_modify_gestion_schema_before() IS 'ASGARD. Fonction appelée par le trigger qui valide les modifications de la table de gestion.';


-- Trigger: asgard_on_modify_gestion_schema_before

CREATE TRIGGER asgard_on_modify_gestion_schema_before
BEFORE INSERT OR DELETE OR UPDATE
ON z_asgard_admin.gestion_schema
FOR EACH ROW
EXECUTE PROCEDURE z_asgard_admin.asgard_on_modify_gestion_schema_before() ;
COMMENT ON TRIGGER asgard_on_modify_gestion_schema_before ON z_asgard_admin.gestion_schema IS 'ASGARD. Trigger qui valide les modifications de la table de gestion.';


------ 5.2 - TRIGGER AFTER ------

-- FUNCTION: z_asgard_admin.asgard_on_modify_gestion_schema_after()

CREATE OR REPLACE FUNCTION z_asgard_admin.asgard_on_modify_gestion_schema_after() RETURNS trigger
LANGUAGE plpgsql
AS $BODY$
/* OBJET : Fonction exécutée par le trigger asgard_on_modify_gestion_schema_after,
qui répercute physiquement les modifications de la table de gestion.
CIBLES : z_asgard_admin.gestion_schema.
PORTEE : FOR EACH ROW.
DECLENCHEMENT : AFTER INSERT OR UPDATE.*/
DECLARE
utilisateur text ;
createur text ;
administrateur text ;
e_mssg text ;
e_hint text ;
e_detl text ;
b_superuser boolean ;
b_test boolean ;
l_commande text[] ;
c text ;
c_reverse text ;
a_producteur text ;
a_editeur text ;
a_lecteur text ;
n int ;
BEGIN

------ REQUETES AUTO A IGNORER ------
-- les remontées du trigger lui-même (SELF),
-- ainsi que des event triggers sur les
-- suppressions de schémas (DROP), n'appellent
-- aucune action, elles sont donc exclues dès
-- le départ
-- les remontées des changements de noms sont
-- conservées, pour le cas où la mise en
-- cohérence avec "bloc" aurait conduit à une
-- modification du nom par le trigger BEFORE
-- (géré au point suivant)
-- les remontées des créations et changements
-- de propriétaire (CREATE et OWNER) appellent
-- des opérations sur les droits plus lourdes
-- qui ne permettent pas de les exclure en
-- amont
IF NEW.ctrl[1] IN ('SELF', 'DROP')
THEN
-- aucune action
RETURN NULL ;
END IF ;

------ MANIPULATIONS PREALABLES ------
utilisateur := current_user ;
-- si besoin pour les futures opérations sur les rôles,
-- récupération du nom d'un rôle dont current_user est membre
-- et qui a l'attribut CREATEROLE. Autant que possible, la
-- requête renvoie current_user lui-même. On exclut d'office les
-- rôles NOINHERIT qui ne pourront pas avoir simultanément les
-- droits du propriétaire de NEW et OLD.producteur
SELECT rolname INTO createur FROM pg_roles
WHERE pg_has_role(rolname, 'MEMBER') AND rolcreaterole AND rolinherit
ORDER BY rolname = current_user DESC ;
IF TG_OP = 'UPDATE'
THEN
-- la validité de OLD.producteur n'ayant
-- pas été contrôlée par le trigger BEFORE,
-- on le fait maintenant
SELECT rolname INTO a_producteur
FROM pg_catalog.pg_roles
WHERE pg_roles.oid = OLD.oid_producteur ;
-- pour la suite, on emploira toujours
-- a_producteur à la place de OLD.producteur
-- pour les opérations sur les droits.
-- Il est réputé non NULL pour un schéma
-- pré-existant (OLD.creation vaut True),
-- dans la mesure où un rôle ne peut être
-- supprimé s'il est propriétaire d'un
-- schéma et où tous les changements de
-- propriétaires sont remontés par event
-- triggers (+ contrôles pour assurer la
-- non-modification manuelle des OID).
IF NOT FOUND AND OLD.creation AND (NEW.ctrl IS NULL OR NOT 'CLEAN producteur' = ANY(array_remove(NEW.ctrl, NULL)))
THEN
RAISE NOTICE '[table de gestion] ANOMALIE. Schéma %. L''OID actuellement renseigné pour le producteur dans la table de gestion est invalide. Poursuite avec l''OID du propriétaire courant du schéma.', OLD.nom_schema ;
SELECT replace(nspowner::regrole::text, '"', '') INTO a_producteur
FROM pg_catalog.pg_namespace
WHERE pg_namespace.oid = NEW.oid_schema ;
IF NOT FOUND
THEN
RAISE EXCEPTION 'TA1. Anomalie critique (schéma %). Le propriétaire du schéma est introuvable.', OLD.nom_schema ;
END IF ;
END IF ;
END IF ;

------ MISE EN APPLICATION D'UN CHANGEMENT DE NOM DE SCHEMA ------
IF NOT NEW.oid_schema::regnamespace::text = quote_ident(NEW.nom_schema)
-- le schéma existe et ne porte pas déjà le nom NEW.nom_schema
THEN
EXECUTE 'ALTER SCHEMA '|| NEW.oid_schema::regnamespace::text ||
' RENAME TO ' || quote_ident(NEW.nom_schema) ;
RAISE NOTICE '... Le schéma % a été renommé.', NEW.nom_schema ;
END IF ;
-- exclusion des remontées d'event trigger correspondant
-- à des changements de noms
IF NEW.ctrl[1] = 'RENAME'
THEN
-- aucune action
RETURN NULL ;
END IF ;

------ PREPARATION DU PRODUCTEUR ------
-- on ne s'intéresse pas aux cas :
-- - d'un schéma qui n'a pas/plus vocation à exister
-- (creation vaut False) ;
-- - d'un schéma pré-existant dont les rôles ne changent pas
-- ou dont le libellé a juste été nettoyé par le trigger
-- BEFORE.
-- ils sont donc exclus au préalable
-- si le moindre rôle a changé, il faudra être membre du
-- groupe propriétaire/producteur pour pouvoir modifier
-- les privilèges en conséquence
b_test := False ;
IF NOT NEW.creation
THEN
b_test := True ;
ELSIF TG_OP = 'UPDATE'
THEN
IF OLD.creation
AND (NEW.producteur = OLD.producteur OR 'CLEAN producteur' = ANY(array_remove(NEW.ctrl, NULL)))
AND (coalesce(NEW.editeur, '') = coalesce(OLD.editeur, '') OR 'CLEAN editeur' = ANY(array_remove(NEW.ctrl, NULL)))
AND (coalesce(NEW.lecteur, '') = coalesce(OLD.lecteur, '') OR 'CLEAN lecteur' = ANY(array_remove(NEW.ctrl, NULL)))
THEN
b_test := True ;
END IF ;
END IF ;
IF NOT b_test
THEN
IF NOT NEW.producteur IN (SELECT rolname FROM pg_catalog.pg_roles)
-- si le producteur désigné n'existe pas, on le crée
-- ou renvoie une erreur si les privilèges de l'utilisateur
-- sont insuffisants
THEN
IF createur IS NULL
THEN
RAISE EXCEPTION 'TA2. Opération interdite. Vous n''êtes pas habilité à créer le rôle %.', NEW.producteur
USING HINT = 'Être membre d''un rôle disposant des attributs CREATEROLE et INHERIT est nécessaire pour créer de nouveaux producteurs.' ;
END IF ;
EXECUTE 'SET ROLE ' || quote_ident(createur) ;
EXECUTE 'CREATE ROLE ' || quote_ident(NEW.producteur) ;
RAISE NOTICE '... Le rôle de groupe % a été créé.', NEW.producteur ;
EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
ELSE
-- si le rôle producteur existe, on vérifie qu'il n'a pas l'option LOGIN
-- les superusers avec LOGIN (comme postgres) sont tolérés
-- paradoxe ou non, dans l'état actuel des choses, cette erreur se
-- déclenche aussi lorsque la modification ne porte que sur les rôles
-- lecteur/éditeur
SELECT rolsuper INTO b_superuser
FROM pg_roles WHERE rolname = NEW.producteur AND rolcanlogin ;
IF NOT b_superuser
THEN
RAISE EXCEPTION 'TA3. Opération interdite (schéma %). Le producteur/propriétaire du schéma ne doit pas être un rôle de connexion.', NEW.nom_schema ;
END IF ;
END IF ;
b_superuser := coalesce(b_superuser, False) ;
-- mise à jour du champ d'OID du producteur
IF NEW.ctrl[1] IS NULL OR NOT NEW.ctrl[1] IN ('OWNER', 'CREATE')
-- pas dans le cas d'une remontée de commande directe
-- où l'OID du producteur sera déjà renseigné
-- et uniquement s'il a réellement été modifié (ce
-- qui n'est pas le cas si les changements ne portent
-- que sur les rôles lecteur/éditeur)
THEN
UPDATE z_asgard.gestion_schema_etr
SET oid_producteur = quote_ident(NEW.producteur)::regrole::oid,
ctrl = ARRAY['SELF', 'x7-A;#rzo']
WHERE nom_schema = NEW.nom_schema AND (
oid_producteur IS NULL
OR NOT oid_producteur = quote_ident(NEW.producteur)::regrole::oid
) ;
END IF ;

-- implémentation des permissions manquantes sur NEW.producteur
IF NOT pg_has_role(utilisateur, NEW.producteur, 'USAGE')
THEN
b_test := True ;
IF createur IS NULL OR b_superuser
THEN
RAISE EXCEPTION 'TA4. Opération interdite. Permissions insuffisantes pour le rôle %.', NEW.producteur
USING HINT = 'Votre rôle doit être membre de ' || NEW.producteur
|| ' ou disposer de l''attribut CREATEROLE pour réaliser cette opération.' ;
END IF ;
END IF ;
IF TG_OP = 'UPDATE'
THEN
IF OLD.creation AND NOT pg_has_role(utilisateur, a_producteur, 'USAGE')
AND NOT (NEW.producteur = OLD.producteur OR 'CLEAN producteur' = ANY(array_remove(NEW.ctrl, NULL)))
-- les permissions sur OLD.producteur ne sont contrôlées que si le producteur
-- a effectivement été modifié
THEN
b_test := True ;
IF createur IS NULL OR b_superuser
THEN
RAISE EXCEPTION 'TA5. Opération interdite. Permissions insuffisantes pour le rôle %.', a_producteur
USING HINT = 'Votre rôle doit être membre de ' || a_producteur
|| ' ou disposer de l''attribut CREATEROLE pour réaliser cette opération.' ;
END IF ;
END IF ;
END IF ;
IF b_test
THEN
EXECUTE 'SET ROLE ' || quote_ident(createur) ;
-- par commodité, on rend createur membre à la fois de NEW et (si besoin)
-- de OLD.producteur, même si l'utilisateur avait déjà accès à
-- l'un des deux par ailleurs :
IF NOT pg_has_role(createur, NEW.producteur, 'USAGE') AND NOT b_superuser
THEN
EXECUTE 'GRANT ' || quote_ident(NEW.producteur) || ' TO ' || quote_ident(createur) ;
RAISE NOTICE '... Permission accordée à %.', createur || ' sur le rôle ' || NEW.producteur ;
END IF ;
IF TG_OP = 'UPDATE'
THEN
IF NOT pg_has_role(createur, a_producteur, 'USAGE') AND NOT b_superuser
THEN
EXECUTE 'GRANT ' || quote_ident(a_producteur) || ' TO ' || quote_ident(createur) ;
RAISE NOTICE '... Permission accordée à %.', createur || ' sur le rôle ' || a_producteur ;
END IF ;
END IF ;
EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
END IF ;
-- permission de g_admin sur le producteur, s'il y a encore lieu
-- à noter que, dans le cas où le producteur n'a pas été modifié, g_admin
-- devrait déjà avoir une permission sur NEW.producteur, sauf à ce qu'elle
-- lui ait été retirée manuellement entre temps. Les requêtes suivantes
-- génèreraient alors une erreur même dans le cas où la modification ne
-- porte que sur les rôles lecteur/éditeur - ce qui peut-être perçu comme
-- discutable.
IF NOT pg_has_role('g_admin', NEW.producteur, 'USAGE') AND NOT b_superuser
THEN
IF createur IS NOT NULL
THEN
EXECUTE 'SET ROLE ' || quote_ident(createur) ;
EXECUTE 'GRANT ' || quote_ident(NEW.producteur) || ' TO g_admin' ;
RAISE NOTICE '... Permission accordée à g_admin sur le rôle %.', NEW.producteur ;
EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
ELSE
SELECT grantee INTO administrateur
FROM information_schema.applicable_roles
WHERE is_grantable = 'YES' AND role_name = NEW.producteur ;
IF FOUND
THEN
EXECUTE 'SET ROLE ' || quote_ident(administrateur) ;
EXECUTE 'GRANT ' || quote_ident(NEW.producteur) || ' TO g_admin' ;
RAISE NOTICE '... Permission accordée à g_admin sur le rôle %.', NEW.producteur ;
EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
ELSE
RAISE EXCEPTION 'TA6. Opération interdite. Permissions insuffisantes pour le rôle %.', NEW.producteur
USING DETAIL = 'GRANT ' || quote_ident(NEW.producteur) || ' TO g_admin',
HINT = 'Votre rôle doit être membre de ' || NEW.producteur
|| ' avec admin option ou disposer de l''attribut CREATEROLE pour réaliser cette opération.' ;
END IF ;
END IF ;
END IF ;
END IF ;
------ PREPARATION DE L'EDITEUR ------
-- limitée ici à la création du rôle et l'implémentation
-- de son OID. On ne s'intéresse donc pas aux cas :
-- - où il y a pas d'éditeur ;
-- - d'un schéma qui n'a pas/plus vocation à exister ;
-- - d'un schéma pré-existant dont l'éditeur ne change pas
-- ou dont le libellé a seulement été nettoyé par le
-- trigger BEFORE.
-- ils sont donc exclus au préalable
b_test := False ;
IF NOT NEW.creation OR NEW.editeur IS NULL
OR 'CLEAN editeur' = ANY(array_remove(NEW.ctrl, NULL))
THEN
b_test := True ;
ELSIF TG_OP = 'UPDATE'
THEN
IF OLD.creation AND NEW.editeur = OLD.editeur
THEN
b_test := True ;
END IF ;
END IF ;
IF NOT b_test
THEN
IF NOT NEW.editeur IN (SELECT rolname FROM pg_catalog.pg_roles)
AND NOT NEW.editeur = 'public'
-- si l'éditeur désigné n'existe pas, on le crée
-- ou renvoie une erreur si les privilèges de l'utilisateur
-- sont insuffisants
THEN
IF createur IS NULL
THEN
RAISE EXCEPTION 'TA7. Opération interdite. Vous n''êtes pas habilité à créer le rôle %.', NEW.editeur
USING HINT = 'Être membre d''un rôle disposant des attributs CREATEROLE et INHERIT est nécessaire pour créer de nouveaux éditeurs.' ;
END IF ;
EXECUTE 'SET ROLE ' || quote_ident(createur) ;
EXECUTE 'CREATE ROLE ' || quote_ident(NEW.editeur) ;
RAISE NOTICE '... Le rôle de groupe % a été créé.', NEW.editeur ;
EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
END IF ;
-- mise à jour du champ d'OID de l'éditeur
IF NEW.editeur = 'public'
THEN
UPDATE z_asgard.gestion_schema_etr
SET oid_editeur = 0,
ctrl = ARRAY['SELF', 'x7-A;#rzo']
WHERE nom_schema = NEW.nom_schema AND (
oid_editeur IS NULL
OR NOT oid_editeur = 0
) ;
ELSE
UPDATE z_asgard.gestion_schema_etr
SET oid_editeur = quote_ident(NEW.editeur)::regrole::oid,
ctrl = ARRAY['SELF', 'x7-A;#rzo']
WHERE nom_schema = NEW.nom_schema AND (
oid_editeur IS NULL
OR NOT oid_editeur = quote_ident(NEW.editeur)::regrole::oid
) ;
END IF ;
END IF ;
------ PREPARATION DU LECTEUR ------
-- limitée ici à la création du rôle et l'implémentation
-- de son OID. On ne s'intéresse donc pas aux cas :
-- - où il y a pas de lecteur ;
-- - d'un schéma qui n'a pas/plus vocation à exister ;
-- - d'un schéma pré-existant dont l'éditeur ne change pas
-- ou dont le libellé a seulement été nettoyé par le
-- trigger BEFORE.
-- ils sont donc exclus au préalable
b_test := False ;
IF NOT NEW.creation OR NEW.lecteur IS NULL
OR 'CLEAN lecteur' = ANY(array_remove(NEW.ctrl, NULL))
THEN
b_test := True ;
ELSIF TG_OP = 'UPDATE'
THEN
IF OLD.creation AND NEW.lecteur = OLD.lecteur
THEN
b_test := True ;
END IF ;
END IF ;
IF NOT b_test
THEN
IF NOT NEW.lecteur IN (SELECT rolname FROM pg_catalog.pg_roles)
AND NOT NEW.lecteur = 'public'
-- si le lecteur désigné n'existe pas, on le crée
-- ou renvoie une erreur si les privilèges de l'utilisateur
-- sont insuffisants
THEN
IF createur IS NULL
THEN
RAISE EXCEPTION 'TA8. Opération interdite. Vous n''êtes pas habilité à créer le rôle %.', NEW.lecteur
USING HINT = 'Être membre d''un rôle disposant des attributs CREATEROLE et INHERIT est nécessaire pour créer de nouveaux éditeurs.' ;
END IF ;
EXECUTE 'SET ROLE ' || quote_ident(createur) ;
EXECUTE 'CREATE ROLE ' || quote_ident(NEW.lecteur) ;
RAISE NOTICE '... Le rôle de groupe % a été créé.', NEW.lecteur ;
EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
END IF ;
-- mise à jour du champ d'OID du lecteur
IF NEW.lecteur = 'public'
THEN
UPDATE z_asgard.gestion_schema_etr
SET oid_lecteur = 0,
ctrl = ARRAY['SELF', 'x7-A;#rzo']
WHERE nom_schema = NEW.nom_schema AND (
oid_lecteur IS NULL
OR NOT oid_lecteur = 0
) ;
ELSE
UPDATE z_asgard.gestion_schema_etr
SET oid_lecteur = quote_ident(NEW.lecteur)::regrole::oid,
ctrl = ARRAY['SELF', 'x7-A;#rzo']
WHERE nom_schema = NEW.nom_schema AND (
oid_lecteur IS NULL
OR NOT oid_lecteur = quote_ident(NEW.lecteur)::regrole::oid
) ;
END IF ;
END IF ;
------ CREATION DU SCHEMA ------
-- on exclut au préalable les cas qui ne
-- correspondent pas à des créations, ainsi que les
-- remontées de l'event trigger sur CREATE SCHEMA,
-- car le schéma existe alors déjà
b_test := False ;
IF NOT NEW.creation OR NEW.ctrl[1] = 'CREATE'
THEN
b_test := True ;
ELSIF TG_OP = 'UPDATE'
THEN
IF OLD.creation
THEN
b_test := True ;
END IF ;
END IF ;
IF NOT b_test
THEN
-- le schéma est créé s'il n'existe pas déjà (cas d'ajout
-- d'un schéma pré-existant qui n'était pas référencé dans
-- gestion_schema jusque-là), sinon on alerte juste
-- l'utilisateur
IF NOT NEW.nom_schema IN (SELECT nspname FROM pg_catalog.pg_namespace)
THEN
IF NOT has_database_privilege(current_database(), 'CREATE')
OR NOT pg_has_role(NEW.producteur, 'USAGE')
THEN
-- si le rôle courant n'a pas les privilèges nécessaires pour
-- créer le schéma, on tente avec le rôle createur [de rôles]
-- pré-identifié, dont on sait au moins qu'il aura les
-- permissions nécessaires sur le rôle producteur - mais pas
-- s'il est habilité à créer des schémas
IF createur IS NOT NULL
THEN
EXECUTE 'SET ROLE ' || quote_ident(createur) ;
END IF ;
IF NOT has_database_privilege(current_database(), 'CREATE')
OR NOT pg_has_role(NEW.producteur, 'USAGE')
THEN
RAISE EXCEPTION 'TA9. Opération interdite. Vous n''êtes pas habilité à créer le schéma %.', NEW.nom_schema
USING HINT = 'Être membre d''un rôle disposant du privilège CREATE sur la base de données est nécessaire pour créer des schémas.' ;
END IF ;
END IF ;
EXECUTE 'CREATE SCHEMA ' || quote_ident(NEW.nom_schema) || ' AUTHORIZATION ' || quote_ident(NEW.producteur) ;
EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
RAISE NOTICE '... Le schéma % a été créé.', NEW.nom_schema ;
ELSE
RAISE NOTICE '(schéma % pré-existant)', NEW.nom_schema ;
END IF ;
-- récupération de l'OID du schéma
UPDATE z_asgard.gestion_schema_etr
SET oid_schema = quote_ident(NEW.nom_schema)::regnamespace::oid,
ctrl = ARRAY['SELF', 'x7-A;#rzo']
WHERE nom_schema = NEW.nom_schema AND (
oid_schema IS NULL
OR NOT oid_schema = quote_ident(NEW.nom_schema)::regnamespace::oid
) ;
END IF ;
------ APPLICATION DES DROITS DU PRODUCTEUR ------
-- comme précédemment pour la préparation du producteur,
-- on ne s'intéresse pas aux cas :
-- - d'un schéma qui n'a pas/plus vocation à exister
-- (creation vaut False) ;
-- - d'un schéma pré-existant dont le producteur ne change pas
-- ou dont le libellé a juste été nettoyé par le trigger
-- BEFORE ;
-- - d'une remontée de l'event trigger asgard_on_create_schema,
-- car le producteur sera déjà propriétaire du schéma
-- et de son éventuel contenu. Par contre on garde les INSERT,
-- pour les cas de référencements ;
-- - de z_asgard_admin (pour permettre sa saisie initiale
-- dans la table de gestion, étant entendu qu'il est
-- impossible au trigger sur gestion_schema de lancer
-- un ALTER TABLE OWNER TO sur cette même table).
-- ils sont donc exclus au préalable
b_test := False ;
IF NOT NEW.creation
OR 'CLEAN producteur' = ANY(array_remove(NEW.ctrl, NULL))
OR NEW.ctrl[1] = 'CREATE'
OR NEW.nom_schema = 'z_asgard_admin'
THEN
b_test := True ;
ELSIF TG_OP = 'UPDATE'
THEN
IF OLD.creation AND NEW.producteur = OLD.producteur
THEN
b_test := True ;
END IF ;
END IF ;
IF NOT b_test
THEN
-- si besoin, on bascule sur le rôle createur. À ce stade,
-- il est garanti que soit l'utilisateur courant soit
-- createur (pour le cas d'un utilisateur courant
-- NOINHERIT) aura les privilèges nécessaires
IF NOT pg_has_role(NEW.producteur, 'USAGE')
THEN
EXECUTE 'SET ROLE ' || quote_ident(createur) ;
ELSIF TG_OP = 'UPDATE'
THEN
IF NOT pg_has_role(a_producteur, 'USAGE')
THEN
EXECUTE 'SET ROLE ' || quote_ident(createur) ;
END IF ;
END IF ;
-- changements de propriétaires
IF (NEW.nom_schema, NEW.producteur)
IN (SELECT schema_name, schema_owner FROM information_schema.schemata)
THEN
-- si producteur est déjà propriétaire du schéma (cas d'une remontée de l'event trigger,
-- principalement), on ne change que les propriétaires des objets éventuels
IF quote_ident(NEW.nom_schema)::regnamespace::oid
IN (SELECT refobjid FROM pg_catalog.pg_depend WHERE deptype = 'n')
THEN
-- la commande n'est cependant lancée que s'il existe des dépendances de type
-- DEPENDENCY_NORMAL sur le schéma, ce qui est une condition nécessaire à
-- l'existence d'objets dans le schéma
RAISE NOTICE 'attribution de la propriété des objets au rôle producteur du schéma % :', NEW.nom_schema ;
SELECT z_asgard.asgard_admin_proprietaire(NEW.nom_schema, NEW.producteur, False)
INTO n ;
IF n = 0
THEN
RAISE NOTICE '> néant' ;
END IF ;
END IF ;
ELSE
-- sinon schéma + objets
RAISE NOTICE 'attribution de la propriété du schéma et des objets au rôle producteur du schéma % :', NEW.nom_schema ;
PERFORM z_asgard.asgard_admin_proprietaire(NEW.nom_schema, NEW.producteur) ;
END IF ;
EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
END IF ;
------ APPLICATION DES DROITS DE L'EDITEUR ------
-- on ne s'intéresse pas aux cas :
-- - d'un schéma qui n'a pas/plus vocation à exister ;
-- - d'un schéma pré-existant dont l'éditeur ne change pas
-- (y compris pour rester vide) ou dont le libellé
-- a seulement été nettoyé par le trigger BEFORE.
-- ils sont donc exclus au préalable
b_test := False ;
IF NOT NEW.creation OR 'CLEAN editeur' = ANY(array_remove(NEW.ctrl, NULL))
THEN
b_test := True ;
ELSIF TG_OP = 'UPDATE'
THEN
IF OLD.creation
AND coalesce(NEW.editeur, '') = coalesce(OLD.editeur, '')
THEN
b_test := True ;
END IF ;
END IF ;
IF NOT b_test
THEN
-- si besoin, on bascule sur le rôle createur. À ce stade,
-- il est garanti que soit l'utilisateur courant soit
-- createur (pour le cas d'un utilisateur courant
-- NOINHERIT) aura les privilèges nécessaires
IF NOT pg_has_role(NEW.producteur, 'USAGE')
THEN
EXECUTE 'SET ROLE ' || quote_ident(createur) ;
END IF ;
IF TG_OP = 'UPDATE'
THEN
-- la validité de OLD.editeur n'ayant
-- pas été contrôlée par le trigger BEFORE,
-- on le fait maintenant
IF OLD.editeur = 'public'
THEN
a_editeur := 'public' ;
-- récupération des modifications manuelles des
-- droits de OLD.editeur/public, grâce à la fonction
-- asgard_synthese_public
SELECT array_agg(commande) INTO l_commande
FROM z_asgard.asgard_synthese_public(
quote_ident(NEW.nom_schema)::regnamespace
) ;
ELSE
SELECT rolname INTO a_editeur
FROM pg_catalog.pg_roles
WHERE pg_roles.oid = OLD.oid_editeur ;
IF FOUND
THEN
-- récupération des modifications manuelles des
-- droits de OLD.editeur, grâce à la fonction
-- asgard_synthese_role
SELECT array_agg(commande) INTO l_commande
FROM z_asgard.asgard_synthese_role(
quote_ident(NEW.nom_schema)::regnamespace,
quote_ident(a_editeur)::regrole
) ;
END IF ;
END IF ;
END IF ;

IF l_commande IS NOT NULL
-- transfert sur NEW.editeur des droits de
-- OLD.editeur, le cas échéant
THEN
IF NEW.editeur IS NOT NULL
THEN
RAISE NOTICE 'suppression et transfert vers le nouvel éditeur des privilèges de l''ancien éditeur du schéma % :', NEW.nom_schema ;
ELSE
RAISE NOTICE 'suppression des privilèges de l''ancien éditeur du schéma % :', NEW.nom_schema ;
END IF ;
FOREACH c IN ARRAY l_commande
LOOP
IF NEW.editeur IS NOT NULL
THEN
EXECUTE format(c, NEW.editeur) ;
RAISE NOTICE '> %', format(c, NEW.editeur) ;
END IF ;
IF c ~ '^GRANT'
THEN
SELECT z_asgard.asgard_grant_to_revoke(c) INTO c_reverse ;
EXECUTE format(c_reverse, a_editeur) ;
RAISE NOTICE '> %', format(c_reverse, a_editeur) ;
END IF ;
END LOOP ;
-- sinon, application des privilèges standards de l'éditeur
ELSIF NEW.editeur IS NOT NULL
THEN
RAISE NOTICE 'application des privilèges standards pour le rôle éditeur du schéma % :', NEW.nom_schema ;
EXECUTE 'GRANT USAGE ON SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
RAISE NOTICE '> %', 'GRANT USAGE ON SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
EXECUTE 'GRANT SELECT, UPDATE, DELETE, INSERT ON ALL TABLES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
RAISE NOTICE '> %', 'GRANT SELECT, UPDATE, DELETE, INSERT ON ALL TABLES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
EXECUTE 'GRANT SELECT, USAGE ON ALL SEQUENCES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
RAISE NOTICE '> %', 'GRANT SELECT, USAGE ON ALL SEQUENCES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
END IF ;
EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
END IF ;
------ APPLICATION DES DROITS DU LECTEUR ------
-- on ne s'intéresse pas aux cas :
-- - d'un schéma qui n'a pas/plus vocation à exister ;
-- - d'un schéma pré-existant dont le lecteur ne change pas
-- (y compris pour rester vide) ou dont le libellé
-- a seulement été nettoyé par le trigger BEFORE.
-- ils sont donc exclus au préalable
b_test := False ;
l_commande := NULL ;
IF NOT NEW.creation OR 'CLEAN lecteur' = ANY(array_remove(NEW.ctrl, NULL))
THEN
b_test := True ;
ELSIF TG_OP = 'UPDATE'
THEN
IF OLD.creation
AND coalesce(NEW.lecteur, '') = coalesce(OLD.lecteur, '')
THEN
b_test := True ;
END IF ;
END IF ;
IF NOT b_test
THEN
-- si besoin, on bascule sur le rôle createur. À ce stade,
-- il est garanti que soit l'utilisateur courant soit
-- createur (pour le cas d'un utilisateur courant
-- NOINHERIT) aura les privilèges nécessaires
IF NOT pg_has_role(NEW.producteur, 'USAGE')
THEN
EXECUTE 'SET ROLE ' || quote_ident(createur) ;
END IF ;
IF TG_OP = 'UPDATE'
THEN
-- la validité de OLD.lecteur n'ayant
-- pas été contrôlée par le trigger BEFORE,
-- on le fait maintenant
IF OLD.lecteur = 'public'
THEN
a_lecteur := 'public' ;
-- récupération des modifications manuelles des
-- droits de OLD.lecteur/public, grâce à la fonction
-- asgard_synthese_public
SELECT array_agg(commande) INTO l_commande
FROM z_asgard.asgard_synthese_public(
quote_ident(NEW.nom_schema)::regnamespace
) ;
ELSE
SELECT rolname INTO a_lecteur
FROM pg_catalog.pg_roles
WHERE pg_roles.oid = OLD.oid_lecteur ;
IF FOUND
THEN
-- récupération des modifications manuelles des
-- droits de OLD.lecteur, grâce à la fonction
-- asgard_synthese_role
SELECT array_agg(commande) INTO l_commande
FROM z_asgard.asgard_synthese_role(
quote_ident(NEW.nom_schema)::regnamespace,
quote_ident(a_lecteur)::regrole
) ;
END IF ;
END IF ;
END IF ;

IF l_commande IS NOT NULL
-- transfert sur NEW.lecteur des droits de
-- OLD.lecteur, le cas échéant
THEN
IF NEW.lecteur IS NOT NULL
THEN
RAISE NOTICE 'suppression et transfert vers le nouveau lecteur des privilèges de l''ancien lecteur du schéma % :', NEW.nom_schema ;
ELSE
RAISE NOTICE 'suppression des privilèges de l''ancien lecteur du schéma % :', NEW.nom_schema ;
END IF ;
FOREACH c IN ARRAY l_commande
LOOP
IF NEW.lecteur IS NOT NULL
THEN
EXECUTE format(c, NEW.lecteur) ;
RAISE NOTICE '> %', format(c, NEW.lecteur) ;
END IF ;
IF c ~ '^GRANT'
THEN
SELECT z_asgard.asgard_grant_to_revoke(c) INTO c_reverse ;
EXECUTE format(c_reverse, a_lecteur) ;
RAISE NOTICE '> %', format(c_reverse, a_lecteur) ;
END IF ;
END LOOP ;
-- sinon, application des privilèges standards du lecteur
ELSIF NEW.lecteur IS NOT NULL
THEN
RAISE NOTICE 'application des privilèges standards pour le rôle lecteur du schéma % :', NEW.nom_schema ;
EXECUTE 'GRANT USAGE ON SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
RAISE NOTICE '> %', 'GRANT USAGE ON SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
EXECUTE 'GRANT SELECT ON ALL TABLES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
RAISE NOTICE '> %', 'GRANT SELECT ON ALL TABLES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
EXECUTE 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
RAISE NOTICE '> %', 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
END IF ;
EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
END IF ;
RETURN NULL ;

EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS e_mssg = MESSAGE_TEXT,
e_hint = PG_EXCEPTION_HINT,
e_detl = PG_EXCEPTION_DETAIL ;
RAISE EXCEPTION 'TA0 > %', e_mssg
USING DETAIL = e_detl,
HINT = e_hint ;
END
$BODY$ ;

ALTER FUNCTION z_asgard_admin.asgard_on_modify_gestion_schema_after()
OWNER TO g_admin ;

COMMENT ON FUNCTION z_asgard_admin.asgard_on_modify_gestion_schema_after() IS 'ASGARD. Fonction appelée par le trigger qui répercute physiquement les modifications de la table de gestion.' ;


-- Trigger: asgard_on_modify_gestion_schema_after

CREATE TRIGGER asgard_on_modify_gestion_schema_after
AFTER INSERT OR UPDATE
ON z_asgard_admin.gestion_schema
FOR EACH ROW
EXECUTE PROCEDURE z_asgard_admin.asgard_on_modify_gestion_schema_after();

COMMENT ON TRIGGER asgard_on_modify_gestion_schema_after ON z_asgard_admin.gestion_schema IS 'ASGARD. Trigger qui répercute physiquement les modifications de la table de gestion.' ;
(2-2/2)