diff --git a/ldap_config.json b/ldap_config.json index 0724fb11ec89ab1148b289c0596b6ead4137a027..d18f8313f487619df82d05f9eaae182db0e1ca91 100644 --- a/ldap_config.json +++ b/ldap_config.json @@ -9,52 +9,55 @@ "comment_3": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les utilisateurs", "user": { - "direct_input": ["givenName","lastName","birthdate", "promotion", "mail","phone","photo","adress"], - "multiple_input": ["ips","forlifes"], - "profil": ["jpegPhoto","displayName","givenName", "sn", "brBirthdate", "brPromo","telephoneNumber","mail","brRoom","brIP","brMemberOf"], - "photo": "jpegPhoto", - "givenName": "givenName", - "lastName": "sn", - "nickname": "displayName", - "birthdate": "brBirthdate", - "nationality": "country", - "promotion": "brPromo", - "phone": "telephoneNumber", - "mail": "mail", - "adress": "brRoom", - "ip": "brIP", - "id": "uidNumber", - "sport": "brMemberOf", - "password": "userPassword", - "forlifes": "brAlias", - "idNum": "gidNumber", - "directory": "homeDirectory", - "readPerm": "brNewsReadAccess", - "writePerm": "brNewsPostAccess", - "fullName": "cn", - "login": "loginShell", - "groups": "brMemberOf", - "school": "brMemberOf", - "course": "brMemberOf", - "cleanFullName": "gecos", - "class": "objectClass" + "single": { + "photo": "jpegPhoto", + "givenName": "givenName", + "lastName": "sn", + "fullName": "cn", + "cleanFullName": "gecos", + "nickname": "displayName", + "birthdate": "brBirthdate", + "nationality": "country", + "promotion": "brPromo", + "phone": "telephoneNumber", + "adress": "brRoom", + "id": "uidNumber", + "sport": "brMemberOf", + "password": "userPassword", + "idNum": "gidNumber", + "directory": "homeDirectory", + "login": "loginShell", + "readPerm": "brNewsReadAccess", + "writePerm": "brNewsPostAccess" + }, + "multiple": { + "mail": "mail", + "ip": "brIP", + "forlifes": "brAlias", + "groups": "brMemberOf", + "school": "brMemberOf", + "course": "brMemberOf", + "class": "objectClass" + } }, "comment_4": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les groupes", "group": { - "direct_input": ["name","type"], - "profil": ["cn","restrictedMemberUid","memberUid", "brNS"], - "name": "cn", - "member": "restrictedMemberUid", - "admin": "memberUid", - "type": "brNS", - "idNumber": "uidNumber", - "idNumber2": "gidNumber", - "password": "userPassword", - "login": "loginShell", - "directory": "homeDirectory", - "cleanFullName": "gecos", - "readPerm": "brNewsReadAccess", - "writePerm": "brNewsPostAccess" + "single": { + "name": "cn", + "nickname": "brAlias", + "type": "brNS", + "idNumber": "uidNumber", + "idNumber2": "gidNumber", + "login": "loginShell", + "password": "userPassword", + "directory": "homeDirectory", + "cleanFullName": "gecos" + }, + "multiple": { + "member": "restrictedMemberUid", + "admin": "memberUid", + "class": "objectClass" + } }, "sessionSecret":"ozyNMHdT,WFTu|t" } \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index d4c9041db70ee44a42d2fa63e4565b4faac39e7b..ec374e4a2df6b26266c534c0f6833f4ced6bc1f2 100644 --- a/src/app.ts +++ b/src/app.ts @@ -32,7 +32,7 @@ import morgan from 'morgan'; // packages pour pouvoir importer depuis des fichiers de config import path from 'path'; -import { ldapConfig, credentialsLdapConfig } from './ldap/config'; +import {ldapConfig, credentialsLdapConfig} from './ldap/config'; const { dn, passwd } = credentialsLdapConfig; diff --git a/src/ldap/admins.ts b/src/ldap/admins.ts index 0fc7b87fcedf80447bf2624cec06b7ff7658949a..4b08db197a27ca14b23cc5ee90993d955c593960 100644 --- a/src/ldap/admins.ts +++ b/src/ldap/admins.ts @@ -7,24 +7,7 @@ import {ldapConfig} from './config'; import {LDAP} from './basics'; import {Tests} from './utilities'; -import { Open, User, userData, groupData } from './users'; - -/** - * @interface userFullData - * @desc Interface avec toutes les données d'un utilisateur. Etend {@link userData}. - * @var {string} password - Mot de passe généré en amont - * @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 - */ -export interface userFullData extends userData { - password: string, - readPerm: string, - writePerm: string, - forlifes: string[], - groupsIsAdmin: string[] -} +import {Open, User, userData, groupData} from './users'; export class Admin extends User { /** @@ -48,7 +31,7 @@ export class Admin extends User { * @async * @static */ - static async addGroupMember(uid: string, gid: string) { + static async addGroupMember(uid: string, gid: string) : Promise<boolean> { try { // Vérifie que l'utilisateur est pas déjà membre pour groupes let lm = await Open.getMembers(gid); @@ -91,7 +74,7 @@ export class Admin extends User { * @async * @static */ - static async delGroupMember(uid: string, gid: string) { + static async delGroupMember(uid: string, gid: string): Promise<boolean> { try { // Vérifie que l'utilisateur est pas déjà viré pour groupes let lm = await Open.getMembers(gid); @@ -147,7 +130,7 @@ export class Admin extends User { * @async * @static */ - static async addGroupAdmin(uid: string, gid: string){ + static async addGroupAdmin(uid: string, gid: string): Promise<boolean> { // Ajoute le membre au groupe avant d'en faire un admin if (!await Admin.addGroupMember(uid,gid)) { throw "Erreur lors de l'ajout du futur admin en tant que membre."; } try { @@ -176,7 +159,7 @@ export class Admin extends User { * @async * @static */ - static async delGroupAdmin(uid: string, gid: string) { + static async delGroupAdmin(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 Admin.delGroupMember(uid, gid)&&Admin.addGroupMember(uid,gid))) { throw "Erreur dans l'éjection/réadmission du futur admin."; } try { @@ -212,7 +195,7 @@ export class Admin extends User { * @async * @static */ - static async editGroup(gid: string, data: groupData) { + static async editGroup(gid: string, data: groupData) : Promise<boolean> { try { // Récupération des anciennes données let profil = await Open.peekGroup(gid); @@ -249,7 +232,7 @@ export class Admin extends User { * @async * @static */ - static async delGroup(gid) { + static async delGroup(gid): Promise<boolean> { try { // Gestion des membres et administrateurs d'abord let profil = await Open.peekGroup(gid); @@ -285,12 +268,12 @@ export class Supervisor extends Admin { /** * @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 {userFullData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis. + * @arg {userData} 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 addUser(data: userFullData) { + static async addUser(data: userData): Promise<boolean> { // Calcul d'un dictionnaire d'ajout let vals = {}; @@ -303,20 +286,22 @@ export class Supervisor extends Admin { throw "Erreur lors de la génération d'un hruid pour un nouvel utilisateur."; } + let uid = vals[ldapConfig.key_id]; + // Ecriture de toutes les valeurs directement inscrites dans le LDAP (in pour input) // Génère une erreur si un champ n'est pas rempli - ldapConfig.user.direct_input.forEach(key_att => vals[ldapConfig.user[key_att]]=data[key_att]); + ldapConfig.user.single.forEach(key_att => vals[ldapConfig.user.single[key_att]]=data[key_att]); // Appel à la fonction de base - if (!await LDAP.add(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+","+ldapConfig.dn_users, vals)) { throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; } + if (!await LDAP.add(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, vals)) { throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; } // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples - ldapConfig.user.multiple_input.forEach(key_att => { + ldapConfig.user.multiple.forEach(key_att => { // On rajoute chaque valeur en entrée data[key_att].forEach(val => { let vals2 = {}; - vals2[ldapConfig.user[key_att]]=val; - LDAP.change(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+","+ldapConfig.dn_users, "add", vals2).then(res => { + vals2[ldapConfig.user.multiple[key_att]]=val; + LDAP.change(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, "add", vals2).then(res => { if (!res) { throw "Erreur lors de l'ajout d'une valeur pour un champ à valeurs multiples à la feuille du nouvel utilisateur."; } }); }); @@ -326,19 +311,19 @@ export class Supervisor extends Admin { let vals3={}; // Création d'un nom complet lisible - vals3[ldapConfig.user['fullName']]=data['givenName']+' '+data['lastName'].toUpperCase(); + vals3[ldapConfig.user.single['fullName']]=data['givenName']+' '+data['lastName'].toUpperCase(); // ldapConfiguration du mot de passe utilisateur // Le préfixe {CRYPT} signifie que le mdp est hashé dans OpenLDAP voir : https://www.openldap.org/doc/admin24/security.html - vals3[ldapConfig.user['password']] = "{CRYPT}"+data['password']; + vals3[ldapConfig.user.single['password']] = "{CRYPT}"+data['password']; // Ecriture d'un surnom s'il y a lieu if ((data['nickname']!=undefined) && (data['nickname']!='')) { - vals3[ldapConfig.user['nickname']]=data['nickname']; + vals3[ldapConfig.user.single['nickname']]=data['nickname']; } try { // Génération id aléatoire unique - vals3[ldapConfig.user['id']]= await Tests.generateId(ldapConfig.user['id'], ldapConfig.dn_users); + vals3[ldapConfig.user.single['id']]= await Tests.generateId(ldapConfig.user.single['id'], ldapConfig.dn_users); } catch(err) { throw "Erreur lors de la génération d'un id numérique pour un nouvel utilisateur."; @@ -348,42 +333,42 @@ export class Supervisor extends Admin { vals3[ldapConfig.user['directory']] = '/hosting/users/' + data['givenName'][0]; // Code root - vals3[ldapConfig.user['cleanFullName']]=data['fullName'].replace(':', ';').toLowerCase().normalize('UFD'); + vals3[ldapConfig.user.single['cleanFullName']]=data['fullName'].replace(':', ';').toLowerCase().normalize('UFD'); // Adressage root - if (data['groups'].includes("on_platal")) { vals3[ldapConfig.user['login']] = "/bin/bash"; } - else { vals3[ldapConfig.user['login']] = "/sbin/nologin"; } + if (data['groups'].includes("on_platal")) { vals3[ldapConfig.user.single['login']] = "/bin/bash"; } + else { vals3[ldapConfig.user.single['login']] = "/sbin/nologin"; } // Permissions BR - vals3[ldapConfig.user['readPerm']] = 'br.*,public.*'; - if (data['readPerm'].length>0) { vals3[ldapConfig.user['readPerm']] += ',' + data['readPerm']; } - vals3[ldapConfig.user['writePerm']] = 'br.*,!br.blague-du-jour,public.*,!br.campagnekes'; - if (data['writePerm'].length>0) { vals3[ldapConfig.user['readPerm']] += ',' + data['writePerm']; } + vals3[ldapConfig.user.single['readPerm']] = 'br.*,public.*'; + if (data['readPerm'].length>0) { vals3[ldapConfig.user.single['readPerm']] += ',' + data['readPerm']; } + vals3[ldapConfig.user.single['writePerm']] = 'br.*,!br.blague-du-jour,public.*,!br.campagnekes'; + if (data['writePerm'].length>0) { vals3[ldapConfig.user.single['readPerm']] += ',' + data['writePerm']; } // Valeur nécessaire ASKIP mais inutile - vals3[ldapConfig.user['idNum']] ='5000'; + vals3[ldapConfig.user.single['idNum']] ='5000'; // Inscription des valeurs calculées - if (!await LDAP.change(ldapConfig.key_id+"="+vals[ldapConfig.user['hruid']]+","+ldapConfig.dn_users, "add", vals3)) { + if (!await LDAP.change(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, "add", vals3)) { throw "Erreur lors de l'ajout des valeurs calculées à la feuille du nouvel utilisateur."; } ["posixAccount", "shadowAccount", "inetOrgPerson", "brAccount"].forEach(cst => { let val3={}; - vals3[ldapConfig.user['class']]=cst; - LDAP.change(ldapConfig.key_id+"="+vals[ldapConfig.user['hruid']]+","+ldapConfig.dn_users, "add", vals3).then(res => { + vals3[ldapConfig.user.multiple['class']]=cst; + LDAP.change(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, "add", vals3).then(res => { if (!res) { throw "Erreur lors de l'ajout d'une valeur constante à la feuille du nouvel utilisateur."; } }); }); // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble data['groupsIsMember'].forEach(gid => { - Admin.addGroupMember(vals[ldapConfig.key_id], gid).then(res => { + Admin.addGroupMember(uid, gid).then(res => { if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe."; } }); }); data['groupsIsAdmin'].forEach(gid => { - Admin.addGroupAdmin( vals[ldapConfig.key_id], gid).then(res => { + Admin.addGroupAdmin(uid, gid).then(res => { if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe en tant qu'admin."; } }); }); @@ -404,15 +389,15 @@ export class Supervisor extends Admin { * @async * @static */ - static async delUser(uid: string) { + static async delUser(uid: string): Promise<boolean> { try { // Gestion des groupes d'abord let profil = await Open.peekUser(uid); - profil[ldapConfig.user['groups']].forEach(gid => { + profil[ldapConfig.user.multiple['groups']].forEach(gid => { if (Open.isGroupAdmin(uid,gid)) { if (!Admin.delGroupAdmin(uid, gid)) { throw "Erreur lors de la suppression des droits d'admin de l'utilisateur."; } } - if (!Admin.delGroupMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenace à un groupe de l'utilisateur."; } + if (!Admin.delGroupMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenance à un groupe de l'utilisateur."; } }); } catch(err) { @@ -420,5 +405,6 @@ export class Supervisor extends Admin { } // Elimination if (!LDAP.clear(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users)) { throw "Erreur lors de la suppression de l'utilisateur."; } + return true; } } \ No newline at end of file diff --git a/src/ldap/basics.ts b/src/ldap/basics.ts index f337e54a9cbd4f8ceafe6b5ec895ad6015b20464..9e29669db22816ed92d17484f6f213192aa79d5f 100644 --- a/src/ldap/basics.ts +++ b/src/ldap/basics.ts @@ -40,20 +40,17 @@ export class LDAP { * @static * @async */ - static async bind(dn: string, password: string) { - return new Promise((resolve, reject) => { - // Escape DN as everywhere in this file, but password is taken as is - client.bind(dn, password, res => { - // Gestion erreur - try { res; } - catch(err) { - reject(err); - throw "Erreur lors de la connection au LDAP."; - } - }); - // End with a boolean - resolve(true); + static async bind(dn: string, password: string) : Promise<boolean> { + // Escape DN as everywhere in this file, but password is taken as is + client.bind(dn, password, res => { + // Gestion erreur + try { res; } + catch(err) { + throw "Erreur lors de la connection au LDAP."; + } }); + // End with a boolean + return true; } /** @@ -63,7 +60,7 @@ export class LDAP { * @static * @async */ - static async adminBind() { return this.bind(credentialsLdapConfig.dn, credentialsLdapConfig.password); } + static async adminBind() : Promise<boolean> { return LDAP.bind(credentialsLdapConfig.dn, credentialsLdapConfig.password); } /** * @summary Fonction qui sert à se déconnecter du LDAP. @@ -73,7 +70,7 @@ export class LDAP { * @static * @async */ - static async unbind() { return this.bind("", ""); } + static async unbind() : Promise<boolean> { return LDAP.bind("", ""); } /** * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit les valeurs trouvées. @@ -87,44 +84,39 @@ export class LDAP { * @static * @async */ - static async search(dn: string, attributes: string[], filter="(objectClass=*)") { - this.adminBind(); - return new Promise((resolve, reject) => { - let vals=[]; - // Interrogation LDAP selon ldapConfiguration fournie en argument - client.search(ldapEscape.dn("${txt}", { txt: dn}), { - "scope": "sub", - "filter": ldapEscape.filter("${txt}", { txt: filter}), - "attributes": attributes - }, (err, res) => { - // Gestion erreur ; pb car pas simple true / autre en sortie - if (err) { - reject(err); - throw "Erreur lors de la recherche sur le LDAP."; - } else { - // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse - res.on('searchEntry', entry => { - // Cas un seul attribut où le résultat est une liste directement - if (!Array.isArray(attributes)) { vals.push(entry.object[attributes]); } - else if (attributes.length == 1) { vals.push(entry.object[attributes[0]]); } - // Cas plusieurs attributs donc résultat dictionnaire - else { - vals.push({}); - attributes.forEach(attribute => { - vals.slice(-1)[0][attribute]=entry.object[attribute]; - }); - } - }); - // Si la recherche renvoie une erreur, on renvoit - res.on('error', resErr => { reject(resErr); }); - // Si la recherche est finie on se déconnecte et on renvoit la liste - res.on('end', res => { - this.unbind(); - resolve(vals); - }); - } - }); + static async search(dn: string, attributes: string[], filter="(objectClass=*)") : Promise<Array<any>> { + LDAP.adminBind(); + let vals=[]; + // Interrogation LDAP selon ldapConfiguration fournie en argument + client.search(ldapEscape.dn("${txt}", { txt: dn}), { + "scope": "sub", + "filter": ldapEscape.filter("${txt}", { txt: filter}), + "attributes": attributes + }, (err, res) => { + // Gestion erreur ; pb car pas simple true / autre en sortie + if (err) { + throw "Erreur lors de la recherche sur le LDAP."; + } else { + // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse + res.on('searchEntry', entry => { + // Cas un seul attribut où le résultat est une liste directement + if (!Array.isArray(attributes)) { vals.push(entry.object[attributes]); } + else if (attributes.length == 1) { vals.push(entry.object[attributes[0]]); } + // Cas plusieurs attributs donc résultat dictionnaire + else { + vals.push({}); + attributes.forEach(attribute => { + vals.slice(-1)[0][attribute]=entry.object[attribute]; + }); + } + }); + // 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 + res.on('end', res => { LDAP.unbind(); }); + } }); + return vals; } //TBT @@ -140,21 +132,18 @@ export class LDAP { * @static * @async */ - static async change(dn: string, op: string, mod) { - this.adminBind(); - return new Promise((resolve, reject) => { - // Modification LDAP selon ldapConfiguration en argument (pourrait prendre une liste de Changes) - client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({ - operation: ldapEscape.dn("${txt}", {txt: op}), - modification: mod, - // Gestion erreur - }), err => { - reject(err); - throw "Erreur lors d'une opération de modification sur le LDAP."; - }); - this.unbind(); - resolve(true); + static async change(dn: string, op: string, mod) : Promise<boolean> { + LDAP.adminBind(); + // Modification LDAP selon ldapConfiguration en argument (pourrait prendre une liste de Changes) + client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({ + operation: ldapEscape.dn("${txt}", {txt: op}), + modification: mod, + // Gestion erreur + }), err => { + throw "Erreur lors d'une opération de modification sur le LDAP."; }); + LDAP.unbind(); + return true; } //TBT @@ -168,17 +157,14 @@ export class LDAP { * @static * @async */ - static async add(dn: string, vals) { - this.adminBind(); - return new Promise((resolve, reject) => { - // Ajout LDAP selon la ldapConfiguration en argument - client.add(ldapEscape.dn(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+",${txt}", { txt: dn}), vals, err => { - reject(err); - throw "Erreur lors d'une opération d'ajout sur le LDAP."; - }); - this.unbind(); - resolve(true); + static async add(dn: string, vals) : Promise<boolean> { + LDAP.adminBind(); + // Ajout LDAP selon la ldapConfiguration en argument + client.add(ldapEscape.dn(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+",${txt}", { txt: dn}), vals, err => { + throw "Erreur lors d'une opération d'ajout sur le LDAP."; }); + LDAP.unbind(); + return true; } //TBT @@ -191,16 +177,13 @@ export class LDAP { * @static * @async */ - static async clear(dn: string) { - this.adminBind(); - return new Promise((resolve, reject) => { - // Suppression LDAP - client.del(ldapEscape.dn("${txt}", {txt: dn}), err => { - reject(err); - throw "Erreur lors d'une opération de suppression sur le LDAP."; - }); - this.unbind(); - resolve(true); + static async clear(dn: string) : Promise<boolean> { + LDAP.adminBind(); + // Suppression LDAP + client.del(ldapEscape.dn("${txt}", {txt: dn}), err => { + throw "Erreur lors d'une opération de suppression sur le LDAP."; }); + LDAP.unbind(); + return true; } } \ No newline at end of file diff --git a/src/ldap/users.ts b/src/ldap/users.ts index 1e6c2643c0e724a6ac0d4808f01aa5c4ebb08be4..67c4a24b61b19653b322974cd60ea9aa1d0fa5d3 100644 --- a/src/ldap/users.ts +++ b/src/ldap/users.ts @@ -4,10 +4,10 @@ * @author hawkspar */ -import {ldapConfig} from './config'; +import { ldapConfig } from './config'; import {LDAP} from './basics'; import {searchUserFields, SmartSearch, Tests} from './utilities'; -import { Admin, Supervisor, userFullData } from './admins'; +import {Admin, Supervisor} from './admins'; import ldap from 'ldapjs'; /** @@ -18,10 +18,8 @@ import ldap from 'ldapjs'; * @var {string[]} admins - Liste des admins du groupe ; supposée être une sous-liste de la précédente */ let groupDataTemplate = {} -for (let key in ldapConfig.group.profil) { - if (ldapConfig.group.direct_input.includes(key)) { groupDataTemplate[key] = ""; } - else { groupDataTemplate[key] = [""]; } -} +for (let key in ldapConfig.group.single) { groupDataTemplate[key] = ""; } +for (let key in ldapConfig.group.multiple) { groupDataTemplate[key] = [""]; } /** * @type groupData @@ -43,12 +41,15 @@ export type groupData = typeof groupDataTemplate; * @var {string} ip - Adresse(s) ip * @var {string} adress - Adresse(s) * @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 + * @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 */ let userDataTemplate = {} -for (let key in ldapConfig.user.profil) { - if (ldapConfig.user.direct_input.includes(key)) { userDataTemplate[key] = ""; } - else { userDataTemplate[key] = [""]; } -} +for (let key in ldapConfig.user.single) { userDataTemplate[key] = ""; } +for (let key in ldapConfig.user.multiple) { userDataTemplate[key] = [""]; } /** * @type groupData @@ -175,7 +176,10 @@ export class Open { */ static async peekUser(uid: string) : Promise<userData> { try { - let LDAPUserData = await LDAP.search(ldapConfig.key_id+uid+ldapConfig.dn_users, ldapConfig.user.profil); + let fields = []; + fields.push(ldapConfig.user.single.values()); + fields.push(ldapConfig.user.multiple.values()); + let LDAPUserData = await LDAP.search(ldapConfig.key_id+'='+uid+','+ldapConfig.dn_users, fields); let cleanUserData = userDataTemplate; // Rename output for (let uncleanKey in LDAPUserData) { @@ -201,7 +205,10 @@ export class Open { */ static async peekGroup(gid: string) : Promise<groupData> { try { - let LDAPGroupData = await LDAP.search(ldapConfig.key_id+gid+ldapConfig.dn_groups, ldapConfig.group.profil); + let fields = []; + fields.push(ldapConfig.user.single.values()); + fields.push(ldapConfig.user.multiple.values()); + let LDAPGroupData = await LDAP.search(ldapConfig.key_id+'='+gid+','+ldapConfig.dn_groups, fields); let cleanGroupData=groupDataTemplate; // Rename output for (let uncleanKey in LDAPGroupData) { @@ -228,10 +235,10 @@ export class Open { * @static * @async */ - static async findGroups(input: string) { + static async findGroups(input: string) : Promise<string[]> { try { // Trucs intelligents faits dans ./utilities - return SmartSearch.groups(input, [ldapConfig.key_id, ldapConfig.group.type]); + return SmartSearch.groups(input, ldapConfig.key_id); } catch(err) { throw "Erreur lors de la recherche approximative d'un groupe."; @@ -247,7 +254,7 @@ export class Open { * @static * @async */ - static async findUsers(data: searchUserFields) { + static async findUsers(data: searchUserFields) : Promise<string[]> { try { return SmartSearch.users(data, ldapConfig.key_id); } @@ -280,78 +287,80 @@ export class User extends Open { * @async * @static */ - static async addGroup(data: groupData) { + static async addGroup(data: groupData) : Promise<boolean> { // 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[ldapConfig.group['name']]=id; }); + Tests.generateReadableId(data['name']).then(id => { + vals[ldapConfig.key_id]=id; + vals[ldapConfig.group.single['name']]=id; + }); } catch(err) { throw "Erreur lors de la génération d'un hruid pour créer un nouveau groupe."; } + let gid = vals[ldapConfig.key_id]; + // Ecriture de toutes les valeurs directement inscrites dans le LDAP (in pour input) - ldapConfig.group.direct_input.forEach(key_att => vals[ldapConfig.group[key_att]]=data[key_att]); + ldapConfig.group.single.forEach(key_att => vals[ldapConfig.group.single[key_att]]=data[key_att]); // Appel à la fonction de base - if (!await LDAP.add(ldapConfig.key_id+"="+vals[ldapConfig.group['name']]+","+ldapConfig.dn_groups, vals)) { + if (!await LDAP.add(ldapConfig.key_id+"="+gid+","+ldapConfig.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[ldapConfig.group["name"]]=data['name']; - // ?! - vals2[ldapConfig.user['password']] = ''; + vals2[ldapConfig.group.single['password']] = ''; // Génération id aléatoire et test contre le LDAP try { - Tests.generateId(ldapConfig.groups["idNumber"], ldapConfig.dn_groups).then(id => { vals2[ldapConfig.group['idNumber']]=id; }); + Tests.generateId(ldapConfig.group.single["idNumber"], ldapConfig.dn_groups).then(id => { vals2[ldapConfig.group.single['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[ldapConfig.group['idNumber2']]=vals2[ldapConfig.group['idNumber']]; + vals2[ldapConfig.group.single['idNumber2']]=vals2[ldapConfig.group.single['idNumber']]; // Stockage machine ; dépend du prénom - vals2[ldapConfig.group['directory']] = '/hosting/groups/'+vals[ldapConfig.key_id]; + vals2[ldapConfig.group.single['directory']] = '/hosting/groups/'+gid; // Code root - vals2[ldapConfig.group['cleanFullName']]=data['name'].replace(':', ';').toLowerCase().normalize('UFD'); + vals2[ldapConfig.group.single['cleanFullName']]=data['name'].replace(':', ';').toLowerCase().normalize('UFD'); // Adressage root - vals2[ldapConfig.group['login']] = "/sbin/nologin"; + vals2[ldapConfig.group.single['login']] = "/sbin/nologin"; // Permissions BR - vals2[ldapConfig.group['readPerm']] = '!*'; - vals2[ldapConfig.group['writePerm']] = '!*'; + vals2[ldapConfig.group.single['readPerm']] = '!*'; + vals2[ldapConfig.group.single['writePerm']] = '!*'; // Inscription des valeurs calculées par effet de bord - if (!await LDAP.change(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+","+ldapConfig.dn_groups, "add", vals2)) { + if (!await LDAP.change(ldapConfig.key_id+"="+gid+","+ldapConfig.dn_groups, "add", vals2)) { throw "Erreur lors de l'ajout des valeurs intelligentes du nouveau groupe."; } ["posixAccount", "posixGroup", "brAccount"].forEach(cst => { let vals3={}; - vals3[ldapConfig.group['class']]=cst; - LDAP.change(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+","+ldapConfig.dn_groups, "add", vals3).then(res => { + vals3[ldapConfig.group.multiple['class']]=cst; + LDAP.change(ldapConfig.key_id+"="+gid+","+ldapConfig.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[ldapConfig.key_att]).then(res => { + Admin.addGroupMember(uid, gid).then(res => { if (!res) { throw "Erreur de l'ajout d'un membre au groupe."; } }); }); data['admins'].forEach(uid => { - Admin.addGroupAdmin( uid, vals[ldapConfig.key_att]).then(res => { + Admin.addGroupAdmin(uid, gid).then(res => { if (!res) { throw "Erreur de l'ajout d'un admin au groupe."; } }); }); @@ -376,18 +385,11 @@ export class User extends Open { * @async * @static */ - static async editUser(uid : string, data : userFullData) { + static async editUser(uid : string, data : userData) : Promise<boolean> { // Récupération des anciennes données let profil = await Open.peekUser(uid); - // Reecriture de profil avec les bons champs - Object.keys(profil).forEach(keyLDAP => { - Object.keys(ldapConfig.user).forEach(keyAlias => { - ldapConfig.user[keyAlias]=keyLDAP; - profil[keyAlias]=profil[keyLDAP]; - }); - }); - // Régénération du champ manquant dans profil try { + // Régénération du champ manquant dans profil let lg = await Open.getGroups(uid); profil['groupsIsAdmin']=[]; lg.forEach(gid => { @@ -396,15 +398,11 @@ export class User extends Open { }); }); // Surcharge des champs à modifier selon data - let unchangeables = ['readPerm','writePerm','forlifes','ips','groups','groupsIsAdmin']; 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 (!unchangeables.includes(key)) { profil[key]=data[key]; } + 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,modification propre par effet de bord + // Modification propre par effet de bord if (!(await Supervisor.delUser(uid) && await Supervisor.addUser(profil))) { throw "Erreur dans la destruction/création du compte."; } else { @@ -415,6 +413,4 @@ export class User extends Open { throw "Erreur lors de la modification des groupes où un utilisateur est admin."; } } - - destr() { LDAP.unbind(); } } \ No newline at end of file diff --git a/src/ldap/utilities.ts b/src/ldap/utilities.ts index 2af9cb17dc4c7a017920eecc425e11feaa090e6f..19b5606d3ab0a625e000656e11850e3518096d1f 100644 --- a/src/ldap/utilities.ts +++ b/src/ldap/utilities.ts @@ -66,7 +66,7 @@ export class SmartSearch { * @static * @async */ - static async groups(input: string, return_attributes: string[]) { + static async groups(input: string, return_attributes: string[]) : Promise<string[]> { // 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 * @@ -95,7 +95,7 @@ export class SmartSearch { * @static * @async */ - static async users(data: searchUserFields, return_attributes: string[]) { + static async users(data: searchUserFields, return_attributes: string[]) : 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) { @@ -156,7 +156,7 @@ export class Tests { * @static * @async */ - static async ensureUnique(value: string, attribute: string, dn: string, changeValue: (string, number) => string, n=0) { + static async ensureUnique(value: string, attribute: string, dn: string, changeValue: (string, number) => string, n=0) : Promise<string> { // Recherche d'autres occurences de l'id try { return LDAP.search(dn, ldapConfig.key_id, "("+attribute+"="+value+")").then(function (matches: string[]) { @@ -164,7 +164,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 this.ensureUnique(changeValue(value, n+1), dn, changeValue, n+1); } + else { return Tests.ensureUnique(changeValue(value, n+1), attribute, dn, changeValue, n+1); } }); } catch(err) { @@ -182,7 +182,7 @@ export class Tests { * @static * @async */ - static async generateUid(givenName: string, lastName: string, promotion: string) { + 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, ldapConfig.dn_users, (id: string, n: number) => { @@ -205,7 +205,7 @@ export class Tests { * @static * @async */ - static async generateReadableId(name: string) { + static async generateReadableId(name: string) : Promise<string> { try { // normalize et lowerCase standardisent le format return this.ensureUnique(name.toLowerCase().normalize('UFD'), ldapConfig.key_id, ldapConfig.dn_groups, (id: string, n: number) => { @@ -227,7 +227,7 @@ export class Tests { * @static * @async */ - static async generateId(attribut: string, dn: string) { + static async generateId(attribut: string, dn: string) : Promise<string> { try { return this.ensureUnique("0", attribut, dn, (id,n) => { return Math.floor((Math.random() * 100000) + 1).toString(); }); }