mardi 20 septembre 2011

PL/SQL: clause AUTHID

Lorsque vous exécutez une procédure ou fonction PL/SQL le code s’exécute selon les privilèges du USER propriétaire du programme en question. Ce comportement peut être modifié en modifiant la clause AUTHID des procédures, packages et fonctions.

Par défaut la clause AUTHID a pour valeur DEFINER et elle peut être définie à CURRENT_USER lorsque vous désirez que le code PL/SQL exécutée par la procédure ou fonction s’exécute selon les privilèges du USER appelant.

Utiliser la clause AUTHID CURRENT_USER peut être pratique dans les cas où vous avez plusieurs schémas dans votre base avec des noms de tables identiques entre les schémas et que vous ne souhaitez pas dupliquer le code applicatif pour chaque schéma. En définissant, vos programmes PL/SQL en mode AUTHID CURRENT_USER il devient possible de les invoquer depuis les autres schémas de la base. Le code exécuté est alors basé sur les objets du schéma qui appelle le programme même si ce programme est stocké dans un autre schéma.

Voyons ça avec un exemple.

Je me connecte en SYS et je crée une procédure qui crée une table T1 :
SQL> conn / as sysdba
Connected.
 
SQL> create user user1 identified by user1;
 
User created.
 
SQL> grant DBA to user1;
 
Grant succeeded.
 
SQL> create or replace procedure create_table
  2  IS
  3  BEGIN
  4     EXECUTE IMMEDIATE 'CREATE TABLE T1 (C1 NUMBER)';
  5  END create_table;
  6  /
 
Procedure created.
 
SQL> grant execute on create_table to public;
 
Grant succeeded.

La clause AUTHID n’étant pas spécifié, la table T1 créée lors de l’appel à la procédure sera créée dans le schéma propriétaire de la procédure stockée à savoir SYS.
On peut le vérifier en appelant la procédure depuis le schéma USER1 :
SQL> conn user1/user1
Connected.
 
SQL> exec sys.create_table;
 
PL/SQL procedure successfully completed.
 
SQL> desc t1
ERROR:
ORA-04043: object t1 does not exist
 
SQL> conn / as sysdba
Connected.
SQL> desc T1
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 C1                                                 NUMBER

On voit bien que la table T1 n’existe pas sous le schéma USER1 mais bien sous le schéma SYS.
Maintenant je vais recréer la procédure stockée en spécifiant la clause AUTHID CURRENT_USER :
SQL> conn / as sysdba
Connected.
 
SQL> create or replace procedure create_table
  2  AUTHID CURRENT_USER
  3  IS
  4  BEGIN
  5     EXECUTE IMMEDIATE 'CREATE TABLE T1 (C1 NUMBER)';
  6  END create_table;
  7  /
 
Procedure created.

Si j’appelle la procédure depuis le user USER1, la table sera créée dans le schéma USER1 :
SQL> conn user1/user1
Connected.
SQL>  exec sys.create_table;
 
PL/SQL procedure successfully completed.
 
SQL> desc T1
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 C1                                                 NUMBER

Je pourrais appeler la même procédure depuis n’importe quel USER. J’ai ainsi une seule procédure à maintenir et qui est applicable sur tous les schémas.

Voyons maintenant un cas un peu plus épineux.
Imaginons que la procédure CREATE_TABLE soit créée sous le schéma USER2 en mode AUTHID DEFINER et que j’ai une procédure CREATE_TABLE en mode AUTHID CURRENT_USER sous le schéma USER1 qui appelle la procédure CREATE_TABLE de USER2. Si j’appelle depuis SYS la procédure CREATE_TABLE du USER1 qui appelle la procédure CREATE_TABLE du USER2, sous quel schéma sera créée la table T1 ? La réponse en exemple…
SQL> conn user2/user2
Connected.
SQL> create or replace procedure create_table
  2      IS
  3  BEGIN
  4     EXECUTE IMMEDIATE 'CREATE TABLE T1 (C1 NUMBER)';
  5  END create_table;
  6  /
 
Procedure created.
 
SQL> grant execute on create_table to public;
 
Grant succeeded.
 
SQL> conn user1/user1
Connected.
SQL> create or replace procedure create_table
  2  AUTHID CURRENT_USER
  3  IS
  4  BEGIN
  5     user2.create_table;
  6  END create_table;
  7  /
 
Procedure created.
 
SQL> grant execute on create_table to public;
 
Grant succeeded.
 
SQL> conn / as sysdba
Connected.
SQL> select owner from all_tables where table_name='T1';
 
no rows selected

Les procédures sont créées et il n’existe aucune table T1 dans la base.
J’appelle ensuite depuis SYS la procédure CREATE_TABLE de USER1 qui est en mode AUTHID CURRENT_USER en espérant que la table soit créée sous SYS :
SQL> exec user1.create_table;
 
PL/SQL procedure successfully completed.
 
SQL> select owner from all_tables where table_name='T1';
 
OWNER
------------------------------
USER2

Malheureusement la table a été créée sous le USER2, ce qui est en fait logique car la procédure de USER2 a été créée en mode DEFINER. Il faut donc être conscient qu’une procédure en mode CURRENT_USER ne garantit pas que ce mode soit appliqué aux procédures appelées par cette procédure.

PLS-00157: AUTHID only allowed on schema-level programs

Si vous obtenez ce message d’erreur lors de la compilation d’un package c’est surement que vous avez tenté de définir la clause AUTHID au niveau d’une procédure ou fonction appartenant à un package:
SQL>  Create or replace package pkg1
  2   IS
  3     procedure create_table;
  4   END pkg1;
  5   /
 
Package created.
 
SQL> Create or replace package body pkg1
  2   IS
  3     procedure create_table
  4     AUTHID CURRENT_USER
  5     IS
  6     BEGIN
  7             EXECUTE IMMEDIATE 'CREATE TABLE T1 (C1 NUMBER)';
  8     END create_table;
  9   END pkg1;
 10   /
 
Warning: Package Body created with compilation errors.
 
SQL> sho errors
Errors for PACKAGE BODY PKG1:
 
LINE/COL ERROR
-------- -----------------------------------------------------------------
4/9      PLS-00157: AUTHID only allowed on schema-level programs

Ce message indique qu’il n’est pas possible d’appliquer la clause AUTHID au niveau d’une seule procédure du package. La clause ne peut être spécifiée qu’au niveau de la spécification du package et s’applique donc à tous les programmes du package :
SQL> Create or replace package pkg1
  2   AUTHID CURRENT_USER
  3   IS
  4     procedure create_table;
  5   END pkg1;
  6   /
 
Package created.
 
SQL> Create or replace package body pkg1
  2   IS
  3     procedure create_table
  4     IS
  5     BEGIN
  6             EXECUTE IMMEDIATE 'CREATE TABLE T1 (C1 NUMBER)';
  7     END create_table;
  8   END pkg1;
  9   /
 
Package body created.