diff --git a/ldap_config.json b/ldap_config.json index efe56e030fa7e02dc8dcce6c69d8c5f55b629dba..c3b3bd22c9cdffea2afc452d76d9850e00c72723 100644 --- a/ldap_config.json +++ b/ldap_config.json @@ -1,11 +1,15 @@ { "comment_1": "Tout ce fichier sert à protéger les vrais champs du LDAP dans les scripts dans src/ldap. Les champs ci-dessous contiennent le nécessaire à une première connexion par exemple.", - "server_prod": "ldap://frankiz.eleves.polytechnique.fr:389", - "server_dev": "ldap://129.104.201.10:389", + "server": { + "prod": "ldap://frankiz.eleves.polytechnique.fr:389", + "dev": "ldap://129.104.201.10:389" + }, "comment_2": "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", + "dn":{ + "group":"ou=groups,dc=frankiz,dc=net", + "user": "ou=eleves,dc=frankiz,dc=net" + }, "key_id": "uid", "comment_3": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les utilisateurs", @@ -32,7 +36,10 @@ "mail": "mail", "ips": "brIP", "forlifes": "brAlias", - "groups": "brMemberOf", + "admins": "TBC", + "speakers": "TBC", + "members": "brMemberOf", + "followers": "TBC", "classes": "objectClass" }, "comment_4": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les groupes", @@ -40,8 +47,10 @@ "gid": "uid", "name": "brAlias", "category": "brNS", - "members": "restrictedMemberUid", "admins": "memberUid", + "speakers": "TBC", + "members": "restrictedMemberUid", + "followers": "TBC", "adress":"cn", "idNumber": "uidNumber", "idNumber2": "gidNumber", @@ -50,7 +59,7 @@ "directory": "homeDirectory", "cleanFullName": "gecos", "classes": "objectClass", - "child": "child", - "parent": "parent" + "childs": "TBC", + "parents": "TBC" } } \ No newline at end of file diff --git a/src/ldap/export/group.ts b/src/ldap/export/group.ts index 2c2bb8afa74025ba0069812dca530e4389d2a268..b586f3f0fe37403141da1bacd5063f8cfa7f6c1e 100644 --- a/src/ldap/export/group.ts +++ b/src/ldap/export/group.ts @@ -3,27 +3,10 @@ * @author hawkspar */ -import {ldapConfig} from '../internal/config'; +import { ldapConfig, groupData, categories } from '../internal/config'; import {Basics} from '../internal/basics'; import {Tools} from '../internal/tools'; - -/** - * @interface groupData - * @var {string} gid - Identifiant du groupe - * @var {string} name - Nom du groupe (souvent son nom mais pas nécessairement) - * @var {string} type - Statut du groupe ; binet, section sportive... (actuellement juste 'binet' ou 'free') - * @var {string[]} members - Liste des membres du groupe - * @var {string[]} admins - Liste des admins du groupe ; supposée être une sous-liste de la précédente - * @var {string} description - Description du groupe (facultatif) - */ -export class groupData { - "gid": string; - "name": string; - "type": string; - "members": string[]; - "admins": string[]; - "description"?: string; -} +import { visit } from 'graphql'; //------------------------------------------------------------------------------------------------------------------------ // Classes à exporter TBT @@ -49,7 +32,7 @@ export class Group { */ static async peek(gid: string) : Promise<groupData> { try { - return Tools.peek<groupData>("gr", gid, groupData); + return Tools.peek<groupData>("group", gid, groupData); } catch(err) { throw "Erreur lors d'une recherche d'informations sur un groupe."; @@ -67,17 +50,79 @@ export class Group { */ static async search(data: groupData) : Promise<string[]> { try { - return Tools.search("gr", data); + return Tools.search("group", data); } catch(err) { throw "Erreur lors de la recherche approximative d'un groupe."; } } + /** + * @memberof LDAP + * @summary Fonction qui permet de rajouter un administrateur à un groupe. + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. Le nouvel administrateur devient aussi membre et porte-parole du groupe, + * mais hérite aussi de son statut d'administrateur sur tous les groupes qui héritent du sien. + * @arg {string} uid - Identifiant du membre futur admin + * @arg {string} gid - Identifiant du groupe + * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async addAdmin(uid: string, gid: string): Promise<boolean> { + return await Tools.add(uid, gid, "members") && + await Tools.add(uid, gid, "speakers") && + await Tools.add(uid, gid, "admins"); + } + + /** + * @memberof LDAP + * @summary Fonction qui permet de supprimer un administrateur. + * @desc Cette fonction fait essentiellement appel à {@link Tools.remove}. + * Elle ne remonte pas les échelons, car cela permettrait à un admin d'un petit groupe de supprimer un admin d'un grand. + * @arg {string} uid - Identifiant de l'admin à dégrader, supposé membre + * @arg {string} gid - Identifiant du groupe + * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remAdmin(uid: string, gid: string): Promise<boolean> { + return Tools.remove(uid, gid, "admins"); + } + + /** + * @memberof LDAP + * @summary Fonction qui permet de rajouter un porte-parole à un groupe. + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. Elle rajoute également l'utilisateur au groupe. + * @arg {string} uid - Identifiant du membre futur porte-parole + * @arg {string} gid - Identifiant du groupe + * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async addSpeaker(uid: string, gid: string): Promise<boolean> { + return await Tools.add(uid, gid, "members") && + await Tools.add(uid, gid, "speakers"); + } + + /** + * @memberof LDAP + * @summary Fonction qui permet de rétrograder un membre du stade de porte-parole d'un groupe au stade d'utilisateur. + * @desc Cette fonction fait essentiellement appel à {@link Tools.remove}. Elle dégrade aussi d'un éventuel statut d'administrateur. + * @arg {string} uid - Identifiant de l'admin à dégrader (pas supposé valide) + * @arg {string} gid - Identifiant du groupe + * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remSpeaker(uid: string, gid: string): Promise<boolean> { + return await Tools.remove(uid, gid, "admins") && + await Tools.remove(uid, gid, "speakers"); + } + /** * @memberof LDAP * @summary Fonction qui permet d'ajouter un utilisateur à un groupe. - * @desc Cette fonction fait essentiellement appel à {@link Tools.getMembers}, {@link Tools.getGroups} et {@link LDAP.change}. + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. * @arg {string} uid - Identifiant de l'utilisateur à ajouter * @arg {string} gid - Identifiant du groupe * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon @@ -85,166 +130,66 @@ export class Group { * @static */ static async addMember(uid: string, gid: string) : Promise<boolean> { - try { - // Vérifie que l'utilisateur est pas déjà membre pour groupes - let lm = await Tools.getMembers(gid); - if (!lm.includes(uid)) { - let vals = {}; - vals[ldapConfig.group.members] = uid; - // Erreur si pb lors de la modification - if (!await Basics.change("gr", gid, "add", vals)) { - throw "Erreur lors de la modification dans l'arbre des groupes pour ajouter un membre."; - } - } - } - catch(err) { - throw "Erreur lors de la recherche de la liste des membres pour ajouter un membre."; - } - try { - // Vérifie que l'utilisateur est pas déjà membre pour users - let lg = await Tools.getGroups(uid); - if (!lg.includes(gid)) { - let vals2 = {}; - vals2[ldapConfig.user.groups] = gid; - // Erreur si pb lors de la modification - if (!await Basics.change("us", uid, "add", vals2)) { - throw "Erreur lors de la modification dans l'arbre des utilisateurs pour ajouter un membre."; - } - } - return true; - } - catch(err) { - throw "Erreur lors de la recherche de la liste des membres pour ajouter un membre."; - } + return Tools.add(uid, gid, "members"); } /** * @memberof LDAP * @summary Fonction qui permet de supprimer un membre existant d'un groupe. - * @desc Cette fonction fait essentiellement appel à {@link Tools.getMembers}, {@link Tools.getGroups} et {@link LDAP.change}. - * @arg {string} uid - Identifiant de l'ex-membre + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. + * Cette fonction supprime tous les droits de l'utilisateur sur le groupe, mais aussi sur les groupes sources si sont statut de membre était hérité. + * @arg {string} uid - Identifiant de l'ex-membre (pas supposé valide) * @arg {string} gid - Identifiant du groupe * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon * @async * @static */ static async remMember(uid: string, gid: string): Promise<boolean> { - try { - // Vérifie que l'utilisateur est pas déjà viré pour groupes - let lm = await Tools.getMembers(gid); - if (lm.includes(uid)) { - // Supprime tous les utilisateurs - if (!await Basics.change("gr", gid, "del", ldapConfig.group.members)) { - throw "Erreur lors de la suppression de tous les membres du groupe."; - } - // Les rajoute un par un, sauf pour le supprimé - lm.forEach(id => { - if (id!=uid) { - Group.addMember(id, gid).then(res => { - if (!res) { throw "Erreur lors du ré-ajout d'un autre membre"; } - }); - } - }); - } - } - catch(err) { - throw "Erreur pour obtenir une liste de membres d'un groupe pour supprimer un membre du groupe."; - } - try { - let lg = await Tools.getGroups(uid); - // Vérifie que l'utilisateur est pas déjà viré pour users - if (lg.includes(gid)) { - // Supprime tous les groupes - if (!await Basics.change("us", uid, "del", ldapConfig.user.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) { - Group.addMember(uid, id).then(res => { - if (!res) { throw "Erreur lors du ré-ajout des autres groupes"; } - }); - } - }); - } - return true; - } - catch(err) { - throw "Erreur pour obtenir une liste de groupes d'un membres pour le supprimer du groupe."; + let stack = []; + let res = true; + let visited = {}; + stack.push(gid); + while (stack.length>0) { + let cur_id = stack.pop(); + if (visited[cur_id] == undefined) { + visited[cur_id] = true; + res = res && await Tools.remove(uid, cur_id, "admins") && + await Tools.remove(uid, cur_id, "speakers") && + await Tools.remove(uid, cur_id, "members"); + stack.concat(await Basics.searchSingle("group", ldapConfig.group.childs, cur_id)); } + return res; } /** * @memberof LDAP - * @summary Fonction qui permet de promouvoir un membre au stade d'administrateur d'un groupe. - * @desc Cette fonction fait essentiellement appel à {@link Group.addMember} {@link Tools.getAdmins} et {@link LDAP.change}. Elle n'autorise pas - * les doublons et opère dans les deux dns users et groups. - * @arg {string} uid - Identifiant du membre futur admin + * @summary Fonction qui permet d'ajouter un sympathisant à un groupe. + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. + * @arg {string} uid - Identifiant de l'utilisateur à ajouter * @arg {string} gid - Identifiant du groupe - * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon * @async * @static */ - static async addAdmin(uid: string, gid: string): Promise<boolean> { - // Ajoute le membre au groupe avant d'en faire un admin - if (!await Group.addMember(uid,gid)) { throw "Erreur lors de l'ajout du futur admin en tant que membre."; } - try { - let la = await Tools.getAdmins(gid); - if (!la.includes(uid)) { - // Finalement modification, uniquement dans groups - let vals = {}; - vals[ldapConfig.group.admins] = uid; - if (!await Basics.change("gr", gid, "add", vals)) { - throw "Erreur lors de l'ajout de l'admin dans l'arbre des groupes."; - } - } - return true; - } - catch(err) { - throw "Erreur lors de l'obtention de la liste des administrateurs d'un groupe."; - } - } + static async addFollower(uid: string, gid: string) : Promise<boolean> { return Tools.add(uid, gid, "followers"); } /** * @memberof LDAP - * @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 Group.remMember}, {@link Group.addMember} {@link LDAP.change}. - * Rajoute l'utilisateur au groupe par effet de bord si l'utilisateur n'est pas administrateur. - * @arg {string} uid - Identifiant de l'admin à dégrader, supposé membre + * @summary Fonction qui permet de supprimer un sympathisant d'un groupe. + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. + * @arg {string} uid - Identifiant de l'ex-sympathisant (pas supposé valide) * @arg {string} gid - Identifiant du groupe - * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon * @async * @static */ - static async remAdmin(uid: string, gid: string): Promise<boolean> { - // Peut paraître absurde mais permet de s'assurer que le membre est bien présent et que ses champs sont comme il faut - if (!(await Group.remMember(uid, gid) && Group.addMember(uid,gid))) { throw "Erreur dans l'éjection/réadmission de l'ex-admin."; } - try { - // Vérifie que l'utilisateur est bien admin (comme dans delGroupMember) - let la = await Tools.getAdmins(gid); - if (la.includes(uid)) { - // Supprime tous les administrateurs - if (!await Basics.change("gr", gid, "del", ldapConfig.group.admins)) { - throw "Erreur dans la suppression de tous les admins pour en supprimer un."; - } - // Les rajoute un par un, sauf pour le supprimé - la.forEach(id => { - if (id!=uid) { Group.addAdmin(id, gid).then(res => { - if (!res) { throw "Erreur dans le réajout d'un des autres admins."; } - }); } - }); - } - return true; - } - catch(err) { - throw "Erreur lors de l'obtention de la liste des administrateurs d'un groupe."; - } - } + static async remFollower(uid: string, gid: string): Promise<boolean> { return Tools.remove(uid, gid, "followers"); } + /** * @memberof LDAP * @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 LDAP.add} et {@link LDAP.change}, mais aussi {@link Group.addMember} et {@link Group.addAdmin} + * l'escape de ses paramètres. Appelle {@link LDAP.add} et {@link LDAP.change}, mais aussi {@link Tools.add} * pour gérer les groupes du nouvel utilisateur. Attention une manip FOIREUSE est cachée dedans. * @arg {groupData} data - Dictionnaire des informations utilisateurs (voir détail des champs dans ldapConfig.json) * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon @@ -272,7 +217,7 @@ export class Group { for (let key_att in data) { vals[ldapConfig.group[key_att]]=data[key_att] }; // Appel à la fonction de base - if (!await Basics.add("gr", vals)) { + if (!await Basics.add("group", vals)) { throw "Erreur lors de la création d'une nouvelle feuille dans l'arbre des groupes."; } // Certains champs nécessitent de petits calculs @@ -286,7 +231,7 @@ export class Group { // Génération id aléatoire et test contre le LDAP try { - Tools.generateId(ldapConfig.group["idNumber"], "gr").then(id => { vals2[ldapConfig.group['idNumber']]=id; }); + Tools.generateId(ldapConfig.group["idNumber"], "group").then(id => { vals2[ldapConfig.group['idNumber']]=id; }); } catch(err) { throw "Erreur lors de la génération d'un id numérique pour créer un nouveau groupe."; @@ -308,29 +253,26 @@ export class Group { vals2[ldapConfig.group['writePerm']] = '!*'; // Inscription des valeurs calculées par effet de bord - if (!await Basics.change("gr", gid, "add", vals2)) { + if (!await Basics.change("group", gid, "add", vals2)) { throw "Erreur lors de l'ajout des valeurs intelligentes du nouveau groupe."; } ["posixAccount", "posixGroup", "brAccount"].forEach(cst => { let vals3={}; vals3[ldapConfig.group['classes']]=cst; - Basics.change("gr", gid, "add", vals3).then(res => { + Basics.change("group", gid, "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 => { - Group.addMember(uid, gid).then(res => { - if (!res) { throw "Erreur de l'ajout d'un membre au groupe."; } - }); - }); - data['admins'].forEach(uid => { - Group.addAdmin(uid, gid).then(res => { - if (!res) { throw "Erreur de l'ajout d'un admin au groupe."; } + for (let cat in categories) { + data[cat].forEach(uid => { + Tools.add(uid, gid, cat).then(res => { + if (!res) { throw "Erreur de l'ajout d'un membre au nouveau groupe."; } + }); }); - }); + } return true; } @@ -338,7 +280,7 @@ export class Group { * @memberof LDAP * @summary Fonction qui supprime un groupe du LDAP. * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement. A modifier une fois que le LDAP incluerait les groupes administres par une utilisateur. - * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Group.remMember} et {@link Group.remAdmin} pour gérer les groupes de l'utilisateur sortant. + * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Tools.remove} 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 @@ -346,30 +288,31 @@ export class Group { */ static async delete(gid: string): Promise<boolean> { try { - // Gestion des administrateur et membres d'abord + // Gestion des catégories en bloc d'abord let profil = await Group.peek(gid); - // Modification du profil de chaque utilisateur - profil[ldapConfig.group['member']].forEach(async function quickPartRemUser(uid: string) { - // Modification des profils de tous les utilisateurs - let lg = await Tools.getGroups(uid); - // Vérifie que l'utilisateur est pas déjà viré pour users - if (lg.includes(gid)) { - // Supprime tous les groupes - if (!await Basics.change("us", uid, "del", ldapConfig.user.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) { - Group.addMember(uid, id).then(res => { - if (!res) { throw "Erreur lors du ré-ajout des autres groupes"; } - }); + for (let cat of ["admins", "speakers", "members", "followers"]) { + profil[ldapConfig.group[cat]].forEach(async function quickPartRemUser(uid: string) { + // Modification des profils de tous les utilisateurs + let lg = await Tools.get(uid, "user", cat); + // Vérifie que l'utilisateur est pas déjà viré pour users + if (lg.includes(gid)) { + // Supprime tous les groupes + if (!await Basics.change("user", uid, "del", ldapConfig.user[cat])) { + throw "Erreur lors de la suppression de tous les groupes du membre."; } - }); - } - }); + // Les rajoute un par un, sauf pour le groupe supprimé + lg.forEach(id => { + if (id!=gid) { + Tools.add(uid, id, cat).then(res => { + if (!res) { throw "Erreur lors du ré-ajout des autres groupes"; } + }); + } + }); + } + }); + } // Elimination - if (!await Basics.clear("gr",gid)) { throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes."; } + if (!await Basics.clear("group",gid)) { throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes."; } return true; } catch(err) { @@ -388,7 +331,7 @@ export class Group { */ static async edit(data: groupData) : Promise<boolean> { try { - return Tools.edit("gr",data); + return Tools.edit("group",data); } catch(err) { throw "Erreur lors de la modification d'un groupe."; diff --git a/src/ldap/export/user.ts b/src/ldap/export/user.ts index 8eb37d2b2193ada3dc7171041519c2705febad7d..d893b7796d1de913312ebcbf8fbbaa35d0a3cffd 100644 --- a/src/ldap/export/user.ts +++ b/src/ldap/export/user.ts @@ -3,60 +3,9 @@ * @author hawkspar */ -import {ldapConfig} from '../internal/config'; +import {ldapConfig, userData, categories} from '../internal/config'; import {Basics} from '../internal/basics'; import {Tools} from '../internal/tools'; -import {Group} from './group'; - -/** - * @interface userData - * @desc Interface avec toutes les données extractables pour un utilisateur. - * @var {string} uid - Identifiant utilisateur - * @var {string} givenName - Prénom - * @var {string} lastName - Nom - * @var {string} nickname - Surnom - * @var {string} photo - Bytestring de la photo de l'utilisateur - * @var {string} birthdate - Date d'anniversaire - * TBA @var {string} nationality - Nationalité d'origine - * @var {string} promotion - Année(s) de promo - * @var {string} phone - Numéro(s) de téléphone - * @var {string[]} address - Adresse(s) - * @var {string[]} mail - Adresse(s) courriel - * @var {string[]} groups - Un ou plusieurs groupes dont l'utilisateur est membre (inclus section sportive, binet, PA...) - * @var {string} password - Mot de passe généré en amont - * @var {string[]} ips - Adresse(s) ip - * @var {string} directory - Adresse soft des données utilisateurs - * @var {string} login - Astuce de root flemmard - * @arg {string} readPerm - Permissions spéciales BR - * @var {string} writePerm - Permissions spéciales BR - * @var {string[]} forlifes - Alias BR (attention le filtre .fkz n'est plus fonctionnel) - * @var {string[]} admins - Liste des gid dont l'utilisateur est admin ; supposé sous-liste de groups - * TBA @var {string[]} likes - Liste des gid dont l'utilisateur est sympathisant - */ -export class userData { - uid?: string; - groups?: string[]; - groupsIsAdmin?: string[]; - password?: string; - givenName?: string; - lastName?: string; - nickname?: string; - promotion?: string; - photo?: string; - birthdate?: string; - nationality?: string; - phone?: string; - address?: string; - mail?: string; - ips?: string[]; - directory?: string; - login?: string; - readPerm?: string; - writePerm?: string; - forlifes?: string[]; - sport?: string; - //"likes"?: string[] -} //------------------------------------------------------------------------------------------------------------------------ // Classes à exporter TBT @@ -82,7 +31,7 @@ export class User { */ static async peek(uid: string) : Promise<userData> { try { - return Tools.peek<userData>("us", uid, userData); + return Tools.peek<userData>("user", uid, userData); } catch(err) { throw "Error while peeking a user."; @@ -102,7 +51,7 @@ export class User { */ static async search(data: userData) : Promise<string[]> { try { - return Tools.search("us", data); + return Tools.search("user", data); } catch(err) { throw "Erreur lors de la recherche approximative d'un utilisateur."; @@ -112,8 +61,8 @@ export class User { /** * @memberof LDAP * @summary Fonction qui créé un nouvel utilisateur dans le LDAP. - * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link Group.addMember} et {@link Group.addAdmin} pour gérer les groupes du nouvel utilisateur. - * @arg {fullUserData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis. + * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link Tools.add} pour gérer les groupes du nouvel utilisateur. + * @arg {userData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis. * Cette application ne permet pas de rejoindre des groupes. * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon * @async @@ -141,7 +90,7 @@ export class User { } // Appel à la fonction de base - if (!await Basics.add("us", vals)) { throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; } + if (!await Basics.add("user", vals)) { throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; } for (let key_att in data) { // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples @@ -150,7 +99,7 @@ export class User { data[key_att].forEach(val => { let vals2 = {}; vals2[ldapConfig.user[key_att]]=val; - Basics.change("us", uid, "add", vals2).then(res => { + Basics.change("user", uid, "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."; } }); }); @@ -173,7 +122,7 @@ export class User { } try { // Génération id aléatoire unique - vals3[ldapConfig.user['id']]= await Tools.generateId(ldapConfig.user['id'], "us"); + vals3[ldapConfig.user['id']]= await Tools.generateId(ldapConfig.user['id'], "user"); } catch(err) { throw "Erreur lors de la génération d'un id numérique pour un nouvel utilisateur."; @@ -199,17 +148,24 @@ export class User { vals3[ldapConfig.user['idNum']] ='5000'; // Inscription des valeurs calculées - if (!await Basics.change("us", uid, "add", vals3)) { + if (!await Basics.change("user", uid, "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[ldapConfig.user['class']]=cst; - Basics.change("us", uid, "add", vals3).then(res => { + Basics.change("user", uid, "add", vals3).then(res => { if (!res) { throw "Erreur lors de l'ajout d'une valeur constante à la feuille du nouvel utilisateur."; } }); }); + + // Ajout dans les groupes à la catégorie voulue + for (let cat of categories) { + for (let gid of data[cat]) { + Tools.add(uid, gid, cat); + } + } return true; } @@ -221,7 +177,7 @@ export class User { * @memberof LDAP * @summary Fonction qui supprime un utilisateur du LDAP. * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement. - * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Group.remMember} et {@link Group.remAdmin} pour gérer les groupes de l'utilisateur sortant. + * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Tools.remove} 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 @@ -231,32 +187,32 @@ export class User { try { // Gestion des groupes d'abord let profil = await User.peek(uid); - profil[ldapConfig.user['groups']].forEach(async function (gid: string) { - // Si l'utilisateur était admin, l'enlever - Group.remAdmin(uid, gid); - // Enlever de la liste des membres - let lm = await Tools.getMembers(gid); - if (lm.includes(uid)) { - // Supprime tous les membres - if (!await Basics.change("gr", gid, "del", ldapConfig.group.members)) { - throw "Erreur lors de la suppression de tous les membres du groupe."; - } - // Les rajoute un par un, sauf pour le supprimé - lm.forEach(id => { - if (id!=uid) { - Group.addMember(id, gid).then(res => { - if (!res) { throw "Erreur lors du ré-ajout d'un autre membre"; } - }); + for (let cat of categories) { + profil[ldapConfig.user[cat]].forEach(async function (gid: string) { + // Enlever de la liste des membres + let lm = await Tools.get(gid, "group", cat); + if (lm.includes(uid)) { + // Supprime tous les membres + if (!await Basics.change("group", gid, "del", ldapConfig.group[cat])) { + throw "Erreur lors de la suppression de tous les membres du groupe."; } - }); - } - }); + // Les rajoute un par un, sauf pour le supprimé + lm.forEach(id => { + if (id!=uid) { + Tools.add(id, gid, cat).then(res => { + if (!res) { throw "Erreur lors du ré-ajout d'un autre membre"; } + }); + } + }); + } + }); + } } catch(err) { throw "Erreur lors de l'obtention des informations de l'utilisateur à supprimer."; } // Elimination - if (!Basics.clear("us", uid)) { throw "Erreur lors de la suppression de l'utilisateur."; } + if (!Basics.clear("user", uid)) { throw "Erreur lors de la suppression de l'utilisateur."; } return true; } @@ -271,7 +227,7 @@ export class User { */ static async edit(data : userData) : Promise<boolean> { try { - return Tools.edit("us",data); + return Tools.edit("user",data); } catch(err) { throw "Erreur lors de la modification d'un utilisateur."; diff --git a/src/ldap/internal/basics.ts b/src/ldap/internal/basics.ts index 10dfabefb84dff905a08c4e70f7497ae92184b9f..7097d58574c7c17d6ef55cc92c07d63a4b9b1773 100644 --- a/src/ldap/internal/basics.ts +++ b/src/ldap/internal/basics.ts @@ -98,12 +98,11 @@ export class Basics { * @static * @async */ - static search(domain: 'gr'|'us', attributes: string[], id: string, filter: string, handler : (entry: any) => void) : Promise<void> { + static search(domain: 'group'|'user', attributes: string[], id: string, filter: string, handler : (entry: any) => void) : Promise<void> { Basics.adminBind(); let dn =""; if (id != null) { dn+=ldapConfig.key_id+'='+ ldapEscape.dn("${txt}", { txt: id}) +','; } - if (domain == "gr") { dn+=ldapConfig.dn_groups; } - else { dn+=ldapConfig.dn_users; } + dn+=ldapConfig.dn[domain]; // Interrogation LDAP selon filter let promise = new Promise<void>(function(resolve, reject) { client.search(dn, { // Must be escaped in case of a malignious false id @@ -141,7 +140,7 @@ export class Basics { * @static * @async */ - static async searchSingle(domain: 'gr'|'us', attribute: string, id: string=null, filter: string="(objectClass=*)") : Promise<string[]> { + static async searchSingle(domain: 'group'|'user', attribute: string, id: string=null, filter: string="(objectClass=*)") : Promise<string[]> { let vals=[]; await Basics.search(domain, [attribute], id, filter, entry => { // Cas un seul attribut où le résultat est une liste directement @@ -164,7 +163,7 @@ export class Basics { * @static * @async */ - static async searchMultiple(domain: 'gr'|'us', attributes: string[], id: string=null, filter: string="(objectClass=*)") : Promise<Array<dic>> { + static async searchMultiple(domain: 'group'|'user', attributes: string[], id: string=null, filter: string="(objectClass=*)") : Promise<Array<dic>> { let vals=[]; await Basics.search(domain, attributes, id, filter, entry => { // Cas plusieurs attributs donc résultat dictionnaire @@ -191,11 +190,10 @@ export class Basics { * @static * @async */ - static async change(domain: 'gr'|'us', id: string, op: "add"|"del"|"replace", mod: dic) : Promise<boolean> { + static async change(domain: 'group'|'user', id: string, op: "add"|"del"|"replace", mod: dic) : Promise<boolean> { Basics.adminBind(); let dn = ldapConfig.key_id+'='+id+',' - if (domain == "gr") { dn+=ldapConfig.dn_groups } - else { dn+=ldapConfig.dn_users } + dn+=ldapConfig.dn[domain]; // Modification LDAP selon dn fourni en argument (pourrait prendre une liste de Changes) client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({ operation: op, @@ -220,11 +218,10 @@ export class Basics { * @static * @async */ - static async add(domain: 'gr'|'us', vals) : Promise<boolean> { + static async add(domain: 'group'|'user', vals) : Promise<boolean> { Basics.adminBind(); let dn = ldapConfig.key_id+"="+vals[ldapConfig.key_id]; - if (domain == "gr") { dn+=ldapConfig.dn_groups; } - else { dn+=ldapConfig.dn_users; } + dn+=ldapConfig.dn[domain]; // Ajout LDAP selon la ldapConfiguration en argument client.add(ldapEscape.dn("${txt}", { txt: dn}), vals, err => { throw "Erreur lors d'une opération d'ajout sur le LDAP."; @@ -245,11 +242,10 @@ export class Basics { * @static * @async */ - static async clear(domain: 'gr'|'us', id: string) : Promise<boolean> { + static async clear(domain: 'group'|'user', id: string) : Promise<boolean> { Basics.adminBind(); let dn = ldapConfig.key_id+'='+id+',' - if (domain == "gr") { dn+=ldapConfig.dn_groups; } - else { dn+=ldapConfig.dn_users; } + dn+=ldapConfig.dn[domain]; // Suppression LDAP client.del(ldapEscape.dn("${txt}", {txt: dn}), err => { throw "Erreur lors d'une opération de suppression sur le LDAP."; diff --git a/src/ldap/internal/config.ts b/src/ldap/internal/config.ts index ad76623e915817caa1234b39cade12b0ae18b5c7..d8366563aadf34eee10077804f865358d32e37b3 100644 --- a/src/ldap/internal/config.ts +++ b/src/ldap/internal/config.ts @@ -12,18 +12,103 @@ import fs from 'fs'; import path from 'path'; import colors from 'colors'; + // Point central ; tous les champs de la BDD sont 'cachés' dans config.json et pas visibles directement -let path_config = path.resolve(__dirname,'..', '..', '..', 'ldap_config.json'); +let path_config = path.resolve(__dirname, '..', '..', '..', 'ldap_config.json'); console.log(colors.cyan("Loading LDAP config file from "+path_config)); export const ldapConfig = JSON.parse(fs.readFileSync(path_config).toString()); -let path_credentials = path.resolve(__dirname,'..', '..', '..', 'ldap_credentials.json') -console.log(colors.cyan("Loading LDAP credentials from "+path_credentials)); -export const credentialsLdapConfig = JSON.parse(fs.readFileSync(path_credentials).toString()); + // Override config server from environment if (process.env.LDAP_URI != null) { ldapConfig.server = process.env.LDAP_URI; } else { - if (process.env.TARGET_ENV == `production`) { ldapConfig.server = ldapConfig.server_prod; } - else { ldapConfig.server = ldapConfig.server_dev; } + if (process.env.TARGET_ENV == `production`) { ldapConfig.server = ldapConfig.server.prod; } + else { ldapConfig.server = ldapConfig.server.dev; } +} + +// Gestion des super-identifiants +let path_credentials = path.resolve(__dirname, '..', '..', '..', 'ldap_credentials.json') +console.log(colors.cyan("Loading LDAP credentials from "+path_credentials)); +export const credentialsLdapConfig = JSON.parse(fs.readFileSync(path_credentials).toString()); + +// Data formats and useful constants +export const categories = ["admins","speakers","members","followers"]; + +/** + * @interface userData + * @desc Interface avec toutes les données extractables pour un utilisateur. + * @var {string?} uid - Identifiant utilisateur + * TBA @var {string[]?} admins - Liste des gid (group id, inclus section sportive, binet, PA...) dont l'utilisateur est admin ; pas forcément sous-liste de groups + * TBA @var {string[]?} speakers - Liste des gid dont l'utilisateur est porte-parole ; pas forcément sous-liste de groups + * @var {string[]?} members - Liste des gid dont l'utilisateur est membre + * TBA @var {string[]?} followers - Liste des gid dont l'utilisateur est sympathisant + * @var {string?} givenName - Prénom + * @var {string?} lastName - Nom + * @var {string?} nickname - Surnom + * @var {string?} photo - Bytestring de la photo de l'utilisateur + * @var {string?} birthdate - Date d'anniversaire + * TBA @var {string?} nationality - Nationalité d'origine + * @var {string?} promotion - Année(s) de promo + * @var {string?} phone - Numéro(s) de téléphone + * @var {string[]} address - Adresse(s) + * @var {string[]?} mail - Adresse(s) courriel + * @var {string?} password - Mot de passe généré en amont (utilisé seulement à l'initialialisation, pas stocké bien sûr) + * @var {string[]?} ips - Adresse(s) ip + * @var {string?} directory - Adresse soft des données utilisateurs + * @var {string?} login - Astuce de root flemmard + * @arg {string?} readPerm - Permissions spéciales BR + * @var {string?} writePerm - Permissions spéciales BR + * @var {string[]?} forlifes - Alias BR (attention le filtre .fkz n'est plus fonctionnel) + * @memberof LDAP + */ +export class userData { + uid?: string; + admins?: string[]; + speakers?: string[]; + members?: string[]; + followers?: string[]; + password?: string; + givenName?: string; + lastName?: string; + nickname?: string; + promotion?: string; + photo?: string; + birthdate?: string; + nationality?: string; + phone?: string; + address?: string; + mail?: string; + ips?: string[]; + directory?: string; + login?: string; + readPerm?: string; + writePerm?: string; + forlifes?: string[]; + sport?: string; +} + +/** + * @interface groupData + * @var {string} gid - Identifiant du groupe + * @var {string} name - Nom du groupe (souvent son nom mais pas nécessairement) + * @var {string} type - Statut du groupe ; binet, section sportive... (actuellement juste 'binet' ou 'free') + * @var {string[]} members - Liste des membres du groupe + * @var {string[]} admins - Liste des admins du groupe ; supposée être une sous-liste de la précédente + * @var {string} description - Description du groupe (facultatif) + * @var {string[]} childs - Liste des groupes enfants de première génération de celui-ci (les admins du groupe seront admins de ce groupe et des enfants suivants) + * @var {string[]} parents - Liste des groupes directement parents de celui-ci (les membres du groupe seront membres de ce groupe et des parents suivants) ; symétrique du précédent + * @memberof LDAP + */ +export class groupData { + gid: string; + name: string; + type: string; + members: string[]; + speakers: string[]; + followers: string[]; + admins: string[]; + description?: string; + childs?: string[]; + parents?: string[]; } \ No newline at end of file diff --git a/src/ldap/internal/tools.ts b/src/ldap/internal/tools.ts index 4adaf7052c10c894a052980c0d294a0ad91d656e..acb065f42d5795fa8a46437dd7edf407b22b03f2 100644 --- a/src/ldap/internal/tools.ts +++ b/src/ldap/internal/tools.ts @@ -6,10 +6,8 @@ // Toutes les entrées utilisateur sont escapées par sécurité import ldapEscape from 'ldap-escape'; // Imports internes -import {ldapConfig} from './config'; +import {ldapConfig, userData, groupData} from './config'; import {Basics} from './basics'; -import {userData} from '../export/user'; -import {groupData} from '../export/group'; //------------------------------------------------------------------------------------------------------------------------ // Fonctions intermédiaires TBT @@ -23,7 +21,7 @@ export class Tools { * @summary Constructeur vide. */ constructor() {} - + /** * @memberof LDAP * @summary Fonction qui renvoit toutes les infos relatives à un groupe ou un utilisateur particulier. @@ -35,13 +33,8 @@ export class Tools { * @static * @async */ - static async peek<T>(domain: 'us'|'gr', id: string, type: new () => T) : Promise<T> { - if (domain=='gr') { - var dirtyKeys = ldapConfig.group; - } - else { - var dirtyKeys = ldapConfig.user; - } + static async peek<T>(domain: 'user'|'group', id: string, type: new () => T) : Promise<T> { + var dirtyKeys = ldapConfig[domain]; let cleanData : T = new type(); let attr = Object.keys(dirtyKeys).map(key => dirtyKeys[key]); //console.log(attr); @@ -57,7 +50,6 @@ export class Tools { return cleanData; } - /** * @memberof LDAP * @summary Fonction qui retrouve les id des paxs ou groupes validant les critères de recherche. Etape vers vrai TOL (Trombino On Line). @@ -73,7 +65,7 @@ export class Tools { * @static * @async */ - static async search(domain : "us"|"gr", data : userData|groupData) : Promise<string[]> { + static async search(domain: "user"|"group", data: userData|groupData) : Promise<string[]> { let filter=""; // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfinis dans ldapConfig encore for (var key in data) { @@ -83,8 +75,7 @@ export class Tools { data[key].forEach(val => { // Traduction en language LDAP let attribute = ""; - if (domain="us") { attribute = ldapConfig.user[key]; } - else { attribute = ldapConfig.group[key]; } + attribute = ldapConfig[domain][key]; // Escape user input val = ldapEscape.filter("${fil}", { fil: val}); // Creation incrémentale du filtre @@ -109,15 +100,10 @@ export class Tools { * @async * @static */ - static async edit(domain: "us"|"gr", data: userData|groupData) : Promise<boolean> { - if (domain = "us") { - var id=data['uid']; - var dirtyKeys=ldapConfig.user; - } - else { - var id=data['gid']; - var dirtyKeys=ldapConfig.group; - } + static async edit(domain: "user"|"group", data: userData|groupData) : Promise<boolean> { + if (domain == "user") { var id=data['uid']; } + else { var id=data['gid']; } + var dirtyKeys=ldapConfig[domain]; // Rename in an LDAP-friendly way let dirtyData = {}; Object.keys(data).forEach(function(key: string) { @@ -129,6 +115,109 @@ export class Tools { return Basics.change(domain,id,"replace",dirtyData); } + /** + * @memberof LDAP + * @summary Fonction qui permet d'ajouter un utilisateur à une catégorie d'un groupe. + * @desc Cette fonction fait essentiellement appel à d'autres fonctions de {@link Tools} passées en argument et {@link LDAP.change}. + * Cette fonction ne créé pas de doublon et opère conjointement dans les deux arbres "group" et "user" + * @arg {string} uid - Identifiant du futur membre + * @arg {string} gid - Identifiant du groupe + * @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower) + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async add(uid: string, gid: string, category: string): Promise<boolean> { + try { + // Vérifie que l'utilisateur est pas déjà membre pour groupes + let lu = await Tools.get(gid, "group", category); + let catName = ldapConfig.group[category]; + if (!lu.includes(uid)) { + // Ajoute l'utilisateur dans la catégorie concernée + if (!await Basics.change("group", gid, "add", catName)) { + throw "Erreur lors de la modification dans l'arbre des groupes pour ajouter un membre dans la catégorie voulue."; + } + } + } + catch(err) { + throw "Erreur pour obtenir une liste de membres d'une catégorie d'un groupe pour ajouter un membre de cette categorie du groupe."; + } + try { + // Vérifie que l'utilisateur est pas déjà membre pour user + let lg = await Tools.get(uid, "user", category); + let catName = ldapConfig.user[category]; + if (!lg.includes(gid)) { + // Ajoute l'utilisateur dans la categorie voulue + if (!await Basics.change("user", uid, "add", catName)) { + throw "Erreur lors de l'ajout d'un utilisateur dans une catégorie d'un groupe."; + } + } + return true; + } + catch(err) { + throw "Erreur pour obtenir une liste de groupes d'une categorie d'un membre pour ajouter un groupe de cette category pour le membre."; + } + } + + /** + * @memberof LDAP + * @summary Fonction qui permet de supprimer un membre d'une catégorie existant d'un groupe. + * @desc Cette fonction fait essentiellement appel à d'autres fonctions de {@link Tools} passées en argument et {@link LDAP.change}. + * @arg {string} uid - Identifiant de l'ex-membre + * @arg {string} gid - Identifiant du groupe + * @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower) + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remove(uid: string, gid: string, category : string): Promise<boolean> { + try { + // Vérifie que l'utilisateur est pas déjà viré pour groupes + let lu = await Tools.get(gid, "group", category); + let catName = ldapConfig.group[category]; + if (lu.includes(uid)) { + // Supprime tous les utilisateurs + if (!await Basics.change("group", gid, "del", catName)) { + throw "Erreur lors de la suppression de tous les membres d'une catégorie du groupe."; + } + // Les rajoute un par un, sauf pour le supprimé + lu.forEach(id => { + if (id!=uid) { + Tools.add(id, gid, category).then(res => { + if (!res) { throw "Erreur lors du ré-ajout d'un autre membre d'une catégorie."; } + }); + } + }); + } + } + catch(err) { + throw "Erreur pour obtenir une liste de membres d'une catégorie d'un groupe pour supprimer un membre de cette categorie du groupe."; + } + try { + // Vérifie que l'utilisateur est pas déjà viré pour user + let lg = await Tools.get(uid, "user", category); + let catName = ldapConfig.user[category]; + if (lg.includes(gid)) { + // Supprime tous les groupes de la catégorie pour l'utilisateur + if (!await Basics.change("user", uid, "del", catName)) { + throw "Erreur lors de la suppression de tous les groupes d'un membre."; + } + // Les rajoute un par un, sauf pour le supprimé + lg.forEach(id => { + if (id!=uid) { + Tools.add(id, gid, category).then(res => { + if (!res) { throw "Erreur lors du ré-ajout d'un autre groupe."; } + }); + } + }); + } + return true; + } + catch(err) { + throw "Erreur pour obtenir une liste de groupes d'une categorie d'un membre pour supprimer un groupe de cette category pour le membre."; + } + } + /** * @callback changeValueCallback * @param {string} id - Id à modifier @@ -151,7 +240,7 @@ export class Tools { * @static * @async */ - static async ensureUnique(value: string, attribute: string, domain: 'gr'|'us', changeValue: (string, number) => string, n: number=0) : Promise<string> { + static async ensureUnique(value: string, attribute: string, domain: 'group'|'user', changeValue: (string, number) => string, n: number=0) : Promise<string> { // Recherche d'autres occurences de l'id try { return Basics.searchSingle(domain, ldapConfig.key_id, null, "("+attribute+"="+value+")").then(function (matches: string[]) { @@ -181,7 +270,7 @@ export class Tools { static async generateUid(givenName: string, lastName: string, promotion: string) : Promise<string> { try { // normalize et lowerCase standardisent le format - return Tools.ensureUnique((givenName+'.'+lastName).toLowerCase().normalize('UFD'), ldapConfig.key_id, "us", (id: string, n: number) => { + return Tools.ensureUnique((givenName+'.'+lastName).toLowerCase().normalize('UFD'), ldapConfig.key_id, "user", (id: string, n: number) => { if (n=1) { id+='.'+promotion; } // Si prénom.nom existe déjà , on rajoute la promo else if (n=2) { id+='.'+(n-1).toString(); } // Puis si prénom.nom.promo existe déjà on passe à nom.prenom.promo .1 else if (n>2) { id+=n; } // Ensuite on continue .123, .1234, etc... @@ -205,7 +294,7 @@ export class Tools { static async generateReadableId(name: string) : Promise<string> { try { // normalize et lowerCase standardisent le format - return Tools.ensureUnique(name.toLowerCase().normalize('UFD'), ldapConfig.key_id, "gr", (id: string, n: number) => { + return Tools.ensureUnique(name.toLowerCase().normalize('UFD'), ldapConfig.key_id, "group", (id: string, n: number) => { if (n=1) { id+='.'+n.toString(); } // Si nom existe déjà , on essaie nom.1 else if (n>1) { id+=n.toString(); } // Ensuite on continue .12, .123, etc... return id; @@ -225,7 +314,7 @@ export class Tools { * @static * @async */ - static async generateId(attribut: string, domain: "gr"|"us") : Promise<string> { + static async generateId(attribut: string, domain: "group"|"user") : Promise<string> { try { return Tools.ensureUnique("0", attribut, domain, (id,n) => { return Math.floor((Math.random() * 100000) + 1).toString(); }); } @@ -236,57 +325,43 @@ export class Tools { /** * @memberof LDAP - * @summary Fonction qui retrouve les groupes dont un individu est membre. - * @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 + * @summary Fonction qui retrouve les utilisateurs ou groupes respectivement correspondant à un groupe ou un utilisateur de la même catégorie. + * @desc Cette fonction utilise {@link LDAP.search} et va directement à la feuille de l'utilisateur ou du groupe interrogé. + * Pour autant, elle est moins naïve qu'elle en a l'air. Elle gère la descente des admins et la remontée des membres. + * @param {string} id - Identifiant du groupe ou de l'individu à interroger (supposé valide) + * @param {"user"|"group"} domain - Arbre à interroger + * @param {"admins"|"speakers"|"members"|"followers"} category - Catégorie considérée + * @return {Promise(string[])} Liste des id de groupes ou d'utilisateurs de la bonne catégorie associé à l'id * @static * @async */ - static async getGroups(uid: string) : Promise<string[]> { + static async get(id : string, domain : "user"|"group", category : string): Promise<string[]> { //"admins"|"speakers"|"members"|"followers") { try { - return Basics.searchSingle("us", ldapConfig.user.groups, uid); + if (!(category in ["admins","members"]) || domain=="group") { + return await Basics.searchSingle(domain, ldapConfig[domain][category], id); + } + else { + // Clean depth-first search for inherited members and admins + let stack = []; + let res = []; + let visited = {}; + stack.push(id); + while (stack.length>0) { + let cur_id = stack.pop(); + if (visited[cur_id] == undefined) { + visited[cur_id] = true; + res.concat(await Basics.searchSingle("group", ldapConfig.group[category], cur_id)); + // In the end, the precise category only changes the iteration direction + if (category == "members") { stack.concat(await Basics.searchSingle("group", ldapConfig.group.childs, cur_id)); } + else { stack.concat(await Basics.searchSingle("group", ldapConfig.group.parents, cur_id)); } + } + } + } } catch(err) { throw "Erreur lors de la recherche des groupes d'un individu."; } } - - /** - * @memberof LDAP - * @summary Fonction qui retrouve la liste des membres d'un groupe. - * @desc Cette fonction utilise {@link LDAP.search} avec un dictionnaire prédéfini dans ldapConfig.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 - * @async - */ - static async getMembers(gid: string) : Promise<string[]> { - try { - return Basics.searchSingle("gr", ldapConfig.group.members, gid); - } - catch(err) { - throw "Erreur lors de la recherche des membres d'un groupe."; - } - } - - /** - * @memberof LDAP - * @summary Fonction qui retrouve la liste des admins d'un groupe. - * @desc Cette fonction utilise {@link LDAP.search} avec un dictionnaire prédéfini dans ldapConfig.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 - * @async - */ - static async getAdmins(gid: string) : Promise<string[]> { - try { - return Basics.searchSingle("gr", ldapConfig.group.admins, gid); - } - catch(err) { - throw "Erreur lors de la recherche des admins d'un groupe."; - } - } /** * @memberof LDAP @@ -300,8 +375,8 @@ export class Tools { */ static async isGroupMember(uid: string, gid: string) : Promise<boolean> { try { - let lg = await Tools.getGroups(uid); - let lm = await Tools.getMembers(gid); + let lg = await Tools.get(uid, "user", "members"); + let lm = await Tools.get(gid, "group", "members"); if (lg.includes(gid) && lm.includes(uid)) { return true; } @@ -324,8 +399,8 @@ export class Tools { */ static async isGroupAdmin(uid: string, gid: string) : Promise<boolean> { try { - let lm = await Tools.getMembers(gid); - let la = await Tools.getAdmins(gid); + let lm = await Tools.get(uid, "user", "admins"); + let la = await Tools.get(gid, "group", "admins"); if (la.includes(uid) && lm.includes(uid)) { return true; } else { return false; } }