From 1d140c79721971e0f1b8a1cfe33b18a88923951f Mon Sep 17 00:00:00 2001 From: Quentin CHEVALIER <quentin.chevalier@polytechnique.edu> Date: Thu, 1 Mar 2018 11:28:53 +0100 Subject: [PATCH] =?UTF-8?q?Requ=C3=AAtes=20mieux=20foutues=20et=20stables,?= =?UTF-8?q?=20TOL=20en=20deux=20parties?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.json | 26 +++--- src/ldap/ldap_data.js | 207 ++++++++++++++++++++++++++++++++---------- 2 files changed, 171 insertions(+), 62 deletions(-) diff --git a/config.json b/config.json index 27f950c..f73dbeb 100644 --- a/config.json +++ b/config.json @@ -1,21 +1,21 @@ { + "connexion": { + "dn": "uid=quentin.chevalier,ou=eleves,dc=frankiz,dc=net", + "passwd": "Ie42fds'eaJm1" + }, "ldap": { "server": "ldap://frankiz.eleves.polytechnique.fr:389", "searchBase": "ou=eleves,dc=frankiz,dc=net", "searchFilter": "(uid={{username}})" }, - "ldap_data_gl": { - "searchBase": "ou=groups,dc=frankiz,dc=net", - "searchFilter":"(restrictedMemberUid=${id})" - }, - "ldap_data_ml": { - "searchBase": "ou=eleves,dc=frankiz,dc=net", - "searchFilter": "(brMemberOf=${id})" - }, - "ldap_data_tol": { - "searchBase": "ou=eleves,dc=frankiz,dc=net", - "searchFilterAttributes": ["givenName", "sn", "brPromo","telephoneNumber","mail","brRoom","brIP","brMemberOf","brMemberOf","brMemberOf","brMemberOf","brMemberOf","brMemberOf"], - "searchAttributes": ["jpegPhoto","givenName", "sn", "brBirthdate", "brPromo","telephoneNumber","mail","brRoom","brIP"] - }, + "dn_groups":"ou=groups,dc=frankiz,dc=net", + "dn_users": "ou=eleves,dc=frankiz,dc=net", + "filter_lm":"(brMemberOf=${id})", + "filter_al":"(brMemberOf=${id})", + "filter_lg":"(restrictedMemberUid=${id})", + "filter_st": "(uid=${id})", + "attributes_id": ["uid"], + "attributes_all": ["jpegPhoto","givenName", "sn", "brBirthdate", "brPromo","telephoneNumber","mail","brRoom","brIP"], + "search_attributes_tol": ["givenName", "sn", "displayName", "country", "brMemberOf", "brPromo", "brMemberOf", "brMemberOf", "brMemberOf", "telephoneNumber","mail","brRoom","brIP"], "sessionSecret":"change this" } \ No newline at end of file diff --git a/src/ldap/ldap_data.js b/src/ldap/ldap_data.js index 9a93f2d..6abfba2 100644 --- a/src/ldap/ldap_data.js +++ b/src/ldap/ldap_data.js @@ -1,16 +1,16 @@ /** - * @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. + * @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 +/* 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'; */ +import ldapEscape from 'ldap-escape'; -var ldap = require('ldapjs'); +/*var ldap = require('ldapjs'); var fs = require('fs'); -var ldapEscape = require('ldap-escape'); +var ldapEscape = require('ldap-escape');*/ // Important ; permet de vérifier que l'utilisateur reste connecté. var ensureLoggedin = require('connect-ensure-login').ensureLoggedIn; @@ -19,94 +19,201 @@ var ensureLoggedin = require('connect-ensure-login').ensureLoggedIn; let config = JSON.parse(fs.readFileSync('../../config.json', 'utf8')); // Connection au serveur LDAP avec des temps de timeout arbitraires -var client = ldap.createClient({ url: config.ldap.server, timeout: 10000, idleTimeout: 10000}); +var client = ldap.createClient({ url: config.ldap.server}); /** - * @summary Fonction qui interroge le LDAP et retrouve les groupes dont un individu est membre. - * @desc 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 listGroups(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 prédéfini dans config.json et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end). - * @arg {int} uid - Identifiant de l'individu à interroger - * @return {string} Liste des uid de groupes (noms flat des groupes) où l'id fourni est membre + * @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(dic).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 listGroups(uid) { +function requeteLDAP(base, filter, filter_dic, attributes) { return new Promise(function(resolve, reject) { - var groupsList=[]; - - client.search(config.ldap_data_gl.searchBase, {scope: "sub", attributes: "uid", filter: ldapEscape.filter(config.ldap_data_gl.searchFilter, {id: uid})}, function(err, res) { + // A terme mettre id du type qui se connecte (permet de pas avoir trop de demandes trop rapides) + client.bind(config.connexion.dn, config.connexion.passwd, (err, res) => {}); + + var vals=[]; + // Interrogation LDAP selon configuration du dic + client.search(base, { + "scope": "sub", + "filter": ldapEscape.filter(filter, filter_dic), + "attributes": attributes + }, function(err, res) { + // Gestion erreur if (err) { reject(err); } else { - res.on('searchEntry', function(entry) { groupsList.push(entry.object.uid); }); - res.on('end', function(res) { resolve(groupsList); }); + // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse + res.on('searchEntry', function(entry) { + attributes.forEach((element) => { + eval("vals.push(entry.object."+element+")"); + console.log(vals); + }); + }); + // Si la recherche est finie on se déconnecte et on renvoit la liste + res.on('end', function(res) { + client.bind("", "", (err, res) => {}); + resolve(vals); + }); } }); }); } + +/** + * @summary Fonction qui interroge le LDAP et retrouve les groupes dont un individu est membre. + * @desc Cette fonction utilise requeteLDAP avec un dictionnaire prédéfini dans config.json. + * @arg {int} uid - Identifiant de l'individu à interroger + * @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) { + resolve(requeteLDAP(config.dn_groups, config.filter_lg, { id : uid }, config.attributes_id)); + }); +} /** * @summary Fonction qui interroge le LDAP et retrouve la liste des membres d'un groupe. - * @desc 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 listMembers(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 prédéfini dans config.json et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end). + * @desc Cette fonction utilise requeteLDAP avec un dictionnaire prédéfini dans config.json. * @arg {int} gid - Identifiant du groupe à interroger * @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) { - var membersList=[]; - - client.search(config.ldap_data_ml.searchBase="", {scope: "sub", attributes: "uid", filter: ldapEscape.filter(config.ldap_data_ml.searchFilter, {id: gid})}, function(err, res) { - if (err) { - reject(err); - } else { - res.on('searchEntry', function(entry) { membersList.push(entry.object.uid); }); - res.on('end', function(res) { resolve(membersList); }); + resolve(requeteLDAP(config.dn_users, config.filter_lm, { id: gid }, config.attributes_id)); + }); +} + +/** + * @summary Fonction qui interroge le LDAP et retrouve la liste des admins d'un groupe. + * @desc Cette fonction utilise requeteLDAP avec un dictionnaire prédéfini dans config.json. + * @arg {int} gid - Identifiant du groupe à interroger + * @return {string} Liste des uid des membres où l'id fournie est membre (noms flat des groupes) + */ +function listAdmins(gid) { + //TBC + return new Promise(function(resolve, reject) { + resolve(requeteLDAP(gid, config.ldap_data_al)); + }); +} + +/** + * @summary Fonction qui interroge le LDAP au sujet d'un uid particulier et qui renvoit toutes ses infos. + * @arg {string} uid - Identifiants de l'utilisateur + * @return {string[]} Informations recueillies ; même quand dans TOL voir config.json. + */ +function rens(uid) { + return new Promise(function(resolve, reject) { + resolve(requeteLDAP(config.dn_users, config.filter_st, { id: uid }, config.attributes_all)); + }); +} + +/** + * @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). + * @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. + * @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) { + var filter=""; + var 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); } }); + + // Appel requeteLDAP avec filtre de l'espace + resolve(requeteLDAP(config.dn_users, filter, filter_dic, config.attributes_id)); }); } /** - * @summary Fonction qui interroge le LDAP et retrouve les paxs validant les critères de recherche. - * @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). + * @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. * @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 {int} c5 - Promotion + * @arg {string} c5 - Promotion * @arg {string} c6 - Groupe * @arg {string} c7 - Cours * @arg {string} c8 - Sport pratiqué - * @arg {int} c9 - Numéro de téléphone + * @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 ; plusieurs fois une liste sur le format ci-dessus pour tous les résultats correspondants plus une photo en byttestring et date de naissance. + * @arg {int} + * @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) { - var candidatesList=[]; + // Pas nécessaire mais bien + client.bind(config.connexion.dn, config.connexion.passwd, (err, res) => {}); var filter=""; var dic={}; - + // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfini dans config.json encore config.ldap_data_tol.searchFilterAttributes.forEach((element, index, list) => { - if (eval("c"+index.toString()) != '') { - filter="(&"+filter+"(|("+element+"=*${app_"+element+index+"})"+ - "(|("+element+"=${app_"+element+index+"})"+ - "(|("+element+"=*${app_"+element+index+"}*)"+ - "("+element+"=${app_"+element+index+"}*)))))"; + 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); - client.search(config.ldap_data_tol.searchBase, {scope: "sub", attributes: config.ldap_data_tol.searchAttributes, filter: ldapEscape.filter(filter, dic)}, function(err, res) { + var candidatesList=[]; + + // Interrogation LDAP et filtre de malade + client.search(config.ldap_data_tol.searchBase, { + scope: "sub", + attributes: config.ldap_data_tol.searchAttributes, + 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) { - for (var i=0; i<config.ldap_data_tol.searchAttributes.length; i++) { - // Complicated, but hides searchAttributes - eval("candidatesList.push(entry.object."+config.ldap_data_tol.searchAttributes[i]+")"); - } + // De nouveau, iteration sur les valeurs recherchées et entrée dans la liste + config.ldap_data_tol.searchAttributes.forEach((element) => { + eval("candidatesList.push(entry.object."+element+")"); + }); + }); + res.on('page', function(result, cb) { + resolve(candidatesList); + console.log("page ends"); }); res.on('end', function(res) { resolve(candidatesList); }); } @@ -114,11 +221,13 @@ function TOL(c0="", c1="", c2="", c3="", c4="", c5="", c6="", c7="", c8="", c9=" }); } -// Synthaxe d'utilisation -//listGroups("quentin.louis").then((meList) => { console.log(meList); }); -TOL("","","","","","","faerix","","","","","","").then((meList) => { console.log(meList); }); +//listMembers("drakkes").then((meList) => { console.log(meList); }); +//listGroups("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 -export { listGroups, listMembers, TOL }; */ +/* Partage pour le reste du monde ; même remarque synthaxe que pour l'import */ +export { listGroups, listMembers, TOL }; -module.exports ={ listGroups, listMembers, TOL }; \ No newline at end of file +/*module.exports ={ listGroups, listMembers, TOL };*/ \ No newline at end of file -- GitLab