Non so voi, ma a me LDAP ha sempre fatto venire il mal di testa.
Ho deciso di scrivere questi appunti su LDAP perchè a distanza di anni non sono mai riuscito a trovare una documentazione di base che spiegasse i pochi concetti fondamentali in un modo dignitosamente chiaro.

LDAP viene utilizzato per la memorizzazione di informazioni relativamente statiche che non cambiano molto nel tempo. Parlando di LDAP risulta molto calzante l’affermazione “scrivi una volta e leggi molte volte” ecco perchè LDAP è lo standard di fatto ad esempio per l’archiviazione degli utenti sui server di posta.

LDAP è un protocollo che permette di gestire “servizi di directory”. Nel caso specifico le directory sono insiemi di dati organizzati ad albero.
Non vorrei dilungarmi molto sulla logica “ad albero”. In letteratura un albero è una struttura dati con una radice, dei rami e delle foglie. La radice è sempre unica mentre i rami e le foglie possono crescere a piacere.
L’esempio più adatto per capire di cosa stiamo parlando è la struttura di un file system, diciamo Windows o Unix. In entrambi i casi abbiamo una radice univoca, “C:\” oppure “/”, abbiamo dei rami che sono le cartelle/directory e delle foglie che sono i singoli file.

All’interno di un albero LDAP le entry sono degli oggetti composti ottenuti appunto “per composizione” a partire da oggetti più semplici che contengono attributi e valori ad essi associati.
Gli “oggetti semplici” di cui sto parlando vengono chiamati Object Class (OC) e possono essere creati o estesi ad hoc in base alle esigenze specifiche o utilizzati attingendo da un insieme standard ampiamente condiviso che sono gli schemi LDAP.

Questo è l’elenco degli schemi fondamentali così come documentato dal sito di OpenLDAP:

core.schema
OpenLDAP core (required)
cosine.schema
Cosine and Internet X.500 (useful)
inetorgperson.schema
InetOrgPerson (useful)
misc.schema
Assorted (experimental)
nis.schema
Network Information Services (FYI)
openldap.schema
OpenLDAP Project (experimental)

Gli schemi non sono altro che file di configurazione all’interno dei quali sono presenti le Object Class previste per quel particolare schema. Le Object Class più comuni ed utilizzate sono ad esempio:

  • person. Grazie a questa OC abbiamo la possibilità di accedere agli attributi “sn” (surname) e “cn” (commonname).
  • inetOrgPerson. Grazie a questa OC abbiamo la possibilità di accedere agli attributi “mobile”, “mail” e “uid” (userid).
  • organization. Grazie a questa OC abbiamo la possibilità di accedere all’attributo “o” (organizationName).
  • organizationalUnit. Grazie a questa OC abbiamo la possibilità di accedere all’attributo “ou” (organizationalUnitName).
  • dcObject. Grazie a questa OC abbiamo la possibilità di accedere all’attributo “dc” (domainComponent).


Ogni OC ha alcuni campi obbligatori, che devono essere valorizzati necessariamente e altri che invece sono solo opzionali.
Dato un albero LDAP possiamo quindi associare a ciascuna entry, sia essa un ramo o una foglia, una o più OC in modo da avere a disposizione un insieme di attributi all’interno dei quali poter memorizzare le informazioni di interesse.
Le Object Class possono essere di 3 tipi:

  • Strutturali, si riferiscono a persone o cose fisiche (utenti, account, aziende, organizzazioni, provider, …).
  • Ausiliarie, aggiungono attributi più o meno generici a delle OC di tipo strutturale.
  • Astratte, rappresentano delle classi o categorie di base generalmente non utilizzabili autonomamente. Un esempio comune è la OC “top” che è la radice di qualunque gerarchia LDAP. Essa offre l’attributo objectClass ma di fatto viene sempre ereditata da tutte le altre OC strutturali.


Vediamo un esempio concreto:

Se il mio blog permettesse agli utenti di iscriversi ad una mailing list e se questi utenti venissero salvati su di un mio LDAP probabilmente la sua struttura ad albero potrebbe essere simile alla seguente:

 

  • blogspot.com”. E’ la radice dell’albero e ad essa associo almeno la OC dcObject che mi permette quindi di memorizzare i valori desiderati “com” e “blogspot” usando l’attributo “dc”.
  • bummolo”. E’ l’unico ramo dell’albero subito sotto la radice e ad esso associo la OC organizationalUnit che mi permette quindi di memorizzare il valore desiderato “bummolo” usando l’attributo “ou”.
  • lucacianci”. E’ una foglia del mio albero presente all’interno del ramo com–>blogspot–>bummolo appena descritto. A questa foglia associo la OC inetOrgPerson che mi permette quindi di memorizzare il valore desiderato “lucacianci” usando l’attributo “uid”.


All’interno di un albero LDAP, una entry o record viene identificata univocamente con quello che viene chiamato Distinguished Name (dn).
Il mio record sarebbe quindi identificato in questo modo:

dn = (uid=lucacianci,ou=bummolo,dc=blogspot,dc=com)

Potete pensare al DN come alla chiave primaria del nostro record.
Se osservate bene, il dn non è altro che un percorso a ritroso che dalla foglia dell’albero risale all’indietro fino alla sua radice. In generale il dn esiste per qualunque elemento dell’albero LDAP anche non foglia, quindi ad esempio il dn della entry bummolo sarebbe:

dn = (ou=bummolo,dc=blogspot,dc=com)

In questo modo se successivamente dovessi registrare anche gli iscritti al mio blog di cucina www.scaccione.blogspot.com, potrei utilizzare lo stesso albero LDAP ma introducendo un nuovo ramo “scaccione” subito sotto al ramo “blogspot”, in questo modo avrei automaticamente creato una separazione logica tra i record degli utenti pur memorizzandoli tutti all’interno del mio albero LDAP.

I concetti spiegati fino a questo momento possono sembrare un pò “contorti”.
Perchè usare dei campi con nomi così strani?
Perchè ragionare in termini di albero di directory?
Non basta un normale database?
La risposta è abbastanza semplice, LDAP è uno standard di fatto ragion per cui attenersi alle Object Class definite negli schemi LDAP condivisi ci permette di rimanere standard a livello di struttura dati e ci mette in condizione di poter accedere facilmente a servizi ldap di terze parti che seguono ovviamente lo stesso standard. Lo stesso non si potrebbe dire nel caso di un database costruito ad hoc con tabelle e campi specifici.

Ciascun oggetto, nodo, all’interno del nostro albero LDAP deve essere associato ad almeno una Object Class.
La OC di partenza è sempre la classe “top” e da questa si diparte una gerarchia di OC molto articolata che tende a coprire i possibili campi di utilizzo di un sistema LDAP.
In tabella riporto una sintesi delle OC di cui abbiamo parlato fino ad ora:

Object Class OC padre Attributi obbligatori Attributi opzionali
top objectClass
dcObject top dc
organization top o userPassword
searchGuide
seeAlso
businessCategory
x121Address
registeredAddress
destinationIndicator
preferredDeliveryMethod
telexNumber
teletexTerminalIdentifier
telephoneNumber
internationaliSDNNumber
facsimileTelephoneNumber
street
postOfficeBox
postalCode
postalAddress
physicalDeliveryOfficeName
st
l
description
organizationalUnit top ou userPassword
searchGuide
seeAlso
businessCategory
x121Address
registeredAddress
destinationIndicator
preferredDeliveryMethod
telexNumber
teletexTerminalIdentifier
telephoneNumber
internationaliSDNNumber
facsimileTelephoneNumber
street
postOfficeBox
postalCode
postalAddress
physicalDeliveryOfficeName
st
l
description
person top sn
cn
userPassword
telephoneNumber
seeAlso
description
organizationalPerson person title
x121Address
registeredAddress
destinationIndicator
preferredDeliveryMethod
telexNumber
teletexTerminalIdentifier
telephoneNumber
internationaliSDNNumber
facsimileTelephoneNumber
street
postOfficeBox
postalCodeobjectClass: personorganizationalPersonperson
postalCode
postalAddress
physicalDeliveryOfficeName
ou
st
l
inetOrgPerson organizationalPerson audio
businessCategory
carLicense
departmentNumber
displayName
employeeNumber
employeeType
givenName
homePhone
homePostalAddress
initials
jpegPhoto
labeledURI
mail
manager
mobile
o
pager
photo
roomNumber
secretary
uid
userCertificate
x500uniqueIdentifier
preferredLanguage
userSMIMECertificate
userPKCS12

Da questo indirizzo è possibile vedere una tabella più completa e dettagliata: http://oav.net/mirrors/LDAP-ObjectClasses.html

Implicitamente gli attributi obbligatori di una OC si candidano a diventare distinguished name dell’oggetto stesso e concorrono alla composizione del dn relativo ad una delle foglie.  
Vediamo a questo punto come vengono definiti i vari nodi del nostro albero usando un file standard in formato LDIF (LDAP Date Interchange Format) e cerchiamo di capire quest’ultimo concetto relativo ai dn.

dn: dc=blogspot, dc=com
objectClass: top
objectClass: dcObject
description: Questo è il dominio di riferimento dei miei blog
dc: blogspot

dn: ou=bummolo, dc=blogspot, dc=com
objectClass: top
objectClass: organizationalUnit
description: Questo è il ramo per gli utenti del blog di informatica
ou: bummolo

dn: ou=scaccione, dc=blogspot, dc=com
objectClass: top
objectClass: organizationalUnit
description: Questo è il ramo per gli utenti del blog di cucina
ou: scaccione

dn: uid=lucacianci, ou=bummolo, dc=blogspot, dc=com
objectClass: top
objectClass: inetOrgPerson
uid: lucacianci
sn: Cianci
givenName: Luca
cn: Luca Cianci
l: Milano
mobile=123123123
mail=luca.cianci@bummolo.com

Ecco che da questa estrazione LDIF si comprende finalmente l’appartenenza di un determinato nodo ad una o più Object Class dei nostri schemi LDAP.
La classe top viene associata ad ogni nodo poi, da li in giù, siamo noi a decidere “la natura” di un determinato nodo quindi nel caso specifico “bummolo” è una organizationalUnit mentre “lucacianci” essendo un nodo di tipo foglia che conterrà le informazioni degli utenti, sarà una Object Class di tipo inetOrgPerson e questo mi permette di avere accesso a tutti gli attributi di organizationalPerson e person sfruttando la gerarchia di OC.

Infine parliamo rapidamente di come ricercare i dati all’interno del nostro albero.
Premesso che esistono dei tool grafici che semplificano la vita, considerate sempre che il vostro tool preferito si baserà quasi sicuramente sul tool a riga di comando “ldapsearch” quindi può essere utile capire la sintassi prevista per impostare i filtri di ricerca.
In generale ldapsearch si usa più o meno in questo modo:

ldapsearch -h mio_host -p mia_porta -b base_ricerca filtro_ricerca

Ovviamente consultando un man a riga di comando avrete accesso a molte altre opzioni.

Concentriamoci sui due parametri finali:

  • base_ricerca permette di definire il punto dell’alberatura a partire dal quale effettuare la ricerca. Se vogliamo cercare un utente sul blog bummolo potremo impostare quindi “ou=bummolo, dc=blogspot, dc=com” andando in questo modo ad escludere l’alberatura che non ci interessa.
  • filtro_ricerca rappresenta i dati che siamo interessati a ricercare. Attenti che questo parametro usa la notazione prefissa degli operatori logici. Gli operatori previsti sono i seguenti:
  • & (and)
  • | (or)
  • ! (not)
  • ~= (circa uguale)
  • >= (maggiore o uguale)
  • <= (minore o uguale)
  • * (qualunque)


Ecco quindi qualche esempio di filtro di ricerca:

  • (objectclass=organizationalUnit), tutti i recod di tipo organizationalUnit.
  • (cn=Luca*), tutti gli utenti che si chiamano Luca.
  • (|(uid=lucacianci)(uid=paolinopaperino)), il record di Luca Cianci o di Paolino Paperino.
  • (&(|(uid=lucacianci)(uid=paolinopaperino))(objectclass=inetOrgPerson)), il record di Luca Cianci o di Paolino Paperino che siano però di tipo inetOrgPerson.
  • (mail=*), qualunque record con l’attributo “mail” valorizzato.
  • (sn>=Cianci), qualunque record alfabeticamente successivo a “Cianci”.


Spero di aver reso un pò meno fumoso il mondo LDAP e di aver chiarito almeno i concetti fondamentali che stanno alla base della struttura.