Skip to content
Snippets Groups Projects
Commit c6cf51c5 authored by Quentin CHEVALIER's avatar Quentin CHEVALIER
Browse files

Added clean generics, fix edit

parent e4fcd212
No related branches found
No related tags found
No related merge requests found
......@@ -20,7 +20,7 @@
"nationality": "country",
"promotion": "brPromo",
"phone": "telephoneNumber",
"adresses": "brRoom",
"adress": "brRoom",
"id": "uidNumber",
"password": "userPassword",
"idNum": "gidNumber",
......@@ -28,7 +28,7 @@
"login": "loginShell",
"readPerm": "brNewsReadAccess",
"writePerm": "brNewsPostAccess",
"mails": "mail",
"mail": "mail",
"ips": "brIP",
"forlifes": "brAlias",
"groups": "brMemberOf",
......
......@@ -117,7 +117,7 @@ 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
res.on('end', res => { LDAP.unbind(); });
res.on('end', _ => { LDAP.unbind(); });
}
});
// On renvoit le résultat
......@@ -130,7 +130,7 @@ export class LDAP {
* @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode modify).
* @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur)
* @arg {string} id - Identifiant unique de la feuille à modifier
* @arg {string} op - Operation à réaliser sur le LDAP. Trois opération sont possibles ; "add", qui rajoute des attributs et qui peut créer des doublons,
* @arg {"add"|"del"|"replace"} op - Operation à réaliser sur le LDAP. Trois opération sont possibles ; "add", qui rajoute des attributs et qui peut créer des doublons,
* "del" qui en supprime, et "replace" qui remplace du contenu par un autre.
* @arg {Object.<string, string>} mod - Dictionnaire contenant les attributs à modifier et les nouvelles valeurs des attributs.
* @arg {Object} mod[key] - Nouvelle valeur de l'attribut key. Une nouvelle valeur vide ("") est équivalent à la suppression de cet attribut.
......@@ -138,14 +138,14 @@ export class LDAP {
* @static
* @async
*/
static async change(domain: 'gr'|'us', id: string, op: string, mod) : Promise<boolean> {
static async change(domain: 'gr'|'us', id: string, op: "add"|"del"|"replace", mod) : Promise<boolean> {
LDAP.adminBind();
let dn = ldapConfig.key_id+'='+id+','
if (domain == "gr") { dn+=ldapConfig.dn_groups }
else { dn+=ldapConfig.dn_users }
// 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}),
operation: op,
modification: mod,
// Gestion erreur
}), err => {
......
......@@ -67,7 +67,7 @@ export class Group {
*/
static async search(input: string) : Promise<string[]> {
try {
return Tools.genericSearch<groupData>("gr", {
return Tools.genericSearch("gr", {
"gid": input,
"name": "",
"type": "",
......@@ -354,17 +354,25 @@ export class Group {
}
/**
* @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 {groupData} 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.
* @summary Fonction qui édite un groupe existant dans le LDAP.
* @desc Appelle {@link genericEdit} bien sûr, mais aussi {@link addMember} et {@link addAdmin}.
* @arg {groupData} data - Dictionnaire des informations du groupe.
* @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
* @async
* @static
*/
static async edit(data: groupData) : Promise<boolean> {
try {
return Tools.genericEdit<groupData>("gr",data);
let gid = data["gid"];
// Remove old members and admins
let profil = Group.peek(gid);
profil["members"].forEach(uid => { Group.remMember(uid, gid); });
profil["admins"].forEach(uid => { Group.remAdmin(uid, gid); });
// Add new members and admins
data["members"].forEach(uid => { Group.addMember(uid, gid); });
data["admins"].forEach(uid => { Group.addAdmin(uid, gid); });
// Edit all other fields
return Tools.genericEdit("gr",data);
}
catch(err) {
throw "Erreur lors de la modification d'un groupe.";
......
......@@ -35,26 +35,26 @@ import {Group} from './group';
* TBA @var {string[]} likes - Liste des gid dont l'utilisateur est sympathisant
*/
export interface userData {
"uid"?: string,
"uid": string,
"groups": string[],
"groupsIsAdmin": string[],
"password"?: string,
"givenName"?: string,
"lastName"?: string,
"nickname"?: string,
"promotion"?: string,
"photo"?: string,
"birthdate"?: string,
//"nationality"?: string,
"promotion"?: string,
"phone"?: string,
"adresses"?: string,
"mails"?: string[],
"groups"?: string[],
"password"?: string,
"adress"?: string,
"mail"?: string,
"ips"?: string[],
"directory"?: string,
"login"?: string,
"readPerm"?: string,
"writePerm"?: string,
"forlifes"?: string[],
"admins"?: string[]
"forlifes"?: string[]
//"likes"?: string[]
}
......@@ -68,49 +68,26 @@ export class User {
* @summary Constructeur vide.
*/
constructor() {}
/**
* @summary Fonction qui renvoit certaines informations relatives à un utilisateur particulier.
* @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis.
* @summary Fonction qui renvoit les infos de base relatives à un utilisateur particulier.
* @desc Cette fonction utilise {@link Tools.genericPeek} avec l'interface {@link userData}.
* @arg {string} uid - Identifiant de l'utilisateur
* @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.
* @return {Promise(userData)} Informations recueillies.
* @static
* @async
* @private
*/
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 : T;
// Rename output
for (let uncleanKey in LDAPUserData) {
for (let cleanKey in cleanUserData) {
if (uncleanKey==ldapConfig.group[cleanKey]) { cleanUserData[cleanKey] = LDAPUserData[uncleanKey]; }
}
}
return cleanUserData;
static async peek(uid: string) : Promise<userData> {
try {
return Tools.genericPeek<userData>("us", uid);
}
catch(err) {
throw "Erreur lors d'une recherche d'informations sur un individu.";
throw "Error while peeking a user.";
}
}
/**
* @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
* car moins gourmande envers le LDAP (utiliser {@link peekUser} au cas par cas après pour obtenir les vraies infos).
* @summary Fonction qui retrouve les uid des paxs validant les critères de recherche. Utiliser {@link peek} 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.
......@@ -123,25 +100,7 @@ export class User {
*/
static async search(data: userData) : Promise<string[]> {
try {
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 = ldapConfig.user[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("us", [ldapConfig.key_id], null, filter);
return Tools.genericSearch("us", data);
}
catch(err) {
throw "Erreur lors de la recherche approximative d'un utilisateur.";
......@@ -298,19 +257,24 @@ export class User {
/**
* @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_ldapConfig est important.
* Une version "nerfée" de cette fonction est envisageable ; elle donne bcp de pouvoir à l'utilisateur.
* @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).
* @desc Appelle simplement {@link genericEdit}.
* @arg {userData} data - Dictionnaire des informations utilisateurs
* @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
* @async
* @static
*/
static async edit(data : userData) : Promise<boolean> {
try {
return Tools.genericEdit<userData>("us",data);
let uid = data["uid"];
// Leave old groups
let profil = User.peek(uid);
profil["groups"].forEach(gid => { Group.remMember(uid, gid); });
profil["groupsIsAdmin"].forEach(gid => { Group.remAdmin(uid, gid); });
// Join new groups
data["groups"].forEach(gid => { Group.addMember(uid, gid); });
data["groupsIsAdmin"].forEach(gid => { Group.addAdmin(uid, gid); });
// Edit all other fields
return Tools.genericEdit("us",data);
}
catch(err) {
throw "Erreur lors de la modification d'un utilisateur.";
......
......@@ -6,10 +6,8 @@
import {ldapConfig} from './config';
import {LDAP} from './basics';
import { groupData } from './group';
import { userData } from './user';
import { Group } from './group';
import { User } from './user';
import { groupData } from './group';
//------------------------------------------------------------------------------------------------------------------------
// Fonctions intermédiaires TBT
......@@ -26,23 +24,26 @@ export class Tools {
/**
* @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.
* @param T - Format renvoyé (en pratique {@link userData} ou {@link groupData})
* @arg {string} domain - Domaine de la recherche (utilisateur ou groupe)
* @arg {string} id - Identifiant de la feuille cherchée (uid ou gid)
* @return {Promise(T)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe tel que défini par le paramètre T.
* @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);
static async genericPeek<T>(domain: 'us'|'gr', id: string) : Promise<T> {
if (domain='gr') {
var dirtyKeys = ldapConfig.group;
}
else {
var dirtyKeys = ldapConfig.user;
}
let cleanData : T;
let dirtyData = await LDAP.search(domain, dirtyKeys.values(), id);
// Rename output
for (let uncleanKey in LDAPData) {
for (let uncleanKey in dirtyData) {
for (let cleanKey in cleanData) {
if (uncleanKey==ldapConfig.group[cleanKey]) { cleanData[cleanKey] = LDAPData[uncleanKey]; }
if (uncleanKey=dirtyKeys[cleanKey]) { cleanData[cleanKey] = dirtyData[uncleanKey]; }
}
}
return cleanData;
......@@ -50,29 +51,30 @@ export class Tools {
/**
* @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).
* @summary Fonction qui retrouve les id des paxs ou groupes validant les critères de recherche. Etape vers vrai TOL (Trombino On Line).
* 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
* @param T - Format renvoyé (en pratique {@link userData} ou {@link groupData})
* @arg {string} domain - Domaine de la recherche (utilisateur ou groupe)
* @arg {userData | groupData} 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 exemple 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.
* @return {Promise(string[])} ids des profils qui "match" les critères proposés.
* @static
* @async
*/
static async genericSearch<T>(domain : "us"|"gr", data : T) : Promise<string[]> {
static async genericSearch(domain : "us"|"gr", 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) {
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
if (!Array.isArray(data[key])) { data[key]=[data[key]]; } // Génération systématique 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]; }
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
......@@ -85,50 +87,33 @@ export class Tools {
// 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...
* @summary Fonction qui édite un groupe existant dans le LDAP.
* @desc Appelle {@link LDAP.change}.
* @arg {userData | groupData} 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); }
});
});
static async genericEdit(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;
}
// 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]; }
let dirtyData = {};
Object.keys(data).forEach(function(key: string) {
if (!['readPerm','writePerm','groups','groupsIsAdmin','members','admins'].includes(key)) {
dirtyData[dirtyKeys.key]=data[key];
}
});
return LDAP.change(domain,id,"replace",dirtyProfil);
return LDAP.change(domain,id,"replace",dirtyData);
}
/**
......@@ -158,7 +143,7 @@ export class Tools {
return LDAP.search(domain, [ldapConfig.key_id], null, "("+attribute+"="+value+")").then(function (matches: string[]) {
if (!matches) { throw ""; }
// On renvoit la valeur si elle est bien unique
else if (matches.length==0) { return value; }
else if (matches.length=0) { return value; }
// Sinon, on tente de nouveau notre chance avec la valeur suivante
else { return Tools.ensureUnique(changeValue(value, n+1), attribute, domain, changeValue, n+1); }
});
......@@ -182,8 +167,8 @@ export class Tools {
try {
// normalize et lowerCase standardisent le format
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
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...
return id;
});
......@@ -205,8 +190,8 @@ export class Tools {
try {
// normalize et lowerCase standardisent le format
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...
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;
});
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment