Source: src/ldap/ldap_data.js

/**
 * @file Ce fichier gère les requêtes LDAP de type données ; liste des groupe d'un individu, liste des membres d'un groupe... A ne pas confondre avec ldap_auth qui lui gère l'authentification. Il est à lire conjointement avec config.json qui détaille les pats et champs spécifique à un LDAP.
 * @author hawkspar
 */

/* Ne pas hésiter à repasser en synthaxe ES5... Plus simple pour tester en solo avec node directement */
import ldap  from 'ldapjs';
import fs from 'fs';
import ldapEscape from 'ldap-escape';
<<<<<<< HEAD
import path from 'path';*/

/**/var ldap = require('ldapjs');
var fs = require('fs');
var ldapEscape = require('ldap-escape');
var path = require('path');
=======
import path from 'path';

/*var ldap = require('ldapjs');
var fs = require('fs');
var ldapEscape = require('ldap-escape');
var path = require('path');*/
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea

// Important ; permet de vérifier que l'utilisateur reste connecté.
var ensureLoggedin =  require('connect-ensure-login').ensureLoggedIn;

// 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');
<<<<<<< HEAD
=======
console.log('LDAP configuration loaded from',configPath);
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
var config = JSON.parse(fs.readFileSync(configPath, 'utf8'));

// Connection au serveur LDAP avec des temps de timeout arbitraires
var client = ldap.createClient({ url: config.ldap.server});
<<<<<<< HEAD

//------------------------------------------------------------------------------------------------------------------------
// Fonctions de recherche
//------------------------------------------------------------------------------------------------------------------------

/**
 * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit les valeurs trouvées.
 * @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 search). Il faut l'appeler suivant un schéma `rechercheLDAP(...).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} base - DN de l'emplacement de la requête
 * @arg {string} filter - Filtre logique de la recherche (format RFC2254)
 * @arg {Object} filter_dic - Dictionnaire qui associe les faux champs dans filter aux vraies valeurs pertinentes de la recherche
 * @arg {string[]} attributes - Liste des attributs qui figureront dans le résultat final
 * @return {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 rechercheLDAP(base, filter, filter_dic, attributes) {
=======

/**
 * @summary Fonction qui interroge le LDAP selon un protocole bien précis et renvoit les valeurs trouvées.
 * @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 search). Il faut l'appeler suivant un schéma requeteLDAP(...).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} base - DN de l'emplacement de la requête
 * @arg {string} filter - Filtre logique de la recherche (format RFC2254)
 * @arg {{}} filter_dic - Dictionnaire qui associe les faux champs dans filter aux vraies valeurs pertinentes de la recherche
 * @arg {string[]} attributes - Liste des attributs qui figureront dans le résultat final
 * @return {string[]} Résultats de la recherche
 */
function requeteLDAP(base, filter, filter_dic, attributes) {
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
    // Debug
    //console.log(base);
    //console.log(filter);
    //console.log(filter_dic);
    //console.log(attributes);
    return new Promise(function(resolve, reject) {
        // A terme mettre id du type qui se connecte (permet de pas avoir trop de demandes trop rapides)
<<<<<<< HEAD
        //client.bind(config.connexion.dn, config.connexion.passwd, (err, res) => {});

        let vals=[];
        // Interrogation LDAP selon configuration fournie en argument
=======
        client.bind(config.connexion.dn, config.connexion.passwd, (err, res) => {});

        let vals=[];
        // Interrogation LDAP selon configuration du dic
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
        client.search(base, {
            "scope": "sub",
            "filter": ldapEscape.filter(filter, filter_dic),
            "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) {
<<<<<<< HEAD
                    // Cas un seul attribut où le résultat est une liste
                    if (attributes.length == 1) { eval("vals.push(entry.object."+attributes[0]+")"); }
                    // Cas plusieurs attributs donc résultat dictionnaire
                    else {
                        vals.push({});
                        attributes.forEach((element) => {
                            vals.slice(-1)[0][element]=eval("entry.object."+element+";");
                        });
                    }
=======
                    attributes.forEach((element) => {
                        eval("vals.push(entry.object."+element+")");
                    });
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
                });
                // Si la recherche est finie on se déconnecte et on renvoit la liste
                res.on('end', function(res) {
                    client.bind("", "", (err, res) => {});
                    // Debug
                    //console.log(vals);
                    resolve(vals);
                });
            }
        });
    });
}

/**
 * @summary Fonction qui interroge le LDAP et retrouve les groupes dont un individu est membre.
<<<<<<< HEAD
 * @desc Cette fonction utilise rechercheLDAP avec un dictionnaire prédéfini dans config.json. Il faut l'appeler selon un schéma `listGroups(...).then((res) => { truc avec res })`;
=======
 * @desc Cette fonction utilise requeteLDAP avec un dictionnaire prédéfini dans config.json. Il faut l'appeler selon un schéma listGroups(...).then((res) => { truc avec res });
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
 * @arg {string} uid - Identifiant de l'individu à interroger (le plus souvent prenom.nom, parfois l'année, supposé valide)
 * @return {string} Liste des uid de groupes (noms flat des groupes) où l'id fourni est membre
 */
function listGroups(uid) {
    return new Promise(function(resolve, reject) {
<<<<<<< HEAD
        resolve(rechercheLDAP(config.dn_users, config.filter_id, { id : uid }, config.attributes_lg).then(res => res[0]));
=======
        resolve(requeteLDAP(config.dn_users, config.filter_id, { id : uid }, config.attributes_lg).then(res => res[0]));
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
    });
}

/**
 * @summary Fonction qui interroge le LDAP et retrouve les groupes dont un individu est membre. Assez différente de la précédente en terme d'implémentation, plus lente mais renvoir moins de résultats plus pertinents, probablement plus pérenne.
<<<<<<< HEAD
 * @desc Cette fonction utilise rechercheLDAP avec un dictionnaire prédéfini dans config.json. Il faut l'appeler selon un schéma `listGroups2(...).then((res) => { truc avec res });`
=======
 * @desc Cette fonction utilise requeteLDAP avec un dictionnaire prédéfini dans config.json. Il faut l'appeler selon un schéma listGroups2(...).then((res) => { truc avec res });
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
 * @arg {string} uid - Identifiant de l'individu à interroger (le plus souvent prenom.nom, parfois l'année, supposé valide)
 * @return {string} Liste des uid de groupes (noms flat des groupes) où l'id fourni est membre
 */
function listGroups2(uid) {
    return new Promise(function(resolve, reject) {
<<<<<<< HEAD
        resolve(rechercheLDAP(config.dn_groups, config.filter_lg, { id : uid }, config.attributes_id));
=======
        resolve(requeteLDAP(config.dn_groups, config.filter_lg, { id : uid }, config.attributes_id));
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
    });
}

/**
 * @summary Fonction qui interroge le LDAP et retrouve la liste des membres d'un groupe.
<<<<<<< HEAD
 * @desc Cette fonction utilise rechercheLDAP avec un dictionnaire prédéfini dans config.json. Il faut l'appeler selon un schéma `listMembers(...).then((res) => { truc avec res });`
=======
 * @desc Cette fonction utilise requeteLDAP avec un dictionnaire prédéfini dans config.json.
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
 * @arg {string} gid - Identifiant du groupe à interroger (le plus souvent nom du groupe en minuscule)
 * @return {string} Liste des uid des membres où l'id fournie est membre (noms flat des groupes)
 */
function listMembers(gid) {
    return new Promise(function(resolve, reject) {
<<<<<<< HEAD
        resolve(rechercheLDAP(config.key_id+"="+gid+","+config.dn_groups, config.filter_id, { id: gid }, config.attributes_lm).then(res => res[0]));
=======
        resolve(requeteLDAP(config.dn_groups, config.filter_id, { id: gid }, config.attributes_lm).then(res => res[0]));
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
    });
}

/**
 * @summary Fonction qui interroge le LDAP et retrouve la liste des admins d'un groupe.
<<<<<<< HEAD
 * @desc Cette fonction utilise rechercheLDAP avec un dictionnaire prédéfini dans config.json. Il faut l'appeler selon un schéma `listAdmins(...).then((res) => { truc avec res });`
=======
 * @desc Cette fonction utilise requeteLDAP avec un dictionnaire prédéfini dans config.json.
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
 * @arg {string} gid - Identifiant du groupe à interroger (le plus souvent nom du groupe en minuscule)
 * @return {string} Liste des uid des membres où l'id fournie est membre (noms flat des groupes)
 */
function listAdmins(gid) {
    return new Promise(function(resolve, reject) {
<<<<<<< HEAD
        resolve(rechercheLDAP(config.key_id+"="+gid+","+config.dn_groups, config.filter_id, { id: gid }, config.attributes_al).then(res => res[0]));
=======
        resolve(requeteLDAP(config.key_al+"="+gid+","+config.dn_groups, config.filter_id, { id: gid }, config.attributes_al).then(res => res[0]));
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
    });
}
 
/**
 * @summary Fonction qui interroge le LDAP au sujet d'un uid particulier et qui renvoit toutes ses infos.
<<<<<<< HEAD
 * @desc Cette fonction utilise rechercheLDAP avec un dictionnaire prédéfini dans config.json. Il faut l'appeler selon un schéma `rens(...).then((res) => { truc avec res });`
=======
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
 * @arg {string} uid - Identifiant de l'utilisateur
 * @return {string[]} Informations recueillies ; même quand dans TOL voir config.json.
 */
function rens(uid) {
    return new Promise(function(resolve, reject) {
<<<<<<< HEAD
        resolve(rechercheLDAP(config.dn_users, config.filter_id, { id: uid }, config.attributes_all));
=======
        resolve(requeteLDAP(config.dn_users, config.filter_id, { id: uid }, config.attributes_all));
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
    });
}

/**
 * @summary Fonction qui interroge le LDAP et retrouve juste les uid des paxs validant les critères de recherche. Première étape vers vrai TOL (Trombino On Line).
<<<<<<< HEAD
 * @desc Accepte des champs exacts ou incomplets mais pas approximatifs et ne gère pas l'auto-complete. Cette fonction utilise une Promise pour être asynchrone ; elle renvoit la promesse d'une réponse puis traite la demande. Il faut l'appeler suivant un schéma `TOL(uid).then((res) => { truc avec res });`. Aucun bind n'est nécessaire donc pas d'identifiant ou de mot de passe à passer. Cette fonction fait une demande au LDAP qu'elle filtre selon un schéma généré à la volée à partir de config.json et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end). MEF Timeout pour des recherches trop vagues.
=======
 * @desc Accepte des champs exacts ou incomplets mais pas approximatifs et ne gère pas l'auto-complete. Cette fonction utilise une Promise pour être asynchrone ; elle renvoit la promesse d'une réponse puis traite la demande. Il faut l'appeler suivant un schéma TOL(uid).then((res) => { truc avec res });. Aucun bind n'est nécessaire donc pas d'identifiant ou de mot de passe à passer. Cette fonction fait une demande au LDAP qu'elle filtre selon un schéma généré à la volée à partir de config.json et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end). MEF Timeout pour des recherches trop vagues.
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
 * @arg {string} c0 - Prénom
 * @arg {string} c1 - Nom
 * @arg {string} c2 - Surnom
 * @arg {string} c3 - Nationalité
 * @arg {string} c4 - Ecole ou université d'origine
 * @arg {string} c5 - Promotion
 * @arg {string} c6 - Groupe
 * @arg {string} c7 - Cours
 * @arg {string} c8 - Sport pratiqué
 * @arg {string} c9 - Numéro de téléphone
 * @arg {string} c10 - Adresse courriel
 * @arg {string} c11 - Adresse physique
 * @arg {string} c12 - Adresse ip
 * @return {string[]} uids des profils qui "match" les critères proposés.
 */
function idTOL(c0="", c1="", c2="", c3="", c4="", c5="", c6="", c7="", c8="", c9="", c10="", c11="", c12="") {
    return new Promise(function(resolve, reject) {
        let filter="";
        let filter_dic={};
        // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfini dans config.json encore
        config.search_attributes_tol.forEach((element, index, list) => {
            if (eval("c"+index) != '') {                                 // Si il y a qque chose à chercher pour ce filtre
                filter="(&"+filter+"(|("+element+"=${app_"+element+index+"})"+      // On cherche la valeur exacte
                                   "(|("+element+"=*${app_"+element+index+"})"+     // La valeur finale avec des trucs avant ; wildcard *
                                   "(|("+element+"=*${app_"+element+index+"}*)"+    // La valeur du milieu avec des trucs avant et après
                                     "("+element+"=${app_"+element+index+"}*)))))"; // La valeur du début avec des trucs après
                // Mise en relation de chaque valeur à la demande utilisateur dans un dico
                filter_dic["app_"+element+index]=eval("c"+index);
            }
        });

<<<<<<< HEAD
        // Appel rechercheLDAP avec filtre de l'espace 
        resolve(rechercheLDAP(config.dn_users, filter, filter_dic, config.attributes_id));
=======
        // Appel requeteLDAP avec filtre de l'espace 
        resolve(requeteLDAP(config.dn_users, filter, filter_dic, config.attributes_id));
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
    });
}

/**
<<<<<<< HEAD
 * @summary Fonction qui interroge le LDAP et retrouve les paxs validant les critères de recherche. Bien mais vite inutilisable car demande trop au LDAP et renvoie des erreurs de type size limit. Préférer idTOL puis stalk au cas par cas.
 * @desc Accepte des champs incomplets mais pas approximatifs et ne gère pas l'auto-complete. Cette fonction utilise une Promise pour être asynchrone ; elle renvoit la promesse d'une réponse puis traite la demande. Il faut l'appeler suivant un schéma `TOL(uid).then((res) => { truc avec res });`. Aucun bind n'est nécessaire donc pas d'identifiant ou de mot de passe à passer. Cette fonction fait une demande au LDAP qu'elle filtre selon un schéma généré à la volée à partir de config.json et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end). MEF Timeout pour des recherches trop vagues.
=======
 * @summary Fonction qui interroge le LDAP et retrouve les paxs validant les critères de recherche. Bien mais vite inutilisable car demande trop au LDAP.
 * @desc Accepte des champs incomplets mais pas approximatifs et ne gère pas l'auto-complete. Cette fonction utilise une Promise pour être asynchrone ; elle renvoit la promesse d'une réponse puis traite la demande. Il faut l'appeler suivant un schéma TOL(uid).then((res) => { truc avec res });. Aucun bind n'est nécessaire donc pas d'identifiant ou de mot de passe à passer. Cette fonction fait une demande au LDAP qu'elle filtre selon un schéma généré à la volée à partir de config.json et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end). MEF Timeout pour des recherches trop vagues.
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
 * @arg {string} c0 - Prénom
 * @arg {string} c1 - Nom
 * @arg {string} c2 - Surnom
 * @arg {string} c3 - Nationalité
 * @arg {string} c4 - Ecole ou université d'origine
 * @arg {string} c5 - Promotion
 * @arg {string} c6 - Groupe
 * @arg {string} c7 - Cours
 * @arg {string} c8 - Sport pratiqué
 * @arg {string} c9 - Numéro de téléphone
 * @arg {string} c10 - Adresse courriel
 * @arg {string} c11 - Adresse physique
 * @arg {string} c12 - Adresse ip
 * @return {string[]} Informations recueillies ; voir config.json.
 */
function TOL(c0="", c1="", c2="", c3="", c4="", c5="", c6="", c7="", c8="", c9="", c10="", c11="", c12="") {
    return new Promise(function(resolve, reject) {
        // Pas nécessaire mais bien
        client.bind(config.connexion.dn, config.connexion.passwd, (err, res) => {});

        let filter="";
        let dic={};
        // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfini dans config.json encore
<<<<<<< HEAD
        config.search_attributes_tol.forEach((element, index, list) => {
=======
        config.ldap_data_tol.searchFilterAttributes.forEach((element, index, list) => {
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
            if (eval("c"+index.toString()) != '') {                                 // Si il y a qque chose à chercher pour ce filtre
                filter="(&"+filter+"(|("+element+"=${app_"+element+index+"})"+      // On cherche la valeur exacte
                                   "(|("+element+"=*${app_"+element+index+"})"+     // La valeur finale avec des trucs avant ; wildcard *
                                   "(|("+element+"=*${app_"+element+index+"}*)"+    // La valeur du milieu avec des trucs avant et après
                                     "("+element+"=${app_"+element+index+"}*)))))"; // La valeur du début avec des trucs après
                // Mise en relation de chaque valeur à la demande utilisateur dans un dico
                dic["app_"+element+index]=eval("c"+index.toString());
            }
        });
        // Debug
        //console.log(filter);
        //console.log(dic);

        var candidatesList=[];

        // Interrogation LDAP et filtre de malade
<<<<<<< HEAD
        client.search(config.dn_users, { 
            scope: "sub",
            attributes: config.attributes_all,
=======
        client.search(config.ldap_data_tol.searchBase, { 
            scope: "sub",
            attributes: config.ldap_data_tol.searchAttributes,
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
            filter: ldapEscape.filter(filter, dic)
        },
        function(err, res) {
            // Gestion erreur
            if (err) {
                reject(err);
            } else {
                // Toujours gestion différenciée début et fin
                res.on('searchEntry', function(entry) {
<<<<<<< HEAD
                    // De nouveau, iteration sur les valeurs recherchées et entrée dans la liste sous la forme d'un dictionnaire
                    candidatesList.push({});
                    config.attributes_all.forEach((element) => {
                        candidatesList.slice(-1)[0][element]=eval("entry.object."+element+";");
=======
                    // De nouveau, iteration sur les valeurs recherchées et entrée dans la liste
                    candidatesList.push([]);
                    config.ldap_data_tol.searchAttributes.forEach((element) => {
                        eval("candidatesList.slice(-1)[0].push(entry.object."+element+")");
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea
                    });
                });
                res.on('page', function(result, cb) {
                    resolve(candidatesList);
                });
                res.on('end', function(res) { resolve(candidatesList); });
            }
        });
    });
}

<<<<<<< HEAD
//------------------------------------------------------------------------------------------------------------------------
// Fonctions de modification
//------------------------------------------------------------------------------------------------------------------------

/**
 * @summary Fonction qui va plonger dans le LDAP et modifier un certain jeu de valeur en argument.
 * @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 search). Il faut l'appeler suivant un schéma `modifierLDAP(...).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} name - DN de l'emplacement de la requête
 * @arg {string} op - Deux types ; "replace" ou "add", "add" pouvant rajouter un champ, "replace" sans modification étant une suppression
 * @arg {Object} mod - Dictionnaire contenant les attributs à modifier et les modifications
 * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
 */
function modifierLDAP(name, op, mod) {
    // Debug TBM
    //console.log(base);
    //console.log(operation);
    //console.log(modification);
    return new Promise(function(resolve, reject) {
        // A terme mettre id du type qui se connecte (permet de pas avoir trop de demandes trop rapides et problème de permission)
        client.bind(config.connexion.dn, config.connexion.passwd, (err, res) => {});

        // Modification LDAP selon configuration en argument
        client.modify(name, new ldap.Change({
            operation: op,
            modification: mod,
        }), function(err) {
            reject(err);
        });
        resolve(true);
    });
}

//modifierLDAP("uid=quentin.chevalier,ou=eleves,dc=frankiz,dc=net","replace", {"displayName": ["hawkspar"]}).then(res => console.log(res));
//rechercheLDAP("uid=quentin.chevalier,ou=eleves,dc=frankiz,dc=net","(displayName=${cn})",{ cn: "Sire hawkspar" }, ["jpegPhoto"]).then(res => console.log(res));

/* Partage pour le reste du monde ; même remarque synthaxe que pour l'import
export { listGroups, listMembers, TOL };*/

/**/module.exports ={ listGroups, listMembers, listAdmins, rens, idTOL, TOL };
=======
//listMembers("drakkes").then((meList) => { console.log(meList); });
//listAdmins("br").then((adList) => { console.log(adList); });
//listGroups("agopia.1999").then((grList) => { console.log(grList); });
//listGroups2("agopia.1999").then((grList) => { console.log(grList); });
//rens("quentin.louis").then((profil) => { console.log(profil); });
//idTOL("","","","","","","bda","","","","","","").then((caList) => { console.log(caList); });
//TOL("","","","","","","faerix","","","","","","").then((caList) => { console.log(caList); });

/* Partage pour le reste du monde ; même remarque synthaxe que pour l'import */
export { listGroups, listMembers, TOL };

/*module.exports ={ listGroups, listMembers, TOL };*/
>>>>>>> 0d37bfa57a7bad303c19ffa3b40a2120b399beea