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
GRANTyREVOKE, incluidos los privilegios por columnas - Entender la diferencia entre
WITH ADMIN OPTIONyWITH GRANT OPTIONy 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) yREVOKE(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 tablaempleadosdel 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_EMPRESAque 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 prefijodcl_: 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 usuarioSYSTEM; los cambios de usuario se hacen con el comandoCONNECTde 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'ySET NULL '(nulo)'). Los mensajes de SQLPlus se muestran en español y los erroresORA-en inglés, como los muestra la edición Free; omitimos la líneaHelp: 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 TABLESPACEpermite ocupar espacio sin límite en cualquier tablespace, incluidoSYSTEM. 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
PASSWORDcambia tu contraseña sin que se vea en pantalla ni quede en el historial de comandos. Es preferible a escribirla en unALTER 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 EXPIREes 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 ... CASCADEno 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 llamaFREEPDB1, 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$ROOTy su nombre debe empezar porC##(C##ADMIN). Se usa para administradores que gestionan todo el contenedor.SYSySYSTEMson 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
ANYson muy peligrosos:DROP ANY TABLEpermite borrar las tablas de toda la empresa, ySELECT ANY TABLEleer 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 TABLEen acción. Concedemos el privilegio a una usuaria de pruebas,dcl_eva(creada conCREATE USER dcl_eva IDENTIFIED BY "Eva_2026" QUOTA 5M ON users;y conCREATE 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 VIEWaunque se lo concediera Ana y Ana ya no lo tenga. Oracle no registra quién concedió un privilegio de sistema (DBA_SYS_PRIVSno tiene columnaGRANTOR), 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 nuevoORA-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 (comoCREATE TABLEsin el privilegio) sigue apareciendoORA-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.
REFERENCESconcede 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
REFERENCESpara crear claves ajenas, hay que revocarlo conCASCADE 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 ALLretira todos los privilegios que el usuario tenga sobre el objeto (aquí, elSELECTrestante).
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
PUBLICes 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 aPUBLICciertos 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 VIEWfalla conORA-01031: insufficient privileges; 23ai/26ai devuelve el más explícitoORA-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 baseWITH 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 ROLEsustituye el conjunto de roles activos: al activardcl_rol_escritura, Pablo ha desactivadodcl_rol_lecturay ya no vedepartamentos. 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_ROLEreúne lo que necesita un desarrollador de aplicaciones sin darle privilegios de administración. En Oracle 26ai incluye 14 privilegios de sistema, entre ellosCREATE 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 rolesRESOURCEyCTXAPP(es el rol con el que trabaja el usuarioCURSOde nuestras pruebas). En 19c, la alternativa clásica es concederCONNECT+RESOURCE+ los privilegios concretos necesarios.
💡 Consejo: No concedas
CONNECTyRESOURCEpor 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
WHEREde la vista filtra las filas permitidas, conWITH CHECK OPTIONpara que tampoco se puedan insertar o modificar filas fuera del ámbito. - Seguridad por usuario: el
WHEREpuede depender del usuario conectado, conUSERoSYS_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
WHEREa 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:
- 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 AUTHENTICATIONo bloquéalo. - Una cuenta por persona, nunca cuentas compartidas: si no, la auditoría no sirve.
- Concede a roles, no a usuarios. Diseña los roles según funciones del negocio (consulta, ventas, RRHH) y documenta su contenido.
- Evita los privilegios
ANY,PUBLIC,WITH GRANT OPTIONy el rolDBApara usuarios que no sean administradores. - Prefiere vistas a tablas para exponer datos sensibles, con
WITH READ ONLYoWITH CHECK OPTION. - Usa perfiles con caducidad y complejidad de contraseñas y bloqueo por intentos fallidos.
- Cuotas explícitas; nada de
UNLIMITED TABLESPACE. - Revisa periódicamente los privilegios (con las consultas de la sección 11) y retira los de quien cambia de puesto o se va.
- Bloquea las cuentas que no se usan (las de ejemplo de Oracle vienen bloqueadas: no las desbloquees en producción).
- Audita las operaciones sensibles (sección 14).
- 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 conAUDIT POLICYy los registros se consultan en la vistaUNIFIED_AUDIT_TRAIL. Requiere los rolesAUDIT_ADMIN(gestionar políticas) yAUDIT_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 OPTIONle impide asignárselo a otro comercial. - El
UPDATEdel 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
clientesni sobreempleados: las comprobaciones de las claves ajenas las hace Oracle con los privilegios del propietario. - Puede modificar el
stockpero no elprecio(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 |