diff --git a/src/ldap/basics.ts b/src/ldap/basics.ts
index 1ce31791fd8ac0d6b0cc1e9fe89337007d6225bc..d064f4dfcc2d64c69bd377f9fc84d0b4d2aa8288 100644
--- a/src/ldap/basics.ts
+++ b/src/ldap/basics.ts
@@ -19,6 +19,10 @@ import {ldapConfig, credentialsLdapConfig} from './config';
 // Connection au serveur LDAP avec des temps de timeout arbitraires
 var client = ldap.createClient({ url: ldapConfig.server});
 
+interface dic {
+    [Key: string]: string;
+}
+
 //------------------------------------------------------------------------------------------------------------------------
 // Fonctions de base agissant sur le LDAP
 //------------------------------------------------------------------------------------------------------------------------
@@ -71,7 +75,52 @@ export class LDAP {
      * @async
      */
     static async unbind() : Promise<boolean> { return LDAP.bind("", ""); }
-
+    
+    /**
+     * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit une liste de valeurs trouvées.
+     * @desc Cette fonction utilise ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode search). Cette fonction fait une demande au LDAP
+     * qu'elle filtre selon un schéma prédéfini dans `filter` et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end).
+     * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur)
+     * @arg {string} attribute - Attribut unique à renvoyer
+     * @arg {string} id [null] - Identifiant facultatif pour une recherche triviale en o(1)
+     * @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape
+     * @return {Promise(string[])} 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
+     * @async
+     */
+    static async searchSingle(domain: 'gr'|'us', attribute: string, id: string=null, filter: string="(objectClass=*)") : Promise<string[]> {
+        LDAP.adminBind();
+        let dn ="";
+        if (id != null)     { dn+=ldapConfig.key_id+'='+id+','; }
+        if (domain == "gr") { dn+=ldapConfig.dn_groups; }
+        else                { dn+=ldapConfig.dn_users; }
+        let vals=[];
+        // Interrogation LDAP selon filter
+        client.search(ldapEscape.dn("${txt}", { txt: dn}), {
+            "scope": "sub",
+            "filter": ldapEscape.filter("${txt}", { txt: filter}),
+            "attributes": [attribute]
+        }, (err, res) => {
+            // Gestion erreur ; pb car pas simple true / autre en sortie
+            if (err) {
+                throw "Erreur lors de la recherche sur le LDAP.";
+            } else {
+                // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse
+                res.on('searchEntry', entry => {
+                    // Cas un seul attribut où le résultat est une liste directement
+                    vals.push(entry.object[attribute]);
+                });
+                // Si la recherche renvoie une erreur, on renvoit
+                res.on('error', resErr => { throw resErr; });
+                // Si la recherche est finie on se déconnecte
+                res.on('end', _ => { LDAP.unbind(); });
+            }
+        });
+        // On renvoit le résultat
+        return vals;
+    }
+    
     /**
      * @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). Cette fonction fait une demande au LDAP
@@ -80,18 +129,19 @@ export class LDAP {
      * @arg {string[]} attributes - Liste des attributs qui figureront dans le résultat final ; peut aussi être un seul élément
      * @arg {string} id [null] - Identifiant facultatif pour une recherche triviale en o(1)
      * @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape
-     * @return {(Promise(Array.<Object>)|Promise(Array.Object.<string, Object>))} Résultats de la recherche ; soit une liste de valeurs d'attributs, 
+     * @return {Promise(string[]|Array<dic>)} 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
      * @async
      */
-    static async search(domain: 'gr'|'us', attributes: string[], id=null, filter="(objectClass=*)") : Promise<Array<any>> {
+    static async searchMultiple(domain: 'gr'|'us', attributes: string[], id: string=null, filter: string="(objectClass=*)") : Promise<Array<dic>> {
         LDAP.adminBind();
-        if (domain == "gr") { var dn = ldapConfig.dn_groups; }
-        else                { var dn = ldapConfig.dn_users; }
-        if (id != null) { dn=ldapConfig.key_id+'='+id+','+dn; }
+        let dn ="";
+        if (id != null)     { dn+=ldapConfig.key_id+'='+id+','; }
+        if (domain == "gr") { dn+=ldapConfig.dn_groups; }
+        else                { dn+=ldapConfig.dn_users; }
         let vals=[];
-        // Interrogation LDAP selon ldapConfiguration fournie en argument
+        // Interrogation LDAP selon filter
         client.search(ldapEscape.dn("${txt}", { txt: dn}), {
             "scope": "sub",
             "filter": ldapEscape.filter("${txt}", { txt: filter}),
@@ -103,16 +153,11 @@ export class LDAP {
             } else {
                 // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse
                 res.on('searchEntry', 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];
-                        });
-                    }
+                    vals.push({});
+                    attributes.forEach(attribute => {
+                        vals.slice(-1)[0][attribute]=entry.object[attribute];
+                    });
                 });
                 // Si la recherche renvoie une erreur, on renvoit
                 res.on('error', resErr => { throw resErr; });
@@ -132,18 +177,18 @@ export class LDAP {
      * @arg {string} id - Identifiant unique de la feuille à modifier
      * @arg {"add"|"del"|"replace"} 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.<string, string>} mod - Dictionnaire contenant les attributs à modifier et les nouvelles valeurs des attributs.
-     * @arg {Object} mod[key] - Nouvelle valeur de l'attribut key. Une nouvelle valeur vide ("") est équivalent à la suppression de cet attribut.
+     * @arg {dic} mod - Dictionnaire contenant les attributs à modifier et les nouvelles valeurs des attributs.
+     * @arg {string} mod[key] - Nouvelle valeur de l'attribut key. 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
      * @async
      */
-    static async change(domain: 'gr'|'us', id: string, op: "add"|"del"|"replace", mod) : Promise<boolean> {
+    static async change(domain: 'gr'|'us', id: string, op: "add"|"del"|"replace", mod: dic) : Promise<boolean> {
         LDAP.adminBind();
         let dn = ldapConfig.key_id+'='+id+','
         if (domain == "gr") { dn+=ldapConfig.dn_groups }
         else                { dn+=ldapConfig.dn_users }
-        // Modification LDAP selon ldapConfiguration en argument (pourrait prendre une liste de Changes)
+        // Modification LDAP selon dn fourni en argument (pourrait prendre une liste de Changes)
         client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({
             operation: op,
             modification: mod,
@@ -168,10 +213,11 @@ export class LDAP {
      */
     static async add(domain: 'gr'|'us', vals) : Promise<boolean> {
         LDAP.adminBind();
-        if (domain == "gr") { var dn = ldapConfig.dn_groups }
-        else                { var dn = ldapConfig.dn_users }
+        let dn = ldapConfig.key_id+"="+vals[ldapConfig.key_id];
+        if (domain == "gr") { dn+=ldapConfig.dn_groups; }
+        else                { dn+=ldapConfig.dn_users; }
         // Ajout LDAP selon la ldapConfiguration en argument
-        client.add(ldapEscape.dn(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+",${txt}", { txt: dn}), vals, err => {
+        client.add(ldapEscape.dn("${txt}", { txt: dn}), vals, err => {
             throw "Erreur lors d'une opération d'ajout sur le LDAP.";
         });
         LDAP.unbind();
@@ -192,8 +238,8 @@ export class LDAP {
     static async clear(domain: 'gr'|'us', id: string) : Promise<boolean> {
         LDAP.adminBind();
         let dn = ldapConfig.key_id+'='+id+','
-        if (domain == "gr") { dn+=ldapConfig.dn_groups }
-        else                { dn+=ldapConfig.dn_users }
+        if (domain == "gr") { dn+=ldapConfig.dn_groups; }
+        else                { dn+=ldapConfig.dn_users; }
         // Suppression LDAP
         client.del(ldapEscape.dn("${txt}", {txt: dn}), err => {
             throw "Erreur lors d'une opération de suppression sur le LDAP.";
diff --git a/src/ldap/config.ts b/src/ldap/config.ts
index 8a9d8ad430f0cab239223504a62e5d9266b30939..e21ee1bcc986fc008f5063935721ec891c7cbf27 100644
--- a/src/ldap/config.ts
+++ b/src/ldap/config.ts
@@ -6,12 +6,12 @@
 import fs from 'fs';
 import path from 'path';
 import colors from 'colors';
-// Point central ; tous les champs de la BDD sont 'cachés' dans config.json et pas visibles directement
+// Point central ; tous les champs du LDAP sont 'cachés' dans config.json et pas visibles directement
 let path_config = path.resolve('..','ldap_config.json')
-let path_credentials = path.resolve('..','ldap_credentials.json')
 console.log(colors.cyan("Loading LDAP config file from "+path_config));
-console.log(colors.cyan("Loading LDAP credentials from "+path_credentials));
 export const ldapConfig = JSON.parse(fs.readFileSync(path_config).toString());
+let path_credentials = path.resolve('..','ldap_credentials.json')
+console.log(colors.cyan("Loading LDAP credentials from "+path_credentials));
 export const credentialsLdapConfig = JSON.parse(fs.readFileSync(path_credentials).toString());
 // Override config server from environment
 if (process.env.LDAP_URI != null) {
diff --git a/src/ldap/group.ts b/src/ldap/group.ts
index 91dbaf6324cdca1a40f42888061a2e501a86a272..39d64ef1f3fcea144f373b04f0d52489499d4b7a 100644
--- a/src/ldap/group.ts
+++ b/src/ldap/group.ts
@@ -10,7 +10,7 @@ import {Tools} from './utilities';
 /**
  * @interface groupData
  * @var {string} gid - Identifiant du groupe
- * @var {string} name - Nom du groupe
+ * @var {string} name - Nom du groupe (souvent son nom mais pas nécessairement)
  * @var {string} type - Statut du groupe ; binet, section sportive... (actuellement juste 'binet' ou 'free')
  * @var {string[]} members - Liste des membres du groupe
  * @var {string[]} admins - Liste des admins du groupe ; supposée être une sous-liste de la précédente
@@ -38,16 +38,15 @@ export class Group {
      
     /**
      * @summary Fonction qui renvoit toutes les infos relatives à un groupe particulier.
-     * @desc Cette fonction utilise {@link Tools.genericPeek} avec l'interface {@link groupData}.
+     * @desc Cette fonction utilise {@link Tools.peek} avec l'interface {@link groupData}.
      * @arg {string} gid - Identifiant du groupe
-     * @return {Promise(groupData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe ;
-     * voir `ldap_ldapConfig.json`(..\..\ldap_ldapConfig.json) pour les clés exactes.
+     * @return {Promise(groupData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe au format {@link groupData}.
      * @static
      * @async
      */
     static async peek(gid: string) : Promise<groupData> {
         try {
-            return Tools.genericPeek<groupData>("gr", gid);
+            return Tools.peek<groupData>("gr", gid);
         }
         catch(err) {
             throw "Erreur lors d'une recherche d'informations sur un groupe.";
@@ -55,31 +54,31 @@ export class Group {
     }
 
     /**
-     * @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 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 ldapConfig.json. MEF Timeout pour
-     * des recherches trop vagues. Renvoit une liste d'uid.
-     * @arg {string} input - String entré par l'utilisateur qui ressemble au nom du groupe.
+     * @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) pour les groupes.
+     * @desc Cette fonction utilise {@link Tools.search}.
+     * @arg {groupData} input - String entré par l'utilisateur qui ressemble au nom du groupe.
      * @return {Promise(string[])} Liste des gid dont le nom ressemble à l'input.
      * @static
      * @async
     */
-    static async search(input: string) : Promise<string[]> {
+    static async search(data: groupData) : Promise<string[]> {
         try {
-            return Tools.genericSearch("gr", {
-                "gid": input,
-                "name": "",
-                "type": "",
-                "members": [],
-                "admins": []
-            });
+            return Tools.search("gr", data);
         }
         catch(err) {
             throw "Erreur lors de la recherche approximative d'un groupe.";
         }
     }
 
+    /**
+     * @summary Fonction qui permet d'ajouter un utilisateur à un groupe.
+     * @desc Cette fonction fait essentiellement appel à {@link Tools.getMembers}, {@link Tools.getGroups} et {@link LDAP.change}.
+     * @arg {string} uid - Identifiant de l'utilisateur à ajouter
+     * @arg {string} gid - Identifiant du groupe
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
     static async addMember(uid: string, gid: string) : Promise<boolean> {
         try {
             // Vérifie que l'utilisateur est pas déjà membre pour groupes
@@ -116,7 +115,7 @@ export class Group {
 
     /**
      * @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}.
+     * @desc Cette fonction fait essentiellement appel à {@link Tools.getMembers}, {@link Tools.getGroups} et {@link LDAP.change}.
      * @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
@@ -135,8 +134,8 @@ export class Group {
                 // Les rajoute un par un, sauf pour le supprimé
                 lm.forEach(id => {
                     if (id!=uid) {
-                        this.addMember(id, gid).then(res => {
-                            if (!res) { throw "Erreur lors du ré-ajout des autres membres"; }
+                        Group.addMember(id, gid).then(res => {
+                            if (!res) { throw "Erreur lors du ré-ajout d'un autre membre"; }
                         });
                     }
                 });
@@ -156,7 +155,7 @@ export class Group {
                 // Les rajoute un par un, sauf pour le supprimé
                 lg.forEach(id => {
                     if (id!=gid) {
-                        this.addMember(uid, id).then(res => {
+                        Group.addMember(uid, id).then(res => {
                             if (!res) { throw "Erreur lors du ré-ajout des autres groupes"; }
                         });
                     }
@@ -170,10 +169,10 @@ export class Group {
     }
 
     /**
-     * @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
+     * @summary Fonction qui permet de promouvoir un membre au stade d'administrateur d'un groupe.
+     * @desc Cette fonction fait essentiellement appel à {@link Group.addMember} {@link Tools.getAdmins} et {@link LDAP.change}. 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} uid - Identifiant du membre futur admin
      * @arg {string} gid - Identifiant du groupe
      * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
      * @async
@@ -201,8 +200,9 @@ export class Group {
 
     /**
      * @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
+     * @desc Cette fonction fait essentiellement appel à {@link Group.remMember}, {@link Group.addMember} {@link LDAP.change}.
+     * Rajoute l'utilisateur au groupe par effet de bord si l'utilisateur n'est pas administrateur.
+     * @arg {string} uid - Identifiant de l'admin à dégrader, supposé membre
      * @arg {string} gid - Identifiant du groupe
      * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
      * @async
@@ -210,13 +210,15 @@ export class Group {
      */
     static async remAdmin(uid: string, gid: string): Promise<boolean> {
         // Peut paraître absurde mais permet de s'assurer que le membre est bien présent et que ses champs sont comme il faut
-        if (!(await Group.remMember(uid, gid) && Group.addMember(uid,gid))) { throw "Erreur dans l'éjection/réadmission du futur admin."; }
+        if (!(await Group.remMember(uid, gid) && Group.addMember(uid,gid))) { throw "Erreur dans l'éjection/réadmission de l'ex-admin."; }
         try {
             // Vérifie que l'utilisateur est bien admin (comme dans delGroupMember)
             let la = await Tools.getAdmins(gid);
             if (la.includes(uid)) {
                 // Supprime tous les administrateurs
-                if (!await LDAP.change("gr", gid, "del", ldapConfig.group.admins)) { throw "Erreur dans la suppression de tous les admins pour en supprimer un."; }
+                if (!await LDAP.change("gr", gid, "del", ldapConfig.group.admins)) {
+                    throw "Erreur dans la suppression de tous les admins pour en supprimer un.";
+                }
                 // Les rajoute un par un, sauf pour le supprimé
                 la.forEach(id => {
                     if (id!=uid) { Group.addAdmin(id, gid).then(res => {
@@ -233,7 +235,7 @@ export class Group {
     /**
      * @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}
+     * l'escape de ses paramètres. Appelle {@link LDAP.add} et {@link LDAP.change}, mais aussi {@link Group.addMember} et {@link Group.addAdmin}
      * pour gérer les groupes du nouvel utilisateur. Attention une manip FOIREUSE est cachée dedans.
      * @arg {groupData} data - Dictionnaire des informations utilisateurs (voir détail des champs dans ldapConfig.json)
      * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
@@ -255,7 +257,7 @@ export class Group {
             throw "Erreur lors de la génération d'un hruid pour créer un nouveau groupe.";
         }
 
-        let gid : string = vals[ldapConfig.key_id];
+        let gid: string = vals[ldapConfig.key_id];
 
         // Ecriture de toutes les valeurs directement inscrites dans le LDAP
         for (let key_att in data) { vals[ldapConfig.group[key_att]]=data[key_att] };
@@ -320,29 +322,41 @@ export class Group {
                 if (!res) { throw "Erreur de l'ajout d'un admin au groupe."; }
             });
         });
-
         return true;
     }
 
     /**
      * @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.
+     * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement. A modifier une fois que le LDAP incluerait les groupes administres par une utilisateur.
+     * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Group.remMember} et {@link Group.remAdmin} 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
      * @async
      * @static
      */
-    static async delete(gid): Promise<boolean> {
+    static async delete(gid: string): Promise<boolean> {
         try {
-            // Gestion des membres et administrateurs d'abord
+            // Gestion des administrateur et membres d'abord
             let profil = await Group.peek(gid);
-            // Ordre important
-            profil[ldapConfig.group['admin']].forEach( id => {
-                Group.remAdmin( id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un admin d'un groupe en cours de suppression."; } });
-            });
-            profil[ldapConfig.group['member']].forEach(id => {
-                Group.remMember(id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un membre."; } });
+            // Modification du profil de chaque utilisateur
+            profil[ldapConfig.group['member']].forEach(async function quickPartRemUser(uid: string) {
+                // Modification des profils de tous les utilisateurs
+                let lg = await Tools.getGroups(uid);
+                // Vérifie que l'utilisateur est pas déjà viré pour users
+                if (lg.includes(gid)) {
+                    // Supprime tous les groupes
+                    if (!await LDAP.change("us", uid, "del", ldapConfig.user.groups)) {
+                        throw "Erreur lors de la suppression de tous les groupes du membre.";
+                    }
+                    // Les rajoute un par un, sauf pour le supprimé
+                    lg.forEach(id => {
+                        if (id!=gid) {
+                            Group.addMember(uid, id).then(res => {
+                                if (!res) { throw "Erreur lors du ré-ajout des autres groupes"; }
+                            });
+                        }
+                    });
+                }
             });
             // Elimination
             if (!await LDAP.clear("gr",gid)) { throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes."; }
@@ -354,8 +368,8 @@ export class Group {
     }
 
     /**
-     * @summary Fonction qui édite un groupe existant dans le LDAP.
-     * @desc Appelle {@link genericEdit} bien sûr, mais aussi {@link addMember} et {@link addAdmin}.
+     * @summary Fonction qui édite un groupe existant dans le LDAP. Sans influence sur ses membres ou admins. 
+     * @desc Appelle {@link Tools.edit}.
      * @arg {groupData} data - Dictionnaire des informations du groupe.
      * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
      * @async
@@ -363,16 +377,7 @@ export class Group {
      */
     static async edit(data: groupData) : Promise<boolean> {
         try {
-            let gid = data["gid"];
-            // Remove old members and admins
-            let profil = Group.peek(gid);
-            profil["members"].forEach(uid => { Group.remMember(uid, gid); });
-            profil["admins"].forEach(uid => { Group.remAdmin(uid, gid); });
-            // Add new members and admins
-            data["members"].forEach(uid => { Group.addMember(uid, gid); });
-            data["admins"].forEach(uid => { Group.addAdmin(uid, gid); });
-            // Edit all other fields
-            return Tools.genericEdit("gr",data);
+            return Tools.edit("gr",data);
         }
         catch(err) {
             throw "Erreur lors de la modification d'un groupe.";
diff --git a/src/ldap/user.ts b/src/ldap/user.ts
index 35fadd5bdc3d6d3cd18dde759ea7ad83a5a1fdc4..f2f9356801208824251f0d25cb1e7addc6e53995 100644
--- a/src/ldap/user.ts
+++ b/src/ldap/user.ts
@@ -71,15 +71,15 @@ export class User {
      
     /**
      * @summary Fonction qui renvoit les infos de base relatives à un utilisateur particulier.
-     * @desc Cette fonction utilise {@link Tools.genericPeek} avec l'interface {@link userData}.
-     * @arg {string} uid - Identifiant de l'utilisateur
-     * @return {Promise(userData)} Informations recueillies.
+     * @desc Cette fonction utilise {@link Tools.peek} avec l'interface {@link userData}.
+     * @arg {string} uid - Identifiant de l'utilisateur, supposé valide.
+     * @return {Promise(userData)} Informations recueillies au format {@link userData}.
      * @static
      * @async
      */
     static async peek(uid: string) : Promise<userData> {
         try { 
-            return Tools.genericPeek<userData>("us", uid);
+            return Tools.peek<userData>("us", uid);
         }
         catch(err) {
             throw "Error while peeking a user.";
@@ -87,12 +87,10 @@ export class User {
     }
     
     /**
-     * @summary Fonction qui retrouve les uid des paxs validant les critères de recherche. Utiliser {@link peek} au cas par cas après pour obtenir les vraies infos.
-     * @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. Va crasher si un champ n'est pas dans ldapConfig.
-     * Utiliser trouverGroupesParTypes pour chaque champ relié à groups.
+     * @summary Fonction qui retrouve les uid des paxs validant les critères de recherche. Utiliser {@link User.peek} au cas par cas après pour obtenir les vraies infos.
+     * @desc Cette fonction utilise {@link Tools.search}.
      * @arg {userData} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse
-     * comme des sous-parties compactes des valeurs renvoyées. Tous les champs ci-dessous peuvent être indifféremment des listes (par exempl pour chercher un membre
+     * comme des sous-parties compactes des valeurs renvoyées. Tous les champs ci-dessous peuvent être indifféremment des listes (par exemple pour chercher un membre
      * de plusieurs groupes) ou des éléments isolés. Si un champ n'est pas pertinent, le mettre à '' ou undefined.
      * @return {Promise(string[])} gids des profils qui "match" les critères proposés.
      * @static
@@ -100,7 +98,7 @@ export class User {
      */
     static async search(data: userData) : Promise<string[]> {
         try {
-            return Tools.genericSearch("us", data);
+            return Tools.search("us", data);
         }
         catch(err) {
             throw "Erreur lors de la recherche approximative d'un utilisateur.";
@@ -109,8 +107,9 @@ export class User {
     
     /**
      * @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.
+     * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link Group.addMember} et {@link Group.addAdmin} pour gérer les groupes du nouvel utilisateur.
      * @arg {fullUserData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis.
+     * Cette application ne permet pas de rejoindre des groupes.
      * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
      * @async
      * @static
@@ -206,19 +205,6 @@ export class User {
                 if (!res) { throw "Erreur lors de l'ajout d'une valeur constante à la feuille du nouvel utilisateur."; }
             });
         });
-
-        // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble
-        data['groupsIsMember'].forEach(gid => {
-            Group.addMember(uid, gid).then(res => {
-                if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe."; }
-            });
-        });
-        data['groupsIsAdmin'].forEach(gid => { 
-            Group.addAdmin(uid, gid).then(res => {
-                if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe en tant qu'admin."; }
-            });
-        });
-
         return true;
     }
 
@@ -229,7 +215,7 @@ export class User {
     /**
      * @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.
+     * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Group.remMember} et {@link Group.remAdmin} 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
      * @async
@@ -239,12 +225,25 @@ export class User {
         try {
             // Gestion des groupes d'abord
             let profil = await User.peek(uid);
-            profil[ldapConfig.user['groups']].forEach(gid => {
-                // Opérations effectuées par effet de bord
-                if (Tools.isGroupAdmin(uid,gid)) {
-                    if (!Group.remAdmin(uid, gid)) { throw "Erreur lors de la suppression des droits d'admin de l'utilisateur."; }
+            profil[ldapConfig.user['groups']].forEach(async function (gid: string) {
+                // Si l'utilisateur était admin, l'enlever
+                Group.remAdmin(uid, gid);
+                // Enlever de la liste des membres
+                let lm = await Tools.getMembers(gid);
+                if (lm.includes(uid)) {
+                    // Supprime tous les membres
+                    if (!await LDAP.change("gr", gid, "del", ldapConfig.group.members)) {
+                        throw "Erreur lors de la suppression de tous les membres du groupe.";
+                    }
+                    // Les rajoute un par un, sauf pour le supprimé
+                    lm.forEach(id => {
+                        if (id!=uid) {
+                            Group.addMember(id, gid).then(res => {
+                                if (!res) { throw "Erreur lors du ré-ajout d'un autre membre"; }
+                            });
+                        }
+                    });
                 }
-                if (!Group.remMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenance à un groupe de l'utilisateur."; }
             });
         }
         catch(err) {
@@ -256,8 +255,8 @@ export class User {
     }
 
     /**
-     * @summary Fonction qui édite un utilisateur existant dans le LDAP. Très similaire à {@link creerUtilisateur}
-     * @desc Appelle simplement {@link genericEdit}.
+     * @summary Fonction qui édite un utilisateur existant dans le LDAP.
+     * @desc Appelle simplement {@link Tools.edit}. Sans effet sur les groupes de l'utilisateur concerné.
      * @arg {userData} data - Dictionnaire des informations utilisateurs
      * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
      * @async
@@ -265,16 +264,7 @@ export class User {
      */
     static async edit(data : userData) : Promise<boolean> {
         try {
-            let uid = data["uid"];
-            // Leave old groups
-            let profil = User.peek(uid);
-            profil["groups"].forEach(gid => { Group.remMember(uid, gid); });
-            profil["groupsIsAdmin"].forEach(gid => { Group.remAdmin(uid, gid); });
-            // Join new groups
-            data["groups"].forEach(gid => { Group.addMember(uid, gid); });
-            data["groupsIsAdmin"].forEach(gid => { Group.addAdmin(uid, gid); });
-            // Edit all other fields
-            return Tools.genericEdit("us",data);
+            return Tools.edit("us",data);
         }
         catch(err) {
             throw "Erreur lors de la modification d'un utilisateur.";
diff --git a/src/ldap/utilities.ts b/src/ldap/utilities.ts
index dc47a122097126bc9301dc10a96f4ef2e4349acf..ab6d6ea161a8fbf9ddfaa732c42ced080484df18 100644
--- a/src/ldap/utilities.ts
+++ b/src/ldap/utilities.ts
@@ -6,8 +6,8 @@
 
 import {ldapConfig} from './config';
 import {LDAP} from './basics';
-import { userData } from './user';
-import { groupData } from './group';
+import {userData} from './user';
+import {groupData} from './group';
 
 //------------------------------------------------------------------------------------------------------------------------
 // Fonctions intermédiaires TBT
@@ -31,7 +31,7 @@ export class Tools {
      * @static
      * @async
      */
-    static async genericPeek<T>(domain: 'us'|'gr', id: string) : Promise<T> {
+    static async peek<T>(domain: 'us'|'gr', id: string) : Promise<T> {
         if (domain='gr') {
             var dirtyKeys = ldapConfig.group;
         }
@@ -39,7 +39,7 @@ export class Tools {
             var dirtyKeys = ldapConfig.user;
         }
         let cleanData : T;
-        let dirtyData = await LDAP.search(domain, dirtyKeys.values(), id);
+        let dirtyData = await LDAP.searchMultiple(domain, dirtyKeys.values(), id)[0];
         // Rename output
         for (let uncleanKey in dirtyData) {
             for (let cleanKey in cleanData) {
@@ -56,7 +56,7 @@ export class Tools {
      * @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. Va crasher si un champ n'est pas dans ldapConfig.
      * @param T - Format renvoyé (en pratique {@link userData} ou {@link groupData})
-     * @arg {string} domain - Domaine de la recherche (utilisateur ou groupe)
+     * @arg {"us"|"gr"} domain - Domaine de la recherche (utilisateur ou groupe)
      * @arg {userData | groupData} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse
      * comme des sous-parties compactes des valeurs renvoyées. Tous les champs ci-dessous peuvent être indifféremment des listes (par exemple pour chercher un membre
      * de plusieurs groupes) ou des éléments isolés. Si un champ n'est pas pertinent, le mettre à '' ou undefined.
@@ -64,7 +64,7 @@ export class Tools {
      * @static
      * @async
      */
-    static async genericSearch(domain : "us"|"gr", data : userData|groupData) : Promise<string[]> {
+    static async search(domain : "us"|"gr", data : userData|groupData) : Promise<string[]> {
         let filter="";
         // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfinis dans ldapConfig encore
         for (var key in data) {
@@ -85,19 +85,19 @@ export class Tools {
             }
         }
         // Appel avec filtre de l'espace 
-        return LDAP.search(domain, [ldapConfig.key_id], null, filter);
+        return LDAP.searchSingle(domain, ldapConfig.key_id, null, filter);
     }
     
     /**
-     * @summary Fonction qui édite un groupe existant dans le LDAP.
+     * @summary Fonction qui édite un groupe ou utilisateur existant dans le LDAP. N'agit pas sur l'apprtenance à un groupe.
      * @desc Appelle {@link LDAP.change}.
-     * @arg {userData | groupData} data - Dictionnaire des informations utilisateurs au même format que pour {@link User.addGroup} avec tous les champs optionnels...
-     * Sauf 'gid', qui permet de savoir quel groupe modifier et qui est donc inchangeable. On peut modifier nickname par contre.
-     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @arg {"us"|"gr"} domain - Domaine de l'opération' (utilisateur ou groupe).
+     * @arg {userData | groupData} data - Dictionnaire avec les nouvelles valeurs de la feuille.
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon.
      * @async
      * @static
      */
-    static async genericEdit(domain: "us"|"gr", data: userData|groupData) : Promise<boolean> {
+    static async edit(domain: "us"|"gr", data: userData|groupData) : Promise<boolean> {
         if (domain = "us") {
             var id=data['uid'];
             var dirtyKeys=ldapConfig.user;
@@ -106,9 +106,10 @@ export class Tools {
             var id=data['gid'];
             var dirtyKeys=ldapConfig.group;
         }
-        // Renommage LDAP-friendly
+        // Rename in an LDAP-friendly way
         let dirtyData = {};
         Object.keys(data).forEach(function(key: string) {
+            // Some values edit can't change
             if (!['readPerm','writePerm','groups','groupsIsAdmin','members','admins'].includes(key)) {
                 dirtyData[dirtyKeys.key]=data[key];
             }
@@ -132,15 +133,15 @@ export class Tools {
      * @param {"gr"|"us"} domain - Domaine dans lequel l'attribut doit être unique
      * @param {changeValueCallback} 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)
+     * @param {number} n [0] - Nombre d'itérations (à initialiser à 0)
      * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié
      * @static
      * @async
      */
-    static async ensureUnique(value: string, attribute: string, domain: 'gr'|'us', changeValue: (string, number) => string, n=0) : Promise<string> {
+    static async ensureUnique(value: string, attribute: string, domain: 'gr'|'us', changeValue: (string, number) => string, n: number=0) : Promise<string> {
         // Recherche d'autres occurences de l'id
         try {
-            return LDAP.search(domain, [ldapConfig.key_id], null, "("+attribute+"="+value+")").then(function (matches: string[]) {
+            return LDAP.searchSingle(domain, ldapConfig.key_id, null, "("+attribute+"="+value+")").then(function (matches: string[]) {
                 if (!matches) { throw ""; }
                 // On renvoit la valeur si elle est bien unique
                 else if (matches.length=0) { return value; }
@@ -227,7 +228,7 @@ export class Tools {
      */
     static async getGroups(uid: string) : Promise<string[]> {
         try {
-            return LDAP.search("us", [ldapConfig.user.groups], uid)[0];
+            return LDAP.searchSingle("us", ldapConfig.user.groups, uid)[0];
         }
         catch(err) {
             throw "Erreur lors de la recherche des groupes d'un individu.";
@@ -244,7 +245,7 @@ export class Tools {
      */
     static async getMembers(gid: string) : Promise<string[]> {
         try {
-            return LDAP.search("gr", [ldapConfig.group.members], gid)[0];
+            return LDAP.searchSingle("gr", ldapConfig.group.members, gid)[0];
         }
         catch(err) {
             throw "Erreur lors de la recherche des membres d'un groupe.";
@@ -261,7 +262,7 @@ export class Tools {
      */
     static async getAdmins(gid: string) : Promise<string[]> {
         try {
-            return LDAP.search("gr", [ldapConfig.group.admins], gid)[0];
+            return LDAP.searchSingle("gr", ldapConfig.group.admins, gid)[0];
         }
         catch(err) {
             throw "Erreur lors de la recherche des admins d'un groupe.";