Projets
Pages enfant
  • 3.5.1 Accès à l'annuaire LDAP en lecture

Bon pour relecture

Sommaire :


Cette page montre comment on peut accéder en lecture aux information contenues dans un annuaire LDAP. Pour un accès en écriture se référer à Accès à l'annuaire LDAP en écriture.

Le lien vers le SI de l'établissement se fait en particulier vers l'annuaire LDAP. esup-commons offre des fonctionnalités concernant les utilisateurs, les groupes et de manière plus générale n'importe quelle entité LDAP :

  • Recherche d'utilisateurs,
  • Recherche des attributs d'un utilisateur donné,
  • Validation d'un filtre pour un utilisateur donné,
  • Recherche de groupes,
  • Recherche des sous-groupes d'un groupe,
  • Recherche des membres d'un groupe,
  • Validation d'un filtre pour un groupe donné,
  • Recherche d'entités,
  • Recherche des attributs d'une entité donnée,
  • Validation d'un filtre pour une entité donnée.

    Les fonctionnalités offertes sont beaucoup mois ambitieuses que celle de l'actuel canal annuaire (cf Recherche dans un annuaire 'pages blanches'), l'accent a été mis sur la facilité de mise en œuvre. En revanche, il ne tient qu'à un développeur motivé d'enrichir le source actuel pour amener les fonctionnalités manquantes.

    Tous les beans concernant LDAP sont définis dans le fichier de configuration /properties/ldap/ldap.xml.

De nombreuses classes sont disponibles :

Manipulation des utilisateurs LDAP

Nous ne détaillons dans ce document que la manipulation des utilisateurs LDAP. Il sera facile au lecteur d'étendre les principes montrés ci-dessous à la manipulation des groupes et des entités LDAP quelconques.

Utilisation basique

L'implémentation utilisée pour manipuler seulement les utilisateurs LDAP est SearchableLdapUserServiceImpl.

La déclaration d'un bean ldapUserService de cette classe ressemblera à :

	<bean id="ldapUserService"
		class="org.esupportail.commons.services.ldap.SearchableLdapUserServiceImpl"
		lazy-init="true"
		>
		<property name="i18nService" ref="i18nService"/>
		<property name="ldapTemplate" ref="ldapTemplate"/>
		<property name="dnSubPath" value="${ldap.dnSubPath}"/>
		<property name="idAttribute" value="${ldap.uidAttribute}"/>
		<property name="attributesAsString" value="${ldap.attributes}"/>
		<property name="searchAttribute" value="${ldap.searchAttribute}"/>
		<property name="searchDisplayedAttributesAsString" value="${ldap.searchDisplayedAttributes}" />
		<property name="cacheManager" ref="cacheManager"/>
		<property name="cacheName" value=""/>
		<property name="testFilter" value="${ldap.testFilter}" />
	</bean>

... avec dans config.properties :

########################################################################
# LDAP
#
ldap.url=ldap://ldap.univ.fr:389,ldap://ldap2.univ.fr:389
ldap.userDn=uid=user,ou=comptes,dc=univ,dc=fr
ldap.password=mdp
ldap.base=dc=univ,dc=fr
ldap.dnSubPath=ou=people
ldap.uidAttribute=uid
ldap.displayNameAttribute=displayName
ldap.emailAttribute=mail
ldap.searchAttribute=cn
ldap.testFilter=cn=*bourges*
ldap.attributes=cn,displayName,employeeType,department,homeDirectory
ldap.searchDisplayedAttributes=cn,displayName,employeeType,department

La propriété uidAttribute donne le nom de l'attribut LDAP qui contient l'identifiant unique des utilisateurs de l'annuaire.

La propriété searchAttributes donne le nom de l'attribut sur lequel on effectue des recherches. Dans l'exemple ci-dessous, la recherche du mot bourges utilisera le filtre cn=bourges*.

La propriété searchDisplayedAttributes donne les noms des attributs qui seront affichés à l'utilisateur lors du choix d'un utilisateur parmi plusieurs (après une recherche dans l'annuaire). Par exemple :
La propriété otherAttributes donne les noms des attributs qui seront remontés lors des requêtes LDAP, pour être utilisés dans du code Java.




La propriété testFilter pourra être utilisée dans les classes de test JUnit.

Cette classe s'appuie sur la bibliothèque LdapTemplate  org.springframework.ldap.core.LdapTemplate:

	<bean 
		id="ldapTemplate" 
		class="org.springframework.ldap.core.LdapTemplate"
		lazy-init="true"
		>		
		<property name="contextSource" ref="contextSource"/>
	</bean>

	<bean id="contextSource"
		class="org.esupportail.commons.services.ldap.MultiUrlLdapContextSource"
		lazy-init="true"
		>
		<property name="url" value="${ldap.url}"/>
		<property name="userDn" value="${ldap.userDn}"/>
		<property name="password" value="${ldap.password}"/>
		<property name="base" value="${ldap.base}"/>
		<property name="baseEnvironmentProperties">
			<map>
				<entry key="com.sun.jndi.ldap.connect.timeout"
					value="${ldap.connectTimeout}" />
			</map>
		</property>
	</bean>

Mise en cache des requêtes LDAP

Il est possible d'injecter un gestionnaire de cache aux instances de cette classe pour leur faire cacher le résultat des requêtes afin de moins solliciter l'annuaire LDAP (pour plus de performances).

Il suffit de rajouter à la déclaration précédente :

<bean
  id="ldapUserService"
  class="[...].commons.services.ldap.SearchableLdapUserServiceImpl"
  >
  ...
  <property name="cacheManager" ref="cacheManager" />
  <property name="cacheName" value="" />
</bean>

Le bean cacheManager est en général défini dans le fichier de configuration /properties/cache/cache.xml et notamment /properties/cache/ehcache.xml Le nom du cache est optionnel.

 <cache 
    	name="org.esupportail.commons.services.ldap.CachingLdapServiceImpl"
    	maxElementsInMemory="1000" 
    	eternal="false" 
    	timeToIdleSeconds="300"
    	timeToLiveSeconds="600" 
    	overflowToDisk="true" 
    	/>

Accès aux statistiques LDAP

La classe SearchableLdapUserServiceImpl supporte également la récupération de statistiques sur son utilisation, sous forme d'une liste de chaînes de caractères internationalisées (d'où la nécessité du service d'internationalisation i18nService). Ces chaînes peuvent par exemple être affichées sur l'interface d'une application web :

Accès LDAP hors connexion

De la même manière que la classe OfflineFixedUserAuthenticator pour l'authentification,  la classe OfflineLdapServiceImpl permet de travailler hors connexion :

<bean
  id="ldapService"
  class=" [...].commons.services.ldap.OfflineLdapServiceImpl"
  />

Applications sans accès LDAP

Les applications sans accès à l'annuaire LDAP doivent tout simplement ne pas déclarer de bean ldapUserService.

Utilisation du service LDAP depuis du code Java

Exceptions

Les exceptions lancées par les appels aux méthodes de LdapUserService peuvent lancer les exceptions suivantes :

  • LdapConnectionException, lorsque l'annuaire LDAP est inaccessible,
  • LdapBadFilterException, lorsqu'un mauvais filtre est utilisé,
  • LdapMiscException, pour toute autre erreur.

Il appartient au programmeur d'attraper ou non ces exceptions en fonction du contexte de l'application.

Recherche d'un utilisateur par son identifiant

Pour rechercher un utilisateur LDAP à partir d'un identifiant par programmation, il faut appeler le service LDAP de la manière suivante :

String uid = "bourges";
LdapUser user = ldapService.getLdapUser(uid);

Cette méthode lance l'exception UserNotFoundException si l'utilisateur est introuvable.

Exercice : Chercher un utilisateur dans l'annuaire LDAP Afficher l'énoncéCacher l'énoncé

Faire en sorte que l'appui du bouton de test1.jsp n'ajoute une entrée dans la base que si la valeur de myInput correspond bien à un utilisateur de l'annuaire LDAP. Afficher un message d'erreur sinon (utiliser un message prédéfini de esup-commons).

Afficher la solutionCacher la solution

Une manière de faire possible est l'utilisation d'un validateur déclaré dans la classe Test1Controller. Pour cela, on utilisera un attribut ldapUserService pour accéder à l'annuaire LDAP (cet attribut sera initialisé par Spring) :

private LdapUserService ldapUserService;
public void setLdapUserService(LdapUserService ldapUserService) {
    this.userLdapService = userLdapService;
}
...
public void validateMyInput(
            FacesContext context,
            UIComponent componentToValidate,
            Object value) throws ValidatorException {
    String string = (String) value;
    try {
      LdapUser ldapUser = ldapService.getLdapUser(string);
    } catch (UserNotFoundException e) {
      throw new ValidatorException(
          getFacesErrorMessage(
             "LDAP_SEARCH.MESSAGE.NO_RESULT", string));
        }
    }
}

On injectera le bean ldapService dans le contrôleur Test1Controller pour qu'il puisse accéder aux services LDAP :

<bean id="test1Controller" ... >
    <property name="ldapUserService" ref="ldapUserService" />
</bean>



Recherche des utilisateurs correspondant à un motif

Pour rechercher les utilisateurs LDAP à partir d'un motif, il faut appeler le service LDAP de la manière suivante :

String token = "ourge" ;
List<LdapUser> users = ldapService.getLdapUsersFromToken(token);

La liste retournée peut éventuellement être vide.

Recherche des utilisateurs à partir d'un filtre LDAP

Pour rechercher les utilisateurs LDAP correspondant à un filtre LDAP, il faut appeler le service LDAP de la manière suivante :

String filter = "&(departmentNumber=univ*)(employeeType=student)" ;
List<LdapUser> users = ldapService.getLdapUsersFromFilter(filter);

La liste retournée peut éventuellement être vide.

Test d'un filtre LDAP

Pour tester la syntaxe d'un filtre LDAP, il faut appeler le service LDAP de la manière suivante :

String errorMessage = ldapService.testLdapFilter(filter);

Le filtre est valide si le message d'erreur retourné est null.

Vérification d'un filtre LDAP pour un utilisateur

Pour savoir si un utilisateur vérifie un filtre LDAP, il faut appeler le service de la manière suivante :

boolean matched = ldapService.userMatchesFilter(uid, filter);

La méthode retourne true si l'utilisateur vérifie le filtre.

Récupération des statistiques LDAP

La récupération des statistiques des accès LDAP se fait en appelant le service LDAP de la manière suivante :

strings = ldapService.getStatistics(locale);

Il faut auparavant s'assurer que l'implémentation du service supporte la récupération des statistiques en appelant ldapService.supportStatistics().

Les statistiques peuvent être réinitialisées en appelant la méthode ldapService.resetStatistics().

Intégration de la recherche d'utilisateurs dans les pages JSF

Nous montrons ici comment simplement inclure dans les pages web d'une application la recherche d'un utilisateur LDAP.

Cinématique

Nous prenons ici le cas d'une page d'administration, qui permet de gérer les administrateurs d'une application. L'utilisateur de l'application doit pouvoir ajouter des administrateurs en cherchant dans un annuaire LDAP.La page d'ajout, administratorAdd.jsp, ressemble à :

On souhaite que l'appui sur le bouton « Recherche LDAP » amène sur la page de recherche suivante (ldapSearch.jsp) :

L'appui sur « Suivant » doit afficher la page ldapSearchResults.jsp suivante :

L'utilisateur doit alors pouvoir sélectionner un des utilisateurs (en cliquant dessus) et revenir sur la page administratorAdd.jsp, en remplissant sa boite de dialogue.Toute cette cinématique est disponible de base dans esup-commons, nous allons détailler cet exemple pour bien comprendre son fonctionnement.

Page appelante

Le contrôleur administratorsControler, chargé de toutes les interactions avec l'utilisateur pour la partie « administration » de l'application, implémente l'interface LdapCaller. Il possède donc une méthode setLdapUid() qui pourra être appelée par le contrôleur de la recherche LDAP en cas de succès. Cela remplira d'ailleurs automatiquement la boîte de dialogue de administratorAdd.jsp puisque celle-ci est liée à la propriété ldapUid du contrôleur administratorsControler :

<e:inputText
    id="ldapUid"
    value="#{administratorsController.ldapUid}"
    required="true" />

Le source JSF du bouton « recherche LDAP » est le suivant :

<e:commandButton
    value="#{msgs['_.BUTTON.LDAP']}"
    action="ldapSearch"
    immediate="true">
  <t:updateActionListener
      value="#{administratorsController}"
      property="#{ldapSearchController.caller}" />
  <t:updateActionListener
      value="userSelectedToAdministratorAdd"
      property="#{ldapSearchController.successResult}" />
  <t:updateActionListener
      value="cancelToAdministratorAdd"
      property="#{ldapSearchController.cancelResult}" />
</e:commandButton>

Fonctionnement

Lorsque l'utilisateur appuie sur ce bouton, l'application :

  • Indique au contrôleur ldapSearchController que l'appelant est le contrôleur administratorsController en positionnant sa propriété caller (de type LdapCaller) pour appeler sa méthode setLdapUid() en cas de recherche fructueuse),
  • Positionne la propriété successResult (resp. cancelResult) du contrôleur ldapSearchController  à userSelectedToAdministratorAdd (resp. cancelToAdmlinistratorAdd). Cette valeur sera utilisée par le contrôleur ldapSearchController comme code de retour de la callback de sélection des utilisateurs (resp. de la callback d'annulation de la recherche).

La recherche LDAP proposée par esup-commons possède son propre code Java (le contrôleur ldapSearchController) et ses propres pages JSF (ldapSearch.jsp et ldapSearchResults.jsp), dont il n'est pas nécessaire de connaître le contenu pour les utiliser.

Règles de navigation

Il faut néanmoins indiquer à JSF les règles de navigation entre les différentes pages, comme indiqué ci-après (dans /properties/jsf/navigation-rules.xml).L'appui du bouton « Recherche LDAP » depuis la page administratorAdd.jsp envoie vers la page ldapSearch.jsp :

<navigation-rule>
  <display-name>administratorAdd</display-name>
  <from-view-id>/stylesheets/administratorAdd.jsp</from-view-id>
  <navigation-case>
    <from-outcome>adminAdded</from-outcome>
    <to-view-id>/stylesheets/administrators.jsp</to-view-id>
  </navigation-case>
  <navigation-case>
    <from-outcome>cancel</from-outcome>
    <to-view-id>/stylesheets/administrators.jsp</to-view-id>
  </navigation-case>
  <navigation-case>
    <from-outcome>ldapSearch</from-outcome>
    <to-view-id>/stylesheets/ldapSearch.jsp</to-view-id>
  </navigation-case>
</navigation-rule>

Sur la page ldapSearch.jsp, l'appui du bouton « Annuler » ramène sur la page administratorAdd.jsp, l'appui sur le bouton « Suivant » passe à la page ldapSearchResults.jsp s'il y a des résultats (usersFound) ; s'il n'y a pas de résultat, le résultat de la callback du bouton « Suivant » est null et la page reste inchangée.

<navigation-rule>
  <display-name>ldapSearch</display-name>
  <from-view-id>/stylesheets/ldapSearch.jsp</from-view-id>
  <navigation-case>
    <from-outcome>usersFound</from-outcome>
    <to-view-id>/stylesheets/ldapResults.jsp</to-view-id>
  </navigation-case>
  <navigation-case>
    <from-outcome>cancelToAdministratorAdd</from-outcome>
    <to-view-id>/stylesheets/administratorAdd.jsp</to-view-id>
  </navigation-case>
</navigation-rule>

Enfin, sur la page ldapSearchResults.jsp, l'appui du bouton « Annuler » (cancelToAdministratorAdd) ainsi que la sélection d'un utilisateur parmi la liste proposée (userSelectedToAdministratorAdd) ramènent tous les deux à la page administratorAdd.jsp (dans le deuxième cas, la méthode setLdapUid() du contrôleur administratorController aura été appelée) :

<navigation-rule>
  <display-name>ldapResults</display-name>
  <from-view-id>/stylesheets/ldapResults.jsp</from-view-id>
  <navigation-case>
    <from-outcome>userSelectedToAdministratorAdd</from-outcome>
    <to-view-id>/stylesheets/administratorAdd.jsp</to-view-id>
  </navigation-case>
  <navigation-case>
    <from-outcome>cancelToAdministratorAdd</from-outcome>
    <to-view-id>/stylesheets/administratorAdd.jsp</to-view-id>
  </navigation-case>
  <navigation-case>
    <from-outcome>back</from-outcome>
    <to-view-id>/stylesheets/ldapSearch.jsp</to-view-id>
  </navigation-case>
</navigation-rule>

En procédant de cette manière, le développeur peut faire appel à la recherche LDAP en utilisant le même contrôleur (ldapSearchController) depuis plusieurs pages différentes (et donc plusieurs contrôleurs associés).

Manipulation des groupes LDAP

La manipulation des groupes LDAP se fait généralement en utilisant un bean ldapGroupService implémentant l'interface LdapGroupService, par exemple de la classe SearchableLdapGroupServiceImpl.

Cette classe manipule des objets implémentant l'interface LdapGroup :

LdapGroup getLdapGroup(String id);
boolean groupMatchesFilter(String id, String filter);
List<LdapGroup> getLdapGroupsFromToken(String token);
List<LdapGroup> getLdapGroupsFromFilter(String filterExpr);
List<String> getSearchDisplayedAttributes();

Manipulation des utilisateurs et des groupes LDAP

esup-commons offre une classe qui permet de manipuler en même temps les utilisateurs et les groupes LDAP, SearchableLdapUserAndGroupServiceImpl.

En plus des méthodes offertes par les classes SearchableLdapUserServiceImpl et SearchableLdapGroupServiceImpl, cette classe permet de récupérer les utilisateurs membres d'un groupe :

List<String> getMemberIds(LdapGroup group);
List<LdapUser> getMembers(LdapGroup group);

Manipulation des entités LDAP quelconques

esup-commons permet enfin de manipuler n'importe quel type d'entité LDAP, à l'aide de la classe CachingLdapEntityServiceImpl.

  • Aucune étiquette