From e4fcd212349966ba119054dccccc8b3ea90e6adb Mon Sep 17 00:00:00 2001
From: hawkspar <quentin.chevalier@polytechnique.edu>
Date: Mon, 3 Dec 2018 16:04:29 +0100
Subject: [PATCH] Commit du vrai merge

---
 README.md             |   1 -
 src/ldap/group.ts     |  50 ++++++-------------
 src/ldap/user.ts      |  66 ++++++-------------------
 src/ldap/utilities.ts | 112 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 143 insertions(+), 86 deletions(-)

diff --git a/README.md b/README.md
index 4d2b8b0..01edf86 100644
--- a/README.md
+++ b/README.md
@@ -109,7 +109,6 @@ On peut définir ces variables d'environnement, **dans l'ordre décroissant de p
     ...
     ```
 
-
 ## Panneau d'administration
 
 Il est accessible au path `/adminview/admin` ; n'importe quel path devrait rediriger dessus, ou alors vers `/adminview/login`. Les identifiants à utiliser sont ceux de Frankiz. L'authentification se fait par le LDAP Frankiz.
diff --git a/src/ldap/group.ts b/src/ldap/group.ts
index 2b7005a..b32643c 100644
--- a/src/ldap/group.ts
+++ b/src/ldap/group.ts
@@ -38,7 +38,7 @@ export class Group {
      
     /**
      * @summary Fonction qui renvoit toutes les infos relatives à un groupe particulier.
-     * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis.
+     * @desc Cette fonction utilise {@link Tools.genericPeek} 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.
@@ -47,17 +47,7 @@ export class Group {
      */
     static async peek(gid: string) : Promise<groupData> {
         try {
-            let fields = [];
-            fields.push(ldapConfig.group.values());
-            let LDAPGroupData = await LDAP.search("gr", fields, gid);
-            let cleanGroupData : groupData;
-            // Rename output
-            for (let uncleanKey in LDAPGroupData) {
-                for (let cleanKey in cleanGroupData) {
-                    if (uncleanKey==ldapConfig.group[cleanKey]) { cleanGroupData[cleanKey] = LDAPGroupData[uncleanKey]; }
-                }
-            }
-            return cleanGroupData;
+            return Tools.genericPeek<groupData>("gr", gid);
         }
         catch(err) {
             throw "Erreur lors d'une recherche d'informations sur un groupe.";
@@ -77,14 +67,13 @@ export class Group {
     */
     static async search(input: string) : Promise<string[]> {
         try {
-            // Construction du filtre custom
-            let filter= "(|("+ldapConfig.key_id+"="+ input+")" +    // On cherche la valeur exacte
-            "(|("+ldapConfig.key_id+"=*"+input+")" +    // La valeur finale avec des trucs avant ; wildcard *
-            "(|("+ldapConfig.key_id+"=*"+input+"*)"+    // La valeur du milieu avec des trucs avant et après
-            "("+  ldapConfig.key_id+"="+ input+"*))))"; // La valeur du début avec des trucs après
-
-            // Appel rechercheLDAP avec filtre de l'espace 
-            return LDAP.search("gr", [ldapConfig.key_id], null, filter);
+            return Tools.genericSearch<groupData>("gr", {
+                "gid": input,
+                "name": "",
+                "type": "",
+                "members": [],
+                "admins": []
+            });
         }
         catch(err) {
             throw "Erreur lors de la recherche approximative d'un groupe.";
@@ -134,7 +123,7 @@ export class Group {
      * @async
      * @static
      */
-    static async delMember(uid: string, gid: string): Promise<boolean> {
+    static async remMember(uid: string, gid: string): Promise<boolean> {
         try {
             // Vérifie que l'utilisateur est pas déjà viré pour groupes
             let lm = await Tools.getMembers(gid);
@@ -219,9 +208,9 @@ export class Group {
      * @async
      * @static
      */
-    static async delAdmin(uid: string, gid: string): Promise<boolean> {
+    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.delMember(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 du futur admin."; }
         try {
             // Vérifie que l'utilisateur est bien admin (comme dans delGroupMember)
             let la = await Tools.getAdmins(gid);
@@ -350,10 +339,10 @@ export class Group {
             let profil = await Group.peek(gid);
             // Ordre important
             profil[ldapConfig.group['admin']].forEach( id => {
-                Group.delAdmin( id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un admin d'un groupe en cours de suppression."; } });
+                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.delMember(id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un membre."; } });
+                Group.remMember(id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un membre."; } });
             });
             // Elimination
             if (!await LDAP.clear("gr",gid)) { throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes."; }
@@ -375,17 +364,10 @@ export class Group {
      */
     static async edit(data: groupData) : Promise<boolean> {
         try {
-            let gid = data['gid'];
-            // Récupération des anciennes données
-            let profil = await Group.peek(gid);
-            // Surcharge des champs à modifier selon data
-            for (let key in data) { profil[key]=data[key]; }
-            // Modification propre
-            if (!await Group.delete(gid) && await Group.create(profil)) { throw "Erreur de la destruction/recréation du groupe pour le modifier."; }
-            return true;
+            return Tools.genericEdit<groupData>("gr",data);
         }
         catch(err) {
-            throw "Erreur lors de l'obtention du profil d'un groupe pour le modifier.";
+            throw "Erreur lors de la modification d'un groupe.";
         }
     }
 }
\ No newline at end of file
diff --git a/src/ldap/user.ts b/src/ldap/user.ts
index 7f1ab31..20c9a90 100644
--- a/src/ldap/user.ts
+++ b/src/ldap/user.ts
@@ -24,6 +24,13 @@ import {Group} from './group';
  * @var {string[]} adresses - Adresse(s)
  * @var {string[]} mails - Adresse(s) courriel
  * @var {string[]} groups - Un ou plusieurs groupes dont l'utilisateur est membre (inclus section sportive, binet, PA...)
+ * @var {string} password - Mot de passe généré en amont
+ * @var {string[]} ips - Adresse(s) ip
+ * @var {string} directory - Adresse soft des données utilisateurs
+ * @var {string} login - Astuce de root flemmard
+ * @arg {string} readPerm - Permissions spéciales BR
+ * @var {string} writePerm - Permissions spéciales BR
+ * @var {string[]} forlifes - Alias BR (attention le filtre .fkz n'est plus fonctionnel)
  * @var {string[]} admins - Liste des gid dont l'utilisateur est admin ; supposé sous-liste de groups
  * TBA @var {string[]} likes - Liste des gid dont l'utilisateur est sympathisant
  */
@@ -40,27 +47,6 @@ export interface userData {
     "adresses"?: string,
     "mails"?: string[],
     "groups"?: string[],
-    "admins"?: string[]
-    //"likes"?: string[]
-}
-
-/**
- * @interface fullUserData
- * @desc Contient les champs utilisateurs intéressants pour le LDAP de l'X, pas pour sigma
- * @var {string} fullName - Nom complet
- * @var {string} password - Mot de passe généré en amont
- * @var {string[]} ips - Adresse(s) ip
- * @var {string} directory - Adresse soft des données utilisateurs
- * @var {string} login - Astuce de root flemmard
- * @arg {string} readPerm - Permissions spéciales BR
- * @var {string} writePerm - Permissions spéciales BR
- * @var {string[]} forlifes - Alias BR (attention le filtre .fkz n'est plus fonctionnel)
- * @var {string[]} classes - Classes du LDAP, pas de sens particulier
- * @var {string} id - Identifiant plus complet
- * @var {string} idNum - Identifiant numérique
- */
-export interface fullUserData extends userData {
-    "fullName"?: string,
     "password"?: string,
     "ips"?: string[],
     "directory"?: string,
@@ -68,9 +54,8 @@ export interface fullUserData extends userData {
     "readPerm"?: string,
     "writePerm"?: string,
     "forlifes"?: string[],
-    "classes"?: string[],
-    "id"?: string,
-    "idNum"?: string
+    "admins"?: string[]
+    //"likes"?: string[]
 }
 
 //------------------------------------------------------------------------------------------------------------------------
@@ -171,7 +156,7 @@ export class User {
      * @async
      * @static
      */
-    static async create(data: fullUserData): Promise<boolean> {
+    static async create(data: userData): Promise<boolean> {
         // Calcul d'un dictionnaire d'ajout
         let vals = {};
 
@@ -298,9 +283,9 @@ export class User {
             profil[ldapConfig.user['groups']].forEach(gid => {
                 // Opérations effectuées par effet de bord
                 if (Tools.isGroupAdmin(uid,gid)) {
-                    if (!Group.delAdmin(uid, gid)) { throw "Erreur lors de la suppression des droits d'admin de l'utilisateur."; }
+                    if (!Group.remAdmin(uid, gid)) { throw "Erreur lors de la suppression des droits d'admin de l'utilisateur."; }
                 }
-                if (!Group.delMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenance à un groupe de l'utilisateur."; }
+                if (!Group.remMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenance à un groupe de l'utilisateur."; }
             });
         }
         catch(err) {
@@ -323,33 +308,12 @@ export class User {
      * @async
      * @static
      */
-    static async edit(data : fullUserData) : Promise<boolean> {
-        let uid = data['uid'];
-        // Récupération des anciennes données
-        let profil = await User.genericPeek<fullUserData>(uid);
+    static async edit(data : userData) : Promise<boolean> {
         try {
-            // Régénération du champ manquant dans profil
-            let lg = await Tools.getGroups(uid);
-            profil['groupsIsAdmin']=[];
-            lg.forEach(gid => {
-                Tools.isGroupAdmin(uid, gid).then(res => {
-                    if (res) { profil['groupsIsAdmin'].push(gid); }
-                });
-            });
-            // Surcharge des champs à modifier selon data
-            Object.keys(data).forEach(function(key: string) {
-                // Some fields the user cannot change (groups and groupsIsAdmin must be changed through addGroupMember and addGroupAdmin in Admin)
-                if (!['readPerm','writePerm','forlifes','ips','groups','groupsIsAdmin'].includes(key)) { profil[key]=data[key]; }
-            });
-            // Modification propre par effet de bord
-            if (!(await User.delete(uid) && await User.create(profil))) {
-                throw "Erreur dans la destruction/création du compte.";
-            } else {
-                return true;
-            }
+            return Tools.genericEdit<userData>("us",data);
         }
         catch(err) {
-            throw "Erreur lors de la modification des groupes où un utilisateur est admin.";
+            throw "Erreur lors de la modification d'un utilisateur.";
         }
     }
 }
\ No newline at end of file
diff --git a/src/ldap/utilities.ts b/src/ldap/utilities.ts
index 0791636..55fb907 100644
--- a/src/ldap/utilities.ts
+++ b/src/ldap/utilities.ts
@@ -6,6 +6,10 @@
 
 import {ldapConfig} from './config';
 import {LDAP} from './basics';
+import { groupData } from './group';
+import { userData } from './user';
+import { Group } from './group';
+import { User } from './user';
 
 //------------------------------------------------------------------------------------------------------------------------
 // Fonctions intermédiaires TBT
@@ -18,6 +22,114 @@ export class Tools {
      * @author hawkspar
     */
     constructor() {}
+     
+    /**
+     * @summary Fonction qui renvoit toutes les infos relatives à un groupe ou un utilisateur particulier.
+     * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis.
+     * @arg {string} domain - Domaine de la recherche
+     * @arg {string} id - Identifiant de la feuille cherchée
+     * @return {Promise(T)} 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
+     * @async
+     */
+    static async genericPeek<T>(domain: 'us' | 'gr', id: string) : Promise<T> {
+        let fields = [];
+        if (domain=='gr')   { fields.push(ldapConfig.group.values()); }
+        else                { fields.push(ldapConfig.user.values()); }
+        let LDAPData = await LDAP.search(domain, fields, id);
+        let cleanData : T;
+        // Rename output
+        for (let uncleanKey in LDAPData) {
+            for (let cleanKey in cleanData) {
+                if (uncleanKey==ldapConfig.group[cleanKey]) { cleanData[cleanKey] = LDAPData[uncleanKey]; }
+            }
+        }
+        return cleanData;
+    }
+
+    
+    /**
+     * @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é à repliquer TOL
+     * 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 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.
+     * @arg {T} 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
+     * 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
+     * @async
+     */
+    static async genericSearch<T>(domain : "us"|"gr", data : T) : 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) {
+            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 = "";
+                    if (domain=="us")   { attribute = ldapConfig.user[key]; }
+                    else                { attribute = ldapConfig.group[key]; }
+                    // Creation incrémentale du filtre
+                    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 avec filtre de l'espace 
+        return LDAP.search(domain, [ldapConfig.key_id], null, filter);
+    }
+
+    
+    /**
+     * @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 {T} 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
+     * @async
+     * @static
+     */
+    static async genericEdit<T>(domain: "us" | "gr", data: T) : Promise<boolean> {
+        let id = "";
+        if (domain == "us")     { id=data['uid']; }
+        else                    { id=data['gid']; }
+        // Récupération des anciennes données
+        let profil = await Tools.genericPeek<T>(domain,id);
+        if (domain == "us") {
+            // Régénération du champ manquant dans profil
+            let lg = await Tools.getGroups(id);
+            profil['groupsIsAdmin']=[];
+            lg.forEach(gid => {
+                Tools.isGroupAdmin(id, gid).then(res => {
+                    if (res) { profil['groupsIsAdmin'].push(gid); }
+                });
+            });
+        }
+        // Surcharge des champs à modifier selon data
+        Object.keys(data).forEach(function(key: string) {
+            // Some fields the user cannot change
+            if (!['readPerm','writePerm','groups','groupsIsAdmin','members','admins'].includes(key)) { profil[key]=data[key]; }
+            // Specialised management of group membership and admin status
+            if (key=="groups") { data[key].array.forEach(gid => { Group.addMember(id, gid); }); }
+            if (key=="groupsIsAdmin") { data[key].array.forEach(gid => { Group.addAdmin(id, gid); }); }
+            if (key=="members") { data[key].array.forEach(uid => { Group.addMember(uid, id); }); }
+            if (key=="admins") { data[key].array.forEach(uid => { Group.addMember(uid, id); }); }
+        });
+        // Renommage LDAP-friendly
+        let dirtyProfil = {};
+        Object.keys(profil).forEach(function(key: string) {
+            if (domain=="gr")   { dirtyProfil[ldapConfig.group.key]=profil[key]; }
+            else                { dirtyProfil[ldapConfig.user.key]=profil[key]; }
+        });
+        return LDAP.change(domain,id,"replace",dirtyProfil);
+    }
 
     /**
      * @callback changeValueCallback
-- 
GitLab