import RoleUtils from '~/services/rights/roleUtils' import {AbilitiesType} from '~/types/interfaces' import YamlDenormalizer from "~/services/data/serializer/denormalizer/yamlDenormalizer"; import {MongoAbility} from "@casl/ability/dist/types/Ability"; import {AnyJson} from "~/types/data"; import {useEach} from "#imports"; import {ABILITIES} from "~/types/enum/enums"; /** * Classe permettant de mener des opérations sur les habilités */ class AbilityUtils { private readonly ability: MongoAbility = {} as MongoAbility private readonly accessProfile: any private readonly organizationProfile: any private readonly configDir = './config/abilities/config.yaml' private abilitiesByRoles: Array = [] private abilitiesByConfig: Array = [] /** * @constructor */ constructor( ability: MongoAbility, accessProfile: any, organizationProfile: any, ) { this.ability = ability this.accessProfile = accessProfile this.organizationProfile = organizationProfile } /** * Définit les abilities de l'utilisateur selon son profil */ setupAbilities() { // Nécessaire pour que l'update des habilités soit correcte après la phase SSR this.ability.update(this.accessProfile.abilities) // Au moment où l'on effectue une action organizationProfileStore.setProfile, il faut aller récupérer // les différentes habilités que l'utilisateur peut effectuer. (Tout cela se passe en SSR) const unsubscribe = this.organizationProfile.$onAction(({ name, // name of the action store, // store instance, same as `someStore` args, // array of parameters passed to the action after, // hook after the action returns or resolves onError, // hook if the action throws or rejects }: any) => { after((result: any)=>{ if (name === 'setProfile'){ //On récupère les habilités const abilities = this.buildAbilities(); //On les store puis on update le service ability pour le mettre à jour. this.accessProfile.abilities = abilities this.ability.update(abilities) // Unsubscribe pour éviter les memory leaks unsubscribe() } }) }) } /** * Récupération de l'ensemble des habilités de l'utilisateur, qu'elles soient par Roles ou par Config * * @return {Array} */ buildAbilities(): Array { this.buildAbilitiesFromRoles() this.buildAbilitiesFromConfig() return this.abilities() } abilities(): Array { return ([] as Array).concat(this.abilitiesByRoles).concat(this.abilitiesByConfig) } /** * Adaptation et transformations des roles symfony en abilities Casl */ buildAbilitiesFromRoles() { this.abilitiesByRoles = RoleUtils.rolesToAbilities(this.accessProfile.roles) } /** * Charge les habilités depuis les fichiers de configuration */ buildAbilitiesFromConfig() { const doc = YamlDenormalizer.denormalize({path: this.configDir}) const fromConfig = doc.abilities useEach(fromConfig, (ability: { action: ABILITIES, services: object }, subject: string) => { const { action, services } = ability if (this.hasConfigAbility(services)) { this.abilitiesByConfig.push({ action, subject }) } }) } /** * Parcourt les services définis dans la configuration, et établit si oui ou non l'habilité est autorisée * * TODO: voir pourquoi on a besoin d'accepter un param null pour le hasProfile? * * @return {boolean} * @param services */ hasConfigAbility(services: AnyJson) { for (const service in services) { let handlers = services[service] as Array<{ function: string, parameters?: Array, result?: any }> if (handlers.some((handler) => !this.testConfigService(handler))) { return false } } return true } handlerMap: any = { hasAllRoleAbilities: (parameters: any) => this.hasAllRoleAbilities(parameters), hasAnyProfile: (parameters: any) => parameters === null || this.hasAnyProfile(parameters), hasAllModules: (parameters: any) => this.hasAllModules(parameters), isAdminAccount: (parameters: any) => this.accessProfile.isAdminAccount, isSchool: (parameters: any) => this.organizationProfile.isSchool, isArtist: (parameters: any) => this.organizationProfile.isArtist, isManagerProduct: (parameters: any) => this.organizationProfile.isManagerProduct, isOrganizationWithChildren: (parameters: any) => this.organizationProfile.hasChildren, isAssociation: (parameters: any) => this.organizationProfile.isAssociation, isShowAdherentList: (parameters: any) => this.organizationProfile.isShowAdherentList, isCmf: (parameters: any) => this.organizationProfile.isCmf, getWebsite: (parameters: any) => this.organizationProfile.getWebsite, } private testConfigService(handler: { function: string, parameters?: Array, result?: any }) { const expectedResult: boolean = handler.result ?? true; const parameters = handler.parameters ?? [] const actualResult = this.handlerMap[handler.function](parameters ?? null) return actualResult === expectedResult } /** * Est-ce que l'utilisateur possède l'habilité passée en paramètre * * @return {boolean} * @param ability */ hasRoleAbility(ability: AbilitiesType) { return this.abilitiesByRoles.some( (candidate) => candidate.action === ability.action && candidate.subject === ability.subject ) } /** * Est-ce que l'utilisateur possède toutes les habilités passées en paramètre * * @param {Array} abilities Habilités à tester * @return {boolean} */ hasAllRoleAbilities(abilities: Array): boolean { return abilities.every( ability => this.hasRoleAbility(ability) ) } /** * Teste si l'utilisateur possède le profil donné * * @param {string} profile Profil à tester * @return {boolean} */ hasProfile(profile: string): boolean { return { 'admin': this.accessProfile.isAdmin, 'administratifManager': this.accessProfile.isAdministratifManager, 'pedagogicManager': this.accessProfile.isPedagogicManager, 'financialManager': this.accessProfile.isFinancialManager, 'caMember': this.accessProfile.isCaMember, 'student': this.accessProfile.isStudent, 'teacher': this.accessProfile.isTeacher, 'member': this.accessProfile.isMember, 'other': this.accessProfile.isOther, 'guardian': this.accessProfile.isGuardian, 'payor': this.accessProfile.isPayer, }[profile] ?? false } /** * Retourne vrai si l'utilisateur connecté possède l'un des profils passés en paramètre * * @param {Array} profiles Profils à tester * @return {boolean} */ hasAnyProfile (profiles: Array): boolean { return profiles.some(p => this.hasProfile(p)) } /** * Retourne vrai si l'utilisateur connecté possède tous les profils passés en paramètre * * @param {Array} profiles Profils à tester * @return {boolean} */ hasAllProfiles (profiles: Array): boolean { return profiles.every(p => this.hasProfile(p)) } /** * Est-ce que l'utilisateur possède le rôle donné ? * * @return {boolean} * @param role */ hasRole(role: string): boolean { return this.accessProfile.hasRole(role) } /** * L'utilisateur possède-t-il au moins l'un des rôles donnés * * @return {boolean} * @param roles */ hasAnyRole(roles: Array): boolean { return roles.some(r => this.hasRole(r)) } /** * L'utilisateur possède-t-il tous les rôles donnés * * @return {boolean} * @param roles */ hasAllRoles(roles: Array): boolean { return roles.every(r => this.hasRole(r)) } /** * Est-ce que l'organisation possède le module donné * * @return {boolean} * @param module */ hasModule(module: string): boolean { return this.organizationProfile.hasModule(module) } /** * Est-ce que l'organisation possède au moins un des modules donnés * * @param modules * @return {boolean} */ hasAnyModule(modules: Array): boolean { return modules.some(r => this.hasModule(r)) } /** * Est-ce que l'organisation possède-t-il tous les modules donnés * * @param modules * @return {boolean} */ hasAllModules(modules: Array): boolean { return modules.every(r => this.hasModule(r)) } } export default AbilityUtils