diff --git a/src/ldap/admins.js b/src/ldap/admins.js
new file mode 100644
index 0000000000000000000000000000000000000000..1c4fe934530a996f9d206676379ae12d10cce150
--- /dev/null
+++ b/src/ldap/admins.js
@@ -0,0 +1,341 @@
+/**
+ * @file Ce fichier regroupe les différentes classes avec différents admins. Ces classes sont dédiées à être exportées directement pour être utilisées par le solver. 
+ * @author hawkspar
+ */
+
+import LDAP from './basics';
+import {SmartSearch, Tests} from './utilities';
+import User from './users';
+// Essentiels pour le fichier de config
+import path from 'path';
+import fs from 'fs';
+
+// Point central ; tous les champs de la BDD sont 'cachés' dans config.json et pas visibles directement
+var configPath = path.resolve('./','ldap_config.json');
+var config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
+
+class Admin extends User {
+    /**
+     * @class Cette classe est la classe de l'administrateur d'un groupe qui lui permet de rajouter des membres, en supprimer, idem pour des admins,
+     * ou éditer, voir supprimer le groupe.
+     * @summary Ce constructeur appelle simplement le constructeur de sa classe mère.
+     * @arg {Object} user - Utilisateur de la forme nécessaire à {@link LDAP.bind}.
+     * @author hawkspar
+    */
+    constructor(user) { super(user); }
+    
+    //------------------------------------------------------------------------------------------------------------------------
+    // Fonctions de relation TBT
+    //------------------------------------------------------------------------------------------------------------------------
+    
+    /**
+     * @summary Fonction qui permet de rajouter un membre déjà créé à un groupe.
+     * @desc Cette fonction fait essentiellement appel à {@link LDAP.modifier} et {@link listerGroupes}. Elle n'autorise pas les doublons et opère dans les deux dns users
+     * et groups.
+     * @arg {string} uid - Identifiant du futur membre
+     * @arg {string} gid - Identifiant du groupe
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     */
+    static addGroupMember(uid, gid) {
+        // Vérifie que l'utilisateur est pas déjà membre pour groupes
+        return this.getMembers(gid).then(lm => {
+            if (!lm.includes(uid)) {
+                let vals = {};
+                vals[config.groups.member] = uid;
+                LDAP.change(config.key_id+gid+config.dn_groups, "add", vals).then(res => {
+                    // Erreur si pb lors de la modification
+                    if (!res) { throw Error; }
+                });
+            }
+        // Vérifie que l'utilisateur est pas déjà membre pour users
+        }).then(res => this.getGroups(uid)).then( lg => {
+            if (!lg.includes(gid)) {
+                let vals2 = {};
+                vals2[config.users.groups] = gid;
+                LDAP.change(config.key_id+uid+config.dn_users, "add", vals2).then(res => {
+                    // Erreur si pb lors de la modification
+                    if (!res) { throw Error; } else { return true; }
+                });
+            }
+        });
+    }
+
+    /**
+     * @summary Fonction qui permet de supprimer un membre existant d'un groupe.
+     * @desc Cette fonction fait essentiellement appel à {@link LDAP.search}, {@link LDAP.change}, {@link Open.getGroups} et {@link Open.getMembers}.
+     * @arg {string} uid - Identifiant de l'ex-membre
+     * @arg {string} gid - Identifiant du groupe
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     */
+    static delGroupMember(uid, gid) {
+        // Vérifie que l'utilisateur est pas déjà viré pour groupes
+        return this.getMembers(gid).then(lm => {
+            if (lm.includes(uid)) {
+                // Supprime tous les utilisateurs
+                LDAP.change(config.key_id+gid+config.dn_groups, "del", config.group.member).then(res => {
+                    // Les rajoute un par un, sauf pour le supprimé
+                    lm.forEach(id => {
+                        if (id!=uid) { this.addGroupMember(id, gid); }
+                    });
+                });
+            }
+        }).then(res => this.getGroups(uid)).then(lg => {
+            // Vérifie que l'utilisateur est pas déjà viré pour users
+            if (lg.includes(gid)) {
+                // Supprime tous les groupes
+                LDAP.change(config.key_id+uid+config.dn_users, "del", config.member.groups).then(res => {
+                    // Les rajoute un par un, sauf pour le supprimé
+                    lg.forEach(id => {
+                        if (id!=gid) { this.addGroupMember(uid, id).then(res => { if (res) { return true; } else { throw Error; }}); }
+                    });
+                });
+            }
+        });
+    }
+
+    /**
+     * @summary Fonction qui permet de promouvoir membre au stade d'administrateur d'un groupe.
+     * @desc Cette fonction fait essentiellement appel à {@link Admin.addGroupMember} {@link LDAP.change} et {@link Open.getAdmins}. Elle n'autorise pas
+     * les doublons et opère dans les deux dns users et groups.
+     * @arg {string} uid - Identifiant du futur membre
+     * @arg {string} gid - Identifiant du groupe
+     * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
+     */
+    static addGroupAdmin(uid, gid){
+        // Ajoute le membre au groupe avant d'en faire un admin
+        return this.addGroupMember(uid,gid).then(res => {
+            this.getAdmins(gid).then(la => {
+                if (!la.includes(uid)) {
+                    // Finalement modification, uniquement dans groups
+                    let vals = {};
+                    vals[config.groups.admin] = uid;
+                    LDAP.change(config.key_id+gid+config.dn_groups, "add", vals).then(res => {
+                        // Gestion d'erreur
+                        if (!res) { throw Error; } else { return true; }
+                    });
+                }
+            });
+        });
+    }
+
+    /**
+     * @summary Fonction qui permet de rétrograder un membre du stade d'administrateur d'un groupe au stade d'utilisateur.
+     * @desc Cette fonction fait essentiellement appel à {@link LDAP.change}.
+     * @arg {string} uid - Identifiant du futur membre
+     * @arg {string} gid - Identifiant du groupe
+     * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
+     */
+    delGroupAdmin(uid, gid) {
+        // Peut paraître absurde mais permet de s'assurer que le membre est bien présent et que ses champs sont comme il faut
+        return this.delGroupMember(uid, gid).then( res => { this.addGroupMember(uid,gid).then( res =>
+            // Vérifie que l'utilisateur est bien admin (comme dans delGroupMember)
+            this.getAdmins(gid).then(la => {
+                if (la.includes(uid)) {
+                    // Supprime tous les administrateurs
+                    LDAP.change(config.key_id+gid+config.dn_groups, "del", config.group.admin).then(res => {
+                        // Les rajoute un par un, sauf pour le supprimé
+                        la.forEach(id => {
+                            if (id!=uid) { this.addGroupAdmin(id, gid); }
+                        });
+                    });
+                }
+            }).then(res => { return true; }));
+        });
+    }
+    
+    //------------------------------------------------------------------------------------------------------------------------
+    // Fonction d'édition TBT
+    //------------------------------------------------------------------------------------------------------------------------
+    
+    /**
+     * @summary Fonction qui édite un groupe existant dans le LDAP. Très similaire à {@link User.addGroup}
+     * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link Admin.addGroupMember} et {@link Admin.addGroupAdmin} en godmode pour gérer les groupes du nouvel utilisateur.
+     * @arg {string} gid - Identifiant du groupe à modifier
+     * @arg {Object} data - Dictionnaire des informations utilisateurs au même format que pour {@link User.addGroup} avec tous les champs optionnels.
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     */
+    editGroup(gid, data) {
+        // Récupération des anciennes données
+        return this.peekGroup(gid).then(profil => {
+            // Reecriture de profil avec les bons champs
+            Object.keys(profil).forEach(keyLDAP => {
+                Object.keys(config.group).forEach(keyAlias => {
+                    config.group[keyAlias]=keyLDAP;
+                    profil[keyAlias]=profil[keyLDAP];
+                });
+            });
+            // Surcharge des champs à modifier selon data
+            Object.keys(data).forEach(key => {
+                profil[key]=data[key];
+            });
+            // Modification propre
+            this.delGroup(gid).then(r => {
+                this.addGroup(profil).then(res => { if (!res) { throw Error; } else { return true; }});
+            });
+        });
+    }
+    
+    //------------------------------------------------------------------------------------------------------------------------
+    // Fonctions de suppression TBT
+    //------------------------------------------------------------------------------------------------------------------------
+    
+    /**
+     * @summary Fonction qui supprime un groupe du LDAP.
+     * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement.
+     * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Admin.delGroupMember} et {@link Admin.delGroupAdmin} pour gérer les groupes de l'utilisateur sortant.
+     * @arg {string} gid - gid de la victime
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     */
+    delGroup(gid) {
+        // Gestion des membres et administrateurs d'abord
+        return this.constructor.peekGroup(gid).then(profil => {
+            // Ordre important
+            profil[config.group['admin']].forEach( id => { this.delGroupAdmin( id, gid); });
+            profil[config.group['member']].forEach(id => { this.delGroupMember(id, gid); });
+        // Elimination
+        }).then(res => {
+            LDAP.clear(config.key_id+"="+gid+","+config.dn_groups);
+        });
+    }
+}
+
+class SuperAdmin extends Admin {
+    /**
+     * @class Cette classe est la classe du super administrateur qui créé et supprime des membres.
+     * @summary Bête appel au constructeur de la classe mère.
+     * @arg {Object} user - Utilisateur de la forme nécessaire à {@link LDAP.bind}.
+     * @author hawkspar
+     */
+    constructor(user) { super(user); }
+
+    //------------------------------------------------------------------------------------------------------------------------
+    // Fonctions de création TBT
+    //------------------------------------------------------------------------------------------------------------------------
+    
+    /**
+     * @summary Fonction qui créé un nouvel utilisateur dans le LDAP.
+     * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link User.addGroupMember} et {@link Admin.addGroupAdmin} pour gérer les groupes du nouvel utilisateur.
+     * @arg {Object} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis.
+     * @arg {string} data[givenName] - Prénom
+     * @arg {string} data[lastName] - Nom
+     * @arg {string} data[nickname] [] - Surnom
+     * @arg {string} data[birthdate] - Date d'anniversaire au format annee-mois-jour
+     * @arg {string} data[password] - Mot de passe
+     * @arg {string} data[promotion] - Simple année d'entrée école (pas d'X17)
+     * @arg {string} data[mail] - Courriel supposé valide
+     * @arg {string} data[phone] - String du numéro de portable
+     * @arg {string} data[photo] - Photo jpeg directement en bytestring
+     * @arg {string} data[room] - Pour l'instant juste le numéro de casert ou rien, à terme l'adresse complète
+     * @arg {string} data[readPerm] [] - Permissions spéciales BR sous la forme "*truc,"
+     * @arg {string} data[writePerm] [] - Permissions spéciales BR sous la forme "*truc,"
+     * @arg {string[]} data[forlifes] [] - Liste d'alias spéciaux BR (attention le filtre .fkz n'est plus fonctionnel)
+     * @arg {string[]} data[ips] - Liste des ips connus de la personne
+     * @arg {string[]} data[groups] [] - Liste des gid dont le pax est membre (doit contenir "on_platal" si il y a lieu)
+     * @arg {string[]} data[groupsIsAdmin] [] - Liste des gid dont le pax est admin ; supposé sous-liste du précédent
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     */
+    addUser(data) {
+        // Calcul d'un dictionnaire d'ajout
+        let vals = {};
+
+        // uid de base généré à partir de nom et prénom, plus potentiellement promo et un offset
+        // MEF mélange de Promise et de fonction standard
+        Tests.generateUid(data['givenName'],data['lastName'],data['promotion']).then(id => { vals[config.key_id]=id; } );
+
+        // Ecriture de toutes les valeurs directement inscrites dans le LDAP (in pour input)
+        // Génère une erreur si un champ n'est pas rempli
+        config.user.direct_input.forEach(key_att => vals[config.user[key_att]]=data[key_att]);
+
+        // Appel à la fonction de base
+        return LDAP.add(config.key_id+"="+vals[config.key_id]+","+config.dn_users, vals).then( res => {
+            // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples (d'où mul)
+            config.user.muliple_input.forEach(key_att => {
+                // On rajoute chaque valeur en entrée
+                data[key_att].forEach(val => {
+                    let vals2 = {};
+                    vals2[config.user[key_att]]=val;
+                    LDAP.change(config.key_id+"="+vals[config.key_id]+","+config.dn_users, "add", vals2).then(res => {
+                        if (!res) { throw Error; }
+                    });
+                });
+            });
+
+            // Certains champs nécessitent de petits calculs
+            let vals3={};
+
+            // Création d'un nom complet lisible
+            vals3[config.user['fullName']]=data['givenName']+' '+data['lastName'].toUpperCase();
+            // ?!
+            vals3[config.user['password']] =  '{CRYPT}' + data['password'];
+            // Ecriture d'un surnom s'il y a lieu
+            if ((data['nickname']!=undefined)&(data['nickname']!='')) {
+                vals3[config.user['nickname']]=data['nickname'];
+            }
+            // Génération id aléatoire unique
+            Tests.generateId(config.user['id'], config.dn_users).then(id => { vals3[config.user['id']]=id; });
+            
+            // Stockage machine ; dépend du prénom
+            vals3[config.user['directory']] = '/hosting/users/' + data['givenName'][0];
+
+            // Code root
+            vals3[config.user['cleanFullName']]=data['fullName'].replace(':', ';').toLowerCase().normalize('UFD');
+            
+            // Adressage root
+            if (data['groups'].includes("on_platal")) { vals3[config.user['login']] = "/bin/bash"; }
+            else  { vals3[config.user['login']] = "/sbin/nologin"; }
+            
+            // Permissions BR
+            vals3[config.user['readPerm']] = 'br.*,public.*';
+            if (data['readPerm'].length>0) { vals3[config.user['readPerm']] += ',' + data['readPerm']; }
+            vals3[config.user['writePerm']] = 'br.*,!br.blague-du-jour,public.*,!br.campagnekes';
+            if (data['writePerm'].length>0) { vals3[config.user['readPerm']] += ',' + data['writePerm']; }
+
+            // Valeur nécessaire mais inutile
+            vals3[config.user['idNum']] ='5000';
+
+            // Inscription des valeurs calculées
+            LDAP.change(config.key_id+"="+vals[config.user['hruid']]+","+config.dn_users, "add", vals3).then(res => {
+                if (!res) { throw Error; }
+            });
+
+            ["posixAccount", "shadowAccount", "inetOrgPerson", "brAccount"].forEach(cst => {
+                let val3={};
+                vals3[config.user['class']]=cst;
+                LDAP.change(config.key_id+"="+vals[config.user['hruid']]+","+config.dn_users, "add", vals3).then(res => {
+                    if (!res) { throw Error; }
+                });
+            });
+
+            // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble
+            data['groupsIsMember'].forEach(gid => { this.addGroupMember(vals[config.key_id], gid); });
+            data['groupsIsAdmin'].forEach(gid => {  this.addGroupAdmin( vals[config.key_id], gid); });
+
+            return true;
+        });
+    }
+
+    //------------------------------------------------------------------------------------------------------------------------
+    // Fonctions de suppression TBT
+    //------------------------------------------------------------------------------------------------------------------------
+    
+    /**
+     * @summary Fonction qui supprime un utilisateur du LDAP.
+     * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement.
+     * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Admin.delGroupMember} et {@link Admin.delGroupAdmin} pour gérer les groupes de l'utilisateur sortant.
+     * @arg {string} uid - uid de la victime
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     */
+    delUser(uid) {
+        // Gestion des groupes d'abord
+        return super.peekUser(uid).then(profil => {
+            profil[config.user['groups']].forEach(gid => {
+                this.isGroupAdmin(gid).then(res => {
+                    this.delGroupAdmin(uid, gid);
+                }).then(res => { this.delGroupMember(uid, gid); });
+            });
+        // Elimination
+        }).then(res => { this.LDAP.clear(config.key_id+"="+uid+","+config.dn_users); });
+    }
+}
+
+export {Admin, SuperAdmin};
\ No newline at end of file
diff --git a/src/ldap/basics.js b/src/ldap/basics.js
index 8384bd8d8900a3fe4bd9006746244e2249102af1..ae880b08d2ecfc5079459bca9ee0c30d320cf271 100644
--- a/src/ldap/basics.js
+++ b/src/ldap/basics.js
@@ -7,9 +7,11 @@
  */
 
 import ldap  from 'ldapjs';
-import fs from 'fs';
+// Toutes les entrées utilisateur sont escapées par sécurité
 import ldapEscape from 'ldap-escape';
+// Essentiels pour le fichier de config
 import path from 'path';
+import fs from 'fs';
 
 // Important ; permet de vérifier que l'utilisateur reste connecté.
 var ensureLoggedin =  require('connect-ensure-login').ensureLoggedIn;
@@ -25,129 +27,151 @@ var client = ldap.createClient({ url: config.ldap.server});
 // Fonctions de base agissant sur le LDAP
 //------------------------------------------------------------------------------------------------------------------------
 
-/**
- * @summary Fonction qui sert à s'identifier sur le LDAP. Ne renvoie rien.
- * @desc Assez important en terme de sécurité, de gestion de conflit, et de droit d'accès. Méthode ldapjs 
- * (voir [`Client API`](http://ldapjs.org/client.html) méthode bind).
- * @arg {Object} user - Utilisateur de la forme suivante :
- * @arg {string} user[uid] - User identifier
- * @arg {string} user[password] - Mot de passe
- */
-function bind(user) { client.bind(ldapEscape.dn("${txt}", { txt: user["dn"]}), ldapEscape.filter("${txt}", { txt: user["password"]}), (err, res) => {}); } // TBM utiliser user 
+class LDAP {
+    /**
+     * @class Cette classe est la brique de base du fichier tout entier puisqu'elle contient les functions qui agisse directement sur le LDAP.
+     * @summary Constructeur vide.
+     * @author hawkspar
+    */
+    constructor() {}
 
-/**
- * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit les valeurs trouvées.
- * @desc Cette fonction utilise ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode search). Il faut l'appeler suivant un schéma
- * `rechercher(...).then((res) => { truc avec res });`. Cette fonction fait une demande au LDAP qu'elle filtre selon un schéma prédéfini dans `dic`
- * et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end).
- * @arg {string} dn - DN de l'emplacement de la requête
- * @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape
- * @arg {string} filter_dic[key] - Vraie valeur pertinente de la recherche
- * @arg {string[]} attributes - Liste des attributs qui figureront dans le résultat final ; peut aussi être un seul élément
- * @return {Promise(Object[])} Résultats de la recherche ; soit une liste de valeurs d'attributs, 
- * soit une liste de dictionnaires si on veut plus d'un attribut (les clés du dictionnaire sont celles du LDAP)
- */
-function search(dn, attributes, filter="(objectClass=*)") {
-    return new Promise((resolve, reject) => {
-        let vals=[];
-        // Interrogation LDAP selon configuration fournie en argument
-        client.search(ldapEscape.dn("${txt}", { txt: dn}), {
-            "scope": "sub",
-            "filter": ldapEscape.filter("${txt}", { txt: filter}),
-            "attributes": attributes
-        }, function(err, res) {
-            // Gestion erreur
-            if (err) {
-                reject(err);
-            } else {
-                // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse
-                res.on('searchEntry', function(entry) {
-                    // Cas un seul attribut où le résultat est une liste directement
-                    if (!Array.isArray(attributes)) { vals.push(entry.object[attributes]); }
-                    else if (attributes.length == 1) { vals.push(entry.object[attributes[0]]); }
-                    // Cas plusieurs attributs donc résultat dictionnaire
-                    else {
-                        vals.push({});
-                        attributes.forEach((attribute) => {
-                            vals.slice(-1)[0][attribute]=entry.object[attribute];
-                        });
-                    }
-                });
-                // Si la recherche est finie on se déconnecte et on renvoit la liste
-                res.on('end', function(res) { resolve(vals); });
-            }
+    /**
+     * @summary Fonction qui sert à s'identifier sur le LDAP. Ne renvoie rien.
+     * @desc Assez important en terme de sécurité, de gestion de conflit, et de droit d'accès. Méthode ldapjs 
+     * (voir [`Client API`](http://ldapjs.org/client.html) méthode bind).
+     * @arg {Object} user - Utilisateur de la forme suivante :
+     * @arg {string} user[uid] - User identifier
+     * @arg {string} user[password] - Mot de passe
+     * @static
+     */
+    static bind(user) { client.bind(ldapEscape.dn("${txt}", { txt: user["dn"]}), ldapEscape.filter("${txt}", { txt: user["password"]}), (err, res) => {}); } // TBM utiliser user 
+
+    /**
+     * @summary Fonction qui sert à se déconnecter du LDAP. Ne renvoie rien.
+     * @desc Assez important en terme de sécurité, de gestion de conflit, et de droit d'accès. Méthode ldapjs 
+     * (voir [`Client API`](http://ldapjs.org/client.html) méthode bind).
+     * @static
+     */
+    static unbind() { client.bind("", "", (err, res) => {}); }
+
+    /**
+     * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit les valeurs trouvées.
+     * @desc Cette fonction utilise ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode search). Il faut l'appeler suivant un schéma
+     * `rechercher(...).then((res) => { truc avec res });`. Cette fonction fait une demande au LDAP qu'elle filtre selon un schéma prédéfini dans `dic`
+     * et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end).
+     * @arg {string} dn - DN de l'emplacement de la requête
+     * @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape
+     * @arg {string} filter_dic[key] - Vraie valeur pertinente de la recherche
+     * @arg {string[]} attributes - Liste des attributs qui figureront dans le résultat final ; peut aussi être un seul élément
+     * @return {Promise(Object[])} Résultats de la recherche ; soit une liste de valeurs d'attributs, 
+     * soit une liste de dictionnaires si on veut plus d'un attribut (les clés du dictionnaire sont celles du LDAP)
+     * @static
+     */
+    static search(dn, attributes, filter="(objectClass=*)") {
+        return new Promise((resolve, reject) => {
+            let vals=[];
+            // Interrogation LDAP selon configuration fournie en argument
+            client.search(ldapEscape.dn("${txt}", { txt: dn}), {
+                "scope": "sub",
+                "filter": ldapEscape.filter("${txt}", { txt: filter}),
+                "attributes": attributes
+            }, function(err, res) {
+                // Gestion erreur
+                if (err) {
+                    reject(err);
+                } else {
+                    // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse
+                    res.on('searchEntry', function(entry) {
+                        // Cas un seul attribut où le résultat est une liste directement
+                        if (!Array.isArray(attributes)) { vals.push(entry.object[attributes]); }
+                        else if (attributes.length == 1) { vals.push(entry.object[attributes[0]]); }
+                        // Cas plusieurs attributs donc résultat dictionnaire
+                        else {
+                            vals.push({});
+                            attributes.forEach((attribute) => {
+                                vals.slice(-1)[0][attribute]=entry.object[attribute];
+                            });
+                        }
+                    });
+                    // Si la recherche est finie on se déconnecte et on renvoit la liste
+                    res.on('end', function(res) { resolve(vals); });
+                }
+            });
         });
-    });
-}
+    }
 
-//TBT
-/**
- * @summary Fonction qui permet de modifier un élément sur le LDAP. Gestion intelligente de l'appartenance à un binet.
- * @desc Cette fonction utilise une Promise pour être asynchrone ; elle renvoit la promesse d'une réponse puis traite la demande avec ldapjs
- * (voir [`Client API`](http://ldapjs.org/client.html) méthode modify). Il faut l'appeler suivant un schéma `LDAP.modifier(...).then((res) => { truc avec res });`.
- * @arg {Object} user - Utilisateur de la forme nécessaire à {@link LDAP.connecter}
- * @arg {string} dn - DN de l'endroit à modifier
- * @arg {string} op - Operation à réaliser sur le LDAP. Trois opération sont possibles ; "add", qui rajoute des attributs et qui peut créer des doublons,
- * "del" qui en supprime, et "replace" qui remplace du contenu par un autre. 
- * @arg {Object} mod - Dictionnaire contenant les attributs à modifier et les nouvelles valeurs des attributs. Une nouvelle valeur vide ("") est équivalent à la suppression
- * de cet attribut.
- * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon.
- */
-function change(dn, op, mod) {
-    return new Promise((resolve, reject) => {
-        // Modification LDAP selon configuration en argument (pourrait prendre une liste de Changes)
-        client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({
-            operation: op,
-            modification: mod,
-        }), err => {
-            reject(err);
+    //TBT
+    /**
+     * @summary Fonction qui permet de modifier un élément sur le LDAP. Gestion intelligente de l'appartenance à un binet.
+     * @desc Cette fonction utilise une Promise pour être asynchrone ; elle renvoit la promesse d'une réponse puis traite la demande avec ldapjs
+     * (voir [`Client API`](http://ldapjs.org/client.html) méthode modify). Il faut l'appeler suivant un schéma `LDAP.modifier(...).then((res) => { truc avec res });`.
+     * @arg {Object} user - Utilisateur de la forme nécessaire à {@link LDAP.connecter}
+     * @arg {string} dn - DN de l'endroit à modifier
+     * @arg {string} op - Operation à réaliser sur le LDAP. Trois opération sont possibles ; "add", qui rajoute des attributs et qui peut créer des doublons,
+     * "del" qui en supprime, et "replace" qui remplace du contenu par un autre. 
+     * @arg {Object} mod - Dictionnaire contenant les attributs à modifier et les nouvelles valeurs des attributs. Une nouvelle valeur vide ("") est équivalent à la suppression
+     * de cet attribut.
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon.
+     * @static
+     */
+    static change(dn, op, mod) {
+        return new Promise((resolve, reject) => {
+            // Modification LDAP selon configuration en argument (pourrait prendre une liste de Changes)
+            client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({
+                operation: ldapEscape.dn("${txt}", {txt: op}),
+                modification: mod,
+            }), err => {
+                reject(err);
+            });
+            resolve(true);
         });
-        resolve(true);
-    });
-}
+    }
 
-//TBT
-/**
- * @summary Fonction qui permet de rajouter un élément sur le LDAP.
- * @desc Cette fonction appelle {@link LDAP.connecter} pour authentifier l'utilisateur puis rompts la connexion. Cette fonction utilise une Promise pour être asynchrone ;
- * elle renvoit la promesse d'une réponse puis traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode add).
- * Il faut l'appeler suivant un schéma `LDAP.ajouter(...).then((res) => { truc avec res });`.
- * @arg {Object} user - Utilisateur de la forme nécessaire au {@linkoperationsLDAP. connecterLDAP}
- * @arg {string} dn - Adresse du parent
- * @arg {Object} vals - Dictionnaire contenant les valeurs à créer
- * @arg {string} vals[key] - Nouvelle valeur pour le champ key
- * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon.
- */
-function add(dn, vals) {
-    return new Promise((resolve, reject) => {
-        // Ajout LDAP selon la configuration en argument
-        client.add(ldapEscape.dn(config.key_id+"="+vals[config.key_id]+",${txt}", { txt: dn}), vals, function(err) {
-            reject(err);
+    //TBT
+    /**
+     * @summary Fonction qui permet de rajouter un élément sur le LDAP.
+     * @desc Cette fonction appelle {@link LDAP.connecter} pour authentifier l'utilisateur puis rompts la connexion. Cette fonction utilise une Promise pour être asynchrone ;
+     * elle renvoit la promesse d'une réponse puis traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode add).
+     * Il faut l'appeler suivant un schéma `LDAP.ajouter(...).then((res) => { truc avec res });`.
+     * @arg {Object} user - Utilisateur de la forme nécessaire au {@linkoperationsLDAP. connecterLDAP}
+     * @arg {string} dn - Adresse du parent
+     * @arg {Object} vals - Dictionnaire contenant les valeurs à créer
+     * @arg {string} vals[key] - Nouvelle valeur pour le champ key
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon.
+     * @static
+     */
+    static add(dn, vals) {
+        return new Promise((resolve, reject) => {
+            // Ajout LDAP selon la configuration en argument
+            client.add(ldapEscape.dn(config.key_id+"="+vals[config.key_id]+",${txt}", { txt: dn}), vals, function(err) {
+                reject(err);
+            });
+            resolve(true);
         });
-        resolve(true);
-    });
-}
+    }
 
-//TBT
-/**
- * @summary Fonction qui permet de supprimer une feuille du LDAP.
- * @desc Cette fonction appelle {@link LDAP.connecter} pour authentifier l'utilisateur puis rompts la connexion. Cette fonction utilise une Promise pour être asynchrone ;
- * elle renvoit la promesse d'une réponse puis traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode del).
- * Il faut l'appeler suivant un schéma `LDAP.supprimer(...).then((res) => { truc avec res });`. Cette fonction fait une demande au LDAP qu'elle filtre selon un
- * schéma prédéfini dans `dic` et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end).
- * Elle est différente de modify avec "del" car elle affecte directement une feuille et pas un attribut.
- * @arg {Object} user - Utilisateur de la forme nécessaire au {@link LDAP.connecter}
- * @arg {string} dn - Adresse de la cible
- * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
- */
-function clear(dn) {
-    return new Promise((resolve, reject) => {
-        // Suppression LDAP
-        client.del(ldapEscape.dn("${txt}", {txt: dn}), function(err) {
-            reject(err);
+    //TBT
+    /**
+     * @summary Fonction qui permet de supprimer une feuille du LDAP.
+     * @desc Cette fonction appelle {@link LDAP.connecter} pour authentifier l'utilisateur puis rompts la connexion. Cette fonction utilise une Promise pour être asynchrone ;
+     * elle renvoit la promesse d'une réponse puis traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode del).
+     * Il faut l'appeler suivant un schéma `LDAP.supprimer(...).then((res) => { truc avec res });`. Cette fonction fait une demande au LDAP qu'elle filtre selon un
+     * schéma prédéfini dans `dic` et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end).
+     * Elle est différente de modify avec "del" car elle affecte directement une feuille et pas un attribut.
+     * @arg {Object} user - Utilisateur de la forme nécessaire au {@link LDAP.connecter}
+     * @arg {string} dn - Adresse de la cible
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @static
+     */
+    static clear(dn) {
+        return new Promise((resolve, reject) => {
+            // Suppression LDAP
+            client.del(ldapEscape.dn("${txt}", {txt: dn}), function(err) {
+                reject(err);
+            });
+            resolve(true);
         });
-        resolve(true);
-    });
+    }
 }
 
-export {bind, search, change, add, clear};
\ No newline at end of file
+export {LDAP};
\ No newline at end of file
diff --git a/src/ldap/users.js b/src/ldap/users.js
new file mode 100644
index 0000000000000000000000000000000000000000..6e0e1b4409de80c177530c3ea30ee35edb1bedf1
--- /dev/null
+++ b/src/ldap/users.js
@@ -0,0 +1,304 @@
+/**
+ * @file Ce fichier regroupe les différentes classes avec différents utilisateurs. Ces classes sont dédiées à être exportées directement pour être utilisées par le solver. 
+ * @author hawkspar
+ */
+
+import LDAP from './basics';
+import {SmartSearch, Tests} from './utilities';
+import {Admin, SuperAdmin} from './admins';
+// Essentiels pour le fichier de config
+import path from 'path';
+import fs from 'fs';
+
+// Point central ; tous les champs de la BDD sont 'cachés' dans config.json et pas visibles directement
+var configPath = path.resolve('./','ldap_config.json');
+var config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
+
+//------------------------------------------------------------------------------------------------------------------------
+// Classes à exporter TBT
+//------------------------------------------------------------------------------------------------------------------------
+
+class Open {
+    /**
+     * @class Cette classe est la classe exportable de base permettant à un utilisateur non connecté de faire des petites recherches simples.
+     * @summary Constructeur vide.
+     * @author hawkspar
+    */
+    constructor(config) {}
+
+    //------------------------------------------------------------------------------------------------------------------------
+    // Fonctions de lecture
+    //------------------------------------------------------------------------------------------------------------------------
+    /**
+     * @summary Fonction qui retrouve les groupes dont un individu est membre.
+     * @desc Cette fonction utilise {@link LDAP.search} va directement à la feuille de l'utilisateur.
+     * @arg {string} uid - Identifiant de l'individu à interroger (le plus souvent prenom.nom, parfois l'année, supposé valide)
+     * @return {Promise(string[])} Liste des uid de groupes (noms flat des groupes) où l'id fourni est membre
+     * @static
+     */
+    static getGroups(uid) {
+        return LDAP.search(config.key_id+uid+config.dn_users, config.user.groups)[0];
+    }
+    
+    /**
+     * @summary Fonction qui retrouve la liste des membres d'un groupe.
+     * @desc Cette fonction utilise {@link LDAP.search} avec un dictionnaire prédéfini dans config.json.
+     * @arg {string} gid - Identifiant du groupe à interroger (le plus souvent nom du groupe en minuscule)
+     * @return {Promise(String[])} Liste des uid des membres où l'id fournie est membre (noms flat des groupes)
+     * @static
+     */
+    static getMembers(gid) {
+        return LDAP.search(config.key_id+gid+config.dn_users, config.group.member)[0];
+    }
+    
+    /**
+     * @summary Fonction qui retrouve la liste des admins d'un groupe.
+     * @desc Cette fonction utilise {@link LDAP.search} avec un dictionnaire prédéfini dans config.json.
+     * @arg {string} gid - Identifiant du groupe à interroger (le plus souvent nom du groupe en minuscule)
+     * @return {Promise(string[])} Liste des uid des membres où l'id fournie est membre (noms flat des groupes)
+     * @static
+     */
+    static getAdmins(gid) {
+        return LDAP.search(config.key_id+gid+config.dn_users, config.group.admin)[0];
+    }
+
+    /**
+     * @summary Cette fonction teste si un utilisateur est membre d'un groupe.
+     * @desc Utilise les méthodes statiques {@link open.getGroups} et {@link open.getMembers}
+     * @param {string} uid - Identifiant de l'utilisateur à tester 
+     * @param {string} gid  - Identification du groupe à tester
+     * @returns {Promise(boolean)} True si l'utilisateur est membre
+     * @static
+     */
+    static isGroupMember(uid, gid) {
+        return this.getGroups(uid).then(lg => {
+            this.getMembers(gid).then(lm => {
+                if (lg.includes(gid) && lm.includes(uid)) {
+                    return true;
+                }
+                else {
+                    return false;
+                }
+            });
+        });
+    }
+
+    /**
+     * @summary Cette fonction teste si un utilisateur est admin d'un groupe.
+     * @desc Utilise la méthode statique {@link Open.getAdmins}
+     * @param {string} uid - Identifiant de l'utilisateur à tester 
+     * @param {string} gid  - Identification du groupe à tester
+     * @returns {Promise(boolean)} True si l'utilisateur est administrateur
+     * @static
+     */
+    static isGroupAdmin(uid, gid) {
+        return this.getAdmins(gid).then(la => {
+            if (la.includes(uid)) {
+                return true;
+            }
+            else {
+                return false;
+            }
+        });
+    }
+     
+    /**
+     * @summary Fonction qui renvoit toutes les infos relatives à un utilisateur particulier.
+     * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis.
+     * @arg {string} uid - Identifiant de l'utilisateur
+     * @return {Promise(Object[])} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet de l'utilisateur ;
+     * voir `ldap_config.json`(..\..\ldap_config.json) pour les clés exactes.
+     * @static
+     */
+    static peekUser(uid) {
+        return LDAP.search(config.key_id+uid+config.dn_users, config.user.profil);
+    }
+     
+    /**
+     * @summary Fonction qui renvoit toutes les infos relatives à un groupe particulier.
+     * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis.
+     * @arg {string} gid - Identifiant du groupe
+     * @return {Promise(Object[])} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe ;
+     * voir `ldap_config.json`(..\..\ldap_config.json) pour les clés exactes.
+     * @static
+     */
+    static peekGroup(gid) {
+        return LDAP.search(config.key_id+gid+config.dn_groups, config.group.profil);
+    }
+
+    //------------------------------------------------------------------------------------------------------------------------
+    // Fonctions de recherche
+    //------------------------------------------------------------------------------------------------------------------------
+
+    /**
+     * @summary Fonction qui retrouve le groupe qui ressemblent à l'input et qui correspond au type fourni. Etape 0 vers un vrai TOL (Trombino On Line).
+     * @desc Cette fonction utilise {@link SmartSearch.groups}.
+     * @arg {string} input - String entré par l'utilisateur qui ressemble au nom du groupe.
+     * @arg {string} type - String aux valeurs prédéfinies dans ldap_config.
+     * @return {Promise(string[])} Liste des gid dont le nom ressemble à l'input.
+     * @static
+    */
+    static findGroups(input, type) {
+        // Trucs intelligents faits dans ./utilities
+        return SmartSearch.groups(input, [config.key_id, config.group.type]).then(gList => {
+            let gidtyList = [];
+            gList.forEach(g => {
+                // Si le groupe est du bon type on rajoute son gid
+                if (g[config.group.type]==type) { gidtyList.push(g[config.key_id]); }
+            });
+            return gidtyList;
+        });
+    }
+
+    /**
+     * @summary Fonction qui retrouve les uid des paxs validant les critères de recherche. Autre étape vers vrai TOL (Trombino On Line). Doit être préféré à repliquerTOL
+     * car moins gourmande envers le LDAP (utiliser {@link peekUser} au cas par cas après pour obtenir les vraies infos).
+     * @desc Cette fonction utilise {@link SmartSearch.users}.
+     * @arg {Object} data - Dictionnaire contenant les données nécessaires à {@link SmartSearch.groups}
+     * @return {Promise(string[])} gids des profils qui "match" les critères proposés.
+     * @static
+     */
+    static findUsers(data) {
+        return SmartSearch.users(data, config.key_id);
+    }
+}
+
+class User extends Open {
+    /**
+     * @class Cette classe est la classe de l'utilisateur connecté qui peut déjà créer un groupe et changer son profil.
+     * @summary Ce constructeur appelle simplement {@link LDAP.bind}.
+     * @arg {Object} user - Utilisateur de la forme nécessaire à {@link LDAP.bind}.
+     * @author hawkspar
+    */
+    constructor(user) {
+        super();
+        LDAP.bind(user);
+    }
+
+    //------------------------------------------------------------------------------------------------------------------------
+    // Fonction de création TBT
+    //------------------------------------------------------------------------------------------------------------------------
+
+    /**
+     * @summary Fonction qui créé un nouveau groupe dans le LDAP.
+     * @desc Cette fonction fait une utilisation massive d'eval pour anonymiser son code ; c'est mal et cela suppose que beaucoup de soins ont été pris lors de
+     * l'escape de ses paramètres. Appelle {@link LDAP.add} et {@link LDAP.change}, mais aussi {@link Admin.addMemberGroup} et {@link Admin.addAdminGroup}
+     * pour gérer les groupes du nouvel utilisateur. Attention une manip FOIREUSE est cachée dedans.
+     * @arg {Object} data - Dictionnaire des informations utilisateurs (voir détail des champs dans config.json)
+     * @arg {string} data[name] - Nom du groupe
+     * @arg {string} data[ns] - Statut du groupe ; 'binet' ou 'free', càd ouvert à tous
+     * @arg {string[]} data[members] - Liste des membres du groupe
+     * @arg {string[]} data[admins] - Liste des admins du groupe ; supposée être une sous-liste de la précédente
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     */
+    addGroup(data) {
+        // Calcul d'un dictionnaire d'ajout
+        let vals = {};
+
+        // uid de base généré à partir du nom standardisé
+        Tests.generateReadableId(data['name']).then(id => { vals[config.group['name']]=id; });
+
+        // Ecriture de toutes les valeurs directement inscrites dans le LDAP (in pour input)
+        config.group.direct_input.forEach(key_att => vals[config.group[key_att]]=data[key_att]);
+
+        // Appel à la fonction de base
+        return LDAP.add(config.key_id+"="+vals[config.group['name']]+","+config.dn_groups, vals).then( res => {
+            // Certains champs nécessitent de petits calculs
+            let vals2={};
+
+            // Sauvegarde du nom (pour le cas où gid != data['name'])
+            vals2[config.group["name"]]=data['name'];
+
+            // ?!
+            vals2[config.user['password']] =  '';
+
+            // Génération id aléatoire et test contre le LDAP
+            Tests.generateId(config.groups["idNumber"], config.dn_groups).then(id => { vals2[config.group['idNumber']]=id; });
+            // FOIREUX : Hypothèse sur la structure du reste des données mais évite un test.assurerUnicite à deux variables
+            vals2[config.group['idNumber2']]=vals2[config.group['idNumber']];
+            
+            // Stockage machine ; dépend du prénom
+            vals2[config.group['directory']] = '/hosting/groups/'+vals[config.key_id];
+
+            // Code root
+            vals2[config.group['cleanFullName']]=data['name'].replace(':', ';').toLowerCase().normalize('UFD');
+            
+            // Adressage root
+            vals2[config.group['login']] = "/sbin/nologin";
+            
+            // Permissions BR
+            vals2[config.group['readPerm']] = '!*';
+            vals2[config.group['writePerm']] = '!*';
+
+            // Inscription des valeurs calculées
+            LDAP.change(config.key_id+"="+vals[config.key_id]+","+config.dn_groups, "add", vals2).then(res => {
+                if (!res) { throw Error; }
+            });
+
+            ["posixAccount", "posixGroup", "brAccount"].forEach(cst => {
+                let vals3={};
+                vals3[config.group['class']]=cst;
+                LDAP.change(config.key_id+"="+vals[config.key_id]+","+config.dn_groups, "add", vals3).then(res => {
+                    if (!res) { throw Error; }
+                });
+            });
+
+            // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble
+            data['members'].forEach(uid => { Admin.addGroupMember(uid, vals[config.key_att]); });
+            data['admins'].forEach(uid => {  Admin.addGroupAdmin( uid, vals[config.key_att]); });
+
+            return true;
+        });
+    }
+
+    //------------------------------------------------------------------------------------------------------------------------
+    // Fonctions d'édition TBT
+    //------------------------------------------------------------------------------------------------------------------------
+    
+    /**
+     * @summary Fonction qui édite un utilisateur existant dans le LDAP. Très similaire à {@link creerUtilisateur}
+     * @desc Appelle simplement {@link creerUtilisateur} et {@link supprimerUtilisateur} en godmode, plus {@link renseignerSurUtilisateur} pour les champs non fournis.
+     * Ce choix a pour conséquence que l'ordre du dictionnaire de correspondance dans ldap_config est important. A modifier car donne trop de pouvoir à l'utilisateur.
+     * @arg {string} uid - Utilisateur à modifier (le plus souvent le même, mais root possible)
+     * @arg {Object} data - Dictionnaire des informations utilisateurs au même format que pour {@link creerUtilisateur} avec tous les champs optionnels ;
+     * MEF ces valeurs vont écraser les précédentes.
+     * attention toutes les clés de cette entrée seront modifiées dans le LDAP
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     */
+    editUser(uid, data) {
+        // Récupération des anciennes données
+        return this.peekUser(uid).then(profil => {
+            // Reecriture de profil avec les bons champs
+            Object.keys(profil).forEach(keyLDAP => {
+                Object.keys(config.user).forEach(keyAlias => {
+                    config.user[keyAlias]=keyLDAP;
+                    profil[keyAlias]=profil[keyLDAP];
+                });
+            });
+            // Régénération du champ manquant dans profil
+            this.getGroups(uid).then(lg => {
+                profil['groupsIsAdmin']=[];
+                lg.forEach(gid =>{
+                    this.isGroupAdmin(uid, gid).then(res =>{
+                        if (res) { profil['groupsIsAdmin'].push(gid); }
+                    }).then(res => {
+                        // Surcharge des champs à modifier selon data
+                        Object.keys(data).forEach(key => {
+                            profil[key]=data[key];
+                        });
+                        // Passage en godmode
+                        var god = SuperAdmin({"uid":"", "password":""});
+                        // Modification propre
+                        god.delUser(uid).then(r => {
+                            god.createUser(profil).then(res => { if (!res) { throw Error; } else { return true; }});
+                        });
+                    });
+                });
+            });
+        });
+    }
+    
+    destr() { LDAP.unbind(); }
+}
+
+export {Open, User};
\ No newline at end of file
diff --git a/src/ldap/utilities.js b/src/ldap/utilities.js
index 9dd45e4ad03a9719595845abdf56f1248d51c1d4..f2f8a791b66de161e7e1c4fd8d7feb8ec9d4c51b 100644
--- a/src/ldap/utilities.js
+++ b/src/ldap/utilities.js
@@ -1,19 +1,22 @@
 /**
- * @file Ce fichier regroupe les fonctions fondamentales aux interactions avec le LDAP.
- * C'est ici que tout le filtrage est opéré, au plus bas niveau.
- * Toutes les fonctions écrites ici sont asynchrones et renvoient des Promises ce qui nécessite de les appeler avec la synthaxe
- * un peu particulière `f(args).then(res => ...)` pour exploiter leur résultat.
+ * @file Ce fichier regroupe les fonctions simples de recherche et de test utiles, mais trop puissantes pour être exportées directement.
  * @author hawkspar
  */
 
-import * as basics from './basics';
+import LDAP from './basics';
+// Essentiels pour le fichier de config
+import path from 'path';
 import fs from 'fs';
 
+// Point central ; tous les champs de la BDD sont 'cachés' dans config.json et pas visibles directement
+var configPath = path.resolve('./','ldap_config.json');
+var config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
+
 //------------------------------------------------------------------------------------------------------------------------
 // Fonctions de recherche
 //------------------------------------------------------------------------------------------------------------------------
 
-class rechercheModulable {
+class SmartSearch {
     /**
      * @class Cette classe contient des fonctions de recherche génériques trop puissantes pour être exportées tel quel.
      * @summary Constructeur vide.
@@ -24,7 +27,7 @@ class rechercheModulable {
     /**
      * @summary Fonction qui interroge le LDAP et retrouve les groupes (voir LDAP) qui ressemblent
      *  à l'entrée. Etape 0 vers un vrai TOL (Trombino On Line).
-     * @desc Cette fonction utilise {@link LDAP.rechercher} mais avec un filtre généré à la volée. 
+     * @desc Cette fonction utilise {@link LDAP.search} mais avec un filtre généré à la volée. 
      * Accepte des champs exacts ou incomplets mais pas approximatifs
      * et ne gère pas l'auto-complete. Cette fonction utilise aussi config.json. MEF Timeout pour
      * des recherches trop vagues. Renvoit une liste d'uid.
@@ -34,25 +37,20 @@ class rechercheModulable {
      * @return {Promise(string[])} Liste des uid de groupes dont le nom ressemble à l'input
      * @static
      */
-    static trouverGroupes(input, return_attributes) {
-        return new Promise((resolve, reject) => {
-            // Escape de l'input utilisateur par sécurité
-            let str=ldapEscape.filter("${txt}", { txt: input});
-
-            // Construction du filtre custom
-            let filter= "(|("+config.key_id+"="+str+")"+      // On cherche la valeur exacte
-                        "(|("+config.key_id+"=*"+str+")"+     // La valeur finale avec des trucs avant ; wildcard *
-                        "(|("+config.key_id+"=*"+str+"*)"+    // La valeur du milieu avec des trucs avant et après
-                        "("+config.key_id+"="+str+"*))))";   // La valeur du début avec des trucs après
+    static groups(input, return_attributes) {
+        // Construction du filtre custom
+        let filter= "(|("+config.key_id+"="+input+")"+      // On cherche la valeur exacte
+                    "(|("+config.key_id+"=*"+input+")"+     // La valeur finale avec des trucs avant ; wildcard *
+                    "(|("+config.key_id+"=*"+input+"*)"+    // La valeur du milieu avec des trucs avant et après
+                    "("+config.key_id+"="+input+"*))))";   // La valeur du début avec des trucs après
 
-            // Appel rechercheLDAP avec filtre de l'espace 
-            LDAP.rechercher(config.dn_groups, return_attributes, filter).then(res => resolve(res));
-        });
+        // Appel rechercheLDAP avec filtre de l'espace 
+        return LDAP.search(config.dn_groups, return_attributes, filter);
     }
 
     /**
      * @summary Fonction qui renvoit les attributs demandés des paxs validant les critères de recherche. Première étape vers vrai TOL (Trombino On Line).
-     * @desc Cette fonction utilise {@link LDAP.rechercher} mais avec un filtre généré à la volée. Accepte des champs exacts ou incomplets pour la plupart des champs
+     * @desc Cette fonction utilise {@link LDAP.search} mais avec un filtre généré à la volée. Accepte des champs exacts ou incomplets pour la plupart des champs
      * mais pas approximatifs et ne gère pas l'auto-complete. MEF Timeout pour des recherches trop vagues. Elle utilise LDAPEscape pour éviter les injections.
      * Utiliser trouverGroupesParTypes pour chaque champ relié à groups.
      * @arg {Object} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse
@@ -74,30 +72,26 @@ class rechercheModulable {
      * @return {Promise(Object[])} Liste de dictionnaires de profils en cohérence avec l'input avec pour clés les attributs des profils.
      * @static
      */
-    static trouverUtilisateurs(data, return_attributes) {
-        return new Promise((resolve, reject) => {
-            let filter="";
-            // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfinis dans config encore
-            for (var key in data) {
-                if ((data[key]!= undefined) & (data[key] != '')) {                    // Si il y a qque chose à chercher pour ce filtre
-                    if (!Array.isArray(data[key])) { data[key]=[data[key]]; }        // Gestion d'une liste de valeurs à rechercher
-                    // Iteration pour chaque valeur fournie par l'utilisateur
-                    data[key].forEach(val => {
-                        // Escape de l'input utilisateur
-                        let str=ldapEscape.filter("${input}", { input: val});
-                        // Traduction en language LDAP
-                        let attribute = config.user[key];
-                        // Creation du filtre étape par étape
-                        filter="(&"+filter+ "(|("+attribute+"="+str+")"+      // On cherche la valeur exacte
-                                            "(|("+attribute+"=*"+str+")"+     // La valeur finale avec des trucs avant ; wildcard * (MEF la wildcart ne marche pas pour tous les attributs)
-                                            "(|("+attribute+"=*"+str+"*)"+    // La valeur du milieu avec des trucs avant et après
-                                            "("+attribute+"="+str+"*)))))";   // La valeur du début avec des trucs après
-                    });
-                }
+    static users(data, return_attributes) {
+        let filter="";
+        // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfinis dans config encore
+        for (var key in data) {
+            if ((data[key]!= undefined) & (data[key] != '')) {                    // Si il y a qque chose à chercher pour ce filtre
+                if (!Array.isArray(data[key])) { data[key]=[data[key]]; }        // Gestion d'une liste de valeurs à rechercher
+                // Iteration pour chaque valeur fournie par l'utilisateur
+                data[key].forEach(val => {
+                    // Traduction en language LDAP
+                    let attribute = config.user[key];
+                    // Creation du filtre étape par étape
+                    filter="(&"+filter+ "(|("+attribute+"="+val+")"+      // On cherche la valeur exacte
+                                        "(|("+attribute+"=*"+val+")"+     // La valeur finale avec des trucs avant ; wildcard * (MEF la wildcart ne marche pas pour tous les attributs)
+                                        "(|("+attribute+"=*"+val+"*)"+    // La valeur du milieu avec des trucs avant et après
+                                        "("+attribute+"="+val+"*)))))";   // La valeur du début avec des trucs après
+                });
             }
-            // Appel rechercheLDAP avec filtre de l'espace 
-            LDAP.rechercher(config.dn_users, return_attributes, filter).then(res => resolve(res));
-        });
+        }
+        // Appel rechercheLDAP avec filtre de l'espace 
+        return LDAP.search(config.dn_users, return_attributes, filter);
     }
 }
 
@@ -105,128 +99,77 @@ class rechercheModulable {
 // Fonctions intermédiaires TBT
 //------------------------------------------------------------------------------------------------------------------------
 
-class test {
-    /**
-     * @class Cette classe contient des fonctions intermédiaires de calcul utiles mais non exportables. La plupart sont des tests contre le LDAP
-     * @summary Constructeur vide.
-     * @author hawkspar
-     */
-    constructor() {}
-
-    /**
-     * @summary Cette fonction teste si un utilisateur est membre d'un groupe.
-     * @desc Utilise les méthodes statiques listerGroupes et listerMembres de {@link utilisateurAnonyme} mais aussi ajouterMembreGroupe de {@link administrateurConnecte}
-     * @param {string} uid - Identifiant de l'utilisateur à tester 
-     * @param {string} gid  - Identification du groupe à tester
-     * @returns {Promise(boolean)} True si l'utilisateur est membre
-     * @static
-     */
-    static etreMembreGroupe(uid, gid) {
-        return new Promise((resolve, reject) => {
-            utilisateurAnonyme.listerGroupes(uid).then(lg => {
-                utilisateurAnonyme.listerMembres(gid).then(lm => {
-                    if (lg.includes(gid) | lm.includes(uid)) {
-                        superAdministrateurConnecte.ajouterMembreGroupe(uid, gid);
-                        resolve(true);
-                    }
-                });
-            });
-        });
-    }
-
-    /**
-     * @summary Cette fonction teste si un utilisateur est admin d'un groupe.
-     * @desc Utilise les méthodes statiques listerAdministrateurs de {@link utilisateurAnonyme} mais aussi ajouterAdministrateurGroupe de {@link administrateurConnecte}
-     * @param {string} uid - Identifiant de l'utilisateur à tester 
-     * @param {string} gid  - Identification du groupe à tester
-     * @returns {Promise(boolean)} True si l'utilisateur est administrateur
-     * @static
-     */
-    static etreAdministrateurGroupe(uid, gid) {
-        return new Promise((resolve, reject) => {
-            utilisateurAnonyme.listerAdministrateurs(gid).then(la => {
-                if (la.includes(uid)) {
-                    superAdministrateurConnecte.ajouterAdministrateurGroupe(uid, gid);
-                    resolve(true);
-                }
-            });
-        });
-    }
-
+class Tests {
     /**
      * @summary Cette fonction teste une valeur d'un attribut (typiquement un identifiant) et le fait évoluer jusqu'à ce qu'il soit unique.
-     * @desc Librement adapté de Stack Overflow. Appelle {@link LDAP.rechercher} pour vérifier 
+     * @desc Librement adapté de Stack Overflow. Appelle {@link LDAP.search} pour vérifier 
      * qu'il n'y a pas d'autres occurences de cette valeur pour cette attribut
      * dans le dn fourni.
-     * @param {string} valeur - Valeur de l'attribut (le plus souvent un identifiant) à tester à cette itération
-     * @param {string} attribut - Attribut à tester
+     * @param {string} value - Valeur de l'attribut (le plus souvent un identifiant) à tester à cette itération
+     * @param {string} attribute - Attribut à tester
      * @param {string} dn - *Domain Name* dans lequel l'attribut doit être unique
-     * @param {function} evoluerValeur - Fonction qui prend uniquement en argument l'id courant et 
+     * @param {function} changeValue - Fonction qui prend uniquement en argument l'id courant et 
      * le nombre d'itérations et qui renvoit la prochaine valeur de l'attribut 
      * @param {int} n [0] - Nombre d'itérations (à initialiser à 0)
      * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié
      * @static
      */
-    static assurerUnicite(valeur, attribut, dn, evoluerValeur, n=0) {
-        return new Promise((resolve, reject) => { 
-            // Recherche d'autres occurences de l'id
-            LDAP.rechercher(dn, config.key_id, "("+attribut+"="+valeur+")").then(matches => {
-                // On renvoit la valeur si elle est bien unique
-                if (matches.length==0) { resolve(valeur); }
-                // Sinon, on tente de nouveau notre chance avec la valeur suivante
-                else { resolve(test.assurerUnicite(evoluerValeur(valeur, n+1), dn, evoluerValeur, n+1)); }
-            });
+    static ensureUnique(value, attribute, dn, changeValue, n=0) {
+        // Recherche d'autres occurences de l'id
+        LDAP.search(dn, config.key_id, "("+attribute+"="+value+")").then(matches => {
+            // On renvoit la valeur si elle est bien unique
+            if (matches.length==0) { return value; }
+            // Sinon, on tente de nouveau notre chance avec la valeur suivante
+            else { return this.ensureUnique(changeValue(value, n+1), dn, changeValue, n+1); }
         });
     }
 
     /**
      * @summary Cette fonction génère un uid standard, puis le fait évoluer jusqu'à ce qu'il soit unique.
-     * @desc Limité à un appel à {@link test.assurerUnicite} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation).
+     * @desc Limité à un appel à {@link Tests.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation).
      * @param {string} givenName - Prénom
      * @param {string} lastName - Nom
      * @param {string} promotion - Année de promotion
      * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié
      * @static
      */
-    static genererUid(givenName, lastName, promotion) {
-        // Le filtrage évite l'injection de code dans le LDAP, le normalize et lowerCase standardisent le format
-        return new Promise((resolve, reject) => {
-            test.assurerUnicite(ldapEscape.filter("${uid}",{uid: givenName+'.'+lastName}).toLowerCase().normalize('UFD'), config.key_id, config.dn_users, (id,n) => {
-                if (n==1) { id+='.'+ldapEscape.filter("${pr}",{pr: promotion}); }   // Si prénom.nom existe déjà, on rajoute la promo
-                else if (n==2) { id+='.'+n-1; }                                     // Puis si prénom.nom.promo existe déjà on passe à nom.prenom.promo .1
-                else if (n>2) { id+=+n; }                                           // Ensuite on continue .23, .234, etc...
-                return id;
-            }).then(id => resolve(id));
+    static generateUid(givenName, lastName, promotion) {
+        // normalize et lowerCase standardisent le format
+        return this.ensureUnique((givenName+'.'+lastName).toLowerCase().normalize('UFD'), config.key_id, config.dn_users, (id,n) => {
+            if (n==1) { id+='.'+promotion; }    // Si prénom.nom existe déjà, on rajoute la promo
+            else if (n==2) { id+='.'+n-1; }     // Puis si prénom.nom.promo existe déjà on passe à nom.prenom.promo .1
+            else if (n>2) { id+=n; }           // Ensuite on continue .23, .234, etc...
+            return id;
         });
     }
 
     /**
-     * @summary Cette fonction génère un gid standard, puis le fait évoluer jusqu'à ce qu'il soit unique.
-     * @desc Limité à un appel à {@link test.assurerUnicite} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation).
+     * @summary Cette fonction génère un id lisible, puis le fait évoluer jusqu'à ce qu'il soit unique.
+     * @desc Limité à un appel à {@link Tests.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation).
      * @param {string} name - Nom
      * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié
      * @static
      */
-    static genererGid(name) {
-        // Le filtrage évite l'injection de code dans le LDAP, le normalize et lowerCase standardisent le format
-        return new Promise((resolve, reject) => {
-            test.assurerUnicite(ldapEscape.filter("${id}",{id: name}).toLowerCase().normalize('UFD'), config.key_id, config.dn_groups, (id,n) => {
-                if (n==1) { id+='.'+n; }    // Si nom existe déjà, on essaie nom.1
-                else if (n>1) { id+=+n; }   // Ensuite on continue .12, .123, etc...
-                return id;
-            }).then(id => resolve(id));
+    static generateReadableId(name) {
+        // normalize et lowerCase standardisent le format
+        return this.ensureUnique(name.toLowerCase().normalize('UFD'), config.key_id, config.dn_groups, (id,n) => {
+            if (n==1) { id+='.'+n; }    // Si nom existe déjà, on essaie nom.1
+            else if (n>1) { id+=+n; }   // Ensuite on continue .12, .123, etc...
+            return id;
         });
     }
 
     /**
      * @summary Cette fonction teste une valeur dummy (0) pour un identifiant numérique puis le fait évoluer aléatoirement (entre 1 et 100 000) jusqu'à ce qu'il soit unique.
-     * @param {Object} user - Utilisateur de la forme nécessaire à {@link LDAP.connecter}.
+     * @param {Object} user - Utilisateur de la forme nécessaire à {@link LDAP.bind}.
      * @param {string} attribut - Intitulé exact de l'id concerné
      * @param {string} dn - *Domain Name* dans lequel l'attribut doit être unique
      * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié
      * @static
      */
-    static genererIdNum(attribut, dn) {
-        return new Promise((resolve,reject) => { test.assurerUnicite("0", attribut, dn, (id,n) => { Math.floor((Math.random() * 100000) + 1).toString(); }).then(id => resolve(id)); });
+    static generateId(attribut, dn) {
+        return this.ensureUnique("0", attribut, dn, (id,n) => { Math.floor((Math.random() * 100000) + 1).toString(); });
     }
-}
\ No newline at end of file
+}
+
+export {SmartSearch, Tests};
\ No newline at end of file