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

Ramp up du LDAP ; refacto add et rem, categories speaker et follower, inclut...

Ramp up du LDAP ; refacto add et rem, categories speaker et follower, inclut DFS pour les admins et les membres
parent 18bf5bf8
No related branches found
No related tags found
No related merge requests found
{
"comment_1": "Tout ce fichier sert à protéger les vrais champs du LDAP dans les scripts dans src/ldap. Les champs ci-dessous contiennent le nécessaire à une première connexion par exemple.",
"server_prod": "ldap://frankiz.eleves.polytechnique.fr:389",
"server_dev": "ldap://129.104.201.10:389",
"server": {
"prod": "ldap://frankiz.eleves.polytechnique.fr:389",
"dev": "ldap://129.104.201.10:389"
},
"comment_2": "Noms de domaines dans LDAP ; le niv d'après est en uid=, voir Wikipedia",
"dn_groups":"ou=groups,dc=frankiz,dc=net",
"dn_users": "ou=eleves,dc=frankiz,dc=net",
"dn":{
"group":"ou=groups,dc=frankiz,dc=net",
"user": "ou=eleves,dc=frankiz,dc=net"
},
"key_id": "uid",
"comment_3": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les utilisateurs",
......@@ -32,7 +36,10 @@
"mail": "mail",
"ips": "brIP",
"forlifes": "brAlias",
"groups": "brMemberOf",
"admins": "TBC",
"speakers": "TBC",
"members": "brMemberOf",
"followers": "TBC",
"classes": "objectClass"
},
"comment_4": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les groupes",
......@@ -40,8 +47,10 @@
"gid": "uid",
"name": "brAlias",
"category": "brNS",
"members": "restrictedMemberUid",
"admins": "memberUid",
"speakers": "TBC",
"members": "restrictedMemberUid",
"followers": "TBC",
"adress":"cn",
"idNumber": "uidNumber",
"idNumber2": "gidNumber",
......@@ -50,7 +59,7 @@
"directory": "homeDirectory",
"cleanFullName": "gecos",
"classes": "objectClass",
"child": "child",
"parent": "parent"
"childs": "TBC",
"parents": "TBC"
}
}
\ No newline at end of file
This diff is collapsed.
......@@ -3,60 +3,9 @@
* @author hawkspar
*/
import {ldapConfig} from '../internal/config';
import {ldapConfig, userData, categories} from '../internal/config';
import {Basics} from '../internal/basics';
import {Tools} from '../internal/tools';
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[]} address - Adresse(s)
* @var {string[]} mail - Adresse(s) courriel
* @var {string[]} groups - Un ou plusieurs groupes dont l'utilisateur est membre (inclus section sportive, binet, PA...)
* @var {string} password - Mot de passe généré en amont
* @var {string[]} ips - Adresse(s) ip
* @var {string} directory - Adresse soft des données utilisateurs
* @var {string} login - Astuce de root flemmard
* @arg {string} readPerm - Permissions spéciales BR
* @var {string} writePerm - Permissions spéciales BR
* @var {string[]} forlifes - Alias BR (attention le filtre .fkz n'est plus fonctionnel)
* @var {string[]} admins - Liste des gid dont l'utilisateur est admin ; supposé sous-liste de groups
* TBA @var {string[]} likes - Liste des gid dont l'utilisateur est sympathisant
*/
export class userData {
uid?: string;
groups?: string[];
groupsIsAdmin?: string[];
password?: string;
givenName?: string;
lastName?: string;
nickname?: string;
promotion?: string;
photo?: string;
birthdate?: string;
nationality?: string;
phone?: string;
address?: string;
mail?: string;
ips?: string[];
directory?: string;
login?: string;
readPerm?: string;
writePerm?: string;
forlifes?: string[];
sport?: string;
//"likes"?: string[]
}
//------------------------------------------------------------------------------------------------------------------------
// Classes à exporter TBT
......@@ -82,7 +31,7 @@ export class User {
*/
static async peek(uid: string) : Promise<userData> {
try {
return Tools.peek<userData>("us", uid, userData);
return Tools.peek<userData>("user", uid, userData);
}
catch(err) {
throw "Error while peeking a user.";
......@@ -102,7 +51,7 @@ export class User {
*/
static async search(data: userData) : Promise<string[]> {
try {
return Tools.search("us", data);
return Tools.search("user", data);
}
catch(err) {
throw "Erreur lors de la recherche approximative d'un utilisateur.";
......@@ -112,8 +61,8 @@ export class User {
/**
* @memberof LDAP
* @summary Fonction qui créé un nouvel utilisateur dans le LDAP.
* @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link Group.addMember} et {@link Group.addAdmin} pour gérer les groupes du nouvel utilisateur.
* @arg {fullUserData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis.
* @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link Tools.add} 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.
* Cette application ne permet pas de rejoindre des groupes.
* @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
* @async
......@@ -141,7 +90,7 @@ export class User {
}
// Appel à la fonction de base
if (!await Basics.add("us", vals)) { throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; }
if (!await Basics.add("user", vals)) { throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; }
for (let key_att in data) {
// Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples
......@@ -150,7 +99,7 @@ export class User {
data[key_att].forEach(val => {
let vals2 = {};
vals2[ldapConfig.user[key_att]]=val;
Basics.change("us", uid, "add", vals2).then(res => {
Basics.change("user", uid, "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."; }
});
});
......@@ -173,7 +122,7 @@ export class User {
}
try {
// Génération id aléatoire unique
vals3[ldapConfig.user['id']]= await Tools.generateId(ldapConfig.user['id'], "us");
vals3[ldapConfig.user['id']]= await Tools.generateId(ldapConfig.user['id'], "user");
}
catch(err) {
throw "Erreur lors de la génération d'un id numérique pour un nouvel utilisateur.";
......@@ -199,17 +148,24 @@ export class User {
vals3[ldapConfig.user['idNum']] ='5000';
// Inscription des valeurs calculées
if (!await Basics.change("us", uid, "add", vals3)) {
if (!await Basics.change("user", uid, "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;
Basics.change("us", uid, "add", vals3).then(res => {
Basics.change("user", uid, "add", vals3).then(res => {
if (!res) { throw "Erreur lors de l'ajout d'une valeur constante à la feuille du nouvel utilisateur."; }
});
});
// Ajout dans les groupes à la catégorie voulue
for (let cat of categories) {
for (let gid of data[cat]) {
Tools.add(uid, gid, cat);
}
}
return true;
}
......@@ -221,7 +177,7 @@ export class User {
* @memberof LDAP
* @summary Fonction qui supprime un utilisateur du LDAP.
* @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement.
* Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Group.remMember} et {@link Group.remAdmin} pour gérer les groupes de l'utilisateur sortant.
* Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Tools.remove} pour gérer les groupes de l'utilisateur sortant.
* @arg {string} uid - uid de la victime
* @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
* @async
......@@ -231,32 +187,32 @@ export class User {
try {
// Gestion des groupes d'abord
let profil = await User.peek(uid);
profil[ldapConfig.user['groups']].forEach(async function (gid: string) {
// Si l'utilisateur était admin, l'enlever
Group.remAdmin(uid, gid);
// Enlever de la liste des membres
let lm = await Tools.getMembers(gid);
if (lm.includes(uid)) {
// Supprime tous les membres
if (!await Basics.change("gr", gid, "del", ldapConfig.group.members)) {
throw "Erreur lors de la suppression de tous les membres du groupe.";
}
// Les rajoute un par un, sauf pour le supprimé
lm.forEach(id => {
if (id!=uid) {
Group.addMember(id, gid).then(res => {
if (!res) { throw "Erreur lors du ré-ajout d'un autre membre"; }
});
for (let cat of categories) {
profil[ldapConfig.user[cat]].forEach(async function (gid: string) {
// Enlever de la liste des membres
let lm = await Tools.get(gid, "group", cat);
if (lm.includes(uid)) {
// Supprime tous les membres
if (!await Basics.change("group", gid, "del", ldapConfig.group[cat])) {
throw "Erreur lors de la suppression de tous les membres du groupe.";
}
});
}
});
// Les rajoute un par un, sauf pour le supprimé
lm.forEach(id => {
if (id!=uid) {
Tools.add(id, gid, cat).then(res => {
if (!res) { throw "Erreur lors du ré-ajout d'un autre membre"; }
});
}
});
}
});
}
}
catch(err) {
throw "Erreur lors de l'obtention des informations de l'utilisateur à supprimer.";
}
// Elimination
if (!Basics.clear("us", uid)) { throw "Erreur lors de la suppression de l'utilisateur."; }
if (!Basics.clear("user", uid)) { throw "Erreur lors de la suppression de l'utilisateur."; }
return true;
}
......@@ -271,7 +227,7 @@ export class User {
*/
static async edit(data : userData) : Promise<boolean> {
try {
return Tools.edit("us",data);
return Tools.edit("user",data);
}
catch(err) {
throw "Erreur lors de la modification d'un utilisateur.";
......
......@@ -98,12 +98,11 @@ export class Basics {
* @static
* @async
*/
static search(domain: 'gr'|'us', attributes: string[], id: string, filter: string, handler : (entry: any) => void) : Promise<void> {
static search(domain: 'group'|'user', attributes: string[], id: string, filter: string, handler : (entry: any) => void) : Promise<void> {
Basics.adminBind();
let dn ="";
if (id != null) { dn+=ldapConfig.key_id+'='+ ldapEscape.dn("${txt}", { txt: id}) +','; }
if (domain == "gr") { dn+=ldapConfig.dn_groups; }
else { dn+=ldapConfig.dn_users; }
dn+=ldapConfig.dn[domain];
// Interrogation LDAP selon filter
let promise = new Promise<void>(function(resolve, reject) {
client.search(dn, { // Must be escaped in case of a malignious false id
......@@ -141,7 +140,7 @@ export class Basics {
* @static
* @async
*/
static async searchSingle(domain: 'gr'|'us', attribute: string, id: string=null, filter: string="(objectClass=*)") : Promise<string[]> {
static async searchSingle(domain: 'group'|'user', attribute: string, id: string=null, filter: string="(objectClass=*)") : Promise<string[]> {
let vals=[];
await Basics.search(domain, [attribute], id, filter, entry => {
// Cas un seul attribut où le résultat est une liste directement
......@@ -164,7 +163,7 @@ export class Basics {
* @static
* @async
*/
static async searchMultiple(domain: 'gr'|'us', attributes: string[], id: string=null, filter: string="(objectClass=*)") : Promise<Array<dic>> {
static async searchMultiple(domain: 'group'|'user', attributes: string[], id: string=null, filter: string="(objectClass=*)") : Promise<Array<dic>> {
let vals=[];
await Basics.search(domain, attributes, id, filter, entry => {
// Cas plusieurs attributs donc résultat dictionnaire
......@@ -191,11 +190,10 @@ export class Basics {
* @static
* @async
*/
static async change(domain: 'gr'|'us', id: string, op: "add"|"del"|"replace", mod: dic) : Promise<boolean> {
static async change(domain: 'group'|'user', id: string, op: "add"|"del"|"replace", mod: dic) : Promise<boolean> {
Basics.adminBind();
let dn = ldapConfig.key_id+'='+id+','
if (domain == "gr") { dn+=ldapConfig.dn_groups }
else { dn+=ldapConfig.dn_users }
dn+=ldapConfig.dn[domain];
// Modification LDAP selon dn fourni en argument (pourrait prendre une liste de Changes)
client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({
operation: op,
......@@ -220,11 +218,10 @@ export class Basics {
* @static
* @async
*/
static async add(domain: 'gr'|'us', vals) : Promise<boolean> {
static async add(domain: 'group'|'user', vals) : Promise<boolean> {
Basics.adminBind();
let dn = ldapConfig.key_id+"="+vals[ldapConfig.key_id];
if (domain == "gr") { dn+=ldapConfig.dn_groups; }
else { dn+=ldapConfig.dn_users; }
dn+=ldapConfig.dn[domain];
// Ajout LDAP selon la ldapConfiguration en argument
client.add(ldapEscape.dn("${txt}", { txt: dn}), vals, err => {
throw "Erreur lors d'une opération d'ajout sur le LDAP.";
......@@ -245,11 +242,10 @@ export class Basics {
* @static
* @async
*/
static async clear(domain: 'gr'|'us', id: string) : Promise<boolean> {
static async clear(domain: 'group'|'user', id: string) : Promise<boolean> {
Basics.adminBind();
let dn = ldapConfig.key_id+'='+id+','
if (domain == "gr") { dn+=ldapConfig.dn_groups; }
else { dn+=ldapConfig.dn_users; }
dn+=ldapConfig.dn[domain];
// Suppression LDAP
client.del(ldapEscape.dn("${txt}", {txt: dn}), err => {
throw "Erreur lors d'une opération de suppression sur le LDAP.";
......
......@@ -12,18 +12,103 @@
import fs from 'fs';
import path from 'path';
import colors from 'colors';
// Point central ; tous les champs de la BDD sont 'cachés' dans config.json et pas visibles directement
let path_config = path.resolve(__dirname,'..', '..', '..', 'ldap_config.json');
let path_config = path.resolve(__dirname, '..', '..', '..', 'ldap_config.json');
console.log(colors.cyan("Loading LDAP config file from "+path_config));
export const ldapConfig = JSON.parse(fs.readFileSync(path_config).toString());
let path_credentials = path.resolve(__dirname,'..', '..', '..', 'ldap_credentials.json')
console.log(colors.cyan("Loading LDAP credentials from "+path_credentials));
export const credentialsLdapConfig = JSON.parse(fs.readFileSync(path_credentials).toString());
// Override config server from environment
if (process.env.LDAP_URI != null) {
ldapConfig.server = process.env.LDAP_URI;
}
else {
if (process.env.TARGET_ENV == `production`) { ldapConfig.server = ldapConfig.server_prod; }
else { ldapConfig.server = ldapConfig.server_dev; }
if (process.env.TARGET_ENV == `production`) { ldapConfig.server = ldapConfig.server.prod; }
else { ldapConfig.server = ldapConfig.server.dev; }
}
// Gestion des super-identifiants
let path_credentials = path.resolve(__dirname, '..', '..', '..', 'ldap_credentials.json')
console.log(colors.cyan("Loading LDAP credentials from "+path_credentials));
export const credentialsLdapConfig = JSON.parse(fs.readFileSync(path_credentials).toString());
// Data formats and useful constants
export const categories = ["admins","speakers","members","followers"];
/**
* @interface userData
* @desc Interface avec toutes les données extractables pour un utilisateur.
* @var {string?} uid - Identifiant utilisateur
* TBA @var {string[]?} admins - Liste des gid (group id, inclus section sportive, binet, PA...) dont l'utilisateur est admin ; pas forcément sous-liste de groups
* TBA @var {string[]?} speakers - Liste des gid dont l'utilisateur est porte-parole ; pas forcément sous-liste de groups
* @var {string[]?} members - Liste des gid dont l'utilisateur est membre
* TBA @var {string[]?} followers - Liste des gid dont l'utilisateur est sympathisant
* @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[]} address - Adresse(s)
* @var {string[]?} mail - Adresse(s) courriel
* @var {string?} password - Mot de passe généré en amont (utilisé seulement à l'initialialisation, pas stocké bien sûr)
* @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)
* @memberof LDAP
*/
export class userData {
uid?: string;
admins?: string[];
speakers?: string[];
members?: string[];
followers?: string[];
password?: string;
givenName?: string;
lastName?: string;
nickname?: string;
promotion?: string;
photo?: string;
birthdate?: string;
nationality?: string;
phone?: string;
address?: string;
mail?: string;
ips?: string[];
directory?: string;
login?: string;
readPerm?: string;
writePerm?: string;
forlifes?: string[];
sport?: string;
}
/**
* @interface groupData
* @var {string} gid - Identifiant du groupe
* @var {string} name - Nom du groupe (souvent son nom mais pas nécessairement)
* @var {string} type - Statut du groupe ; binet, section sportive... (actuellement juste 'binet' ou 'free')
* @var {string[]} members - Liste des membres du groupe
* @var {string[]} admins - Liste des admins du groupe ; supposée être une sous-liste de la précédente
* @var {string} description - Description du groupe (facultatif)
* @var {string[]} childs - Liste des groupes enfants de première génération de celui-ci (les admins du groupe seront admins de ce groupe et des enfants suivants)
* @var {string[]} parents - Liste des groupes directement parents de celui-ci (les membres du groupe seront membres de ce groupe et des parents suivants) ; symétrique du précédent
* @memberof LDAP
*/
export class groupData {
gid: string;
name: string;
type: string;
members: string[];
speakers: string[];
followers: string[];
admins: string[];
description?: string;
childs?: string[];
parents?: string[];
}
\ No newline at end of file
......@@ -6,10 +6,8 @@
// Toutes les entrées utilisateur sont escapées par sécurité
import ldapEscape from 'ldap-escape';
// Imports internes
import {ldapConfig} from './config';
import {ldapConfig, userData, groupData} from './config';
import {Basics} from './basics';
import {userData} from '../export/user';
import {groupData} from '../export/group';
//------------------------------------------------------------------------------------------------------------------------
// Fonctions intermédiaires TBT
......@@ -23,7 +21,7 @@ export class Tools {
* @summary Constructeur vide.
*/
constructor() {}
/**
* @memberof LDAP
* @summary Fonction qui renvoit toutes les infos relatives à un groupe ou un utilisateur particulier.
......@@ -35,13 +33,8 @@ export class Tools {
* @static
* @async
*/
static async peek<T>(domain: 'us'|'gr', id: string, type: new () => T) : Promise<T> {
if (domain=='gr') {
var dirtyKeys = ldapConfig.group;
}
else {
var dirtyKeys = ldapConfig.user;
}
static async peek<T>(domain: 'user'|'group', id: string, type: new () => T) : Promise<T> {
var dirtyKeys = ldapConfig[domain];
let cleanData : T = new type();
let attr = Object.keys(dirtyKeys).map(key => dirtyKeys[key]);
//console.log(attr);
......@@ -57,7 +50,6 @@ export class Tools {
return cleanData;
}
/**
* @memberof LDAP
* @summary Fonction qui retrouve les id des paxs ou groupes validant les critères de recherche. Etape vers vrai TOL (Trombino On Line).
......@@ -73,7 +65,7 @@ export class Tools {
* @static
* @async
*/
static async search(domain : "us"|"gr", data : userData|groupData) : Promise<string[]> {
static async search(domain: "user"|"group", 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) {
......@@ -83,8 +75,7 @@ export class Tools {
data[key].forEach(val => {
// Traduction en language LDAP
let attribute = "";
if (domain="us") { attribute = ldapConfig.user[key]; }
else { attribute = ldapConfig.group[key]; }
attribute = ldapConfig[domain][key];
// Escape user input
val = ldapEscape.filter("${fil}", { fil: val});
// Creation incrémentale du filtre
......@@ -109,15 +100,10 @@ export class Tools {
* @async
* @static
*/
static async edit(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;
}
static async edit(domain: "user"|"group", data: userData|groupData) : Promise<boolean> {
if (domain == "user") { var id=data['uid']; }
else { var id=data['gid']; }
var dirtyKeys=ldapConfig[domain];
// Rename in an LDAP-friendly way
let dirtyData = {};
Object.keys(data).forEach(function(key: string) {
......@@ -129,6 +115,109 @@ export class Tools {
return Basics.change(domain,id,"replace",dirtyData);
}
/**
* @memberof LDAP
* @summary Fonction qui permet d'ajouter un utilisateur à une catégorie d'un groupe.
* @desc Cette fonction fait essentiellement appel à d'autres fonctions de {@link Tools} passées en argument et {@link LDAP.change}.
* Cette fonction ne créé pas de doublon et opère conjointement dans les deux arbres "group" et "user"
* @arg {string} uid - Identifiant du futur membre
* @arg {string} gid - Identifiant du groupe
* @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower)
* @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
* @async
* @static
*/
static async add(uid: string, gid: string, category: string): Promise<boolean> {
try {
// Vérifie que l'utilisateur est pas déjà membre pour groupes
let lu = await Tools.get(gid, "group", category);
let catName = ldapConfig.group[category];
if (!lu.includes(uid)) {
// Ajoute l'utilisateur dans la catégorie concernée
if (!await Basics.change("group", gid, "add", catName)) {
throw "Erreur lors de la modification dans l'arbre des groupes pour ajouter un membre dans la catégorie voulue.";
}
}
}
catch(err) {
throw "Erreur pour obtenir une liste de membres d'une catégorie d'un groupe pour ajouter un membre de cette categorie du groupe.";
}
try {
// Vérifie que l'utilisateur est pas déjà membre pour user
let lg = await Tools.get(uid, "user", category);
let catName = ldapConfig.user[category];
if (!lg.includes(gid)) {
// Ajoute l'utilisateur dans la categorie voulue
if (!await Basics.change("user", uid, "add", catName)) {
throw "Erreur lors de l'ajout d'un utilisateur dans une catégorie d'un groupe.";
}
}
return true;
}
catch(err) {
throw "Erreur pour obtenir une liste de groupes d'une categorie d'un membre pour ajouter un groupe de cette category pour le membre.";
}
}
/**
* @memberof LDAP
* @summary Fonction qui permet de supprimer un membre d'une catégorie existant d'un groupe.
* @desc Cette fonction fait essentiellement appel à d'autres fonctions de {@link Tools} passées en argument et {@link LDAP.change}.
* @arg {string} uid - Identifiant de l'ex-membre
* @arg {string} gid - Identifiant du groupe
* @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower)
* @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
* @async
* @static
*/
static async remove(uid: string, gid: string, category : string): Promise<boolean> {
try {
// Vérifie que l'utilisateur est pas déjà viré pour groupes
let lu = await Tools.get(gid, "group", category);
let catName = ldapConfig.group[category];
if (lu.includes(uid)) {
// Supprime tous les utilisateurs
if (!await Basics.change("group", gid, "del", catName)) {
throw "Erreur lors de la suppression de tous les membres d'une catégorie du groupe.";
}
// Les rajoute un par un, sauf pour le supprimé
lu.forEach(id => {
if (id!=uid) {
Tools.add(id, gid, category).then(res => {
if (!res) { throw "Erreur lors du ré-ajout d'un autre membre d'une catégorie."; }
});
}
});
}
}
catch(err) {
throw "Erreur pour obtenir une liste de membres d'une catégorie d'un groupe pour supprimer un membre de cette categorie du groupe.";
}
try {
// Vérifie que l'utilisateur est pas déjà viré pour user
let lg = await Tools.get(uid, "user", category);
let catName = ldapConfig.user[category];
if (lg.includes(gid)) {
// Supprime tous les groupes de la catégorie pour l'utilisateur
if (!await Basics.change("user", uid, "del", catName)) {
throw "Erreur lors de la suppression de tous les groupes d'un membre.";
}
// Les rajoute un par un, sauf pour le supprimé
lg.forEach(id => {
if (id!=uid) {
Tools.add(id, gid, category).then(res => {
if (!res) { throw "Erreur lors du ré-ajout d'un autre groupe."; }
});
}
});
}
return true;
}
catch(err) {
throw "Erreur pour obtenir une liste de groupes d'une categorie d'un membre pour supprimer un groupe de cette category pour le membre.";
}
}
/**
* @callback changeValueCallback
* @param {string} id - Id à modifier
......@@ -151,7 +240,7 @@ export class Tools {
* @static
* @async
*/
static async ensureUnique(value: string, attribute: string, domain: 'gr'|'us', changeValue: (string, number) => string, n: number=0) : Promise<string> {
static async ensureUnique(value: string, attribute: string, domain: 'group'|'user', changeValue: (string, number) => string, n: number=0) : Promise<string> {
// Recherche d'autres occurences de l'id
try {
return Basics.searchSingle(domain, ldapConfig.key_id, null, "("+attribute+"="+value+")").then(function (matches: string[]) {
......@@ -181,7 +270,7 @@ export class Tools {
static async generateUid(givenName: string, lastName: string, promotion: string) : Promise<string> {
try {
// normalize et lowerCase standardisent le format
return Tools.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, "user", (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...
......@@ -205,7 +294,7 @@ export class Tools {
static async generateReadableId(name: string) : Promise<string> {
try {
// normalize et lowerCase standardisent le format
return Tools.ensureUnique(name.toLowerCase().normalize('UFD'), ldapConfig.key_id, "gr", (id: string, n: number) => {
return Tools.ensureUnique(name.toLowerCase().normalize('UFD'), ldapConfig.key_id, "group", (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;
......@@ -225,7 +314,7 @@ export class Tools {
* @static
* @async
*/
static async generateId(attribut: string, domain: "gr"|"us") : Promise<string> {
static async generateId(attribut: string, domain: "group"|"user") : Promise<string> {
try {
return Tools.ensureUnique("0", attribut, domain, (id,n) => { return Math.floor((Math.random() * 100000) + 1).toString(); });
}
......@@ -236,57 +325,43 @@ export class Tools {
/**
* @memberof LDAP
* @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
* @summary Fonction qui retrouve les utilisateurs ou groupes respectivement correspondant à un groupe ou un utilisateur de la même catégorie.
* @desc Cette fonction utilise {@link LDAP.search} et va directement à la feuille de l'utilisateur ou du groupe interrogé.
* Pour autant, elle est moins naïve qu'elle en a l'air. Elle gère la descente des admins et la remontée des membres.
* @param {string} id - Identifiant du groupe ou de l'individu à interroger (supposé valide)
* @param {"user"|"group"} domain - Arbre à interroger
* @param {"admins"|"speakers"|"members"|"followers"} category - Catégorie considérée
* @return {Promise(string[])} Liste des id de groupes ou d'utilisateurs de la bonne catégorie associé à l'id
* @static
* @async
*/
static async getGroups(uid: string) : Promise<string[]> {
static async get(id : string, domain : "user"|"group", category : string): Promise<string[]> { //"admins"|"speakers"|"members"|"followers") {
try {
return Basics.searchSingle("us", ldapConfig.user.groups, uid);
if (!(category in ["admins","members"]) || domain=="group") {
return await Basics.searchSingle(domain, ldapConfig[domain][category], id);
}
else {
// Clean depth-first search for inherited members and admins
let stack = [];
let res = [];
let visited = {};
stack.push(id);
while (stack.length>0) {
let cur_id = stack.pop();
if (visited[cur_id] == undefined) {
visited[cur_id] = true;
res.concat(await Basics.searchSingle("group", ldapConfig.group[category], cur_id));
// In the end, the precise category only changes the iteration direction
if (category == "members") { stack.concat(await Basics.searchSingle("group", ldapConfig.group.childs, cur_id)); }
else { stack.concat(await Basics.searchSingle("group", ldapConfig.group.parents, cur_id)); }
}
}
}
}
catch(err) {
throw "Erreur lors de la recherche des groupes d'un individu.";
}
}
/**
* @memberof LDAP
* @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)
* @static
* @async
*/
static async getMembers(gid: string) : Promise<string[]> {
try {
return Basics.searchSingle("gr", ldapConfig.group.members, gid);
}
catch(err) {
throw "Erreur lors de la recherche des membres d'un groupe.";
}
}
/**
* @memberof LDAP
* @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 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)
* @static
* @async
*/
static async getAdmins(gid: string) : Promise<string[]> {
try {
return Basics.searchSingle("gr", ldapConfig.group.admins, gid);
}
catch(err) {
throw "Erreur lors de la recherche des admins d'un groupe.";
}
}
/**
* @memberof LDAP
......@@ -300,8 +375,8 @@ export class Tools {
*/
static async isGroupMember(uid: string, gid: string) : Promise<boolean> {
try {
let lg = await Tools.getGroups(uid);
let lm = await Tools.getMembers(gid);
let lg = await Tools.get(uid, "user", "members");
let lm = await Tools.get(gid, "group", "members");
if (lg.includes(gid) && lm.includes(uid)) {
return true;
}
......@@ -324,8 +399,8 @@ export class Tools {
*/
static async isGroupAdmin(uid: string, gid: string) : Promise<boolean> {
try {
let lm = await Tools.getMembers(gid);
let la = await Tools.getAdmins(gid);
let lm = await Tools.get(uid, "user", "admins");
let la = await Tools.get(gid, "group", "admins");
if (la.includes(uid) && lm.includes(uid)) { return true; }
else { return false; }
}
......
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