This post is also available in: Português
When a user's password expires in one of the databases that I manage, some users complain that it is an application user and the password should never expires. Thus, if the schema really should be used only by the application, I've created a process that will control this access based on the user session information, which can be one or more of the following:
- User hostname
- OS username
- OS Terminal
- User Application Module
- User IP Address
In this article I will show the operation of this script. Let's start:
You must create the following objects:
- Application user Profile
- Control Table
- Access Trigger
First, I create a profile that the password does not expire and whose users belonging to it will be controlled by the login trigger:
CREATE PROFILE "DEFAULT_APP_USER" LIMIT COMPOSITE_LIMIT DEFAULT SESSIONS_PER_USER DEFAULT CPU_PER_SESSION DEFAULT CPU_PER_CALL DEFAULT LOGICAL_READS_PER_SESSION DEFAULT LOGICAL_READS_PER_CALL DEFAULT IDLE_TIME DEFAULT CONNECT_TIME DEFAULT PRIVATE_SGA DEFAULT FAILED_LOGIN_ATTEMPTS 10 PASSWORD_LIFE_TIME UNLIMITED PASSWORD_REUSE_TIME UNLIMITED PASSWORD_REUSE_MAX UNLIMITED PASSWORD_VERIFY_FUNCTION DEFAULT PASSWORD_LOCK_TIME 30/1440 PASSWORD_GRACE_TIME UNLIMITED;
PS: Note that in this PROFILE, most of the parameters are inherited from the DEFAULT one except that the password never expires and the login error attempts, which are 10 tries to every 30 minutes.
Next, we must create the table (and index) where you will control the access:
CREATE TABLE TB_FILTER_LOGON ( SESSION_USER VARCHAR2(50) NOT NULL, HOST VARCHAR2(50), OS_USER VARCHAR2(50), TERMINAL VARCHAR2(50), MODULE VARCHAR2(50), IP_ADDRESS VARCHAR2(50) ) TABLESPACE USERS; CREATE INDEX I1_FILTER_LOGON ON TB_FILTER_LOGON(SESSION_USER) TABLESPACE USERS;
In this table, the only mandatory field is the "SESSION_USER", which is the user who is logging in the database. Other fields can be filled or not according to the restrictions you want to do.
As you can notice, the "SESSION_USER" column is not a PK, which allows a combination of different filters to be applied.
Finally, create a trigger that will be used to control the access:
CREATE OR REPLACE TRIGGER TRG_FILTER_LOGON AFTER LOGON ON DATABASE DECLARE V_GLOBAL_NAME VARCHAR2(50); V_PROFILE VARCHAR2(50); V_CHECK NUMBER; --- V_HOST VARCHAR2(50); V_OS_USER VARCHAR2(50); V_TERMINAL VARCHAR2(50); V_MODULE VARCHAR2(50); V_IP_ADDRESS VARCHAR2(50); V_SESSION_USER VARCHAR2(50); BEGIN -- Created by DBA RJ - www.dbarj.com.br - 2015/01 -- Get Connection Info V_HOST := SUBSTR(TRIM(UPPER(SYS_CONTEXT('USERENV', 'HOST'))), 1, 50); V_OS_USER := SUBSTR(TRIM(UPPER(SYS_CONTEXT('USERENV', 'OS_USER'))), 1, 50); V_TERMINAL := SUBSTR(TRIM(UPPER(SYS_CONTEXT('USERENV', 'TERMINAL'))), 1, 50); V_MODULE := SUBSTR(TRIM(UPPER(SYS_CONTEXT('USERENV', 'MODULE'))), 1, 50); V_IP_ADDRESS := SUBSTR(TRIM(UPPER(SYS_CONTEXT('USERENV', 'IP_ADDRESS'))), 1, 50); V_SESSION_USER := SUBSTR(TRIM(UPPER(SYS_CONTEXT('USERENV', 'SESSION_USER'))), 1, 50); -- Get Database Name SELECT GLOBAL_NAME INTO V_GLOBAL_NAME FROM GLOBAL_NAME; -- Get Profile Name SELECT PROFILE INTO V_PROFILE FROM DBA_USERS WHERE USERNAME = V_SESSION_USER; IF V_PROFILE = 'DEFAULT_APP_USER' THEN -- Check CASE V_GLOBAL_NAME WHEN 'DBFAIRUS' THEN SELECT TOTAL1 + TOTAL2 INTO V_CHECK FROM (SELECT COUNT(*) TOTAL1 FROM TB_FILTER_LOGON WHERE TRIM(UPPER(SESSION_USER)) = V_SESSION_USER AND NVL(TRIM(UPPER(HOST)), NVL(V_HOST, 1)) = NVL(V_HOST, 1) AND NVL(TRIM(UPPER(OS_USER)), NVL(V_OS_USER, 1)) = NVL(V_OS_USER, 1) AND NVL(TRIM(UPPER(TERMINAL)), NVL(V_TERMINAL, 1)) = NVL(V_TERMINAL, 1) AND NVL(TRIM(UPPER(MODULE)), NVL(V_MODULE, 1)) = NVL(V_MODULE, 1) AND NVL(TRIM(UPPER(IP_ADDRESS)), NVL(V_IP_ADDRESS, 1)) = NVL(V_IP_ADDRESS, 1)), (SELECT DECODE(COUNT(*), 0, 1, 0) TOTAL2 -- If the entry does not exist, will return 1 to unlock the user FROM TB_FILTER_LOGON WHERE TRIM(UPPER(SESSION_USER)) = V_SESSION_USER); IF (V_CHECK <> 0) THEN NULL; --OK ELSE --NULL; RAISE_APPLICATION_ERROR(-20000, 'YOU ARE NOT AUTHORIZED TO LOGIN WITH THIS USERNAME. PLEASE CONTACT YOUR DBA.'); END IF; ELSE NULL; END CASE; END IF; EXCEPTION -- Avoid problems when fails to get GLOBAL_NAME or PROFILE (Data Guard) WHEN NO_DATA_FOUND THEN NULL; END; /
Sample scenarios:
1) The SCOTT user can connect only:
- From IP 10.192.12.43 with OS user "scripts" OR
- From IP 10.192.12.40 with OS user "root"
SQL> select t.* from tb_filter_logon t SESSION_USER HOST OS_USER TERMINAL MODULE IP_ADDRESS 1 SCOTT scripts 10.192.12.43 2 SCOTT root 10.192.12.40
2) The EXPUSER user can connect only:
- From the server whose hostname is "syslogfubddsne001" and using "SQL Developer" OR
- From IP 10.192.12.40 using JDBC and with OS user "javacode"
SQL> select t.* from tb_filter_logon t SESSION_USER HOST OS_USER TERMINAL MODULE IP_ADDRESS 1 EXPUSER syslogfubddsne001 SQL Developer 2 EXPUSER javacode JDBC Thin Client 10.192.12.40
PS: If there is no row for some (SESSION_USER) in the table, the user will not be blocked.
Finally, remember: if the user has the "Administer Database Trigger" GRANT, directly or indirectly (as having the DBA role), it will never be blocked by login triggers!
Have you enjoyed? Please leave a comment or give a 👍!
6 comments
Skip to comment form
Any way to block or give a warning to a DBA user? Thanks.
Author
Only if you revoke "ADMINISTER DATABASE TRIGGER" from DBA role.
Thanks,
RJ
will it work in Oracle 12 C I have tried but no luck
Author
Haven't tried yet, however I don't see any limitation in 12c for this PL/SQL.
Any idea how to go about being able to use IP wildcards or ranges? If i want a user to login using a certain ip range. 192.168.2.0/24? Thanks.
Dear DBA RJ,
I have tried through 12c but it doesn't work. I already stored data in table and then tried to connect through sql developer. connection successfully created.. as the user data was not existed in table.
Please let me why ?