From 99e1be282a97dcea2cfd68bed118cb8735fb92ed Mon Sep 17 00:00:00 2001 From: hawkspar <quentin.chevalier@polytechnique.edu> Date: Sat, 20 Oct 2018 12:30:51 +0200 Subject: [PATCH] Refacto de l'infini --- ldap_config.json | 5 +- src/graphql/typeDefs/objects_ldap.graphql | 2 +- src/ldap/basics.ts | 3 +- src/ldap/group.ts | 22 ++--- src/ldap/user.ts | 114 +++++++++++++--------- src/ldap/utilities.ts | 47 ++++----- 6 files changed, 102 insertions(+), 91 deletions(-) diff --git a/ldap_config.json b/ldap_config.json index 60790a9..dbb5dc3 100644 --- a/ldap_config.json +++ b/ldap_config.json @@ -20,9 +20,8 @@ "nationality": "country", "promotion": "brPromo", "phone": "telephoneNumber", - "adress": "brRoom", + "adresses": "brRoom", "id": "uidNumber", - "sport": "brMemberOf", "password": "userPassword", "idNum": "gidNumber", "directory": "homeDirectory", @@ -33,8 +32,6 @@ "ips": "brIP", "forlifes": "brAlias", "groups": "brMemberOf", - "schools": "brMemberOf", - "courses": "brMemberOf", "classes": "objectClass" }, "comment_4": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les groupes", diff --git a/src/graphql/typeDefs/objects_ldap.graphql b/src/graphql/typeDefs/objects_ldap.graphql index 577b693..3efc076 100644 --- a/src/graphql/typeDefs/objects_ldap.graphql +++ b/src/graphql/typeDefs/objects_ldap.graphql @@ -19,7 +19,7 @@ type User { likes: [Group] # A terme rajouter aussi admin # Adresse(s) de l'utilisateur. - address: [String] + addresses: [String] # Promotion promotion: String photo: String diff --git a/src/ldap/basics.ts b/src/ldap/basics.ts index 85cf2d6..e2ab6e0 100644 --- a/src/ldap/basics.ts +++ b/src/ldap/basics.ts @@ -116,10 +116,11 @@ export class LDAP { }); // Si la recherche renvoie une erreur, on renvoit res.on('error', resErr => { throw resErr; }); - // Si la recherche est finie on se déconnecte et on renvoit la liste + // Si la recherche est finie on se déconnecte res.on('end', res => { LDAP.unbind(); }); } }); + // On renvoit le résultat return vals; } diff --git a/src/ldap/group.ts b/src/ldap/group.ts index ab04387..2b7005a 100644 --- a/src/ldap/group.ts +++ b/src/ldap/group.ts @@ -3,9 +3,9 @@ * @author hawkspar */ -import { ldapConfig } from './config'; +import {ldapConfig} from './config'; import {LDAP} from './basics'; -import {Tests} from './utilities'; +import {Tools} from './utilities'; /** * @interface groupData @@ -94,7 +94,7 @@ export class Group { 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 Tests.getMembers(gid); + let lm = await Tools.getMembers(gid); if (!lm.includes(uid)) { let vals = {}; vals[ldapConfig.group.members] = uid; @@ -109,7 +109,7 @@ export class Group { } try { // Vérifie que l'utilisateur est pas déjà membre pour users - let lg = await Tests.getGroups(uid); + let lg = await Tools.getGroups(uid); if (!lg.includes(gid)) { let vals2 = {}; vals2[ldapConfig.user.groups] = gid; @@ -137,7 +137,7 @@ export class Group { static async delMember(uid: string, gid: string): Promise<boolean> { try { // Vérifie que l'utilisateur est pas déjà viré pour groupes - let lm = await Tests.getMembers(gid); + let lm = await Tools.getMembers(gid); if (lm.includes(uid)) { // Supprime tous les utilisateurs if (!await LDAP.change("gr", gid, "del", ldapConfig.group.members)) { @@ -157,7 +157,7 @@ export class Group { throw "Erreur pour obtenir une liste de membres d'un groupe pour supprimer un membre du groupe."; } try { - let lg = await Tests.getGroups(uid); + 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 @@ -194,7 +194,7 @@ export class Group { // 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 Tests.getAdmins(gid); + let la = await Tools.getAdmins(gid); if (!la.includes(uid)) { // Finalement modification, uniquement dans groups let vals = {}; @@ -224,7 +224,7 @@ export class Group { if (!(await Group.delMember(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 Tests.getAdmins(gid); + let la = await Tools.getAdmins(gid); if (la.includes(uid)) { // Supprime tous les administrateurs if (!await LDAP.change("gr", gid, "del", ldapConfig.group.admins)) { throw "Erreur dans la suppression de tous les admins pour en supprimer un."; } @@ -257,7 +257,7 @@ export class Group { // gid de base généré à partir du nom standardisé, pas à partir de l'entrée 'gid' ! try { - Tests.generateReadableId(data['name']).then(id => { + Tools.generateReadableId(data['name']).then(id => { vals[ldapConfig.key_id]=id; vals[ldapConfig.group['name']]=id; }); @@ -278,7 +278,7 @@ export class Group { // Certains champs nécessitent de petits calculs let vals2={}; - // Encore un cahmp redondant + // Encore un champ redondant vals2[ldapConfig.group['adress']] = gid; // ?! @@ -286,7 +286,7 @@ export class Group { // Génération id aléatoire et test contre le LDAP try { - Tests.generateId(ldapConfig.group["idNumber"], "gr").then(id => { vals2[ldapConfig.group['idNumber']]=id; }); + Tools.generateId(ldapConfig.group["idNumber"], "gr").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."; diff --git a/src/ldap/user.ts b/src/ldap/user.ts index 3652c9e..7f1ab31 100644 --- a/src/ldap/user.ts +++ b/src/ldap/user.ts @@ -4,60 +4,73 @@ * @author hawkspar */ -import { ldapConfig } from './config'; +import {ldapConfig} from './config'; import {LDAP} from './basics'; -import {Tests} from './utilities'; +import {Tools} from './utilities'; 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[]} mail - Adresse(s) courriel - * @var {string[]} adress - Adresses - * @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 - * @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[]} groupsIsAdmin - Liste des gid dont le pax est admin ; supposé sous-liste de groups + * @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[]} 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 interface userData { - "uid": string, - "photo"?: string, + "uid"?: string, "givenName"?: string, "lastName"?: string, - "fullName"?: string, - "cleanFullName": string, "nickname"?: string, + "photo"?: string, "birthdate"?: string, - "nationality"?: string, - "promotion": string, + //"nationality"?: string, + "promotion"?: string, "phone"?: string, - "adress"?: string, - "id"?: string, - "sport"?: string, + "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, - "idNum"?: string, + "ips"?: string[], "directory"?: string, "login"?: string, "readPerm"?: string, "writePerm"?: string, - "mails"?: string[], - "ips"?: string[], "forlifes"?: string[], - "groups": string[], - "groupsIsAdmin": string[], - "schools"?: string[], - "courses"?: string[], - "classes"?: string[] + "classes"?: string[], + "id"?: string, + "idNum"?: string } //------------------------------------------------------------------------------------------------------------------------ @@ -68,24 +81,25 @@ export class User { /** * @class Cette classe est une des deux classes exportables permettant de faire des opérations sur les utilisateurs. * @summary Constructeur vide. - */ + */ constructor() {} - + /** - * @summary Fonction qui renvoit toutes les infos relatives à un utilisateur particulier. + * @summary Fonction qui renvoit certaines informations relatives à un utilisateur particulier. * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis. * @arg {string} uid - Identifiant de l'utilisateur - * @return {Promise(userData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet de l'utilisateur ; - * voir `ldap_ldapConfig.json`(..\..\ldap_ldapConfig.json) pour les clés exactes. + * @return {Promise(T)} Informations recueillies ; renvoie une partie du profil de l'utilisateur selon le format choisi. + * Voir `ldap_ldapConfig.json`(..\..\ldap_ldapConfig.json) pour les clés exactes. * @static * @async + * @private */ - static async peek(uid: string) : Promise<userData> { + private static async genericPeek<T>(uid: string) : Promise<T> { try { let fields = []; fields.push(ldapConfig.user.values()); let LDAPUserData = await LDAP.search("us", fields, uid); - let cleanUserData : userData; + let cleanUserData : T; // Rename output for (let uncleanKey in LDAPUserData) { for (let cleanKey in cleanUserData) { @@ -98,6 +112,16 @@ export class User { throw "Erreur lors d'une recherche d'informations sur un individu."; } } + + /** + * @summary Fonction qui renvoit les infos de base relatives à un utilisateur particulier. + * @desc Cette fonction utilise {@link User.genericPeek} avec l'interface {@link userData}. + * @arg {string} uid - Identifiant de l'utilisateur + * @return {Promise(userData)} Informations recueillies. + * @static + * @async + */ + static async peek(uid: string) : Promise<userData> { return User.genericPeek<userData>(uid); } /** * @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 @@ -142,19 +166,19 @@ export class User { /** * @summary Fonction qui créé un nouvel utilisateur dans le LDAP. * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link User.addGroupMember} et {@link Admin.addGroupAdmin} 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. + * @arg {fullUserData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis. * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon * @async * @static */ - static async create(data: userData): Promise<boolean> { + static async create(data: fullUserData): Promise<boolean> { // Calcul d'un dictionnaire d'ajout let vals = {}; // uid de base généré à partir de nom et prénom, plus potentiellement promo et un offset // MEF mélange de Promise et de fonction standard try { - Tests.generateUid(data['givenName'],data['lastName'],data['promotion']).then(id => { vals[ldapConfig.key_id]=id; } ); + Tools.generateUid(data['givenName'],data['lastName'],data['promotion']).then(id => { vals[ldapConfig.key_id]=id; } ); } catch(err) { throw "Erreur lors de la génération d'un hruid pour un nouvel utilisateur."; @@ -201,7 +225,7 @@ export class User { } try { // Génération id aléatoire unique - vals3[ldapConfig.user['id']]= await Tests.generateId(ldapConfig.user['id'], "us"); + vals3[ldapConfig.user['id']]= await Tools.generateId(ldapConfig.user['id'], "us"); } catch(err) { throw "Erreur lors de la génération d'un id numérique pour un nouvel utilisateur."; @@ -273,7 +297,7 @@ export class User { let profil = await User.peek(uid); profil[ldapConfig.user['groups']].forEach(gid => { // Opérations effectuées par effet de bord - if (Tests.isGroupAdmin(uid,gid)) { + 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.delMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenance à un groupe de l'utilisateur."; } @@ -292,23 +316,23 @@ export class User { * @desc Appelle simplement {@link creerUtilisateur} et {@link supprimerUtilisateur} en godmode, plus {@link renseignerSurUtilisateur} pour les champs non fournis. * Ce choix a pour conséquence que l'ordre du dictionnaire de correspondance dans ldap_ldapConfig est important. * Une version "nerfée" de cette fonction est envisageable ; elle donne bcp de pouvoir à l'utilisateur. - * @arg {userData} data - Dictionnaire des informations utilisateurs au même format que pour {@link creerUtilisateur} avec tous les champs optionnels sauf 'uid', + * @arg {fullUserData} data - Dictionnaire des informations utilisateurs au même format que pour {@link creerUtilisateur} avec tous les champs optionnels sauf 'uid', * qui permet de savoir qui modifier. Attention toutes les clés de cette entrée seront modifiées dans le LDAP ; les nouveaux résultats écrasant les précédents, * sauf 'readPerm','writePerm', 'forlifes','ips','groups' et 'groupsIsAdmin' qui sont censurés pour cette fonction). * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon * @async * @static */ - static async edit(data : userData) : Promise<boolean> { + static async edit(data : fullUserData) : Promise<boolean> { let uid = data['uid']; // Récupération des anciennes données - let profil = await User.peek(uid); + let profil = await User.genericPeek<fullUserData>(uid); try { // Régénération du champ manquant dans profil - let lg = await Tests.getGroups(uid); + let lg = await Tools.getGroups(uid); profil['groupsIsAdmin']=[]; lg.forEach(gid => { - Tests.isGroupAdmin(uid, gid).then(res => { + Tools.isGroupAdmin(uid, gid).then(res => { if (res) { profil['groupsIsAdmin'].push(gid); } }); }); diff --git a/src/ldap/utilities.ts b/src/ldap/utilities.ts index f336794..0791636 100644 --- a/src/ldap/utilities.ts +++ b/src/ldap/utilities.ts @@ -6,15 +6,14 @@ import {ldapConfig} from './config'; import {LDAP} from './basics'; -import {Group} from './group'; //------------------------------------------------------------------------------------------------------------------------ // Fonctions intermédiaires TBT //------------------------------------------------------------------------------------------------------------------------ -export class Tests { +export class Tools { /** - * @class Cette classe contient des fonctions de test d'unicité trop puissantes pour être exportées tel quel. + * @class Cette classe contient des fonctions intermédiaires qui ne sont pas destinées à être utilisées dans les resolvers. * @summary Constructeur vide. * @author hawkspar */ @@ -49,7 +48,7 @@ export class Tests { // On renvoit la valeur si elle est bien unique else if (matches.length==0) { return value; } // Sinon, on tente de nouveau notre chance avec la valeur suivante - else { return Tests.ensureUnique(changeValue(value, n+1), attribute, domain, changeValue, n+1); } + else { return Tools.ensureUnique(changeValue(value, n+1), attribute, domain, changeValue, n+1); } }); } catch(err) { @@ -59,7 +58,7 @@ export class Tests { /** * @summary Cette fonction génère un uid standard, puis le fait évoluer jusqu'à ce qu'il soit unique. - * @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). + * @desc Limité à un appel à {@link Tools.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). * @param {string} givenName - Prénom * @param {string} lastName - Nom * @param {string} promotion - Année de promotion @@ -70,7 +69,7 @@ export class Tests { static async generateUid(givenName: string, lastName: string, promotion: string) : Promise<string> { try { // normalize et lowerCase standardisent le format - return this.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, "us", (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... @@ -84,7 +83,7 @@ export class Tests { /** * @summary Cette fonction génère un id lisible, puis le fait évoluer jusqu'à ce qu'il soit unique. - * @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). + * @desc Limité à un appel à {@link Tools.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 @@ -93,7 +92,7 @@ export class Tests { static async generateReadableId(name: string) : Promise<string> { try { // normalize et lowerCase standardisent le format - return this.ensureUnique(name.toLowerCase().normalize('UFD'), ldapConfig.key_id, "gr", (id: string, n: number) => { + return Tools.ensureUnique(name.toLowerCase().normalize('UFD'), ldapConfig.key_id, "gr", (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; @@ -114,7 +113,7 @@ export class Tests { */ static async generateId(attribut: string, domain: "gr"|"us") : Promise<string> { try { - return this.ensureUnique("0", attribut, domain, (id,n) => { return Math.floor((Math.random() * 100000) + 1).toString(); }); + return Tools.ensureUnique("0", attribut, domain, (id,n) => { return Math.floor((Math.random() * 100000) + 1).toString(); }); } catch(err) { throw "Erreur lors de l'assurance de l'unicité d'un unique identifier numérique."; @@ -129,7 +128,7 @@ export class Tests { * @static * @async */ - static async getGroups(uid: string) { + static async getGroups(uid: string) : Promise<string[]> { try { return LDAP.search("us", [ldapConfig.user.groups], uid)[0]; } @@ -142,11 +141,11 @@ export class Tests { * @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) + * @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) { + static async getMembers(gid: string) : Promise<string[]> { try { return LDAP.search("gr", [ldapConfig.group.members], gid)[0]; } @@ -163,7 +162,7 @@ export class Tests { * @static * @async */ - static async getAdmins(gid: string) { + static async getAdmins(gid: string) : Promise<string[]> { try { return LDAP.search("gr", [ldapConfig.group.admins], gid)[0]; } @@ -181,18 +180,13 @@ export class Tests { * @static * @async */ - static async isGroupMember(uid: string, gid: string) { + static async isGroupMember(uid: string, gid: string) : Promise<boolean> { try { - let lg = await Tests.getGroups(uid); - let lm = await Tests.getMembers(gid); + let lg = await Tools.getGroups(uid); + let lm = await Tools.getMembers(gid); if (lg.includes(gid) && lm.includes(uid)) { return true; } - // Réalignement forcé - else if (lg.includes(gid) || lm.includes(uid)) { - Group.addMember(uid, gid); - return true; - } else { return false; } } catch(err) { @@ -209,16 +203,11 @@ export class Tests { * @static * @async */ - static async isGroupAdmin(uid: string, gid: string) { + static async isGroupAdmin(uid: string, gid: string) : Promise<boolean> { try { - let lm = await Tests.getMembers(gid); - let la = await Tests.getAdmins(gid); + let lm = await Tools.getMembers(gid); + let la = await Tools.getAdmins(gid); if (la.includes(uid) && lm.includes(uid)) { return true; } - // Réalignement forcé - else if (la.includes(uid)) { - Group.addAdmin(uid, gid); - return true; - } else { return false; } } catch(err) { -- GitLab