From e4fcd212349966ba119054dccccc8b3ea90e6adb Mon Sep 17 00:00:00 2001 From: hawkspar <quentin.chevalier@polytechnique.edu> Date: Mon, 3 Dec 2018 16:04:29 +0100 Subject: [PATCH] Commit du vrai merge --- README.md | 1 - src/ldap/group.ts | 50 ++++++------------- src/ldap/user.ts | 66 ++++++------------------- src/ldap/utilities.ts | 112 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 4d2b8b0..01edf86 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,6 @@ On peut définir ces variables d'environnement, **dans l'ordre décroissant de p ... ``` - ## Panneau d'administration Il est accessible au path `/adminview/admin` ; n'importe quel path devrait rediriger dessus, ou alors vers `/adminview/login`. Les identifiants à utiliser sont ceux de Frankiz. L'authentification se fait par le LDAP Frankiz. diff --git a/src/ldap/group.ts b/src/ldap/group.ts index 2b7005a..b32643c 100644 --- a/src/ldap/group.ts +++ b/src/ldap/group.ts @@ -38,7 +38,7 @@ export class Group { /** * @summary Fonction qui renvoit toutes les infos relatives à un groupe particulier. - * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis. + * @desc Cette fonction utilise {@link Tools.genericPeek} avec l'interface {@link groupData}. * @arg {string} gid - Identifiant du groupe * @return {Promise(groupData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe ; * voir `ldap_ldapConfig.json`(..\..\ldap_ldapConfig.json) pour les clés exactes. @@ -47,17 +47,7 @@ export class Group { */ static async peek(gid: string) : Promise<groupData> { try { - let fields = []; - fields.push(ldapConfig.group.values()); - let LDAPGroupData = await LDAP.search("gr", fields, gid); - let cleanGroupData : groupData; - // Rename output - for (let uncleanKey in LDAPGroupData) { - for (let cleanKey in cleanGroupData) { - if (uncleanKey==ldapConfig.group[cleanKey]) { cleanGroupData[cleanKey] = LDAPGroupData[uncleanKey]; } - } - } - return cleanGroupData; + return Tools.genericPeek<groupData>("gr", gid); } catch(err) { throw "Erreur lors d'une recherche d'informations sur un groupe."; @@ -77,14 +67,13 @@ export class Group { */ static async search(input: string) : Promise<string[]> { try { - // Construction du filtre custom - let filter= "(|("+ldapConfig.key_id+"="+ input+")" + // On cherche la valeur exacte - "(|("+ldapConfig.key_id+"=*"+input+")" + // La valeur finale avec des trucs avant ; wildcard * - "(|("+ldapConfig.key_id+"=*"+input+"*)"+ // La valeur du milieu avec des trucs avant et après - "("+ ldapConfig.key_id+"="+ input+"*))))"; // La valeur du début avec des trucs après - - // Appel rechercheLDAP avec filtre de l'espace - return LDAP.search("gr", [ldapConfig.key_id], null, filter); + return Tools.genericSearch<groupData>("gr", { + "gid": input, + "name": "", + "type": "", + "members": [], + "admins": [] + }); } catch(err) { throw "Erreur lors de la recherche approximative d'un groupe."; @@ -134,7 +123,7 @@ export class Group { * @async * @static */ - static async delMember(uid: string, gid: string): Promise<boolean> { + 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); @@ -219,9 +208,9 @@ export class Group { * @async * @static */ - static async delAdmin(uid: string, gid: string): Promise<boolean> { + 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.delMember(uid, gid) && Group.addMember(uid,gid))) { throw "Erreur dans l'éjection/réadmission du futur admin."; } + if (!(await Group.remMember(uid, gid) && Group.addMember(uid,gid))) { throw "Erreur dans l'éjection/réadmission du futur admin."; } try { // Vérifie que l'utilisateur est bien admin (comme dans delGroupMember) let la = await Tools.getAdmins(gid); @@ -350,10 +339,10 @@ export class Group { let profil = await Group.peek(gid); // Ordre important profil[ldapConfig.group['admin']].forEach( id => { - Group.delAdmin( id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un admin d'un groupe en cours de suppression."; } }); + Group.remAdmin( id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un admin d'un groupe en cours de suppression."; } }); }); profil[ldapConfig.group['member']].forEach(id => { - Group.delMember(id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un membre."; } }); + Group.remMember(id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un membre."; } }); }); // Elimination if (!await LDAP.clear("gr",gid)) { throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes."; } @@ -375,17 +364,10 @@ export class Group { */ static async edit(data: groupData) : Promise<boolean> { try { - let gid = data['gid']; - // Récupération des anciennes données - let profil = await Group.peek(gid); - // Surcharge des champs à modifier selon data - for (let key in data) { profil[key]=data[key]; } - // Modification propre - if (!await Group.delete(gid) && await Group.create(profil)) { throw "Erreur de la destruction/recréation du groupe pour le modifier."; } - return true; + return Tools.genericEdit<groupData>("gr",data); } catch(err) { - throw "Erreur lors de l'obtention du profil d'un groupe pour le modifier."; + throw "Erreur lors de la modification d'un groupe."; } } } \ No newline at end of file diff --git a/src/ldap/user.ts b/src/ldap/user.ts index 7f1ab31..20c9a90 100644 --- a/src/ldap/user.ts +++ b/src/ldap/user.ts @@ -24,6 +24,13 @@ import {Group} from './group'; * @var {string[]} adresses - Adresse(s) * @var {string[]} mails - 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 */ @@ -40,27 +47,6 @@ export interface userData { "adresses"?: string, "mails"?: string[], "groups"?: string[], - "admins"?: string[] - //"likes"?: string[] -} - -/** - * @interface fullUserData - * @desc Contient les champs utilisateurs intéressants pour le LDAP de l'X, pas pour sigma - * @var {string} fullName - Nom complet - * @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[]} classes - Classes du LDAP, pas de sens particulier - * @var {string} id - Identifiant plus complet - * @var {string} idNum - Identifiant numérique - */ -export interface fullUserData extends userData { - "fullName"?: string, "password"?: string, "ips"?: string[], "directory"?: string, @@ -68,9 +54,8 @@ export interface fullUserData extends userData { "readPerm"?: string, "writePerm"?: string, "forlifes"?: string[], - "classes"?: string[], - "id"?: string, - "idNum"?: string + "admins"?: string[] + //"likes"?: string[] } //------------------------------------------------------------------------------------------------------------------------ @@ -171,7 +156,7 @@ export class User { * @async * @static */ - static async create(data: fullUserData): Promise<boolean> { + static async create(data: userData): Promise<boolean> { // Calcul d'un dictionnaire d'ajout let vals = {}; @@ -298,9 +283,9 @@ export class User { profil[ldapConfig.user['groups']].forEach(gid => { // Opérations effectuées par effet de bord if (Tools.isGroupAdmin(uid,gid)) { - if (!Group.delAdmin(uid, gid)) { throw "Erreur lors de la suppression des droits d'admin de l'utilisateur."; } + if (!Group.remAdmin(uid, gid)) { throw "Erreur lors de la suppression des droits d'admin de l'utilisateur."; } } - if (!Group.delMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenance à un groupe de l'utilisateur."; } + if (!Group.remMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenance à un groupe de l'utilisateur."; } }); } catch(err) { @@ -323,33 +308,12 @@ export class User { * @async * @static */ - static async edit(data : fullUserData) : Promise<boolean> { - let uid = data['uid']; - // Récupération des anciennes données - let profil = await User.genericPeek<fullUserData>(uid); + static async edit(data : userData) : Promise<boolean> { try { - // Régénération du champ manquant dans profil - let lg = await Tools.getGroups(uid); - profil['groupsIsAdmin']=[]; - lg.forEach(gid => { - Tools.isGroupAdmin(uid, gid).then(res => { - if (res) { profil['groupsIsAdmin'].push(gid); } - }); - }); - // Surcharge des champs à modifier selon data - Object.keys(data).forEach(function(key: string) { - // Some fields the user cannot change (groups and groupsIsAdmin must be changed through addGroupMember and addGroupAdmin in Admin) - if (!['readPerm','writePerm','forlifes','ips','groups','groupsIsAdmin'].includes(key)) { profil[key]=data[key]; } - }); - // Modification propre par effet de bord - if (!(await User.delete(uid) && await User.create(profil))) { - throw "Erreur dans la destruction/création du compte."; - } else { - return true; - } + return Tools.genericEdit<userData>("us",data); } catch(err) { - throw "Erreur lors de la modification des groupes où un utilisateur est admin."; + throw "Erreur lors de la modification d'un utilisateur."; } } } \ No newline at end of file diff --git a/src/ldap/utilities.ts b/src/ldap/utilities.ts index 0791636..55fb907 100644 --- a/src/ldap/utilities.ts +++ b/src/ldap/utilities.ts @@ -6,6 +6,10 @@ import {ldapConfig} from './config'; import {LDAP} from './basics'; +import { groupData } from './group'; +import { userData } from './user'; +import { Group } from './group'; +import { User } from './user'; //------------------------------------------------------------------------------------------------------------------------ // Fonctions intermédiaires TBT @@ -18,6 +22,114 @@ export class Tools { * @author hawkspar */ constructor() {} + + /** + * @summary Fonction qui renvoit toutes les infos relatives à un groupe ou un utilisateur particulier. + * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis. + * @arg {string} domain - Domaine de la recherche + * @arg {string} id - Identifiant de la feuille cherchée + * @return {Promise(T)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe ; + * voir `ldap_config.json`(..\..\ldap_config.json) pour les clés exactes. + * @static + * @async + */ + static async genericPeek<T>(domain: 'us' | 'gr', id: string) : Promise<T> { + let fields = []; + if (domain=='gr') { fields.push(ldapConfig.group.values()); } + else { fields.push(ldapConfig.user.values()); } + let LDAPData = await LDAP.search(domain, fields, id); + let cleanData : T; + // Rename output + for (let uncleanKey in LDAPData) { + for (let cleanKey in cleanData) { + if (uncleanKey==ldapConfig.group[cleanKey]) { cleanData[cleanKey] = LDAPData[uncleanKey]; } + } + } + return cleanData; + } + + + /** + * @summary Fonction qui retrouve les uid des paxs validant les critères de recherche. Autre étape vers vrai TOL (Trombino On Line). Doit être préféré à repliquer TOL + * car moins gourmande envers le LDAP (utiliser {@link peekUser} au cas par cas après pour obtenir les vraies infos). + * @desc Cette fonction utilise {@link LDAP.search} mais avec un filtre généré à la volée. Accepte des champs exacts ou incomplets pour la plupart des champs + * mais pas approximatifs et ne gère pas l'auto-complete. MEF Timeout pour des recherches trop vagues. Va crasher si un champ n'est pas dans ldapConfig. + * Utiliser trouverGroupesParTypes pour chaque champ relié à groups. + * @arg {T} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse + * comme des sous-parties compactes des valeurs renvoyées. Tous les champs ci-dessous peuvent être indifféremment des listes (par exempl pour chercher un membre + * de plusieurs groupes) ou des éléments isolés. Si un champ n'est pas pertinent, le mettre à '' ou undefined. + * @return {Promise(string[])} gids des profils qui "match" les critères proposés. + * @static + * @async + */ + static async genericSearch<T>(domain : "us"|"gr", data : T) : 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) { + if ((data[key]!= undefined) && (data[key] != '')) { // Si il y a qque chose à chercher pour ce filtre + if (!Array.isArray(data[key])) { data[key]=[data[key]]; } // Gestion d'une liste de valeurs à rechercher + // Iteration pour chaque valeur fournie par l'utilisateur + data[key].forEach(val => { + // Traduction en language LDAP + let attribute = ""; + if (domain=="us") { attribute = ldapConfig.user[key]; } + else { attribute = ldapConfig.group[key]; } + // Creation incrémentale du filtre + filter="(&"+filter+ "(|("+attribute+"="+ val+")"+ // On cherche la valeur exacte + "(|("+attribute+"=*"+val+")"+ // La valeur finale avec des trucs avant ; wildcard * (MEF la wildcart ne marche pas pour tous les attributs) + "(|("+attribute+"=*"+val+"*)"+ // La valeur du milieu avec des trucs avant et après + "("+ attribute+"="+ val+"*)))))"; // La valeur du début avec des trucs après + }); + } + } + // Appel avec filtre de l'espace + return LDAP.search(domain, [ldapConfig.key_id], null, filter); + } + + + /** + * @summary Fonction qui édite un groupe existant dans le LDAP. Très similaire à {@link User.addGroup} + * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link Admin.addGroupMember} et {@link Admin.addGroupAdmin} en godmode pour gérer les groupes du nouvel utilisateur. + * @arg {T} data - Dictionnaire des informations utilisateurs au même format que pour {@link User.addGroup} avec tous les champs optionnels... + * Sauf 'gid', qui permet de savoir quel groupe modifier et qui est donc inchangeable. On peut modifier nickname par contre. + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async genericEdit<T>(domain: "us" | "gr", data: T) : Promise<boolean> { + let id = ""; + if (domain == "us") { id=data['uid']; } + else { id=data['gid']; } + // Récupération des anciennes données + let profil = await Tools.genericPeek<T>(domain,id); + if (domain == "us") { + // Régénération du champ manquant dans profil + let lg = await Tools.getGroups(id); + profil['groupsIsAdmin']=[]; + lg.forEach(gid => { + Tools.isGroupAdmin(id, gid).then(res => { + if (res) { profil['groupsIsAdmin'].push(gid); } + }); + }); + } + // Surcharge des champs à modifier selon data + Object.keys(data).forEach(function(key: string) { + // Some fields the user cannot change + if (!['readPerm','writePerm','groups','groupsIsAdmin','members','admins'].includes(key)) { profil[key]=data[key]; } + // Specialised management of group membership and admin status + if (key=="groups") { data[key].array.forEach(gid => { Group.addMember(id, gid); }); } + if (key=="groupsIsAdmin") { data[key].array.forEach(gid => { Group.addAdmin(id, gid); }); } + if (key=="members") { data[key].array.forEach(uid => { Group.addMember(uid, id); }); } + if (key=="admins") { data[key].array.forEach(uid => { Group.addMember(uid, id); }); } + }); + // Renommage LDAP-friendly + let dirtyProfil = {}; + Object.keys(profil).forEach(function(key: string) { + if (domain=="gr") { dirtyProfil[ldapConfig.group.key]=profil[key]; } + else { dirtyProfil[ldapConfig.user.key]=profil[key]; } + }); + return LDAP.change(domain,id,"replace",dirtyProfil); + } /** * @callback changeValueCallback -- GitLab