From b3cf4860c13b466ea79358d595a05089b32885a2 Mon Sep 17 00:00:00 2001 From: Quentin CHEVALIER <quentin.chevalier@polytechnique.edu> Date: Thu, 5 Jul 2018 23:18:59 +0200 Subject: [PATCH] Passage en synthaxe await --- src/graphql/connectors/selectors.js | 1 - src/ldap/admins.js | 336 +++++++++++++++------------- src/ldap/basics.js | 64 ++++-- src/ldap/users.js | 213 +++++++++--------- src/ldap/utilities.js | 35 +-- 5 files changed, 350 insertions(+), 299 deletions(-) diff --git a/src/graphql/connectors/selectors.js b/src/graphql/connectors/selectors.js index 9cde12e..5e4720d 100644 --- a/src/graphql/connectors/selectors.js +++ b/src/graphql/connectors/selectors.js @@ -3,7 +3,6 @@ * @author akka vodol */ - //Give a user, get the groups linked to that user /** diff --git a/src/ldap/admins.js b/src/ldap/admins.js index e0b9b80..2da5cd4 100644 --- a/src/ldap/admins.js +++ b/src/ldap/admins.js @@ -35,29 +35,30 @@ class Admin extends User { * @arg {string} uid - Identifiant du futur membre * @arg {string} gid - Identifiant du groupe * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async */ - static addGroupMember(uid, gid) { + async addGroupMember(uid, gid) { // Vérifie que l'utilisateur est pas déjà membre pour groupes - return this.getMembers(gid).then(lm => { - if (!lm.includes(uid)) { - let vals = {}; - vals[config.groups.member] = uid; - LDAP.change(config.key_id+gid+config.dn_groups, "add", vals).then(res => { - // Erreur si pb lors de la modification - if (!res) { throw Error; } - }); + let lm = await this.getMembers(gid); + if (!lm.includes(uid)) { + let vals = {}; + vals[config.groups.member] = uid; + // Erreur si pb lors de la modification + if (!await LDAP.change(config.key_id+gid+config.dn_groups, "add", vals)) { + throw "Erreur lors de la modification dans l'arbre des groupes"; } + } // Vérifie que l'utilisateur est pas déjà membre pour users - }).then(res => this.getGroups(uid)).then( lg => { - if (!lg.includes(gid)) { - let vals2 = {}; - vals2[config.users.groups] = gid; - LDAP.change(config.key_id+uid+config.dn_users, "add", vals2).then(res => { - // Erreur si pb lors de la modification - if (!res) { throw Error; } else { return true; } - }); + let lg = await this.getGroups(uid); + if (!lg.includes(gid)) { + let vals2 = {}; + vals2[config.users.groups] = gid; + // Erreur si pb lors de la modification + if (!await LDAP.change(config.key_id+uid+config.dn_users, "add", vals2)) { + throw "Erreur lors de la modification dans l'arbre des utilisateurs"; } - }); + } + return true; } /** @@ -66,31 +67,42 @@ class Admin extends User { * @arg {string} uid - Identifiant de l'ex-membre * @arg {string} gid - Identifiant du groupe * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async */ - static delGroupMember(uid, gid) { + async delGroupMember(uid, gid) { // Vérifie que l'utilisateur est pas déjà viré pour groupes - return this.getMembers(gid).then(lm => { - if (lm.includes(uid)) { - // Supprime tous les utilisateurs - LDAP.change(config.key_id+gid+config.dn_groups, "del", config.group.member).then(res => { - // Les rajoute un par un, sauf pour le supprimé - lm.forEach(id => { - if (id!=uid) { this.addGroupMember(id, gid); } - }); - }); + let lm = await this.getMembers(gid); + if (lm.includes(uid)) { + // Supprime tous les utilisateurs + if (!await LDAP.change(config.key_id+gid+config.dn_groups, "del", config.group.member)) { + throw "Erreur lors de la suppression de tous les membres du groupe."; } - }).then(res => this.getGroups(uid)).then(lg => { - // Vérifie que l'utilisateur est pas déjà viré pour users - if (lg.includes(gid)) { - // Supprime tous les groupes - LDAP.change(config.key_id+uid+config.dn_users, "del", config.member.groups).then(res => { - // Les rajoute un par un, sauf pour le supprimé - lg.forEach(id => { - if (id!=gid) { this.addGroupMember(uid, id).then(res => { if (res) { return true; } else { throw Error; }}); } + // Les rajoute un par un, sauf pour le supprimé + lm.forEach(id => { + if (id!=uid) { + this.addGroupMember(id, gid).then(res => { + if (!res) { throw "Erreur lors du ré-ajout des autres membres"; } }); - }); + } + }); + } + let lg = await this.getGroups(uid); + // Vérifie que l'utilisateur est pas déjà viré pour users + if (lg.includes(gid)) { + // Supprime tous les groupes + if (!await LDAP.change(config.key_id+uid+config.dn_users, "del", config.member.groups)) { + throw "Erreur lors de la suppression de tous les groupes du membre."; } - }); + // Les rajoute un par un, sauf pour le supprimé + lg.forEach(id => { + if (id!=gid) { + this.addGroupMember(uid, id).then(res => { + if (!res) { throw "Erreur lors du ré-ajout des autres groupes"; } + }); + } + }); + } + return true; } /** @@ -100,22 +112,21 @@ class Admin extends User { * @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 + * @async */ - static addGroupAdmin(uid, gid){ + async addGroupAdmin(uid, gid){ // Ajoute le membre au groupe avant d'en faire un admin - return this.addGroupMember(uid,gid).then(res => { - this.getAdmins(gid).then(la => { - if (!la.includes(uid)) { - // Finalement modification, uniquement dans groups - let vals = {}; - vals[config.groups.admin] = uid; - LDAP.change(config.key_id+gid+config.dn_groups, "add", vals).then(res => { - // Gestion d'erreur - if (!res) { throw Error; } else { return true; } - }); - } - }); - }); + if (!await this.addGroupMember(uid,gid)) { throw "Erreur lors de l'ajout du futur admin en tant que membre."; } + let la = await this.getAdmins(gid); + if (!la.includes(uid)) { + // Finalement modification, uniquement dans groups + let vals = {}; + vals[config.groups.admin] = uid; + if (!await LDAP.change(config.key_id+gid+config.dn_groups, "add", vals)) { + throw "Erreur lors de l'ajout de l'admin dans l'arbre des groupes"; + } + } + return true; } /** @@ -124,23 +135,24 @@ class Admin extends User { * @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 + * @async */ - delGroupAdmin(uid, gid) { + async delGroupAdmin(uid, gid) { // Peut paraître absurde mais permet de s'assurer que le membre est bien présent et que ses champs sont comme il faut - return this.delGroupMember(uid, gid).then( res => { this.addGroupMember(uid,gid).then( res => - // Vérifie que l'utilisateur est bien admin (comme dans delGroupMember) - this.getAdmins(gid).then(la => { - if (la.includes(uid)) { - // Supprime tous les administrateurs - LDAP.change(config.key_id+gid+config.dn_groups, "del", config.group.admin).then(res => { - // Les rajoute un par un, sauf pour le supprimé - la.forEach(id => { - if (id!=uid) { this.addGroupAdmin(id, gid); } - }); - }); - } - }).then(res => { return true; })); - }); + if (!(await this.delGroupMember(uid, gid)&&this.addGroupMember(uid,gid))) { throw "Erreur dans l'éjection/réadmission du futur admin."; } + // Vérifie que l'utilisateur est bien admin (comme dans delGroupMember) + let la = await this.getAdmins(gid); + if (la.includes(uid)) { + // Supprime tous les administrateurs + if (!await LDAP.change(config.key_id+gid+config.dn_groups, "del", config.group.admin)) { throw "Erreur dans la suppression de tous les admins."; } + // Les rajoute un par un, sauf pour le supprimé + la.forEach(id => { + if (id!=uid) { this.addGroupAdmin(id, gid).then(res => { + if (!res) { throw "Erreur dans le réajout d'un des autres admins."; } + }); } + }); + } + return true; } //------------------------------------------------------------------------------------------------------------------------ @@ -153,26 +165,25 @@ class Admin extends User { * @arg {string} gid - Identifiant du groupe à modifier * @arg {Object} data - Dictionnaire des informations utilisateurs au même format que pour {@link User.addGroup} avec tous les champs optionnels. * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async */ - editGroup(gid, data) { + async editGroup(gid, data) { // Récupération des anciennes données - return this.peekGroup(gid).then(profil => { - // Reecriture de profil avec les bons champs - Object.keys(profil).forEach(keyLDAP => { - Object.keys(config.group).forEach(keyAlias => { - config.group[keyAlias]=keyLDAP; - profil[keyAlias]=profil[keyLDAP]; - }); - }); - // Surcharge des champs à modifier selon data - Object.keys(data).forEach(key => { - profil[key]=data[key]; - }); - // Modification propre - this.delGroup(gid).then(r => { - this.addGroup(profil).then(res => { if (!res) { throw Error; } else { return true; }}); + let profil = await this.peekGroup(gid); + // Reecriture de profil avec les bons champs + Object.keys(profil).forEach(keyLDAP => { + Object.keys(config.group).forEach(keyAlias => { + config.group[keyAlias]=keyLDAP; + profil[keyAlias]=profil[keyLDAP]; }); }); + // Surcharge des champs à modifier selon data + Object.keys(data).forEach(key => { + profil[key]=data[key]; + }); + // Modification propre + if (!await this.delGroup(gid)&&await this.addGroup(profil)) { throw "Erreur de la destruction/recréation du groupe."; } + return true; } //------------------------------------------------------------------------------------------------------------------------ @@ -185,17 +196,21 @@ class Admin extends User { * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Admin.delGroupMember} et {@link Admin.delGroupAdmin} pour gérer les groupes de l'utilisateur sortant. * @arg {string} gid - gid de la victime * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async */ - delGroup(gid) { + async delGroup(gid) { // Gestion des membres et administrateurs d'abord - return this.constructor.peekGroup(gid).then(profil => { - // Ordre important - profil[config.group['admin']].forEach( id => { this.delGroupAdmin( id, gid); }); - profil[config.group['member']].forEach(id => { this.delGroupMember(id, gid); }); - // Elimination - }).then(res => { - LDAP.clear(config.key_id+"="+gid+","+config.dn_groups); + let profil = await this.constructor.peekGroup(gid); + // Ordre important + profil[config.group['admin']].forEach( id => { + this.delGroupAdmin( id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un admin."; } }); + }); + profil[config.group['member']].forEach(id => { + this.delGroupMember(id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un membre."; } }); }); + // Elimination + if (!await LDAP.clear(config.key_id+"="+gid+","+config.dn_groups)) { throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes."; } + return true; } } @@ -233,8 +248,9 @@ class SuperAdmin extends Admin { * @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 + * @async */ - addUser(data) { + async addUser(data) { // Calcul d'un dictionnaire d'ajout let vals = {}; @@ -247,71 +263,83 @@ class SuperAdmin extends Admin { config.user.direct_input.forEach(key_att => vals[config.user[key_att]]=data[key_att]); // Appel à la fonction de base - return LDAP.add(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; - LDAP.change(config.key_id+"="+vals[config.key_id]+","+config.dn_users, "add", vals2).then(res => { - if (!res) { throw Error; } - }); + if (!await LDAP.add(config.key_id+"="+vals[config.key_id]+","+config.dn_users, vals)) { throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; } + + // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples + config.user.multiple_input.forEach(key_att => { + // On rajoute chaque valeur en entrée + data[key_att].forEach(val => { + let vals2 = {}; + vals2[config.user[key_att]]=val; + LDAP.change(config.key_id+"="+vals[config.key_id]+","+config.dn_users, "add", vals2).then(res => { + if (!res) { throw "Erreur lors de l'ajout d'une valeur pour un champ à valeurs multiples à la feuille du nouvel utilisateur."; } }); }); + }); - // Certains champs nécessitent de petits calculs - let vals3={}; + // 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 - Tests.generateId(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]; + // Création d'un nom complet lisible + vals3[config.user['fullName']]=data['givenName']+' '+data['lastName'].toUpperCase(); + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // TBM CA MARCHERA PB PAS + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + 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 + vals3[config.user['id']]= await Tests.generateId(config.user['id'], config.dn_users); + + // 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']; } + // 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'; + // Valeur nécessaire mais inutile + vals3[config.user['idNum']] ='5000'; - // Inscription des valeurs calculées + // Inscription des valeurs calculées + if (!await LDAP.change(config.key_id+"="+vals[config.user['hruid']]+","+config.dn_users, "add", vals3)) { + throw "Erreur lors de l'ajout des valeurs calculées à la feuille du nouvel utilisateur."; + } + + ["posixAccount", "shadowAccount", "inetOrgPerson", "brAccount"].forEach(cst => { + let val3={}; + vals3[config.user['class']]=cst; LDAP.change(config.key_id+"="+vals[config.user['hruid']]+","+config.dn_users, "add", vals3).then(res => { - if (!res) { throw Error; } + if (!res) { throw "Erreur lors de l'ajout d'une valeur constante à la feuille du nouvel utilisateur."; } }); + }); - ["posixAccount", "shadowAccount", "inetOrgPerson", "brAccount"].forEach(cst => { - let val3={}; - vals3[config.user['class']]=cst; - LDAP.change(config.key_id+"="+vals[config.user['hruid']]+","+config.dn_users, "add", vals3).then(res => { - if (!res) { throw Error; } - }); + // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble + data['groupsIsMember'].forEach(gid => { + this.addGroupMember(vals[config.key_id], gid).then(res => { + if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe."; } }); - - // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble - data['groupsIsMember'].forEach(gid => { this.addGroupMember(vals[config.key_id], gid); }); - data['groupsIsAdmin'].forEach(gid => { this.addGroupAdmin( vals[config.key_id], gid); }); - - return true; }); + data['groupsIsAdmin'].forEach(gid => { + this.addGroupAdmin( vals[config.key_id], gid).then(res => { + if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe en tant qu'admin."; } + }); + }); + + return true; } //------------------------------------------------------------------------------------------------------------------------ @@ -324,17 +352,19 @@ class SuperAdmin extends Admin { * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Admin.delGroupMember} et {@link Admin.delGroupAdmin} pour gérer les groupes de l'utilisateur sortant. * @arg {string} uid - uid de la victime * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async */ - delUser(uid) { + async delUser(uid) { // Gestion des groupes d'abord - return super.peekUser(uid).then(profil => { - profil[config.user['groups']].forEach(gid => { - this.isGroupAdmin(gid).then(res => { - this.delGroupAdmin(uid, gid); - }).then(res => { this.delGroupMember(uid, gid); }); - }); + let profil = await this.peekUser(uid); + profil[config.user['groups']].forEach(gid => { + if (this.isGroupAdmin(gid)) { + if (!this.delGroupAdmin(uid, gid)) { throw "Erreur lors de la suppression des droits d'admin de l'utilisateur."; } + } + if (!this.delGroupMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenace à un groupe de l'utilisateur."; } + }); // Elimination - }).then(res => { this.LDAP.clear(config.key_id+"="+uid+","+config.dn_users); }); + if (!this.LDAP.clear(config.key_id+"="+uid+","+config.dn_users)) { throw "Erreur lors de la suppression de l'utilisateur."; } } } diff --git a/src/ldap/basics.js b/src/ldap/basics.js index 4780a20..5ec3415 100644 --- a/src/ldap/basics.js +++ b/src/ldap/basics.js @@ -42,17 +42,32 @@ class LDAP { * @arg {Object} user - Utilisateur de la forme suivante : * @arg {string} user[uid] - User identifier * @arg {string} user[password] - Mot de passe - * @static + * @returns {Promise(boolean)} `true` si l'opération s'est bien déroulée, `false` sinon. + * @static @async */ - static bind(user) { client.bind(ldapEscape.dn("${txt}", { txt: user["dn"]}), ldapEscape.filter("${txt}", { txt: user["password"]}), (err, res) => {}); } + static async bind(user) { + return new Promise((resolve, reject) => { + // Escape DN as everywhere in this file, but password is taken as is + client.bind(ldapEscape.dn("${txt}", { txt: user["dn"]}), user["password"], res => { + // Gestion erreur + try { res; } + catch(err) { + reject(err); + throw "Erreur lors de la connection au LDAP."; + } + }); + // End with a boolean + resolve(true); + }); + } /** * @summary Fonction qui sert à se déconnecter du LDAP. Ne renvoie rien. - * @desc Assez important en terme de sécurité, de gestion de conflit, et de droit d'accès. Méthode ldapjs - * (voir [`Client API`](http://ldapjs.org/client.html) méthode bind). - * @static + * @desc Assez important en terme de sécurité, de gestion de conflit, et de droit d'accès. Fait appel à {@link LDAP.bind} + * @returns {Promise(boolean)} `true` si l'opération s'est bien déroulée, `false` sinon. + * @static @async */ - static unbind() { client.bind("", "", (err, res) => {}); } + static async unbind() { return this.bind({"uid":"", "password":""}); } /** * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit les valeurs trouvées. @@ -65,9 +80,9 @@ class LDAP { * @arg {string[]} attributes - Liste des attributs qui figureront dans le résultat final ; peut aussi être un seul élément * @return {Promise(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) - * @static + * @static @async */ - static search(dn, attributes, filter="(objectClass=*)") { + static async search(dn, attributes, filter="(objectClass=*)") { return new Promise((resolve, reject) => { let vals=[]; // Interrogation LDAP selon configuration fournie en argument @@ -75,26 +90,27 @@ class LDAP { "scope": "sub", "filter": ldapEscape.filter("${txt}", { txt: filter}), "attributes": attributes - }, function(err, res) { + }, (err, res) => { // Gestion erreur if (err) { reject(err); + throw "Erreur lors de la recherche sur le LDAP."; } else { // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse - res.on('searchEntry', function(entry) { + res.on('searchEntry', entry => { // Cas un seul attribut où le résultat est une liste directement - if (!Array.isArray(attributes)) { vals.push(entry.object[attributes]); } + if (!Array.isArray(attributes)) { vals.push(entry.object[attributes]); } else if (attributes.length == 1) { vals.push(entry.object[attributes[0]]); } // Cas plusieurs attributs donc résultat dictionnaire else { vals.push({}); - attributes.forEach((attribute) => { + attributes.forEach(attribute => { vals.slice(-1)[0][attribute]=entry.object[attribute]; }); } }); // Si la recherche est finie on se déconnecte et on renvoit la liste - res.on('end', function(res) { resolve(vals); }); + res.on('end', res => { resolve(vals); }); } }); }); @@ -111,17 +127,19 @@ class LDAP { * "del" qui en supprime, et "replace" qui remplace du contenu par un autre. * @arg {Object} mod - Dictionnaire contenant les attributs à modifier et les nouvelles valeurs des attributs. Une nouvelle valeur vide ("") est équivalent à la suppression * de cet attribut. - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon. - * @static + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, `false` sinon. + * @static @async */ - static change(dn, op, mod) { + static async change(dn, op, mod) { return new Promise((resolve, reject) => { // Modification LDAP selon configuration en argument (pourrait prendre une liste de Changes) client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({ operation: ldapEscape.dn("${txt}", {txt: op}), modification: mod, + // Gestion erreur }), err => { reject(err); + throw "Erreur lors d'une opération de modification sur le LDAP."; }); resolve(true); }); @@ -138,13 +156,14 @@ class LDAP { * @arg {Object} vals - Dictionnaire contenant les valeurs à créer * @arg {string} vals[key] - Nouvelle valeur pour le champ key * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon. - * @static + * @static @async */ - static add(dn, vals) { + static async add(dn, vals) { return new Promise((resolve, reject) => { // Ajout LDAP selon la configuration en argument - client.add(ldapEscape.dn(config.key_id+"="+vals[config.key_id]+",${txt}", { txt: dn}), vals, function(err) { + client.add(ldapEscape.dn(config.key_id+"="+vals[config.key_id]+",${txt}", { txt: dn}), vals, err => { reject(err); + throw "Erreur lors d'une opération d'ajout sur le LDAP."; }); resolve(true); }); @@ -161,13 +180,14 @@ class LDAP { * @arg {Object} user - Utilisateur de la forme nécessaire au {@link LDAP.connecter} * @arg {string} dn - Adresse de la cible * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @static + * @static @async */ - static clear(dn) { + static async clear(dn) { return new Promise((resolve, reject) => { // Suppression LDAP - client.del(ldapEscape.dn("${txt}", {txt: dn}), function(err) { + client.del(ldapEscape.dn("${txt}", {txt: dn}), err => { reject(err); + throw "Erreur lors d'une opération de suppression sur le LDAP."; }); resolve(true); }); diff --git a/src/ldap/users.js b/src/ldap/users.js index f36729b..8fd2f2e 100644 --- a/src/ldap/users.js +++ b/src/ldap/users.js @@ -23,7 +23,6 @@ class Open { /** * @class Cette classe est la classe exportable de base permettant à un utilisateur non connecté de faire des petites recherches simples. * @summary Constructeur vide. - * @author hawkspar */ constructor(config) {} @@ -35,9 +34,9 @@ class Open { * @desc Cette fonction utilise {@link LDAP.search} va directement à la feuille de l'utilisateur. * @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 - * @static + * @static @async */ - static getGroups(uid) { + static async getGroups(uid) { return LDAP.search(config.key_id+uid+config.dn_users, config.user.groups)[0]; } @@ -46,9 +45,9 @@ class Open { * @desc Cette fonction utilise {@link LDAP.search} avec un dictionnaire prédéfini dans config.json. * @arg {string} gid - Identifiant du groupe à interroger (le plus souvent nom du groupe en minuscule) * @return {Promise(String[])} Liste des uid des membres où l'id fournie est membre (noms flat des groupes) - * @static + * @static @async */ - static getMembers(gid) { + static async getMembers(gid) { return LDAP.search(config.key_id+gid+config.dn_users, config.group.member)[0]; } @@ -57,9 +56,9 @@ class Open { * @desc Cette fonction utilise {@link LDAP.search} avec un dictionnaire prédéfini dans config.json. * @arg {string} gid - Identifiant du groupe à interroger (le plus souvent nom du groupe en minuscule) * @return {Promise(string[])} Liste des uid des membres où l'id fournie est membre (noms flat des groupes) - * @static + * @static @async */ - static getAdmins(gid) { + static async getAdmins(gid) { return LDAP.search(config.key_id+gid+config.dn_users, config.group.admin)[0]; } @@ -69,19 +68,17 @@ class Open { * @param {string} uid - Identifiant de l'utilisateur à tester * @param {string} gid - Identification du groupe à tester * @returns {Promise(boolean)} True si l'utilisateur est membre - * @static + * @static @async */ - static isGroupMember(uid, gid) { - return this.getGroups(uid).then(lg => { - this.getMembers(gid).then(lm => { - if (lg.includes(gid) && lm.includes(uid)) { - return true; - } - else { - return false; - } - }); - }); + static async isGroupMember(uid, gid) { + let lg = await this.getGroups(uid); + let lm = await this.getMembers(gid); + if (lg.includes(gid) && lm.includes(uid)) { + return true; + } + else { + return false; + } } /** @@ -90,17 +87,16 @@ class Open { * @param {string} uid - Identifiant de l'utilisateur à tester * @param {string} gid - Identification du groupe à tester * @returns {Promise(boolean)} True si l'utilisateur est administrateur - * @static + * @static @async */ - static isGroupAdmin(uid, gid) { - return this.getAdmins(gid).then(la => { - if (la.includes(uid)) { - return true; - } - else { - return false; - } - }); + static async isGroupAdmin(uid, gid) { + let la = await this.getAdmins(gid); + if (la.includes(uid)) { + return true; + } + else { + return false; + } } /** @@ -137,18 +133,17 @@ class Open { * @arg {string} input - String entré par l'utilisateur qui ressemble au nom du groupe. * @arg {string} type - String aux valeurs prédéfinies dans ldap_config. * @return {Promise(string[])} Liste des gid dont le nom ressemble à l'input. - * @static + * @static @async */ - static findGroups(input, type) { + static async findGroups(input, type) { // Trucs intelligents faits dans ./utilities - return SmartSearch.groups(input, [config.key_id, config.group.type]).then(gList => { - let gidtyList = []; - gList.forEach(g => { - // Si le groupe est du bon type on rajoute son gid - if (g[config.group.type]==type) { gidtyList.push(g[config.key_id]); } - }); - return gidtyList; + let gList = await SmartSearch.groups(input, [config.key_id, config.group.type]); + let gidtyList = []; + gList.forEach(g => { + // Si le groupe est du bon type on rajoute son gid + if (g[config.group.type]==type) { gidtyList.push(g[config.key_id]); } }); + return gidtyList; } /** @@ -157,9 +152,9 @@ class Open { * @desc Cette fonction utilise {@link SmartSearch.users}. * @arg {Object} data - Dictionnaire contenant les données nécessaires à {@link SmartSearch.groups} * @return {Promise(string[])} gids des profils qui "match" les critères proposés. - * @static + * @static @async */ - static findUsers(data) { + static async findUsers(data) { return SmartSearch.users(data, config.key_id); } } @@ -167,9 +162,11 @@ class Open { class User extends Open { /** * @class Cette classe est la classe de l'utilisateur connecté qui peut déjà créer un groupe et changer son profil. + * Techniquement, c'est la première classe qui a vraiment besoin de méthodes dynamiques dans l'arborescence, puisque c'est à partir du niveau User + * qu'on peut commencer à vouloir tracer les actions de l'utilisateur. * @summary Ce constructeur appelle simplement {@link LDAP.bind}. * @arg {Object} user - Utilisateur de la forme nécessaire à {@link LDAP.bind}. - * @author hawkspar + * @async */ constructor(user) { super(); @@ -191,8 +188,9 @@ class User extends Open { * @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 + * @async */ - addGroup(data) { + async addGroup(data) { // Calcul d'un dictionnaire d'ajout let vals = {}; @@ -203,53 +201,54 @@ class User extends Open { config.group.direct_input.forEach(key_att => vals[config.group[key_att]]=data[key_att]); // Appel à la fonction de base - return LDAP.add(config.key_id+"="+vals[config.group['name']]+","+config.dn_groups, vals).then( res => { - // Certains champs nécessitent de petits calculs - let vals2={}; + if (!await LDAP.add(config.key_id+"="+vals[config.group['name']]+","+config.dn_groups, vals)) { + throw "Erreur lors de la création d'une nouvelle feuille dans l'arbre des groupes."; + } + // 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']; + // Sauvegarde du nom (pour le cas où gid != data['name']) + vals2[config.group["name"]]=data['name']; - // ?! - vals2[config.user['password']] = ''; + // ?! + vals2[config.user['password']] = ''; - // Génération id aléatoire et test contre le LDAP - Tests.generateId(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 test.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]; + // Génération id aléatoire et test contre le LDAP + Tests.generateId(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 test.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']] = '!*'; + // 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 - LDAP.change(config.key_id+"="+vals[config.key_id]+","+config.dn_groups, "add", vals2).then(res => { - if (!res) { throw Error; } - }); + // Inscription des valeurs calculées par effet de bord + if (!await LDAP.change(config.key_id+"="+vals[config.key_id]+","+config.dn_groups, "add", vals2)) { + throw "Erreur lors de l'ajout des valeurs intelligentes du nouveau groupe."; + } - ["posixAccount", "posixGroup", "brAccount"].forEach(cst => { - let vals3={}; - vals3[config.group['class']]=cst; - LDAP.change(config.key_id+"="+vals[config.key_id]+","+config.dn_groups, "add", vals3).then(res => { - if (!res) { throw Error; } - }); + ["posixAccount", "posixGroup", "brAccount"].forEach(cst => { + let vals3={}; + vals3[config.group['class']]=cst; + LDAP.change(config.key_id+"="+vals[config.key_id]+","+config.dn_groups, "add", vals3).then(res => { + if (!res) { throw "Erreur lors de l'ajout des valeurs constantes du nouveau groupe."; } }); + }); - // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble - data['members'].forEach(uid => { Admin.addGroupMember(uid, vals[config.key_att]); }); - data['admins'].forEach(uid => { Admin.addGroupAdmin( uid, vals[config.key_att]); }); + // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble + data['members'].forEach(uid => { Admin.addGroupMember(uid, vals[config.key_att]); }); + data['admins'].forEach(uid => { Admin.addGroupAdmin( uid, vals[config.key_att]); }); - return true; - }); + return true; } //------------------------------------------------------------------------------------------------------------------------ @@ -265,38 +264,38 @@ class User extends Open { * MEF ces valeurs vont écraser les précédentes. * 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 + * @async */ - editUser(uid, data) { + async editUser(uid, data) { // Récupération des anciennes données - return this.peekUser(uid).then(profil => { - // Reecriture de profil avec les bons champs - Object.keys(profil).forEach(keyLDAP => { - Object.keys(config.user).forEach(keyAlias => { - config.user[keyAlias]=keyLDAP; - profil[keyAlias]=profil[keyLDAP]; - }); + let profil = await this.peekUser(uid); + // Reecriture de profil avec les bons champs + Object.keys(profil).forEach(keyLDAP => { + Object.keys(config.user).forEach(keyAlias => { + config.user[keyAlias]=keyLDAP; + profil[keyAlias]=profil[keyLDAP]; }); - // Régénération du champ manquant dans profil - this.getGroups(uid).then(lg => { - profil['groupsIsAdmin']=[]; - lg.forEach(gid =>{ - this.isGroupAdmin(uid, gid).then(res =>{ - if (res) { profil['groupsIsAdmin'].push(gid); } - }).then(res => { - // Surcharge des champs à modifier selon data - Object.keys(data).forEach(key => { - profil[key]=data[key]; - }); - // Passage en godmode - var god = SuperAdmin({"uid":"", "password":""}); - // Modification propre - god.delUser(uid).then(r => { - god.createUser(profil).then(res => { if (!res) { throw Error; } else { return true; }}); - }); - }); - }); + }); + // Régénération du champ manquant dans profil + let lg = await this.getGroups(uid); + profil['groupsIsAdmin']=[]; + lg.forEach(gid => { + this.isGroupAdmin(uid, gid).then(res => { + if (res) { profil['groupsIsAdmin'].push(gid); } }); }); + // Surcharge des champs à modifier selon data + Object.keys(data).forEach(key => { + profil[key]=data[key]; + }); + // Passage en godmode + var god = SuperAdmin({"uid":"", "password":""}); + // Modification propre par effet de bord + if (!(await god.delUser(uid)&&await god.createUser(profil))) { + throw "Erreur dans la destruction/création du compte."; + } else { + return true; + } } destr() { LDAP.unbind(); } diff --git a/src/ldap/utilities.js b/src/ldap/utilities.js index 71f38a1..2a18ceb 100644 --- a/src/ldap/utilities.js +++ b/src/ldap/utilities.js @@ -36,14 +36,14 @@ class SmartSearch { * @arg {string} input - String entré par l'utilisateur qui ressemble au nom du groupe. * @arg {string[]} return_attributes - Liste d'attributs à renvoyer dans le résultat final * @return {Promise(string[])} Liste des uid de groupes dont le nom ressemble à l'input - * @static + * @static @async */ - static groups(input, return_attributes) { + static async groups(input, return_attributes) { // Construction du filtre custom - let filter= "(|("+config.key_id+"="+input+")"+ // On cherche la valeur exacte - "(|("+config.key_id+"=*"+input+")"+ // La valeur finale avec des trucs avant ; wildcard * + let filter= "(|("+config.key_id+"="+ input+")" + // On cherche la valeur exacte + "(|("+config.key_id+"=*"+input+")" + // La valeur finale avec des trucs avant ; wildcard * "(|("+config.key_id+"=*"+input+"*)"+ // La valeur du milieu avec des trucs avant et après - "("+config.key_id+"="+input+"*))))"; // La valeur du début avec des trucs après + "("+ config.key_id+"="+ input+"*))))"; // La valeur du début avec des trucs après // Appel rechercheLDAP avec filtre de l'espace return LDAP.search(config.dn_groups, return_attributes, filter); @@ -71,9 +71,9 @@ class SmartSearch { * @arg {string} data[sport] - Section sportive ou autre Doit être exact. * @arg {string[]} return_attributes - Liste d'attributs à renvoyer dans le résultat final * @return {Promise(Object[])} Liste de dictionnaires de profils en cohérence avec l'input avec pour clés les attributs des profils. - * @static + * @static @async */ - static users(data, return_attributes) { + static async users(data, return_attributes) { let filter=""; // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfinis dans config encore for (var key in data) { @@ -113,11 +113,14 @@ class Tests { * 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é - * @static + * @static @async */ - static ensureUnique(value, attribute, dn, changeValue, n=0) { + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // TBM PB DE THROW ; PB ABSENCE TOTAL DE DEBUG + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + static async ensureUnique(value, attribute, dn, changeValue, n=0) { // Recherche d'autres occurences de l'id - LDAP.search(dn, config.key_id, "("+attribute+"="+value+")").then(matches => { + return LDAP.search(dn, config.key_id, "("+attribute+"="+value+")").then(matches => { // On renvoit la valeur si elle est bien unique if (matches.length==0) { return value; } // Sinon, on tente de nouveau notre chance avec la valeur suivante @@ -132,9 +135,9 @@ class Tests { * @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é - * @static + * @static @async */ - static generateUid(givenName, lastName, promotion) { + static async generateUid(givenName, lastName, promotion) { // normalize et lowerCase standardisent le format return this.ensureUnique((givenName+'.'+lastName).toLowerCase().normalize('UFD'), config.key_id, config.dn_users, (id,n) => { if (n==1) { id+='.'+promotion; } // Si prénom.nom existe déjà , on rajoute la promo @@ -149,9 +152,9 @@ class Tests { * @desc Limité à un appel à {@link Tests.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). * @param {string} name - Nom * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié - * @static + * @static @async */ - static generateReadableId(name) { + static async generateReadableId(name) { // normalize et lowerCase standardisent le format return this.ensureUnique(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 @@ -166,9 +169,9 @@ class Tests { * @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é - * @static + * @static @async */ - static generateId(attribut, dn) { + static async generateId(attribut, dn) { return this.ensureUnique("0", attribut, dn, (id,n) => { Math.floor((Math.random() * 100000) + 1).toString(); }); } } -- GitLab