-
Notifications
You must be signed in to change notification settings - Fork 3
/
asgard--1.4.0--1.4.1.sql
1414 lines (1276 loc) · 65.6 KB
/
asgard--1.4.0--1.4.1.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\echo Use "ALTER EXTENSION plume_pg UPDATE TO '1.4.1'" to load this file. \quit
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- ASGARD - Système de gestion des droits pour PostgreSQL, version 1.4.1
-- > Script de mise à jour depuis la version 1.4.0
--
-- Copyright République Française, 2020-2024.
-- Secrétariat général du Ministère de la Transition écologique et
-- de la Cohésion des territoires.
-- Direction du Numérique.
--
-- contributrice pour cette version : Leslie Lemaire (DNUM/UNI/DRC).
--
-- mél : [email protected]
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- Note de version :
-- https://snum.scenari-community.org/Asgard/Documentation/co/SEC_1-4-1.html
--
-- Documentation :
-- https://snum.scenari-community.org/Asgard/Documentation
--
-- GitHub :
-- https://github.com/MTES-MCT/asgard-postgresql
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- Ce logiciel est un programme informatique complémentaire au système de
-- gestion de base de données PosgreSQL ("https://www.postgresql.org/"). Il
-- met à disposition un cadre méthodologique et des outils pour la gestion
-- des droits sur les serveurs PostgreSQL.
--
-- Ce logiciel est régi par la licence CeCILL-B soumise au droit français
-- et respectant les principes de diffusion des logiciels libres. Vous
-- pouvez utiliser, modifier et/ou redistribuer ce programme sous les
-- conditions de la licence CeCILL-B telle que diffusée par le CEA, le
-- CNRS et l'INRIA sur le site "http://www.cecill.info".
-- Lien SPDX : "https://spdx.org/licenses/CECILL-B.html".
--
-- En contrepartie de l'accessibilité au code source et des droits de copie,
-- de modification et de redistribution accordés par cette licence, il n'est
-- offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons,
-- seule une responsabilité restreinte pèse sur l'auteur du programme, le
-- titulaire des droits patrimoniaux et les concédants successifs.
--
-- A cet égard l'attention de l'utilisateur est attirée sur les risques
-- associés au chargement, à l'utilisation, à la modification et/ou au
-- développement et à la reproduction du logiciel par l'utilisateur étant
-- donné sa spécificité de logiciel libre, qui peut le rendre complexe à
-- manipuler et qui le réserve donc à des développeurs et des professionnels
-- avertis possédant des connaissances informatiques approfondies. Les
-- utilisateurs sont donc invités à charger et tester l'adéquation du
-- logiciel à leurs besoins dans des conditions permettant d'assurer la
-- sécurité de leurs systèmes et ou de leurs données et, plus généralement,
-- à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.
--
-- Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
-- pris connaissance de la licence CeCILL-B, et que vous en avez accepté
-- les termes.
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- Cette extension ne peut être installée que par un super-utilisateur
-- (création de déclencheurs sur évènement).
--
-- Elle n'est pas compatible avec les versions 9.4 ou antérieures de
-- PostgreSQL.
--
-- schémas contenant les objets : z_asgard et z_asgard_admin.
--
-- objets créés par le script :
-- - Function: z_asgard_admin.asgard_nettoyage_oids()
-- - Function: z_asgard.asgard_cherche_lecteur(text, boolean, boolean, boolean)
-- - Function: z_asgard.asgard_cherche_editeur(text, boolean, boolean, boolean)
-- - Function: z_asgard_admin.asgard_restaure_editeurs_lecteurs(text, boolean, boolean, boolean, boolean)
--
-- objets modifiés par le script :
-- - Table: z_asgard_admin.gestion_schema
-- - Function: z_asgard_admin.asgard_on_modify_gestion_schema_before()
--
-- objets supprimés par le script :
-- Néant.
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- MOT DE PASSE DE CONTRÔLE : 'x7-A;#rzo'
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
------ 2.2 - TABLE GESTION_SCHEMA ------
-- Table: z_asgard_admin.gestion_schema
ALTER TABLE z_asgard_admin.gestion_schema
ADD CONSTRAINT gestion_schema_no_system_schema CHECK (
NOT nom_schema ~ ANY(
ARRAY[
'^pg_toast', '^pg_temp', '^pg_catalog$', '^public$',
'^information_schema$', '^topology$'
]
)
) ;
------ 4.7 - NETTOYAGE DE LA TABLE DE GESTION ------
-- Function: z_asgard_admin.asgard_nettoyage_oids()
CREATE OR REPLACE FUNCTION z_asgard_admin.asgard_nettoyage_oids()
RETURNS text
LANGUAGE plpgsql
AS $_$
/* Recalcule les OIDs des schémas et rôles référencés dans la table de gestion en fonction de leurs noms.
Partant du nom de schéma renseigné dans le champ "nom_schema",
cette fonction corrige les valeurs des champs suivants d'autant que
de besoin :
* "creation" est mis à True si le schéma existe dans la base,
à False sinon.
* "oid_schema" est mis à NULL si le schéma n'existe pas, sinon
sa valeur est actualisée pour correspondre à l'OID du schéma
de nom "nom_schema".
* "oid_producteur" est mis à NULL si le schéma n'existe pas,
sinon sa valeur est actualisée pour correspondre à l'OID du
rôle propriétaire du schéma de nom "nom_schema".
* Si le schéma existe, "producteur" est actualisé pour
correspondre au nom du rôle propriétaire du schéma.
* "oid_editeur" et "oid_lecteur" sont mis à NULL si le schéma
n'existe pas. Sinon, et si "editeur" et "lecteur" respectivement
sont renseignés, ils sont mis à jour avec les OID de ces rôles
s'ils existent. Si lesdits rôles n'existent pas, les champs
"oid_editeur" et "editeur" ou "oid_lecteur" et "lecteur" sont
mis à NULL.
Cette fonction est en quelque sorte l'inverse de z_asgard.asgard_nettoyage_roles(),
qui met à jour les noms des rôles en fonction des OID référencés
dans la table gestion.
Returns
-------
text
'__ NETTOYAGE REUSSI.'
*/
DECLARE
rec record ;
e_mssg text ;
e_detl text ;
e_hint text ;
BEGIN
ALTER TABLE z_asgard_admin.gestion_schema
DISABLE TRIGGER asgard_on_modify_gestion_schema_before,
DISABLE TRIGGER asgard_on_modify_gestion_schema_after ;
FOR rec IN (
SELECT
gestion_schema.nom_schema,
pg_namespace.oid AS oid_schema,
pg_namespace.oid IS NOT NULL AS creation,
CASE WHEN pg_namespace.oid IS NOT NULL
THEN
rolowner.rolname
ELSE
gestion_schema.producteur
END AS producteur,
pg_namespace.nspowner AS oid_producteur,
CASE WHEN pg_namespace.oid IS NULL
OR gestion_schema.editeur = 'public'
OR rolediteur.oid IS NOT NULL
THEN
gestion_schema.editeur
END AS editeur,
CASE WHEN pg_namespace.oid IS NOT NULL AND gestion_schema.editeur = 'public'
THEN
0
WHEN pg_namespace.oid IS NOT NULL
THEN
rolediteur.oid
END AS oid_editeur,
CASE WHEN pg_namespace.oid IS NULL
OR gestion_schema.lecteur = 'public'
OR rollecteur.oid IS NOT NULL
THEN
gestion_schema.lecteur
END AS lecteur,
CASE WHEN pg_namespace.oid IS NOT NULL AND gestion_schema.lecteur = 'public'
THEN
0
WHEN pg_namespace.oid IS NOT NULL
THEN
rollecteur.oid
END AS oid_lecteur
FROM z_asgard_admin.gestion_schema
LEFT JOIN pg_catalog.pg_namespace ON pg_namespace.nspname = gestion_schema.nom_schema
LEFT JOIN pg_catalog.pg_roles AS rolowner ON rolowner.oid = pg_namespace.nspowner
LEFT JOIN pg_catalog.pg_roles AS rolediteur ON rolediteur.rolname = gestion_schema.editeur
LEFT JOIN pg_catalog.pg_roles AS rollecteur ON rollecteur.rolname = gestion_schema.lecteur
)
LOOP
UPDATE z_asgard_admin.gestion_schema
SET creation = rec.creation,
oid_schema = rec.oid_schema,
producteur = rec.producteur,
oid_producteur = rec.oid_producteur,
editeur = rec.editeur,
oid_editeur = rec.oid_editeur,
lecteur = rec.lecteur,
oid_lecteur = rec.oid_lecteur
WHERE gestion_schema.nom_schema = rec.nom_schema ;
END LOOP ;
ALTER TABLE z_asgard_admin.gestion_schema
ENABLE TRIGGER asgard_on_modify_gestion_schema_before,
ENABLE TRIGGER asgard_on_modify_gestion_schema_after ;
RETURN '__ NETTOYAGE REUSSI.' ;
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS e_mssg = MESSAGE_TEXT,
e_hint = PG_EXCEPTION_HINT,
e_detl = PG_EXCEPTION_DETAIL ;
RAISE EXCEPTION 'FNO0 > %', e_mssg
USING DETAIL = e_detl,
HINT = e_hint ;
END
$_$;
ALTER FUNCTION z_asgard_admin.asgard_nettoyage_oids()
OWNER TO g_admin ;
COMMENT ON FUNCTION z_asgard_admin.asgard_nettoyage_oids() IS 'ASGARD. Recalcule les OIDs des schémas et rôles référencés dans la table de gestion en fonction de leurs noms.' ;
------ 4.19 - RECHERCHE DE LECTEURS ET EDITEURS ------
-- Function: z_asgard.asgard_cherche_lecteur(text, boolean, boolean, boolean)
CREATE OR REPLACE FUNCTION z_asgard.asgard_cherche_lecteur(
nom_schema text,
autorise_public boolean DEFAULT True,
autorise_login boolean DEFAULT False,
autorise_superuser boolean DEFAULT False
)
RETURNS text
LANGUAGE SQL
RETURNS NULL ON NULL INPUT
AS $BODY$
/* Au vu des privilèges établis, cherche le rôle le plus susceptible d''être
qualifié de "lecteur" du schéma.
Cette fonction renvoie, s'il existe, le rôle qui remplit les
conditions suivantes :
* Ce n'est pas le propriétaire du schéma.
* Ce n'est pas un rôle de connexion (pas d'attribut LOGIN), sauf
si "autorise_login" vaut True.
* Ce n'est pas un super-utilisateur (pas d'attribut SUPERUSER), sauf
si "autorise_superuser" vaut True.
* Il dispose du privilège USAGE sur le schéma.
* Il ne dispose pas du privilège CREATE sur le schéma.
* Il dispose du privilège SELECT sur strictement plus de la moitié des
tables, tables partitionnées, vues, vues matérialisées et tables
étrangères du schéma.
* Il ne dispose des privilèges UPDATE, INSERT, DELETE ou TRUNCATE
sur aucune des tables, tables partitionnées, vues, vues matérialisées
et tables étrangères du schéma.
Si plusieurs rôles remplissent ces conditions, la fonction renvoie celui
qui dispose du privilège SELECT sur le plus grand nombre de tables
ou objets assimilés. En cas d'égalité, le rôle renvoyé sera le premier
dans l'ordre alphabétique.
Le pseudo-rôle "public" est pris en compte, sauf si "autorise_public"
vaut False.
Parameters
----------
nom_schema : text
Nom du schéma.
autorise_public : boolean, default True
Le pseudo-rôle "public" est-il inclus dans la recherche ?
autorise_login : boolean, default False
Les rôles de connexion (attribut LOGIN) sont-ils inclus dans la
recherche ?
autorise_superuser : boolean, default False
Les super-utilisateurs (attribut SUPERUSER) sont-ils inclus dans la
recherche ?
Returns
-------
text
Le nom du rôle pouvant être qualifié de lecteur du schéma, ou NULL
si aucun rôle ne remplit les conditions.
*/
WITH relations AS (
SELECT relname, relacl, relowner
FROM pg_catalog.pg_class
WHERE pg_class.relnamespace = quote_ident(nom_schema)::regnamespace
AND relkind IN ('r', 'v', 'm', 'f', 'p')
),
total AS (
SELECT floor(count(*) / 2)::int AS half FROM pg_catalog.pg_class
WHERE pg_class.relnamespace = quote_ident(nom_schema)::regnamespace
AND relkind IN ('r', 'v', 'm', 'f', 'p')
),
relprivileges AS (
SELECT
acl.grantee,
count(DISTINCT relations.relname) FILTER (WHERE acl.privilege = 'SELECT') AS count_select,
count(DISTINCT relations.relname) FILTER (WHERE acl.privilege IN ('INSERT', 'UPDATE', 'DELETE', 'TRUNCATE')) AS count_modify
FROM relations, aclexplode(relacl) AS acl (grantor, grantee, privilege, grantable)
GROUP BY grantee
),
nspprivileges AS (
SELECT
acl.grantee,
count(DISTINCT pg_namespace.nspname) FILTER (WHERE acl.privilege = 'USAGE') AS count_usage,
count(DISTINCT pg_namespace.nspname) FILTER (WHERE acl.privilege = 'CREATE') AS count_create
FROM pg_catalog.pg_namespace, aclexplode(nspacl) AS acl (grantor, grantee, privilege, grantable)
WHERE pg_namespace.nspname = nom_schema AND NOT acl.grantee = pg_namespace.nspowner
GROUP BY grantee
)
SELECT
CASE WHEN nspprivileges.grantee = 0
THEN
'public'
ELSE
pg_roles.rolname::text
END AS rolname
FROM nspprivileges
INNER JOIN relprivileges USING (grantee)
INNER JOIN total ON total.half < relprivileges.count_select
LEFT JOIN pg_catalog.pg_roles ON pg_roles.oid = nspprivileges.grantee
AND (autorise_login OR NOT pg_roles.rolcanlogin)
AND (autorise_superuser OR NOT pg_roles.rolsuper)
WHERE
relprivileges.count_modify = 0
AND nspprivileges.count_usage = 1
AND nspprivileges.count_create = 0
AND (nspprivileges.grantee = 0 AND autorise_public OR pg_roles.rolname IS NOT NULL)
ORDER BY relprivileges.count_select DESC, coalesce(pg_roles.rolname, 'public')
LIMIT 1 ;
$BODY$ ;
COMMENT ON FUNCTION z_asgard.asgard_cherche_lecteur(text, boolean, boolean, boolean) IS 'ASGARD. Au vu des privilèges établis, cherche le rôle le plus susceptible d''être qualifié de "lecteur" du schéma.' ;
-- Function: z_asgard.asgard_cherche_editeur(text, boolean, boolean, boolean)
CREATE OR REPLACE FUNCTION z_asgard.asgard_cherche_editeur(
nom_schema text,
autorise_public boolean DEFAULT True,
autorise_login boolean DEFAULT False,
autorise_superuser boolean DEFAULT False
)
RETURNS text
LANGUAGE SQL
RETURNS NULL ON NULL INPUT
AS $BODY$
/* Au vu des privilèges établis, cherche le rôle le plus susceptible d''être
qualifié d'"éditeur" du schéma.
Cette fonction renvoie, s'il existe, le rôle qui remplit les
conditions suivantes :
* Ce n'est pas le propriétaire du schéma.
* Ce n'est pas un rôle de connexion (pas d'attribut LOGIN), sauf
si "autorise_login" vaut True.
* Ce n'est pas un super-utilisateur (pas d'attribut SUPERUSER), sauf
si "autorise_superuser" vaut True.
* Il dispose du privilège USAGE sur le schéma.
* Il ne dispose pas du privilège CREATE sur le schéma.
* Il dispose des privilèges INSERT et/ou UPDATE sur strictement plus
de la moitié des tables, tables partitionnées, vues, vues matérialisées
et tables étrangères du schéma.
Si plusieurs rôles remplissent ces conditions, la fonction renvoie celui
qui dispose des privilèges INSERT et/ou UPDATE sur le plus grand nombre
de tables ou objets assimilés. En cas d'égalité, le rôle renvoyé sera le
premier dans l'ordre alphabétique.
Le pseudo-rôle "public" est pris en compte, sauf si "autorise_public"
vaut False.
Parameters
----------
nom_schema : text
Nom du schéma.
autorise_public : boolean, default True
Le pseudo-rôle "public" est-il inclus dans la recherche ?
autorise_login : boolean, default False
Les rôles de connexion (attribut LOGIN) sont-ils inclus dans la
recherche ?
autorise_superuser : boolean, default False
Les super-utilisateurs (attribut SUPERUSER) sont-ils inclus dans la
recherche ?
Returns
-------
text
Le nom du rôle pouvant être qualifié d'éditeur du schéma, ou NULL
si aucun rôle ne remplit les conditions.
*/
WITH relations AS (
SELECT relname, relacl, relowner
FROM pg_catalog.pg_class
WHERE pg_class.relnamespace = quote_ident(nom_schema)::regnamespace
AND relkind IN ('r', 'v', 'm', 'f', 'p')
),
total AS (
SELECT floor(count(*) / 2)::int AS half FROM pg_catalog.pg_class
WHERE pg_class.relnamespace = quote_ident(nom_schema)::regnamespace
AND relkind IN ('r', 'v', 'm', 'f', 'p')
),
relprivileges AS (
SELECT
acl.grantee,
count(DISTINCT relations.relname) FILTER (WHERE acl.privilege IN ('INSERT', 'UPDATE')) AS count_edit
FROM relations, aclexplode(relacl) AS acl (grantor, grantee, privilege, grantable)
GROUP BY grantee
),
nspprivileges AS (
SELECT
acl.grantee,
count(DISTINCT pg_namespace.nspname) FILTER (WHERE acl.privilege = 'USAGE') AS count_usage,
count(DISTINCT pg_namespace.nspname) FILTER (WHERE acl.privilege = 'CREATE') AS count_create
FROM pg_catalog.pg_namespace, aclexplode(nspacl) AS acl (grantor, grantee, privilege, grantable)
WHERE pg_namespace.nspname = nom_schema AND NOT acl.grantee = pg_namespace.nspowner
GROUP BY grantee
)
SELECT
CASE WHEN nspprivileges.grantee = 0
THEN
'public'
ELSE
pg_roles.rolname::text
END AS rolname
FROM nspprivileges
INNER JOIN relprivileges USING (grantee)
INNER JOIN total ON total.half < relprivileges.count_edit
LEFT JOIN pg_catalog.pg_roles ON pg_roles.oid = nspprivileges.grantee
AND (autorise_login OR NOT pg_roles.rolcanlogin)
AND (autorise_superuser OR NOT pg_roles.rolsuper)
WHERE nspprivileges.count_usage = 1
AND nspprivileges.count_create = 0
AND (nspprivileges.grantee = 0 AND autorise_public OR pg_roles.rolname IS NOT NULL)
ORDER BY relprivileges.count_edit DESC, coalesce(pg_roles.rolname, 'public')
LIMIT 1 ;
$BODY$ ;
COMMENT ON FUNCTION z_asgard.asgard_cherche_editeur(text, boolean, boolean, boolean) IS 'ASGARD. Au vu des privilèges établis, cherche le rôle le plus susceptible d''être qualifié d''"éditeur" du schéma.' ;
-- Function: z_asgard_admin.asgard_restaure_editeurs_lecteurs(text, boolean, boolean, boolean, boolean)
CREATE OR REPLACE FUNCTION z_asgard_admin.asgard_restaure_editeurs_lecteurs(
nom_schema text DEFAULT NULL,
preserve boolean DEFAULT True,
autorise_public boolean DEFAULT True,
autorise_login boolean DEFAULT False,
autorise_superuser boolean DEFAULT False
)
RETURNS text
LANGUAGE plpgsql
AS $_$
/* Recalcule les éditeurs et lecteurs renseignés dans la table de gestion en fonction des droits effectifs.
Cette fonction s'appuie sur "z_asgard"."asgard_cherche_editeur" et
"z_asgard"."asgard_cherche_lecteur" pour recalculer les lecteurs et éditeurs
de la table de gestion en fonction des privilèges effectifs sur les
objets de la base. Au contraire d'un simple UPDATE des champs "editeur" et
"lecteur" de la table, qui confère une fonction au rôle spécifié et
pourra donc avoir pour effet de modifier les privilèges dont il dispose selon
ceux qui sont prévus pour ladite fonction (et, le cas échéant, de retirer lesdits
privilèges au rôle qui occupait auparavant la fonction),
"asgard_restaure_editeurs_lecteurs" n'altère en aucune façon les droits de la base.
Parameters
----------
nom_schema : text, optional
Si renseigné, les champs "lecteur" et "editeur" ne seront recalculés
que pour le rôle considéré. Sinon, ils seront mis à jour pour tous les
schémas actifs de la table.
preserve : boolean, default True
Si True, la fonction ne modifiera pas les lecteurs et éditeurs
déjà renseignés dans la table de gestion. Elle en ajoutera
simplement là où il n'y en avait pas, sous réserve que les fonctions de
recherche aient renvoyé un résultat. À noter que si "preserve" vaut False,
la fonction aura aussi pour effet d'effacer les éditeurs et lecteurs
sans en renseigner de nouveaux quand les fonctions de recherche
n'identifient pas de rôles satisfaisant aux conditions.
autorise_public : boolean, default False
Passé en argument aux fonctions "asgard_cherche_editeur"
et "asgard_cherche_lecteur". Cf. définition de ces fonctions pour plus
de détails.
autorise_login : boolean, default False
Passé en argument aux fonctions "asgard_cherche_editeur"
et "asgard_cherche_lecteur". Cf. définition de ces fonctions pour plus
de détails.
autorise_superuser : boolean, default False
Passé en argument aux fonctions "asgard_cherche_editeur"
et "asgard_cherche_lecteur". Cf. définition de ces fonctions pour plus
de détails.
Returns
-------
text
'__ RESTAURATION DES LECTEURS ET EDITEURS REUSSIE.'
*/
DECLARE
rec record ;
e_mssg text ;
e_detl text ;
e_hint text ;
BEGIN
ALTER TABLE z_asgard_admin.gestion_schema
DISABLE TRIGGER asgard_on_modify_gestion_schema_before,
DISABLE TRIGGER asgard_on_modify_gestion_schema_after ;
FOR rec IN (
SELECT
gestion_schema.nom_schema,
gestion_schema.editeur AS old_editeur,
z_asgard.asgard_cherche_editeur(
nom_schema := gestion_schema.nom_schema,
autorise_public := asgard_restaure_editeurs_lecteurs.autorise_public,
autorise_login := asgard_restaure_editeurs_lecteurs.autorise_login,
autorise_superuser := asgard_restaure_editeurs_lecteurs.autorise_superuser
) AS new_editeur,
gestion_schema.lecteur AS old_lecteur,
z_asgard.asgard_cherche_lecteur(
nom_schema := gestion_schema.nom_schema,
autorise_public := asgard_restaure_editeurs_lecteurs.autorise_public,
autorise_login := asgard_restaure_editeurs_lecteurs.autorise_login,
autorise_superuser := asgard_restaure_editeurs_lecteurs.autorise_superuser
) AS new_lecteur
FROM z_asgard_admin.gestion_schema
WHERE creation AND
(
asgard_restaure_editeurs_lecteurs.nom_schema IS NULL
OR gestion_schema.nom_schema = asgard_restaure_editeurs_lecteurs.nom_schema
)
)
LOOP
-- éditeur
IF (rec.old_editeur IS NULL OR NOT asgard_restaure_editeurs_lecteurs.preserve)
AND coalesce(rec.old_editeur, '') != coalesce(rec.new_editeur, '')
THEN
IF rec.new_editeur = 'public'
THEN
UPDATE z_asgard_admin.gestion_schema
SET editeur = 'public',
oid_editeur = 0
WHERE gestion_schema.nom_schema = rec.nom_schema ;
ELSIF rec.new_editeur IS NULL
THEN
UPDATE z_asgard_admin.gestion_schema
SET editeur = NULL,
oid_editeur = NULL
WHERE gestion_schema.nom_schema = rec.nom_schema ;
ELSE
UPDATE z_asgard_admin.gestion_schema
SET editeur = rec.new_editeur,
oid_editeur = quote_ident(rec.new_editeur)::regrole::oid
WHERE gestion_schema.nom_schema = rec.nom_schema ;
END IF ;
RAISE NOTICE '%', format(
'Restauration de l''éditeur du schéma "%s" dans la table de gestion. Avant : %s ; après : %s.',
rec.nom_schema,
coalesce(rec.old_editeur, 'NULL'),
coalesce(rec.new_editeur, 'NULL')
) ;
END IF ;
-- lecteur
IF (rec.old_lecteur IS NULL OR NOT asgard_restaure_editeurs_lecteurs.preserve)
AND coalesce(rec.old_lecteur, '') != coalesce(rec.new_lecteur, '')
THEN
IF rec.new_lecteur = 'public'
THEN
UPDATE z_asgard_admin.gestion_schema
SET lecteur = 'public',
oid_lecteur = 0
WHERE gestion_schema.nom_schema = rec.nom_schema ;
ELSIF rec.new_lecteur IS NULL
THEN
UPDATE z_asgard_admin.gestion_schema
SET lecteur = NULL,
oid_lecteur = NULL
WHERE gestion_schema.nom_schema = rec.nom_schema ;
ELSE
UPDATE z_asgard_admin.gestion_schema
SET lecteur = rec.new_lecteur,
oid_lecteur = quote_ident(rec.new_lecteur)::regrole::oid
WHERE gestion_schema.nom_schema = rec.nom_schema ;
END IF ;
RAISE NOTICE '%', format(
'Restauration du lecteur du schéma "%s" dans la table de gestion. Avant : %s ; après : %s.',
rec.nom_schema,
coalesce(rec.old_lecteur, 'NULL'),
coalesce(rec.new_lecteur, 'NULL')
) ;
END IF ;
END LOOP ;
ALTER TABLE z_asgard_admin.gestion_schema
ENABLE TRIGGER asgard_on_modify_gestion_schema_before,
ENABLE TRIGGER asgard_on_modify_gestion_schema_after ;
RETURN '__ RESTAURATION DES LECTEURS ET EDITEURS REUSSIE.' ;
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS e_mssg = MESSAGE_TEXT,
e_hint = PG_EXCEPTION_HINT,
e_detl = PG_EXCEPTION_DETAIL ;
RAISE EXCEPTION 'FRE0 > %', e_mssg
USING DETAIL = e_detl,
HINT = e_hint ;
END
$_$;
ALTER FUNCTION z_asgard_admin.asgard_restaure_editeurs_lecteurs(text, boolean, boolean, boolean, boolean)
OWNER TO g_admin ;
COMMENT ON FUNCTION z_asgard_admin.asgard_restaure_editeurs_lecteurs(text, boolean, boolean, boolean, boolean) IS 'ASGARD. Recalcule les éditeurs et lecteurs renseignés dans la table de gestion en fonction des droits effectifs.' ;
------ 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$
/* Fonction exécutée par le déclencheur asgard_on_modify_gestion_schema_before
sur z_asgard_admin.gestion_schema, qui valide et normalise les informations
saisies dans la table de gestion avant leur enregistrement.
*/
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 ;
------- SCHEMA DEJA REFERENCE ------
-- en cas d'INSERT portant sur un schéma actif déjà référencé
-- dans la table de gestion, Asgard tente de déréférencer le
-- schéma pour permettre au référencement de se dérouler sans
-- erreur
IF TG_OP = 'INSERT'
THEN
IF NEW.creation AND NEW.nom_schema IN (
SELECT gestion_schema_usr.nom_schema
FROM z_asgard.gestion_schema_usr
WHERE creation
)
THEN
RAISE NOTICE 'Le schéma % est déjà référencé dans la table de gestion. Tentative de dé-référencement préalable.', NEW.nom_schema ;
PERFORM z_asgard_admin.asgard_sortie_gestion_schema(NEW.nom_schema) ;
END IF ;
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 ce schéma, 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 de la table de gestion les informations relatives à ce schéma, 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).
-- c'est oid_producteur et pas producteur qui est utilisé au cas
-- où le nom du rôle aurait été modifié entre temps
IF NOT pg_has_role(OLD.oid_producteur, 'USAGE')
THEN
RAISE EXCEPTION 'TB23. Opération interdite (schéma %).', OLD.nom_schema
USING DETAIL = format('Seuls les membres du rôle producteur %s peuvent supprimer ce schéma.', OLD.oid_producteur::regrole) ;
ELSE
EXECUTE format('DROP SCHEMA %I CASCADE', OLD.nom_schema) ;
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', 'END')
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 ou END)
-- 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', 'END')
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 rolname, nspowner
INTO NEW.producteur, NEW.oid_producteur
FROM pg_catalog.pg_namespace
LEFT JOIN pg_catalog.pg_roles ON pg_roles.oid = nspowner
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 = format('Ancien nom "%s", nouveau nom "%s".', OLD.producteur, 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 = format('Ancien nom "%s".', 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 = format('Ancien nom "%s", nouveau nom "%s".', OLD.editeur, 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 = format('Ancien nom "%s".', 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 = format('Ancien nom "%s", nouveau nom "%s".', OLD.lecteur, NEW.lecteur) ;
NEW.ctrl := array_append(NEW.ctrl, 'CLEAN lecteur') ;