ActiveLDAP, ActiveRecord pour le LDAP

ActiveRecord est un patron de conception pour le mapping entre du code et une base de données. Son implémentation la plus connues est celle de l'ORM utilisé par Ruby on Rails : ActiveRecord. Chaque table de la base de données est représentée par une classe, et chaque ligne d'une table par une instance d'une classe. Les attributs des objets sont les colonnes de la table en base. De plus, chaque objet est responsable de sa persistance, de fournir des opérations basiques de type CRUD (Create, Read, Update, Delete), et de la gestion de sa partie métier. Ce patron de conception a été popularisé par Martin Fowler qui sera un des speakers de l'USI 2010.

ActiveLDAP est une bibliothèque en Ruby qui se veut respecter au maximum l'interface proposée par ActiveRecord, mais pour un annuaire LDAP. Cet article va donc faire un tour des possibilités d'ActiveLDAP. On verra dans un premier temps comment l'installer pour fonctionner avec Ruby ou JRuby, puis comment l'utiliser à la manière d'ActiveRecord. Si vous avez quelques notions de Ruby et LDAP cela vous aidera à mieux comprendre l'article.

L'installation

Avant d'installer ActiveLDAP, vous devez avoir trois pré-requis d'installés :

  • Un interpréteur Ruby
  • Une librairie cliente LDAP
  • Un annuaire LDAP

ActiveLDAP fonctionne avec Ruby 1.8 ou 1.9 et avec l'interpréteur Ruby "classique" ou avec JRuby.

L'annuaire LDAP doit être compatible avec la norme (OpenLDAP, ActiveDirectory, OpenDS, etc.). Par contre, il faut qu'il soit configuré pour accepter les requêtes root_dse. Ce type de requêtes permet d'accéder à la racine de l'annuaire, qui contient une partie de la configuration de celui-ci. En particulier, la branche où se trouve la définition du schéma des classes et des attributs. En effet, ActiveLDAP se base sur ces informations pour construire dynamiquement les classes Ruby, de la même manière qu'ActiveRecord a besoin d'accéder au schéma de la base pour construire le mapping entre une table et un objet Ruby.

Il y a plusieurs choix pour le client LDAP en fonction de votre environnement :

Si vous utilisez Ruby, je vous conseille d'utiliser Ruby/LDAP qui est un binding de l'API LDAP C en Ruby. Il faut donc avoir une version existant de l'API C pour pouvoir l'utiliser. Sur la plupart des Linux, celle-ci est disponible sous forme de paquet et est installée de base sous MacOS X. Si vous avez le choix, je vous recommande l'API développée par le projet OpenLDAP, qui est considérée comme la plus rapide. Si vous utilisez JRuby, je conseille JNDI.

Cette bibliothèque est soit disponible au format GEM, soit au format classique de librairie Ruby. Le meilleur moyen de l'installer dans une application Rails est encore de la spécifier comme dépendance dans le fichier environnement.rb :

# dans environment.rb
config.gem :activeldap

puis en ligne de commande :

rake gems:install

Il faut ensuite lancer la commande de scaffolding :

script/generate scaffold_active_ldap

qui va créer le fichier config/ldap.yml de configuration de connexion à l'annuaire LDAP :

On remarque qu'il est découpé de la même manière que le fichier database.yml, en plusieurs sections en fonction de l'environnement Rails, on reste donc dans ce que l'on connaît avec ActiveRecord.

Un peu de code

Il existe un deuxième générateur (script/generate model_active_ldap) qui permet de créer plus rapidement un modèle, le fichier de tests associé et un fichier de fixtures YAML. Nous n'allons pas l'utiliser dans cet article.

Tout d'abord il faut créer une classe Ruby qui va servir de mapping entre le LDAP et Ruby.

class User < ActiveLdap::Base

Nous sommes en terrain connu, c'est la même chose avec qu'ActiveRecord.

Une des différences avec ActiveRecord c'est qu'il n'y a pas de mapping entre un objet LDAP et une classe Ruby. En effet, dans une base de données relationnelle, un enregistrement ne peut avoir qu'une seule forme, celle de la table le contenant. Alors qu'en LDAP, un objet peut avoir plusieurs formes (classe structurelle et classes auxiliaires). On ne peut donc pas faire de mapping 1-1 entre le nom d'une classe Ruby et un type d'objet LDAP. Il faut donc rajouter un peu de glue qui va faire le lien entre notre classe Ruby et une ou plusieurs classes LDAP (de type structurelle, abstraite ou auxiliaire) :

ldap_mapping :dn_attribute => "cn",
  :prefix => "ou=people,dc=octo,dc=com",
  :classes => ["inetOrgPerson"],
  :scope => &#58;one

L'option :

  • :dn_attribute, permet de spécifier l'attribut "pivot" pour cette classe (le rdn pour les LDAPeux)
  • :prefix, permet de spécifier dans quelle branche de notre annuaire on se positionne
  • :classes, permet de lister les classes LDAP que l'on souhaite mapper à cette classe Ruby
  • :scope, permet de spécifier le niveau de recherche des éléments (one, sub, base)

Et c'est tout !

On a donc maintenant un mapping d'une partie de notre annuaire. On peut donc effectuer des recherches et des modifications comme sur un objet ActiveRecord :

  • Permet de faire une recherche sur tous les User nommé rcs :

    User.find("rcs")
    
  • Permet de faire une recherche sur tous les User dont le nom commence par r :

    User.find("r*")
    
  • Permet de récupérer le premier utilisateur :

    user = User.find(:first)
    
  • Permet de trouver tous les utilisateurs dont le gidNumber à pour valeur 1003 :

    User.find(:all, :attribute => 'gidNumber', :value => '1003')
    
  • Permet d'accéder à la valeur du Common Name d'un utilisateur, c'est-à-dire son nom complet :

    user.cn
    
  • Permet de spécifier une valeur à l'attribut cn :

    user.cn = "toto"
    
  • Permet de sauvegarder une entrée :

    user.save
    
  • Permet de faire une recherche LDAP quelconque :

    User.search(:base => 'dc=octo,dc=com', :filter => '(uid=rc*)',
      :scope => :sub, :attributes => ['uid', 'cn'])
    

Les associations

ActiveLDAP fourni deux des associations que fourni ActiveRecord :

  • belongs_to
  • has_many

Elles ont la même signification qu'avec ActiveRecord, et avec globalement les mêmes paramètres. Par exemple, si on veut qu'un utilisateur appartienne à un groupe :

belongs_to :groups, :class_name => 'Group', :many => 'memberUid', :foreign_key => 'uid'

Avec :

  • :class_name, le nom de la classe Ruby représentant un groupe
  • :many, le nom de l'attribut mulitvalué, qui regroupe les références vers d'autres objets
  • :foreign_key, le nom de la "clé étrangère", en LDAP le nom de l'attribut

De même pour une relation has_many :

has_many :members, :class_name => "User", :wrap => "memberUid", :primary_key => 'uid'

Avec :

  • :wrap, le nom de l'attribut multi-valué

Conclusion

ActiveLDAP permet d'offrir enfin une API simple d'utilisation pour faire du LDAP. En effet, la plupart des librairies LDAP (que ce soit en Ruby ou non) sont assez proche de la norme LDAP, et ne proposent donc qu'un faible niveau d'abstraction.

À ma connaissance il n'existe pas d'API équivalente et ce quelque soit le langage. Malgré tout, cette API est assez lente et possède encore quelques limitations pour être iso-fonctionnel avec ActiveRecord. De plus, l'abstraction de l'annuaire n'est pas encore au niveau de ce que des ORMs modernes sont capables de fournir. Néanmoins, pour 90% des utilisations d'un LDAP dans une application Rails, ActiveRecord répond largement au besoin. C'est donc un grand pas en avant pour le LDAP !