Benutzer-Werkzeuge

Webseiten-Werkzeuge


prog:oracle_apex_active_directory_integration

Oracle Apex 5 Securtiy und Microsoft Active Directory Integration

Erstellt 05/2016

Um Zugriffe auf eine Apex Applikation zu steuern, kann auch das Active Directory eingebunden werden.

Zwei Aufgaben sind dabei zu lösen, den User zu authentisieren und den Zugriff auf die Pages der Applikation zu autorisieren.

Im folgenden Beispiel erfolgt die Authentifizierung über den Apache Webserver mit NTLM und für die Autorisierung von Teilen der Applikation wird auf die Hinterlegten Gruppen im AD zurückgegriffen.

Die AD Gruppen werden dynamisch in den Session Kontext des aktuellen Users übernommen und werden dann in der Apex Applikation über einen „Authorisation Scheme“ referenziert.

Übersicht:

 Apex und Microsoft AD Integration


Authentisieren über den Webserver

Ob ein User sich in der Domaine angemeldet hat muss der eingesetzte Webserver prüfen.

Single Sign On unter Windows 2012 mit Apache HTTP und dem NTLM Module

Unter Windows kann am einfachsten mit dem Apache HTTP Server NTLM mit Mod Auth NTLM eingesetzt werden. Siehe dazu ⇒ Oracle ORDS 3.0 (Oracle REST Data Services) mit dem Apache Application Server Tomcat für Single Sign On unter Windows 2012 betreiben

Über das Modul wird geprüft, ob der User sich gültig in der Windows Domain angemeldet hat, ist das True wird auf die Apex Application weitergleitet.

Die weitere Prüfung, ob der User fachlich die Anwendung bzw. einzelnen Seiten oder Menüpunkte sehen darf, muss in der Apex Applikation getestet werden.

Der Domain Name des User wird in den HTTP Header geschrieben (siehe die NTLM Konfiguration des obigen Beispiels) und in eine von uns definiert Variable abgelegt, im diesem Beispiel in „WIN_USER“.


Apex - Authentication Scheme einrichten - Wie meldet man sich in Apex an?

Die Windows Anmeldung bzw. in Prinzip nur der Windows User Name wird an die Single Sign On Konfiguration von Apex für die jeweilige Applikation weitergereicht.

Damit sich nun der User nicht nicht nochmal anmelden muss, muss in APEX das „Authentification Scheme“ auf Applikationsebene gesetzt werden.

  • In der Applikation auf „Shared Components“ klicken, „Authentication Schemes“ auswählen  Authentication Schemes anlegen
  • Create Button rechte wählen
  • Vorlage auswählen : Based on a pre-configured scheme from the gallery ⇒ Next Authentication Schemes anlegen
  • Nun einen Namen vergeben und die Methode HTTP HEADER Variable auswählen Authentication Schemes anlegen
  • Hinterlegen in welchen Header Feld der Username steht  Authentication Scheme Header Variable hinterlegen
  • Speichern

In den Application Settings prüfen, ob auch das richtige Schema gewählt wurde:

 Authentication Schemes überprüfen

Nun kann der Test beginnen ob das auch geklappt hat.

Außerhalb der Domain sollte jetzt eine Benutzerabfrage erfolgen, angemeldet an der Domain sollte sich die Applikation ohne Login starten lassen.

Debuggen

Klappt es nicht, die Header Variablen auf einer Seite mit Hilfe von „owa_util.print_cgi_env;“ anzeigen lassen (Page mit einer Region auf PL/SQL Basis):

BEGIN
 htp.p(owa_util.get_cgi_env('WIN_USER'));
 htp.p('<p>');
 owa_util.print_cgi_env
END;

Den Variablen Namen haben wir zuvor in der Apache Konfiguration auf NTLM Ebene festgelegt!



Autorisieren - In Apex prüfen ob der jeweilige User die Seite auch wirklich sehen darf

In der Windows Welt ist das Gruppen Konzept selbstverständlich um Rechte auf Objekte im Betriebssystem zu definieren.

Das gleiche kann auch in Apex erfolgen, über Gruppen kann der Zugriff auf Seiten, einzelne Elemente der Seite und Menüeinträge gesteuert werden.

In Apex 5 kann die Zugehörigkeit zu einer Gruppe für den aktuelle angemeldeten Anwender beim Login gesetzt werden.

Um das Verhalten einfach zu testen ein statisches Beispiel als Procedure zwei Gruppen zum Testen (angelegt im Parsing Schema der Apex Application) erstellen:

CREATE PROCEDURE setRole
IS
BEGIN
  apex_authorization.enable_dynamic_groups ( p_group_names => apex_t_varchar2('KOSTENSTELLE', 'EINKAUF') );
END;
/

https://docs.oracle.com/cd/E59726_01/doc.50/e39149/apex_authorization.htm#AEAPI29592

Nun wird diese Procedure im Authentication Scheme als die „Post-Authentication Procedure“ hinterlegt:

 'Post-Authentication Procedure Name' hinterlegen

Der Trick dahinter ist es nun über das Active Directory die Gruppen des Windows Users auszulesen und diese Windows Gruppen dynamisch in den Session Kontext von Apex zu schrieben.

Apex - Authorization Schemes verwenden

In Apex wird ein „Authorization Scheme“ anlegt, mit dem geprüft wird ob der Apex User die notwendige Gruppe für das Apex Element besitzt.

In der Apex Application wird dann auf Seiten oder Menü Ebene dieses „Authorization Scheme“ referenziert, um zu prüfen ob die Seite/der Menüpunkt angezeigt werden kann.

  • In der Applikation auf „Shared Components“ klicken, „Authorization Schemes“ auswählen
  • Mit den „Create Button“ den Wizard für ein neues Scheme starten
  • „From Scratch“ auswählen
    • Name vergeben
    • Scheme Type „Is in Group“ wählen
    • Eine Gruppe einfach eintragen (nicht über die Dialog suchen,werden ja dann später erst dynamisch hinterlegt!)
    •  ein Apex Authorization Schemes anlegen
    • Mit „Create Authorization Scheme“ anlegen

In der Applikation nun auf der jeweiligen Seite das Authorization Scheme in den Security Settings hinterlegen:

  • Authorization Scheme in den Apex Seite hinterlegen

Mit DBMS_LDAP das AD abfragen

Nach dem nun in den obigen Schritte mit der statischen Methode alles geklappt hat, gilt es nun das ganze dynamisch auch aus dem AD zu füllen.

Im ersten Schritt muss geprüft werden ob aus der Datenbank überhaupt auf das AD zugegriffen werden kann.

Ein sehr gutes Beispiel um das zu testen findet sich hier ⇒ https://oracle-base.com/articles/9i/ldap-from-plsql-9i .

Den Beispiel Code aus diesem guten Beispiel in eine Datei kopieren und UNTER dem Parsing Schema der Apex Applikation den Zugriff auf das AD prüfen.

Meist muss unter 11g und 12c eine Netzwerk ACL hinterlegt werden, um das überhaupt verwenden zu können, siehe dazu mehr am Ende dieser Seite.

Mit diesen Schritt werden dann die notwendigen Zugangsdaten (AD IP Adresse und Port) + Username und Passwort ermittelt und die richtige LDAP Abfrage Struktur wird geklärt.

Mit diese Daten kann dann mein Code Beispiel ldap_ad_util ergänzt werden.

LDAP_AD_UTIL

Mit Hilfe des Windows User Name wird das AD abgefragt, die gefunden Gruppe werden in den Session Kontext von Apex geschrieben.

Das Schreiben in den Session Kontext von APEX erfolgt durch das Hinterlegen einer Procedure in der current „Authentifcation Scheme“ der Application im Bereich „post_authentication“ mit dem 'Post-Authentication Procedure name' Attribut.

Der Code

Für die dynamische Übernahme der Gruppen aus dem AD nach Apex wird der folgende PL/SQL verwendet (muss im Pasing Schema liegen oder von dort mit entsprechenden Rechten lesbar sein).

Spezifikation:

ldap_ad_util_spc.sql
CREATE OR REPLACE PACKAGE ldap_ad_util
IS
  -- +============================================================================
  --   NAME:       ldap_ad_util
  --   PURPOSE:    Read User information from the active directory
  --                    
  -- +============================================================================
 
 
  -- exception handling
  g_pck       CONSTANT VARCHAR2 (30) := 'ldap_util';
 
  ex_gperrors EXCEPTION;
  PRAGMA exception_init (ex_gperrors, -20100);
  g_emerrors VARCHAR2 (100) := 'An error occured. Please view the ERRORS-table for more information.';
 
  -- global variables
  -- You have to edit carefully this section to set all the values of your enviroment!
 
  -- IP or name of the AD and the port
  g_ldap_host    VARCHAR2(256) := '10.10.10.180';
  g_ldap_port    VARCHAR2(256) := '389';
 
  -- user to read from the AD
  g_ldap_user    VARCHAR2(256) := 'ORASYSTEM';
  g_ldap_passwd  VARCHAR2(256) := 'secret_password';
 
  -- The entry to the AD tree
  -- check the cn ! and adjust to your needs!
  g_ldap_base    VARCHAR2(256) := 'cn=Users,dc=pipperr,dc=local';
 
  -- how to seach the user in the ad
  -- check how the loginname in your domain is defined!
  g_ad_user_type VARCHAR2(255) := 'cn=';
 
 
  -- +===========================================================+
  --  function : connectAD
  -- +===========================================================+
  FUNCTION connectad
    RETURN DBMS_LDAP.session;
  -- +===========================================================+
  --  procedure : disconnectAD
  -- +===========================================================+
  PROCEDURE disconnectad(
      p_session IN OUT DBMS_LDAP.session);
  -- +===========================================================+
  --  procedure : disconnectAD
  -- +===========================================================+
  FUNCTION readgroups(
      p_session DBMS_LDAP.session ,
      p_username VARCHAR2)
    RETURN apex_t_varchar2;
  -- +===========================================================+
  --  procedure : setApexGroups
  --  set in the Apex Session dynamic groups
  -- +===========================================================+
  PROCEDURE setapexgroups(
      p_username VARCHAR2 DEFAULT SYS_CONTEXT(
        'APEX$SESSION',
        'APP_USER'));
END ldap_ad_util;
/

Body:

ldap_ad_util_spc.sql
CREATE OR REPLACE PACKAGE BODY ldap_ad_util
IS
  -- +============================================================================
  --   NAME:       ldap_ad_util
  --   PURPOSE:    Read User information from the active directory
  --   GPI 2016
  -- +============================================================================
  -- +===========================================================+
  --  function : getADPWD
  --   get the Password for the AD User
  --   later we will save the pwd encrypted in the database
  -- +===========================================================+
  FUNCTION getadpwd
    RETURN VARCHAR2
  IS
  BEGIN
    RETURN g_ldap_passwd;
  END;
-- +===========================================================+
--  function : connectAD
-- connect to the AD
-- +===========================================================+
  FUNCTION connectad
    RETURN DBMS_LDAP.session
  IS
    v_session DBMS_LDAP.session;
    v_retval PLS_INTEGER;
  BEGIN
    -- choose to raise exceptions.
    DBMS_LDAP.use_exception  := TRUE;
    DBMS_LDAP.utf8_conversion:=FALSE;
    -- connect to the ldap server.
    v_session := DBMS_LDAP.init(hostname => g_ldap_host, portnum => g_ldap_port);
    -- connect with this user to the AD
    v_retval := DBMS_LDAP.simple_bind_s(ld => v_session , dn => g_ldap_user , passwd => getadpwd );
     IF v_retval != DBMS_LDAP.SUCCESS THEN
       DBMS_OUTPUT.put_line('-- Error at::'||$$plsql_unit||' :: dbms_ldap.simple_bind_s for User  ::'||  g_ldap_user );
       raise_application_error( -21001 , '-- Error at::'||$$plsql_unit||' ::  dbms_ldap.simple_bind_s for User  ::'||  g_ldap_user );  
  END IF;
    RETURN v_session;
  EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.put_line('-- Error at::'||$$plsql_unit||' :: '||SQLERRM);
    raise_application_error( SQLCODE , '-- Error at::'||$$plsql_unit||' :: '||SQLERRM);
  END connectad;
-- +===========================================================+
--  procedure : disconnectAD
-- +===========================================================+
  PROCEDURE disconnectad(
      p_session IN OUT DBMS_LDAP.session)
  IS
    v_retval PLS_INTEGER;
  BEGIN
    v_retval := DBMS_LDAP.unbind_s(ld => p_session);
    IF v_retval != DBMS_LDAP.SUCCESS THEN
       DBMS_OUTPUT.put_line('-- Error at::'||$$plsql_unit||' :: Can not close connection to LDAP');
       raise_application_error( -21009 , '-- Error at::'||$$plsql_unit||' :: Can not close connection to LDAP');  
    END IF;
  EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.put_line('-- Error at::'||$$plsql_unit||' :: '||SQLERRM);   
  END disconnectad;
 
-- +===========================================================+
--  procedure : disconnectAD
--  Code Logic copied from https://oracle-base.com/articles/9i/ldap-from-plsql-9i . 
--  Thanks to Tim Hall
-- +===========================================================+
  FUNCTION readgroups(
      p_session DBMS_LDAP.session ,
      p_username VARCHAR2)
    RETURN apex_t_varchar2
  IS
    v_retval PLS_INTEGER;
    v_attrs DBMS_LDAP.string_collection;
    v_message DBMS_LDAP.message;
    v_entry DBMS_LDAP.message;
    v_attr_name VARCHAR2(256);
    v_ber_element DBMS_LDAP.ber_element;
    v_vals DBMS_LDAP.string_collection;
    -- empty collection 
    v_group_tab apex_t_varchar2:=apex_t_varchar2();
 
    v_apex_ary apex_application_global.vc_arr2;
BEGIN
  v_attrs(1) := 'memberOf'; 
  -- retrieve all attributes
  v_retval := DBMS_LDAP.search_s(ld => p_session 
                                 , base => g_ldap_base 
                                 , scope => DBMS_LDAP.scope_subtree 
                                 , filter => g_ad_user_type||p_username 
                                 , attrs => v_attrs 
                                 , attronly => 0 
                                 , res => v_message
                                 );
  IF v_retval != DBMS_LDAP.SUCCESS THEN
       DBMS_OUTPUT.put_line('-- Error at::'||$$plsql_unit||' :: dbms_ldap.search_s for filter ::'|| g_ad_user_type||p_username );
       raise_application_error( -21002 , '-- Error at::'||$$plsql_unit||' :: dbms_ldap.search_s for filter ::'|| g_ad_user_type||p_username);  
  END IF;
 
  IF DBMS_LDAP.count_entries(ld => p_session 
                            , msg => v_message) > 0 THEN
 
    -- Get all the entries returned by our search.
    v_entry := DBMS_LDAP.first_entry( ld => p_session ,msg => v_message);
    << entry_loop >>
    WHILE v_entry IS NOT NULL
    LOOP
      -- Get all the attributes for this entry.
      DBMS_OUTPUT.put_line('------------------------------------');
      v_attr_name := DBMS_LDAP.first_attribute(ld => p_session, ldapentry => v_entry, ber_elem => v_ber_element);
      << attributes_loop >>
      WHILE v_attr_name IS NOT NULL
      LOOP
        -- Get all the values for this attribute.
        v_vals := DBMS_LDAP.get_values (ld => p_session
                                     , ldapentry => v_entry
                                     , attr => v_attr_name);
        BEGIN
          << values_loop >>
          FOR i IN v_vals.FIRST .. v_vals.LAST
          LOOP
            DBMS_OUTPUT.put_line('-- Info: Found: ' || v_attr_name || ' = ' || SUBSTR(v_vals(i),1,500));
 
            -- decode memberOf = CN=ORA_ASMDBA,CN=Users,DC=pipperr,DC=local
            -- to the the group name
            v_apex_ary:=apex_util.string_to_table(p_string=> v_vals(i),p_separator => ',' );
            FOR y IN v_apex_ary.FIRST .. v_apex_ary.LAST
            LOOP
              IF v_apex_ary.EXISTS(y) THEN
                DBMS_OUTPUT.put_line('-- Info:  Catch Group Details ' || v_apex_ary(y));
                IF y=1 THEN
                  v_group_tab.extend;
                  v_group_tab( v_group_tab.LAST ) :=  (REPLACE(v_apex_ary(y),'CN=',''));
                  DBMS_OUTPUT.put_line('-- Info:  Found Group ' || REPLACE(v_apex_ary(y),'CN=',''));
                END IF;
              END IF;
            END LOOP;
          END LOOP values_loop;
        EXCEPTION
        WHEN OTHERS THEN
          DBMS_OUTPUT.put_line('-- Error read Attribute: ' || v_attr_name || ' :: Errror '||SQLERRM);
        END;
        v_attr_name := DBMS_LDAP.next_attribute(ld => p_session
                                               , ldapentry => v_entry
                                               , ber_elem => v_ber_element);
      END LOOP attibutes_loop;
      v_entry := DBMS_LDAP.next_entry(ld => p_session
                                    , msg => v_entry);
    END LOOP entry_loop;
  END IF;
  RETURN v_group_tab;
END readgroups;
 
-- +===========================================================+
--  procedure : setApexGroups
--  set in the Apex Session dynamic groups
-- +===========================================================+
PROCEDURE setapexgroups(
    p_username VARCHAR2 DEFAULT SYS_CONTEXT(
      'APEX$SESSION',
      'APP_USER'))
IS
  v_session DBMS_LDAP.session;
  v_group_tab apex_t_varchar2;
BEGIN
  -- connect to LDAP
  v_session:=connectad;
  -- read the groups into
  DBMS_OUTPUT.put_line('-- Info:  Get AD Groups for ' || p_username);
  v_group_tab:=readgroups(p_session => v_session, p_username => p_username);
  -- disconnect the LDAP Session
  disconnectad(p_session => v_session);
  -- add this groups to the Apex Session
  IF v_group_tab.COUNT > 0 THEN
    FOR i IN v_group_tab.FIRST .. v_group_tab.LAST 
    LOOP
       IF v_group_tab.EXISTS(i) THEN
        DBMS_OUTPUT.put_line('-- Info:  set Group in Apex Session ' || v_group_tab(i));
       END IF;
    END LOOP;
  ELSE
    DBMS_OUTPUT.put_line('-- Info:  No Groups for this user found' ||p_username );
  END IF;
 
  -- set the groups with the group collection
  -- apex_t_varchar2('KOSTENSTELLE','EINKAUF')
  apex_authorization.enable_dynamic_groups ( p_group_names => v_group_tab);
  --
   EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.put_line('-- Error at::'||$$plsql_unit||' :: '||SQLERRM);
    -- check that the connection to the ldap is closed!
    -- Check if connection ist still open is in the function!
    disconnectad(p_session => v_session);
END setapexgroups;
BEGIN
  -- Initialization
  NULL;
END ldap_ad_util ;
/

Ein noch zu lösendes Problem ist das Passwort im Package, in einer produktiven Umgebung sollte das entweder verschlüsselt hinterlegt werden oder anderweitig geschützt gespeichert sein!

Um das ganze zu testen, das Package mit den richtigen Globalen Einstellungen einspielen und in SQL*Plus im APEX Parsing Schema mit einen gekannten AD User aufrufen:

SET serveroutput ON 
 
EXEC setApexGroups('ORACLE_ADMIN');

Da der Default die Apex Session Info ist, braucht später keine User übergeben zu werden!

PWD des Domain Users verstecken

Wollte jetzt keine eigenen Tabelle für das eine Password anlegen,

Idee:

  • Key setzt sich aus der DB Laufzeit Umgebung zusammen
  • Key wird ergänzt um einen privaten Key bei Aufruf der Entschlüsselung.
  • Funktion oder Object wird in der DB für das Speichern des verschlüsselten Wertes verwendet

Lösung:

siehe ⇒ Passwörter und ähnliche Schlüssel in PL/SQL Packages schützen

Fehler Suche - ORA-24247: network access denied by access control list (ACL)

Oracle 12c Netzwerk ACL setzen!

Fehler:

ERROR at line 1:
ORA-24247: network access denied BY access control list (ACL)
ORA-06512: at "SYS.DBMS_LDAP_API_FFI", line 25
ORA-06512: at "SYS.DBMS_LDAP", line 48
ORA-06512: at "GPI.LDAP_UTIL", line 23
ORA-06512: at "GPI.LDAP_UTIL", line 148
ORA-06512: at line 1
BEGIN
 DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE
 (
     host       => '10.10.10.180'
  ,  lower_port => 389
  ,  upper_port => 389
  ,   ace       => xs$ace_type( privilege_list => xs$name_list('connect')
                              , principal_name => 'GPI'
                              , principal_type => xs_acl.ptype_db)
 );
END;
/

Siehe dazu ⇒ http://oracle.informatik.haw-hamburg.de/network.121/e17607/fine_grained_access.htm#DBSEG114

ACL in 11g hinterlegen:

Quellen

Cookies helfen bei der Bereitstellung von Inhalten. Durch die Nutzung dieser Seiten erklären Sie sich damit einverstanden, dass Cookies auf Ihrem Rechner gespeichert werden. Weitere Information
"Autor: Gunther Pipperr"
prog/oracle_apex_active_directory_integration.txt · Zuletzt geändert: 2018/04/24 00:26 von Gunther Pippèrr