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 /** * @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) // const abilities: Array = this.buildAbilities(); // this.accessProfile.abilities = abilities // this.ability.update(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: Array = 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 { const abilitiesByRoles: Array = this.buildAbilitiesFromRoles(this.accessProfile.roles) const abilitiesByConfig = this.buildAbilitiesFromConfig('./config/abilities/config.yaml') return abilitiesByRoles.concat(abilitiesByConfig) } /** * Adaptation et transformations des roles symfony en abilities Casl * * @param {Array} roles * @return {Array} */ buildAbilitiesFromRoles(roles: Array): Array { return RoleUtils.rolesToAbilities(roles) } /** * Charge les habilités depuis les fichiers de configuration * * @param {string} configPath * @return {Array} */ buildAbilitiesFromConfig(configPath: string): Array { const doc = YamlDenormalizer.denormalize({path: configPath}) const fromConfig = doc.abilities const abilities: Array = [] useEach(fromConfig, (ability: { action: ABILITIES, services: object }, subject: string) => { const { action, services } = ability if (this.hasConfigAbility(services)) { abilities.push({ action, subject }) } }) return abilities } /** * Parcourt les services définis dans la configuration, et établit si oui ou non l'habilité est autorisée * * @return {boolean} * @param services */ hasConfigAbility(services: AnyJson) { const handlerMap: any = { hasRole: (parameters: any) => this.hasRoles(parameters), hasAbility: (parameters: any) => this.hasAbilities(parameters), hasProfile: (parameters: any) => this.hasProfileAmong(parameters), isAdminAccount: (parameters: any) => this.accessProfile.isAdminAccount, hasModule: (parameters: any) => this.hasModule(parameters), 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, } let hasAbility = true useEach(services, (handlers: Array<{ function: string, parameters?: Array, result?: any }>, service: string) => { useEach(handlers, (handler: { function: string, parameters?: Array, result?: any }) => { const expectedResult: boolean = handler.result ?? true; const parametersArray = handler.parameters ?? [] useEach(parametersArray, (parameters: any) => { const actualResult = handlerMap[handler.function](parameters ?? null) if (actualResult !== expectedResult) { hasAbility = false return false } }) if (!hasAbility) { return false } }) if (!hasAbility) { return false } }) return hasAbility } /** * Est-ce que l'utilisateur possède la ou les habilités * * @param {Array} abilities Habilités à tester * @return {boolean} */ hasAbilities(abilities: Array|null): boolean{ useEach(abilities ?? [], (ability) => { if (!this.ability.can(ability.action, ability.subject)) { return false } }) return true } /** * Teste le profil d'un utilisateur * * @param {string} profile : profile à tester * @return {boolean} */ private testProfile(profile: string): boolean { const factory: {[key: string]: boolean|null} = { '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, } return factory[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} */ hasProfileAmong (profiles: Array|null): boolean { if (null === profiles) return true; useEach(profiles, (profile) => { if (this.testProfile(profile)) { return true } }) return false } /** * Est-ce que l'utilisateur possède le rôle donné ? * * @return {boolean} * @param role */ hasRole(role: string|null): boolean { return role === null || this.accessProfile.roles.includes(role) } /** * Est-ce que l'utilisateur possède tous les rôles donnés ? * * @return {boolean} * @param roles */ hasRoles(roles: Array): boolean { useEach(roles, (r: string) => { if (!this.accessProfile.roles.includes(r)) { return false } }) return true } /** * Est-ce que l'organisation possède le module donné * * @return {boolean} * @param module */ hasModule(module: string): boolean { return this.organizationProfile.modules.includes(module) } } export default AbilityUtils