begin
htp.p(owa_util.get_cgi_env('WIN_USER'));
htp.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:
{{ :prog:apex:apex_authentication_scheme_set_post_procedure_name_v01.png | '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!)
* {{ :prog:apex:apex_create_authorization_scheme_v01.png | 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:
* {{ :prog:apex:apex_use_authorization_scheme_v01.png |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:
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:
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 => [[dba:passwort_in_psql_schuetzen|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!
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:
* https://matthiashoys.wordpress.com/2012/04/24/ora-24247-during-ldap-authentication-from-apex-4-1-1-on-oracle-11gr2/
==== Quellen ====
LDAP
* http://www.idevelopment.info/data/Oracle/DBA_tips/LDAP/LDAP_21.shtml
* http://sql-plsql-de.blogspot.de/2007/09/ldap-server-abfragen-mit-sql.html
* https://docs.oracle.com/cd/B10501_01/network.920/a96577/concepts.htm#725943
Verschlüsseln:
* http://www.oracleflash.com/41/Encrypt-or-Decrypt-sensitive-data-using-PLSQL---DBMS_CRYPTO.html