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

Reorganisation LDAP

parent 182c9010
No related branches found
No related tags found
No related merge requests found
......@@ -27,6 +27,6 @@
"extensions": ["ts", "tsx"],
"babelrc": false,
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }], "@babel/typescript"],
"plugins": ["@babel/proposal-class-properties", "@babel/proposal-object-rest-spread"]
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/proposal-object-rest-spread"]
}
}
\ No newline at end of file
......@@ -9,55 +9,49 @@
"comment_3": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les utilisateurs",
"user": {
"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"
}
"uid": "uid",
"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",
"mails": "mail",
"ips": "brIP",
"forlifes": "brAlias",
"groups": "brMemberOf",
"schools": "brMemberOf",
"courses": "brMemberOf",
"classes": "objectClass"
},
"comment_4": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les groupes",
"group": {
"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"
}
"gid": "uid",
"name": "brAlias",
"type": "brNS",
"members": "restrictedMemberUid",
"admins": "memberUid",
"adress":"cn",
"idNumber": "uidNumber",
"idNumber2": "gidNumber",
"login": "loginShell",
"password": "userPassword",
"directory": "homeDirectory",
"cleanFullName": "gecos",
"classes": "objectClass"
},
"sessionSecret":"ozyNMHdT,WFTu|t"
}
\ No newline at end of file
Source diff could not be displayed: it is too large. Options to address this: view the blob.
......@@ -18,12 +18,13 @@
"license": "ISC",
"dependencies": {
"apollo-server-express": "^2.1.0",
"babel-cli": "^6.26.0",
"body-parser": "^1.18.3",
"colors": "^1.3.2",
"connect-ensure-login": "^0.1.1",
"connect-flash": "^0.1.1",
"cookie-parser": "^1.4.3",
"copy-webpack-plugin": "^4.5.3",
"copy-webpack-plugin": "^4.5.4",
"cors": "^2.8.4",
"dotenv": "^6.1.0",
"express": "^4.16.4",
......@@ -53,17 +54,17 @@
"url-loader": "^0.6.2"
},
"devDependencies": {
"@babel/cli": "^7.1.2",
"@babel/core": "^7.1.2",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-typescript": "^7.1.0",
"@babel/cli": "^7.0.0-beta.40",
"@babel/core": "^7.0.0-beta.40",
"@babel/plugin-proposal-class-properties": "^7.0.0-beta.40",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.40",
"@babel/preset-env": "^7.0.0-beta.40",
"@babel/preset-typescript": "^7.0.0-beta.40",
"@types/connect-ensure-login": "^0.1.4",
"@types/connect-flash": "0.0.34",
"@types/graphql": "^0.13.4",
"@types/knex": "^0.14.26",
"@types/node": "^10.11.7",
"@types/node": "^10.12.0",
"@types/passport": "^0.4.6",
"babel-eslint": "^8.2.6",
"eslint": "^4.19.1",
......@@ -74,12 +75,12 @@
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-standard": "^3.1.0",
"jsdoc": "^3.5.5",
"jsdoc-babel": "^0.4.0",
"jsdoc-babel": "^0.4.0-alpha.0",
"jsdoc-to-markdown": "^4.0.1",
"nodemon": "^1.18.4",
"ts-loader": "^5.2.1",
"ts-loader": "^5.2.2",
"typescript": "^3.1.3",
"webpack": "^4.20.2",
"webpack": "^4.21.0",
"webpack-cli": "^3.1.2",
"webpack-graphql-loader": "^1.0.0",
"webpack-node-externals": "^1.7.2"
......
......@@ -4,7 +4,8 @@
* La configuration inclut tout le _middleware_ définissant les API et les services
* nécessaire utilisés, comme `express-session`, GraphiQL, GraphQL Voyager.
*
* @todo changer cette description... ^
* @todo changer cette description... ^^
* @todo Passer les docstrings en FR
* @todo qu'arrive-t-il aux requetes avec un cookie expire? elles ne sont traitees ni par passport.session() ni par passport.authenticate('ldapauth')...
*
* @author manifold, kadabra
......@@ -24,7 +25,7 @@ import router from './routing/admin.router';
// packages pour l'authentification
import passport from 'passport';
import session from 'express-session';
import cookieParser from 'cookie-parser'; //hawkspar->manifold VSCode râle ici pr moi
import cookieParser from 'cookie-parser';
import cors from 'cors';
// packages divers
import favicon from 'serve-favicon';
......
......@@ -31,11 +31,12 @@ simples, dont les membres sont des utilisateurs, et les métagroupes, dont les m
des groupes simples (tel que Federez, dont les membres incluent le BR et DaTA).
"""
interface Group {
uid: ID
gid: ID!
name: String
# Site Web.
website: String
description: String
type: String
# Jour et heure de création du groupe.
createdAt: String!
......@@ -49,9 +50,7 @@ interface Group {
# Les questions addressees à ce groupe
questions: [Question]
# Les reponses donnees par ce groupe
answers: [Answer]
answers: [Answer]
}
# Le groupe de base, dont les membres sont des utilisateurs : binets, Kès...
......
# hawkspar->all ; doc ?
# Utilisateurs
type User {
# Prénom de l'utilisateur
givenName: String!
# Nom de famille
lastName: String!
# Surnom
nickname: String
nationality: String
uid: ID!
birthdate: String!
mail: String
phone: String
# Groupes dont l'utilisateur est membre.
groups: [SimpleGroup]
# Groupes que l'utilisateur aime.
likes: [Group]
# A terme rajouter aussi admin
# Adresse(s) de l'utilisateur.
address: [String]
# Promotion
promotion: String
photo: String
}
# Groupes associatifs
"""
L'interface Group représente les deux types de groupes implémentés dans Sigma : les groupes
simples, dont les membres sont des utilisateurs, et les métagroupes, dont les membres sont
des groupes simples (tel que Federez, dont les membres incluent le BR et DaTA).
"""
interface Group {
uid: ID
name: String
# Site Web.
website: String
description: String
# Jour et heure de création du groupe.
createdAt: String!
# Dernière mise à jour du groupe.
updatedAt: String!
# member requests
# Les posts prives dans ce groupe
privatePosts: [PrivatePost]
# Les questions addressees à ce groupe
questions: [Question]
# Les reponses donnees par ce groupe
answers: [Answer]
}
# Le groupe de base, dont les membres sont des utilisateurs : binets, Kès...
type SimpleGroup implements Group {
uid: ID
name: String
website: String
createdAt: String!
updatedAt: String!
# Admin, membres, sympathisants du groupe
admins: [User]
members: [User]
likers: [User]
description: String
# École d'origine du groupe
school: String
# Groupe parent
parent: Group
privatePosts: [PrivatePost]
questions: [Question]
answers: [Answer]
}
# Un groupe dont les membre sont d'autres groupes
type MetaGroup implements Group {
uid: ID
name: String
website: String
createdAt: String!
updatedAt: String!
description: String
# Les groupes constitutifs du méta-groupe.
members: [Group]!
privatePosts: [PrivatePost]
questions: [Question]
answers: [Answer]
}
# Tout type de message adressé à un ou plusieurs groupes.
# Auteur possible d'un Message
# union AuthorUnion = Group | [Group] | User
# union RecipientUnion = Group | [Group]
# Les unions sont assez faibles dans GraphQL,
# elles n'acceptent pas les listes ni les interfaces
# L'interface Message représente toute information que veut communiquer un groupe ou un user.
# Par choix de paradigme, tout Message est adressé à un (ou des) groupe(s).
# Les types implémentés sont divisés en deux :
# - les Message émanant d'un groupe : Announcement et Event, ainsi que Answer
# - les Message émanant d'un user : PrivatePost, ainsi que Question
interface Message {
id: ID!
# Titre du message
title: String!
content: String
createdAt: String!
updatedAt: String!
}
# Annonce publique effectuée par un ou plusieurs groupes.
type Announcement implements Message {
id: ID!
title: String!
createdAt: String!
updatedAt: String!
content: String!
importance: Int
views: Int
forEvent: Event
authors: [Group]
recipients: [Group]
}
# Événements organisés par un ou plusieurs groupes.
type Event implements Message {
id: ID!
# Intitulé de l'événement
title: String!
# Lieu de l'événement
location: String
createdAt: String!
updatedAt: String!
startTime: String!
endTime: String!
# Organisateurs
# Personnes qui participent à l'événement.
participatingGroups: [Group]
participatingUsers: [User]
content: String
asAnnouncement: Announcement
authors: [Group]
recipients: [Group]
}
# Post interne d'un membre sur la page interne de son groupe
type PrivatePost implements Message {
id: ID!
createdAt: String!
updatedAt: String!
title: String!
content: String!
authors: User
recipients: Group
}
# Question posée par un user à un groupe
type Question implements Message {
id: ID!
createdAt: String!
updatedAt: String!
title: String!
content: String!
authors: User
recipients: Group
# Une annonce éventuellement concernée par cette question.
# Null si la question ne concerne pas une annonce particulière
forAnnouncement: Announcement
# Référence la réponse donnée par le groupe à cette Question. Si pas encore répondu, null.
forAnswer: Answer
}
# Réponse à une Question
type Answer implements Message {
id: ID!
createdAt: String!
updatedAt: String!
title: String!
content: String!
authors: Group
recipients: Group
# La question à laquelle cette Answer répond. Non-nullable bien sûr
forQuestion: Question!
}
interface Request {
# ID de la demande
id: ID!
# message accompagnant la demande
message: String
}
# Demande d'un utilisateur désirant rejoindre le groupe.
type UserJoinGroup implements Request{
id: ID!
message: String
# Émetteur de la demande
user: User
}
# Demande d'un groupe voulant rejoindre un événement
type GroupJoinEvent implements Request{
id: ID!
message: String
# Événement concerné
event: Event
# Groupe voulant rejoindre l'événement
groupWantingToJoin: Group
}
# Demande au récipiendaire de rejoindre l'organisation d'un événement.
type YourGroupHostEvent implements Request{
id: ID!
message: String
# Événement concerné
event: Event
# Groupe ayant publié l'évènement et lancé l'invitation
sender: Group
}
......@@ -76,16 +76,20 @@ export class LDAP {
* @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit les valeurs trouvées.
* @desc Cette fonction utilise ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode search). Cette fonction fait une demande au LDAP
* qu'elle filtre selon un schéma prédéfini dans `filter` et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end).
* @arg {string} dn - DN de l'emplacement de la requête
* @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape
* @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur)
* @arg {string[]} attributes - Liste des attributs qui figureront dans le résultat final ; peut aussi être un seul élément
* @arg {string} id [null] - Identifiant facultatif pour une recherche triviale en o(1)
* @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape
* @return {(Promise(Array.<Object>)|Promise(Array.Object.<string, Object>))} Résultats de la recherche ; soit une liste de valeurs d'attributs,
* soit une liste de dictionnaires si on veut plus d'un attribut (les clés du dictionnaire sont celles du LDAP)
* @static
* @async
*/
static async search(dn: string, attributes: string[], filter="(objectClass=*)") : Promise<Array<any>> {
static async search(domain: 'gr'|'us', attributes: string[], id=null, filter="(objectClass=*)") : Promise<Array<any>> {
LDAP.adminBind();
if (domain == "gr") { var dn = ldapConfig.dn_groups; }
else { var dn = ldapConfig.dn_users; }
if (id != null) { dn=ldapConfig.key_id+'='+id+','+dn; }
let vals=[];
// Interrogation LDAP selon ldapConfiguration fournie en argument
client.search(ldapEscape.dn("${txt}", { txt: dn}), {
......@@ -123,7 +127,8 @@ export class LDAP {
/**
* @summary Fonction qui permet de modifier un élément sur le LDAP. Gestion intelligente de l'appartenance à un binet.
* @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode modify).
* @arg {string} dn - DN de l'endroit à modifier
* @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,
* "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.
......@@ -132,8 +137,11 @@ export class LDAP {
* @static
* @async
*/
static async change(dn: string, op: string, mod) : Promise<boolean> {
static async change(domain: 'gr'|'us', id: string, op: string, 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}),
......@@ -150,15 +158,17 @@ export class LDAP {
/**
* @summary Fonction qui permet de rajouter un élément sur le LDAP.
* @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode add).
* @arg {string} dn - Adresse du parent
* @arg {Object.<string, string>} vals - Dictionnaire contenant les valeurs à créer
* @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur)
* @arg {Object.<string, string>} vals - Dictionnaire contenant les valeurs à créer (contient un champ en ldapConfig.key_id)
* @arg {Object} vals[key] - Nouvelle valeur pour le champ key
* @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon.
* @static
* @async
*/
static async add(dn: string, vals) : Promise<boolean> {
static async add(domain: 'gr'|'us', vals) : Promise<boolean> {
LDAP.adminBind();
if (domain == "gr") { var dn = ldapConfig.dn_groups }
else { var dn = ldapConfig.dn_users }
// 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.";
......@@ -172,13 +182,17 @@ export class LDAP {
* @summary Fonction qui permet de supprimer une feuille du LDAP.
* @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode del).
* Elle est différente de modify avec "del" car elle affecte directement une feuille et pas un attribut.
* @arg {string} dn - Adresse de la cible
* @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur)
* @arg {string} id - Identifiant unique de la cible
* @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
* @static
* @async
*/
static async clear(dn: string) : Promise<boolean> {
static async clear(domain: 'gr'|'us', id: string) : Promise<boolean> {
LDAP.adminBind();
let dn = ldapConfig.key_id+'='+id+','
if (domain == "gr") { dn+=ldapConfig.dn_groups }
else { dn+=ldapConfig.dn_users }
// Suppression LDAP
client.del(ldapEscape.dn("${txt}", {txt: dn}), err => {
throw "Erreur lors d'une opération de suppression sur le LDAP.";
......
This diff is collapsed.
This diff is collapsed.
......@@ -6,122 +6,7 @@
import {ldapConfig} from './config';
import {LDAP} from './basics';
/**
* @interface searchUserFields
* @desc Interface permettant la recherche d'un utilisateur avec des champs incomplets. Plusieurs valeurs sont possibles pour le même champ.
* Aucun de ces champs n'est obligatoire, mais certains de ces champs doivent être exacts pour obtenir un bon résultat.
* @var {string|string[]} givenName - Prénom(s)
* @var {string|string[]} lastName - Nom(s)
* @var {string|string[]} nickname - Surnom(s)
* @var {string|string[]} nationality - Nationalité(s) (à implémenter)
* @var {string|string[]} promotion - Année(s) de promo
* @var {string|string[]} phone - Numéro(s) de téléphone
* @var {string|string[]} mail - Adresse(s) courriel
* @var {string|string[]} ip - Adresse(s) ip
* @var {string|string[]} adress - Adresse(s)
* @var {string} school - Ecole d'appartenance (instable, doit être exact)
* @var {string|string[]} groups - Un ou plusieurs groupes dont l'utilisateur est membre (doit être exact).
* @var {string} course - PA ou autre. Doit être exact.
*/
export interface searchUserFields {
givenName: string,
lastName: string,
nickname: string,
nationality: string,
promotion: string,
phone: string,
mail: string,
ip: string,
adress: string,
school: string,
groups: string[],
studies: string,
sport: string
}
//------------------------------------------------------------------------------------------------------------------------
// Fonctions de recherche
//------------------------------------------------------------------------------------------------------------------------
export class SmartSearch {
/**
* @class Cette classe contient des fonctions de recherche génériques trop puissantes pour être exportées tel quel.
* @summary Constructeur vide.
* @author hawkspar
*/
constructor() {}
/**
* @summary Fonction qui interroge le LDAP et retrouve les groupes (voir LDAP) qui ressemblent
* à l'entrée. Etape 0 vers un vrai TOL (Trombino On Line).
* @desc Cette fonction utilise {@link LDAP.search} mais avec un filtre généré à la volée.
* Accepte des champs exacts ou incomplets mais pas approximatifs
* et ne gère pas l'auto-complete. Cette fonction utilise aussi ldapConfig.json. MEF Timeout pour
* des recherches trop vagues. Renvoit une liste d'uid.
* Elle utilise LDAPEscape pour éviter les injections.
* @arg {string} input - String entré par l'utilisateur qui ressemble au nom du groupe.
* @arg {string[]} return_attributes - Liste d'attributs à renvoyer dans le résultat final
* @return {Promise(string[])} Liste des uid de groupes dont le nom ressemble à l'input
* @static
* @async
*/
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 *
"(|("+ldapConfig.key_id+"=*"+input+"*)"+ // La valeur du milieu avec des trucs avant et après
"("+ ldapConfig.key_id+"="+ input+"*))))"; // La valeur du début avec des trucs après
// Appel rechercheLDAP avec filtre de l'espace
try {
return LDAP.search(ldapConfig.dn_groups, return_attributes, filter);
}
catch(err) {
throw "Erreur lors de la recherche intelligente d'un groupe.";
}
}
/**
* @summary Fonction qui renvoit les attributs demandés des paxs validant les critères de recherche. Première étape vers vrai TOL (Trombino On Line).
* @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. Elle utilise LDAPEscape pour éviter les injections.
* Utiliser trouverGroupesParTypes pour chaque champ relié à groups.
* @arg {searchUserFields} 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
* de plusieurs groupes) ou des éléments isolés. Si un champ n'est pas pertinent, le mettre à '' ou undefined.
* @arg {string[]} return_attributes - Liste d'attributs à renvoyer dans le résultat final.
* @return {Promise(Object[])} Liste de dictionnaires de profils en cohérence avec l'input avec pour clés les attributs des profils.
* @static
* @async
*/
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) {
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
try {
return LDAP.search(ldapConfig.dn_users, return_attributes, filter);
}
catch(err) {
throw "Erreur lors de la recherche intelligente d'un utilisateur.";
}
}
}
import {Group} from './group';
//------------------------------------------------------------------------------------------------------------------------
// Fonctions intermédiaires TBT
......@@ -148,7 +33,7 @@ export class Tests {
* dans le dn fourni.
* @param {string} value - Valeur de l'attribut (le plus souvent un identifiant) à tester à cette itération
* @param {string} attribute - Attribut à tester
* @param {string} dn - *Domain Name* dans lequel l'attribut doit être unique
* @param {"gr"|"us"} domain - Domaine dans lequel l'attribut doit être unique
* @param {changeValueCallback} changeValue - Fonction qui prend uniquement en argument l'id courant et
* le nombre d'itérations et qui renvoit la prochaine valeur de l'attribut
* @param {int} n [0] - Nombre d'itérations (à initialiser à 0)
......@@ -156,15 +41,15 @@ export class Tests {
* @static
* @async
*/
static async ensureUnique(value: string, attribute: string, dn: string, changeValue: (string, number) => string, n=0) : Promise<string> {
static async ensureUnique(value: string, attribute: string, domain: 'gr'|'us', 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[]) {
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; }
// Sinon, on tente de nouveau notre chance avec la valeur suivante
else { return Tests.ensureUnique(changeValue(value, n+1), attribute, dn, changeValue, n+1); }
else { return Tests.ensureUnique(changeValue(value, n+1), attribute, domain, changeValue, n+1); }
});
}
catch(err) {
......@@ -185,7 +70,7 @@ export class Tests {
static async generateUid(givenName: string, lastName: string, promotion: string) : Promise<string> {
try {
// normalize et lowerCase standardisent le format
return this.ensureUnique((givenName+'.'+lastName).toLowerCase().normalize('UFD'), ldapConfig.key_id, ldapConfig.dn_users, (id: string, n: number) => {
return this.ensureUnique((givenName+'.'+lastName).toLowerCase().normalize('UFD'), ldapConfig.key_id, "us", (id: string, n: number) => {
if (n==1) { id+='.'+promotion; } // Si prénom.nom existe déjà, on rajoute la promo
else if (n==2) { id+='.'+(n-1).toString(); } // Puis si prénom.nom.promo existe déjà on passe à nom.prenom.promo .1
else if (n>2) { id+=n; } // Ensuite on continue .123, .1234, etc...
......@@ -208,7 +93,7 @@ export class Tests {
static async generateReadableId(name: string) : Promise<string> {
try {
// normalize et lowerCase standardisent le format
return this.ensureUnique(name.toLowerCase().normalize('UFD'), ldapConfig.key_id, ldapConfig.dn_groups, (id: string, n: number) => {
return this.ensureUnique(name.toLowerCase().normalize('UFD'), ldapConfig.key_id, "gr", (id: string, n: number) => {
if (n==1) { id+='.'+n.toString(); } // Si nom existe déjà, on essaie nom.1
else if (n>1) { id+=n.toString(); } // Ensuite on continue .12, .123, etc...
return id;
......@@ -222,17 +107,122 @@ export class Tests {
/**
* @summary Cette fonction teste une valeur dummy (0) pour un identifiant numérique puis le fait évoluer aléatoirement (entre 1 et 100 000) jusqu'à ce qu'il soit unique.
* @param {string} attribut - Intitulé exact de l'id concerné
* @param {string} dn - *Domain Name* dans lequel l'attribut doit être unique
* @param {"gr"|"us"} domain - Domaine dans lequel l'attribut doit être unique
* @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié
* @static
* @async
*/
static async generateId(attribut: string, dn: string) : Promise<string> {
static async generateId(attribut: string, domain: "gr"|"us") : Promise<string> {
try {
return this.ensureUnique("0", attribut, dn, (id,n) => { return Math.floor((Math.random() * 100000) + 1).toString(); });
return this.ensureUnique("0", attribut, domain, (id,n) => { return Math.floor((Math.random() * 100000) + 1).toString(); });
}
catch(err) {
throw "Erreur lors de l'assurance de l'unicité d'un unique identifier numérique.";
}
}
/**
* @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: string) {
try {
return LDAP.search("us", [ldapConfig.user.groups], uid)[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 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) {
try {
return LDAP.search("gr", [ldapConfig.group.members], gid)[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 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) {
try {
return LDAP.search("gr", [ldapConfig.group.admins], gid)[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: string, gid: string) {
try {
let lg = await Tests.getGroups(uid);
let lm = await Tests.getMembers(gid);
if (lg.includes(gid) && lm.includes(uid)) {
return true;
}
// Réalignement forcé
else if (lg.includes(gid) || lm.includes(uid)) {
Group.addMember(uid, gid);
return true;
}
else { return false; }
}
catch(err) {
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: string, gid: string) {
try {
let lm = await Tests.getMembers(gid);
let la = await Tests.getAdmins(gid);
if (la.includes(uid) && lm.includes(uid)) { return true; }
// Réalignement forcé
else if (la.includes(uid)) {
Group.addAdmin(uid, gid);
return true;
}
else { return false; }
}
catch(err) {
throw "Erreur lors du test d'appartenance au bureau d'administration un groupe.";
}
}
}
\ No newline at end of file
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