From 2360c7ddcc2dbdc49460bdd8c0387443228cf829 Mon Sep 17 00:00:00 2001 From: Quentin CHEVALIER <quentin.chevalier@polytechnique.edu> Date: Mon, 5 Mar 2018 01:23:33 +0100 Subject: [PATCH] =?UTF-8?q?Ttes=20fonctions=20=C3=A9dition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ldap_config.json | 134 ++++----- src/ldap/ldap_data.js | 640 +++++++++++++++++++++++++++--------------- 2 files changed, 463 insertions(+), 311 deletions(-) diff --git a/ldap_config.json b/ldap_config.json index 9962671..11d58ea 100644 --- a/ldap_config.json +++ b/ldap_config.json @@ -9,94 +9,53 @@ "COMMENT2": "Noms de domaines dans LDAP ; le niv d'après est en uid=, voir Wikipedia", "dn_groups":"ou=groups,dc=frankiz,dc=net", "dn_users": "ou=eleves,dc=frankiz,dc=net", - - "COMMENT3": "Anonymisation du champ id", - "key_id": "uid", - "COMMENT4": "Anonymisation des attributs retournés par les différentes fonctions listerGroupes (lg), listerMembres (lm), etc...", - "lg": { "attributs": "brMemberOf" }, - "lm": { "attributs": "restrictedMemberUid" }, - "la": { "attributs": "memberUid" }, - "rs": { - "attributs": ["jpegPhoto","givenName", "sn", "brBirthdate", "brPromo","telephoneNumber","mail","brRoom","brIP","brMemberOf"] - }, - "tgty": { - "filtre": "(brNS=${ty})" - }, - - "tolm": { - "input_names": ["givenName", "lastName", "nickname", "nationality", "promotion", "phone", "adress", "ip", "school", "groups", "studies", "sport", "mail"], - "correspondance": { - "givenName": "givenName", - "lastName": "sn", - "nickname": "displayName", - "nationality": "country", - "promotion": "brPromo", - "phone": "telephoneNumber", - "mail": "mail", - "adress": "brRoom", - "ip": "brIP", - "school": "brMemberOf", "groups": "brMemberOf", "studies": "brMemberOf", "sport": "brMemberOf" - } - }, - "tol": { - "attributes": ["jpegPhoto","givenName", "sn", "brBirthdate", "brPromo","telephoneNumber","mail","brRoom","brIP","brMemberOf"] + "key_id": "uid", + "user": { + "direct_input": ["givenName","lastName","birthdate", "promotion", "mail","phone","photo","adress"], + "multiple_input": ["ips","forlifes"], + "profil": ["jpegPhoto","givenName", "sn", "brBirthdate", "brPromo","telephoneNumber","mail","brRoom","brIP","brMemberOf"], + "photo": "jpegPhoto", + "givenName": "givenName", + "lastName": "sn", + "nickname": "displayName", + "nationality": "country", + "promotion": "brPromo", + "phone": "telephoneNumber", + "mail": "mail", + "adress": "brRoom", + "ips": "brIP", + "school": "brMemberOf", + "groups": "brMemberOf", + "studies": "brMemberOf", + "birthdate": "brBirthdate", + "sport": "brMemberOf", + "id": "uidNumber", + "password": "userPassword", + "forlifes": "brAlias", + "idNum": "gidNumber", + "directory": "homeDirectory", + "login": "loginShell", + "readPerm": "brNewsReadAccess", + "writePerm": "brNewsPostAccess", + "fullName": "cn", + "cleanFullName": "gecos", + "class": "objectClass" }, - - "cu": { - "COMMENT7": "Le détail des calculs des différents champs est ci-dessous", - "single_user_infos": ["uid","givenName","sn","displayName", "brBirthdate", "uidNumber","gidNumber", "homeDirectory", "userPassword","brPromo","brMemberOf","loginShell","email","telephoneNumber","jpegPhoto","brRoom","brNewsReadAccess","brNewsPostAccess","brAlias","brIP","cn","gecos"], - "expr_single_values_user": { - "uid": "data['hruid']", - "givenName": "data['givenName']", - "sn": "data['lastName']", - "displayName": "data['nickname']", - "brBirthdate": "data['birthdate']", - "uidNumber": "(2*data['uid']+10000).toString()", - "gidNumber": "5000.toString()", - "homeDirectory": "'/hosting/users/' + data['hruid']", - "userPassword": "data['password']", - "brPromo": "data['promo']", - "loginShell": "if (data['onPlatal']==1) {'/bin/bash' } else { '/sbin/nologin' }", - "email": "data['email']", - "telephoneNumber":"data['phone']", - "jpegPhoto": "data['photo']", - "brRoom": "data['room']", - "brNewsReadAccess": "if (data['readPerm'].length>0) { 'br.*,public.*' } else { 'br.*,public.*,'+data['read_perm'] }", - "brNewsPostAccess": "if (data['writePerm'].length>0) { 'br.*,!br.blague-du-jour,public.*,!br.campagnekes' } else { 'br.*,!br.blague-du-jour,public.*,!br.campagnekes,'+data['read_perm'] }", - "brIP": "data['ips'].split(',')", - "cn": "data['givenName']+' '+data['sn'].toUpperCase()", - "gecos": "btoa(data['givenName']+' '+data['sn'].toUpperCase())" - }, - "multiple_user_infos": ["objectClass","brIP","brAlias"], - "expr_multiple_values_user": { - "objectClass": "['posixAccount', 'shadowAccount', 'inetOrgPerson', 'brAccount']", - "brIP": "data['ips']", - "brAlias": "data['forlifes']" - } - }, - - "cg": { - "COMMENT10": "Détail des champs d'un groupe", - "single_user_infos": ["name","ns","gid","label"], - "expr_single_values_user": { - "uid": "btoa(data['name'].toLowerCase())", - "brAlias": "data['name']", - "brNS": "data['ns']", - "uidNumber": "(2*data['gid']+10001).toString()", - "gidNumber": "(2*data['gid']+10001).toString()", - "userPassword": "", - "loginShell": "/sbin/nologin", - "cn": "if (data['label']!= '') { data['label'] } else { data['name'] }", - "homeDirectory": "'/hosting/groups/'+btoa(data['name'].toLowerCase())", - "gecos": "btoa(data['name'].toLowerCase())", - "brNewsReadAccess": "!*", - "brNewsPostAccess": "!*" - }, - "multiple_user_infos": ["objectClass"], - "expr_multiple_values_user": { - "objectClass": "[ 'posixGroup','posixAccount','brAccount' ]" - } + "group": { + "direct_input": ["name","ns"], + "name": "cn", + "member": "restrictedMemberUid", + "admin": "memberUid", + "type": "brNS", + "idNumber": "uidNumber", + "idNumber2": "gidNumber", + "password": "userPassword", + "login": "loginShell", + "directory": "homeDirectory", + "cleanFullName": "gecos", + "readPerm": "brNewsReadAccess", + "writePerm": "brNewsPostAccess" }, "am": { "key_gr": "restrictedMemberUid", @@ -108,5 +67,8 @@ "sm": { "attributs": ["restrictedMemberUid", "brMemberOf"] }, + "sa": { + "attributs": "memberUid" + }, "sessionSecret":"change this" } \ No newline at end of file diff --git a/src/ldap/ldap_data.js b/src/ldap/ldap_data.js index 16e8867..dd10885 100644 --- a/src/ldap/ldap_data.js +++ b/src/ldap/ldap_data.js @@ -1,5 +1,5 @@ /** - * @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 paxs et champs spécifique à un LDAP. Ce fichier permet en outre de changer facilement les paramètres d'une fonction et de mieux comprendre les formats attendus en entrée. Il est fortement conseillé d'utiliser [`JXplorer`](http://jxplorer.org/) pour naviguer sur le LDAP en parallèle du dévelopement. Toutes les fonctions écrites ici sont asynchrones et renvoient des Promises ce qui nécessite de les appeler avec la synthaxe un peu particulière `f(args).then(res => ...)` pour exploiter leur résultat. + * @file Ce fichier 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 [`ldap_config.json`](..\..\config.json) qui détaille les champs spécifiques à chaque LDAP, anonymisés ici. Il est fortement conseillé d'utiliser [`JXplorer`](http://jxplorer.org/) pour naviguer sur le LDAP en parallèle du dévelopement. Toutes les fonctions écrites ici sont asynchrones et renvoient des Promises ce qui nécessite de les appeler avec la synthaxe un peu particulière `f(args).then(res => ...)` pour exploiter leur résultat. * @author hawkspar */ @@ -39,10 +39,7 @@ var client = ldap.createClient({ url: config.ldap.server}); * @arg {string} user[uid] - User identifier * @arg {string} user[password] - Mot de passe */ -function connecterLDAP(user) { - // TBM utiliser user - client.bind(connect.dn, connect.passwd, (err, res) => {}); -} +function connecterLDAP(user) { client.bind(connect.dn, connect.passwd, (err, res) => {}); } // TBM utiliser user /** * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit les valeurs trouvées. @@ -56,12 +53,8 @@ function connecterLDAP(user) { * soit une liste de dictionnaires si on veut plus d'un attribut (les clés du dictionnaire sont celles du LDAP) */ function rechercherLDAP(user, base, attributes, filter="(objectClass=*)") { - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { connecterLDAP(user); - //Debug - /*console.log(base); - console.log(filter); - console.log(attributes);*/ let vals=[]; // Interrogation LDAP selon configuration fournie en argument @@ -97,6 +90,7 @@ function rechercherLDAP(user, base, attributes, filter="(objectClass=*)") { }); } +//TBT /** * @summary Fonction qui permet de modifier un élément sur le LDAP. Gestion intelligente de l'appartenance à un binet. * @desc Cette fonction appelle {@link bind} pour authentifier l'utilisateur pour authentifier l'utilisateur puis rompts la connexion. Cette fonction utilise une Promise pour être asynchrone ; elle renvoit la promesse d'une réponse puis traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode modify). 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). @@ -107,13 +101,13 @@ function rechercherLDAP(user, base, attributes, filter="(objectClass=*)") { * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon */ function modifierLDAP(user, name, op, mod) { - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { connecterLDAP(user); // Modification LDAP selon configuration en argument (pourrait prendre une liste de Changes) client.modify(name, new ldap.Change({ operation: op, modification: mod, - }), function(err) { + }), err => { reject(err); }); client.bind("", "", (err, res) => {}); @@ -121,6 +115,7 @@ function modifierLDAP(user, name, op, mod) { }); } +//TBT /** * @summary Fonction qui permet de rajouter un élément sur le LDAP. * @desc Cette fonction appelle {@link connecterLDAP} pour authentifier l'utilisateur puis rompts la connexion. Cette fonction utilise une Promise pour être asynchrone ; elle renvoit la promesse d'une réponse puis traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode add). Il faut l'appeler suivant un schéma `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). @@ -131,10 +126,10 @@ function modifierLDAP(user, name, op, mod) { * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon */ function ajouterLDAP(user, dn, vals) { - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { connecterLDAP(user); // Ajout LDAP selon la configuration en argument - client.add(config.key_id+"="+vals["uid"]+","+dn, vals, function(err) { + client.add(config.key_id+"="+vals[config.key_id]+","+dn, vals, function(err) { reject(err); }); client.bind("", "", (err, res) => {}); @@ -148,14 +143,14 @@ function ajouterLDAP(user, dn, vals) { /** * @summary Fonction qui retrouve les groupes dont un individu est membre. - * @desc Cette fonction utilise {@link rechercherLDAP} avec un dictionnaire prédéfini dans config.json, entrée lg. Elle va directement à la bonne feuille et n'a donc pas de filtre. Elle utilise ldapEscape pour éviter les injections. + * @desc Cette fonction utilise {@link rechercherLDAP} ; elle va directement à la feuille de l'utilisateur et n'a donc pas de filtre. Elle utilise ldapEscape pour éviter les injections. * @arg {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP} * @arg {string} uid - Identifiant de l'individu à interroger (le plus souvent prenom.nom, parfois l'année, supposé valide) * @return {Promise(string[])} Liste des uid de groupes (noms flat des groupes) où l'id fourni est membre */ function listerGroupes(user, uid) { - return new Promise(function(resolve, reject) { - rechercherLDAP(user,ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id : uid}), config.lg.attributs).then(res => resolve(res[0])); + return new Promise((resolve, reject) => { + rechercherLDAP(user,ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id : uid}), config.user.groups).then(res => resolve(res[0])); }); } @@ -167,8 +162,8 @@ function listerGroupes(user, uid) { * @return {Promise(string[])} Liste des uid des membres où l'id fournie est membre (noms flat des groupes) */ function listerMembres(user, gid) { - return new Promise(function(resolve, reject) { - rechercherLDAP(user,ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id : gid}), config.lm.attributs).then(res => resolve(res[0])); + return new Promise((resolve, reject) => { + rechercherLDAP(user,ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id : gid}), config.group.member).then(res => resolve(res[0])); }); } @@ -180,21 +175,22 @@ function listerMembres(user, gid) { * @return {Promise(string[])} Liste des uid des membres où l'id fournie est membre (noms flat des groupes) */ function listerAdministrateurs(user, gid) { - return new Promise(function(resolve, reject) { - rechercherLDAP(user,ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id : gid}), config.la.attributs).then(res => resolve(res[0])); + return new Promise((resolve, reject) => { + rechercherLDAP(user,ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id : gid}), config.group.admin).then(res => resolve(res[0])); }); } /** * @summary Fonction qui renvoit toutes les infos relatives à un hruid particulier. - * @desc Cette fonction utilise {@link rechercherLDAP} avec un dictionnaire prédéfini dans config.json. Elle utilise LDAPEscape pour éviter les injections. + * @desc Cette fonction utilise {@link rechercherLDAP} avec des attributs prédéfinis. Elle utilise LDAPEscape pour éviter les injections. * @arg {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP} * @arg {string} uid - Identifiant de l'utilisateur - * @return {Promise(Object[])} Informations recueillies ; renvoie une liste de dictionnaire avec les mêmes clés que dans le TOL, voir config.json. + * @return {Promise(Object[])} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet de l'utilisateur ; voir `ldap_config.json`(..\..\ldap_config.json) pour les clés exactes. */ function renseignerSurUtilisateur(user, uid) { - return new Promise(function(resolve, reject) { - rechercherLDAP(user,ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id : uid}), config.rs.attributs).then(res => resolve(res)); + return new Promise((resolve, reject) => { + let attributes =[]; + rechercherLDAP(user,ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id : uid}), config.user.profil).then(res => resolve(res)); }); } @@ -204,13 +200,13 @@ function renseignerSurUtilisateur(user, uid) { /** * @summary Fonction qui interroge le LDAP et retrouve les groupes (voir LDAP) qui ressemblent à l'input. Etape 0 vers un vrai TOL (Trombino On Line). - * @desc Cette fonction utilise {@link rechercherLDAP} mais avec un filtre généré à la volée. Accepte des champs exacts ou incomplets mais pas approximatifs et ne gère pas l'auto-complete. Cette fonction utilise aussi config.json. MEF Timeout pour des recherches trop vagues. Renvoit une liste d'uid. Elle utilise LDAPEscape pour éviter les injections. + * @desc Cette fonction utilise {@link rechercherLDAP} 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 [`ldap_config.json`](..\..\ldap_config.json). MEF Timeout pour des recherches trop vagues. Renvoit une liste d'uid. Elle utilise LDAPEscape pour éviter les injections. * @arg {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP} * @arg {string} input - String entré par l'utilisateur qui ressemble au nom du groupe. * @return {Promise(string[])} Liste des uid de groupes dont le nom ressemble à l'input */ function trouverGroupes(user, input) { - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { // Escape de l'input utilisateur par sécurité let str=ldapEscape.filter("${txt}", { txt: input}); @@ -225,7 +221,7 @@ function trouverGroupes(user, input) { }); } -// ASKIP elle crash +// TBT /** * @summary Fonction qui interroge le LDAP et retrouve le groupe qui ressemblent à l'input et qui correspond. Etape 0 vers un vrai TOL (Trombino On Line). * @desc Cette fonction utilise {@link rechercherLDAP} mais avec un filtre généré à la volée. Accepte des champs exacts ou incomplets mais pas approximatifs et ne gère pas l'auto-complete. Cette fonction utilise aussi config.json. MEF Timeout pour des recherches trop vagues. Renvoit une liste d'uid. Elle utilise LDAPEscape pour éviter les injections. @@ -235,13 +231,16 @@ function trouverGroupes(user, input) { * @return {Promise(string[])} Liste des uid de groupes dont le nom ressemble à l'input */ function trouverGroupesParTypes(user, input, type) { - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { // Appels imbriqués trouverGroupes(user, input).then(gidList => { let gidtyList = []; gidList.forEach(gid => { - // Nouvelle recherche à partir des résultats de la première - rechercherLDAP(ldapEscape.filter(config.key_id+"=${id},"+config.dn_groups, {id : gid} ), config.key_id, ldapEscape.filter(config.tgty.filter, { ty: type })).then(res => { + // Nouvelle recherche à partir des résultats de la première ; pour chaque groupe qui correspond on vérifie que son type est celui attendu + let base = ldapEscape.filter(config.key_id+"=${id},"+config.dn_groups, {id : gid} ); + let attribute = config.key_id; + let filter = ldapEscape.filter("("+config.groups.type+"=${ty})", { ty: type }); + rechercherLDAP(base, attribute, filter).then(res => { if (res.length!=0) { gidtyList.push(res); } }); }); @@ -254,7 +253,7 @@ function trouverGroupesParTypes(user, input, type) { * @summary Fonction qui renvoit les attributs demandés des paxs validant les critères de recherche. Première étape vers vrai TOL (Trombino On Line). * @desc Cette fonction utilise {@link rechercherLDAP} mais avec un filtre généré à la volée. Accepte des champs exacts ou incomplets pour la plupart des champs mais pas approximatifs et ne gère pas l'auto-complete. MEF Timeout pour des recherches trop vagues. Elle utilise LDAPEscape pour éviter les injections. * @arg {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP} - * @arg {Object} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse 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. + * @arg {Object} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse 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. * @arg {string} data[givenName] - Prénom * @arg {string} data[lastName] - Nom * @arg {string} data[nickname] - Surnom @@ -262,7 +261,7 @@ function trouverGroupesParTypes(user, input, type) { * @arg {string} data[promotion] - String de l'année de promo * @arg {string} data[phone] - String du numéro de portable * @arg {string} data[mail] - Adresse mail - * @arg {string} data[ip] - Une ou des adresses ip + * @arg {string} data[ips] - Une ou des adresses ip * @arg {string} data[school] - Ecole d'appartenance (pour l'instant instable) * @arg {string} data[groups] - Un ou plusieurs groupes (pas de différence entre membre simple et admin) * @arg {string} data[studies] - PA ou autre @@ -271,18 +270,18 @@ function trouverGroupesParTypes(user, input, type) { * @return {Promise(Object[])} Liste de dictionnaires de profils en cohérence avec l'input avec pour clés les attributs des profils. */ function repliquerTOLModulable(user, data, return_values) { - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { let filter=""; - // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfini dans config.json encore - config.tolm.input_names.forEach(name => { + // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfini dans config encore + config.user.keys().forEach(name => { if ((data[name]!= undefined) & (data[name] != '')) { // Si il y a qque chose à chercher pour ce filtre - if (!Array.isArray(data[name])) { data[name]=[data[name]]; } // Gestion d'une liste de valeurs à rechercher + if (!Array.isArray(data[name])) { data[name]=[data[name]]; } // Gestion d'une liste de valeurs à rechercher // Iteration pour chaque valeur fournie par l'utilisateur data[name].forEach(val => { // Escape de l'input utilisateur let str=ldapEscape.filter("${input}", { input: val}); // Traduction en language LDAP - let attribute = config.tolm.correspondance[name]; + let attribute = config.user[name]; // Creation du filtre étape par étape filter="(&"+filter+ "(|("+attribute+"="+str+")"+ // On cherche la valeur exacte "(|("+attribute+"=*"+str+")"+ // La valeur finale avec des trucs avant ; wildcard * (MEF la wildcart ne marche pas pour tous les attributs) @@ -304,7 +303,7 @@ function repliquerTOLModulable(user, data, return_values) { * @return {Promise(string[])} uids des profils qui "match" les critères proposés. */ function repliquerTOLdesIds(user, data) { - return new Promise( function(resolve, reject) { + return new Promise((resolve, reject) => { repliquerTOLModulable(user, data, config.key_id).then(res => resolve(res)); }); } @@ -317,62 +316,194 @@ function repliquerTOLdesIds(user, data) { * @return {Promise(Object[])} Liste de dictionnaires de profils en cohérence avec l'input avec pour clés tous les attributs disponibles ou presque (voir config). */ function repliquerTOL(user, data) { - return new Promise( function(resolve, reject) { - repliquerTOLModulable(user, data, config.tol.attributes).then(res => resolve(res)); + return new Promise((resolve, reject) => { + repliquerTOLModulable(user, data, config.user.profil).then(res => resolve(res)); }); } //------------------------------------------------------------------------------------------------------------------------ -// Fonctions de création +// Fonctions intermédiaires TBT +//------------------------------------------------------------------------------------------------------------------------ + +/** + * @summary Cette fonction teste une valeur d'un attribut (typiquement un identifiant) et le fait évoluer jusqu'à ce qu'il soit unique. + * @desc Librement adapté de Stack Overflow. Appelle {@link rechercherLDAP} pour vérifier qu'il n'y a pas d'autres occurences de cette valeur pour cette attribut dans le dn fourni. + * @param {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP}. + * @param {string} valeur - Valeur de l'attribut (le plus souvent un identifiant) à tester à cette itération + * @param {string} attribut - Attribut à tester + * @param {string} dn - *Domain Name* dans lequel l'attribut doit être unique + * @param {function} evoluerValeur - Fonction qui prend uniquement en argument l'id courant et le nombre d'itérations et qui renvoit la prochaine valeur de l'attribut + * @param {int} n [0] - Nombre d'itérations (à initialiser à 0) + * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié + */ +function assurerUnicite(user, valeur, attribut, dn, evoluerValeur, n=0) { + return new Promise((resolve, reject) => { + // Recherche d'autres occurences de l'id + rechercherLDAP(user, dn, config.key_id, "("+attribut+"="+valeur+")").then(matches => { + // On renvoit la valeur si elle est bien unique + if (matches.length==0) { resolve(valeur); } + // Sinon, on tente de nouveau notre chance avec la valeur suivante + else { resolve(assurerUnicite(user, evoluerValeur(valeur, n+1), dn, evoluerValeur, n+1)); } + }); + }); +} + +/** + * @summary Cette fonction génère un uid standard, puis le fait évoluer jusqu'à ce qu'il soit unique. + * @desc Limité à un appel à {@link assurerUnicite} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). + * @param {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP}. + * @param {string} givenName - Prénom + * @param {string} lastName - Nom + * @param {string} promotion - Année de promotion + * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié + */ +function genererUid(user, givenName, lastName, promotion) { + // Le filtrage évite l'injection de code dans le LDAP, le normalize et lowerCase standardisent le format + return new Promise((resolve, reject) => { + assurerUnicite(user, ldapEscape.filter("${uid}",{uid: givenName+'.'+lastName}).toLowerCase().normalize('UFD'), config.key_id, config.dn_users, (id,n) => { + if (n==1) { id+='.'+ldapEscape.filter("${pr}",{pr: promotion}); } // Si prénom.nom existe déjà , on rajoute la promo + else if (n==2) { id+='.'+n-1; } // Puis si prénom.nom.promo existe déjà on passe à nom.prenom.promo .1 + else if (n>2) { id+=+n; } // Ensuite on continue .23, .234, etc... + return id; + }).then(id => resolve(id)); + }); +} + +/** + * @summary Cette fonction génère un gid standard, puis le fait évoluer jusqu'à ce qu'il soit unique. + * @desc Limité à un appel à {@link assurerUnicite} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). + * @param {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP}. + * @param {string} name - Nom + * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié + */ +function genererGid(user, name) { + // Le filtrage évite l'injection de code dans le LDAP, le normalize et lowerCase standardisent le format + return new Promise((resolve, reject) => { + assurerUnicite(user, ldapEscape.filter("${id}",{id: name}).toLowerCase().normalize('UFD'), config.key_id, config.dn_groups, (id,n) => { + if (n==1) { id+='.'+n; } // Si nom existe déjà , on essaie nom.1 + else if (n>1) { id+=+n; } // Ensuite on continue .12, .123, etc... + return id; + }).then(id => resolve(id)); + }); +} + +/** + * @summary Cette fonction teste une valeur dummy (0) pour un identifiant numérique puis le fait évoluer aléatoirement (entre 1 et 100 000) jusqu'à ce qu'il soit unique. + * @desc Limité à un appel à {@link assurerUnicite} avec les bons paramètres. + * @param {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP}. + * @param {string} attribut - Intitulé exact de l'id concerné + * @param {string} dn - *Domain Name* dans lequel l'attribut doit être unique + * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié + */ +function genererIdNum(user, attribut, dn) { + return new Promise((resolve,reject) => { assurerUnicite(user, "0", attribut, dn, (id,n) => { Math.floor((Math.random() * 100000) + 1).toString(); }).then(id => resolve(id)); }); +} + +//------------------------------------------------------------------------------------------------------------------------ +// Fonctions de création TBT //------------------------------------------------------------------------------------------------------------------------ /** * @summary Fonction qui créé un nouvel utilisateur dans le LDAP. - * @desc Cette fonction fait une utilisation massive d'eval pour anonymiser son code ; voir ldap_config.json pour le détail de la génération des champs finaux. Appelle {@link ajouterLDAP} bien sûr, mais aussi {@link ajouterMembreGroupe} et {@link ajouterAdministrateurGroupe} pour gérer les groupes du nouvel utilisateur. - * @arg {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP} - * @arg {Object} data - Dictionnaire des informations utilisateurs + * @desc Appelle {@link ajouterLDAP} bien sûr, mais aussi {@link ajouterMembreGroupe} et {@link ajouterAdministrateurGroupe} pour gérer les groupes du nouvel utilisateur. + * @arg {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP}. + * @arg {Object} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis. * @arg {string} data[hruid] - prenom.nom ou nom.promo * @arg {string} data[givenName] - Prénom * @arg {string} data[lastName] - Nom * @arg {string} data[nickname] [] - Surnom * @arg {string} data[birthdate] - Date d'anniversaire au format annee-mois-jour - * @arg {string} data[uid] - Numéro d'identifiant plus ou moins arbitraire * @arg {string} data[password] - Mot de passe - * @arg {string} data[promo] - Simple année d'entrée école (pas d'X17) - * @arg {string} data[onPlatal] - "String booléen" valant 0 ou 1 - * @arg {string} data[email] - Courriel supposé valide + * @arg {string} data[promotion] - Simple année d'entrée école (pas d'X17) + * @arg {string} data[mail] - Courriel supposé valide * @arg {string} data[phone] - String du numéro de portable * @arg {string} data[photo] - Photo jpeg directement en bytestring * @arg {string} data[room] - Pour l'instant juste le numéro de casert ou rien, à terme l'adresse complète - * @arg {string} data[readPerm] [] - Permission spéciales BR sous la forme *truc, - * @arg {string} data[writePerm] [] - Permission spéciales BR sous la forme *truc, + * @arg {string} data[readPerm] [] - Permissions spéciales BR sous la forme "*truc," + * @arg {string} data[writePerm] [] - Permissions spéciales BR sous la forme "*truc," * @arg {string[]} data[forlifes] [] - Liste d'alias spéciaux BR (attention le filtre .fkz n'est plus fonctionnel) * @arg {string[]} data[ips] - Liste des ips connus de la personne - * @arg {string[]} data[groupsIsMember] [] - Liste des gid dont le pax est membre + * @arg {string[]} data[groups] [] - Liste des gid dont le pax est membre (doit contenir "on_platal" si il y a lieu) * @arg {string[]} data[groupsIsAdmin] [] - Liste des gid dont le pax est admin ; supposé sous-liste du précédent * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon */ function creerUtilisateur(user, data) { - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { // Calcul d'un dictionnaire d'ajout let vals = {}; - // EVAL DIRECTE D'UN STRING UTILISATEUR ! FAILLE DE SECURITE MAJEURE ! - config.cu.single_user_infos.forEach(key_att => vals[key_att]=eval(config.cu.expr_single_values_user[key_att])); + + // uid de base généré à partir de nom et prénom, plus potentiellement promo et un offset + // MEF mélange de Promise et de fonction standard + genererUid(user, data['givenName'],data['lastName'],data['promotion']).then(id => { vals[config.key_id]=id; } ); + + // Ecriture de toutes les valeurs directement inscrites dans le LDAP (in pour input) + // Génère une erreur si un champ n'est pas rempli + config.user.direct_input.forEach(key_att => vals[config.user[key_att]]=data[key_att]); + // Appel à la fonction de base - ajouterLDAP(user, ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, { id: data["uid"]}), vals).then( res => { - // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples - config.cu.multiple_user_infos.forEach(key_att => { - // Evaluation des expressions des liste de valeurs des attributs - // EVAL DIRECTE D'UN STRING UTILISATEUR ! FAILLE DE SECURITE MAJEURE ! - eval(config.cu.expr_multiple_values_user[key_att]).forEach(val => { - // Nouvel ajout attribut par attribut pour démultiplier les champs - modifierLDAP(user, ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, { id: data["uid"]}), "add", { key_att: val }); + ajouterLDAP(user, config.key_id+"="+vals[config.key_id]+","+config.dn_users, vals).then( res => { + // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples (d'où mul) + config.user.muliple_input.forEach(key_att => { + // On rajoute chaque valeur en entrée + data[key_att].forEach(val => { + let vals2 = {}; + vals2[config.user[key_att]]=val; + modifierLDAP(user, config.key_id+"="+vals[config.key_id]+","+config.dn_users, "add", vals2).then(res => { + if (!res) { reject(false); } + }); }); }); - // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble - data['groupsIsMember'].forEach(gid => { ajouterMembreGroupe(user, data['uid'], gid); }); - data['groupsIsAdmin'].forEach(gid => { ajouterAdministrateurGroupe(user, data['uid'], gid); + + // Certains champs nécessitent de petits calculs + let vals3={}; + + // Création d'un nom complet lisible + vals3[config.user['fullName']]=data['givenName']+' '+data['lastName'].toUpperCase(); + // ?! + vals3[config.user['password']] = '{CRYPT}' + data['password']; + // Ecriture d'un surnom s'il y a lieu + if ((data['nickname']!=undefined)&(data['nickname']!='')) { + vals3[config.user['nickname']]=data['nickname']; + } + // Génération id aléatoire unique + genererIdNum(user, config.user['id'], config.dn_users).then(id => { vals3[config.user['id']]=id; }); + + // Stockage machine ; dépend du prénom + vals3[config.user['directory']] = '/hosting/users/' + data['givenName'][0]; + + // Code root + vals3[config.user['cleanFullName']]=data['fullName'].replace(':', ';').toLowerCase().normalize('UFD'); + + // Adressage root + if (data['groups'].includes("on_platal")) { vals3[config.user['login']] = "/bin/bash"; } + else { vals3[config.user['login']] = "/sbin/nologin"; } + + // Permissions BR + vals3[config.user['readPerm']] = 'br.*,public.*'; + if (data['readPerm'].length>0) { vals3[config.user['readPerm']] += ',' + data['readPerm']; } + vals3[config.user['writePerm']] = 'br.*,!br.blague-du-jour,public.*,!br.campagnekes'; + if (data['writePerm'].length>0) { vals3[config.user['readPerm']] += ',' + data['writePerm']; } + + // Valeur nécessaire mais inutile + vals3[config.user['idNum']] ='5000'; + + // Inscription des valeurs calculées + modifierLDAP(user, config.key_id+"="+vals[config.user['hruid']]+","+config.dn_users, "add", vals3).then(res => { + if (!res) { reject(false); } + }); + + ["posixAccount", "shadowAccount", "inetOrgPerson", "brAccount"].forEach(cst => { + let val3={}; + vals3[config.user['class']]=cst; + modifierLDAP(user, config.key_id+"="+vals[config.user['hruid']]+","+config.dn_users, "add", vals3).then(res => { + if (!res) { reject(false); } + }); }); + + // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble + data['groupsIsMember'].forEach(gid => { ajouterMembreGroupe(user, vals[config.key_id], gid); }); + data['groupsIsAdmin'].forEach(gid => { ajouterAdministrateurGroupe(user, vals[config.key_id], gid); }); + resolve(true); }); reject(false); @@ -381,37 +512,72 @@ function creerUtilisateur(user, data) { /** * @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 ajouterLDAP} et {@link modifierLDAP}, mais aussi {@link ajouterMembreGroupe} et {@link ajouterAdministrateurGroupe} pour gérer les groupes du nouvel utilisateur. + * @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 ajouterLDAP} et {@link modifierLDAP}, mais aussi {@link ajouterMembreGroupe} et {@link ajouterAdministrateurGroupe} pour gérer les groupes du nouvel utilisateur. Attention une manip FOIREUSE est cachée dedans. * @arg {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP} * @arg {Object} data - Dictionnaire des informations utilisateurs (voir détail des champs dans config.json) * @arg {string} data[name] - Nom du groupe * @arg {string} data[ns] - Statut du groupe ; 'binet' ou 'free', cà d ouvert à tous - * @arg {string} data[gid] - Numéro d'identifiant du groupe - * @arg {string} data[label] [] - Sorte de surnom ; remplacé par le nom si égal à '' * @arg {string[]} data[members] - Liste des membres du groupe * @arg {string[]} data[admins] - Liste des admins du groupe ; supposée être une sous-liste de la précédente * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon */ function creerGroupe(user, data) { - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { // Calcul d'un dictionnaire d'ajout let vals = {}; - // EVAL DIRECTE D'UN STRING UTILISATEUR ! FAILLE DE SECURITE MAJEURE ! - config.cru.single_user_infos.forEach(key_att => vals[key_att]=eval(config.cru.expr_single_values_user[key_att])); + + // uid de base généré à partir du nom standardisé + genererGid(user, data['name']).then(id => { vals[config.group['name']]=id; }); + + // Ecriture de toutes les valeurs directement inscrites dans le LDAP (in pour input) + config.group.direct_input.forEach(key_att => vals[config.group[key_att]]=data[key_att]); + // Appel à la fonction de base - ajouterLDAP(user, ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id: data['uid']}), vals).then( res => { - // Modifications multiples pour avoir plusieurs champs de même type - config.cru.multiple_user_infos.forEach(key_att => { - // EVAL DIRECTE D'UN STRING UTILISATEUR ! FAILLE DE SECURITE MAJEURE ! - eval(config.cru.expr_multiple_values_user[key_att]).forEach(key_val => { - // Nouveau dictionnaire d'ajout - let vals2 = { key_att: key_val }; - modifierLDAP(user, ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id: data['uid']}), "add", vals2); + ajouterLDAP(user, config.key_id+"="+vals[config.group['name']]+","+config.dn_groups, vals).then( res => { + // Certains champs nécessitent de petits calculs + let vals2={}; + + // Sauvegarde du nom (pour le cas où gid != data['name']) + vals2[config.group["name"]]=data['name']; + + // ?! + vals2[config.user['password']] = ''; + + // Génération id aléatoire et test contre le LDAP + genererIdNum(user, config.groups["idNumber"], config.dn_groups).then(id => { vals2[config.group['idNumber']]=id; }); + // FOIREUX : Hypothèse sur la structure du reste des données mais évite un assurerUnicite à deux variables + vals2[config.group['idNumber2']]=vals2[config.group['idNumber']]; + + // Stockage machine ; dépend du prénom + vals2[config.group['directory']] = '/hosting/groups/'+vals[config.key_id]; + + // Code root + vals2[config.group['cleanFullName']]=data['name'].replace(':', ';').toLowerCase().normalize('UFD'); + + // Adressage root + vals2[config.group['login']] = "/sbin/nologin"; + + // Permissions BR + vals2[config.group['readPerm']] = '!*'; + vals2[config.group['writePerm']] = '!*'; + + // Inscription des valeurs calculées + modifierLDAP(user, config.key_id+"="+vals[config.key_id]+","+config.dn_groups, "add", vals2).then(res => { + if (!res) { reject(false); } + }); + + ["posixAccount", "posixGroup", "brAccount"].forEach(cst => { + let vals3={}; + vals3[config.group['class']]=cst; + modifierLDAP(user, config.key_id+"="+vals[config.key_id]+","+config.dn_groups, "add", vals3).then(res => { + if (!res) { reject(false); } }); }); + // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble - data["members"].forEach(gid => { ajouterMembreGroupe(user, data['uid'], gid); }); - data["admins"].forEach(gid => { ajouterAdministrateurGroupe(user, data['uid'], gid); }); + data['members'].forEach(uid => { ajouterMembreGroupe(user, uid, vals[config.key_att]); }); + data['admins'].forEach(uid => { ajouterAdministrateurGroupe(user, uid, vals[config.key_att]); }); + resolve(true); }); reject(false); @@ -419,7 +585,7 @@ function creerGroupe(user, data) { } //------------------------------------------------------------------------------------------------------------------------ -// Fonctions d'édition +// Fonctions de relation TBT //------------------------------------------------------------------------------------------------------------------------ /** @@ -431,12 +597,12 @@ function creerGroupe(user, data) { * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon */ function ajouterMembreGroupe(user, uid, gid) { - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { // Vérifie que l'utilisateur est pas déjà membre pour groupes listerMembres(user,gid).then(lm => { if (!lm.includes(uid)) { let vals = {}; - vals[config.am.key_gr] = uid; + vals[config.groups.member] = uid; modifierLDAP(user, ldapEscape(config.key_id+"=${id},"+config.dn_groups, { id: gid }), "add", vals).then(res => { // Erreur si pb lors de la modification if (!res) { reject(false); } @@ -446,7 +612,7 @@ function ajouterMembreGroupe(user, uid, gid) { }).then(res => listerGroupes(user,uid)).then( lg => { if (!lg.includes(gid)) { let vals2 = {}; - vals2[config.am.key_u] = gid; + vals2[config.users.groups] = gid; modifierLDAP(user, ldapEscape(config.key_id+"=${id},"+config.dn_users, { id: uid }), "add", vals2).then(res => { // Erreur si pb lors de la modification if (!res) { reject(false); } @@ -459,37 +625,21 @@ function ajouterMembreGroupe(user, uid, gid) { /** * @summary Fonction qui permet de promouvoir membre au stade d'administrateur d'un groupe. - * @desc Cette fonction fait essentiellement appel à {@link modifierLDAP}. + * @desc Cette fonction fait essentiellement appel à {@link ajouterMembreGroupe} {@link modifierLDAP} et {@link listerAdministrateurs}. Elle n'autorise pas les doublons et opère dans les deux dns users et groups. * @arg {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP} * @arg {string} uid - Identifiant du futur membre * @arg {string} gid - Identifiant du groupe * @return {boolean} `true` si la modification s'est bien déroulée, false sinon */ function ajouterAdministrateurGroupe(user, uid, gid){ - return new Promise(function(resolve, reject) { - // Vérifie que l'utilisateur est pas déjà membre pour groupes - listerMembres(user,gid).then(lm => { - if (!lm.includes(uid)) { - ajouterMembreGroupe(user,uid,gid).then(res => { - // Erreur si pb lors de la modification - if (!res) { reject(false); } - }); - } - // Vérifie que l'utilisateur est pas déjà membre pour users - }).then(res => listerGroupes(user,uid)).then( lg => { - if (!lg.includes(gid)) { - ajouterMembreGroupe(user,uid,gid).then(res => { - // Erreur si pb lors de la modification - if (!res) { reject(false); } - }); - } - // Attends le résultat des autres puis vérifie que pas déjà admin - }).then(res => { + return new Promise((resolve, reject) => { + // ajouter + ajouterMembreGroupe(user,uid,gid).then(res => { listerAdministrateurs(user,gid).includes(uid).then(la => { if (!la.includes(uid)) { // Finalement modification, uniquement dans groups let vals = {}; - vals[config.aa.key_gr] = uid; + vals[config.groups.admin] = uid; modifierLDAP(user, ldapEscape(config.key_id+"=${id},"+config.dn_groups, { id: gid }), "add", vals).then(res => { // Gestion d'erreur if (!res) { reject(false); } @@ -510,12 +660,12 @@ function ajouterAdministrateurGroupe(user, uid, gid){ * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon */ function supprimerMembreGroupe(user, uid, gid) { - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { // Vérifie que l'utilisateur est pas déjà viré pour groupes listerMembres(user,gid).then(lm => { if (lm.includes(uid)) { // Supprime tous les utilisateurs - modifierLDAP(user, ldapEscape(config.key_id+"=${id},"+config.dn_groups, { id: gid }), "del", config.sm.attributs[0]).then(res => { + modifierLDAP(user, ldapEscape(config.key_id+"=${id},"+config.dn_groups, { id: gid }), "del", config.group.member).then(res => { // Les rajoute un par un, sauf pour le supprimé lm.forEach(id => { if (id!=uid) { ajouterMembreGroupe(user,id, gid); } @@ -526,7 +676,7 @@ function supprimerMembreGroupe(user, uid, gid) { // Vérifie que l'utilisateur est pas déjà viré pour users if (lg.includes(gid)) { // Supprime tous les groupes - modifierLDAP(user, ldapEscape(config.key_id+"=${id},"+config.dn_users, { id: uid }), "del", config.sm.attributs[1]).then(res => { + modifierLDAP(user, ldapEscape(config.key_id+"=${id},"+config.dn_users, { id: uid }), "del", config.group.admin).then(res => { // Les rajoute un par un, sauf pour le supprimé lg.forEach(id => { if (id!=gid) { ajouterMembreGroupe(user,uid, id); } @@ -538,7 +688,6 @@ function supprimerMembreGroupe(user, uid, gid) { }); } -//TBC /** * @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 modifierLDAP}. @@ -547,134 +696,187 @@ function supprimerMembreGroupe(user, uid, gid) { * @arg {string} gid - Identifiant du groupe * @return {boolean} `true` si la modification s'est bien déroulée, false sinon */ -/*function supprimerAdministrateurGroupe(user, uid, gid) { - return new Promise(function(resolve, reject) { - // Vérifie que l'utilisateur est pas déjà viré pour groupes - listerMembres(user,gid).then(lm => { - if (lm.includes(uid)) { - // Supprime tous les utilisateurs - modifierLDAP(user, ldapEscape(config.key_id+"=${id},"+config.dn_groups, { id: gid }), "del", config.sm.attributs[0]).then(res => { - // Les rajoute un par un, sauf pour le supprimé - lm.forEach(id => { - if (id!=uid) { ajouterMembreGroupe(user,id, gid); } - }); - }); - } - }).then(res => listerGroupes(user,uid)).then(lg => { - // Vérifie que l'utilisateur est pas déjà viré pour users - if (lg.includes(gid)) { - // Supprime tous les groupes - modifierLDAP(user, ldapEscape(config.key_id+"=${id},"+config.dn_users, { id: uid }), "del", config.sm.attributs[1]).then(res => { +function supprimerAdministrateurGroupe(user, uid, gid) { + return new Promise((resolve, reject) => { + // Peut paraître absurde mais permet de s'assurer que le membre est bien présent et que ses champs sont comme il faut + supprimerMembreGroupe(user, uid, gid).then( res => { ajouterMembreGroupe(user,uid,gid); }); + // Vérifie que l'utilisateur est bien admins (comme dans supprimerMembreGroupe) + listerAdministrateurs(user,gid).then(la => { + if (la.includes(uid)) { + // Supprime tous les administrateurs + modifierLDAP(user, ldapEscape(config.key_id+"=${id},"+config.dn_groups, { id: gid }), "del", config.group.admin).then(res => { // Les rajoute un par un, sauf pour le supprimé - lg.forEach(id => { - if (id!=gid) { ajouterMembreGroupe(user,uid, id); } + la.forEach(id => { + if (id!=uid) { ajouterAdministrateurGroupe(user, id, gid); } }); }); } - resolve(true); - }); - }); - // Vérifie que l'utilisateur est bien membre pour groupes et le cas échéant le rajoute - if (!(listerMembres(user,gid).includes(uid) & listerGroupes(user,uid).includes(gid))) { - ajouterMembreGroupe(user, uid, gid).then(res => { - // Gestion d'erreur - if (!res) { reject(false); } - }); - } - // Vérifie que l'utilisateur n'est pas déjà admin - if (!listerAdministrateurs(user,gid).includes(uid)) { - let vals = {}; - vals[config.am.key_gr] = uid; - modifierLDAP(user, ldapEscape(config.key_id+"=${id},"+config.dn_groups, { id: gid }), "add", vals).then(res => { - // Gestion d'erreur - if (!res) { reject(false); } - }); - resolve(true); - } + }).then(res => resolve(true)); }); -}*/ +} + +//------------------------------------------------------------------------------------------------------------------------ +// Fonctions d'édition TBT +//------------------------------------------------------------------------------------------------------------------------ -//TBC /** * @summary Fonction qui édite un utilisateur existant dans le LDAP. Très similaire à {@link creerUtilisateur} - * @desc Appelle {@link ajouterLDAP} bien sûr, mais aussi {@link ajouterMembreGroupe} et {@link ajouterAdministrateurGroupe} pour gérer les groupes du nouvel utilisateur. + * @desc Appelle {@link modifierLDAP} bien sûr, mais aussi {@link ajouterMembreGroupe} et {@link ajouterAdministrateurGroupe} pour gérer les groupes du nouvel utilisateur. Peut aussi être utilisé * @arg {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP} - * @arg {Object} data - Dictionnaire des informations utilisateurs largement au même format que pour {@link creerUtilisateur} avec tous les champs optionnels - * @arg {string} data[givenName] [] - Prénom - * @arg {string} data[lastName] [] - Nom - * @arg {string} data[nickname] [] - Surnom - * @arg {string} data[birthdate] [] - Date d'anniversaire au format annee-mois-jour - * @arg {string} data[password] [] - Mot de passe - * @arg {string} data[promo] [] - Simple année d'entrée école (pas d'X17) - * @arg {string} data[onPlatal] [] - "String booléen" valant 0 ou 1 - * @arg {string} data[email] [] - Courriel supposé valide - * @arg {string} data[phone] [] - String du numéro de portable - * @arg {string} data[photo] [] - Photo jpeg directement en bytestring - * @arg {string} data[room] [] - Pour l'instant juste le numéro de casert ou rien, à terme l'adresse complète - * @arg {string[]} data[ips] [] - Liste des ips connus de la personne + * @arg {string} uid - Utilisateur à modifier (le plus souvent le même, mais root possible) + * @arg {Object} data - Dictionnaire des informations utilisateurs au même format que pour {@link creerUtilisateur} avec tous les champs optionnels ; attention toutes les clés de cette entrée seront modifiées dans le LDAP * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon */ -function editerUtilisateur(user, data) { - return new Promise(function(resolve, reject) { - // Calcul d'un dictionnaire de modifs +function editerUtilisateur(user, uid, data) { + return new Promise((resolve, reject) => { + let data_keys =data.keys(); + + // Calcul d'un dictionnaire d'ajout let vals = {}; - // EVAL DIRECTE D'UN STRING UTILISATEUR ! FAILLE DE SECURITE MAJEURE ! - config.cu.single_user_infos.forEach(key_att => vals[key_att]=eval(config.cu.expr_single_values_user[key_att])); - // Appel à la fonction de base - ajouterLDAP(user, ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, { id: data["uid"]}), vals).then( res => { - // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples - config.cu.multiple_user_infos.forEach(key_att => { - // Evaluation des expressions des liste de valeurs des attributs - // EVAL DIRECTE D'UN STRING UTILISATEUR ! FAILLE DE SECURITE MAJEURE ! - eval(config.cu.expr_multiple_values_user[key_att]).forEach(val => { - // Nouvel ajout attribut par attribut pour démultiplier les champs - modifierLDAP(user, ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, { id: data["uid"]}), "add", { key_att: val }); + + // Iteration plus souple qu'à la création puisqu'on accepte que certains champs soient vides + data_keys.forEach(key => { + // Attribution directe des champs pour lesquels c'est possible + if (config.user.direct_input.includes(key)) { vals[config.user[key]]=data[key]; } + }); + + // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples (d'où mul) + config.user.muliple_input.forEach(key_att => { + if (data_keys.includes(key_att)) { + // On rajoute chaque valeur en entrée + data[key_att].forEach(val => { + vals[config.user[key_att]]=val; }); + } + }); + + // Manipulation sur noms et prénoms (nécessité de gérer ce qui est à changer et de rechercher les valeurs courantes) + let gnb = data_keys.includes("givenName"); + let lnb = data_keys.includes("lastName"); + if (gnb | lnb) { + // Création de variables locales instantiées selon les cas par data ou rechercheLDAP + let givenName; + let lastName; + let promo; + // A ce niveau dans le code la promo a déjà été changée si elle été fausse + rechercherLDAP(user, config.key_id+"="+uid, config.user['promotion']).then(pr => { + promo=pr; + if (gnb & !lnb) { + rechercherLDAP(user, config.key_id+"="+uid, config.user['lastName']).then(ln => { + givenName = data['giveName']; + lastName = ln; + }); + } + else if (!gnb & lnb) { + rechercherLDAP(user, config.key_id+"="+uid, config.user['givenName']).then(gn => { + givenName = gn; + lastName = data['lastName']; + }); + } + else { + givenName = data['giveName']; + lastName = data['lastName']; + } }); - // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble - data['groupsIsMember'].forEach(gid => { ajouterMembreGroupe(user, data['uid'], gid); }); - data['groupsIsAdmin'].forEach(gid => { ajouterAdministrateurGroupe(user, data['uid'], gid); - }); - resolve(true); + // Régénération de l'uid (FOIREUX) + genererUid(user, givenName, lastName, promo).then(id => { vals[config.key_id]=id; }); + // Nom complet + vals[config.user['fullName']]=givenName+' '+lastName.toUpperCase(); + // Stockage machine ; dépend du prénom + vals[config.user['directory']] = '/hosting/users/' + givenName[0]; + // Code root + vals[config.user['cleanFullName']]=vals[config.user['fullName']].replace(':', ';').toLowerCase().normalize('UFD'); + } + + // ?! + if (data_keys.includes('password')) { vals[config.user['password']] = '{CRYPT}' + data['password']; } + + // Ecriture d'un surnom s'il y a lieu, évaluation paresseuse & + if (data_keys.includes('nickname')&(data['nickname']!=undefined)&(data['nickname']!='')) { + vals[config.user['nickname']]=data['nickname']; + } + + // Adressage root + if (data.keys().includes('groups')){ + if (data['groups'].includes("on_platal")) { vals[config.user['login']] = "/bin/bash"; } + else { vals[config.user['login']] = "/sbin/nologin"; } + } + + // Permissions BR + if (data_keys.includes('readPerm')){ + vals[config.user['readPerm']] = 'br.*,public.*'; + if (data['readPerm'].length>0) { vals[config.user['readPerm']] += ',' + data['readPerm']; } + } + + if (data_keys.includes('writePerm')){ + vals[config.user['writePerm']] = 'br.*,!br.blague-du-jour,public.*,!br.campagnekes'; + if (data['writePerm'].length>0) { vals[config.user['readPerm']] += ',' + data['writePerm']; } + } + + // Inscription des valeurs calculées + modifierLDAP(user, config.key_id+"="+uid+","+config.dn_users, "modify", vals).then(res => { + if (!res) { reject(false); } }); - reject(false); + + // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble + if (data_keys.includes('groupsIsMember')){ + data['groupsIsMember'].forEach(gid => { ajouterMembreGroupe(user, vals[config.key_id], gid); }); + } + if (data_keys.includes('groupsIsAdmin')){ + data['groupsIsAdmin'].forEach(gid => { ajouterAdministrateurGroupe(user, vals[config.key_id], gid); }); + } + + resolve(true); }); } -//TBC /** * @summary Fonction qui édite un groupe existant dans le LDAP. Très similaire à {@link creerGroupe} * @desc Appelle {@link ajouterLDAP} bien sûr, mais aussi {@link ajouterMembreGroupe} et {@link ajouterAdministrateurGroupe} pour gérer les groupes du nouvel utilisateur. * @arg {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP} - * @arg {Object} data - Dictionnaire des informations utilisateurs au même format que pour {@link creerGroupe} avec tous les champs optionnels - * @arg {string} data[name] [] - Nom du groupe - * @arg {string} data[ns] [] - Statut du groupe ; 'binet' ou 'free', cà d ouvert à tous - * @arg {string} data[label] [] - Sorte de surnom ; remplacé par le nom si égal à '' + * @arg {string} gid - Identifiant du groupe à modifier + * @arg {Object} data - Dictionnaire des informations utilisateurs au même format que pour {@link creerGroupe} avec tous les champs optionnels. * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon */ -function editerGroupe(user, data) { - return new Promise(function(resolve, reject) { +function editerGroupe(user, gid, data) { + return new Promise((resolve, reject) => { + let data_keys =data.keys(); + // Calcul d'un dictionnaire d'ajout let vals = {}; - // EVAL DIRECTE D'UN STRING UTILISATEUR ! FAILLE DE SECURITE MAJEURE ! - config.cru.single_user_infos.forEach(key_att => vals[key_att]=eval(config.cru.expr_single_values_user[key_att])); - // Appel à la fonction de base - ajouterLDAP(user, ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id: data['uid']}), vals).then( res => { - // Modifications multiples pour avoir plusieurs champs de même type - config.cru.multiple_user_infos.forEach(key_att => { - // EVAL DIRECTE D'UN STRING UTILISATEUR ! FAILLE DE SECURITE MAJEURE ! - eval(config.cru.expr_multiple_values_user[key_att]).forEach(key_val => { - // Nouveau dictionnaire d'ajout - let vals2 = { key_att: key_val }; - modifierLDAP(user, ldapEscape.filter(config.key_id+"=${id},"+config.dn_users, {id: data['uid']}), "add", vals2); - }); - }); - // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble - data["members"].forEach(gid => { ajouterMembreGroupe(user, data['uid'], gid); }); - data["admins"].forEach(gid => { ajouterAdministrateurGroupe(user, data['uid'], gid); }); - resolve(true); + + // Ecriture de toutes les valeurs directement inscrites dans le LDAP + data_keys.forEach(key_att => vals[config.group[key_att]]=data[key_att]); + + if (data_keys.includes('name')) { + // uid de base généré à partir du nom standardisé + genererGid(user, data['name']).then(id => { vals[config.group['name']]=id; }); + + // Sauvegarde du nom (pour le cas où gid != data['name']) + vals[config.group["name"]]=data['name']; + + // Stockage machine ; dépend du prénom + vals[config.group['directory']] = '/hosting/groups/'+vals[config.key_id]; + + // Code root + vals[config.group['cleanFullName']]=data['name'].replace(':', ';').toLowerCase().normalize('UFD'); + } + + if (data_keys.includes('password')) { + // ?! + vals[config.user['password']] = ''; + } + + // Inscription des valeurs calculées + modifierLDAP(user, config.key_id+"="+vals[config.key_id]+","+config.dn_groups, "modify", vals).then(res => { + if (!res) { reject(false); } }); - reject(false); + + // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble + data['members'].forEach(uid => { ajouterMembreGroupe(user, uid, vals[config.key_att]); }); + data['admins'].forEach(uid => { ajouterAdministrateurGroupe(user, uid, vals[config.key_att]); }); + + resolve(true); }); } @@ -685,7 +887,7 @@ function editerGroupe(user, data) { //TBC /** * @summary Fonction qui supprime un utilisateur du LDAP. - * @desc Cette fonction fait une utilisation massive d'eval pour anonymiser son code ; voir ldap_config.json pour le détail de la génération des champs finaux. Appelle {@link ajouterLDAP} bien sûr, mais aussi {@link ajouterMembreGroupe} et {@link ajouterAdministrateurGroupe} pour gérer les groupes du nouvel utilisateur. + * @desc Cette fonction fait une utilisation massive d'eval pour anonymiser son code ; voir [`ldap_config.json`](..\..\ldap_config.json) pour le détail de la génération des champs finaux. Appelle {@link ajouterLDAP} bien sûr, mais aussi {@link ajouterMembreGroupe} et {@link ajouterAdministrateurGroupe} pour gérer les groupes du nouvel utilisateur. * @arg {Object} user - Utilisateur de la forme nécessaire à {@link connecterLDAP} * @arg {string} uid - uid de la victime * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon @@ -752,18 +954,6 @@ function supprimerGroupe(user, data) { } - -//listerGroupes({},"anatole.romon").then(res => console.log(res)); -//listerMembres({},"bda").then(res => console.log(res)); -//listerAdministrateurs({},"kes").then(res => console.log(res)); -//renseignerSurUtilisateur({},"wilson.jallet").then(res => console.log(res)); -//trouverGroupes({},"ensta").then(res => console.log(res)); -//repliquerTOLdesIds({},{"groups": ["faerix","br"]}).then(res => console.log(res)); -//repliquerTOL({},{"groups": ["faerix","br"]}).then(res => console.log(res)); -//rechercherLDAP({},"ou=groups,dc=frankiz,dc=net","(cn=f*)",["uid"]).then(res => console.log(res)); -//listeAdmins({},"zqfnsflswndf").then(res => console.log(res)); - - //trouverGroupesParTypes({},"fe","binet").then(res => console.log(res)); /* Partage pour le reste du monde ; même remarque synthaxe que pour l'import -- GitLab