InterLDAP - Wiki - Main - DocumentationTechniqueOrganisation -1-

publicité
InterLDAP - Wiki - Main - DocumentationTechniqueOrganisation
Synchronisation de comptes organisations
La constitution de branches organisationnelles est un point important. De façon générale, cette
documentation décrit la mise en œuvre complète d'une branche organisationnelle à partir de données en
base de données. Par ailleurs, ce chapitre s'appuie très fortement sur les acquis précisés dans les chapitres
précédents, étant donné le fonctionnement très similaire au niveau de certains points.
La particularité, dans les exemples qui vont suivre, est que chaque nœud de l'organisation est susceptible
de contenir des utilisateurs. Objectifs L'objectif ici est de constituer une branche LDAP dans un annuaire,
reflétant une certaine organisation implémentée en base de données. A première vue, cela devrait
ressembler à l'illustration suivante.
Définition de classes d'objet LDAP Plusieurs possibilités sont offertes, le choix le plus simple reste d'utiliser
l'attribut uniqueMember parmi les attributs LDAP de base proposés dans la RFC 2256. Cet attribut LDAP
contient alors un DN vers un compte utilisateur de l'annuaire.
L'exemple suivant est la définition complète au format LDAP (RFC 2252) d'une classe d'objet auxiliaire
nommée monOrganisation. Cette classe d'objet est couplée à la classe d'objet structurel organizationalUnit
afin de constituer les entrées de base de l'arborescence à générer.
objectClass ( objectclassOid:10 NAME 'monOrganisation' DESC 'objectClass for example' AUXILIARY
MAY uniqueMember )
Auto-génération de classes JAVA Comme dans le chapitre 3.3, il s'agit de générer automatiquement les
fichiers JAVA relatifs à la classe d'objet nouvellement définie, et incluse dans le schéma de l'annuaire.
$ ant -DocName="monOrganisation" generator [...]
Comme indiqué dans le chapitre 2, les fichiers générés sont disponibles dans le répertoire src/impl, au sein
des sous-paquetages beans, jndi, objects, objects.flat, persistance.xml et service. Configuration « logicielle
» A l'identique du chapitre 3.4, il est supposé que la configuration fichier du logiciel LSC est fixée. Il s'agit
uniquement de configuration dite « logicielle », c'est à dire par l'intermédiaire de la classe Configuration.java
du paquetage org.linagora.ldap.lsc dans le répertoire src/app.
Un besoin unique, celui de définir, dans le logiciel, la branche qui va accueillir l'arborescence :
public final class Configuration { [...] public static final String DN_STRUCTURES_ORGANISATION =
Configuration.getString("dn.structures_organisation", "ou=Organisation"); [...]
Adaptation du comportement Il existe une différence majeur entre l'architecture JAVA mise en œuvre dans
le chapitre 3, où il n'y a aucune notion de structure arborescente, et l'architecture à utiliser ici. Afin de
réaliser une synchronisation de données d'une base de données vers un annuaire, il va falloir utiliser une
classe bien spécifique charger de construire l'arborescence. Dans le cas de LSC, il va s'agir principalement
de la classe de service JDBC associée à la classe d'objet LDAP qui sert de base aux entrées de
l'arborescence annuaire.
-1-
InterLDAP - Wiki - Main - DocumentationTechniqueOrganisation
Il n'est donc aucunement question ici de revenir sur le code détaillé des autres classes. Il seront indiqué par
la suite à titre d'exemple uniquement.
Classes JAVA modélisant la classe d'objet LDAP
Ces classes sont basiques, et conservent la même idée développée dans le chapitre 3.5.1 : une classe
d'objet de base et une classe « flat ». La première se nomme monOrganisation.java et est disponible dans
le paquetage org.linagora.ldap.lsc.objects, la seconde est fMonOrganisation.java dans le paquetage
org.linagora.ldap.lsc.objects.flat. Toutes deux se trouvent au sein des paquetages pré-cités, dans le
répertoire src/impl.
La seule information ajoutée est le fait que la classe est considéré au niveau JAVA comme héritant de la
classe organizationalUnit. Ce comportement est permis, car la nouvelle classe est auxiliaire, et fonctionne
au niveau JAVA comme une classe héritant d'une autre classe - au final, ce sont les entrées annuaire qui
seront importantes.
Contenu de la classe monOrganisation.java :
package org.linagora.ldap.lsc.objects;import java.util.List;public class monOrganisation extends
organizationalUnit { protected List uniqueMember; public lapTaxonomie() { super();
objectClass.add("monOrganisation"); } public List getUniqueMember() { return uniqueMember; }
public void setUniqueMember(List values) { this.uniqueMember = values; } }
Contenu de la classe fMonOrganisation.java :
package org.linagora.ldap.lsc.objects.flat;public class fMonOrganisation extends
fOrganizationalUnit { protected String uniqueMember; public String getUniqueMember() { return
uniqueMember; } public void setUniqueMember(String value) { this.uniqueMember = value; } }
L'objet BEAN
L'objet BEAN monOrganisationBean associé à la classe d'objet LDAP monOrganisation est auto-généré
aussi. Il reste relativement simple. Le BEAN auto-généré est disponible au sein du paquetage
org.linagora.ldap.lsc.beans, dans le répertoire src/impl.
Une petite modification est cependant effectuée afin de garantir que le BEAN ne puisse pas générer
lui-même le DN de l'entrée LDAP qu'il a en charge. En effet, cette opération sera effectuée plus loin, par du
code extérieur, puisque que les différents BEAN de l'arborescence à gérer possède une relation
hiérarchique.
package org.linagora.ldap.lsc.beans ;import java.lang.reflect.InvocationTargetException ; import
java.lang.reflect.Method ; import java.util.HashMap ; import java.util.Iterator ; import
java.util.List ; import java.util.Vector ; import javax.naming.NamingException ;import
org.linagora.ldap.lsc.Configuration ; import org.linagora.ldap.lsc.jndi.JndiModifications ;
import org.linagora.ldap.lsc.objects.monOrganisation ; import
org.linagora.utils.CharacterUnacceptedException ;public class monOrganisationBean extends
organizationalUnitBean implements IBean { static { localMethods = new HashMap<String,Method>() ;
} public static monOrganisationBean getInstance(monOrganisation myclass) throws
IllegalArgumentException, IllegalAccessException, InvocationTargetException,
CharacterUnacceptedException, NamingException { monOrganisationBean mybean = new
monOrganisationBean() ; AbstractBean.mapper(monOrganisationBean.class, localMethods, mybean,
myclass) ; if(myclass.getDistinguishName() == null) { mybean.generateDn() ; } else {
mybean.setDistinguishName(myclass.getDistinguishName()) ; } return mybean ; } public
monOrganisationBean() { super() ; } public static void mapUniqueMember(monOrganisation soc,
IBean doc, List values) throws NamingException, CharacterUnacceptedException { if (values !=
null && values.size() > 0) { Vector<String> v = new Vector<String>() ; Iterator valuesIter =
values.iterator() ; while (valuesIter.hasNext()) { String value = (String) valuesIter.next() ;
if (value != null && value.trim().length() > 0) { // Reconstruction du DN vers l'entrée de //
l'utilisateur concerné v.add( "uid=" + value + "," + Configuration.DN_USERS + "," +
Configuration.DN_REAL_ROOT ) ; } } mapList(doc, "uniqueMember", v) ; } } public static void
generateUniqueMember(monOrganisation soc, IBean doc) throws NamingException {} public
JndiModifications[] checkDependenciesWithmonOrganisation( monOrganisation soc, JndiModifications
jm) { return new JndiModifications[] {} ; } public void generateDn() throws NamingException { //
-2-
InterLDAP - Wiki - Main - DocumentationTechniqueOrganisation
Le DN ne peut pas être fabriqué. throw new RuntimeException("Unavailable generateDn method") ; }
}
La classe du service JNDI
Il faut nécessairement adapter la classe de service JNDI. En effet, il n'est plus question de retourner une
entrée suivant un identifiant particulier dans une branche particulière. Du fait de la présence de notion
d'arborescence, il est plus judicieux d'utiliser directement le DN de l'entrée LDAP concernée.
La nouvelle classe auto-générée MonOrganisationJNDIService.java se situe au sein du paquetage
org.linagora.ldap.lsc.jndi, dans le répertoire src/impl, et ressemble à ceci :
package org.linagora.ldap.lsc.jndi ;import javax.naming.NamingException ;import
org.linagora.ldap.lsc.beans.monOrganisationBean ;public class MonOrganisationJNDIService {
private static final MonOrganisationJNDIService instance = new MonOrganisationJNDIService() ;
public MonOrganisationJNDIService() {} public static MonOrganisationJNDIService getInstance() {
return instance; } public monOrganisationBean getMonOrganisation(String dn) throws
NamingException { return (monOrganisationBean) monOrganisationBean.getInstance(
JndiServices.getInstance().readEntry(dn,true), dn, monOrganisationBean.class); } }
La classe du service JDBC
Tout le travail de construction de l'arborescence s'effectue au niveau de cette classe. LSC est déjà doté
d'une fonctionnalité permettant de construire une arborescence à partir de données en base. C'est la classe
abstraite OrganizationalUnitJDBCService - au sein du paquetage org.linagora.ldap.lsc.service dans le
répertoire src/impl - qui l'implémente. Détail sur la classe OrganizationalUnitJDBCService Globalement,
cette classe générique permet de construire une arborescence, grâce à quelques méthodes indispensables,
automatiquement appelées : initialize(...) : c'est une méthode publique, appelée généralement dans le
constructeur de la classe même, qui se charge d'effectuer les appels SQL nécessaire afin de récupérer les
données adéquates en vue de tenter de construire une arborescence ; initializeVector(...) : c'est une
méthode privée, appelée par la méthode précédemment citée, et permet de fixer les DNs pour chaque
nœud de l'arborescence construire ; _getOu(...) : c'est une méthode publique, appelée en de rares
occasions directement par le développeur. Elle retourne un objet « flat » de type primaire
fOrganizationalUnit, contenant les informations sélectionnée dans la base ; _getOuIdList(...) : cette méthode
publique retourne l'ensemble des identifiants des entrées en base qui constitueront par la suite les nœuds
de l'arbre ; _getParent(...) ; c'est une méthode publique qui retourne une liste d'identifiants des nœuds
parent à l'identifiant de nœud passé en paramètre ; _getOudIdMemberList(...) : cette méthode publique
particulière retourne l'ensemble des identifiants utilisateur faisant partie intégrante de l'identifiant de nœud
passé en paramètre.
Cette classe possède quelques attributs en plus permettant de recenser les nœuds de différentes façons.
Globalement, il existe : Un attribut de classe privé - ou - qui contient la liste des nœuds retournée telle quelle
par la base de données ; Un attribut de classe privé - orderedOu - qui contient cette même liste, mais
ordonnée par ordre d'apparition, c'est à dire qu'un nœud apparaît après son nœud parent dans cette liste ;
Un attribut de classe privée - ouChildren - qui contient la liste des nœuds fils pour chaque nœud de l'arbre.
Pour finir, la principale particularité réside dans le fait que les classes qui en héritent n'ont juste qu'à fixer les
noms des requêtes SQL, introduites dans le fichier XML associé, et le DN de base de l'arborescence.
Tout ceci fait que cette classe permet de réaliser l'objectif visé initialement. Implémentation de la classe
MonOrganisationJDBCService Cette classe est alors relativement courte. Elle s'appuie fortement sur la
classe OrganizationalUnitJDBCService :
package org.linagora.ldap.lsc.service;import org.linagora.ldap.lsc.Configuration;public class
MonOrganisationJDBCService extends OrganizationalUnitJDBCService { private static final
MonOrganisationJDBCService INSTANCE = new MonOrganisationJDBCService(); public static
MonOrganisationJDBCService getInstance() { return INSTANCE; } public
MonOrganisationJDBCService() { baseDn = Configuration.DN_STRUCTURES_ORGANISATION;
sqlFunctionNameForGetMembersList = "getMonOrganisationMembersList"; sqlFunctionNameForGetObject
= "getMonOrganisation"; sqlFunctionNameForGetObjectList = "getMonOrganisationList";
sqlFunctionNameForGetParentObjectId = "getParentMonOrganisationId"; initialize(); } }
-3-
InterLDAP - Wiki - Main - DocumentationTechniqueOrganisation
Le fichier XML de requêtes SQL
Le fichier XML contenant les requêtes SQL se situe au sein du paquetage approprié, c'est à dire
org.linagora.ldap.lsc.persistence.xml, dans le répertoire src/impl.
Comme expliqué dans le chapitre 3.5.5, ce fichier XML contient les deux requêtes SQL suivantes : La
requête d'accès à la liste des identifiants des entrées en base qui constitueront les nœuds de l'arbre ; La
requête permettant de récupérer les données complètes d'une entrée en base.
Outre ces deux requêtes, viennent s'en ajouter deux autres : Une requête permettant de récupérer
l'identifiant de l'entrée parente d'une entrée en base ; Une requête qui retourne une liste d'identifiants des
utilisateurs appartenant à une entrée.
Ces requêtes sont construites sur le même principe déjà abordé dans ce documentation. Il ne s'agit donc
pas de revenir sur la structure syntaxique stricte déjà évoquée. Le fichier XML ressemble donc à ceci :
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE sqlMap PUBLIC
"-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd"><sqlMap
namespace="monOrganisation"> <typeAlias alias="MonOrganisation"
type="org.linagora.ldap.lsc.objects.flat.fMonOrganisation" /> <resultMap
id="MonOrganisationResult" class="MonOrganisation"> <result property="ou"
column="ORGANISATION_LABEL"/> <result property="description" column="ORGANISATION_ID"/>
</resultMap> <select id="getMonOrganisation" resultMap="MonOrganisationResult"
parameterClass="string"> select ORGANISATION_ID, ORGANISATION_LABEL from ORGANISATION where
ORGANISATION_LABEL = #value# </select> <select id="getMonOrganisationList" resultClass="string">
select ORGANISATION_LABEL from ORGANISATION </select> <select id="getMonOrganisationMembersList"
resultClass="string"> select C.COMPTE_UTILISATEUR_ID from COMPTE_UTILISATEUR_SI C, ORGANISATION
O where C.ORGANISATION_ID = O.ORGANISATION_ID and O.ORGANISATION_LABEL = #value# </select>
<select id="getParentMonOrganisationId" resultClass="string"> select ORGANISATION_LABEL from
ORGANISATION where ORGANISATION_ID = ( select ORGANISATION_ID from ORGANISATION where
ORGANISATION_LABEL = #value# ) </select> </sqlMap>
Il est tout de même très important de remarquer que, pour construire l'arborescence, il faut que toutes les
requêtes SQL s'appuie sur un champs communs contenant des valeurs référentielles identiques. Dans
l'exemple précédant, la requête SQL getMonOrganisation retourne des données à partir d'une valeur
correspondant au champs en base « ORGANISATION_LABEL » de la table « ORGANISATION ». Ainsi, la
requête SQL getMonOrganisationList retourne la liste de toutes les valeurs contenues dans la colonne «
ORGANISATION_LABEL » de la table « ORGANISATION ». Il en est donc de même pour les deux autres
requêtes SQL, qui s'appuient alors aussi sur le champs « ORGANISATION_LABEL ». Modification sur la
classe principale de synchronisation C'est la classe Synchronize qu'il faut maintenant adapter pour pouvoir
synchroniser, en plus des comptes utilisateurs, la branche organisationnelle. Le code suivant doit donc être
ajouté à la classe écrite dans le chapitre 3.6.
Tout d'abord, il faut modifier la méthode lauch(..) afin d'y ajouter la ligne qui suit :
private void launch(String args[]) { [...] synchronizeMonOrganisation() ; }
Ensuite, il faut créer la méthode synchronizeMonOrganisation(). Cette méthode va effectuer tout le travail de
synchronisation de la branche arborescente.
private void synchronizeMonOrganisation() { MonOrganisationJDBCService
monOrganisationJdbcService = MonOrganisationJDBCService.getInstance() ;
MonOrganisationJNDIService monOrganisationJndiService = MonOrganisationJNDIService.getInstance()
; Iterator ids = monOrganisationJdbcService.getList().iterator() ; while (lapSiteIds.hasNext())
{ String id = (String) ids.next() ; try { fMonOrganisation fmonOrganisation = (fMonOrganisation)
monOrganisationJdbcService.get(id) ; monOrganisation monOrganisation = new monOrganisation() ;
monOrganisation.setUpFromFlatObject(fMonOrganisation) ; monOrganisationBean ouJDBC =
monOrganisationBean.getInstance(monOrganisation) ; monOrganisationBean ouJNDI =
monOrganisationJndiService.getMonOrganisation( monOrganisation.getDistinguishName()) ;
-4-
InterLDAP - Wiki - Main - DocumentationTechniqueOrganisation
JndiModifications jm = BeanComparator.calculateModifications( ouJDBC, ouJNDI, true) ; if (jm !=
null) { JndiServices.getInstance().apply(jm) ; } } catch (Exception e) { LOGGER.error("Erreur en
cours de synchronisation " + " de la structure ayant l'identifiant : " + id + "(" + e.toString()
+ ")", e) ; } } }
Cas particulier : utilisation de l'attribut uniqueMember Comme vu précédemment, la représentation sous
forme arborescente au niveau annuaire de données en base prend en compte initialement la présence ou
non d'utilisateurs dans chacun des nœuds. Bien que la classe primaire OrganizationalUnitJDBCService le
prenne déjà en compte, il n'en ait pas de même pour le traitement. En effet, l'implémentation reste libre,
l'outil n'impose pas de restrictions. Le choix s'est cependant porté sur la présence de l'attribut LDAP
multi-valué uniqueMember.
Le chapitre 4.5.2 décrit déjà l'adaptation du BEAN. Il ne reste maintenant plus qu'à faire en sorte d'ajouter
les utilisateurs effectivement présent dans l'annuaire. La manœuvre s'effectue par le biais d'un traitement
externe, dans la classe principale de synchronisation Synchronize.java. Le plus simple reste de créer une
méthode se chargeant d'effectuer le travail, appelée ici setUpUniqueMembers(...), comme le montre le code
ci-dessous.
Cette méthode manipule des objets organizationalUnit et inetOrgPerson, de façon générale. Il faut bien voir
que la classe d'objet LDAP organizationalUnit ne contient pas d'attribut uniqueMember. C'est grâce à la
définition LDAP évoquée dans le chapitre 4.2 que cela est possible.
private void setUpUniqueMembers(OrganizationalUnitJDBCService ouJDBC, organizationalUnit ou,
String id) throws SQLException { List ids = ouJDBC._getOuMemberIdList(id) ; if(ids != null) {
List<String> l = new ArrayList<String>() ; Iterator idsIter = ids.iterator() ;
while(idsIter.hasNext()) { String idTmp = (String) idsIter.next() ; try {
inetOrgPersonJNDIService userJNDI = inetOrgPersonJNDIService.getInstance() ; inetOrgPersonBean
userLdapEntry = userJNDI.getInetOrgPerson(idTmp) ; if(userLdapEntry != null) { Attribute attrUid
= userLdapEntry.getAttributeById("uid") ; String uid = attrUid.get().toString() ; l.add(uid) ; }
else { LOGGER.warn("Unable to found DN for uid = " + idTmp + " to add as unique member " + "in
ou = " + ou.getOu()) ; } } catch (NamingException e) { LOGGER.warn("Unable to found DN for uid =
" + idTmp + " to add as unique member " + "in ou = " + ou.getOu()) + " - exception (" + e + ")",
e) ; } } ou.setUniqueMember(l); } }
Dernière petite remarque quant à ce code, il faut souligner que les DNs des entrées LDAP des utilisateurs
sont fabriqués grâce à la méthode mapUniqueMember(...) du BEAN MonOrganisationBean. Ce code ne fait
que vérifier la présence effective dans l'annuaire des utilisateurs évoqués.
Ceci fait, il ne reste plus qu'à compléter la classe principale de synchronisation :
private void synchronizeMonOrganisation() { [...] try { fMonOrganisation fmonOrganisation =
(fMonOrganisation) monOrganisationJdbcService.get(id) ; monOrganisation monOrganisation = new
monOrganisation() ; monOrganisation.setUpFromFlatObject(fMonOrganisation) ; // Fixe les
utilisateurs setUpUniqueMembers(monOrganisationJdbcService, monOrganisation, id) ; [...] }
Synchronisation de comptes organisations (fr)
Creator: xwiki:XWiki.sbahloul Date: 2007/06/11 19:59
Last Author: xwiki:XWiki.sbahloul Date: 2007/06/11 20:00
Copyright (c) 2003-2007, ObjectWeb Consortium
-5-
Téléchargement