/** * @file Ce fichier regroupe les différentes classes avec différents utilisateurs. Ces classes sont dédiées à être exportées directement pour être utilisées par le solver. * Le découpage par fichier est arbitraire mais permet de regrouper certaines classes proches. * @author hawkspar */ import LDAP from './basics.js'; import {SmartSearch, Tests} from './utilities.js'; import {Admin, SuperAdmin} from './admins.js'; // Essentiels pour le fichier de config import path from 'path'; import fs from 'fs'; // Point central ; tous les champs de la BDD sont 'cachés' dans config.json et pas visibles directement var configPath = path.resolve('./','ldap_config.json'); var config = JSON.parse(fs.readFileSync(configPath, 'utf8')); //------------------------------------------------------------------------------------------------------------------------ // Classes à exporter TBT //------------------------------------------------------------------------------------------------------------------------ export 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. */ constructor() {} //------------------------------------------------------------------------------------------------------------------------ // Fonctions de lecture //------------------------------------------------------------------------------------------------------------------------ /** * @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 * @static @async */ static async getGroups(uid) { try { return LDAP.search(config.key_id+uid+config.dn_users, config.user.groups)[0]; } catch(err) { throw "Erreur lors de la recherche des groupes d'un individu."; } } /** * @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 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 @async */ static async getMembers(gid) { try { return LDAP.search(config.key_id+gid+config.dn_users, config.group.member)[0]; } catch(err) { throw "Erreur lors de la recherche des membres d'un groupe."; } } /** * @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 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 @async */ static async getAdmins(gid) { try { return LDAP.search(config.key_id+gid+config.dn_users, config.group.admin)[0]; } catch(err) { throw "Erreur lors de la recherche des admins d'un groupe."; } } /** * @summary Cette fonction teste si un utilisateur est membre d'un groupe. * @desc Utilise les méthodes statiques {@link open.getGroups} et {@link open.getMembers} * @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 @async */ static async isGroupMember(uid, gid) { try { let lg = await this.getGroups(uid); let lm = await this.getMembers(gid); if (lg.includes(gid) && lm.includes(uid)) { return true; } } catch(err) { throw "Erreur lors du test d'appartenance à un groupe."; } } /** * @summary Cette fonction teste si un utilisateur est admin d'un groupe. * @desc Utilise la méthode statique {@link Open.getAdmins} * @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 @async */ static async isGroupAdmin(uid, gid) { try { let la = await this.getAdmins(gid); if (la.includes(uid)) { return true; } } catch(err) { throw "Erreur lors du test d'appartenance au bureau d'administration un groupe."; } } /** * @summary Fonction qui renvoit toutes les infos 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(Object[])} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet de l'utilisateur ; * voir `ldap_config.json`(..\..\ldap_config.json) pour les clés exactes. * @static */ static peekUser(uid) { try { return LDAP.search(config.key_id+uid+config.dn_users, config.user.profil); } catch(err) { throw "Erreur lors d'une recherche d'informations sur un individu."; } } /** * @summary Fonction qui renvoit toutes les infos relatives à un groupe particulier. * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis. * @arg {string} gid - Identifiant du groupe * @return {Promise(Object[])} 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 */ static peekGroup(gid) { try { return LDAP.search(config.key_id+gid+config.dn_groups, config.group.profil); } catch(err) { throw "Erreur lors d'une recherche d'informations sur un groupe."; } } //------------------------------------------------------------------------------------------------------------------------ // Fonctions de recherche //------------------------------------------------------------------------------------------------------------------------ /** * @summary Fonction qui retrouve le groupe qui ressemblent à l'input et qui correspond au type fourni. Etape 0 vers un vrai TOL (Trombino On Line). * @desc Cette fonction utilise {@link SmartSearch.groups}. * @arg {string} input - String entré par l'utilisateur qui ressemble au nom du groupe. * @return {Promise(string[])} Liste des gid dont le nom ressemble à l'input. * @static @async */ static async findGroups(input) { try { // Trucs intelligents faits dans ./utilities return SmartSearch.groups(input, [config.key_id, config.group.type]); } catch(err) { throw "Erreur lors de la recherche approximative d'un groupe."; } /** 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; */ } /** * @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é à repliquerTOL * 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 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 @async */ static async findUsers(data) { try { return SmartSearch.users(data, config.key_id); } catch(err) { throw "Erreur lors de la recherche approximative d'un utilisateur."; } } } export 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}. * @async */ constructor(user) { super(); try { LDAP.bind(user); } catch(err) { throw "Erreur lors de la connexion à un compte utilisateur."; } } //------------------------------------------------------------------------------------------------------------------------ // Fonction de création TBT //------------------------------------------------------------------------------------------------------------------------ /** * @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 Admin.addMemberGroup} et {@link Admin.addAdminGroup} * pour gérer les groupes du nouvel utilisateur. Attention une manip FOIREUSE est cachée dedans. * @arg {Object} data - Dictionnaire des informations utilisateurs (voir détail des champs dans config.json) * @arg {string} data[name] - Nom du groupe * @arg {string} data[ns] - Statut du groupe ; 'binet' ou 'free', càd ouvert à tous * @arg {string[]} data[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 */ async addGroup(data) { // Calcul d'un dictionnaire d'ajout let vals = {}; // uid de base généré à partir du nom standardisé try { Tests.generateReadableId(data['name']).then(id => { vals[config.group['name']]=id; }); } catch(err) { throw "Erreur lors de la génération d'un hruid pour créer un nouveau groupe."; } // Ecriture de toutes les valeurs directement inscrites dans le LDAP (in pour input) config.group.direct_input.forEach(key_att => vals[config.group[key_att]]=data[key_att]); // Appel à la fonction de base 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']; // ?! vals2[config.user['password']] = ''; // Génération id aléatoire et test contre le LDAP try { Tests.generateId(config.groups["idNumber"], config.dn_groups).then(id => { vals2[config.group['idNumber']]=id; }); } catch(err) { throw "Erreur lors de la génération d'un id numérique pour créer un nouveau groupe."; } // 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']] = '!*'; // 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 "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]).then(res => { if (!res) { throw "Erreur de l'ajout d'un membre au groupe."; } }); }); data['admins'].forEach(uid => { Admin.addGroupAdmin( uid, vals[config.key_att]).then(res => { if (!res) { throw "Erreur de l'ajout d'un admin au groupe."; } }); }); return true; } //------------------------------------------------------------------------------------------------------------------------ // Fonctions d'édition TBT //------------------------------------------------------------------------------------------------------------------------ /** * @summary Fonction qui édite un utilisateur existant dans le LDAP. Très similaire à {@link creerUtilisateur} * @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_config est important. Une version "nerfée" de cette fonction est envisageable ; elle donne bcp de pouvoir à l'utilisateur. * @arg {string} uid - Utilisateur à modifier (le plus souvent le même, mais root possible) * @arg {Object} data - Dictionnaire des informations utilisateurs au même format que pour {@link creerUtilisateur} avec tous les champs optionnels ; * Attention toutes les clés de cette entrée seront modifiées dans le LDAP ; 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 */ async editUser(uid, data) { // Récupération des anciennes données 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 try { 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 => { // 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]; } }); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // TBM : rajouter god passwd. Moche mais bonne façon de faire // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // 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; } } catch(err) { throw "Erreur lors de la modification des groupes où un utilisateur est admin."; } } destr() { LDAP.unbind(); } }