Projet

Général

Profil

Anomalie #292 » asgard_5_triggers_v13.sql

Leslie Lemaire, 03/08/2020 16:05

 
1

    
2
-- v0 2020.05.07 AF
3
-- ...
4
-- v7 2020.05.29 LL
5
-- v8 2020.05.29 LL
6
-- v9 2020.05.30 LL
7
-- v10 2020.06.02 LL
8
-- v11 2020.06.11 LL (asgard 0.7.0)
9
-- v12 2020.06.25 LL (asgard 0.7.1)
10
-- v13 2020.08.03 LL (asgard 1.0.0)
11
/*
12
(ligne 1008) correction d'une coquille qui faisait échouer les créations
13
via UPDATE creation = True sur les schémas pré-référencés avec des noms
14
non standard (anomalie #292)
15
 */
16

    
17

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

    
21

    
22
---------------------------------------------
23
------ 5 - TRIGGERS SUR GESTION_SCHEMA ------
24
---------------------------------------------
25
/* 5.1 - TRIGGER BEFORE
26
   5.2 - TRIGGER AFTER */
27
   
28

    
29
------ 5.1 - TRIGGER BEFORE ------
30

    
31
-- FUNCTION: z_asgard_admin.asgard_on_modify_gestion_schema_before()
32

    
33
CREATE OR REPLACE FUNCTION z_asgard_admin.asgard_on_modify_gestion_schema_before() RETURNS trigger
34
    LANGUAGE plpgsql
35
    AS $BODY$
36
/* OBJET : Fonction exécutée par le trigger asgard_on_modify_gestion_schema_before,
37
           qui valide les informations saisies dans la table de gestion.
38
CIBLES : z_asgard_admin.gestion_schema.
39
PORTEE : FOR EACH ROW.
40
DECLENCHEMENT : BEFORE INSERT, UPDATE, DELETE.*/
41
DECLARE
42
    n_role text ;
43
BEGIN
44
    
45
    ------ SAISIE PAR UN UTILISATEUR NON HABILITE ------
46
    IF TG_OP = 'INSERT' AND NOT has_database_privilege(current_database(), 'CREATE')
47
    -- même si creation vaut faux, seul un rôle habilité à créer des
48
    -- schéma peut ajouter des lignes dans la table de gestion
49
    THEN
50
        RAISE EXCEPTION 'TB1. Vous devez être habilité à créer des schémas pour réaliser cette opération.' ;
51
    END IF ;
52
    
53
    ------ APPLICATION DES VALEURS PAR DEFAUT ------
54
    -- au tout début car de nombreux tests sont faits par la
55
    -- suite sur "NOT NEW.creation"
56
    IF TG_OP IN ('INSERT', 'UPDATE')
57
    THEN
58
        NEW.creation := coalesce(NEW.creation, False) ;
59
        NEW.nomenclature := coalesce(NEW.nomenclature, False) ;
60
    END IF ;
61
    
62
    ------ EFFACEMENT D'UN ENREGISTREMENT ------
63
    IF TG_OP = 'DELETE'
64
    THEN   
65
        -- on n'autorise pas l'effacement si creation vaut True
66
        -- avec une exception pour les commandes envoyées par la fonction
67
        -- de maintenance asgard_sortie_gestion_schema
68
        IF OLD.creation AND (OLD.ctrl[1] IS NULL OR NOT OLD.ctrl[1] = 'EXIT')
69
        THEN
70
            RAISE EXCEPTION 'TB2. Opération interdite (schéma %). L''effacement n''est autorisé que si creation vaut False.', OLD.nom_schema
71
                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.' ;
72
        END IF;
73
        
74
        -- on n'autorise pas l'effacement pour les schémas de la nomenclature
75
        IF OLD.nomenclature
76
        THEN
77
            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
78
                USING HINT = 'Si vous tenez à supprimer définitivement cet enregistrement, basculez préalablement nomenclature sur False.' ;
79
        END IF ;
80
    END IF;
81

    
82
    ------ DE-CREATION D'UN SCHEMA ------
83
    IF TG_OP = 'UPDATE'
84
    THEN
85
        -- si bloc valait déjà d (schéma "mis à la corbeille")
86
        -- on exécute une commande de suppression du schéma. Toute autre modification sur
87
        -- la ligne est ignorée.
88
        IF OLD.bloc = 'd' AND OLD.creation AND NOT NEW.creation AND NEW.ctrl[2] IS NULL
89
                AND OLD.nom_schema IN (SELECT nspname FROM pg_catalog.pg_namespace)
90
        THEN
91
            EXECUTE 'DROP SCHEMA ' || quote_ident(OLD.nom_schema) || ' CASCADE' ;
92
            RAISE NOTICE '... Le schéma % a été supprimé.', OLD.nom_schema ;
93
            RETURN NULL ;
94
        -- sinon, on n'autorise creation à passer de true à false que si le schéma
95
        -- n'existe plus (permet notamment à l'event trigger qui gère les
96
        -- suppressions de mettre creation à false)
97
        ELSIF OLD.creation and NOT NEW.creation
98
                AND NEW.nom_schema IN (SELECT nspname FROM pg_catalog.pg_namespace)
99
        THEN
100
            RAISE EXCEPTION 'TB4. Opération interdite (schéma %). Le champ creation ne peut passer de Vrai à Faux si le schéma existe.', NEW.nom_schema
101
                USING HINT =  'Si vous supprimez physiquement le schéma avec la commande DROP SCHEMA, creation basculera sur False automatiquement.' ;
102
        END IF ;
103
    END IF ;
104
    
105
    IF TG_OP <> 'DELETE'
106
    THEN
107
        ------ PROHIBITION DE LA SAISIE MANUELLE DES OID ------
108
        -- vérifié grâce au champ ctrl
109
        IF NEW.ctrl[2] IS NULL
110
            OR NOT array_length(NEW.ctrl, 1) >= 2
111
            OR NEW.ctrl[1] IS NULL
112
            OR NOT NEW.ctrl[1] IN ('CREATE', 'RENAME', 'OWNER', 'DROP', 'SELF', 'EXIT')
113
            OR NOT NEW.ctrl[2] = 'x7-A;#rzo'
114
            -- ctrl NULL ou invalide
115
        THEN
116

    
117
            IF NEW.ctrl[1] = 'EXIT'
118
            THEN
119
                RAISE EXCEPTION 'TB17. Opération interdite (schéma %).', coalesce(NEW.nom_schema, '?')
120
                    USING HINT = 'Pour déréférencer un schéma, veuillez utiliser la fonction z_asgard_admin.asgard_sortie_gestion_schema.' ;
121
            END IF ;
122
            
123
            -- réinitialisation du champ ctrl, qui peut contenir des informations
124
            -- issues de commandes antérieures (dans ctrl[1])
125
            NEW.ctrl := ARRAY['MANUEL', NULL]::text[] ;
126
            
127
            IF TG_OP = 'INSERT' AND (
128
                    NEW.oid_producteur IS NOT NULL
129
                    OR NEW.oid_lecteur IS NOT NULL
130
                    OR NEW.oid_editeur IS NOT NULL
131
                    OR NEW.oid_schema IS NOT NULL
132
                    )
133
            -- cas d'un INSERT manuel pour lequel des OID ont été saisis
134
            -- on les remet à NULL
135
            THEN
136
                NEW.oid_producteur = NULL ;
137
                NEW.oid_editeur = NULL ;
138
                NEW.oid_lecteur = NULL ;
139
                NEW.oid_schema = NULL ;
140
            ELSIF TG_OP = 'UPDATE'
141
            THEN
142
                IF NOT coalesce(NEW.oid_producteur, -1) = coalesce(OLD.oid_producteur, -1)
143
                        OR NOT coalesce(NEW.oid_editeur, -1) = coalesce(OLD.oid_editeur, -1)
144
                        OR NOT coalesce(NEW.oid_lecteur, -1) = coalesce(OLD.oid_lecteur, -1)
145
                        OR NOT coalesce(NEW.oid_schema, -1) = coalesce(OLD.oid_schema, -1)
146
                -- cas d'un UPDATE avec modification des OID
147
                -- on les remet à OLD
148
                THEN
149
                    NEW.oid_producteur = OLD.oid_producteur ;
150
                    NEW.oid_editeur = OLD.oid_editeur ;
151
                    NEW.oid_lecteur = OLD.oid_lecteur ;
152
                    NEW.oid_schema = OLD.oid_schema ;
153
                END IF ;
154
            END IF ;                
155
        ELSE
156
            -- suppression du mot de passe de contrôle.
157
            -- ctrl[1] est par contre conservé - il sera utilisé
158
            -- par le trigger AFTER pour connaître l'opération
159
            -- à l'origine de son déclenchement.
160
            NEW.ctrl[2] := NULL ;
161
        END IF ;
162
        
163
        ------ REQUETES AUTO A IGNORER ------
164
        -- les remontées du trigger AFTER (SELF)
165
        -- sont exclues, car les contraintes ont déjà
166
        -- été validées (et pose problèmes avec les
167
        -- contrôles d'OID sur les UPDATE, car ceux-ci
168
        -- ne seront pas nécessairement déjà remplis) ;
169
        -- les requêtes EXIT de même, car c'est un
170
        -- pré-requis à la suppression qui ne fait
171
        -- que modifier le champ ctrl
172
        IF NEW.ctrl[1] IN ('SELF', 'EXIT')
173
        THEN
174
            -- aucune action
175
            RETURN NEW ;
176
        END IF ;
177
        
178
        ------ VERROUILLAGE DES CHAMPS LIES A LA NOMENCLATURE ------
179
        -- modifiables uniquement par l'ADL
180
        IF TG_OP = 'UPDATE'
181
        THEN
182
            IF (OLD.nomenclature OR NEW.nomenclature) AND NOT pg_has_role('g_admin', 'MEMBER') AND (
183
                    NOT coalesce(OLD.nomenclature, False) = coalesce(NEW.nomenclature, False)
184
                    OR NOT coalesce(OLD.niv1, '') = coalesce(NEW.niv1, '')
185
                    OR NOT coalesce(OLD.niv1_abr, '') = coalesce(NEW.niv1_abr, '')
186
                    OR NOT coalesce(OLD.niv2, '') = coalesce(NEW.niv2, '')
187
                    OR NOT coalesce(OLD.niv2_abr, '') = coalesce(NEW.niv2_abr, '')
188
                    OR NOT coalesce(OLD.nom_schema, '') = coalesce(NEW.nom_schema, '')
189
                    OR NOT coalesce(OLD.bloc, '') = coalesce(NEW.bloc, '')
190
                    )
191
            THEN
192
                RAISE EXCEPTION 'TB18. Opération interdite (schéma %).', NEW.nom_schema
193
                    USING HINT = '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.' ;
194
            END IF ;
195
        ELSIF TG_OP = 'INSERT'
196
        THEN
197
            IF NEW.nomenclature AND NOT pg_has_role('g_admin', 'MEMBER')
198
            THEN
199
                RAISE EXCEPTION 'TB19. Opération interdite (schéma %).', NEW.nom_schema
200
                    USING HINT = 'Seuls les membres de g_admin sont autorisés à ajouter des schémas à la nomenclature (nomenclature = True).' ;
201
            END IF ;
202
        END IF ;
203
    
204
        ------ NETTOYAGE DES CHAÎNES VIDES ------
205
        -- si l'utilisateur a entré des chaînes vides on met des NULL
206
        NEW.editeur := nullif(NEW.editeur, '') ;
207
        NEW.lecteur := nullif(NEW.lecteur, '') ;
208
        NEW.bloc := nullif(NEW.bloc, '') ;
209
        NEW.niv1 := nullif(NEW.niv1, '') ;
210
        NEW.niv1_abr := nullif(NEW.niv1_abr, '') ;
211
        NEW.niv2 := nullif(NEW.niv2, '') ;
212
        NEW.niv2_abr := nullif(NEW.niv2_abr, '') ;
213
        NEW.nom_schema := nullif(NEW.nom_schema, '') ;
214
        -- si producteur est vide on met par défaut g_admin
215
        NEW.producteur := coalesce(nullif(NEW.producteur, ''), 'g_admin') ;
216
        
217
        ------ NETTOYAGE DES CHAMPS OID ------
218
        -- pour les rôles de lecteur et éditeur,
219
        -- si le champ de nom est vidé par l'utilisateur,
220
        -- on vide en conséquence l'OID
221
        IF NEW.editeur IS NULL
222
        THEN
223
            NEW.oid_editeur := NULL ;
224
        END IF ;
225
        IF NEW.lecteur IS NULL
226
        THEN
227
            NEW.oid_lecteur := NULL ;
228
        END IF ;
229
        -- si le schéma n'est pas créé, on s'assure que les champs
230
        -- d'OID restent vides
231
        -- à noter que l'event trigger sur DROP SCHEMA vide
232
        -- déjà le champ oid_schema
233
        IF NOT NEW.creation
234
        THEN
235
            NEW.oid_schema := NULL ;
236
            NEW.oid_lecteur := NULL ;
237
            NEW.oid_editeur := NULL ;
238
            NEW.oid_producteur := NULL ;
239
        END IF ;
240
        
241
        ------ VALIDITE DES NOMS DE ROLES ------
242
        -- dans le cas d'un schéma pré-existant, on s'assure que les rôles qui
243
        -- ne changent pas sont toujours valides (qu'ils existent et que le nom
244
        -- n'a pas été modifié entre temps)
245
        -- si tel est le cas, on les met à jour et on le note dans
246
        -- ctrl, pour que le trigger AFTER sache qu'il ne s'agit
247
        -- pas réellement de nouveaux rôles sur lesquels les droits
248
        -- devraient être réappliqués
249
        IF TG_OP = 'UPDATE' AND NEW.creation
250
        THEN
251
            -- producteur
252
            IF OLD.creation AND OLD.producteur = NEW.producteur
253
            THEN
254
                SELECT rolname INTO n_role
255
                    FROM pg_catalog.pg_roles
256
                    WHERE pg_roles.oid = NEW.oid_producteur ;
257
                IF NOT FOUND
258
                -- le rôle producteur n'existe pas
259
                THEN
260
                    -- cas invraisemblable, car un rôle ne peut pas être
261
                    -- supprimé alors qu'il est propriétaire d'un schéma, et la
262
                    -- commande ALTER SCHEMA OWNER TO aurait été interceptée
263
                    -- mais, s'il advient, on repart du propriétaire
264
                    -- renseigné dans pg_namespace
265
                    SELECT replace(nspowner::regrole::text, '"', ''), nspowner
266
                        INTO NEW.producteur, NEW.oid_producteur
267
                        FROM pg_catalog.pg_namespace
268
                        WHERE pg_namespace.oid = NEW.oid_schema ;
269
                    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 ;
270
                    NEW.ctrl := array_append(NEW.ctrl, 'CLEAN producteur') ;
271
                ELSIF NOT n_role = NEW.producteur
272
                -- libellé obsolète du producteur
273
                THEN
274
                    NEW.producteur := n_role ;
275
                    RAISE NOTICE '[table de gestion] Schéma %. Mise à jour du libellé du rôle producteur, renommé entre temps.', NEW.nom_schema
276
                        USING DETAIL = 'Ancien nom "' || OLD.producteur || '", nouveau nom "' || NEW.producteur || '".' ;
277
                    NEW.ctrl := array_append(NEW.ctrl, 'CLEAN producteur') ;
278
                END IF ; 
279
            END IF ;
280
            -- éditeur
281
            IF OLD.creation AND OLD.editeur = NEW.editeur
282
                    AND NOT NEW.editeur = 'public'
283
            THEN
284
                SELECT rolname INTO n_role
285
                    FROM pg_catalog.pg_roles
286
                    WHERE pg_roles.oid = NEW.oid_editeur ;
287
                IF NOT FOUND
288
                -- le rôle éditeur n'existe pas
289
                THEN
290
                    NEW.editeur := NULL ;
291
                    NEW.oid_editeur := NULL ;
292
                    RAISE NOTICE '[table de gestion] Schéma %. Le rôle éditeur n''existant plus, il est déréférencé.', NEW.nom_schema
293
                        USING DETAIL = 'Ancien nom "' || OLD.editeur || '".' ;
294
                    NEW.ctrl := array_append(NEW.ctrl, 'CLEAN editeur') ;
295
                ELSIF NOT n_role = NEW.editeur
296
                -- libellé obsolète de l'éditeur
297
                THEN
298
                    NEW.editeur := n_role ;
299
                    RAISE NOTICE '[table de gestion] Schéma %. Mise à jour du libellé du rôle éditeur, renommé entre temps.', NEW.nom_schema
300
                        USING DETAIL = 'Ancien nom "' || OLD.editeur || '", nouveau nom "' || NEW.editeur || '".' ;
301
                    NEW.ctrl := array_append(NEW.ctrl, 'CLEAN editeur') ;
302
                END IF ; 
303
            END IF ;
304
            -- lecteur
305
            IF OLD.creation AND OLD.lecteur = NEW.lecteur
306
                    AND NOT NEW.lecteur = 'public'
307
            THEN
308
                SELECT rolname INTO n_role
309
                    FROM pg_catalog.pg_roles
310
                    WHERE pg_roles.oid = NEW.oid_lecteur ;
311
                IF NOT FOUND
312
                -- le rôle lecteur n'existe pas
313
                THEN
314
                    NEW.lecteur := NULL ;
315
                    NEW.oid_lecteur := NULL ;
316
                    RAISE NOTICE '[table de gestion] Schéma %. Le rôle lecteur n''existant plus, il est déréférencé.', NEW.nom_schema
317
                        USING DETAIL = 'Ancien nom "' || OLD.lecteur || '".' ;
318
                    NEW.ctrl := array_append(NEW.ctrl, 'CLEAN lecteur') ;
319
                ELSIF NOT n_role = NEW.lecteur
320
                -- libellé obsolète du lecteur
321
                THEN
322
                    NEW.lecteur := n_role ;
323
                    RAISE NOTICE '[table de gestion] Schéma %. Mise à jour du libellé du rôle lecteur, renommé entre temps.', NEW.nom_schema
324
                        USING DETAIL = 'Ancien nom "' || OLD.lecteur || '", nouveau nom "' || NEW.lecteur || '".' ;
325
                    NEW.ctrl := array_append(NEW.ctrl, 'CLEAN lecteur') ;
326
                END IF ; 
327
            END IF ;    
328
        END IF ;
329

    
330
        ------ NON RESPECT DES CONTRAINTES ------
331
        -- non nullité de nom_schema
332
        IF NEW.nom_schema IS NULL
333
        THEN
334
            RAISE EXCEPTION 'TB8. Saisie incorrecte. Le nom du schéma doit être renseigné (champ nom_schema).' ;
335
        END IF ;
336
        
337
        -- unicité de nom_schema
338
        IF TG_OP = 'INSERT' AND NEW.nom_schema IN (SELECT gestion_schema_etr.nom_schema FROM z_asgard.gestion_schema_etr)
339
        THEN
340
            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 ;
341
        ELSIF TG_OP = 'UPDATE'
342
        THEN
343
            -- cas d'un changement de nom
344
            IF NOT NEW.nom_schema = OLD.nom_schema
345
                   AND NEW.nom_schema IN (SELECT gestion_schema_etr.nom_schema FROM z_asgard.gestion_schema_etr)
346
            THEN 
347
                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 ;
348
            END IF ;
349
        END IF ;
350
        
351
        -- unicité de oid_schema
352
        IF TG_OP = 'INSERT' AND NEW.oid_schema IN (SELECT gestion_schema_etr.oid_schema FROM z_asgard.gestion_schema_etr
353
                                                       WHERE gestion_schema_etr.oid_schema IS NOT NULL)
354
        THEN
355
            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 ;
356
        ELSIF TG_OP = 'UPDATE'
357
        THEN
358
            -- cas (très hypothétique) d'une modification d'OID
359
            IF NOT coalesce(NEW.oid_schema, -1) = coalesce(OLD.oid_schema, -1)
360
                    AND NEW.oid_schema IN (SELECT gestion_schema_etr.oid_schema FROM z_asgard.gestion_schema_etr
361
                                                       WHERE gestion_schema_etr.oid_schema IS NOT NULL)
362
            THEN
363
                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 ;
364
            END IF ;
365
        END IF ;
366
        
367
        -- non répétition des rôles
368
        IF NOT ((NEW.oid_lecteur IS NULL OR NOT NEW.oid_lecteur = NEW.oid_producteur)
369
                AND (NEW.oid_editeur IS NULL OR NOT NEW.oid_editeur = NEW.oid_producteur)
370
                AND (NEW.oid_lecteur IS NULL OR NEW.oid_editeur IS NULL OR NOT NEW.oid_lecteur = NEW.oid_editeur))
371
        THEN
372
            RAISE EXCEPTION 'TB13. Saisie incorrecte (schéma %). Les rôles producteur, lecteur et éditeur doivent être distincts.', NEW.nom_schema ;
373
        END IF ;
374
    END IF ;
375
    
376
    ------ COHERENCE BLOC/NOM DU SCHEMA ------
377
    IF TG_OP IN ('INSERT', 'UPDATE')
378
    THEN
379
        IF NEW.bloc IS NULL AND NEW.nom_schema ~ '^[a-z]_'
380
        -- si bloc est NULL, mais que le nom du schéma
381
        -- comporte un préfixe, on met ce préfixe dans bloc
382
        THEN
383
            NEW.bloc := substring(NEW.nom_schema, '^([a-z])_') ;
384
            RAISE NOTICE '[table de gestion] Mise à jour du bloc pour le schéma %.', NEW.nom_schema || ' (' || NEW.bloc || ')' ;
385
        ELSIF NEW.bloc IS NULL
386
        -- si bloc est NULL, et que (sous-entendu) le nom du schéma ne
387
        -- respecte pas la nomenclature, on avertit l'utilisateur
388
        THEN            
389
            RAISE NOTICE '[table de gestion] Le nom du schéma % ne respecte pas la nomenclature.', NEW.nom_schema
390
                USING HINT = 'Si vous saisissez un préfixe dans le champ bloc, il sera automatiquement ajouté au nom du schéma.' ;
391
        ELSIF NOT NEW.nom_schema ~ ('^'|| NEW.bloc || '_') AND NOT NEW.bloc = 'd'
392
        -- le bloc est renseigné mais le nom du schéma ne correspond pas
393
        -- (et il ne s'agit pas d'un schéma mis à la corbeille) :
394
        -- si le nom est de la forme 'a_...', alors :
395
        -- - dans le cas d'un UPDATE avec modification du nom
396
        -- du schéma et pas du bloc, on se fie au nom du schéma
397
        -- et on change le bloc ;
398
        -- - si bloc n'est pas une lettre, on renvoie une erreur ;
399
        -- - dans les autres cas, on se fie au bloc et change le
400
        -- préfixe.
401
        -- Si le nom ne comporte pas de préfixe :
402
        -- - si le bloc est une lettre, on l'ajoute au début du
403
        -- nom (sans doubler l'underscore, si le nom commençait par
404
        -- un underscore) ;
405
        -- - sinon on renvoie une erreur.
406
        THEN
407
            IF NEW.nom_schema ~ '^([a-z])?_'
408
            THEN
409
                IF TG_OP = 'UPDATE'
410
                THEN
411
                    IF NOT NEW.nom_schema = OLD.nom_schema AND NEW.bloc = OLD.bloc
412
                    THEN
413
                        NEW.bloc := substring(NEW.nom_schema, '^([a-z])_') ;
414
                        RAISE NOTICE '[table de gestion] Mise à jour du bloc pour le schéma %.', NEW.nom_schema || ' (' || NEW.bloc || ')' ;
415
                    ELSIF NOT NEW.bloc ~ '^[a-z]$'
416
                    THEN
417
                        RAISE EXCEPTION 'TB14. Saisie invalide (schéma %). Le bloc doit être une lettre minuscule ou rien.', NEW.nom_schema ;
418
                    ELSE
419
                        NEW.nom_schema := regexp_replace(NEW.nom_schema, '^([a-z])?_', NEW.bloc || '_') ;
420
                        RAISE NOTICE '[table de gestion] Mise à jour du préfixe du schéma %.', NEW.nom_schema || ' d''après son bloc (' || NEW.bloc || ')' ;
421
                    END IF ;
422
                ELSIF NOT NEW.bloc ~ '^[a-z]$'
423
                THEN
424
                    RAISE EXCEPTION 'TB15. Saisie invalide (schéma %). Le bloc doit être une lettre minuscule ou rien.', NEW.nom_schema ;
425
                ELSE
426
                    NEW.nom_schema := regexp_replace(NEW.nom_schema, '^([a-z])?_', NEW.bloc || '_') ;
427
                    RAISE NOTICE '[table de gestion] Mise à jour du préfixe du schéma %.', NEW.nom_schema || ' d''après son bloc (' || NEW.bloc || ')' ;
428
                END IF ;
429
            ELSIF NOT NEW.bloc ~ '^[a-z]$'
430
            THEN
431
                RAISE EXCEPTION 'TB16. Saisie invalide (schéma %). Le bloc doit être une lettre minuscule ou rien.', NEW.nom_schema ;
432
            ELSE
433
                NEW.nom_schema := NEW.bloc || '_' || NEW.nom_schema ;
434
                RAISE NOTICE '[table de gestion] Mise à jour du préfixe du schéma %.', NEW.nom_schema || ' d''après son bloc (' || NEW.bloc || ')' ;
435
            END IF ;
436
            -- le trigger AFTER se chargera de renommer physiquement le
437
            -- schéma d'autant que de besoin
438
        END IF ;
439
    END IF ;
440
    
441
    ------ MISE À LA CORBEILLE ------
442
    -- notification de l'utilisateur
443
    IF TG_OP = 'UPDATE'
444
    THEN
445
        -- schéma existant dont bloc bascule sur 'd'
446
        -- ou schéma créé par bascule de creation sur True dans bloc vaut 'd'
447
        IF NEW.creation AND NEW.bloc = 'd' AND (NOT OLD.bloc = 'd' OR OLD.bloc IS NULL)
448
                OR NEW.creation AND NOT OLD.creation AND NEW.bloc = 'd'
449
        THEN
450
            RAISE NOTICE '[table de gestion] Le schéma % a été mis à la corbeille (bloc = ''d'').', NEW.nom_schema
451
                USING HINT = 'Si vous basculez creation sur False, le schéma et son contenu seront automatiquement supprimés.' ;
452
        -- restauration
453
        ELSIF NEW.creation AND OLD.bloc = 'd' AND (NOT NEW.bloc = 'd' OR NEW.bloc IS NULL)
454
        THEN
455
            RAISE NOTICE '[table de gestion] Le schéma % a été retiré de la corbeille (bloc ne vaut plus ''d'').', NEW.nom_schema ;
456
        END IF ;
457
    ELSIF TG_OP = 'INSERT'
458
    THEN
459
        -- nouveau schéma dont bloc vaut 'd'
460
        IF NEW.creation AND NEW.bloc = 'd'
461
        THEN
462
            RAISE NOTICE '[table de gestion] Le schéma % a été mis à la corbeille (bloc = ''d'').', NEW.nom_schema
463
                USING HINT = 'Si vous basculez creation sur False, le schéma et son contenu seront automatiquement supprimés.' ;  
464
        END IF ;
465
    END IF ;
466
    
467
    ------ RETURN ------
468
	IF TG_OP IN ('UPDATE', 'INSERT')
469
    THEN
470
        RETURN NEW ;
471
    ELSIF TG_OP = 'DELETE'
472
    THEN
473
        RETURN OLD ;
474
    END IF ;
475
    
476
END
477
$BODY$ ;
478

    
479
ALTER FUNCTION z_asgard_admin.asgard_on_modify_gestion_schema_before()
480
    OWNER TO g_admin ;
481

    
482
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.';
483

    
484

    
485
-- Trigger: asgard_on_modify_gestion_schema_before
486

    
487
CREATE TRIGGER asgard_on_modify_gestion_schema_before
488
    BEFORE INSERT OR DELETE OR UPDATE
489
    ON z_asgard_admin.gestion_schema
490
    FOR EACH ROW
491
    EXECUTE PROCEDURE z_asgard_admin.asgard_on_modify_gestion_schema_before() ;
492
    
493
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.';
494
    
495

    
496

    
497
------ 5.2 - TRIGGER AFTER ------
498

    
499
-- FUNCTION: z_asgard_admin.asgard_on_modify_gestion_schema_after()
500

    
501
CREATE OR REPLACE FUNCTION z_asgard_admin.asgard_on_modify_gestion_schema_after() RETURNS trigger
502
    LANGUAGE plpgsql
503
    AS $BODY$
504
/* OBJET : Fonction exécutée par le trigger asgard_on_modify_gestion_schema_after,
505
           qui répercute physiquement les modifications de la table de gestion.
506
CIBLES : z_asgard_admin.gestion_schema.
507
PORTEE : FOR EACH ROW.
508
DECLENCHEMENT : AFTER INSERT OR UPDATE.*/
509
DECLARE
510
    utilisateur text ;
511
    createur text ;
512
    administrateur text ;
513
    e_mssg text ;
514
    e_hint text ;
515
    e_detl text ;
516
    b_superuser boolean ;
517
    b_test boolean ;
518
    l_commande text[] ;
519
    c text ;
520
    c_reverse text ;
521
    a_producteur text ;
522
    a_editeur text ;
523
    a_lecteur text ;
524
    n int ;
525
BEGIN
526

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

    
549
    ------ MANIPULATIONS PREALABLES ------
550
    utilisateur := current_user ;
551
    
552
    -- si besoin pour les futures opérations sur les rôles,
553
    -- récupération du nom d'un rôle dont current_user est membre
554
    -- et qui a l'attribut CREATEROLE. Autant que possible, la
555
    -- requête renvoie current_user lui-même. On exclut d'office les
556
    -- rôles NOINHERIT qui ne pourront pas avoir simultanément les
557
    -- droits du propriétaire de NEW et OLD.producteur
558
    SELECT rolname INTO createur FROM pg_roles
559
        WHERE pg_has_role(rolname, 'MEMBER') AND rolcreaterole AND rolinherit
560
        ORDER BY rolname = current_user DESC ;
561
    
562
    IF TG_OP = 'UPDATE'
563
    THEN
564
        -- la validité de OLD.producteur n'ayant
565
        -- pas été contrôlée par le trigger BEFORE,
566
        -- on le fait maintenant
567
        SELECT rolname INTO a_producteur
568
            FROM pg_catalog.pg_roles
569
            WHERE pg_roles.oid = OLD.oid_producteur ;
570
        -- pour la suite, on emploira toujours
571
        -- a_producteur à la place de OLD.producteur
572
        -- pour les opérations sur les droits.
573
        -- Il est réputé non NULL pour un schéma
574
        -- pré-existant (OLD.creation vaut True),
575
        -- dans la mesure où un rôle ne peut être
576
        -- supprimé s'il est propriétaire d'un
577
        -- schéma et où tous les changements de
578
        -- propriétaires sont remontés par event
579
        -- triggers (+ contrôles pour assurer la
580
        -- non-modification manuelle des OID).
581
        IF NOT FOUND AND OLD.creation AND NOT 'CLEAN producteur' = ANY(NEW.ctrl)
582
        THEN
583
            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 ;
584
            SELECT replace(nspowner::regrole::text, '"', '') INTO a_producteur
585
                FROM pg_catalog.pg_namespace
586
                WHERE pg_namespace.oid = NEW.oid_schema ;
587
            IF NOT FOUND
588
            THEN
589
                RAISE EXCEPTION 'TA1. Anomalie critique (schéma %). Le propriétaire du schéma est introuvable.', OLD.nom_schema ;
590
            END IF ;
591
        END IF ;
592
    END IF ;
593

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

    
610
    ------ PREPARATION DU PRODUCTEUR ------
611
    -- on ne s'intéresse pas aux cas :
612
    -- - d'un schéma qui n'a pas/plus vocation à exister
613
    --   (creation vaut False) ;
614
    -- - d'un schéma pré-existant dont les rôles ne changent pas
615
    --   ou dont le libellé a juste été nettoyé par le trigger
616
    --   BEFORE.
617
    -- ils sont donc exclus au préalable
618
    -- si le moindre rôle a changé, il faudra être membre du
619
    -- groupe propriétaire/producteur pour pouvoir modifier
620
    -- les privilèges en conséquence
621
    b_test := False ;
622
    IF NOT NEW.creation
623
    THEN
624
        b_test := True ;
625
    ELSIF TG_OP = 'UPDATE'
626
    THEN
627
        IF OLD.creation
628
                AND (NEW.producteur = OLD.producteur  OR 'CLEAN producteur' = ANY(NEW.ctrl))
629
                AND (coalesce(NEW.editeur, '') = coalesce(OLD.editeur, '') OR 'CLEAN editeur' = ANY(NEW.ctrl))
630
                AND (coalesce(NEW.lecteur, '') = coalesce(OLD.lecteur, '') OR 'CLEAN lecteur' = ANY(NEW.ctrl))
631
        THEN
632
            b_test := True ;
633
        END IF ;
634
    END IF ;
635
    
636
    IF NOT b_test
637
    THEN
638
        IF NOT NEW.producteur IN (SELECT rolname FROM pg_catalog.pg_roles)
639
        -- si le producteur désigné n'existe pas, on le crée
640
        -- ou renvoie une erreur si les privilèges de l'utilisateur
641
        -- sont insuffisants
642
        THEN
643
            IF createur IS NULL
644
            THEN
645
                RAISE EXCEPTION 'TA2. Opération interdite. Vous n''êtes pas habilité à créer le rôle %.', NEW.producteur
646
                    USING HINT = 'Être membre d''un rôle disposant des attributs CREATEROLE et INHERIT est nécessaire pour créer de nouveaux producteurs.' ;
647
            END IF ;
648
            EXECUTE 'SET ROLE ' || quote_ident(createur) ;
649
            EXECUTE 'CREATE ROLE ' || quote_ident(NEW.producteur) ;
650
            RAISE NOTICE '... Le rôle de groupe % a été créé.', NEW.producteur ;
651
            EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;                
652
        ELSE
653
        -- si le rôle producteur existe, on vérifie qu'il n'a pas l'option LOGIN
654
        -- les superusers avec LOGIN (comme postgres) sont tolérés
655
        -- paradoxe ou non, dans l'état actuel des choses, cette erreur se
656
        -- déclenche aussi lorsque la modification ne porte que sur les rôles
657
        -- lecteur/éditeur
658
            SELECT rolsuper INTO b_superuser
659
                FROM pg_roles WHERE rolname = NEW.producteur AND rolcanlogin ;
660
            IF NOT b_superuser
661
            THEN
662
                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 ;
663
            END IF ;
664
        END IF ;
665
        b_superuser := coalesce(b_superuser, False) ;
666
        
667
        -- mise à jour du champ d'OID du producteur
668
        IF NEW.ctrl[1] IS NULL OR NOT NEW.ctrl[1] IN ('OWNER', 'CREATE')
669
        -- pas dans le cas d'une remontée de commande directe
670
        -- où l'OID du producteur sera déjà renseigné
671
        -- et uniquement s'il a réellement été modifié (ce
672
        -- qui n'est pas le cas si les changements ne portent
673
        -- que sur les rôles lecteur/éditeur)
674
        THEN
675
            UPDATE z_asgard.gestion_schema_etr
676
                SET oid_producteur = quote_ident(NEW.producteur)::regrole::oid,
677
                    ctrl = ARRAY['SELF', 'x7-A;#rzo']
678
                WHERE nom_schema = NEW.nom_schema AND (
679
                    oid_producteur IS NULL
680
                    OR NOT oid_producteur = quote_ident(NEW.producteur)::regrole::oid
681
                    ) ;
682
        END IF ;
683

    
684
        -- implémentation des permissions manquantes sur NEW.producteur
685
        IF NOT pg_has_role(utilisateur, NEW.producteur, 'USAGE')
686
        THEN
687
            b_test := True ;
688
            IF createur IS NULL OR b_superuser
689
            THEN
690
                RAISE EXCEPTION 'TA4. Opération interdite. Permissions insuffisantes pour le rôle %.', NEW.producteur
691
                    USING HINT = 'Votre rôle doit être membre de ' || NEW.producteur
692
                                     || ' ou disposer de l''attribut CREATEROLE pour réaliser cette opération.' ;
693
            END IF ;
694
        END IF ;
695
        IF TG_OP = 'UPDATE'
696
        THEN
697
            IF OLD.creation AND NOT pg_has_role(utilisateur, a_producteur, 'USAGE')
698
                AND NOT (NEW.producteur = OLD.producteur  OR 'CLEAN producteur' = ANY(NEW.ctrl))
699
                -- les permissions sur OLD.producteur ne sont contrôlées que si le producteur
700
                -- a effectivement été modifié
701
            THEN
702
                b_test := True ;
703
                IF createur IS NULL OR b_superuser
704
                THEN
705
                    RAISE EXCEPTION 'TA5. Opération interdite. Permissions insuffisantes pour le rôle %.', a_producteur
706
                        USING HINT = 'Votre rôle doit être membre de ' || a_producteur
707
                                         || ' ou disposer de l''attribut CREATEROLE pour réaliser cette opération.' ;
708
                END IF ;            
709
            END IF ;
710
        END IF ;       
711
        IF b_test
712
        THEN
713
            EXECUTE 'SET ROLE ' || quote_ident(createur) ;            
714
            -- par commodité, on rend createur membre à la fois de NEW et (si besoin)
715
            -- de OLD.producteur, même si l'utilisateur avait déjà accès à
716
            -- l'un des deux par ailleurs :
717
            IF NOT pg_has_role(createur, NEW.producteur, 'USAGE') AND NOT b_superuser
718
            THEN
719
                EXECUTE 'GRANT ' || quote_ident(NEW.producteur) || ' TO ' || quote_ident(createur) ;
720
                RAISE NOTICE '... Permission accordée à %.', createur || ' sur le rôle ' || NEW.producteur ;
721
            END IF ;
722
            IF TG_OP = 'UPDATE'
723
            THEN
724
                IF NOT pg_has_role(createur, a_producteur, 'USAGE') AND NOT b_superuser
725
                THEN
726
                    EXECUTE 'GRANT ' || quote_ident(a_producteur) || ' TO ' || quote_ident(createur) ;
727
                    RAISE NOTICE '... Permission accordée à %.', createur || ' sur le rôle ' || a_producteur ;
728
                END IF ;
729
            END IF ;
730
            EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
731
        END IF ;
732
           
733
        -- permission de g_admin sur le producteur, s'il y a encore lieu
734
        -- à noter que, dans le cas où le producteur n'a pas été modifié, g_admin
735
        -- devrait déjà avoir une permission sur NEW.producteur, sauf à ce qu'elle
736
        -- lui ait été retirée manuellement entre temps. Les requêtes suivantes
737
        -- génèreraient alors une erreur même dans le cas où la modification ne
738
        -- porte que sur les rôles lecteur/éditeur - ce qui peut-être perçu comme
739
        -- discutable.
740
        IF NOT pg_has_role('g_admin', NEW.producteur, 'USAGE') AND NOT b_superuser
741
        THEN
742
            IF createur IS NOT NULL
743
            THEN
744
                EXECUTE 'SET ROLE ' || quote_ident(createur) ;
745
                EXECUTE 'GRANT ' || quote_ident(NEW.producteur) || ' TO g_admin' ;
746
                RAISE NOTICE '... Permission accordée à g_admin sur le rôle %.', NEW.producteur ;
747
                EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
748
            ELSE
749
                SELECT grantee INTO administrateur
750
                    FROM information_schema.applicable_roles
751
                    WHERE is_grantable = 'YES' AND role_name = NEW.producteur ;
752
                IF FOUND
753
                THEN
754
                    EXECUTE 'SET ROLE ' || quote_ident(administrateur) ;
755
                    EXECUTE 'GRANT ' || quote_ident(NEW.producteur) || ' TO g_admin' ;
756
                    RAISE NOTICE '... Permission accordée à g_admin sur le rôle %.', NEW.producteur ;
757
                    EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
758
                ELSE
759
                    RAISE EXCEPTION 'TA6. Opération interdite. Permissions insuffisantes pour le rôle %.', NEW.producteur
760
                        USING DETAIL = 'GRANT ' || quote_ident(NEW.producteur) || ' TO g_admin',
761
                              HINT = 'Votre rôle doit être membre de ' || NEW.producteur
762
                                         || ' avec admin option ou disposer de l''attribut CREATEROLE pour réaliser cette opération.' ;
763
                END IF ;
764
            END IF ;
765
        END IF ;
766
    END IF ;
767
    
768
    ------ PREPARATION DE L'EDITEUR ------
769
    -- limitée ici à la création du rôle et l'implémentation
770
    -- de son OID. On ne s'intéresse donc pas aux cas :
771
    -- - où il y a pas d'éditeur ;
772
    -- - d'un schéma qui n'a pas/plus vocation à exister ;
773
    -- - d'un schéma pré-existant dont l'éditeur ne change pas
774
    --   ou dont le libellé a seulement été nettoyé par le
775
    --   trigger BEFORE.
776
    -- ils sont donc exclus au préalable
777
    b_test := False ;
778
    IF NOT NEW.creation OR NEW.editeur IS NULL
779
            OR 'CLEAN editeur' = ANY(NEW.ctrl)
780
    THEN
781
        b_test := True ;
782
    ELSIF TG_OP = 'UPDATE'
783
    THEN
784
        IF OLD.creation AND NEW.editeur = OLD.editeur
785
        THEN
786
            b_test := True ;           
787
        END IF ;
788
    END IF ;
789
    
790
    IF NOT b_test
791
    THEN
792
        IF NOT NEW.editeur IN (SELECT rolname FROM pg_catalog.pg_roles)
793
                AND NOT NEW.editeur = 'public'
794
        -- si l'éditeur désigné n'existe pas, on le crée
795
        -- ou renvoie une erreur si les privilèges de l'utilisateur
796
        -- sont insuffisants
797
        THEN
798
            IF createur IS NULL
799
            THEN
800
                RAISE EXCEPTION 'TA7. Opération interdite. Vous n''êtes pas habilité à créer le rôle %.', NEW.editeur
801
                    USING HINT = 'Être membre d''un rôle disposant des attributs CREATEROLE et INHERIT est nécessaire pour créer de nouveaux éditeurs.' ;
802
            END IF ;
803
            EXECUTE 'SET ROLE ' || quote_ident(createur) ;
804
            EXECUTE 'CREATE ROLE ' || quote_ident(NEW.editeur) ;
805
            RAISE NOTICE '... Le rôle de groupe % a été créé.', NEW.editeur ;
806
            EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
807
        END IF ;
808
        
809
        -- mise à jour du champ d'OID de l'éditeur
810
        IF NEW.editeur = 'public'
811
        THEN
812
            UPDATE z_asgard.gestion_schema_etr
813
                SET oid_editeur = 0,
814
                    ctrl = ARRAY['SELF', 'x7-A;#rzo']
815
                WHERE nom_schema = NEW.nom_schema AND (
816
                    oid_editeur IS NULL
817
                    OR NOT oid_editeur = 0
818
                    ) ;
819
        ELSE
820
            UPDATE z_asgard.gestion_schema_etr
821
                SET oid_editeur = quote_ident(NEW.editeur)::regrole::oid,
822
                    ctrl = ARRAY['SELF', 'x7-A;#rzo']
823
                WHERE nom_schema = NEW.nom_schema AND (
824
                    oid_editeur IS NULL
825
                    OR NOT oid_editeur = quote_ident(NEW.editeur)::regrole::oid
826
                    ) ;
827
        END IF ;
828
    END IF ;
829
    
830
    ------ PREPARATION DU LECTEUR ------
831
    -- limitée ici à la création du rôle et l'implémentation
832
    -- de son OID. On ne s'intéresse donc pas aux cas :
833
    -- - où il y a pas de lecteur ;
834
    -- - d'un schéma qui n'a pas/plus vocation à exister ;
835
    -- - d'un schéma pré-existant dont l'éditeur ne change pas
836
    --   ou dont le libellé a seulement été nettoyé par le
837
    --   trigger BEFORE.
838
    -- ils sont donc exclus au préalable
839
    b_test := False ;
840
    IF NOT NEW.creation OR NEW.lecteur IS NULL
841
            OR 'CLEAN lecteur' = ANY(NEW.ctrl)
842
    THEN
843
        b_test := True ;
844
    ELSIF TG_OP = 'UPDATE'
845
    THEN
846
        IF OLD.creation AND NEW.lecteur = OLD.lecteur
847
        THEN
848
            b_test := True ;
849
        END IF ;
850
    END IF ;
851
    
852
    IF NOT b_test
853
    THEN
854
        IF NOT NEW.lecteur IN (SELECT rolname FROM pg_catalog.pg_roles)
855
                AND NOT NEW.lecteur = 'public'
856
        -- si le lecteur désigné n'existe pas, on le crée
857
        -- ou renvoie une erreur si les privilèges de l'utilisateur
858
        -- sont insuffisants
859
        THEN
860
            IF createur IS NULL
861
            THEN
862
                RAISE EXCEPTION 'TA8. Opération interdite. Vous n''êtes pas habilité à créer le rôle %.', NEW.lecteur
863
                    USING HINT = 'Être membre d''un rôle disposant des attributs CREATEROLE et INHERIT est nécessaire pour créer de nouveaux éditeurs.' ;
864
            END IF ;
865
            EXECUTE 'SET ROLE ' || quote_ident(createur) ;
866
            EXECUTE 'CREATE ROLE ' || quote_ident(NEW.lecteur) ;
867
            RAISE NOTICE '... Le rôle de groupe % a été créé.', NEW.lecteur ;
868
            EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
869
        END IF ;
870
        
871
        -- mise à jour du champ d'OID du lecteur
872
        IF NEW.lecteur = 'public'
873
        THEN
874
            UPDATE z_asgard.gestion_schema_etr
875
                SET oid_lecteur = 0,
876
                    ctrl = ARRAY['SELF', 'x7-A;#rzo']
877
                WHERE nom_schema = NEW.nom_schema AND (
878
                    oid_lecteur IS NULL
879
                    OR NOT oid_lecteur = 0
880
                    ) ;
881
        ELSE
882
            UPDATE z_asgard.gestion_schema_etr
883
                SET oid_lecteur = quote_ident(NEW.lecteur)::regrole::oid,
884
                    ctrl = ARRAY['SELF', 'x7-A;#rzo']
885
                WHERE nom_schema = NEW.nom_schema AND (
886
                    oid_lecteur IS NULL
887
                    OR NOT oid_lecteur = quote_ident(NEW.lecteur)::regrole::oid
888
                    ) ;
889
        END IF ;
890
    END IF ;
891
    
892
    ------ CREATION DU SCHEMA ------
893
    -- on exclut au préalable les cas qui ne
894
    -- correspondent pas à des créations, ainsi que les
895
    -- remontées de l'event trigger sur CREATE SCHEMA,
896
    -- car le schéma existe alors déjà
897
    b_test := False ;
898
    IF NOT NEW.creation OR NEW.ctrl[1] = 'CREATE'
899
    THEN
900
        b_test := True ;
901
    ELSIF TG_OP = 'UPDATE'
902
    THEN
903
        IF OLD.creation
904
        THEN
905
            b_test := True ;
906
        END IF ;
907
    END IF ;
908
    
909
    IF NOT b_test
910
    THEN
911
        -- le schéma est créé s'il n'existe pas déjà (cas d'ajout
912
        -- d'un schéma pré-existant qui n'était pas référencé dans
913
        -- gestion_schema jusque-là), sinon on alerte juste
914
        -- l'utilisateur
915
        IF NOT NEW.nom_schema IN (SELECT nspname FROM pg_catalog.pg_namespace)
916
        THEN
917
            IF NOT has_database_privilege(current_database(), 'CREATE')
918
                    OR NOT pg_has_role(NEW.producteur, 'USAGE')
919
            THEN
920
                -- si le rôle courant n'a pas les privilèges nécessaires pour
921
                -- créer le schéma, on tente avec le rôle createur [de rôles]
922
                -- pré-identifié, dont on sait au moins qu'il aura les
923
                -- permissions nécessaires sur le rôle producteur - mais pas
924
                -- s'il est habilité à créer des schémas
925
                IF createur IS NOT NULL
926
                THEN
927
                    EXECUTE 'SET ROLE ' || quote_ident(createur) ;
928
                END IF ;
929
                IF NOT has_database_privilege(current_database(), 'CREATE')
930
                        OR NOT pg_has_role(NEW.producteur, 'USAGE')
931
                THEN
932
                    RAISE EXCEPTION 'TA9. Opération interdite. Vous n''êtes pas habilité à créer le schéma %.', NEW.nom_schema
933
                        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.' ;
934
                END IF ;
935
            END IF ;
936
            EXECUTE 'CREATE SCHEMA ' || quote_ident(NEW.nom_schema) || ' AUTHORIZATION ' || quote_ident(NEW.producteur) ;
937
            EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
938
            RAISE NOTICE '... Le schéma % a été créé.', NEW.nom_schema ;
939
        ELSE
940
            RAISE NOTICE '(schéma % pré-existant)', NEW.nom_schema ;
941
        END IF ;
942
        -- récupération de l'OID du schéma
943
        UPDATE z_asgard.gestion_schema_etr
944
            SET oid_schema = quote_ident(NEW.nom_schema)::regnamespace::oid,
945
                ctrl = ARRAY['SELF', 'x7-A;#rzo']
946
            WHERE nom_schema = NEW.nom_schema AND (
947
                oid_schema IS NULL
948
                OR NOT oid_schema = quote_ident(NEW.nom_schema)::regnamespace::oid
949
                ) ;   
950
    END IF ;
951
    
952
    ------ APPLICATION DES DROITS DU PRODUCTEUR ------
953
    -- comme précédemment pour la préparation du producteur,
954
    -- on ne s'intéresse pas aux cas :
955
    -- - d'un schéma qui n'a pas/plus vocation à exister
956
    --   (creation vaut False) ;
957
    -- - d'un schéma pré-existant dont le producteur ne change pas
958
    --   ou dont le libellé a juste été nettoyé par le trigger
959
    --   BEFORE ;
960
    -- - d'un schéma qui vient d'être créé, car le producteur
961
    --   sera déjà propriétaire du schéma et de son éventuel
962
    --   contenu (INSERT ou remontée de l'event trigger
963
    --   asgard_on_create_schema) ;
964
    -- - de z_asgard_admin (pour permettre sa saisie initiale
965
    --   dans la table de gestion, étant entendu qu'il est
966
    --   impossible au trigger sur gestion_schema de lancer
967
    --   un ALTER TABLE OWNER TO sur cette même table).
968
    -- ils sont donc exclus au préalable
969
    b_test := False ;
970
    IF NOT NEW.creation
971
            OR 'CLEAN producteur' = ANY(NEW.ctrl)
972
            OR TG_OP = 'INSERT'
973
            OR NEW.ctrl[1] = 'CREATE'
974
            OR NEW.nom_schema = 'z_asgard_admin'
975
    THEN
976
        b_test := True ;
977
    ELSIF TG_OP = 'UPDATE'
978
    THEN
979
        IF OLD.creation AND NEW.producteur = OLD.producteur
980
        THEN
981
            b_test := True ;
982
        END IF ;
983
    END IF ;
984
    
985
    IF NOT b_test
986
    THEN
987
        -- si besoin, on bascule sur le rôle createur. À ce stade,
988
        -- il est garanti que soit l'utilisateur courant soit
989
        -- createur (pour le cas d'un utilisateur courant
990
        -- NOINHERIT) aura les privilèges nécessaires
991
        IF NOT pg_has_role(NEW.producteur, 'USAGE')
992
        THEN
993
            EXECUTE 'SET ROLE ' || quote_ident(createur) ;
994
        ELSIF TG_OP = 'UPDATE'
995
        THEN
996
            IF NOT pg_has_role(a_producteur, 'USAGE')
997
            THEN
998
                EXECUTE 'SET ROLE ' || quote_ident(createur) ; 
999
            END IF ;
1000
        END IF ;
1001
        
1002
        -- changements de propriétaires
1003
        IF (NEW.nom_schema, NEW.producteur)
1004
                IN (SELECT schema_name, schema_owner FROM information_schema.schemata)
1005
        THEN
1006
            -- si producteur est déjà propriétaire du schéma (cas d'une remontée de l'event trigger,
1007
            -- principalement), on ne change que les propriétaires des objets éventuels
1008
            IF quote_ident(NEW.nom_schema)::regnamespace::oid
1009
                    IN (SELECT refobjid FROM pg_catalog.pg_depend WHERE deptype = 'n')
1010
            THEN 
1011
                -- la commande n'est cependant lancée que s'il existe des dépendances de type
1012
                -- DEPENDENCY_NORMAL sur le schéma, ce qui est une condition nécessaire à
1013
                -- l'existence d'objets dans le schéma
1014
                RAISE NOTICE 'attribution de la propriété des objets au rôle producteur du schéma % :', NEW.nom_schema ;
1015
                SELECT z_asgard.asgard_admin_proprietaire(NEW.nom_schema, NEW.producteur, False)
1016
                    INTO n ;
1017
                IF n = 0
1018
                THEN
1019
                    RAISE NOTICE '> néant' ;
1020
                END IF ; 
1021
            END IF ;
1022
        ELSE
1023
            -- sinon schéma + objets
1024
            RAISE NOTICE 'attribution de la propriété du schéma et des objets au rôle producteur du schéma % :', NEW.nom_schema ;
1025
            PERFORM z_asgard.asgard_admin_proprietaire(NEW.nom_schema, NEW.producteur) ;
1026
        END IF ;
1027
        
1028
        EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
1029
    END IF ;
1030
    
1031
    ------ APPLICATION DES DROITS DE L'EDITEUR ------
1032
    -- on ne s'intéresse pas aux cas :
1033
    -- - d'un schéma qui n'a pas/plus vocation à exister ;
1034
    -- - d'un schéma pré-existant dont l'éditeur ne change pas
1035
    --   (y compris pour rester vide) ou dont le libellé
1036
    --   a seulement été nettoyé par le trigger BEFORE.
1037
    -- ils sont donc exclus au préalable
1038
    b_test := False ;
1039
    IF NOT NEW.creation OR 'CLEAN editeur' = ANY(NEW.ctrl)
1040
    THEN
1041
        b_test := True ;
1042
    ELSIF TG_OP = 'UPDATE'
1043
    THEN
1044
        IF OLD.creation
1045
            AND coalesce(NEW.editeur, '') = coalesce(OLD.editeur, '')
1046
        THEN
1047
            b_test := True ;           
1048
        END IF ;
1049
    END IF ;
1050
    
1051
    IF NOT b_test
1052
    THEN
1053
        -- si besoin, on bascule sur le rôle createur. À ce stade,
1054
        -- il est garanti que soit l'utilisateur courant soit
1055
        -- createur (pour le cas d'un utilisateur courant
1056
        -- NOINHERIT) aura les privilèges nécessaires
1057
        IF NOT pg_has_role(NEW.producteur, 'USAGE')
1058
        THEN
1059
            EXECUTE 'SET ROLE ' || quote_ident(createur) ;
1060
        END IF ;
1061
        
1062
        IF TG_OP = 'UPDATE'
1063
        THEN
1064
            -- la validité de OLD.editeur n'ayant
1065
            -- pas été contrôlée par le trigger BEFORE,
1066
            -- on le fait maintenant
1067
            IF OLD.editeur = 'public'
1068
            THEN
1069
                a_editeur := 'public' ;
1070
                -- récupération des modifications manuelles des
1071
                -- droits de OLD.editeur/public, grâce à la fonction
1072
                -- asgard_synthese_public
1073
                SELECT array_agg(commande) INTO l_commande
1074
                    FROM z_asgard.asgard_synthese_public(
1075
                        quote_ident(NEW.nom_schema)::regnamespace
1076
                        ) ;   
1077
            ELSE
1078
                SELECT rolname INTO a_editeur
1079
                    FROM pg_catalog.pg_roles
1080
                    WHERE pg_roles.oid = OLD.oid_editeur ;
1081
                IF FOUND
1082
                THEN
1083
                    -- récupération des modifications manuelles des
1084
                    -- droits de OLD.editeur, grâce à la fonction
1085
                    -- asgard_synthese_role
1086
                    SELECT array_agg(commande) INTO l_commande
1087
                        FROM z_asgard.asgard_synthese_role(
1088
                            quote_ident(NEW.nom_schema)::regnamespace,
1089
                            quote_ident(a_editeur)::regrole
1090
                            ) ;
1091
                END IF ;
1092
            END IF ;
1093
        END IF ;
1094

    
1095
        IF l_commande IS NOT NULL
1096
        -- transfert sur NEW.editeur des droits de
1097
        -- OLD.editeur, le cas échéant
1098
        THEN
1099
            IF NEW.editeur IS NOT NULL
1100
            THEN
1101
                RAISE NOTICE 'suppression et transfert vers le nouvel éditeur des privilèges de l''ancien éditeur du schéma % :', NEW.nom_schema ;
1102
            ELSE
1103
                RAISE NOTICE 'suppression des privilèges de l''ancien éditeur du schéma % :', NEW.nom_schema ;
1104
            END IF ;
1105
            FOREACH c IN ARRAY l_commande
1106
            LOOP
1107
                IF NEW.editeur IS NOT NULL
1108
                THEN
1109
                    EXECUTE format(c, NEW.editeur) ;
1110
                    RAISE NOTICE '> %', format(c, NEW.editeur) ;
1111
                END IF ;
1112
                IF c ~ '^GRANT'
1113
                THEN
1114
                    SELECT z_asgard.asgard_grant_to_revoke(c) INTO c_reverse ;
1115
                    EXECUTE format(c_reverse, a_editeur) ;
1116
                    RAISE NOTICE '> %', format(c_reverse, a_editeur) ;
1117
                END IF ;
1118
            END LOOP ;
1119
            
1120
        -- sinon, application des privilèges standards de l'éditeur
1121
        ELSIF NEW.editeur IS NOT NULL
1122
        THEN
1123
            RAISE NOTICE 'application des privilèges standards pour le rôle éditeur du schéma % :', NEW.nom_schema ;
1124
            
1125
            EXECUTE 'GRANT USAGE ON SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
1126
            RAISE NOTICE '> %', 'GRANT USAGE ON SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
1127
            
1128
            EXECUTE 'GRANT SELECT, UPDATE, DELETE, INSERT ON ALL TABLES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
1129
            RAISE NOTICE '> %', 'GRANT SELECT, UPDATE, DELETE, INSERT ON ALL TABLES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
1130
            
1131
            EXECUTE 'GRANT SELECT, USAGE ON ALL SEQUENCES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
1132
            RAISE NOTICE '> %', 'GRANT SELECT, USAGE ON ALL SEQUENCES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.editeur) ;
1133
            
1134
        END IF ;
1135
        
1136
        EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
1137
    END IF ;
1138
    
1139
    ------ APPLICATION DES DROITS DU LECTEUR ------
1140
    -- on ne s'intéresse pas aux cas :
1141
    -- - d'un schéma qui n'a pas/plus vocation à exister ;
1142
    -- - d'un schéma pré-existant dont le lecteur ne change pas
1143
    --   (y compris pour rester vide) ou dont le libellé
1144
    --   a seulement été nettoyé par le trigger BEFORE.
1145
    -- ils sont donc exclus au préalable
1146
    b_test := False ;
1147
    l_commande := NULL ;
1148
    IF NOT NEW.creation OR 'CLEAN lecteur' = ANY(NEW.ctrl)
1149
    THEN
1150
        b_test := True ;
1151
    ELSIF TG_OP = 'UPDATE'
1152
    THEN
1153
        IF OLD.creation
1154
            AND coalesce(NEW.lecteur, '') = coalesce(OLD.lecteur, '')
1155
        THEN
1156
            b_test := True ;           
1157
        END IF ;
1158
    END IF ;
1159
    
1160
    IF NOT b_test
1161
    THEN
1162
        -- si besoin, on bascule sur le rôle createur. À ce stade,
1163
        -- il est garanti que soit l'utilisateur courant soit
1164
        -- createur (pour le cas d'un utilisateur courant
1165
        -- NOINHERIT) aura les privilèges nécessaires
1166
        IF NOT pg_has_role(NEW.producteur, 'USAGE')
1167
        THEN
1168
            EXECUTE 'SET ROLE ' || quote_ident(createur) ;
1169
        END IF ;
1170
        
1171
        IF TG_OP = 'UPDATE'
1172
        THEN
1173
            -- la validité de OLD.lecteur n'ayant
1174
            -- pas été contrôlée par le trigger BEFORE,
1175
            -- on le fait maintenant
1176
            IF OLD.lecteur = 'public'
1177
            THEN
1178
                a_lecteur := 'public' ;
1179
                -- récupération des modifications manuelles des
1180
                -- droits de OLD.lecteur/public, grâce à la fonction
1181
                -- asgard_synthese_public
1182
                SELECT array_agg(commande) INTO l_commande
1183
                    FROM z_asgard.asgard_synthese_public(
1184
                        quote_ident(NEW.nom_schema)::regnamespace
1185
                        ) ;   
1186
            ELSE
1187
                SELECT rolname INTO a_lecteur
1188
                    FROM pg_catalog.pg_roles
1189
                    WHERE pg_roles.oid = OLD.oid_lecteur ;
1190
                IF FOUND
1191
                THEN
1192
                    -- récupération des modifications manuelles des
1193
                    -- droits de OLD.lecteur, grâce à la fonction
1194
                    -- asgard_synthese_role
1195
                    SELECT array_agg(commande) INTO l_commande
1196
                        FROM z_asgard.asgard_synthese_role(
1197
                            quote_ident(NEW.nom_schema)::regnamespace,
1198
                            quote_ident(a_lecteur)::regrole
1199
                            ) ;
1200
                END IF ;
1201
            END IF ;
1202
        END IF ;
1203

    
1204
        IF l_commande IS NOT NULL
1205
        -- transfert sur NEW.lecteur des droits de
1206
        -- OLD.lecteur, le cas échéant
1207
        THEN
1208
            IF NEW.lecteur IS NOT NULL
1209
            THEN
1210
                RAISE NOTICE 'suppression et transfert vers le nouveau lecteur des privilèges de l''ancien lecteur du schéma % :', NEW.nom_schema ;
1211
            ELSE
1212
                RAISE NOTICE 'suppression des privilèges de l''ancien lecteur du schéma % :', NEW.nom_schema ;
1213
            END IF ;
1214
            FOREACH c IN ARRAY l_commande
1215
            LOOP
1216
                IF NEW.lecteur IS NOT NULL
1217
                THEN
1218
                    EXECUTE format(c, NEW.lecteur) ;
1219
                    RAISE NOTICE '> %', format(c, NEW.lecteur) ;
1220
                END IF ;
1221
                IF c ~ '^GRANT'
1222
                THEN
1223
                    SELECT z_asgard.asgard_grant_to_revoke(c) INTO c_reverse ;
1224
                    EXECUTE format(c_reverse, a_lecteur) ;
1225
                    RAISE NOTICE '> %', format(c_reverse, a_lecteur) ;
1226
                END IF ;
1227
            END LOOP ;
1228
            
1229
        -- sinon, application des privilèges standards du lecteur
1230
        ELSIF NEW.lecteur IS NOT NULL
1231
        THEN
1232
            RAISE NOTICE 'application des privilèges standards pour le rôle lecteur du schéma % :', NEW.nom_schema ;
1233
            
1234
            EXECUTE 'GRANT USAGE ON SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
1235
            RAISE NOTICE '> %', 'GRANT USAGE ON SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
1236
            
1237
            EXECUTE 'GRANT SELECT ON ALL TABLES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
1238
            RAISE NOTICE '> %', 'GRANT SELECT ON ALL TABLES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
1239
            
1240
            EXECUTE 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
1241
            RAISE NOTICE '> %', 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA ' || quote_ident(NEW.nom_schema) || ' TO ' || quote_ident(NEW.lecteur) ;
1242
            
1243
        END IF ;
1244
        
1245
        EXECUTE 'SET ROLE ' || quote_ident(utilisateur) ;
1246
    END IF ;
1247
    
1248
	RETURN NULL ;
1249

    
1250
EXCEPTION WHEN OTHERS THEN
1251
    GET STACKED DIAGNOSTICS e_mssg = MESSAGE_TEXT,
1252
                            e_hint = PG_EXCEPTION_HINT,
1253
                            e_detl = PG_EXCEPTION_DETAIL ;
1254
    RAISE EXCEPTION 'TA0. Opération annulée. Anomalie lors de la traduction physique des modifications de la table de gestion.'
1255
         USING DETAIL = CASE WHEN e_detl = '' THEN e_mssg ELSE e_mssg || ' / ' || e_detl END,
1256
               HINT = e_hint ;    
1257
END
1258
$BODY$ ;
1259

    
1260
ALTER FUNCTION z_asgard_admin.asgard_on_modify_gestion_schema_after()
1261
    OWNER TO g_admin ;
1262

    
1263
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.' ;
1264

    
1265

    
1266
-- Trigger: asgard_on_modify_gestion_schema_after
1267

    
1268
CREATE TRIGGER asgard_on_modify_gestion_schema_after
1269
    AFTER INSERT OR UPDATE
1270
    ON z_asgard_admin.gestion_schema
1271
    FOR EACH ROW
1272
    EXECUTE PROCEDURE z_asgard_admin.asgard_on_modify_gestion_schema_after();
1273

    
1274
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.' ;
1275
   
    (1-1/1)