diff --git a/README.md b/README.md index 0252299cfd73570d02d228f8dc7d0bd650921dd5..16a92272a1d0d7d9eb93d215adb02863633b7bb4 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,6 @@ On peut définir ces variables d'environnement, **dans l'ordre décroissant de p ... ``` - ## Panneau d'administration Il est accessible par navigateur au path `/adminview/admin` ; n'importe quel path devrait rediriger dessus. diff --git a/configfile_doc.json b/configfile_doc.json index 3c4db54b4e44b38898249152e450588b681da74e..e2a217abfdb2c74f3bf9714af0d2a26008ccbd10 100644 --- a/configfile_doc.json +++ b/configfile_doc.json @@ -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 diff --git a/ldap_config.json b/ldap_config.json index 57370d5e8279fa05723aa4f9c3e9290246b161d7..03484ee0cd6facfe2060d30a43f8ced9107d6672 100644 --- a/ldap_config.json +++ b/ldap_config.json @@ -9,54 +9,46 @@ "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", + "password": "userPassword", + "idNum": "gidNumber", + "directory": "homeDirectory", + "login": "loginShell", + "readPerm": "brNewsReadAccess", + "writePerm": "brNewsPostAccess", + "mail": "mail", + "ips": "brIP", + "forlifes": "brAlias", + "groups": "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 diff --git a/package-lock.json b/package-lock.json index cc9e93eb3715ad11a37bfa3f82476fecb1cc041c..1afc35330e24ddb34796d256b64734cb391dee76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4748,13 +4748,21 @@ } }, "cross-fetch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.0.0.tgz", - "integrity": "sha512-gnx0GnDyW73iDq6DpqceL8i4GGn55PPKDzNwZkopJ3mKPcfJ0BUIXBsnYfJBVw+jFDB+hzIp2ELNRdqoxN6M3w==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.2.tgz", + "integrity": "sha1-pH/09/xxLauo9qaVoRyUhEDUVyM=", "dev": true, "requires": { - "node-fetch": "2.0.0", - "whatwg-fetch": "2.0.3" + "node-fetch": "2.1.2", + "whatwg-fetch": "2.0.4" + }, + "dependencies": { + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==", + "dev": true + } } }, "cross-spawn": { @@ -6859,12 +6867,12 @@ } }, "graphql-request": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-1.6.0.tgz", - "integrity": "sha512-qqAPLZuaGlwZDsMQ2FfgEyZMcXFMsPPDl6bQQlmwP/xCnk1TqxkE1S644LsHTXAHYPvmRWsIimfdcnys5+o+fQ==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-1.8.2.tgz", + "integrity": "sha512-dDX2M+VMsxXFCmUX0Vo0TopIZIX4ggzOtiCsThgtrKR4niiaagsGTDIHj3fsOMFETpa064vzovI+4YV4QnMbcg==", "dev": true, "requires": { - "cross-fetch": "2.0.0" + "cross-fetch": "2.2.2" } }, "graphql-subscriptions": { @@ -8799,9 +8807,9 @@ } }, "node-fetch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.0.0.tgz", - "integrity": "sha1-mCu6Q+zU8pIqKcwYamu7C7c/y6Y=", + "version": "2.1.2", + "resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=", "dev": true }, "node-libs-browser": { @@ -9423,7 +9431,7 @@ }, "semver": { "version": "4.3.2", - "resolved": "http://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" } } diff --git a/src/graphql/typeDefs/objects.graphql b/src/graphql/typeDefs/objects.graphql index 47ba8ea4b15433890952cfcfeb72fcaa39383d89..eca057718b39a57b80ded07094d43ecc3a694da3 100644 --- a/src/graphql/typeDefs/objects.graphql +++ b/src/graphql/typeDefs/objects.graphql @@ -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... diff --git a/src/graphql/typeDefs/objects_ldap.graphql b/src/graphql/typeDefs/objects_ldap.graphql new file mode 100644 index 0000000000000000000000000000000000000000..3efc076abd01ff4b13d99061bbafc977fa3d9e64 --- /dev/null +++ b/src/graphql/typeDefs/objects_ldap.graphql @@ -0,0 +1,243 @@ +# 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. + addresses: [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 +} diff --git a/src/ldap/admins.ts b/src/ldap/admins.ts deleted file mode 100644 index 4b08db197a27ca14b23cc5ee90993d955c593960..0000000000000000000000000000000000000000 --- a/src/ldap/admins.ts +++ /dev/null @@ -1,410 +0,0 @@ -/** - * @file Ce fichier regroupe les différentes classes avec différents admins. Ces classes sont dédiées à être exportées directement pour être utilisées par le solver. - * Le découpage par fichier est arbitraire mais permet de regrouper certaines classes proches. - * @author hawkspar - */ - -import {ldapConfig} from './config'; -import {LDAP} from './basics'; -import {Tests} from './utilities'; -import {Open, User, userData, groupData} from './users'; - -export class Admin extends User { - /** - * @class Cette classe est la classe de l'administrateur d'un groupe qui lui permet de rajouter des membres, en supprimer, idem pour des admins, - * ou éditer, voir supprimer le groupe. - * @summary Ce constructeur appelle simplement le constructeur de sa classe mère. - */ - constructor() { super(); } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de relation TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui permet de rajouter un membre déjà créé à un groupe. - * @desc Cette fonction fait essentiellement appel à {@link LDAP.modifier} et {@link listerGroupes}. Elle n'autorise pas les doublons et opère dans les deux dns users - * et groups. - * @arg {string} uid - Identifiant du futur membre - * @arg {string} gid - Identifiant du groupe - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - 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); - if (!lm.includes(uid)) { - let vals = {}; - vals[ldapConfig.groups.member] = uid; - // Erreur si pb lors de la modification - if (!await LDAP.change(ldapConfig.key_id+gid+ldapConfig.dn_groups, "add", vals)) { - throw "Erreur lors de la modification dans l'arbre des groupes pour ajouter un membre."; - } - } - } - catch(err) { - throw "Erreur lors de la recherche de la liste des membres pour ajouter un membre."; - } - try { - // Vérifie que l'utilisateur est pas déjà membre pour users - let lg = await Open.getGroups(uid); - if (!lg.includes(gid)) { - let vals2 = {}; - vals2[ldapConfig.users.groups] = gid; - // Erreur si pb lors de la modification - if (!await LDAP.change(ldapConfig.key_id+uid+ldapConfig.dn_users, "add", vals2)) { - throw "Erreur lors de la modification dans l'arbre des utilisateurs pour ajouter un membre."; - } - } - return true; - } - catch(err) { - throw "Erreur lors de la recherche de la liste des membres pour ajouter un membre."; - } - } - - /** - * @summary Fonction qui permet de supprimer un membre existant d'un groupe. - * @desc Cette fonction fait essentiellement appel à {@link LDAP.search}, {@link LDAP.change}, {@link Open.getGroups} et {@link Open.getMembers}. - * @arg {string} uid - Identifiant de l'ex-membre - * @arg {string} gid - Identifiant du groupe - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - 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); - if (lm.includes(uid)) { - // Supprime tous les utilisateurs - if (!await LDAP.change(ldapConfig.key_id+gid+ldapConfig.dn_groups, "del", ldapConfig.group.member)) { - 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) { - this.addGroupMember(id, gid).then(res => { - if (!res) { throw "Erreur lors du ré-ajout des autres membres"; } - }); - } - }); - } - } - catch(err) { - throw "Erreur pour obtenir une liste de membres d'un groupe pour supprimer un membre du groupe."; - } - try { - let lg = await Open.getGroups(uid); - // Vérifie que l'utilisateur est pas déjà viré pour users - if (lg.includes(gid)) { - // Supprime tous les groupes - if (!await LDAP.change(ldapConfig.key_id+uid+ldapConfig.dn_users, "del", ldapConfig.member.groups)) { - throw "Erreur lors de la suppression de tous les groupes du membre."; - } - // Les rajoute un par un, sauf pour le supprimé - lg.forEach(id => { - if (id!=gid) { - this.addGroupMember(uid, id).then(res => { - if (!res) { throw "Erreur lors du ré-ajout des autres groupes"; } - }); - } - }); - } - return true; - } - catch(err) { - throw "Erreur pour obtenir une liste de groupes d'un membres pour le supprimer du groupe."; - } - } - - /** - * @summary Fonction qui permet de promouvoir membre au stade d'administrateur d'un groupe. - * @desc Cette fonction fait essentiellement appel à {@link Admin.addGroupMember} {@link LDAP.change} et {@link Open.getAdmins}. Elle n'autorise pas - * les doublons et opère dans les deux dns users et groups. - * @arg {string} uid - Identifiant du futur membre - * @arg {string} gid - Identifiant du groupe - * @return {boolean} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - 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 { - let la = await Open.getAdmins(gid); - if (!la.includes(uid)) { - // Finalement modification, uniquement dans groups - let vals = {}; - vals[ldapConfig.groups.admin] = uid; - if (!await LDAP.change(ldapConfig.key_id+gid+ldapConfig.dn_groups, "add", vals)) { - throw "Erreur lors de l'ajout de l'admin dans l'arbre des groupes."; - } - } - return true; - } - catch(err) { - throw "Erreur lors de l'obtention de la liste des administrateurs d'un groupe."; - } - } - - /** - * @summary Fonction qui permet de rétrograder un membre du stade d'administrateur d'un groupe au stade d'utilisateur. - * @desc Cette fonction fait essentiellement appel à {@link LDAP.change}. - * @arg {string} uid - Identifiant du futur membre - * @arg {string} gid - Identifiant du groupe - * @return {boolean} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - 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 { - // Vérifie que l'utilisateur est bien admin (comme dans delGroupMember) - let la = await Open.getAdmins(gid); - if (la.includes(uid)) { - // Supprime tous les administrateurs - if (!await LDAP.change(ldapConfig.key_id+gid+ldapConfig.dn_groups, "del", ldapConfig.group.admin)) { throw "Erreur dans la suppression de tous les admins pour en supprimer un."; } - // Les rajoute un par un, sauf pour le supprimé - la.forEach(id => { - if (id!=uid) { Admin.addGroupAdmin(id, gid).then(res => { - if (!res) { throw "Erreur dans le réajout d'un des autres admins."; } - }); } - }); - } - return true; - } - catch(err) { - throw "Erreur lors de l'obtention de la liste des administrateurs d'un groupe."; - } - } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonction d'édition TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @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 {string} gid - Identifiant du groupe à modifier - * @arg {groupData} data - Dictionnaire des informations utilisateurs au même format que pour {@link User.addGroup} avec tous les champs optionnels. - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async editGroup(gid: string, data: groupData) : Promise<boolean> { - try { - // Récupération des anciennes données - let profil = await Open.peekGroup(gid); - // Reecriture de profil avec les bons champs - Object.keys(profil).forEach(keyLDAP => { - Object.keys(ldapConfig.group).forEach(keyAlias => { - ldapConfig.group[keyAlias]=keyLDAP; - profil[keyAlias]=profil[keyLDAP]; - }); - }); - // Surcharge des champs à modifier selon data - Object.keys(data).forEach(key => { - profil[key]=data[key]; - }); - // Modification propre - if (!await Admin.delGroup(gid)&&await User.addGroup(profil)) { throw "Erreur de la destruction/recréation du groupe pour le modifier."; } - return true; - } - catch(err) { - throw "Erreur lors de l'obtention du profil d'un groupe pour le modifier."; - } - } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de suppression TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui supprime un groupe 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 Admin.delGroupMember} et {@link Admin.delGroupAdmin} pour gérer les groupes de l'utilisateur sortant. - * @arg {string} gid - gid de la victime - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async delGroup(gid): Promise<boolean> { - try { - // Gestion des membres et administrateurs d'abord - let profil = await Open.peekGroup(gid); - // Ordre important - profil[ldapConfig.group['admin']].forEach( id => { - this.delGroupAdmin( id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un admin d'un groupe en cours de suppression."; } }); - }); - profil[ldapConfig.group['member']].forEach(id => { - this.delGroupMember(id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un membre."; } }); - }); - // Elimination - if (!await LDAP.clear(ldapConfig.key_id+"="+gid+","+ldapConfig.dn_groups)) { throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes."; } - return true; - } - catch(err) { - throw "Erreur lors de l'obtention du profil d'un groupe pour le supprimer."; - } - } -} - -export class Supervisor extends Admin { - /** - * @class Cette classe est la classe du super administrateur qui créé et supprime des membres. - * @summary Constructeur vide. - * @author hawkspar - */ - constructor(user) { super(); } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de création TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @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 {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: userData): Promise<boolean> { - // Calcul d'un dictionnaire d'ajout - let vals = {}; - - // uid de base généré à partir de nom et prénom, plus potentiellement promo et un offset - // MEF mélange de Promise et de fonction standard - try { - Tests.generateUid(data['givenName'],data['lastName'],data['promotion']).then(id => { vals[ldapConfig.key_id]=id; } ); - } - catch(err) { - 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.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+"="+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.forEach(key_att => { - // On rajoute chaque valeur en entrée - data[key_att].forEach(val => { - let vals2 = {}; - 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."; } - }); - }); - }); - - // Certains champs nécessitent de petits calculs - let vals3={}; - - // Création d'un nom complet lisible - 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.single['password']] = "{CRYPT}"+data['password']; - - // Ecriture d'un surnom s'il y a lieu - if ((data['nickname']!=undefined) && (data['nickname']!='')) { - vals3[ldapConfig.user.single['nickname']]=data['nickname']; - } - try { - // Génération id aléatoire unique - 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."; - } - - // Stockage machine ; dépend du prénom - vals3[ldapConfig.user['directory']] = '/hosting/users/' + data['givenName'][0]; - - // Code root - vals3[ldapConfig.user.single['cleanFullName']]=data['fullName'].replace(':', ';').toLowerCase().normalize('UFD'); - - // Adressage root - 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.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.single['idNum']] ='5000'; - - // Inscription des valeurs calculées - 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.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(uid, gid).then(res => { - if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe."; } - }); - }); - data['groupsIsAdmin'].forEach(gid => { - Admin.addGroupAdmin(uid, gid).then(res => { - if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe en tant qu'admin."; } - }); - }); - - return true; - } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de suppression TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @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 Admin.delGroupMember} et {@link Admin.delGroupAdmin} 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 - * @static - */ - static async delUser(uid: string): Promise<boolean> { - try { - // Gestion des groupes d'abord - let profil = await Open.peekUser(uid); - 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'appartenance à un groupe de l'utilisateur."; } - }); - } - catch(err) { - throw "Erreur lors de l'obtention des informations de l'utilisateur à supprimer."; - } - // 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 9e29669db22816ed92d17484f6f213192aa79d5f..1ce31791fd8ac0d6b0cc1e9fe89337007d6225bc 100644 --- a/src/ldap/basics.ts +++ b/src/ldap/basics.ts @@ -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}), { @@ -112,10 +116,11 @@ 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 et on renvoit la liste - res.on('end', res => { LDAP.unbind(); }); + // Si la recherche est finie on se déconnecte + res.on('end', _ => { LDAP.unbind(); }); } }); + // On renvoit le résultat return vals; } @@ -123,8 +128,9 @@ 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 {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 {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur) + * @arg {string} id - Identifiant unique de la feuille à modifier + * @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. @@ -132,11 +138,14 @@ export class LDAP { * @static * @async */ - static async change(dn: 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 => { @@ -150,15 +159,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 +183,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."; diff --git a/src/ldap/group.ts b/src/ldap/group.ts new file mode 100644 index 0000000000000000000000000000000000000000..91dbaf6324cdca1a40f42888061a2e501a86a272 --- /dev/null +++ b/src/ldap/group.ts @@ -0,0 +1,381 @@ +/** + * @file Ce fichier contient la classe de l'API du LDAP qui gère les opérations sur les groupes. + * @author hawkspar + */ + +import {ldapConfig} from './config'; +import {LDAP} from './basics'; +import {Tools} from './utilities'; + +/** + * @interface groupData + * @var {string} gid - Identifiant du groupe + * @var {string} name - Nom du groupe + * @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) + */ +export interface groupData { + "gid": string, + "name": string, + "type": string, + "members": string[], + "admins": string[], + "description"?: string +} + +//------------------------------------------------------------------------------------------------------------------------ +// Classes à exporter TBT +//------------------------------------------------------------------------------------------------------------------------ + +export class Group { + /** + * @class Cette classe est une des deux classes exportables permettant de faire des opérations sur les groupes. + * @summary Constructeur vide. + */ + constructor() {} + + /** + * @summary Fonction qui renvoit toutes les infos relatives à un groupe particulier. + * @desc Cette fonction utilise {@link Tools.genericPeek} avec l'interface {@link groupData}. + * @arg {string} gid - Identifiant du groupe + * @return {Promise(groupData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe ; + * voir `ldap_ldapConfig.json`(..\..\ldap_ldapConfig.json) pour les clés exactes. + * @static + * @async + */ + static async peek(gid: string) : Promise<groupData> { + try { + return Tools.genericPeek<groupData>("gr", gid); + } + catch(err) { + throw "Erreur lors d'une recherche d'informations sur un groupe."; + } + } + + /** + * @summary Fonction qui retrouve le groupe qui ressemblent à l'input et qui correspond au type fourni. Etape 0 vers un vrai TOL (Trombino On Line). + * @desc Cette fonction utilise {@link 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. + * @arg {string} input - String entré par l'utilisateur qui ressemble au nom du groupe. + * @return {Promise(string[])} Liste des gid dont le nom ressemble à l'input. + * @static + * @async + */ + static async search(input: string) : Promise<string[]> { + try { + return Tools.genericSearch("gr", { + "gid": input, + "name": "", + "type": "", + "members": [], + "admins": [] + }); + } + catch(err) { + throw "Erreur lors de la recherche approximative d'un groupe."; + } + } + + static async addMember(uid: string, gid: string) : Promise<boolean> { + try { + // Vérifie que l'utilisateur est pas déjà membre pour groupes + let lm = await Tools.getMembers(gid); + if (!lm.includes(uid)) { + let vals = {}; + vals[ldapConfig.group.members] = uid; + // Erreur si pb lors de la modification + if (!await LDAP.change("gr", gid, "add", vals)) { + throw "Erreur lors de la modification dans l'arbre des groupes pour ajouter un membre."; + } + } + } + catch(err) { + throw "Erreur lors de la recherche de la liste des membres pour ajouter un membre."; + } + try { + // Vérifie que l'utilisateur est pas déjà membre pour users + let lg = await Tools.getGroups(uid); + if (!lg.includes(gid)) { + let vals2 = {}; + vals2[ldapConfig.user.groups] = gid; + // Erreur si pb lors de la modification + if (!await LDAP.change("us", uid, "add", vals2)) { + throw "Erreur lors de la modification dans l'arbre des utilisateurs pour ajouter un membre."; + } + } + return true; + } + catch(err) { + throw "Erreur lors de la recherche de la liste des membres pour ajouter un membre."; + } + } + + /** + * @summary Fonction qui permet de supprimer un membre existant d'un groupe. + * @desc Cette fonction fait essentiellement appel à {@link LDAP.search}, {@link LDAP.change}, {@link Open.getGroups} et {@link Open.getMembers}. + * @arg {string} uid - Identifiant de l'ex-membre + * @arg {string} gid - Identifiant du groupe + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remMember(uid: string, gid: string): Promise<boolean> { + try { + // Vérifie que l'utilisateur est pas déjà viré pour groupes + let lm = await Tools.getMembers(gid); + if (lm.includes(uid)) { + // Supprime tous les utilisateurs + if (!await LDAP.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) { + this.addMember(id, gid).then(res => { + if (!res) { throw "Erreur lors du ré-ajout des autres membres"; } + }); + } + }); + } + } + catch(err) { + throw "Erreur pour obtenir une liste de membres d'un groupe pour supprimer un membre du groupe."; + } + try { + let lg = await Tools.getGroups(uid); + // Vérifie que l'utilisateur est pas déjà viré pour users + if (lg.includes(gid)) { + // Supprime tous les groupes + if (!await LDAP.change("us", uid, "del", ldapConfig.user.groups)) { + throw "Erreur lors de la suppression de tous les groupes du membre."; + } + // Les rajoute un par un, sauf pour le supprimé + lg.forEach(id => { + if (id!=gid) { + this.addMember(uid, id).then(res => { + if (!res) { throw "Erreur lors du ré-ajout des autres groupes"; } + }); + } + }); + } + return true; + } + catch(err) { + throw "Erreur pour obtenir une liste de groupes d'un membres pour le supprimer du groupe."; + } + } + + /** + * @summary Fonction qui permet de promouvoir membre au stade d'administrateur d'un groupe. + * @desc Cette fonction fait essentiellement appel à {@link Admin.addGroupMember} {@link LDAP.change} et {@link Open.getAdmins}. Elle n'autorise pas + * les doublons et opère dans les deux dns users et groups. + * @arg {string} uid - Identifiant du futur membre + * @arg {string} gid - Identifiant du groupe + * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async addAdmin(uid: string, gid: string): Promise<boolean> { + // Ajoute le membre au groupe avant d'en faire un admin + if (!await Group.addMember(uid,gid)) { throw "Erreur lors de l'ajout du futur admin en tant que membre."; } + try { + let la = await Tools.getAdmins(gid); + if (!la.includes(uid)) { + // Finalement modification, uniquement dans groups + let vals = {}; + vals[ldapConfig.group.admins] = uid; + if (!await LDAP.change("gr", gid, "add", vals)) { + throw "Erreur lors de l'ajout de l'admin dans l'arbre des groupes."; + } + } + return true; + } + catch(err) { + throw "Erreur lors de l'obtention de la liste des administrateurs d'un groupe."; + } + } + + /** + * @summary Fonction qui permet de rétrograder un membre du stade d'administrateur d'un groupe au stade d'utilisateur. + * @desc Cette fonction fait essentiellement appel à {@link LDAP.change}. + * @arg {string} uid - Identifiant du futur membre + * @arg {string} gid - Identifiant du groupe + * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remAdmin(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 Group.remMember(uid, gid) && Group.addMember(uid,gid))) { throw "Erreur dans l'éjection/réadmission du futur admin."; } + try { + // Vérifie que l'utilisateur est bien admin (comme dans delGroupMember) + let la = await Tools.getAdmins(gid); + if (la.includes(uid)) { + // Supprime tous les administrateurs + if (!await LDAP.change("gr", gid, "del", ldapConfig.group.admins)) { throw "Erreur dans la suppression de tous les admins pour en supprimer un."; } + // Les rajoute un par un, sauf pour le supprimé + la.forEach(id => { + if (id!=uid) { Group.addAdmin(id, gid).then(res => { + if (!res) { throw "Erreur dans le réajout d'un des autres admins."; } + }); } + }); + } + return true; + } + catch(err) { + throw "Erreur lors de l'obtention de la liste des administrateurs d'un groupe."; + } + } + /** + * @summary Fonction qui créé un nouveau groupe dans le LDAP. + * @desc Cette fonction fait une utilisation massive d'eval pour anonymiser son code ; c'est mal et cela suppose que beaucoup de soins ont été pris lors de + * l'escape de ses paramètres. Appelle {@link LDAP.add} et {@link LDAP.change}, mais aussi {@link Admin.addMemberGroup} et {@link Admin.addAdminGroup} + * pour gérer les groupes du nouvel utilisateur. Attention une manip FOIREUSE est cachée dedans. + * @arg {groupData} data - Dictionnaire des informations utilisateurs (voir détail des champs dans ldapConfig.json) + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async create(data: groupData) : Promise<boolean> { + // Calcul d'un dictionnaire d'ajout + let vals = {}; + + // gid de base généré à partir du nom standardisé, pas à partir de l'entrée 'gid' ! + try { + Tools.generateReadableId(data['name']).then(id => { + vals[ldapConfig.key_id]=id; + vals[ldapConfig.group['name']]=id; + }); + } + catch(err) { + throw "Erreur lors de la génération d'un hruid pour créer un nouveau groupe."; + } + + let gid : string = vals[ldapConfig.key_id]; + + // Ecriture de toutes les valeurs directement inscrites dans le LDAP + for (let key_att in data) { vals[ldapConfig.group[key_att]]=data[key_att] }; + + // Appel à la fonction de base + if (!await LDAP.add("gr", 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={}; + + // Encore un champ redondant + vals2[ldapConfig.group['adress']] = gid; + + // ?! + vals2[ldapConfig.group['password']] = ''; + + // Génération id aléatoire et test contre le LDAP + try { + Tools.generateId(ldapConfig.group["idNumber"], "gr").then(id => { vals2[ldapConfig.group['idNumber']]=id; }); + } + catch(err) { + throw "Erreur lors de la génération d'un id numérique pour créer un nouveau groupe."; + } + // FOIREUX : Hypothèse sur la structure du reste des données mais évite un test.assurerUnicite à deux variables + vals2[ldapConfig.group['idNumber2']]=vals2[ldapConfig.group['idNumber']]; + + // Stockage machine ; dépend du prénom + vals2[ldapConfig.group['directory']] = '/hosting/groups/'+gid; + + // Code root + vals2[ldapConfig.group['cleanFullName']]=data['name'].replace(':', ';').toLowerCase().normalize('UFD'); + + // Adressage root + vals2[ldapConfig.group['login']] = "/sbin/nologin"; + + // Permissions BR + vals2[ldapConfig.group['readPerm']] = '!*'; + vals2[ldapConfig.group['writePerm']] = '!*'; + + // Inscription des valeurs calculées par effet de bord + if (!await LDAP.change("gr", gid, "add", vals2)) { + throw "Erreur lors de l'ajout des valeurs intelligentes du nouveau groupe."; + } + + ["posixAccount", "posixGroup", "brAccount"].forEach(cst => { + let vals3={}; + vals3[ldapConfig.group['classes']]=cst; + LDAP.change("gr", gid, "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 => { + Group.addMember(uid, gid).then(res => { + if (!res) { throw "Erreur de l'ajout d'un membre au groupe."; } + }); + }); + data['admins'].forEach(uid => { + Group.addAdmin(uid, gid).then(res => { + if (!res) { throw "Erreur de l'ajout d'un admin au groupe."; } + }); + }); + + return true; + } + + /** + * @summary Fonction qui supprime un groupe 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 Admin.delGroupMember} et {@link Admin.delGroupAdmin} pour gérer les groupes de l'utilisateur sortant. + * @arg {string} gid - gid de la victime + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async delete(gid): Promise<boolean> { + try { + // Gestion des membres et administrateurs d'abord + let profil = await Group.peek(gid); + // Ordre important + profil[ldapConfig.group['admin']].forEach( id => { + Group.remAdmin( id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un admin d'un groupe en cours de suppression."; } }); + }); + profil[ldapConfig.group['member']].forEach(id => { + Group.remMember(id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un membre."; } }); + }); + // Elimination + if (!await LDAP.clear("gr",gid)) { throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes."; } + return true; + } + catch(err) { + throw "Erreur lors de l'obtention du profil d'un groupe pour le supprimer."; + } + } + + /** + * @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 { + 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."; + } + } +} \ No newline at end of file diff --git a/src/ldap/user.ts b/src/ldap/user.ts new file mode 100644 index 0000000000000000000000000000000000000000..35fadd5bdc3d6d3cd18dde759ea7ad83a5a1fdc4 --- /dev/null +++ b/src/ldap/user.ts @@ -0,0 +1,283 @@ +/** + * @file Ce fichier regroupe les différentes classes avec différents utilisateurs. Ces classes sont dédiées à être exportées directement pour être utilisées par le solver. + * Le découpage par fichier est arbitraire mais permet de regrouper certaines classes proches. + * @author hawkspar + */ + +import {ldapConfig} from './config'; +import {LDAP} from './basics'; +import {Tools} from './utilities'; +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[]} adresses - Adresse(s) + * @var {string[]} mails - 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 interface 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, + "adress"?: string, + "mail"?: string, + "ips"?: string[], + "directory"?: string, + "login"?: string, + "readPerm"?: string, + "writePerm"?: string, + "forlifes"?: string[] + //"likes"?: string[] +} + +//------------------------------------------------------------------------------------------------------------------------ +// Classes à exporter TBT +//------------------------------------------------------------------------------------------------------------------------ + +export class User { + /** + * @class Cette classe est une des deux classes exportables permettant de faire des opérations sur les utilisateurs. + * @summary Constructeur vide. + */ + constructor() {} + + /** + * @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(userData)} Informations recueillies. + * @static + * @async + */ + static async peek(uid: string) : Promise<userData> { + try { + return Tools.genericPeek<userData>("us", uid); + } + catch(err) { + throw "Error while peeking a user."; + } + } + + /** + * @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. + * @arg {userData} 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. + * @return {Promise(string[])} gids des profils qui "match" les critères proposés. + * @static + * @async + */ + static async search(data: userData) : Promise<string[]> { + try { + return Tools.genericSearch("us", data); + } + catch(err) { + throw "Erreur lors de la recherche approximative d'un utilisateur."; + } + } + + /** + * @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 {fullUserData} 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 create(data: userData): Promise<boolean> { + // Calcul d'un dictionnaire d'ajout + let vals = {}; + + // uid de base généré à partir de nom et prénom, plus potentiellement promo et un offset + // MEF mélange de Promise et de fonction standard + try { + Tools.generateUid(data['givenName'],data['lastName'],data['promotion']).then(id => { vals[ldapConfig.key_id]=id; } ); + } + catch(err) { + throw "Erreur lors de la génération d'un hruid pour un nouvel utilisateur."; + } + + let uid = vals[ldapConfig.key_id]; + + // Génère une erreur si un champ n'est pas rempli + for (let key_att in data) { + // Ecriture de toutes les valeurs uniques + if (!Array.isArray(data[key_att])) { vals[ldapConfig.user[key_att]]=data[key_att]; } + } + + // Appel à la fonction de base + if (!await LDAP.add("us", 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 + if (Array.isArray(data[key_att])) { + // On rajoute chaque valeur en entrée + data[key_att].forEach(val => { + let vals2 = {}; + vals2[ldapConfig.user[key_att]]=val; + LDAP.change("us", 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."; } + }); + }); + } + } + + // Certains champs nécessitent de petits calculs + let vals3={}; + + // Création d'un nom complet lisible + vals3[ldapConfig.user['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']; + + // Ecriture d'un surnom s'il y a lieu + if ((data['nickname']!=undefined) && (data['nickname']!='')) { + vals3[ldapConfig.user['nickname']]=data['nickname']; + } + try { + // Génération id aléatoire unique + vals3[ldapConfig.user['id']]= await Tools.generateId(ldapConfig.user['id'], "us"); + } + catch(err) { + throw "Erreur lors de la génération d'un id numérique pour un nouvel utilisateur."; + } + + // Stockage machine ; dépend du prénom + vals3[ldapConfig.user['directory']] = '/hosting/users/' + data['givenName'][0]; + + // Code root + vals3[ldapConfig.user['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"; } + + // 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']; } + + // Valeur nécessaire ASKIP mais inutile + vals3[ldapConfig.user['idNum']] ='5000'; + + // Inscription des valeurs calculées + if (!await LDAP.change("us", 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; + LDAP.change("us", uid, "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 => { + Group.addMember(uid, gid).then(res => { + if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe."; } + }); + }); + data['groupsIsAdmin'].forEach(gid => { + Group.addAdmin(uid, gid).then(res => { + if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe en tant qu'admin."; } + }); + }); + + return true; + } + + //------------------------------------------------------------------------------------------------------------------------ + // Fonctions de suppression TBT + //------------------------------------------------------------------------------------------------------------------------ + + /** + * @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 Admin.delGroupMember} et {@link Admin.delGroupAdmin} 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 + * @static + */ + static async delete(uid: string): Promise<boolean> { + try { + // Gestion des groupes d'abord + let profil = await User.peek(uid); + profil[ldapConfig.user['groups']].forEach(gid => { + // Opérations effectuées par effet de bord + if (Tools.isGroupAdmin(uid,gid)) { + if (!Group.remAdmin(uid, gid)) { throw "Erreur lors de la suppression des droits d'admin de l'utilisateur."; } + } + if (!Group.remMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenance à un groupe de l'utilisateur."; } + }); + } + catch(err) { + throw "Erreur lors de l'obtention des informations de l'utilisateur à supprimer."; + } + // Elimination + if (!LDAP.clear("us", uid)) { throw "Erreur lors de la suppression de l'utilisateur."; } + return true; + } + + /** + * @summary Fonction qui édite un utilisateur existant dans le LDAP. Très similaire à {@link creerUtilisateur} + * @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 { + 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."; + } + } +} \ No newline at end of file diff --git a/src/ldap/users.ts b/src/ldap/users.ts deleted file mode 100644 index f8fbab0e313d736e688dbd26b15a059c4619096e..0000000000000000000000000000000000000000 --- a/src/ldap/users.ts +++ /dev/null @@ -1,416 +0,0 @@ -/** - * @file Ce fichier regroupe les différentes classes avec différents utilisateurs. Ces classes sont dédiées à être exportées directement pour être utilisées par le solver. - * Le découpage par fichier est arbitraire mais permet de regrouper certaines classes proches. - * @author hawkspar - */ - -import { ldapConfig } from './config'; -import {LDAP} from './basics'; -import {searchUserFields, SmartSearch, Tests} from './utilities'; -import {Admin, Supervisor} from './admins'; -import ldap from 'ldapjs'; - -/** - * @const groupDataTemplate - * @var {string} name - Nom du groupe - * @var {string} ns - Statut du groupe ; 'binet' ou 'free', cà d ouvert à tous - * @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 - */ -let groupDataTemplate = {}; -for (let key in ldapConfig.group.single) { groupDataTemplate[key] = ""; } -for (let key in ldapConfig.group.multiple) { groupDataTemplate[key] = [""]; } - -/** - * @type groupData - * @summary Interface sur le modèle de {@link groupDataTemplate} - */ -export type groupData = typeof groupDataTemplate; - -/** - * @interface userData - * @desc Interface avec toutes les données extractables pour un 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 - * @var {string} promotion - Année(s) de promo - * @var {string} phone - Numéro(s) de téléphone - * @var {string} mail - Adresse(s) courriel - * @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.single) { userDataTemplate[key] = ""; } -for (let key in ldapConfig.user.multiple) { userDataTemplate[key] = [""]; } - -/** - * @type groupData - * @summary Interface sur le modèle de {@link userDataTemplate} - */ -export type userData = typeof userDataTemplate; - -//------------------------------------------------------------------------------------------------------------------------ -// Classes à exporter TBT -//------------------------------------------------------------------------------------------------------------------------ - -export class Open { - /** - * @class Cette classe est la classe exportable de base permettant à un utilisateur non connecté de faire des petites recherches simples. - * @summary Constructeur vide. - */ - constructor() {} - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de lecture - //------------------------------------------------------------------------------------------------------------------------ - /** - * @summary Fonction qui retrouve les groupes dont un individu est membre. - * @desc Cette fonction utilise {@link LDAP.search} va directement à la feuille de l'utilisateur. - * @arg {string} uid - Identifiant de l'individu à interroger (le plus souvent prenom.nom, parfois l'année, supposé valide) - * @return {Promise(string[])} Liste des uid de groupes (noms flat des groupes) où l'id fourni est membre - * @static - * @async - */ - static async getGroups(uid: string) { - try { - return LDAP.search(ldapConfig.key_id+uid+ldapConfig.dn_users, ldapConfig.user.groups)[0]; - } - catch(err) { - throw "Erreur lors de la recherche des groupes d'un individu."; - } - } - - /** - * @summary Fonction qui retrouve la liste des membres d'un groupe. - * @desc Cette fonction utilise {@link LDAP.search} avec un dictionnaire prédéfini dans 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(ldapConfig.key_id+gid+ldapConfig.dn_users, ldapConfig.group.member)[0]; - } - catch(err) { - throw "Erreur lors de la recherche des membres d'un groupe."; - } - } - - /** - * @summary Fonction qui retrouve la liste des admins d'un groupe. - * @desc Cette fonction utilise {@link LDAP.search} avec un dictionnaire prédéfini dans 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(ldapConfig.key_id+gid+ldapConfig.dn_users, ldapConfig.group.admin)[0]; - } - catch(err) { - throw "Erreur lors de la recherche des admins d'un groupe."; - } - } - - /** - * @summary Cette fonction teste si un utilisateur est membre d'un groupe. - * @desc Utilise les méthodes statiques {@link open.getGroups} et {@link open.getMembers} - * @param {string} uid - Identifiant de l'utilisateur à tester - * @param {string} gid - Identification du groupe à tester - * @returns {Promise(boolean)} True si l'utilisateur est membre - * @static - * @async - */ - static async isGroupMember(uid: string, gid: string) { - try { - let lg = await this.getGroups(uid); - let lm = await this.getMembers(gid); - if (lg.includes(gid) && lm.includes(uid)) { - return true; - } - } - catch(err) { - throw "Erreur lors du test d'appartenance à un groupe."; - } - } - - /** - * @summary Cette fonction teste si un utilisateur est admin d'un groupe. - * @desc Utilise la méthode statique {@link Open.getAdmins} - * @param {string} uid - Identifiant de l'utilisateur à tester - * @param {string} gid - Identification du groupe à tester - * @returns {Promise(boolean)} True si l'utilisateur est administrateur - * @static - * @async - */ - static async isGroupAdmin(uid: string, gid: string) { - try { - let la = await this.getAdmins(gid); - if (la.includes(uid)) { - return true; - } - } - catch(err) { - throw "Erreur lors du test d'appartenance au bureau d'administration un groupe."; - } - } - - /** - * @summary Fonction qui renvoit toutes les infos relatives à un utilisateur particulier. - * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis. - * @arg {string} uid - Identifiant de l'utilisateur - * @return {Promise(userData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet de l'utilisateur ; - * voir `ldap_ldapConfig.json`(..\..\ldap_ldapConfig.json) pour les clés exactes. - * @static - * @async - */ - static async peekUser(uid: string) : Promise<userData> { - try { - 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) { - for (let cleanKey in cleanUserData) { - if (uncleanKey==ldapConfig.group.cleanKey) { cleanUserData[cleanKey] = LDAPUserData[uncleanKey]; } - } - } - return cleanUserData; - } - catch(err) { - throw "Erreur lors d'une recherche d'informations sur un individu."; - } - } - - /** - * @summary Fonction qui renvoit toutes les infos relatives à un groupe particulier. - * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis. - * @arg {string} gid - Identifiant du groupe - * @return {Promise(groupData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe ; - * voir `ldap_ldapConfig.json`(..\..\ldap_ldapConfig.json) pour les clés exactes. - * @static - * @async - */ - static async peekGroup(gid: string) : Promise<groupData> { - try { - 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) { - for (let cleanKey in cleanGroupData) { - if (uncleanKey==ldapConfig.group.cleanKey) { cleanGroupData[cleanKey] = LDAPGroupData[uncleanKey]; } - } - } - return cleanGroupData; - } - catch(err) { - throw "Erreur lors d'une recherche d'informations sur un groupe."; - } - } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de recherche - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui retrouve le groupe qui ressemblent à l'input et qui correspond au type fourni. Etape 0 vers un vrai TOL (Trombino On Line). - * @desc Cette fonction utilise {@link SmartSearch.groups}. - * @arg {string} input - String entré par l'utilisateur qui ressemble au nom du groupe. - * @return {Promise(string[])} Liste des gid dont le nom ressemble à l'input. - * @static - * @async - */ - static async findGroups(input: string) : Promise<string[]> { - try { - // Trucs intelligents faits dans ./utilities - return SmartSearch.groups(input, ldapConfig.key_id); - } - catch(err) { - throw "Erreur lors de la recherche approximative d'un groupe."; - } - } - - /** - * @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). - * @desc Cette fonction utilise {@link SmartSearch.users}. - * @arg {searchUserFields} data - Dictionnaire contenant les données nécessaires à {@link SmartSearch.groups} - * @return {Promise(string[])} gids des profils qui "match" les critères proposés. - * @static - * @async - */ - static async findUsers(data: searchUserFields) : Promise<string[]> { - try { - return SmartSearch.users(data, ldapConfig.key_id); - } - catch(err) { - throw "Erreur lors de la recherche approximative d'un utilisateur."; - } - } -} - -export class User extends Open { - /** - * @class Cette classe est la classe de l'utilisateur connecté qui peut déjà créer un groupe et changer son profil. - * Techniquement, c'est la première classe qui a vraiment besoin de méthodes dynamiques dans l'arborescence, puisque c'est à partir du niveau User - * qu'on peut commencer à vouloir tracer les actions de l'utilisateur. - * @summary Constructeur vide. - */ - constructor() { super(); } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonction de création TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui créé un nouveau groupe dans le LDAP. - * @desc Cette fonction fait une utilisation massive d'eval pour anonymiser son code ; c'est mal et cela suppose que beaucoup de soins ont été pris lors de - * l'escape de ses paramètres. Appelle {@link LDAP.add} et {@link LDAP.change}, mais aussi {@link Admin.addMemberGroup} et {@link Admin.addAdminGroup} - * pour gérer les groupes du nouvel utilisateur. Attention une manip FOIREUSE est cachée dedans. - * @arg {groupData} data - Dictionnaire des informations utilisateurs (voir détail des champs dans ldapConfig.json) - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - 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.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.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+"="+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={}; - - // ?! - vals2[ldapConfig.group.single['password']] = ''; - - // Génération id aléatoire et test contre le LDAP - try { - 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.single['idNumber2']]=vals2[ldapConfig.group.single['idNumber']]; - - // Stockage machine ; dépend du prénom - vals2[ldapConfig.group.single['directory']] = '/hosting/groups/'+gid; - - // Code root - vals2[ldapConfig.group.single['cleanFullName']]=data['name'].replace(':', ';').toLowerCase().normalize('UFD'); - - // Adressage root - vals2[ldapConfig.group.single['login']] = "/sbin/nologin"; - - // Permissions BR - 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+"="+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.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, gid).then(res => { - if (!res) { throw "Erreur de l'ajout d'un membre au groupe."; } - }); - }); - data['admins'].forEach(uid => { - Admin.addGroupAdmin(uid, gid).then(res => { - if (!res) { throw "Erreur de l'ajout d'un admin au groupe."; } - }); - }); - - return true; - } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions d'édition TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui édite un utilisateur existant dans le LDAP. Très similaire à {@link creerUtilisateur} - * @desc Appelle simplement {@link creerUtilisateur} et {@link supprimerUtilisateur} en godmode, plus {@link renseignerSurUtilisateur} pour les champs non fournis. - * Ce choix a pour conséquence que l'ordre du dictionnaire de correspondance dans ldap_ldapConfig est important. - * Une version "nerfée" de cette fonction est envisageable ; elle donne bcp de pouvoir à l'utilisateur. - * @arg {string} uid - Utilisateur à modifier (le plus souvent le même, mais root possible) - * @arg {userData} data - Dictionnaire des informations utilisateurs au même format que pour {@link creerUtilisateur} avec tous les champs optionnels ; - * Attention toutes les clés de cette entrée seront modifiées dans le LDAP ; les nouveaux résultats écrasant les précédents, sauf 'readPerm','writePerm','forlifes','ips','groups' et 'groupsIsAdmin' - * qui sont censurés pour cette fonction) - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async editUser(uid : string, data : userData) : Promise<boolean> { - // Récupération des anciennes données - let profil = await Open.peekUser(uid); - try { - // Régénération du champ manquant dans profil - let lg = await Open.getGroups(uid); - profil['groupsIsAdmin']=[]; - lg.forEach(gid => { - Open.isGroupAdmin(uid, gid).then(res => { - if (res) { profil['groupsIsAdmin'].push(gid); } - }); - }); - // Surcharge des champs à modifier selon data - 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 (!['readPerm','writePerm','forlifes','ips','groups','groupsIsAdmin'].includes(key)) { profil[key]=data[key]; } - }); - // 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 { - return true; - } - } - catch(err) { - throw "Erreur lors de la modification des groupes où un utilisateur est admin."; - } - } -} \ No newline at end of file diff --git a/src/ldap/utilities.ts b/src/ldap/utilities.ts index 19b5606d3ab0a625e000656e11850e3518096d1f..dc47a122097126bc9301dc10a96f4ef2e4349acf 100644 --- a/src/ldap/utilities.ts +++ b/src/ldap/utilities.ts @@ -6,105 +6,76 @@ 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 -} +import { userData } from './user'; +import { groupData } from './group'; //------------------------------------------------------------------------------------------------------------------------ -// Fonctions de recherche +// Fonctions intermédiaires TBT //------------------------------------------------------------------------------------------------------------------------ -export class SmartSearch { +export class Tools { /** - * @class Cette classe contient des fonctions de recherche génériques trop puissantes pour être exportées tel quel. + * @class Cette classe contient des fonctions intermédiaires qui ne sont pas destinées à être utilisées dans les resolvers. * @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 + * @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. + * @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 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); + static async genericPeek<T>(domain: 'us'|'gr', id: string) : Promise<T> { + if (domain='gr') { + var dirtyKeys = ldapConfig.group; } - catch(err) { - throw "Erreur lors de la recherche intelligente d'un groupe."; + else { + var dirtyKeys = ldapConfig.user; + } + let cleanData : T; + let dirtyData = await LDAP.search(domain, dirtyKeys.values(), id); + // Rename output + for (let uncleanKey in dirtyData) { + for (let cleanKey in cleanData) { + if (uncleanKey=dirtyKeys[cleanKey]) { cleanData[cleanKey] = dirtyData[uncleanKey]; } + } } + return cleanData; } + /** - * @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). + * @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. 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 + * 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. + * @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. - * @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. + * @return {Promise(string[])} ids des profils qui "match" les critères proposés. * @static * @async */ - static async users(data: searchUserFields, return_attributes: string[]) : 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 = ldapConfig.user[key]; + let attribute = ""; + 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 "(|("+attribute+"=*"+val+")"+ // La valeur finale avec des trucs avant ; wildcard * (MEF la wildcart ne marche pas pour tous les attributs) @@ -114,26 +85,36 @@ export class SmartSearch { } } // Appel avec filtre de l'espace - try { - return LDAP.search(ldapConfig.dn_users, return_attributes, filter); + return LDAP.search(domain, [ldapConfig.key_id], null, filter); + } + + /** + * @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(domain: "us"|"gr", data: userData|groupData) : Promise<boolean> { + if (domain = "us") { + var id=data['uid']; + var dirtyKeys=ldapConfig.user; } - catch(err) { - throw "Erreur lors de la recherche intelligente d'un utilisateur."; + else { + var id=data['gid']; + var dirtyKeys=ldapConfig.group; } + // Renommage LDAP-friendly + 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",dirtyData); } -} - -//------------------------------------------------------------------------------------------------------------------------ -// Fonctions intermédiaires TBT -//------------------------------------------------------------------------------------------------------------------------ - -export class Tests { - /** - * @class Cette classe contient des fonctions de test d'unicité trop puissantes pour être exportées tel quel. - * @summary Constructeur vide. - * @author hawkspar - */ - constructor() {} /** * @callback changeValueCallback @@ -148,7 +129,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 +137,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; } + 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 Tools.ensureUnique(changeValue(value, n+1), attribute, domain, changeValue, n+1); } }); } catch(err) { @@ -174,7 +155,7 @@ export class Tests { /** * @summary Cette fonction génère un uid standard, puis le fait évoluer jusqu'à ce qu'il soit unique. - * @desc Limité à un appel à {@link Tests.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). + * @desc Limité à un appel à {@link Tools.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). * @param {string} givenName - Prénom * @param {string} lastName - Nom * @param {string} promotion - Année de promotion @@ -185,9 +166,9 @@ 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) => { - 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 + 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 else if (n>2) { id+=n; } // Ensuite on continue .123, .1234, etc... return id; }); @@ -199,7 +180,7 @@ export class Tests { /** * @summary Cette fonction génère un id lisible, puis le fait évoluer jusqu'à ce qu'il soit unique. - * @desc Limité à un appel à {@link Tests.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). + * @desc Limité à un appel à {@link Tools.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). * @param {string} name - Nom * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié * @static @@ -208,9 +189,9 @@ 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) => { - 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 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... return id; }); } @@ -222,17 +203,112 @@ 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 Tools.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) : Promise<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) : Promise<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) : Promise<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) : Promise<boolean> { + try { + let lg = await Tools.getGroups(uid); + let lm = await Tools.getMembers(gid); + if (lg.includes(gid) && lm.includes(uid)) { + 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) : Promise<boolean> { + try { + let lm = await Tools.getMembers(gid); + let la = await Tools.getAdmins(gid); + if (la.includes(uid) && lm.includes(uid)) { 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