26. September 2007

LDAP-Server abfragen ... mit SQL

In vielen Unternehmen haben LDAP-Server eine zentrale Bedeutung - nahezu alle Mitarbeiter sind dort mit ihren Kontaktdaten abgelegt - So kann es vorkommen, dass man aus der Datenbank heraus auf diese Informationen zugreifen möchte. Und die Oracle-Datenbank bringt dazu auch ein Hilfsmittel mit: DBMS_LDAP. Mit diesem PL/SQL-Paket kann man auf LDAP-Directories zugreifen und dort Informationen abrufen oder manipulieren. Allerdings ist es recht aufwändig, mit diesem Paket zu programmieren ... viel hübscher wäre es, wenn man mit einem SQL SELECT auf die Informationen zugreifen könne - dies würde die Aufbereitung der Daten bspw. in einer Web-Anwendung (Application Express-Bericht) massiv vereinfachen.
Der folgende Code ermöglicht genau dies. Die LDAP-Abfrage wird durch eine Table Function durchgeführt; diese Funktionen liefern keine skalaren Werte, sondern eine ganze Tabelle zurück (mehr zu Table Functions in diesem Blog). Es gibt hier aber noch eine Besonderheit zu beachten: Normalerweise muss man die "Rückgabetabelle" der Table Function durch einen Objekttypen (CREATE TYPE) festlegen und so beschreiben. Das folgende Beispiel geht etwas anders vor: Mit Hilfe des Oracle Data Cartridge Interface (ODCI) kann man ebenfalls Table Functions bauen und hier muss man vorher keine Objekttypen erstellen.
Wenn DBMS_LDAP in der Datenbank nicht vorhanden ist, kann der DBA es mit $ORACLE_HOME/rdbms/admin/catldap.sql einspielen. Doch genug der Vorrede: hier der Code

drop type ldapquery
/


CREATE type ldapquery as object(
  ldap_host        varchar2(4000),
  ldap_port        number,
  ldap_session     raw(32),
  ldap_resultset   raw(32),

  q_search_base    varchar2(4000), 
  q_search_filter  varchar2(4000), 
  q_return_fields  varchar2(4000),
  q_timeout        number,

  row_types          anytype,
  last_entry_fetched number,

  static function query(
    p_ldap_host        in varchar2,
    p_ldap_port        in number,
    p_search_base      in varchar2, 
    p_search_filter    in varchar2,
    p_return_fields    in varchar2,
    p_timeout          in number default 3
  ) return anydataset pipelined using ldapquery,

  static function ODCITableDescribe(
    record_table       out anytype,
    p_ldap_host        in varchar2,
    p_ldap_port        in number,
    p_search_base      in varchar2, 
    p_search_filter    in varchar2,
    p_return_fields    in varchar2,
    p_timeout          in number
  ) return number,

  static function ODCITablePrepare (
    sctx               out ldapquery, 
    tab_func_info      in sys.ODCITabFuncInfo,
    p_ldap_host        in varchar2,
    p_ldap_port        in number,
    p_search_base      in varchar2, 
    p_search_filter    in varchar2,
    p_return_fields    in varchar2,
    p_timeout          in number
  ) return number,

  static function ODCITableStart   (
    sctx               in out ldapquery, 
    p_ldap_host        in varchar2,
    p_ldap_port        in number,
    p_search_base      in varchar2, 
    p_search_filter    in varchar2,
    p_return_fields    in varchar2,
    p_timeout          in number
  ) return number, 

  member function ODCITableFetch   (
    self               in out ldapquery,
    nrows              in number, 
    record_out         out anydataset
  ) return number,
  
  member function ODCITableClose   (
    self               in ldapquery 
  ) return number


)
/


create or replace package ldapquery_helper is
  function tokenize(
    p_string    in varchar2, 
    p_delim     in varchar2 default ','
  ) return dbms_ldap.String_collection;
end ldapquery_helper;
/

create or replace package body ldapquery_helper is
  function tokenize(
    p_string    in varchar2, 
    p_delim     in varchar2 default ','
  ) return dbms_ldap.String_collection is
    v_startpos  pls_integer := 1;
    v_comma_pos pls_integer;
   
    v_result_array dbms_ldap.string_collection;
    v_array_index  pls_integer := 0;
  begin 
    while v_startpos != 0 loop
      v_comma_pos := instr(p_string, p_delim, v_startpos);  
      if v_comma_pos != 0 then 
        v_result_array(v_array_index) := substr(p_string, v_startpos, v_comma_pos - v_startpos);
        v_startpos := v_comma_pos + 1;
      else 
        v_result_array(v_array_index) := substr(p_string, v_startpos);
        v_startpos := v_comma_pos;
      end if;
      v_array_index := v_array_index + 1;
    end loop;
    return v_result_array;
  end tokenize;
end ldapquery_helper;
/


CREATE or replace type BODY ldapquery as
 static function ODCITableDescribe(
    record_table       out anytype,
    p_ldap_host        in varchar2,
    p_ldap_port        in number,
    p_search_base      in varchar2, 
    p_search_filter    in varchar2,
    p_return_fields    in varchar2,
    p_timeout          in number
  ) return number as
    v_record_structure anytype; 
   
    v_result_array dbms_ldap.string_collection;
  begin 
    v_result_array := ldapquery_helper.tokenize(p_return_fields);

    anytype.begincreate(dbms_types.typecode_object, v_record_structure);

    for i in v_result_array.first..v_result_array.last loop
      v_record_structure.addattr(   
        ANAME     => v_result_array(i),
        TYPECODE  => dbms_types.typecode_varchar2,
        PREC      => null,
        SCALE     => null,
        LEN       => 4000,
        CSID      => null,    
        CSFRM     => null,
        ATTR_TYPE => null
      );
    end loop;
    
    v_record_structure.endcreate();

    anytype.begincreate(dbms_types.typecode_table, record_table); 

    record_table.setinfo(nullnullnullnullnull, v_record_structure, dbms_types.typecode_object, 0); 
    record_table.endcreate(); 

    return odciconst.success;
  
  exception when others then 
    -- indicate that an error has occured somewhere.
    return odciconst.error;
  end;   

  static function ODCITablePrepare (
    sctx               out ldapquery, 
    tab_func_info      in sys.ODCITabFuncInfo, 
    p_ldap_host        in varchar2,
    p_ldap_port        in number,
    p_search_base      in varchar2, 
    p_search_filter    in varchar2,
    p_return_fields    in varchar2,
    p_timeout          in number 
  ) return number is
    prec         pls_integer; 
    scale        pls_integer; 
    len          pls_integer; 
    csid         pls_integer; 
    csfrm        pls_integer; 
    record_desc  anytype; 
    aname        varchar2(30); 
    dummy        pls_integer; 
  begin 
    dummy := tab_func_info.RetType.GetAttrElemInfo(null, prec, scale, len, csid, csfrm, record_desc, aname); 
    sctx := ldapquery(p_ldap_host, p_ldap_port, nullnull, p_search_base, p_search_filter, p_return_fields, p_timeout, record_desc, 0); 
    return odciconst.success; 
  end; 

  static function ODCITableStart   (
    sctx               in out ldapquery, 
    p_ldap_host        in varchar2,
    p_ldap_port        in number,
    p_search_base      in varchar2, 
    p_search_filter    in varchar2,
    p_return_fields    in varchar2,
    p_timeout          in number 
  ) return number is
    v_ldap_session   dbms_ldap.session;
    v_ldap_resultset dbms_ldap.message;
    v_bind_status    pls_integer;
    v_search_status  pls_integer;
    v_return_attrs   dbms_ldap.string_collection;
    v_timeout        dbms_ldap.timeval;
  begin 
    v_return_attrs := ldapquery_helper.tokenize(p_return_fields);
    v_timeout.seconds := p_timeout;
    v_timeout.useconds := 0;

    
    v_ldap_session := dbms_ldap.init(p_ldap_host, p_ldap_port);
    v_bind_status := dbms_ldap.bind_s(
      ld => v_ldap_session
     ,dn => null
     ,cred => null
     ,meth => dbms_ldap.AUTH_SIMPLE
    );

    v_search_status := dbms_ldap.search_st(
      ld         => v_ldap_session,
      base       => p_search_base,
      scope      => dbms_ldap.SCOPE_SUBTREE,
      filter     => p_search_filter,
      attrs      => v_return_attrs,
      attronly   => 0,
      res        => v_ldap_resultset,
      tv         => v_timeout
    );
    sctx.ldap_session := v_ldap_session;
    sctx.ldap_resultset := v_ldap_resultset;
    return odciconst.success; 
  end; 
 

  member function ODCITableFetch   (
    self               in out ldapquery,
    nrows              in number, 
    record_out         out anydataset
  ) return number is
    v_return_attrs dbms_ldap.string_collection;
    v_att_values   dbms_ldap.string_collection;
    v_field varchar2(4000);
  
    v_no_more_rows boolean := true;
  begin 
    record_out := null;
    
    v_return_attrs := ldapquery_helper.tokenize(q_return_fields);
    begin
      anydataset.begincreate(dbms_types.typecode_object, self.row_types, record_out); 
      record_out.addinstance;
      record_out.piecewise(); 
      for i in v_return_attrs.first..v_return_attrs.last loop
         v_att_values := dbms_ldap.get_values(
           ld        => ldap_session,
           ldapentry => ldap_resultset,
           attr      => v_return_attrs(i)
         );
         if v_att_values.exists(0) then
           v_field := v_att_values(0);
         else
           v_field := null;
         end if;
         record_out.setvarchar2(v_field);
      end loop;
      record_out.endcreate;
            
      ldap_resultset := dbms_ldap.next_entry(
       ld => ldap_session,
       msg => ldap_resultset
      );
    exception
      when others then 
        v_no_more_rows := false;
    end;
    if not v_no_more_rows then 
      begin
        record_out.endcreate;
      exception when others then null;
      end;
      record_out := null;
    end if;
    return odciconst.success; 
  end; 

  
  member function ODCITableClose   (
    self               in ldapquery 
  ) return number is
    v_ldap_session   dbms_ldap.session;
    v_unbind_status  pls_integer;
  begin
    v_ldap_session := ldap_session;
    v_unbind_status := dbms_ldap.unbind_s(
      ld => v_ldap_session
    );
    return odciconst.success; 
  end;
end;
/


Ausprobieren ist dann ganz einfach ...
select * from table(
  ldapquery.query(
    'ldapserv.mydomain.com',   -- Servername oder IP-Adresse
    389,                       -- TCP/IP-POrt
    '',                        -- Die Suche erfolgt ab diesem Eintrag im LDAP-Baum
    '(cn=Czarski*)',           -- Suchabfrage
    'cn,c,title',              -- Rückgabefelder
    3                          -- Timeout für Abfrage: 3 Sekunden
  )
);

cn                   c       title
-------------------- ------- --------------------------------------
Czarski,Carsten      de      Leitende/R Systemberater/In

1 Zeile wurde ausgewählt.

Kommentare:

Anonym hat gesagt…

Hallo ich habe die Sache mit der LDAP-Abfrage probiert, bekomme aber folgenden Fehler beim Ausführen des Selects

ORA-31202:DBMS_LDAP:LDAP_Client-/Server-Fehler:Fehler bei Vorgängen. 00000000:LdapErr: DSID-0C090627, comment: In order to perform this operation a succesful bind must be completed on the connection., data 0, vece
ORA-06512: in SYS.DBMS_SYS_ERROR, Zeile 86
ORA-06512: in SYS.DBMS_LDAP, Zeile 1455
ORA-06512: in SYS.DBMS_LDAP, Zeile 312
ORA-06512: in LDAP_TEST.LDAPQUERY, Zeile 105

Carsten Czarski hat gesagt…

Das ist nun ein Bereich, wo u.U. eine Anpassung an Ihren LDAP-Server nötig ist ... Das Beispiel geht davon aus, dass der LDAP-Server anonyme Anfragen zulässt - im BIND_S-Call werden NULL-Werte für "dn" (Username) und "cred" (Passwort) übergeben.

v_ldap_session := dbms_ldap.init(p_ldap_host, p_ldap_port);
v_bind_status := dbms_ldap.bind_s(
ld => v_ldap_session
,dn => null
,cred => null
,meth => dbms_ldap.AUTH_SIMPLE
);

Ihre Fehlermeldung deutet darauf hin, dass Ihr LDAP-Server eine "richtige" Anmeldung erwartet ... Dann muss das Skript entsprechend angepasst werden ...

Anonym hat gesagt…

Super Arbeit! Habe schon seit längerem nach einer Lösung mit diesem Ansatz gesucht. Habe den Type erweitert, sodass User/PWD für AD-Abfrage angegeben werden kann. Zusätzlich erstellte ich Views basierend auf ldapquery.query für eine komfortable Abfrage der Memberships.
Leider erhalte ich jedoch jeweils pro AD-Gruppe nur ein Rekord zurück, obwohl die Gruppe Member von mehreren anderen Gruppen ist.
Gibts im Resultset irgendwo eine Einschränkung, sodass nur ein FETCH erfolgt?

create or replace view lpadm_ad_base_groups_v
as
select
x."cn" base_group
,x."member" member_string
from table
( ldapquery.query
('xy.ch'
,389
,'sa_oracle'
,'*pwd*'
,'ou=Groups,ou=_test,dc=xyv,dc=ch'
,'(cn=*)'
,'cn,member'
,3)
) x;

CREATE OR REPLACE VIEW lpadm_ad_groups_memberships_v
AS
select base_group
,SUBSTR(member_string, INSTR(member_string, 'CN=')+3, INSTR(member_string, ',OU=')-4) is_member
,member_string
from lpadm_ad_base_groups_v;

Carsten Czarski hat gesagt…

Das ResultSet kann mehrere Sätze zurückliefern. Die Vorgehensweise ist die, dass mit ...

ldap_resultset :=
dbms_ldap.next_entry(
ld => ldap_session,
msg => ldap_resultset
);

jeweils auf den nächsten Datensatz vorgerückt wird. Ist der letzte erreicht, wird eine Exception ausgelöst; daraufhin wird record_out auf SQL NULL gesetzt - wodurch der Fetch beendet wird.
Ich habe das Beispiel auch schon mit mehreren Ergebniszeilen getestet - lief sehr gut.

Anonym hat gesagt…

Beim Compilieren der OT-Specification erhalte ich die Fehlermeldung:
(S5093) Expecting:), AGGREGATE AS AUTHID DETERMINISTIC IS PARALLEL_ENABLE PIPELINED

Trotzdem kann ich den Body compilieren und auch Ausführen. Könnte dies der Grund sein, dass nicht alle Rekords (Einträge im Active Directory) ausgelesen werden? Wir verwenden Ora 10g R2.

Carsten Czarski hat gesagt…

das ist schon möglich ...

S5093 ist eine seltsame Fehlermeldung; scheint mir nicht aus der Datenbank zu kommen. Arbeitest Du mit einem Werkzeug (TOAD, etc) ...?
Dann am besten den Code mal aus SQL*Plus heraus laufen lassen und dessen Fehlermeldungen ansehen ...

Kurt Kniendl hat gesagt…

Ausgezeichnetes Script! Eine Frage, die sich mir stellt: gewisse Felder wie die objectGUID werden in unserer OracleDB als Schlüssel verwendet. Durch ein Script wird mir diese ID als hexadezimale Zahl (32Zeichen) nach Oracle geschrieben. Um nun direkt abzufragen, müsste ich dieses Feld nun ebenfalls hex darstellen bzw umformatieren können. rawtohex, rawidtochar, o.ä.?

stabilo hat gesagt…

Hallo Carsten,

gibt es eine Möglichkeit, mehrere AD-Server (IP-Adressen) zu übergeben? Oder anders, wenn ein bestimmter Server nicht erreichbar ist, ein Fehler abgefangen werden kann, um einen "Ersatz-Server" abzufragen?

Carsten Czarski hat gesagt…

Hi,

out-of-the-box geht das nicht; DBMS_LDAP kennt sowas wie ein Failover nicht. Man kann das natürlich noch selbst "dranprogrammieren"; das würde dann bedeuten, dass man eben eine Liste mit LDAP-Server-Adressen mitgibt, der PL/SQL-Code probiert diese aus, fängt evtl. auftretende Fehler ab und probiert im Bedarfsfall die nächste ...

Wäre noch ein wenig Arbeit ...

Viele Grüße

-Carsten

Anonym hat gesagt…

Hallo, das ist echt eine super Lösung ... leider hab ich mal noch 'ne dumme Anfängerfrage: Wie kann ich das Select-Statement in einer Procedure aufrufen (zum Beispiel als Cursor).

Carsten Czarski hat gesagt…

Hallo,

naja, ein "impliziter" Cursor könnte so aussehen ...

create or replace ...

begin
:
for c in (select ... from table(ldapquery.query(....))) loop
:
-- machwas

:
end loop;
end;

Hilft das ...?

-Carsten

Anonym hat gesagt…

Hallo, leider nicht, weil ich da immer folgenden Fehler bekomme: cannot access rows from a non-nested table item.
Danke

Carsten Czarski hat gesagt…

Hallo,


ok ... auch gerade erkannt. Das hier (arbeitet mit dem eigentlich für dynamisches SQL vorgesehenen OPEN ... FOR) tut es bei mir ...

set serveroutput on
declare
  type c_t is ref cursor;
  v_c c_t;

  v_cn varchar2(4000); 
begin
  open v_c for 
    q'#select * from table(
      ldapquery.query(
        'meinldap.domain.com',   
        389,                       
        '',                        
        '(cn=Czarski*)',           
        'cn',                      
        3                          
      )
    )#';
  loop
    fetch v_c into v_cn;
    exit when v_c%NOTFOUND;
    dbms_output.put_line(v_cn);
  end loop;
end;
/



... funktioniert bei mir.

-Carsten

Anonym hat gesagt…

Danke, super ... so funktioniert es.

Anonym hat gesagt…

Hi,

ist es möglich bei der Abfrage die Anzahl der Treffer zu bestimmen z.B. nur 50 Treffer.

Danke
Marek

Carsten Czarski hat gesagt…

Hallo,

DBMS_LDAP bietet diese Möglichkeit leider nicht an; man kann in der SQL-Abfrage natürlich ein WHERE ROWNUM < x anhängen, aber dann werden dennoch alle Ergebnisse vom LDAP-Server geholt ...

Viele Grüße

-Carsten

Anonym hat gesagt…

ich würde gerne das ganze in einer Function verwenden, und als table casten, aber das gibt mir immer den Fehler cannot fetch rows from a non nested object.
Wenn ich nun einen eigenen type erstelle und den caste, dann bekomme ich den Fehler inkonsistente datentypen.
im reinen sql fenster funktioniert es problemlos.

select
cast(
multiset(
select * from table(cast(
ldapquery.query(
'', -- Servername oder IP-Adresse
389, -- TCP/IP-POrt
'',-- Die Suche erfolgt ab diesem Eintrag im LDAP-Baum
'(&(objectCategory=group))', -- Suchabfrage
'distinguishedname,sAMAccountName,mailnickname,AdsPath,CN', -- Rückgabefelder
10
) -- Timeout für Abfrage: 3 Sekunden
as t_adsget))
)as t_table_getUsersADS
)
into v_ret from dual;

Anonym hat gesagt…

Hallo

ich bekomme es einfach nicht hin Type und Package sind erstellt dann versuche ich das Teststatement auszuführen


select * from table(
ldapquery.query(
'AD Servername', -- Servername oder IP-Adresse
3268, -- TCP/IP-POrt
'', -- Die Suche erfolgt ab diesem Eintrag im LDAP-Baum
'(cn=*)', -- Suchabfrage
'cn,c', -- Rückgabefelder
3 -- Timeout für Abfrage: 3 Sekunden
)
);

bekome dann die meldung ora-03113 Unerwartetes ende der Kommunikation

habe keine Idee was ich da falsch mache?!?!?

Carsten Czarski hat gesagt…

Hallo,

das sieht nach einem Netzwerkproblem aus ... ist em Ende wirklich der LDAP-Server .. ist dieser einigermaßen LDAP-konform ..

Bei einem ORA-3113 wird i.d.R. noch eine Tracedatei geschrieben - vielleicht findet man dort mehr Informationen ...

Beste Grüße und ein gutes neues Jahr 2012!

-Carsten Czarski

Anonym hat gesagt…

Hallo Carsten,

beim Ausführen des Beispiel-Selects erhalte ich noch folgende Fehlermeldung:


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 "APEX_TEST.LDAPQUERY", line 91


Muss mir der DBA hier noch irgendwelche Rechte geben?

Danke + Gruß

Daniel

Carsten Czarski hat gesagt…

Hallo,

jawohl - der DBA muss den Netzwerk-Connect auf den externen LDAP-Server zuerst mit dem Packet DBMS_NETWORK_ACL_ADMIN freigeben ...

Beste Grüße

-Carsten

Anonym hat gesagt…

Ok, das hat jetzt funktioniert.

Danke.

Gruß, Daniel

Anonym hat gesagt…

Ich erhalte nun die Meldung

ORA-31202: DBMS_LDAP: LDAP client/server error: No such object. 0000208D: NameErr: DSID-031001E5, problem 2001 (NO_OBJECT), data 0, best match of:
''
ORA-06512: at "SYS.DBMS_SYS_ERROR", line 86
ORA-06512: at "SYS.DBMS_LDAP", line 1487
ORA-06512: at "SYS.DBMS_LDAP", line 312
.

Ist das ein Anmeldeproblem bei LDAP oder stimmt meine Abfrage nicht?

Danke + Gruß

Daniel

Carsten Czarski hat gesagt…

Hallo,

da ist wahrscheinlich was im LDAP-Server selbst schiefgegangen - am besten nehmen wir das "offline" ...

Schreib' mich doch einfach mit ein paar Details (welche konkrete Abfrage, welcher LDAP-Server) an ...

Beste Grüße

-Carsten

Anonym hat gesagt…

Ok, hab Dir eben eine Email geschickt.

Danke + Gruß

Daniel

Anonym hat gesagt…

Hallo, das funktioniert alles Super, Vielen Dank dafür.
Ich habe nur ein Problem ich lese verschiedene Gruppen aus und suche dann die Memebers dazu raus. Dort laufe ich auf eine Beschränkung von 1499 Sätzen. Gibt es irgendwo noch einen Parameter der wie z.B. sizelimit in nem VB-Script heisst??? Wie könnte man diese Problematik umgehen? Einstellung LDAP-Server? LG Dieter

Carsten Czarski hat gesagt…

Hallo Dieter,

aus dem Paket DBMS_LDAP ist mir keine entsprechende Einstellung bekannt. Es kann durchaus sein, dass der LDAP Server so konfiguriert ist, dass er maximal 1499 oder 1500 Ergebnisse ausliefert - LDAP Server sind ja gerade für Einzel-Lookups ausgelegt - "Massen-Selects" werden oft verhindert ...

Beste Grüße

-Carsten

Anonym hat gesagt…

Hi,

ich habe alles genau wie beschrieben gemacht und scheitere aber bereits beim Testen des ersten SQL. Dort erhalte ich folgende Fehlermeldung:

Fehlerbericht:
ORA-06550: Zeile 4, Spalte 27:
PLS-00302: Komponente 'LDAPQUERY' muss deklariert werden
ORA-06550: Zeile 4, Spalte 6:
PL/SQL: Statement ignored
ORA-06512: in Zeile 7

Mit der Bitte um Hilfe!

Anonym hat gesagt…

Das Problem existiert unter dem User SYS nicht, aber noch eine weitere Frage:

Was muss man tun, um mehrere Einträge zurückzuliefern? Ich erhalte nach wie vor nur den jeweils ersten Record ausgegeben (in meinem Fall den nur den ersten Mitarbeiter der AD-Gruppe).

Es wurde schonmal hier beschrieben:
ldap_resultset :=
dbms_ldap.next_entry(
ld => ldap_session,
msg => ldap_resultset
);

Allerdings steht dies exakt so in meiner TYPE-Definition und funktioniert leider nicht.

Please help! THX!!!

Carsten Czarski hat gesagt…

Hallo "Anonym",

eine Antwort auf beide Kommentare. Wenn er das Objekt "LDAPQUERY" nicht finden (bzw. nur als SYS findet), dann dürfte das daran liegen, dass es als SYS eingespielt wurde und nun versucht wird, es mit einem anderen User zu nutzen. Eine Lösung wäre, das EXECUTE Privileg zu granten.

Zwei zweiten Frage nach dem Select für mehrere Zeilen ...

Es ist im Posting unten erklärt ...
select * from table(
ldapquery.query(
'ldapserv.mydomain.com', -- Servername oder IP-Adresse
389, -- TCP/IP-POrt
'', -- Die Suche erfolgt ab diesem Eintrag im LDAP-Baum
'(cn=Czarski*)', -- Suchabfrage
'cn,c,title', -- Rückgabefelder
3 -- Timeout für Abfrage: 3 Sekunden
)
);

Beste Grüße

-Carsten

Anonym hat gesagt…

Wer das Problem mit den mehrfachen Datensätzen auch hat, hier die Lösung (danke an Carsten für den Support)! Folgende Member-Funktion ersetzen:

MEMBER FUNCTION odcitablefetch (
SELF IN OUT ldapquery,
nrows IN NUMBER,
record_out OUT ANYDATASET
)
RETURN NUMBER
IS
v_return_attrs DBMS_LDAP.string_collection;
v_att_values DBMS_LDAP.string_collection;
v_field VARCHAR2 (4000);
v_no_more_rows BOOLEAN := TRUE;
BEGIN
record_out := NULL;
v_return_attrs := ldapquery_helper.tokenize (q_return_fields);

BEGIN
ANYDATASET.begincreate (DBMS_TYPES.typecode_object,
SELF.row_types,
record_out
);
record_out.addinstance;
record_out.piecewise ();

FOR i IN v_return_attrs.FIRST .. v_return_attrs.LAST
LOOP
v_att_values :=
DBMS_LDAP.get_values (ld => ldap_session,
ldapentry => ldap_resultset,
attr => v_return_attrs (i)
);
v_field := '';

IF v_att_values.EXISTS (0)
THEN
FOR idx IN v_att_values.FIRST .. v_att_values.LAST
LOOP
IF v_field IS NULL
OR (LENGTH (v_field) + LENGTH (v_att_values (idx))) <
4000
THEN
v_field := v_field || v_att_values (idx) || ',';
END IF;
END LOOP;

v_field := SUBSTR (v_field, 1, LENGTH (v_field) - 1);
ELSE
v_field := NULL;
END IF;

record_out.setvarchar2 (v_field);
END LOOP;

record_out.endcreate;
ldap_resultset :=
DBMS_LDAP.next_entry (ld => ldap_session,
msg => ldap_resultset);
EXCEPTION
WHEN OTHERS
THEN
v_no_more_rows := FALSE;
END;

IF NOT v_no_more_rows
THEN
BEGIN
record_out.endcreate;
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;

record_out := NULL;
END IF;

RETURN odciconst.success;
END;

Anonym hat gesagt…

Das Problem

ORA-06550: Zeile 4, Spalte 24:
PLS-00302: Komponente 'LDAPQUERY' muss deklariert werden
ORA-06550: Zeile 4, Spalte 6:
PL/SQL: Statement ignored

tritt auf ein einem Schema, das eine Tabelle besitzt die den gleichen Namen hat wie das Schema.

Problem reproduzieren:
- Im Schema TESTTEST arbeiten

- ldapquery einrichten und testen
select SELECT * FROM TABLE( ldapquery.query(... funktioniert

- Tabelle TESTTEST.TESTTEST erzeugen
select SELECT * FROM TABLE( ldapquery.query(... funktioniert noch

- Type ldapquery kompilieren: Select funktioniert nicht mehr, Fehlermeldung s.o.

Noch aufgefallen:
Die Spalte "24" in der Fehlermeldung hängt von der Länge des Schemanamens ab:
Spalte = Länge(Schemaname)+16

Ralf Merker hat gesagt…

Hallo Carsten,

der Code ist extrem hilfreich. Vielen Dank dafür.

Wie kann man von einem Objekt den DN zurückbekommen (also die Position des Treffers im Baum ...), dieser lässt sich nicht normal auslesen (ist ja auch kein reguläres Attribut)?

Carsten Czarski hat gesagt…

Hallo Ralf,

das fehlt tatsächlich - im Paket DBMS_LDAP gibt es dafür die Funktion GET_DN. Man müsste zwei Dinge tun.

Im ODCITableDescribe muss eine Spalte für den DN hinzugefügt werden, im ODCITableFetch der entsprechende DBMS_LDAP.GET_DN Call ausgeführt werden.

Ich schaue mal, ob ich die Tage dazu komme ...

Beste Grüße

Carsten

Ralf Merker hat gesagt…

Hallo Carsten,

ich bekomme es leider nicht alleine hin.

Ich erhalte immer wieder PLS-00306 Falsche Anzahl oder Typen von Argumenten in ODCITABLEDESCRIBE.

Basti hat gesagt…

Hallo Carsten,

ich bin gerade auf Suche nach einer Möglichkeit einen User über einen LDAP Server zu authentifizieren. Das bedeutet, dass automatisch die User Daten von der Datenbank und dem LDAP Server Synchron sind. Ich möchte dies ohne weitere Oracle Software machen. Ich habe viel mit dem DBMS_LDAP Package und auch mit dem DBMS_LDAP_UTL Package probiert,aber keine Lösung gefunden. Ich kann zwar einen global User mit der DN des LDAP Server erstellen. Aber ich kann mich dann nicht mit diesem Account auf der Datenbank anmelden. Irgendwelche Ideen oder Tipps?

Viele Grüße Basti

Carsten Czarski hat gesagt…

Hallo Basti,

wenn ich das richtig sehe, geht es Dir darum, einen Datenbank-User gegen LDAP zu authentifizieren. Das geht im Prinzip schon, ist aber im Feature "Enterprise User Security" enthalten - was wiederum eine Lizenz der "Advanced Security Option" erfordert.
http://docs.oracle.com/database/121/DBIMI/toc.htm

Das ist allerdings eine sehr grundsätzliche Änderung in der Userverwaltung der Datenbank; mit Bedacht einzusetzen.

Welchen Zweck verfolgst Du denn ...? Geht es um die Endbenutzer für eine Applikation (APEX, PHP, Java ...?) In diesem Fall würde ich die Nutzerverwaltung in Tabellen realisieren und die LDAP-Authentifizierung in der Applikation durchführen (so macht Application Express es ja auch) ...

Hilft Dir das?

Beste Grüße

Carsten

Basti hat gesagt…

Hallo Carsten,

ich versuche den Login für den LDAP und die Datenbank synchron zu halten. Wenn ein User Probleme mit dem einloggen hat, soll er sich an den LDAP Administrator wenden und dort sein Passwort zurücksetzen lassen.Das Datenbank Passwort soll dann automatisch auch zurückgesetzt werden. Ich habe dies schon über "Identified Globally as 'LDAP_DN'" probiert. Aber beim einloggen kommt die Fehlermeldung das die Anmeldedaten falsch sind.

Es soll später für keine Applikation verwendet werden.

Der Benutzer erstellt seinen User auf einer Website, indem er seinen LDAP Account und Passwort eingibt.
Hierbei wird nach einem erfolgreichem LDAP_BIND ein Datenbankbenutzer mit den gleichen Daten erstellt.Dann kann dieser sich mit seinen Daten von dem LDAP Server in der Datenbank anmelden.

Viele Grüße Basti

Carsten Czarski hat gesagt…

Hi Basti,

was Du natürlich tun kannst, wäre eine Eigenimplementierung. Nach dem Ändern des PW im LDAP könntest Du in der DB ein ALTER USER IDENTIFIED BY .. absetzen. Damit kannst Du die Passwörter in Richtung LDAP -> Datenbank schon mal synchron halten. Umgekehrt ist es schwieriger - hier lässt sich u.U was mit DB Triggern machen.

IDENTIFIED GLOBALLY gehört zur erwähnten Enterprise Security - erfordert also auf jeden Fall eine Lizenz von Advanced Security; und da müsstest Du Dich genau an die Doku halten.

Beste Grüße

Carsten

Anonym hat gesagt…

Hallo,

den Code zur Abfrage eines LDAP-Servers finde ich sehr interessant. Leider tritt bei uns das Problem auf, dass als Ausgabe leere Zeilen mit einer Spalte erscheinen, unabhängig davon, welche Rückgabefelder man angibt. Die Anzahl der Treffer scheint zu passen. Offenbar liefert die Abfrage "v_att_values.exists(0)" immer false zurück. Woran könnte das liegen? Das Ganze wurde auf Oracle 10.2.0.4.0 getestet.

Danke und Gruß
Andreas

Carsten Czarski hat gesagt…

Hallo Andreas,

welche SQL-Query setzt Du denn ab und welches Ergebnis kommt zurück ...?

Beste Grüße

Carsten

Anonym hat gesagt…

Servus,

tolle Funktion. Nur gibt mir mein LDAP Max 250 Einträge zurück ...
Wie könnte ich das noch Abfagen? Aktuell läuft er ein einen ORA-31202 Sizelimit exceeded ...

Größe Tobias

Carsten Czarski hat gesagt…

Hallo Tobias,

hier sind einige Links zum Thema:
https://oracle-base.com/misc/comments?page_id=249
https://stackoverflow.com/questions/28510194/how-to-perform-large-greater-than-sizelimit-ldap-queries-with-oracle-dbms-ldap

Letztlich ist DBMS_LDAP nicht dazu vorgesehen, größere Datenmengen vom LDAP auf die Datenbankseite zu übertragen; du könntest in einer loop mehrere (dafür selektivere) LDAP Abfragen absetzen.

Ich hoffe, das hilft ...

Grüße

-Carsten

Anonym hat gesagt…

Guten Morgen,
danke für deine schnelle Antwort.
Mir würde schon reichen, wenn ich in einem SQL Statment die Antwort "Es sind mehr als 250 Treffer, bitte Sucher verfeinern." zurückgebkommen würde, und nicht einen ORA fehler. Ich teste gerade schon mit EXCEPTIONS aber ich finde nicht die richtige Stelle, bzw gibt es Probleme mit den Rückgabewerten innerhalb der Funktionen.

Grüße Tobi

Carsten Czarski hat gesagt…

Hi Tobi,

ich denke, Du müsstest der Member-Function ODCITableStart einen Exception Handler hinzufügen; diese gibt dann eben nicht mehr ODCISuccess zurück, sondern löst wirklich eine Exception aus ... du kannst ja mal ein wenig mit raise_application_error experimentieren ...

Beste Grüße

-Carsten

Beliebte Postings