Módulo 41: Lenguaje de Control de Datos (DCL)

Objetivos del módulo

  • Comprender los objetivos de la seguridad en bases de datos: confidencialidad, integridad y disponibilidad
  • Distinguir autenticación de autorización
  • Crear, modificar, bloquear y borrar usuarios en Oracle, y asignarles tablespaces y cuotas
  • Conocer la diferencia entre usuarios comunes y locales en la arquitectura multitenant
  • Conceder y revocar privilegios de sistema y de objeto con GRANT y REVOKE, incluidos los privilegios por columnas
  • Entender la diferencia entre WITH ADMIN OPTION y WITH GRANT OPTION y su efecto en cascada al revocar
  • Usar los privilegios de esquema de Oracle 23ai
  • Agrupar privilegios en roles, conocer los roles predefinidos y gestionar los roles activos de una sesión
  • Limitar recursos y endurecer contraseñas con perfiles
  • Consultar los privilegios en el diccionario de datos
  • Usar vistas como mecanismo de seguridad por filas y columnas
  • Aplicar el principio de mínimo privilegio y conocer la auditoría unificada
  • Diseñar y probar la seguridad completa del esquema EMPRESA

1. La seguridad en las bases de datos

Una base de datos guarda uno de los activos más valiosos de una organización: sus datos. Nóminas, historiales médicos, datos bancarios, secretos comerciales… Un error de seguridad puede suponer pérdidas económicas, sanciones legales (el RGPD europeo prevé multas de hasta el 4 % de la facturación mundial) y una pérdida de confianza difícil de recuperar.

📘 Concepto: La seguridad de la información se resume en tres objetivos, la tríada CIA:

  • Confidencialidad (confidentiality): solo las personas autorizadas pueden ver los datos. Que un comercial no pueda consultar las nóminas.
  • Integridad (integrity): los datos solo pueden ser modificados por quien está autorizado, de la forma permitida, y son correctos y coherentes. Que nadie pueda subirse el sueldo.
  • Disponibilidad (availability): los datos están accesibles cuando los usuarios autorizados los necesitan. Que un usuario no pueda acaparar todos los recursos del servidor ni que un fallo deje la empresa sin servicio.

Cada objetivo se protege con mecanismos distintos, y el SGBD es solo una de las capas:

Objetivo Amenazas típicas Mecanismos del SGBD Otros mecanismos
Confidencialidad Accesos no autorizados, robo de credenciales, empleados curiosos Autenticación, privilegios, roles, vistas, cifrado (TDE), redacción de datos Cifrado de red (TLS), control físico
Integridad Modificaciones malintencionadas o erróneas, inyección SQL Privilegios, restricciones de integridad (módulo 24), transacciones (módulo 35), auditoría Validación en la aplicación, consultas parametrizadas
Disponibilidad Consumo abusivo de recursos, borrados masivos, fallos de hardware Perfiles con límites de recursos, papelera, flashback, copias de seguridad Redundancia, réplicas, SAI
flowchart LR
    U["Usuario"] --> R["Red<br/>(TLS, cortafuegos)"]
    R --> A["Autenticación<br/>¿quién eres?"]
    A --> Z["Autorización<br/>¿qué puedes hacer?<br/>privilegios, roles"]
    Z --> V["Vistas<br/>¿qué parte puedes ver?"]
    V --> D[("Datos<br/>cifrado, restricciones")]
    Z -.-> AU["Auditoría<br/>¿qué has hecho?"]

📘 Concepto: El Lenguaje de Control de Datos (DCL, Data Control Language) es la parte de SQL que gestiona los permisos: GRANT (conceder) y REVOKE (revocar). En Oracle, las sentencias de gestión de usuarios, roles y perfiles (CREATE USER, CREATE ROLE, CREATE PROFILE…) son técnicamente DDL, pero se estudian siempre junto al DCL porque forman el sistema de seguridad. Todas ellas hacen commit implícito (módulo 25).

1.1. Autenticación y autorización

  Autenticación (authentication) Autorización (authorization)
Pregunta ¿Quién eres? ¿Qué puedes hacer?
Cuándo Al conectarse En cada sentencia
Mecanismo en Oracle Usuario y contraseña, sistema operativo, certificados, Kerberos, directorio LDAP, tokens de la nube Privilegios de sistema, de objeto y de esquema; roles
Error típico ORA-01017: invalid credential or not authorized; logon denied ORA-01031: insufficient privileges, ORA-00942

Analogía: en un hotel, la recepción comprueba tu DNI (autenticación) y te entrega una tarjeta que abre tu habitación y el gimnasio, pero no la cocina ni las otras habitaciones (autorización).

1.2. Usuarios y esquemas

📘 Concepto: En Oracle, un usuario es una cuenta con la que se conecta a la base de datos, y un esquema (schema) es el conjunto de objetos (tablas, vistas, secuencias…) que pertenecen a un usuario. Cada usuario tiene exactamente un esquema con su mismo nombre. Por eso escribimos dcl_empresa.empleados: la tabla empleados del esquema (usuario) dcl_empresa.

En una instalación profesional se separan dos tipos de cuentas:

  • Cuentas propietarias del esquema (schema owners): poseen las tablas. Nadie debería conectarse con ellas en el día a día; solo se usan para desplegar cambios.
  • Cuentas de usuario o de aplicación: no poseen tablas; acceden a las del propietario mediante privilegios concedidos.

⚠️ Importante: Para este módulo hemos creado un usuario DCL_EMPRESA que es propietario de una copia del esquema EMPRESA (cargado con los scripts empresa_crear.sql y empresa_datos.sql), y varios usuarios de prueba. Todos llevan el prefijo dcl_: en un servidor compartido (como el de un aula) es una buena práctica que permite localizar y borrar de golpe todo lo creado para unas pruebas. Las sentencias de administración las ejecuta el usuario SYSTEM; los cambios de usuario se hacen con el comando CONNECT de SQL*Plus.

⚠️ Importante: Las salidas se han obtenido en Oracle AI Database 26ai Free con SQLPlus (sesión con NLS_LANGUAGE='SPANISH', NLS_TERRITORY='SPAIN', NLS_DATE_FORMAT='DD/MM/YYYY' y SET NULL '(nulo)'). Los mensajes de SQLPlus se muestran en español y los errores ORA- en inglés, como los muestra la edición Free; omitimos la línea Help: https://docs.oracle.com/... que 23ai añade a cada error. Como verás, Oracle 23ai/26ai ha cambiado algunos mensajes de error de seguridad: lo indicaremos en cada caso.


2. Usuarios

2.1. CREATE USER

CREATE USER nombre IDENTIFIED BY "contraseña"
    [DEFAULT TABLESPACE ts]
    [TEMPORARY TABLESPACE ts_temporal]
    [QUOTA {n [K | M | G] | UNLIMITED} ON ts [QUOTA ... ON ...]]
    [PROFILE perfil]
    [PASSWORD EXPIRE]
    [ACCOUNT {LOCK | UNLOCK}];
Cláusula Significado Por defecto
IDENTIFIED BY Autenticación por contraseña (desde 11g distingue mayúsculas y minúsculas; entre comillas dobles admite caracteres especiales) —
IDENTIFIED EXTERNALLY / GLOBALLY Autenticación por el sistema operativo o por un servicio de directorio —
NO AUTHENTICATION Cuenta sin contraseña con la que no se puede conectar (18c): ideal para propietarios de esquema —
DEFAULT TABLESPACE Tablespace donde se crean sus objetos si no se indica otro El de la base de datos (USERS)
TEMPORARY TABLESPACE Tablespace para ordenaciones y tablas temporales El de la base de datos (TEMP)
QUOTA Espacio máximo que puede ocupar en un tablespace 0 (ninguno)
PROFILE Perfil de límites de recursos y contraseñas (sección 9) DEFAULT
PASSWORD EXPIRE Obliga a cambiar la contraseña en el primer inicio de sesión No
ACCOUNT LOCK Crea la cuenta bloqueada UNLOCK

📘 Concepto: Un tablespace (espacio de tablas) es una unidad lógica de almacenamiento formada por uno o varios ficheros de datos. Su gestión es materia de administración; aquí basta con saber que los objetos de un usuario ocupan espacio en un tablespace y que hace falta cuota para ello.

Creamos el propietario del esquema y le damos los privilegios para conectarse y crear objetos (la sentencia GRANT se explica en la sección 4):

-- Conectado como SYSTEM
CREATE USER dcl_empresa IDENTIFIED BY "Empresa_2026"
    DEFAULT TABLESPACE users
    TEMPORARY TABLESPACE temp
    QUOTA 50M ON users;

GRANT CREATE SESSION, CREATE TABLE, CREATE VIEW, CREATE SEQUENCE, CREATE SYNONYM TO dcl_empresa;

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
@empresa_crear.sql
@empresa_datos.sql
SELECT COUNT(*) FROM empleados;
Usuario creado.

Concesión terminada correctamente.

  COUNT(*)
----------
        24

1 fila seleccionada.

2.2. Sin CREATE SESSION no hay conexión

Un usuario recién creado no puede hacer nada, ni siquiera conectarse:

-- Conectado como SYSTEM
CREATE USER dcl_ana IDENTIFIED BY "Ana_2026"
    DEFAULT TABLESPACE users
    TEMPORARY TABLESPACE temp;

SELECT username, account_status, default_tablespace, temporary_tablespace, profile, created
FROM   dba_users
WHERE  username = 'DCL_ANA';

CONNECT dcl_ana/Ana_2026@FREEPDB1
Usuario creado.

USERNAME       ACCOUNT_STATUS   DEFAULT_TA TEMPORARY_ PROFILE    CREATED
-------------- ---------------- ---------- ---------- ---------- ----------
DCL_ANA        OPEN             USERS      TEMP       DEFAULT    04/10/2026

1 fila seleccionada.

ERROR:
ORA-01045: Login denied. User DCL_ANA does not have CREATE SESSION privilege.

Advertencia: Ya no está conectado a ORACLE.
-- Conectado como SYSTEM
GRANT CREATE SESSION TO dcl_ana;

CONNECT dcl_ana/Ana_2026@FREEPDB1
SELECT * FROM session_privs;
CREATE TABLE notas (id NUMBER, texto VARCHAR2(100));
Concesión terminada correctamente.

PRIVILEGE
----------------------
CREATE SESSION

1 fila seleccionada.

CREATE TABLE notas (id NUMBER, texto VARCHAR2(100))
*
ERROR en línea 1:
ORA-01031: insufficient privileges

SESSION_PRIVS muestra los privilegios de sistema activos en la sesión actual. Ana puede conectarse, pero no crear tablas.

2.3. Cuotas: crear una tabla no es lo mismo que poder llenarla

-- Conectado como SYSTEM
GRANT CREATE TABLE TO dcl_ana;

CONNECT dcl_ana/Ana_2026@FREEPDB1
CREATE TABLE notas (id NUMBER, texto VARCHAR2(100));
INSERT INTO notas VALUES (1, 'Primera nota');
SELECT tablespace_name, bytes, max_bytes FROM user_ts_quotas;
Concesión terminada correctamente.

Tabla creada.

INSERT INTO notas VALUES (1, 'Primera nota')
            *
ERROR en línea 1:
ORA-01950: The user DCL_ANA has insufficient quota on tablespace USERS.

ninguna fila seleccionada

¡La tabla se ha creado, pero no admite filas! Desde 11g Oracle usa la creación diferida de segmentos (deferred segment creation): CREATE TABLE solo registra la tabla en el diccionario, y el espacio se reserva con la primera fila. Es entonces cuando se comprueba la cuota, que es cero. Se la concedemos:

-- Conectado como SYSTEM
ALTER USER dcl_ana QUOTA 5M ON users;

CONNECT dcl_ana/Ana_2026@FREEPDB1
INSERT INTO notas VALUES (1, 'Primera nota');
COMMIT;
SELECT tablespace_name, bytes, max_bytes FROM user_ts_quotas;
SELECT username, default_tablespace, temporary_tablespace FROM user_users;
Usuario modificado.

1 fila creada.

Confirmación terminada.

TABLESPACE      BYTES  MAX_BYTES
---------- ---------- ----------
USERS           65536    5242880

1 fila seleccionada.

USERNAME       DEFAULT_TA TEMPORARY_
-------------- ---------- ----------
DCL_ANA        USERS      TEMP

1 fila seleccionada.

Ana ocupa 64 KB (el primer extent de la tabla) de sus 5 MB. USER_TS_QUOTAS muestra las cuotas propias (MAX_BYTES = -1 significa UNLIMITED).

💡 Consejo: El privilegio de sistema UNLIMITED TABLESPACE permite ocupar espacio sin límite en cualquier tablespace, incluido SYSTEM. No lo concedas a usuarios normales: usa cuotas explícitas.

2.4. ALTER USER

Con ALTER USER se cambia cualquier característica de una cuenta. Un usuario puede cambiar su propia contraseña (con REPLACE indicando la anterior, obligatorio si su perfil tiene función de verificación); el resto de cambios requieren el privilegio ALTER USER:

CONNECT dcl_ana/Ana_2026@FREEPDB1
ALTER USER dcl_ana IDENTIFIED BY "Ana_2027" REPLACE "Ana_2026";

CONNECT dcl_ana/Ana_2027@FREEPDB1
ALTER USER dcl_ana QUOTA UNLIMITED ON users;
Usuario modificado.

ALTER USER dcl_ana QUOTA UNLIMITED ON users
*
ERROR en línea 1:
ORA-01031: insufficient privileges

💡 Consejo: En SQL*Plus y SQLcl el comando PASSWORD cambia tu contraseña sin que se vea en pantalla ni quede en el historial de comandos. Es preferible a escribirla en un ALTER USER.

Cambio Sentencia
Nueva contraseña ALTER USER u IDENTIFIED BY "nueva";
Obligar a cambiarla ALTER USER u PASSWORD EXPIRE;
Bloquear / desbloquear ALTER USER u ACCOUNT LOCK; / ACCOUNT UNLOCK;
Cuotas ALTER USER u QUOTA 100M ON users; / QUOTA 0 ON users;
Tablespaces ALTER USER u DEFAULT TABLESPACE ts TEMPORARY TABLESPACE tmp;
Perfil ALTER USER u PROFILE p;
Roles por defecto ALTER USER u DEFAULT ROLE r1, r2; / ALL / ALL EXCEPT r / NONE

2.5. Cuentas caducadas y bloqueadas

-- Conectado como SYSTEM
CREATE USER dcl_luis IDENTIFIED BY "Luis_2026" PASSWORD EXPIRE ACCOUNT LOCK;
GRANT CREATE SESSION TO dcl_luis;

SELECT username, account_status, lock_date, expiry_date
FROM   dba_users
WHERE  username = 'DCL_LUIS';

CONNECT dcl_luis/Luis_2026@FREEPDB1
Usuario creado.

Concesión terminada correctamente.

USERNAME       ACCOUNT_STATUS   LOCK_DATE  EXPIRY_DATE
-------------- ---------------- ---------- -----------
DCL_LUIS       EXPIRED & LOCKED 04/10/2026 04/10/2026

1 fila seleccionada.

ERROR:
ORA-28000: The account is locked; login denied.

Advertencia: Ya no está conectado a ORACLE.

Al desbloquearla, la cuenta sigue caducada y SQL*Plus pide la nueva contraseña al conectarse:

-- Conectado como SYSTEM
ALTER USER dcl_luis ACCOUNT UNLOCK;
SELECT username, account_status FROM dba_users WHERE username = 'DCL_LUIS';

CONNECT dcl_luis/Luis_2026@FREEPDB1
Usuario modificado.

USERNAME       ACCOUNT_STATUS
-------------- ----------------
DCL_LUIS       EXPIRED

1 fila seleccionada.

ERROR:
ORA-28001: The account has expired. The password must be changed.

Changing password for dcl_luis
New password:
Retype new password:

(Los avisos de cambio de contraseña los muestra SQL*Plus en el idioma de su instalación.) Si el administrador asigna una contraseña nueva, la cuenta deja de estar caducada:

ALTER USER dcl_luis IDENTIFIED BY "Luis_2026";
SELECT username, account_status FROM dba_users WHERE username = 'DCL_LUIS';
Usuario modificado.

USERNAME       ACCOUNT_STATUS
-------------- ----------------
DCL_LUIS       OPEN

1 fila seleccionada.

💡 Consejo: PASSWORD EXPIRE es la forma correcta de dar de alta a una persona: el administrador le comunica una contraseña provisional y el usuario la cambia en su primer acceso, de modo que el administrador no conoce la definitiva.

2.6. DROP USER

DROP USER nombre [CASCADE];

Sin CASCADE, solo se puede borrar un usuario que no tiene objetos; si los tiene, Oracle exige CASCADE, que borra el usuario y todo su esquema (tablas, vistas, índices…). Las vistas y sinónimos de otros usuarios que dependían de esos objetos quedan inválidos. Así limpiamos al final de este módulo:

-- Conectado como SYSTEM
DROP USER dcl_ana CASCADE;
DROP USER dcl_empresa CASCADE;
Usuario borrado.

Usuario borrado.

⚠️ Importante: DROP USER ... CASCADE no pasa por la papelera y es irreversible. En producción, antes de borrar una cuenta se bloquea (ACCOUNT LOCK) durante un tiempo prudencial por si alguien la echa en falta.


3. Usuarios comunes y locales: la arquitectura multitenant

📘 Concepto: Desde Oracle 12c, una instalación puede ser multitenant: una base de datos contenedora (CDB, container database) con un contenedor raíz (CDB$ROOT) alberga varias bases de datos conectables (PDB, pluggable databases), cada una con sus propios usuarios y datos, como si fueran bases de datos independientes. Desde 21c es la única arquitectura soportada. En la edición Free, la PDB se llama FREEPDB1, y es a la que nos conectamos en todo el curso.

  • Un usuario local existe solo en una PDB. Es el caso normal de los usuarios de aplicación (todos los de este curso).
  • Un usuario común (common user) existe en la raíz y en todas las PDB. Se crea desde CDB$ROOT y su nombre debe empezar por C## (C##ADMIN). Se usa para administradores que gestionan todo el contenedor. SYS y SYSTEM son usuarios comunes predefinidos.

Desde una PDB, Oracle no permite crear usuarios comunes:

-- Conectado como SYSTEM a FREEPDB1
SHOW CON_NAME
CREATE USER c##prueba IDENTIFIED BY x;
CREATE USER prueba_comun CONTAINER = ALL IDENTIFIED BY x;
CON_NAME
------------------------------
FREEPDB1

CREATE USER c##prueba IDENTIFIED BY x
            *
ERROR en línea 1:
ORA-65094: invalid local user or role name

CREATE USER prueba_comun CONTAINER = ALL IDENTIFIED BY x
*
ERROR en línea 1:
ORA-65050: Common DDLs only allowed in root.

En una PDB, C## está reservado a los usuarios comunes; y CONTAINER = ALL solo puede usarse en la raíz. Lo mismo ocurre con los roles: los roles comunes empiezan por C## (salvo los predefinidos de Oracle, que son comunes sin prefijo).


4. Privilegios: conceptos

📘 Concepto: Un privilegio (privilege) es el derecho a ejecutar un tipo de sentencia o a acceder a un objeto. Oracle distingue:

  • Privilegios de sistema (system privileges): permiten realizar una acción en general (crear tablas en tu esquema, conectarse, consultar cualquier tabla de la base de datos…). Hay más de 200.
  • Privilegios de objeto (object privileges): permiten realizar una acción sobre un objeto concreto de otro usuario (consultar la tabla dcl_empresa.empleados).
  • Privilegios de esquema (schema privileges, 23ai): permiten una acción sobre todos los objetos de un esquema, presentes y futuros.

El propietario de un objeto tiene implícitamente todos los privilegios sobre él y es quien puede concederlos a otros. Los privilegios se conceden con GRANT y se retiran con REVOKE; pueden concederse a usuarios, a roles (sección 8) o a PUBLIC (todos los usuarios).


5. Privilegios de sistema

5.1. Los más importantes

Privilegio Permite
CREATE SESSION Conectarse a la base de datos
CREATE TABLE Crear tablas (e índices sobre ellas) en el propio esquema
CREATE VIEW Crear vistas en el propio esquema
CREATE SEQUENCE Crear secuencias en el propio esquema
CREATE SYNONYM Crear sinónimos privados
CREATE PUBLIC SYNONYM / DROP PUBLIC SYNONYM Crear y borrar sinónimos públicos
CREATE MATERIALIZED VIEW Crear vistas materializadas
CREATE PROCEDURE / CREATE TRIGGER Crear código PL/SQL (curso de PL/SQL)
CREATE ANY TABLE Crear tablas en cualquier esquema
ALTER ANY TABLE / DROP ANY TABLE Modificar o borrar tablas de cualquier esquema
SELECT ANY TABLE Consultar cualquier tabla o vista de cualquier esquema (salvo el diccionario)
INSERT ANY TABLE / UPDATE ANY TABLE / DELETE ANY TABLE Modificar datos de cualquier tabla
SELECT ANY DICTIONARY Consultar las vistas DBA_ y V$
CREATE USER / ALTER USER / DROP USER Gestionar usuarios
CREATE ROLE / DROP ANY ROLE / GRANT ANY ROLE Gestionar roles
CREATE PROFILE / ALTER PROFILE / DROP PROFILE Gestionar perfiles
GRANT ANY PRIVILEGE Conceder cualquier privilegio de sistema
GRANT ANY OBJECT PRIVILEGE Conceder privilegios sobre objetos de cualquier usuario
UNLIMITED TABLESPACE Ocupar espacio sin límite en cualquier tablespace
SYSDBA / SYSOPER Privilegios administrativos especiales (arrancar y parar la instancia…)

⚠️ Importante: Los privilegios con ANY son muy peligrosos: DROP ANY TABLE permite borrar las tablas de toda la empresa, y SELECT ANY TABLE leer las nóminas de cualquier aplicación. Solo deben tenerlos administradores, y aun así con cautela.

5.2. GRANT y REVOKE de privilegios de sistema

GRANT privilegio1 [, privilegio2 ...] TO {usuario | rol | PUBLIC} [, ...] [WITH ADMIN OPTION];
REVOKE privilegio1 [, privilegio2 ...] FROM {usuario | rol | PUBLIC} [, ...];

💻 Ejemplo: SELECT ANY TABLE en acción. Concedemos el privilegio a una usuaria de pruebas, dcl_eva (creada con CREATE USER dcl_eva IDENTIFIED BY "Eva_2026" QUOTA 5M ON users; y con CREATE SESSION), que no tiene ningún privilegio de objeto.

-- Conectado como SYSTEM
GRANT SELECT ANY TABLE TO dcl_eva;

CONNECT dcl_eva/Eva_2026@FREEPDB1
SELECT COUNT(*) FROM dcl_empresa.clientes;
SELECT COUNT(*) FROM curso.empleados;
SELECT COUNT(*) FROM sys.user$;
SELECT COUNT(*) FROM dba_users;
Concesión terminada correctamente.

  COUNT(*)
----------
        14

1 fila seleccionada.

  COUNT(*)
----------
        24

1 fila seleccionada.

SELECT COUNT(*) FROM sys.user$
                         *
ERROR en línea 1:
ORA-00942: table or view "SYS"."USER$" does not exist

SELECT COUNT(*) FROM dba_users
                     *
ERROR en línea 1:
ORA-00942: table or view "SYS"."DBA_USERS" does not exist

Eva puede leer los clientes de DCL_EMPRESA… y los empleados de otro esquema que no tiene nada que ver (CURSO). Lo único que la protege es que los privilegios ANY no dan acceso al diccionario de datos (SYS); para eso existe SELECT ANY DICTIONARY. Lo retiramos de inmediato:

REVOKE SELECT ANY TABLE FROM dcl_eva;
Revocación terminada correctamente.

5.3. WITH ADMIN OPTION

WITH ADMIN OPTION permite al receptor conceder a su vez el privilegio de sistema a otros (y revocarlo).

-- Conectado como SYSTEM
GRANT CREATE VIEW TO dcl_ana WITH ADMIN OPTION;

CONNECT dcl_ana/Ana_2027@FREEPDB1
GRANT CREATE VIEW TO dcl_luis;
GRANT CREATE TABLE TO dcl_luis;
Concesión terminada correctamente.

Concesión terminada correctamente.

GRANT CREATE TABLE TO dcl_luis
*
ERROR en línea 1:
ORA-01031: insufficient privileges

Ana puede conceder CREATE VIEW (lo tiene con ADMIN OPTION), pero no CREATE TABLE (lo tiene sin ella). ¿Qué pasa si ahora se le revoca CREATE VIEW a Ana?

-- Conectado como SYSTEM
SELECT grantee, privilege, admin_option
FROM   dba_sys_privs
WHERE  grantee IN ('DCL_ANA', 'DCL_LUIS')
ORDER BY grantee, privilege;

REVOKE CREATE VIEW FROM dcl_ana;

SELECT grantee, privilege, admin_option
FROM   dba_sys_privs
WHERE  grantee IN ('DCL_ANA', 'DCL_LUIS')
ORDER BY grantee, privilege;
GRANTEE      PRIVILEGE              ADM
------------ ---------------------- ---
DCL_ANA      CREATE SESSION         NO
DCL_ANA      CREATE SYNONYM         NO
DCL_ANA      CREATE TABLE           NO
DCL_ANA      CREATE VIEW            YES
DCL_LUIS     CREATE SESSION         NO
DCL_LUIS     CREATE VIEW            NO

6 filas seleccionadas.

Revocación terminada correctamente.

GRANTEE      PRIVILEGE              ADM
------------ ---------------------- ---
DCL_ANA      CREATE SESSION         NO
DCL_ANA      CREATE SYNONYM         NO
DCL_ANA      CREATE TABLE           NO
DCL_LUIS     CREATE SESSION         NO
DCL_LUIS     CREATE VIEW            NO

5 filas seleccionadas.

📘 Concepto: La revocación de un privilegio de sistema no se propaga en cascada: Luis conserva CREATE VIEW aunque se lo concediera Ana y Ana ya no lo tenga. Oracle no registra quién concedió un privilegio de sistema (DBA_SYS_PRIVS no tiene columna GRANTOR), así que no puede seguir la cadena.


6. Privilegios de objeto

6.1. Tipos

Privilegio Tabla Vista Secuencia Procedimiento/función/paquete Permite
SELECT ✓ ✓ ✓   Consultar (en secuencias: NEXTVAL/CURRVAL)
READ ✓ ✓     Consultar, pero sin SELECT ... FOR UPDATE (no bloquea filas)
INSERT ✓ ✓     Insertar filas (puede limitarse a columnas)
UPDATE ✓ ✓     Modificar filas (puede limitarse a columnas)
DELETE ✓ ✓     Borrar filas
REFERENCES ✓       Crear claves ajenas que apunten a la tabla (puede limitarse a columnas)
ALTER ✓   ✓   Modificar la estructura (ALTER TABLE, ALTER SEQUENCE)
INDEX ✓       Crear índices sobre la tabla
EXECUTE       ✓ Ejecutar código PL/SQL (Curso de PL/SQL)
ALL [PRIVILEGES] ✓ ✓ ✓ ✓ Todos los aplicables al objeto
GRANT {privilegio [(columnas)] [, ...] | ALL [PRIVILEGES]}
    ON [esquema.]objeto
    TO {usuario | rol | PUBLIC} [, ...]
    [WITH GRANT OPTION];

REVOKE {privilegio [, ...] | ALL [PRIVILEGES]}
    ON [esquema.]objeto
    FROM {usuario | rol | PUBLIC} [, ...]
    [CASCADE CONSTRAINTS];

6.2. Conceder y comprobar privilegios de objeto

Sin privilegios, Ana ni siquiera “ve” las tablas de DCL_EMPRESA:

CONNECT dcl_ana/Ana_2027@FREEPDB1
SELECT COUNT(*) FROM dcl_empresa.empleados;
SELECT COUNT(*) FROM dcl_empresa.empleados
                                 *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."EMPLEADOS" does not exist

📘 Concepto: Cuando un usuario no tiene ningún privilegio sobre un objeto, Oracle responde ORA-00942 (“no existe”) en lugar de “no tienes permiso”. Es deliberado: así no se revela la existencia de objetos a quien no debe conocerlos.

El propietario concede privilegios, incluido un UPDATE limitado a dos columnas:

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
GRANT SELECT ON empleados TO dcl_ana;
GRANT SELECT, INSERT ON departamentos TO dcl_ana;
GRANT UPDATE (telefono, email) ON empleados TO dcl_ana;
Concesión terminada correctamente.

Concesión terminada correctamente.

Concesión terminada correctamente.
CONNECT dcl_ana/Ana_2027@FREEPDB1
SELECT emp_id, nombre, apellidos, telefono FROM dcl_empresa.empleados WHERE dept_id = 40;
UPDATE dcl_empresa.empleados SET telefono = '600100131' WHERE emp_id = 131;
UPDATE dcl_empresa.empleados SET salario = 9999 WHERE emp_id = 131;
DELETE FROM dcl_empresa.empleados WHERE emp_id = 160;
INSERT INTO dcl_empresa.departamentos VALUES (80, 'CALIDAD', 'Málaga', 90000);
UPDATE dcl_empresa.departamentos SET presupuesto = 0;
SELECT COUNT(*) FROM dcl_empresa.clientes;
ROLLBACK;
    EMP_ID NOMBRE     APELLIDOS          TELEFONO
---------- ---------- ------------------ ----------
       103 Pedro      Gómez Díaz         600100103
       130 Isabel     Domínguez Vega     600100130
       131 Jorge      Vázquez Ramos      (nulo)

3 filas seleccionadas.

1 fila actualizada.

UPDATE dcl_empresa.empleados SET salario = 9999 WHERE emp_id = 131
                   *
ERROR en línea 1:
ORA-41900: missing UPDATE privilege on "DCL_EMPRESA"."EMPLEADOS"

DELETE FROM dcl_empresa.empleados WHERE emp_id = 160
                        *
ERROR en línea 1:
ORA-41900: missing DELETE privilege on "DCL_EMPRESA"."EMPLEADOS"

1 fila creada.

UPDATE dcl_empresa.departamentos SET presupuesto = 0
                   *
ERROR en línea 1:
ORA-41900: missing UPDATE privilege on "DCL_EMPRESA"."DEPARTAMENTOS"

SELECT COUNT(*) FROM dcl_empresa.clientes
                                 *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."CLIENTES" does not exist

Rollback terminado.

⚠️ Importante: Cuando el usuario sí tiene algún privilegio sobre el objeto (y por tanto sabe que existe) pero le falta el necesario, Oracle 19c y anteriores devuelven el genérico ORA-01031: insufficient privileges. Oracle 23ai/26ai devuelve el nuevo ORA-41900: missing <privilegio> privilege on "<esquema>"."<objeto>", mucho más informativo. Verás los dos en la documentación y en los foros; significan lo mismo. Para las operaciones que requieren privilegios de sistema (como CREATE TABLE sin el privilegio) sigue apareciendo ORA-01031.

6.3. Los privilegios en el diccionario

El receptor ve lo que ha recibido en USER_TAB_PRIVS_RECD (privilegios sobre objetos completos) y USER_COL_PRIVS_RECD (privilegios por columna):

SELECT owner, table_name, grantor, privilege, grantable
FROM   user_tab_privs_recd
ORDER BY table_name, privilege;

SELECT owner, table_name, column_name, privilege
FROM   user_col_privs_recd
ORDER BY column_name;
OWNER        TABLE_NAME     GRANTOR      PRIVILEGE    GRA
------------ -------------- ------------ ------------ ---
DCL_EMPRESA  DEPARTAMENTOS  DCL_EMPRESA  INSERT       NO
DCL_EMPRESA  DEPARTAMENTOS  DCL_EMPRESA  SELECT       NO
DCL_EMPRESA  EMPLEADOS      DCL_EMPRESA  SELECT       NO

3 filas seleccionadas.

OWNER        TABLE_NAME     COLUMN_NAME  PRIVILEGE
------------ -------------- ------------ ------------
DCL_EMPRESA  EMPLEADOS      EMAIL        UPDATE
DCL_EMPRESA  EMPLEADOS      TELEFONO     UPDATE

2 filas seleccionadas.

6.4. REFERENCES: claves ajenas hacia tablas de otro usuario

Para crear una clave ajena que apunte a una tabla de otro esquema no basta con SELECT: hace falta REFERENCES.

CONNECT dcl_ana/Ana_2027@FREEPDB1
CREATE TABLE reuniones (
    reunion_id  NUMBER     CONSTRAINT pk_reuniones PRIMARY KEY,
    dept_id     NUMBER(4)  CONSTRAINT fk_reu_dept REFERENCES dcl_empresa.departamentos (dept_id)
);
    dept_id     NUMBER(4)  CONSTRAINT fk_reu_dept REFERENCES dcl_empresa.departamentos (dept_id)
                                                                         *
ERROR en línea 3:
ORA-01031: insufficient privileges
CONNECT dcl_empresa/Empresa_2026@FREEPDB1
GRANT REFERENCES ON departamentos TO dcl_ana;

CONNECT dcl_ana/Ana_2027@FREEPDB1
CREATE TABLE reuniones (
    reunion_id  NUMBER     CONSTRAINT pk_reuniones PRIMARY KEY,
    dept_id     NUMBER(4)  CONSTRAINT fk_reu_dept REFERENCES dcl_empresa.departamentos (dept_id)
);
INSERT INTO reuniones VALUES (1, 99);
INSERT INTO reuniones VALUES (1, 20);
COMMIT;

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
DELETE FROM departamentos WHERE dept_id = 20;
Concesión terminada correctamente.

Tabla creada.

INSERT INTO reuniones VALUES (1, 99)
*
ERROR en línea 1:
ORA-02291: integrity constraint (DCL_ANA.FK_REU_DEPT) violated - parent key not found

1 fila creada.

Confirmación terminada.

DELETE FROM departamentos WHERE dept_id = 20
*
ERROR en línea 1:
ORA-02292: integrity constraint (DCL_ANA.FK_REU_DEPT) violated - child record found

⚠️ Importante: Fíjate en el último error: una tabla de Ana impide ahora al propietario borrar su propio departamento. REFERENCES concede a otro usuario la capacidad de restringir lo que el propietario puede hacer con sus datos. Por eso se concede con mucha prudencia.

El propietario puede consultar todo lo que ha concedido en USER_TAB_PRIVS_MADE y USER_COL_PRIVS_MADE:

SELECT grantee, table_name, privilege, grantable
FROM   user_tab_privs_made
ORDER BY grantee, table_name, privilege;
GRANTEE      TABLE_NAME     PRIVILEGE              GRA
------------ -------------- ---------------------- ---
DCL_ANA      DEPARTAMENTOS  INSERT                 NO
DCL_ANA      DEPARTAMENTOS  REFERENCES             NO
DCL_ANA      DEPARTAMENTOS  SELECT                 NO
DCL_ANA      EMPLEADOS      SELECT                 NO
PUBLIC       DCL_EMPRESA    INHERIT PRIVILEGES     NO

5 filas seleccionadas.

(INHERIT PRIVILEGES a PUBLIC lo concede Oracle automáticamente a cada usuario nuevo; tiene que ver con la ejecución de código PL/SQL con derechos del invocador y puedes ignorarlo.)

6.5. REVOKE de privilegios de objeto

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
REVOKE REFERENCES ON departamentos FROM dcl_ana;
REVOKE REFERENCES ON departamentos FROM dcl_ana CASCADE CONSTRAINTS;
REVOKE INSERT ON departamentos FROM dcl_ana;
REVOKE UPDATE (telefono) ON empleados FROM dcl_ana;
REVOKE UPDATE ON empleados FROM dcl_ana;
REVOKE ALL ON departamentos FROM dcl_ana;
REVOKE REFERENCES ON departamentos FROM dcl_ana
*
ERROR en línea 1:
ORA-01981: CASCADE CONSTRAINTS must be specified to perform this REVOKE.

Revocación terminada correctamente.

Revocación terminada correctamente.

REVOKE UPDATE (telefono) ON empleados FROM dcl_ana
              *
ERROR en línea 1:
ORA-01750: UPDATE/REFERENCES may only be REVOKEd from the whole table, not by column

Revocación terminada correctamente.

Revocación terminada correctamente.
  • Si el receptor ha usado REFERENCES para crear claves ajenas, hay que revocarlo con CASCADE CONSTRAINTS, que borra esas claves ajenas del esquema del receptor.
  • Los privilegios por columna se conceden por columnas, pero se revocan para toda la tabla.
  • REVOKE ALL retira todos los privilegios que el usuario tenga sobre el objeto (aquí, el SELECT restante).
CONNECT dcl_ana/Ana_2027@FREEPDB1
SELECT constraint_name, constraint_type FROM user_constraints WHERE table_name = 'REUNIONES';
SELECT owner, table_name, privilege FROM user_tab_privs_recd;
CONSTRAINT_NAME   C
----------------- -
PK_REUNIONES      P

1 fila seleccionada.

OWNER        TABLE_NAME     PRIVILEGE
------------ -------------- ------------
DCL_EMPRESA  EMPLEADOS      SELECT

1 fila seleccionada.

La clave ajena fk_reu_dept ha desaparecido de la tabla de Ana.

6.6. Un sinónimo no da permisos

Tras conceder a Ana el privilegio CREATE SYNONYM (como SYSTEM), Ana crea dos sinónimos:

CONNECT dcl_ana/Ana_2027@FREEPDB1
CREATE SYNONYM cli FOR dcl_empresa.clientes;
CREATE SYNONYM emp FOR dcl_empresa.empleados;
SELECT COUNT(*) FROM cli;
SELECT COUNT(*) FROM emp;
Sinónimo creado.

Sinónimo creado.

SELECT COUNT(*) FROM cli
                     *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."CLIENTES" does not exist

  COUNT(*)
----------
        24

1 fila seleccionada.

Ana puede crear un sinónimo hacia cualquier objeto (incluso uno al que no tiene acceso), pero al usarlo Oracle comprueba los privilegios sobre el objeto real: clientes sigue siendo inaccesible y empleados sí, porque conserva su SELECT.


7. WITH GRANT OPTION y la revocación en cascada

WITH GRANT OPTION permite al receptor de un privilegio de objeto concederlo a terceros. Montamos una cadena: DCL_EMPRESA → Ana → Luis → Eva.

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
GRANT SELECT ON productos TO dcl_ana WITH GRANT OPTION;

CONNECT dcl_ana/Ana_2027@FREEPDB1
GRANT SELECT ON dcl_empresa.productos TO dcl_luis WITH GRANT OPTION;
GRANT SELECT ON dcl_empresa.empleados TO dcl_luis;

CONNECT dcl_luis/Luis_2026@FREEPDB1
GRANT SELECT ON dcl_empresa.productos TO dcl_eva;
SELECT COUNT(*) FROM dcl_empresa.productos;
Concesión terminada correctamente.

Concesión terminada correctamente.

GRANT SELECT ON dcl_empresa.empleados TO dcl_luis
                            *
ERROR en línea 1:
ORA-01031: insufficient privileges

Concesión terminada correctamente.

  COUNT(*)
----------
        20

1 fila seleccionada.

Ana puede reenviar SELECT sobre productos (lo recibió con GRANT OPTION), pero no sobre empleados. A diferencia de los privilegios de sistema, Oracle sí registra quién concede cada privilegio de objeto (columna GRANTOR):

-- Conectado como SYSTEM
SELECT grantee, owner, table_name, grantor, privilege, grantable
FROM   dba_tab_privs
WHERE  owner = 'DCL_EMPRESA' AND table_name = 'PRODUCTOS'
ORDER BY grantee;
GRANTEE      OWNER        TABLE_NAME     GRANTOR      PRIVILEGE              GRA
------------ ------------ -------------- ------------ ---------------------- ---
DCL_ANA      DCL_EMPRESA  PRODUCTOS      DCL_EMPRESA  SELECT                 YES
DCL_EVA      DCL_EMPRESA  PRODUCTOS      DCL_LUIS     SELECT                 NO
DCL_LUIS     DCL_EMPRESA  PRODUCTOS      DCL_ANA      SELECT                 YES

3 filas seleccionadas.

Ahora el propietario revoca el privilegio a Ana:

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
REVOKE SELECT ON productos FROM dcl_ana;

-- Conectado como SYSTEM
SELECT grantee, grantor, privilege
FROM   dba_tab_privs
WHERE  owner = 'DCL_EMPRESA' AND table_name = 'PRODUCTOS';

CONNECT dcl_eva/Eva_2026@FREEPDB1
SELECT COUNT(*) FROM dcl_empresa.productos;
Revocación terminada correctamente.

ninguna fila seleccionada

SELECT COUNT(*) FROM dcl_empresa.productos
                                 *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."PRODUCTOS" does not exist

La revocación se ha propagado en cascada: al perder Ana el privilegio, lo pierden también Luis (que lo recibió de Ana) y Eva (que lo recibió de Luis).

flowchart LR
    subgraph OBJ["Privilegio de OBJETO con WITH GRANT OPTION"]
        direction LR
        E1["DCL_EMPRESA"] -->|GRANT| A1["ANA"] -->|GRANT| L1["LUIS"] -->|GRANT| V1["EVA"]
        E1 -. "REVOKE a ANA" .-> X1["ANA, LUIS y EVA<br/>lo pierden"]
    end
    subgraph SIS["Privilegio de SISTEMA con WITH ADMIN OPTION"]
        direction LR
        S2["SYSTEM"] -->|GRANT| A2["ANA"] -->|GRANT| L2["LUIS"]
        S2 -. "REVOKE a ANA" .-> X2["Solo ANA lo pierde;<br/>LUIS lo conserva"]
    end
  WITH ADMIN OPTION WITH GRANT OPTION
Se aplica a Privilegios de sistema y roles Privilegios de objeto
Permite Conceder y revocar el privilegio a cualquiera Conceder el privilegio (solo puede revocar lo que él mismo concedió)
¿Se registra quién concede? No Sí (GRANTOR)
Revocación al intermediario No se propaga en cascada Sí se propaga en cascada
¿Se puede conceder a PUBLIC o a un rol? Sí A PUBLIC sí; a un rol, WITH GRANT OPTION no

💡 Consejo: Evita WITH GRANT OPTION: los privilegios acaban repartidos por cadenas difíciles de auditar. Es mejor que sea siempre el propietario (o un administrador) quien conceda los privilegios, preferiblemente a través de roles.


8. Privilegios a PUBLIC y privilegios de esquema

8.1. PUBLIC

PUBLIC representa a todos los usuarios, presentes y futuros. Un privilegio concedido a PUBLIC lo tiene todo el mundo:

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
GRANT SELECT ON categorias TO PUBLIC;

CONNECT dcl_eva/Eva_2026@FREEPDB1
SELECT COUNT(*) FROM dcl_empresa.categorias;
SELECT * FROM session_privs;

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
REVOKE SELECT ON categorias FROM PUBLIC;
Concesión terminada correctamente.

  COUNT(*)
----------
        11

1 fila seleccionada.

PRIVILEGE
----------------------
CREATE SESSION

1 fila seleccionada.

Revocación terminada correctamente.

Eva lee las categorías sin tener ningún privilegio propio sobre la tabla.

⚠️ Importante: Conceder a PUBLIC es casi siempre un error de seguridad: cualquier cuenta nueva (incluida una creada por un atacante con algún privilegio de creación de usuarios) heredará el acceso. Oracle concede a PUBLIC ciertos privilegios de sistema (por ejemplo, la ejecución de algunos paquetes); las guías de bastionado (hardening) recomiendan revisarlos.

8.2. Privilegios de esquema (Oracle 23ai)

⚠️ Importante: Disponible desde Oracle 23ai.

Antes de 23ai, para que un usuario pudiera consultar todas las tablas de un esquema había dos opciones malas: conceder SELECT tabla a tabla (y acordarse de hacerlo con cada tabla nueva) o conceder SELECT ANY TABLE (acceso a toda la base de datos). Los privilegios de esquema resuelven el problema: se conceden sobre un esquema concreto y se aplican a todos sus objetos, incluidos los que se creen en el futuro.

GRANT privilegio_de_sistema ON SCHEMA esquema TO {usuario | rol};
REVOKE privilegio_de_sistema ON SCHEMA esquema FROM {usuario | rol};

Pueden concederlos el propio propietario del esquema o un usuario con GRANT ANY SCHEMA PRIVILEGE:

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
GRANT SELECT ANY TABLE ON SCHEMA dcl_empresa TO dcl_eva;

-- Conectado como SYSTEM
SELECT grantee, privilege, schema, admin_option
FROM   dba_schema_privs
WHERE  grantee LIKE 'DCL%';
Concesión terminada correctamente.

GRANTEE      PRIVILEGE              SCHEMA       ADM
------------ ---------------------- ------------ ---
DCL_EVA      SELECT ANY TABLE       DCL_EMPRESA  NO

1 fila seleccionada.
CONNECT dcl_eva/Eva_2026@FREEPDB1
SELECT COUNT(*) FROM dcl_empresa.clientes;
SELECT COUNT(*) FROM curso.empleados;
SELECT privilege, schema FROM user_schema_privs;
INSERT INTO dcl_empresa.categorias VALUES (12, 'Gaming', 1);
  COUNT(*)
----------
        14

1 fila seleccionada.

SELECT COUNT(*) FROM curso.empleados
                          *
ERROR en línea 1:
ORA-00942: table or view "CURSO"."EMPLEADOS" does not exist

PRIVILEGE              SCHEMA
---------------------- ------------
SELECT ANY TABLE       DCL_EMPRESA

1 fila seleccionada.

INSERT INTO dcl_empresa.categorias VALUES (12, 'Gaming', 1)
                        *
ERROR en línea 1:
ORA-41900: missing INSERT privilege on "DCL_EMPRESA"."CATEGORIAS"

Ahora Eva lee todo DCL_EMPRESA, pero nada de CURSO. Y lo mejor: las tablas nuevas quedan cubiertas automáticamente.

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
CREATE TABLE avisos (id NUMBER, texto VARCHAR2(50));
INSERT INTO avisos VALUES (1, 'Tabla creada después del GRANT');
COMMIT;

CONNECT dcl_eva/Eva_2026@FREEPDB1
SELECT * FROM dcl_empresa.avisos;

-- Conectado como SYSTEM
REVOKE SELECT ANY TABLE ON SCHEMA dcl_empresa FROM dcl_eva;

CONNECT dcl_eva/Eva_2026@FREEPDB1
SELECT * FROM dcl_empresa.avisos;
Tabla creada.

1 fila creada.

Confirmación terminada.

        ID TEXTO
---------- --------------------------------------------------
         1 Tabla creada después del GRANT

1 fila seleccionada.

Revocación terminada correctamente.

SELECT * FROM dcl_empresa.avisos
                          *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."AVISOS" does not exist

Los privilegios de esquema se consultan en DBA_SCHEMA_PRIVS, USER_SCHEMA_PRIVS, ROLE_SCHEMA_PRIVS y SESSION_SCHEMA_PRIVS. Admiten la mayoría de los privilegios ANY aplicables a objetos de un esquema: SELECT ANY TABLE, INSERT ANY TABLE, UPDATE ANY TABLE, DELETE ANY TABLE, CREATE ANY TABLE, CREATE ANY VIEW, EXECUTE ANY PROCEDURE, SELECT ANY SEQUENCE…

Alternativa clásica (19c/21c): conceder los privilegios de objeto tabla a tabla (normalmente a un rol) e incluir los GRANT en el script de creación de cada tabla nueva.


9. Roles

9.1. Concepto

📘 Concepto: Un rol (role) es un conjunto de privilegios con nombre que se concede a usuarios (o a otros roles) como si fuera un único privilegio. En lugar de conceder los mismos quince privilegios a cada uno de los veinte comerciales, se crea un rol ventas, se le conceden los quince privilegios y se concede el rol a los veinte comerciales. Cuando cambian las necesidades, se modifica el rol y el cambio afecta a todos sus miembros a la vez.

flowchart LR
    P1["SELECT ON departamentos"] --> R["Rol<br/>DCL_ROL_LECTURA"]
    P2["SELECT ON productos"] --> R
    P3["SELECT ON categorias"] --> R
    P4["CREATE SESSION"] --> R
    R --> U1["DCL_PABLO"]
    R --> U2["otro usuario"]
    R --> U3["otro rol"]

Ventajas: administración más sencilla, menos errores, coherencia (todos los miembros de un perfil tienen exactamente los mismos permisos), activación selectiva (los roles pueden activarse y desactivarse en una sesión) y documentación (el nombre del rol explica el porqué de los privilegios).

9.2. Crear roles, darles privilegios y asignarlos

CREATE ROLE nombre [NOT IDENTIFIED | IDENTIFIED BY "contraseña"];
GRANT privilegios TO rol;
GRANT rol TO {usuario | rol | PUBLIC} [WITH ADMIN OPTION];
REVOKE rol FROM {usuario | rol | PUBLIC};
DROP ROLE rol;
-- Conectado como SYSTEM
CREATE ROLE dcl_rol_lectura;
GRANT CREATE SESSION TO dcl_rol_lectura;

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
GRANT SELECT ON departamentos TO dcl_rol_lectura;
GRANT SELECT ON productos     TO dcl_rol_lectura;
GRANT SELECT ON categorias    TO dcl_rol_lectura;

-- Conectado como SYSTEM
CREATE USER dcl_pablo IDENTIFIED BY "Pablo_2026";
GRANT dcl_rol_lectura TO dcl_pablo;

SELECT grantee, granted_role, admin_option, default_role
FROM   dba_role_privs
WHERE  grantee = 'DCL_PABLO';

SELECT role, privilege FROM role_sys_privs WHERE role = 'DCL_ROL_LECTURA';

SELECT role, owner, table_name, privilege
FROM   role_tab_privs
WHERE  role = 'DCL_ROL_LECTURA'
ORDER BY table_name;
Rol creado.

Concesión terminada correctamente.

Concesión terminada correctamente.

Concesión terminada correctamente.

Concesión terminada correctamente.

Usuario creado.

Concesión terminada correctamente.

GRANTEE            GRANTED_ROLE         ADM DEF
------------------ -------------------- --- ---
DCL_PABLO          DCL_ROL_LECTURA      NO  YES

1 fila seleccionada.

ROLE                 PRIVILEGE
-------------------- ------------------------
DCL_ROL_LECTURA      CREATE SESSION

1 fila seleccionada.

ROLE                 OWNER        TABLE_NAME     PRIVILEGE
-------------------- ------------ -------------- ------------------------
DCL_ROL_LECTURA      DCL_EMPRESA  CATEGORIAS     SELECT
DCL_ROL_LECTURA      DCL_EMPRESA  DEPARTAMENTOS  SELECT
DCL_ROL_LECTURA      DCL_EMPRESA  PRODUCTOS      SELECT

3 filas seleccionadas.

Pablo no ha recibido ningún privilegio directamente, pero puede conectarse (CREATE SESSION le llega por el rol) y consultar las tres tablas:

CONNECT dcl_pablo/Pablo_2026@FREEPDB1
SELECT COUNT(*) FROM dcl_empresa.productos;
SELECT * FROM session_roles;
SELECT * FROM session_privs;
SELECT username, granted_role, default_role FROM user_role_privs;
  COUNT(*)
----------
        20

1 fila seleccionada.

ROLE
--------------------
DCL_ROL_LECTURA

1 fila seleccionada.

PRIVILEGE
------------------------
CREATE SESSION

1 fila seleccionada.

USERNAME     GRANTED_ROLE         DEF
------------ -------------------- ---
DCL_PABLO    DCL_ROL_LECTURA      YES

1 fila seleccionada.

Si se concede un privilegio nuevo al rol, todos sus miembros lo reciben al instante:

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
GRANT SELECT ON proyectos TO dcl_rol_lectura;

CONNECT dcl_pablo/Pablo_2026@FREEPDB1
SELECT COUNT(*) FROM dcl_empresa.proyectos;
Concesión terminada correctamente.

  COUNT(*)
----------
         5

1 fila seleccionada.

9.3. Una limitación importante: los roles y las vistas

Los privilegios recibidos a través de un rol no sirven para crear objetos que dependen de ellos, como vistas (ni procedimientos PL/SQL). Oracle exige que el privilegio sobre la tabla base se haya concedido directamente al usuario:

-- Conectado como SYSTEM
GRANT CREATE VIEW TO dcl_pablo;

CONNECT dcl_pablo/Pablo_2026@FREEPDB1
CREATE VIEW v_productos AS SELECT producto_id, nombre FROM dcl_empresa.productos;

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
GRANT SELECT ON productos TO dcl_pablo;

CONNECT dcl_pablo/Pablo_2026@FREEPDB1
CREATE VIEW v_productos AS SELECT producto_id, nombre FROM dcl_empresa.productos;
SELECT COUNT(*) FROM v_productos;
DROP VIEW v_productos;
Concesión terminada correctamente.

CREATE VIEW v_productos AS SELECT producto_id, nombre FROM dcl_empresa.productos
                                                                       *
ERROR en línea 1:
ORA-41904: Missing object privilege on "DCL_EMPRESA"."PRODUCTOS" to create a view.

Concesión terminada correctamente.

Vista creada.

  COUNT(*)
----------
        20

1 fila seleccionada.

Vista borrada.

⚠️ Importante: En Oracle 19c el primer CREATE VIEW falla con ORA-01031: insufficient privileges; 23ai/26ai devuelve el más explícito ORA-41904. La razón es que los roles pueden activarse y desactivarse en cada sesión, mientras que una vista existe permanentemente: Oracle necesita un privilegio que no dependa del estado de la sesión. Si además otros usuarios van a consultar esa vista, el creador necesita el privilegio sobre la tabla base WITH GRANT OPTION.

9.4. Roles por defecto, SET ROLE y roles con contraseña

Un usuario puede tener varios roles concedidos, pero solo los activos en la sesión le dan privilegios. Al conectarse se activan sus roles por defecto (default roles); los demás se activan con SET ROLE. Un rol puede protegerse con contraseña, de modo que solo pueda activarse conociéndola (útil para que una aplicación active un rol que el usuario no puede usar desde otra herramienta).

-- Conectado como SYSTEM
CREATE ROLE dcl_rol_escritura IDENTIFIED BY "Clave_Rol_2026";

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
GRANT INSERT, UPDATE ON productos TO dcl_rol_escritura;

-- Conectado como SYSTEM
GRANT dcl_rol_escritura TO dcl_pablo;
ALTER USER dcl_pablo DEFAULT ROLE dcl_rol_lectura;

SELECT grantee, granted_role, default_role
FROM   dba_role_privs
WHERE  grantee = 'DCL_PABLO'
ORDER BY granted_role;
Rol creado.

Concesión terminada correctamente.

Concesión terminada correctamente.

Usuario modificado.

GRANTEE            GRANTED_ROLE         DEF
------------------ -------------------- ---
DCL_PABLO          DCL_ROL_ESCRITURA    NO
DCL_PABLO          DCL_ROL_LECTURA      YES

2 filas seleccionadas.
CONNECT dcl_pablo/Pablo_2026@FREEPDB1
SELECT * FROM session_roles;
UPDATE dcl_empresa.productos SET stock = stock + 1 WHERE producto_id = 8;

SET ROLE dcl_rol_escritura;
SET ROLE dcl_rol_escritura IDENTIFIED BY "Clave_Rol_2026";
SELECT * FROM session_roles;
SELECT COUNT(*) FROM dcl_empresa.departamentos;
ROLE
--------------------
DCL_ROL_LECTURA

1 fila seleccionada.

UPDATE dcl_empresa.productos SET stock = stock + 1 WHERE producto_id = 8
                   *
ERROR en línea 1:
ORA-41900: missing UPDATE privilege on "DCL_EMPRESA"."PRODUCTOS"

SET ROLE dcl_rol_escritura
*
ERROR en línea 1:
ORA-01979: missing or invalid password for role "DCL_ROL_ESCRITURA"

Rol definido.

ROLE
--------------------
DCL_ROL_ESCRITURA

1 fila seleccionada.

SELECT COUNT(*) FROM dcl_empresa.departamentos
                                 *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."DEPARTAMENTOS" does not exist

⚠️ Importante: SET ROLE sustituye el conjunto de roles activos: al activar dcl_rol_escritura, Pablo ha desactivado dcl_rol_lectura y ya no ve departamentos. Para tener varios, hay que enumerarlos todos:

SET ROLE dcl_rol_lectura, dcl_rol_escritura IDENTIFIED BY "Clave_Rol_2026";
UPDATE dcl_empresa.productos SET stock = stock + 1 WHERE producto_id = 8;
ROLLBACK;
SELECT * FROM session_roles;

SET ROLE NONE;
SELECT * FROM session_roles;
SELECT COUNT(*) FROM dcl_empresa.departamentos;
SELECT COUNT(*) FROM dcl_empresa.productos;
Rol definido.

1 fila actualizada.

Rollback terminado.

ROLE
--------------------
DCL_ROL_ESCRITURA
DCL_ROL_LECTURA

2 filas seleccionadas.

Rol definido.

ninguna fila seleccionada

SELECT COUNT(*) FROM dcl_empresa.departamentos
                                 *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."DEPARTAMENTOS" does not exist

  COUNT(*)
----------
        20

1 fila seleccionada.

Con SET ROLE NONE no queda ningún rol activo, pero Pablo sigue leyendo productos: ese privilegio lo recibió directamente en la sección 9.3, y los privilegios directos están siempre activos. Las variantes de SET ROLE son: SET ROLE r1, r2, SET ROLE ALL, SET ROLE ALL EXCEPT r y SET ROLE NONE.

9.5. Revocar y borrar roles

-- Conectado como SYSTEM
REVOKE dcl_rol_lectura FROM dcl_pablo;
DROP ROLE dcl_rol_escritura;
SELECT grantee, granted_role FROM dba_role_privs WHERE grantee = 'DCL_PABLO';

CONNECT dcl_pablo/Pablo_2026@FREEPDB1
Revocación terminada correctamente.

Rol borrado.

ninguna fila seleccionada

ERROR:
ORA-01045: Login denied. User DCL_PABLO does not have CREATE SESSION privilege.

Advertencia: Ya no está conectado a ORACLE.

Al perder el rol, Pablo pierde también CREATE SESSION, que solo tenía a través de él. DROP ROLE retira el rol de todos los usuarios y roles que lo tuvieran.

9.6. Roles predefinidos

Oracle incluye decenas de roles predefinidos. Los más conocidos:

-- Conectado como SYSTEM
SELECT grantee, privilege
FROM   dba_sys_privs
WHERE  grantee IN ('CONNECT', 'RESOURCE')
ORDER BY grantee, privilege;

SELECT COUNT(*) AS privilegios_dba FROM dba_sys_privs WHERE grantee = 'DBA';
GRANTEE            PRIVILEGE
------------------ ------------------------------
CONNECT            CREATE SESSION
CONNECT            SET CONTAINER
RESOURCE           CREATE ANALYTIC VIEW
RESOURCE           CREATE ATTRIBUTE DIMENSION
RESOURCE           CREATE CLUSTER
RESOURCE           CREATE HIERARCHY
RESOURCE           CREATE INDEXTYPE
RESOURCE           CREATE MATERIALIZED VIEW
...

17 filas seleccionadas.

PRIVILEGIOS_DBA
---------------
            287

1 fila seleccionada.
Rol Contenido Comentario
CONNECT CREATE SESSION y SET CONTAINER Desde 10g solo permite conectarse (antes incluía crear tablas, vistas…)
RESOURCE CREATE TABLE, CREATE VIEW, CREATE SEQUENCE, CREATE SYNONYM, CREATE PROCEDURE, CREATE TRIGGER, CREATE TYPE, CREATE MATERIALIZED VIEW, CREATE CLUSTER… (15 privilegios en 26ai; en versiones anteriores tenía menos: compruébalo en la tuya con DBA_SYS_PRIVS) Para desarrolladores; ya no incluye UNLIMITED TABLESPACE (desde 12c)
DBA 287 privilegios de sistema (en 26ai), casi todos con ADMIN OPTION Administración completa. Nunca a usuarios normales
SELECT_CATALOG_ROLE Consulta de las vistas DBA_ y V$ Para personal de soporte o monitorización
DB_DEVELOPER_ROLE Ver tabla siguiente Disponible desde Oracle 23ai.

⚠️ Importante: Disponible desde Oracle 23ai. DB_DEVELOPER_ROLE reúne lo que necesita un desarrollador de aplicaciones sin darle privilegios de administración. En Oracle 26ai incluye 14 privilegios de sistema, entre ellos CREATE SESSION, CREATE DOMAIN, CREATE ASSERTION, CREATE JOB, CREATE MLE, CREATE CUBE, CREATE DIMENSION, CREATE MINING MODEL, DEBUG CONNECT SESSION, ON COMMIT REFRESH, FORCE TRANSACTION, y los roles RESOURCE y CTXAPP (es el rol con el que trabaja el usuario CURSO de nuestras pruebas). En 19c, la alternativa clásica es conceder CONNECT + RESOURCE + los privilegios concretos necesarios.

💡 Consejo: No concedas CONNECT y RESOURCE por costumbre (es lo que se hacía en versiones antiguas). Crea tus propios roles con exactamente los privilegios necesarios.


10. Perfiles

📘 Concepto: Un perfil (profile) es un conjunto con nombre de límites de recursos y de reglas de contraseñas que se asigna a usuarios. Contribuye a la disponibilidad (impide que un usuario monopolice el servidor) y a la confidencialidad (contraseñas que caducan, bloqueo tras intentos fallidos). Todo usuario tiene un perfil; si no se indica, el perfil DEFAULT.

10.1. Parámetros

Límites de recursos (tipo KERNEL). Solo se aplican si el parámetro de inicialización RESOURCE_LIMIT es TRUE (lo es por defecto desde 12c):

Parámetro Límite
SESSIONS_PER_USER Sesiones simultáneas por usuario
CPU_PER_SESSION / CPU_PER_CALL Tiempo de CPU por sesión o por llamada (centésimas de segundo)
CONNECT_TIME Duración máxima de una sesión (minutos)
IDLE_TIME Tiempo máximo de inactividad (minutos)
LOGICAL_READS_PER_SESSION / LOGICAL_READS_PER_CALL Bloques leídos
PRIVATE_SGA Memoria privada en servidor compartido
COMPOSITE_LIMIT Coste ponderado de varios recursos

Parámetros de contraseña (tipo PASSWORD):

Parámetro Significado DEFAULT en 26ai
FAILED_LOGIN_ATTEMPTS Intentos fallidos antes de bloquear la cuenta 10
PASSWORD_LOCK_TIME Días que dura el bloqueo (admite fracciones: 1/24 = una hora) 1
PASSWORD_LIFE_TIME Días de validez de la contraseña 180
PASSWORD_GRACE_TIME Días de gracia tras caducar, con avisos 7
PASSWORD_REUSE_TIME / PASSWORD_REUSE_MAX Días y número de cambios antes de poder repetir una contraseña UNLIMITED
PASSWORD_VERIFY_FUNCTION Función que valida la complejidad (Oracle incluye, por ejemplo, ORA12C_VERIFY_FUNCTION; escribir una propia es PL/SQL) NULL
INACTIVE_ACCOUNT_TIME Días sin conectarse tras los que la cuenta se bloquea (18c) 365
PASSWORD_ROLLOVER_TIME Días en que la contraseña antigua y la nueva valen a la vez (21c) 0

Cada parámetro admite un valor, UNLIMITED o DEFAULT (tomar el del perfil DEFAULT).

10.2. Crear, asignar y modificar perfiles

-- Conectado como SYSTEM
CREATE PROFILE dcl_perfil_alumnos LIMIT
    SESSIONS_PER_USER        1
    IDLE_TIME                30
    CONNECT_TIME             240
    FAILED_LOGIN_ATTEMPTS    3
    PASSWORD_LOCK_TIME       1/1440
    PASSWORD_LIFE_TIME       90
    PASSWORD_GRACE_TIME      7
    PASSWORD_REUSE_MAX       5
    PASSWORD_REUSE_TIME      365;

SELECT profile, resource_name, resource_type, limit
FROM   dba_profiles
WHERE  profile = 'DCL_PERFIL_ALUMNOS' AND limit <> 'DEFAULT'
ORDER BY resource_type, resource_name;
Perfil creado.

PROFILE              RESOURCE_NAME            RESOURCE_ LIMIT
-------------------- ------------------------ --------- ------------
DCL_PERFIL_ALUMNOS   CONNECT_TIME             KERNEL    240
DCL_PERFIL_ALUMNOS   IDLE_TIME                KERNEL    30
DCL_PERFIL_ALUMNOS   SESSIONS_PER_USER        KERNEL    1
DCL_PERFIL_ALUMNOS   FAILED_LOGIN_ATTEMPTS    PASSWORD  3
DCL_PERFIL_ALUMNOS   PASSWORD_GRACE_TIME      PASSWORD  7
DCL_PERFIL_ALUMNOS   PASSWORD_LIFE_TIME       PASSWORD  90
DCL_PERFIL_ALUMNOS   PASSWORD_LOCK_TIME       PASSWORD  ,0006
DCL_PERFIL_ALUMNOS   PASSWORD_REUSE_MAX       PASSWORD  5
DCL_PERFIL_ALUMNOS   PASSWORD_REUSE_TIME      PASSWORD  365

9 filas seleccionadas.

Los parámetros que no se indican quedan como DEFAULT. PASSWORD_LOCK_TIME de 1/1440 días es un minuto (para poder probarlo).

ALTER USER dcl_eva PROFILE dcl_perfil_alumnos;

SELECT username, profile, account_status, expiry_date
FROM   dba_users
WHERE  username = 'DCL_EVA';

ALTER PROFILE dcl_perfil_alumnos LIMIT PASSWORD_LIFE_TIME 60;
Usuario modificado.

USERNAME     PROFILE              ACCOUNT_STATUS   EXPIRY_DATE
------------ -------------------- ---------------- ----------------
DCL_EVA      DCL_PERFIL_ALUMNOS   OPEN             02/01/2027 05:43

1 fila seleccionada.

Perfil modificado.

La fecha de caducidad se calcula a partir del último cambio de contraseña de Eva y la vida del perfil.

10.3. Comprobar los límites

Sesiones simultáneas: con SESSIONS_PER_USER 1, mientras Eva tiene una sesión abierta, un segundo intento de conexión falla:

ERROR:
ORA-02391: exceeded simultaneous SESSIONS_PER_USER limit

Intentos fallidos: el bloqueo tras tres contraseñas erróneas lo verás en el ejercicio resuelto 6.

10.4. Borrar perfiles

DROP PROFILE dcl_perfil_alumnos;
DROP PROFILE dcl_perfil_alumnos CASCADE;
DROP PROFILE dcl_perfil_alumnos
*
ERROR en línea 1:
ORA-02382: profile DCL_PERFIL_ALUMNOS has users assigned, cannot drop without CASCADE

Perfil borrado.

Con CASCADE, los usuarios que lo tenían pasan al perfil DEFAULT. El perfil DEFAULT no se puede borrar, pero sí modificar (con lo que cambia para todos los usuarios que no tienen otro).


11. Consultar los privilegios en el diccionario de datos

Vista Contenido
USER_SYS_PRIVS Privilegios de sistema concedidos directamente al usuario
USER_TAB_PRIVS Privilegios de objeto en los que el usuario es propietario, concedente o receptor
USER_TAB_PRIVS_MADE / USER_TAB_PRIVS_RECD Privilegios de objeto concedidos por el usuario / recibidos por él
USER_COL_PRIVS_MADE / USER_COL_PRIVS_RECD Ídem, por columnas
USER_ROLE_PRIVS Roles concedidos al usuario (con DEFAULT_ROLE y ADMIN_OPTION)
USER_SCHEMA_PRIVS Privilegios de esquema (23ai)
ROLE_SYS_PRIVS Privilegios de sistema de los roles a los que tienes acceso
ROLE_TAB_PRIVS Privilegios de objeto de los roles a los que tienes acceso
ROLE_ROLE_PRIVS Roles concedidos a roles
SESSION_PRIVS Privilegios de sistema activos ahora en tu sesión (directos + de roles activos)
SESSION_ROLES Roles activos ahora en tu sesión
ALL_TAB_PRIVS Privilegios de objeto en los que participas tú o PUBLIC o un rol activo
DBA_USERS Todos los usuarios: estado, tablespaces, perfil, fechas
DBA_SYS_PRIVS, DBA_TAB_PRIVS, DBA_COL_PRIVS, DBA_ROLE_PRIVS, DBA_SCHEMA_PRIVS Todas las concesiones de la base de datos
DBA_ROLES, DBA_PROFILES, DBA_TS_QUOTAS Roles, perfiles y cuotas

Lo que ve una comercial (dcl_carmen, del caso de la sección 15) sobre sí misma:

CONNECT dcl_carmen/Carmen_2026@FREEPDB1
SELECT * FROM user_sys_privs;
SELECT username, granted_role, admin_option, default_role FROM user_role_privs;
SELECT * FROM session_roles ORDER BY role;
SELECT * FROM session_privs;
SELECT role, privilege, admin_option FROM role_sys_privs ORDER BY role;
SELECT role, table_name, column_name, privilege
FROM   role_tab_privs
ORDER BY role, table_name, privilege;
ninguna fila seleccionada

USERNAME     GRANTED_ROLE       ADM DEF
------------ ------------------ --- ---
DCL_CARMEN   DCL_ROL_VENTAS     NO  YES

1 fila seleccionada.

ROLE
------------------
DCL_ROL_CONSULTA
DCL_ROL_VENTAS

2 filas seleccionadas.

PRIVILEGE
----------------
CREATE SESSION

1 fila seleccionada.

ROLE               PRIVILEGE        ADM
------------------ ---------------- ---
DCL_ROL_CONSULTA   CREATE SESSION   NO

1 fila seleccionada.

ROLE               TABLE_NAME           COLUMN_NAME  PRIVILEGE
------------------ -------------------- ------------ ----------------
DCL_ROL_CONSULTA   CATEGORIAS           (nulo)       SELECT
DCL_ROL_CONSULTA   DEPARTAMENTOS        (nulo)       SELECT
DCL_ROL_CONSULTA   PRODUCTOS            (nulo)       SELECT
DCL_ROL_CONSULTA   PROYECTOS            (nulo)       SELECT
DCL_ROL_CONSULTA   V_EMPLEADOS_PUBLICO  (nulo)       SELECT
DCL_ROL_VENTAS     LINEAS_PEDIDO        (nulo)       DELETE
DCL_ROL_VENTAS     LINEAS_PEDIDO        (nulo)       INSERT
DCL_ROL_VENTAS     LINEAS_PEDIDO        (nulo)       SELECT
...

16 filas seleccionadas.

Carmen no tiene ningún privilegio directo (USER_SYS_PRIVS vacía). Tiene un rol (DCL_ROL_VENTAS), pero dos roles activos: DCL_ROL_VENTAS contiene a su vez DCL_ROL_CONSULTA. Las vistas ROLE_ muestran los privilegios de ambos.

Para ver un panorama completo de un usuario, un administrador consulta DBA_USERS:

-- Conectado como SYSTEM
SELECT username, account_status, default_tablespace, temporary_tablespace, profile
FROM   dba_users
WHERE  username LIKE 'DCL%'
ORDER BY username;
USERNAME     ACCOUNT_STATUS DEFAULT_ TEMPORAR PROFILE
------------ -------------- -------- -------- ------------------
DCL_ANA      OPEN           USERS    TEMP     DEFAULT
DCL_CARMEN   OPEN           USERS    TEMP     DCL_PERFIL_EMPRESA
DCL_EMPRESA  OPEN           USERS    TEMP     DEFAULT
DCL_EVA      OPEN           USERS    TEMP     DCL_PERFIL_ALUMNOS
DCL_JORGE    OPEN           USERS    TEMP     DCL_PERFIL_EMPRESA
DCL_LUIS     OPEN           USERS    TEMP     DEFAULT
DCL_MARTA    OPEN           USERS    TEMP     DCL_PERFIL_EMPRESA
DCL_PABLO    OPEN           USERS    TEMP     DEFAULT

8 filas seleccionadas.

Los valores habituales de ACCOUNT_STATUS son OPEN, LOCKED, LOCKED(TIMED) (bloqueo temporal por intentos fallidos), EXPIRED, EXPIRED(GRACE) y sus combinaciones (EXPIRED & LOCKED).


12. Las vistas como mecanismo de seguridad

Los privilegios de objeto actúan sobre tablas completas: si concedes SELECT sobre empleados, se ven todas las filas y todas las columnas (incluido el salario). Las vistas (módulo 26) permiten afinar:

  • Seguridad por columnas: la vista solo incluye las columnas permitidas.
  • Seguridad por filas: el WHERE de la vista filtra las filas permitidas, con WITH CHECK OPTION para que tampoco se puedan insertar o modificar filas fuera del ámbito.
  • Seguridad por usuario: el WHERE puede depender del usuario conectado, con USER o SYS_CONTEXT('USERENV', 'SESSION_USER').

El patrón es siempre el mismo: el propietario crea la vista y concede privilegios sobre la vista, no sobre la tabla. Quien consulta la vista no necesita ningún privilegio sobre la tabla base: la vista se ejecuta con los privilegios de su propietario.

💻 Ejemplo: En el caso de la sección 15, el propietario crea una tabla que asocia cada cuenta de usuario con su empleado, y una vista que solo muestra los clientes del comercial conectado:

CONNECT dcl_empresa/Empresa_2026@FREEPDB1

CREATE OR REPLACE VIEW v_empleados_publico AS
SELECT emp_id, nombre, apellidos, email, telefono, oficio, dept_id
FROM   empleados
WITH READ ONLY;

CREATE TABLE usuarios_empleado (
    usuario  VARCHAR2(128) CONSTRAINT pk_usuarios_empleado PRIMARY KEY,
    emp_id   NUMBER(6)     CONSTRAINT fk_usu_emp REFERENCES empleados (emp_id)
);
INSERT INTO usuarios_empleado VALUES ('DCL_CARMEN', 110);
INSERT INTO usuarios_empleado VALUES ('DCL_MARTA', 102);
INSERT INTO usuarios_empleado VALUES ('DCL_JORGE', 131);
COMMIT;

CREATE OR REPLACE VIEW v_mis_clientes AS
SELECT cliente_id, nombre, nif, email, telefono, ciudad, provincia, limite_credito, comercial_id
FROM   clientes
WHERE  comercial_id = (SELECT u.emp_id
                       FROM   usuarios_empleado u
                       WHERE  u.usuario = SYS_CONTEXT('USERENV', 'SESSION_USER'))
WITH CHECK OPTION CONSTRAINT ck_v_mis_clientes;
Vista creada.

Tabla creada.

1 fila creada.

1 fila creada.

1 fila creada.

Confirmación terminada.

Vista creada.

SYS_CONTEXT('USERENV', 'SESSION_USER') devuelve el nombre del usuario conectado: la misma vista muestra a cada comercial solo sus clientes. Lo comprobarás en la sección 15.4.

💡 Consejo: Para seguridad por filas a gran escala, Oracle ofrece Virtual Private Database (VPD, políticas que añaden automáticamente un WHERE a cada consulta) y Real Application Security. Las políticas VPD se programan con PL/SQL (Curso de PL/SQL). Para enmascarar datos al mostrarlos (por ejemplo, ver solo los cuatro últimos dígitos de una tarjeta) existe Data Redaction.


13. El principio de mínimo privilegio y buenas prácticas

📘 Concepto: El principio de mínimo privilegio (principle of least privilege) establece que cada usuario, aplicación o proceso debe tener exactamente los privilegios necesarios para su función, ni uno más, y solo durante el tiempo que los necesite. Limita el daño de errores, de cuentas robadas y de empleados malintencionados.

Buenas prácticas:

  1. Separa la cuenta propietaria del esquema de las cuentas de uso. Nadie (ni la aplicación) trabaja con el propietario; en 18c+ créalo con NO AUTHENTICATION o bloquéalo.
  2. Una cuenta por persona, nunca cuentas compartidas: si no, la auditoría no sirve.
  3. Concede a roles, no a usuarios. Diseña los roles según funciones del negocio (consulta, ventas, RRHH) y documenta su contenido.
  4. Evita los privilegios ANY, PUBLIC, WITH GRANT OPTION y el rol DBA para usuarios que no sean administradores.
  5. Prefiere vistas a tablas para exponer datos sensibles, con WITH READ ONLY o WITH CHECK OPTION.
  6. Usa perfiles con caducidad y complejidad de contraseñas y bloqueo por intentos fallidos.
  7. Cuotas explícitas; nada de UNLIMITED TABLESPACE.
  8. Revisa periódicamente los privilegios (con las consultas de la sección 11) y retira los de quien cambia de puesto o se va.
  9. Bloquea las cuentas que no se usan (las de ejemplo de Oracle vienen bloqueadas: no las desbloquees en producción).
  10. Audita las operaciones sensibles (sección 14).
  11. Protege también la aplicación: consultas parametrizadas contra la inyección SQL; las credenciales de la base de datos nunca en el código fuente.

14. Auditoría: Unified Auditing

📘 Concepto: La auditoría registra quién hizo qué y cuándo en la base de datos. No impide nada, pero permite detectar abusos, investigar incidentes y cumplir normativas (RGPD, ENS, PCI-DSS). Desde 12c, Oracle usa la auditoría unificada (Unified Auditing): se definen políticas de auditoría (CREATE AUDIT POLICY), se activan con AUDIT POLICY y los registros se consultan en la vista UNIFIED_AUDIT_TRAIL. Requiere los roles AUDIT_ADMIN (gestionar políticas) y AUDIT_VIEWER (consultar el registro).

💻 Ejemplo: Auditar cualquier modificación o borrado en la tabla de empleados.

-- Conectado como SYSTEM (que tiene los roles de auditoría a través de DBA)
CREATE AUDIT POLICY dcl_aud_empleados
    ACTIONS UPDATE ON dcl_empresa.empleados,
            DELETE ON dcl_empresa.empleados;
AUDIT POLICY dcl_aud_empleados;

SELECT policy_name, enabled_option, entity_name
FROM   audit_unified_enabled_policies
WHERE  policy_name = 'DCL_AUD_EMPLEADOS';

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
UPDATE empleados SET salario = salario + 100 WHERE emp_id = 160;
DELETE FROM empleados WHERE emp_id = 999;
ROLLBACK;

-- Conectado como SYSTEM
SELECT TO_CHAR(event_timestamp, 'DD/MM/YYYY HH24:MI') AS fecha,
       dbusername, action_name, object_name, sql_text
FROM   unified_audit_trail
WHERE  unified_audit_policies LIKE '%DCL_AUD_EMPLEADOS%'
ORDER BY event_timestamp;

NOAUDIT POLICY dcl_aud_empleados;
DROP AUDIT POLICY dcl_aud_empleados;
Política de auditoría creada.

Auditoría terminada correctamente.

POLICY_NAME          ENABLED_OPTION ENTITY_NAM
-------------------- -------------- ----------
DCL_AUD_EMPLEADOS    BY USER        ALL USERS

1 fila seleccionada.

1 fila actualizada.

0 filas suprimidas.

Rollback terminado.

FECHA            DBUSERNAME   ACTION_N OBJECT_NAM SQL_TEXT
---------------- ------------ -------- ---------- --------------------------------------------------
04/10/2026 05:44 DCL_EMPRESA  UPDATE   EMPLEADOS  UPDATE empleados SET salario = salario + 100 WHERE emp_id =
                                                  160

04/10/2026 05:44 DCL_EMPRESA  DELETE   EMPLEADOS  DELETE FROM empleados WHERE emp_id = 999

2 filas seleccionadas.

Noaudit terminado correctamente.

Política de auditoría borrada.

Observa que la auditoría registra las sentencias aunque se deshagan con ROLLBACK y aunque no afecten a ninguna fila: registra el intento. Incluso el propietario de la tabla queda auditado. Oracle trae políticas predefinidas (como ORA_SECURECONFIG o ORA_LOGON_FAILURES, que registra los inicios de sesión fallidos).


15. Caso completo: la seguridad del esquema EMPRESA

15.1. Requisitos

La empresa quiere dar acceso a la base de datos a tres perfiles de empleados:

Perfil Ejemplo Necesita
Consulta Jorge (administrativo, 131) Ver departamentos, categorías, productos y proyectos, y el directorio de empleados sin salarios ni comisiones. No puede modificar nada
Ventas Carmen (vendedora, 110) Lo de consulta + gestionar solo sus clientes (ver, dar de alta, modificar), crear y modificar pedidos y sus líneas (sin borrar pedidos: se cancelan cambiando su estado), y descontar stock
RRHH Marta (directora, 102) Lo de consulta + ver y gestionar los datos completos de los empleados (sin borrarlos: las bajas se registran), gestionar las asignaciones a proyectos y ajustar el presupuesto de los departamentos

Además: contraseñas que caducan cada 90 días, bloqueo de una hora tras 5 intentos fallidos, máximo 3 sesiones simultáneas por persona y desconexión tras 60 minutos de inactividad.

15.2. Diseño

flowchart TB
    C["DCL_ROL_CONSULTA<br/>CREATE SESSION<br/>SELECT departamentos, categorias,<br/>productos, proyectos, v_empleados_publico"]
    V["DCL_ROL_VENTAS<br/>SELECT, INSERT, UPDATE v_mis_clientes, pedidos<br/>SELECT, INSERT, UPDATE, DELETE lineas_pedido<br/>UPDATE (stock) productos"]
    R["DCL_ROL_RRHH<br/>SELECT, INSERT, UPDATE empleados<br/>SELECT, INSERT, UPDATE, DELETE asignaciones<br/>UPDATE (presupuesto) departamentos"]
    C --> V
    C --> R
    V --> CA["DCL_CARMEN"]
    R --> MA["DCL_MARTA"]
    C --> JO["DCL_JORGE"]
    P["Perfil DCL_PERFIL_EMPRESA"] -.-> CA
    P -.-> MA
    P -.-> JO

Matriz de privilegios (✓ = concedido; — = denegado):

Objeto Consulta Ventas RRHH
departamentos S S S, U(presupuesto)
categorias, productos, proyectos S S, U(stock) en productos S
v_empleados_publico (sin salario) S S S
empleados (tabla completa) — — S, I, U
v_mis_clientes (solo sus clientes) — S, I, U —
clientes (tabla completa) — — —
pedidos — S, I, U —
lineas_pedido — S, I, U, D —
asignaciones — — S, I, U, D

(S = SELECT, I = INSERT, U = UPDATE, D = DELETE.)

15.3. Script

Las vistas v_empleados_publico y v_mis_clientes se crearon en la sección 12. El resto del script:

-- ===================== Como SYSTEM: perfil y roles =====================
CREATE PROFILE dcl_perfil_empresa LIMIT
    SESSIONS_PER_USER      3
    IDLE_TIME              60
    FAILED_LOGIN_ATTEMPTS  5
    PASSWORD_LOCK_TIME     1/24
    PASSWORD_LIFE_TIME     90
    PASSWORD_GRACE_TIME    7
    PASSWORD_REUSE_MAX     5
    PASSWORD_REUSE_TIME    365;

CREATE ROLE dcl_rol_consulta;
CREATE ROLE dcl_rol_ventas;
CREATE ROLE dcl_rol_rrhh;

GRANT CREATE SESSION   TO dcl_rol_consulta;
GRANT dcl_rol_consulta TO dcl_rol_ventas;      -- jerarquía de roles
GRANT dcl_rol_consulta TO dcl_rol_rrhh;

-- ============ Como DCL_EMPRESA (propietario): privilegios de objeto ============
GRANT SELECT ON departamentos       TO dcl_rol_consulta;
GRANT SELECT ON categorias          TO dcl_rol_consulta;
GRANT SELECT ON productos           TO dcl_rol_consulta;
GRANT SELECT ON proyectos           TO dcl_rol_consulta;
GRANT SELECT ON v_empleados_publico TO dcl_rol_consulta;

GRANT SELECT, INSERT, UPDATE         ON v_mis_clientes TO dcl_rol_ventas;
GRANT SELECT, INSERT, UPDATE         ON pedidos        TO dcl_rol_ventas;
GRANT SELECT, INSERT, UPDATE, DELETE ON lineas_pedido  TO dcl_rol_ventas;
GRANT UPDATE (stock)                 ON productos      TO dcl_rol_ventas;

GRANT SELECT, INSERT, UPDATE         ON empleados      TO dcl_rol_rrhh;
GRANT SELECT, INSERT, UPDATE, DELETE ON asignaciones   TO dcl_rol_rrhh;
GRANT UPDATE (presupuesto)           ON departamentos  TO dcl_rol_rrhh;

-- ===================== Como SYSTEM: usuarios =====================
CREATE USER dcl_marta  IDENTIFIED BY "Marta_2026"  PROFILE dcl_perfil_empresa;
CREATE USER dcl_carmen IDENTIFIED BY "Carmen_2026" PROFILE dcl_perfil_empresa;
CREATE USER dcl_jorge  IDENTIFIED BY "Jorge_2026"  PROFILE dcl_perfil_empresa;

GRANT dcl_rol_rrhh     TO dcl_marta;
GRANT dcl_rol_ventas   TO dcl_carmen;
GRANT dcl_rol_consulta TO dcl_jorge;
Perfil creado.

Rol creado.

Rol creado.

Rol creado.

Concesión terminada correctamente.

Concesión terminada correctamente.

Concesión terminada correctamente.

Concesión terminada correctamente.
...

Usuario creado.

Usuario creado.

Usuario creado.

Concesión terminada correctamente.

Concesión terminada correctamente.

Concesión terminada correctamente.

En un caso real, los usuarios se crearían con PASSWORD EXPIRE; aquí no lo hacemos para poder conectarnos en las pruebas. Ningún usuario tiene cuota: no pueden crear objetos.

Verificación del diseño desde el diccionario:

-- Conectado como SYSTEM
SELECT username, profile, account_status
FROM   dba_users
WHERE  username IN ('DCL_MARTA', 'DCL_CARMEN', 'DCL_JORGE')
ORDER BY username;

SELECT grantee, granted_role
FROM   dba_role_privs
WHERE  grantee LIKE 'DCL%'
ORDER BY grantee, granted_role;

SELECT grantee AS role, table_name,
       LISTAGG(privilege, ', ') WITHIN GROUP (ORDER BY privilege) AS privilegios
FROM   dba_tab_privs
WHERE  owner = 'DCL_EMPRESA' AND grantee LIKE 'DCL_ROL%'
GROUP BY grantee, table_name
ORDER BY grantee, table_name;

SELECT grantee AS role, table_name, column_name, privilege
FROM   dba_col_privs
WHERE  owner = 'DCL_EMPRESA' AND grantee LIKE 'DCL_ROL%'
ORDER BY grantee;
USERNAME     PROFILE            ACCOUNT_STATUS
------------ ------------------ --------------
DCL_CARMEN   DCL_PERFIL_EMPRESA OPEN
DCL_JORGE    DCL_PERFIL_EMPRESA OPEN
DCL_MARTA    DCL_PERFIL_EMPRESA OPEN

3 filas seleccionadas.

GRANTEE            GRANTED_ROLE
------------------ ------------------
DCL_CARMEN         DCL_ROL_VENTAS
DCL_JORGE          DCL_ROL_CONSULTA
DCL_MARTA          DCL_ROL_RRHH
DCL_ROL_RRHH       DCL_ROL_CONSULTA
DCL_ROL_VENTAS     DCL_ROL_CONSULTA

5 filas seleccionadas.

ROLE               TABLE_NAME           PRIVILEGIOS
------------------ -------------------- ----------------------------------
DCL_ROL_CONSULTA   CATEGORIAS           SELECT
DCL_ROL_CONSULTA   DEPARTAMENTOS        SELECT
DCL_ROL_CONSULTA   PRODUCTOS            SELECT
DCL_ROL_CONSULTA   PROYECTOS            SELECT
DCL_ROL_CONSULTA   V_EMPLEADOS_PUBLICO  SELECT
DCL_ROL_RRHH       ASIGNACIONES         DELETE, INSERT, SELECT, UPDATE
DCL_ROL_RRHH       EMPLEADOS            INSERT, SELECT, UPDATE
DCL_ROL_VENTAS     LINEAS_PEDIDO        DELETE, INSERT, SELECT, UPDATE
DCL_ROL_VENTAS     PEDIDOS              INSERT, SELECT, UPDATE
DCL_ROL_VENTAS     V_MIS_CLIENTES       INSERT, SELECT, UPDATE

10 filas seleccionadas.

ROLE               TABLE_NAME           COLUMN_NAME  PRIVILEGE
------------------ -------------------- ------------ ----------
DCL_ROL_RRHH       DEPARTAMENTOS        PRESUPUESTO  UPDATE
DCL_ROL_VENTAS     PRODUCTOS            STOCK        UPDATE

2 filas seleccionadas.

15.4. Pruebas: qué puede y qué no puede hacer cada usuario

Jorge (consulta):

CONNECT dcl_jorge/Jorge_2026@FREEPDB1
SELECT * FROM session_roles;
SELECT COUNT(*) FROM dcl_empresa.productos;
SELECT emp_id, nombre, apellidos, oficio FROM dcl_empresa.v_empleados_publico WHERE dept_id = 40;
SELECT salario FROM dcl_empresa.v_empleados_publico;
SELECT salario FROM dcl_empresa.empleados;
SELECT COUNT(*) FROM dcl_empresa.clientes;
UPDATE dcl_empresa.productos SET precio = 1 WHERE producto_id = 1;
UPDATE dcl_empresa.v_empleados_publico SET telefono = '600000000' WHERE emp_id = 131;
CREATE TABLE prueba (x NUMBER);
ROLE
------------------
DCL_ROL_CONSULTA

1 fila seleccionada.

  COUNT(*)
----------
        20

1 fila seleccionada.

    EMP_ID NOMBRE                       APELLIDOS          OFICIO
---------- ---------------------------- ------------------ ------------------------------
       103 Pedro                        Gómez Díaz         DIRECTOR
       130 Isabel                       Domínguez Vega     CONTABLE
       131 Jorge                        Vázquez Ramos      ADMINISTRATIVO

3 filas seleccionadas.

SELECT salario FROM dcl_empresa.v_empleados_publico
       *
ERROR en línea 1:
ORA-00904: "SALARIO": invalid identifier

SELECT salario FROM dcl_empresa.empleados
                                *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."EMPLEADOS" does not exist

SELECT COUNT(*) FROM dcl_empresa.clientes
                                 *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."CLIENTES" does not exist

UPDATE dcl_empresa.productos SET precio = 1 WHERE producto_id = 1
                   *
ERROR en línea 1:
ORA-41900: missing UPDATE privilege on "DCL_EMPRESA"."PRODUCTOS"

UPDATE dcl_empresa.v_empleados_publico SET telefono = '600000000' WHERE emp_id = 131
                   *
ERROR en línea 1:
ORA-41900: missing UPDATE privilege on "DCL_EMPRESA"."V_EMPLEADOS_PUBLICO"

CREATE TABLE prueba (x NUMBER)
*
ERROR en línea 1:
ORA-01031: insufficient privileges

Los tres tipos de “no”: ORA-00904 (la columna no existe en la vista: seguridad por columnas), ORA-00942 (no tiene ningún privilegio sobre el objeto: ni sabe que existe) y ORA-41900/ORA-01031 (sabe que existe, pero le falta el privilegio concreto).

Carmen (ventas):

CONNECT dcl_carmen/Carmen_2026@FREEPDB1
SELECT * FROM session_roles ORDER BY role;
SELECT cliente_id, nombre, ciudad FROM dcl_empresa.v_mis_clientes ORDER BY cliente_id;
SELECT COUNT(*) FROM dcl_empresa.clientes;

INSERT INTO dcl_empresa.v_mis_clientes (cliente_id, nombre, nif, ciudad, provincia, comercial_id)
VALUES (1015, 'Ferretería Sol', 'B11223344', 'Valencia', 'Valencia', 110);
INSERT INTO dcl_empresa.v_mis_clientes (cliente_id, nombre, nif, ciudad, provincia, comercial_id)
VALUES (1016, 'Óptica Luna', 'B55667788', 'Valencia', 'Valencia', 111);
UPDATE dcl_empresa.v_mis_clientes SET limite_credito = 12000 WHERE cliente_id = 1001;
UPDATE dcl_empresa.v_mis_clientes SET limite_credito = 12000 WHERE cliente_id = 1003;

INSERT INTO dcl_empresa.pedidos (pedido_id, cliente_id, empleado_id, fecha_pedido, estado)
VALUES (5025, 1015, 110, DATE '2026-10-04', 'PENDIENTE');
INSERT INTO dcl_empresa.lineas_pedido (pedido_id, num_linea, producto_id, cantidad, precio_unitario)
VALUES (5025, 1, 6, 5, 19.90);
UPDATE dcl_empresa.productos SET stock = stock - 5 WHERE producto_id = 6;
UPDATE dcl_empresa.productos SET precio = 15 WHERE producto_id = 6;
DELETE FROM dcl_empresa.pedidos WHERE pedido_id = 5025;
SELECT salario FROM dcl_empresa.empleados WHERE emp_id = 110;
ROLLBACK;
ROLE
------------------
DCL_ROL_CONSULTA
DCL_ROL_VENTAS

2 filas seleccionadas.

CLIENTE_ID NOMBRE                       CIUDAD
---------- ---------------------------- ----------
      1001 Talleres Martín S.L.         Valencia
      1002 Construcciones Levante S.A.  Alicante
      1012 Diseños Creativos S.L.       Barcelona

3 filas seleccionadas.

SELECT COUNT(*) FROM dcl_empresa.clientes
                                 *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."CLIENTES" does not exist

1 fila creada.

INSERT INTO dcl_empresa.v_mis_clientes (cliente_id, nombre, nif, ciudad, provincia, comercial_id)
                        *
ERROR en línea 1:
ORA-01402: view WITH CHECK OPTION where-clause violation

1 fila actualizada.

0 filas actualizadas.

1 fila creada.

1 fila creada.

1 fila actualizada.

UPDATE dcl_empresa.productos SET precio = 15 WHERE producto_id = 6
                   *
ERROR en línea 1:
ORA-41900: missing UPDATE privilege on "DCL_EMPRESA"."PRODUCTOS"

DELETE FROM dcl_empresa.pedidos WHERE pedido_id = 5025
                        *
ERROR en línea 1:
ORA-41900: missing DELETE privilege on "DCL_EMPRESA"."PEDIDOS"

SELECT salario FROM dcl_empresa.empleados WHERE emp_id = 110
                                *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."EMPLEADOS" does not exist

Rollback terminado.
  • Carmen ve exactamente sus 3 clientes (los del comercial 110), sin tener acceso a la tabla clientes.
  • Puede dar de alta un cliente suyo, pero WITH CHECK OPTION le impide asignárselo a otro comercial.
  • El UPDATE del cliente 1003 (de David, el comercial 111) no da error, pero no modifica nada (“0 filas actualizadas”): para Carmen esa fila simplemente no existe.
  • Puede insertar el pedido y sus líneas aunque no tenga privilegios sobre clientes ni sobre empleados: las comprobaciones de las claves ajenas las hace Oracle con los privilegios del propietario.
  • Puede modificar el stock pero no el precio (privilegio por columna), y no puede borrar pedidos.

Marta (RRHH):

CONNECT dcl_marta/Marta_2026@FREEPDB1
SELECT emp_id, nombre, salario FROM dcl_empresa.empleados WHERE dept_id = 40;
UPDATE dcl_empresa.empleados SET salario = salario * 1.03 WHERE dept_id = 40;
DELETE FROM dcl_empresa.empleados WHERE emp_id = 160;
INSERT INTO dcl_empresa.asignaciones (emp_id, proyecto_id, horas, funcion)
VALUES (131, 5, 20, 'Apoyo administrativo');
UPDATE dcl_empresa.departamentos SET presupuesto = 160000 WHERE dept_id = 40;
UPDATE dcl_empresa.departamentos SET nombre = 'FINANZAS' WHERE dept_id = 40;
SELECT COUNT(*) FROM dcl_empresa.pedidos;
SELECT COUNT(*) FROM dcl_empresa.v_mis_clientes;
ROLLBACK;
    EMP_ID NOMBRE                          SALARIO
---------- ---------------------------- ----------
       103 Pedro                              4200
       130 Isabel                             2600
       131 Jorge                              1650

3 filas seleccionadas.

3 filas actualizadas.

DELETE FROM dcl_empresa.empleados WHERE emp_id = 160
                        *
ERROR en línea 1:
ORA-41900: missing DELETE privilege on "DCL_EMPRESA"."EMPLEADOS"

1 fila creada.

1 fila actualizada.

UPDATE dcl_empresa.departamentos SET nombre = 'FINANZAS' WHERE dept_id = 40
                   *
ERROR en línea 1:
ORA-41900: missing UPDATE privilege on "DCL_EMPRESA"."DEPARTAMENTOS"

SELECT COUNT(*) FROM dcl_empresa.pedidos
                                 *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."PEDIDOS" does not exist

SELECT COUNT(*) FROM dcl_empresa.v_mis_clientes
                                 *
ERROR en línea 1:
ORA-00942: table or view "DCL_EMPRESA"."V_MIS_CLIENTES" does not exist

Rollback terminado.

Resumen de las pruebas:

Operación Jorge Carmen Marta
Consultar productos ✓ ✓ ✓
Ver salarios ORA-00942 ORA-00942 ✓
Modificar salarios — — ✓
Borrar empleados — — ORA-41900
Ver la tabla clientes ORA-00942 ORA-00942 ORA-00942
Ver sus clientes (v_mis_clientes) ORA-00942 ✓ (3) ORA-00942
Asignar un cliente a otro comercial — ORA-01402 —
Crear pedidos y líneas — ✓ ORA-00942
Borrar pedidos — ORA-41900 —
Cambiar precios ORA-41900 ORA-41900 —
Cambiar stock ORA-41900 ✓ —
Cambiar presupuesto / nombre de departamento — — ✓ / ORA-41900
Crear tablas ORA-01031 — —

15.5. Limpieza

Al terminar las pruebas del módulo se borra todo lo creado. El orden importa poco porque DROP USER ... CASCADE y DROP ROLE retiran también las concesiones:

-- Conectado como SYSTEM
DROP USER dcl_marta CASCADE;
DROP USER dcl_carmen CASCADE;
DROP USER dcl_jorge CASCADE;
DROP USER dcl_empresa CASCADE;
DROP ROLE dcl_rol_consulta;
DROP ROLE dcl_rol_ventas;
DROP ROLE dcl_rol_rrhh;
DROP PROFILE dcl_perfil_empresa CASCADE;

SELECT 'USER' AS tipo, username AS nombre FROM dba_users WHERE username LIKE 'DCL%'
UNION ALL
SELECT 'ROLE', role FROM dba_roles WHERE role LIKE 'DCL%'
UNION ALL
SELECT 'PROFILE', profile FROM dba_profiles WHERE profile LIKE 'DCL%';
Usuario borrado.

Usuario borrado.

Usuario borrado.

Usuario borrado.

Rol borrado.

Rol borrado.

Rol borrado.

Perfil borrado.

ninguna fila seleccionada

16. Ejercicios

Para los ejercicios necesitas una cuenta de administración (como SYSTEM en tu instalación con Docker, módulo 22). Usa el prefijo dcl_ para todo lo que crees y bórralo al terminar. Los ejercicios parten del estado de la sección 15 (usuarios, roles y vistas del caso EMPRESA).

Ejercicios resueltos

Ejercicio 1: UPDATE sin SELECT

El propietario concede a Jorge UPDATE (telefono) ON empleados, pero no SELECT. ¿Puede Jorge cambiar el teléfono de todos los empleados? ¿Y el de un empleado concreto? Compruébalo.

Ver solución
CONNECT dcl_empresa/Empresa_2026@FREEPDB1
GRANT UPDATE (telefono) ON empleados TO dcl_jorge;

CONNECT dcl_jorge/Jorge_2026@FREEPDB1
UPDATE dcl_empresa.empleados SET telefono = '600100131';
UPDATE dcl_empresa.empleados SET telefono = '600100131' WHERE emp_id = 131;
ROLLBACK;

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
REVOKE UPDATE ON empleados FROM dcl_jorge;
Concesión terminada correctamente.

24 filas actualizadas.

UPDATE dcl_empresa.empleados SET telefono = '600100131' WHERE emp_id = 131
                   *
ERROR en línea 1:
ORA-41900: missing SELECT privilege on "DCL_EMPRESA"."EMPLEADOS"

Rollback terminado.

Revocación terminada correctamente.

¡Sorprendente! Jorge puede modificar todas las filas, pero no una concreta. La cláusula WHERE lee columnas de la tabla (emp_id), y leer requiere SELECT; el UPDATE sin WHERE no lee nada. Este comportamiento depende del parámetro SQL92_SECURITY, que vale TRUE por defecto desde 12.2 (estándar SQL:1992). Moraleja: UPDATE y DELETE casi siempre deben ir acompañados de SELECT, y conceder UPDATE “a ciegas” puede ser más peligroso de lo que parece.

Ejercicio 2: Rol de auditor con privilegio de esquema

Crea un rol dcl_rol_auditor que permita consultar todas las tablas y vistas de DCL_EMPRESA (también las que se creen en el futuro) sin modificar nada, y un usuario dcl_auditor con ese rol. Comprueba que puede leer incluso usuarios_empleado y que no puede borrar.

Ver solución
-- Conectado como SYSTEM
CREATE ROLE dcl_rol_auditor;
GRANT SELECT ANY TABLE ON SCHEMA dcl_empresa TO dcl_rol_auditor;
CREATE USER dcl_auditor IDENTIFIED BY "Auditor_2026";
GRANT CREATE SESSION, dcl_rol_auditor TO dcl_auditor;

SELECT grantee, privilege, schema FROM dba_schema_privs WHERE grantee LIKE 'DCL%';

CONNECT dcl_auditor/Auditor_2026@FREEPDB1
SELECT COUNT(*) FROM dcl_empresa.usuarios_empleado;
SELECT COUNT(*) FROM dcl_empresa.empleados;
DELETE FROM dcl_empresa.empleados WHERE emp_id = 160;
SELECT privilege, schema FROM session_schema_privs;
Rol creado.

Concesión terminada correctamente.

Usuario creado.

Concesión terminada correctamente.

GRANTEE          PRIVILEGE        SCHEMA
---------------- ---------------- ------------
DCL_ROL_AUDITOR  SELECT ANY TABLE DCL_EMPRESA

1 fila seleccionada.

  COUNT(*)
----------
         3

1 fila seleccionada.

  COUNT(*)
----------
        24

1 fila seleccionada.

DELETE FROM dcl_empresa.empleados WHERE emp_id = 160
                        *
ERROR en línea 1:
ORA-41900: missing DELETE privilege on "DCL_EMPRESA"."EMPLEADOS"

PRIVILEGE        SCHEMA
---------------- ------------
SELECT ANY TABLE DCL_EMPRESA

1 fila seleccionada.

Los privilegios de esquema también se pueden conceder a roles. Observa que un mismo GRANT puede conceder a la vez un privilegio de sistema y un rol (GRANT CREATE SESSION, dcl_rol_auditor TO ...). En 19c habría que conceder SELECT tabla a tabla al rol y repetirlo con cada tabla nueva.

Ejercicio 3: Todos los privilegios de objeto de un usuario

Como administrador, escribe una consulta que muestre todos los privilegios de objeto de dcl_carmen, indicando si los tiene directamente o a través de qué rol (incluidos los roles concedidos a sus roles).

Ver solución
-- Conectado como SYSTEM
SELECT 'DIRECTO' AS via, owner || '.' || table_name AS objeto, privilege
FROM   dba_tab_privs
WHERE  grantee = 'DCL_CARMEN'
UNION ALL
SELECT r.granted_role, t.owner || '.' || t.table_name, t.privilege
FROM   dba_role_privs r
       JOIN dba_tab_privs t ON t.grantee = r.granted_role
WHERE  r.grantee = 'DCL_CARMEN'
UNION ALL
SELECT r2.granted_role, t.owner || '.' || t.table_name, t.privilege
FROM   dba_role_privs r1
       JOIN dba_role_privs r2 ON r2.grantee = r1.granted_role
       JOIN dba_tab_privs t   ON t.grantee = r2.granted_role
WHERE  r1.grantee = 'DCL_CARMEN'
ORDER BY 1, 2, 3;
VIA                OBJETO                         PRIVILEGE
------------------ ------------------------------ ----------------
DCL_ROL_CONSULTA   DCL_EMPRESA.CATEGORIAS         SELECT
DCL_ROL_CONSULTA   DCL_EMPRESA.DEPARTAMENTOS      SELECT
DCL_ROL_CONSULTA   DCL_EMPRESA.PRODUCTOS          SELECT
DCL_ROL_CONSULTA   DCL_EMPRESA.PROYECTOS          SELECT
DCL_ROL_CONSULTA   DCL_EMPRESA.V_EMPLEADOS_PUBLICO SELECT
DCL_ROL_VENTAS     DCL_EMPRESA.LINEAS_PEDIDO      DELETE
DCL_ROL_VENTAS     DCL_EMPRESA.LINEAS_PEDIDO      INSERT
DCL_ROL_VENTAS     DCL_EMPRESA.LINEAS_PEDIDO      SELECT
...

15 filas seleccionadas.

La primera parte da los privilegios directos (ninguno), la segunda los de sus roles y la tercera los de los roles de sus roles (DCL_ROL_CONSULTA, dentro de DCL_ROL_VENTAS). Con dos niveles basta en este caso; para jerarquías de cualquier profundidad se usa una consulta jerárquica (ejercicio 4). Los privilegios por columna (UPDATE (stock)) están en DBA_COL_PRIVS y no aparecen aquí.

Ejercicio 4: Jerarquía de roles

Muestra la cadena completa de roles de dcl_carmen con una consulta jerárquica sobre DBA_ROLE_PRIVS (CONNECT BY se estudia en el módulo 33).

Ver solución
-- Conectado como SYSTEM
SELECT grantee, granted_role
FROM   dba_role_privs
START WITH grantee = 'DCL_CARMEN'
CONNECT BY PRIOR granted_role = grantee;
GRANTEE          GRANTED_ROLE
---------------- ------------------
DCL_CARMEN       DCL_ROL_VENTAS
DCL_ROL_VENTAS   DCL_ROL_CONSULTA

2 filas seleccionadas.

La consulta parte de Carmen y, en cada paso, busca los roles concedidos al rol encontrado en el paso anterior (PRIOR granted_role = grantee). Sustituyendo la primera parte de la consulta del ejercicio 3 por esta jerarquía se obtienen los privilegios de cualquier profundidad.

Ejercicio 5: Una empleada deja la empresa

Carmen se marcha. Retírale el acceso de forma que no pueda conectarse, pero sin borrar su cuenta (por si hay que investigar algo). Demuestra después que, aunque alguien desbloquee la cuenta por error, sigue sin poder entrar.

Ver solución
-- Conectado como SYSTEM
REVOKE dcl_rol_ventas FROM dcl_carmen;
ALTER USER dcl_carmen ACCOUNT LOCK;
SELECT username, account_status FROM dba_users WHERE username = 'DCL_CARMEN';

CONNECT dcl_carmen/Carmen_2026@FREEPDB1

-- Conectado como SYSTEM
ALTER USER dcl_carmen ACCOUNT UNLOCK;
CONNECT dcl_carmen/Carmen_2026@FREEPDB1
Revocación terminada correctamente.

Usuario modificado.

USERNAME     ACCOUNT_STATUS
------------ --------------
DCL_CARMEN   LOCKED

1 fila seleccionada.

ERROR:
ORA-28000: The account is locked; login denied.

Advertencia: Ya no está conectado a ORACLE.

Usuario modificado.

ERROR:
ORA-01045: Login denied. User DCL_CARMEN does not have CREATE SESSION privilege.

Advertencia: Ya no está conectado a ORACLE.

Dos barreras independientes: la cuenta bloqueada (ORA-28000) y la falta de CREATE SESSION, que solo tenía a través del rol (ORA-01045). Además, conviene quitar su fila de usuarios_empleado para que, si alguien reutilizara el nombre de cuenta, la vista v_mis_clientes no le mostrara los clientes de Carmen.

Ejercicio 6: Bloqueo por intentos fallidos

Con el perfil dcl_perfil_alumnos de la sección 10 asignado a dcl_eva (3 intentos, bloqueo de un minuto), provoca el bloqueo de la cuenta con contraseñas erróneas, comprueba su estado y desbloquéala.

Ver solución
CONNECT dcl_eva/mala1@FREEPDB1
CONNECT dcl_eva/mala2@FREEPDB1
CONNECT dcl_eva/mala3@FREEPDB1
CONNECT dcl_eva/Eva_2026@FREEPDB1

-- Conectado como SYSTEM
ALTER SESSION SET NLS_DATE_FORMAT = 'DD/MM/YYYY HH24:MI';
SELECT username, account_status, lock_date FROM dba_users WHERE username = 'DCL_EVA';
ALTER USER dcl_eva ACCOUNT UNLOCK;
SELECT username, account_status FROM dba_users WHERE username = 'DCL_EVA';
ERROR:
ORA-01017: invalid credential or not authorized; logon denied

Advertencia: Ya no está conectado a ORACLE.
ERROR:
ORA-01017: invalid credential or not authorized; logon denied

ERROR:
ORA-01017: invalid credential or not authorized; logon denied

ERROR:
ORA-28000: The account is locked; login denied.

Sesión modificada.

USERNAME     ACCOUNT_STATUS   LOCK_DATE
------------ ---------------- ----------------
DCL_EVA      LOCKED(TIMED)    04/10/2026 05:44

1 fila seleccionada.

Usuario modificado.

USERNAME     ACCOUNT_STATUS
------------ ----------------
DCL_EVA      OPEN

1 fila seleccionada.

Tras el tercer fallo la cuenta queda bloqueada, y el cuarto intento falla aunque la contraseña sea correcta. El estado LOCKED(TIMED) indica que el bloqueo se levantará solo al pasar PASSWORD_LOCK_TIME (un minuto); el administrador puede adelantarlo con ACCOUNT UNLOCK. Observa que el mensaje ORA-01017 no dice si el fallo está en el usuario o en la contraseña: así no da pistas a un atacante.

Ejercicio 7: Revocar REFERENCES con claves ajenas creadas

Ana tiene REFERENCES sobre departamentos y ha creado la tabla reuniones con una FK hacia ella (sección 6.4). El propietario quiere retirarle el privilegio. ¿Qué ocurre si lo intenta sin más? ¿Qué le pasa a la tabla de Ana si usa la opción necesaria?

Ver solución
CONNECT dcl_empresa/Empresa_2026@FREEPDB1
REVOKE REFERENCES ON departamentos FROM dcl_ana;
REVOKE REFERENCES ON departamentos FROM dcl_ana CASCADE CONSTRAINTS;

CONNECT dcl_ana/Ana_2027@FREEPDB1
SELECT constraint_name, constraint_type FROM user_constraints WHERE table_name = 'REUNIONES';
REVOKE REFERENCES ON departamentos FROM dcl_ana
*
ERROR en línea 1:
ORA-01981: CASCADE CONSTRAINTS must be specified to perform this REVOKE.

Revocación terminada correctamente.

CONSTRAINT_NAME   C
----------------- -
PK_REUNIONES      P

1 fila seleccionada.

Oracle no deja revocar el privilegio mientras haya claves ajenas que dependan de él. Con CASCADE CONSTRAINTS se revoca y se borran esas claves ajenas en el esquema de Ana: su tabla y sus datos siguen ahí, pero ya sin integridad referencial (reuniones podría tener ahora departamentos inexistentes).

Ejercicio 8: ¿Quién puede revocar?

Con la cadena de la sección 7 (DCL_EMPRESA → Ana → Luis → Eva, todos con SELECT sobre productos), predice y comprueba qué pasa si Luis intenta revocar el privilegio a Ana, y si el propietario intenta revocárselo directamente a Eva.

Ver solución
CONNECT dcl_luis/Luis_2026@FREEPDB1
REVOKE SELECT ON dcl_empresa.productos FROM dcl_ana;

CONNECT dcl_empresa/Empresa_2026@FREEPDB1
REVOKE SELECT ON productos FROM dcl_eva;
REVOKE SELECT ON dcl_empresa.productos FROM dcl_ana
*
ERROR en línea 1:
ORA-01927: A privilege cannot be revoked if you did not grant it.

REVOKE SELECT ON productos FROM dcl_eva
*
ERROR en línea 1:
ORA-01927: A privilege cannot be revoked if you did not grant it.

Un privilegio de objeto solo puede revocarlo quien lo concedió (el GRANTOR). Luis no se lo concedió a Ana; y el propietario tampoco se lo concedió a Eva (fue Luis). Ni siquiera el propietario puede saltarse la cadena: para quitarle el acceso a Eva, o lo revoca Luis, o el propietario lo revoca a Ana y la cascada alcanza a Luis y a Eva (sección 7). Es una razón más para evitar WITH GRANT OPTION.

Ejercicios propuestos

Ejercicio 9: Usuario para un becario

Crea el usuario dcl_becario con contraseña caducada, tablespace por defecto USERS, cuota de 2 MB, perfil dcl_perfil_empresa y el rol dcl_rol_consulta. Comprueba en DBA_USERS, DBA_TS_QUOTAS y DBA_ROLE_PRIVS que todo es correcto. ¿Podrá crear tablas? ¿Por qué?

Ejercicio 10: Rol de logística

Diseña el rol dcl_rol_logistica para el personal de almacén: debe poder consultar pedidos y líneas de pedido, marcar pedidos como enviados (solo las columnas estado y fecha_envio) y modificar el stock de los productos, pero no ver clientes, precios de venta ni datos de empleados. Escribe el script, concédelo a un usuario de prueba y comprueba cada permiso y cada prohibición con el error que corresponda.

Ejercicio 11: Vista de proyectos de mi departamento

Crea en DCL_EMPRESA una vista v_mis_proyectos que muestre a cada usuario los proyectos de su departamento (usando usuarios_empleado y SYS_CONTEXT). Concede SELECT sobre ella a dcl_rol_consulta y comprueba qué ve Jorge (departamento 40) y qué ve Marta (departamento 30).

Ejercicio 12: Contraseña de rol para la aplicación

Convierte dcl_rol_ventas en un rol protegido con contraseña y quítalo de los roles por defecto de Carmen. Comprueba que, al conectarse, Carmen solo tiene dcl_rol_consulta… ¿o tampoco? Explica qué pasa con el rol que está dentro de un rol no activo y cómo lo resolverías.

Ejercicio 13: ADMIN OPTION con roles

Concede dcl_rol_consulta a Marta WITH ADMIN OPTION. Comprueba que Marta puede concedérselo a otro usuario y que también puede revocárselo a Jorge aunque no fuera ella quien se lo concedió. Después revoca el rol a Marta: ¿lo conserva el usuario al que ella se lo dio?

Ejercicio 14: Perfil para cuentas de servicio

Crea un perfil dcl_perfil_servicio para la cuenta con la que se conecta una aplicación web: contraseña que no caduca, sin bloqueo por intentos fallidos (para que un atacante no pueda dejar la web sin servicio bloqueando la cuenta), hasta 50 sesiones simultáneas y sin límite de inactividad. Justifica cada valor y explica qué riesgos asumes y cómo los compensarías.

Ejercicio 15: Auditoría de accesos a salarios

Crea una política de auditoría unificada que registre las consultas (SELECT) sobre dcl_empresa.empleados. Haz que Marta y el auditor del ejercicio 2 consulten la tabla, y obtén de UNIFIED_AUDIT_TRAIL quién la consultó y cuándo. Desactiva y borra la política al terminar.

Ejercicio 16: Informe de seguridad

Escribe un conjunto de consultas sobre el diccionario que sirva como “informe de seguridad” de la base de datos: usuarios con el rol DBA, usuarios con algún privilegio ANY, privilegios de objeto concedidos a PUBLIC sobre tablas de DCL_EMPRESA, privilegios concedidos WITH GRANT OPTION, usuarios con perfil DEFAULT y cuentas abiertas que nunca han iniciado sesión (columna LAST_LOGIN de DBA_USERS). Ejecútalo y comenta qué cambiarías.


Resumen

Concepto Sintaxis / idea clave
Seguridad Confidencialidad, integridad, disponibilidad; autenticación (¿quién?) frente a autorización (¿qué?)
Crear usuario CREATE USER u IDENTIFIED BY "clave" DEFAULT TABLESPACE ts TEMPORARY TABLESPACE tmp QUOTA n ON ts PROFILE p PASSWORD EXPIRE ACCOUNT LOCK;
Modificar / borrar ALTER USER u ...; DROP USER u [CASCADE];
Errores de conexión ORA-01045 (sin CREATE SESSION), ORA-28000 (bloqueada), ORA-28001 (caducada), ORA-01017 (credenciales)
Cuota ORA-01950 al insertar la primera fila (creación diferida de segmentos)
Multitenant Usuarios comunes C##... en CDB$ROOT (ORA-65094 en una PDB); usuarios locales en la PDB
Privilegio de sistema GRANT CREATE TABLE TO u [WITH ADMIN OPTION]; — su revocación no se propaga
Privilegio de objeto GRANT SELECT, UPDATE (c1) ON t TO u [WITH GRANT OPTION]; — su revocación sí se propaga
Revocar REVOKE priv [ON t] FROM u [CASCADE CONSTRAINTS]; solo el GRANTOR (ORA-01927)
Errores de autorización ORA-00942 (sin ningún privilegio: “no existe”), ORA-01031 / ORA-41900 (23ai) (falta el privilegio concreto)
PUBLIC Todos los usuarios; evitar
Privilegios de esquema (23ai) GRANT SELECT ANY TABLE ON SCHEMA esq TO u; incluye objetos futuros
Rol CREATE ROLE r [IDENTIFIED BY "x"]; GRANT privs TO r; GRANT r TO u;
Roles activos SET ROLE r1, r2 / ALL / NONE; ALTER USER u DEFAULT ROLE ...; SESSION_ROLES
Roles y vistas Para crear vistas sobre tablas ajenas el privilegio debe ser directo (ORA-41904 / ORA-01031)
Roles predefinidos CONNECT, RESOURCE, DBA, SELECT_CATALOG_ROLE, DB_DEVELOPER_ROLE (23ai)
Perfil CREATE PROFILE p LIMIT SESSIONS_PER_USER n FAILED_LOGIN_ATTEMPTS n PASSWORD_LIFE_TIME n ...;
Diccionario USER_SYS_PRIVS, USER_TAB_PRIVS, USER_ROLE_PRIVS, ROLE_SYS_PRIVS, ROLE_TAB_PRIVS, SESSION_PRIVS, SESSION_ROLES, DBA_USERS, DBA_*_PRIVS
Vistas de seguridad Por columnas (lista del SELECT), por filas (WHERE + WITH CHECK OPTION), por usuario (SYS_CONTEXT('USERENV','SESSION_USER'))
Mínimo privilegio Solo lo necesario, a través de roles, sin ANY, PUBLIC ni GRANT OPTION
Auditoría CREATE AUDIT POLICY ... ACTIONS ...; AUDIT POLICY p; → UNIFIED_AUDIT_TRAIL