import {$accessProfile} from '~/services/profile/accessProfile' import {$organizationProfile} from '~/services/profile/organizationProfile' import {$roleUtils} from '~/services/rights/roleUtils' import {AbilitiesType} from '~/types/interfaces' import {useAccessProfileStore} from "~/store/profile/access"; import {useProfileOrganizationStore} from "~/store/profile/organization"; import YamlDenormalizer from "~/services/data/serializer/denormalizer/yamlDenormalizer"; import {MongoAbility} from "@casl/ability/dist/types/Ability"; import {AnyJson} from "~/types/data"; /** * Classe permettant de mener des opérations sur les habilités */ class AbilitiesUtils { private readonly $ability: MongoAbility = {} as MongoAbility private factory: AnyJson = {} /** * @constructor */ constructor(ability: MongoAbility) { this.$ability = ability } /** * Retourne la factory des services * * @return {AnyJson} factory */ getFactory() { return this.factory } /** * Initialise les services factories */ initFactory() { this.factory = { access: $accessProfile, organization: $organizationProfile() } this.setAbilitiesAndStore() } /** * Initialise les Abilities pour le service AccessProfile */ setAbilitiesAndStore() { this.factory.access.setAbility(this.$ability) this.factory.access.setPinia() } /** * Définit les abilities de l'utilisateur à chaque fois qu'on met à jour son profile */ setAbilities() { const accessProfileStore = useAccessProfileStore() const organizationProfileStore = useProfileOrganizationStore() // Nécessaire pour que l'update des habilités soit correcte après la phase SSR this.$ability.update(accessProfileStore.abilities) // // Au moment où l'on effectue un SetProfile, il faut aller récupérer // // les différentes abilitées que l'utilisateur peut effectuer. (Tout cela se passe en SSR) const unsubscribe = organizationProfileStore.$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 }) => { after((result)=>{ if(name === 'setProfile'){ //On récupère les abilités const abilities: Array = this.getAbilities(); //On les store puis on update le service ability pour le mettre à jour. accessProfileStore.abilities = abilities this.$ability.update(abilities) // Unsubscribe pour éviter les memory leaks unsubscribe() } }) }) } /** * Récupération de l'ensemble des abilities, qu'elles soient par Roles ou par Config * * @return {Array} */ getAbilities(): Array { const accessProfileStore = useAccessProfileStore() const abilitiesByRoles: Array = this.getAbilitiesByRoles(accessProfileStore.roles) this.$ability.update(abilitiesByRoles) this.initFactory() return abilitiesByRoles.concat(this.getAbilitiesByConfig('./config/abilities/config.yaml')) } /** * Adaptation et transformations des roles en abilities * * @param {Array} roles * @return {Array} */ getAbilitiesByRoles(roles: Array): Array { roles = $roleUtils.transformUnderscoreToHyphenBeforeCompleteMigration(roles) return $roleUtils.transformRoleToAbilities(roles) } /** * - Parcourt la config d'abilities en Yaml * - filtres la config pour ne garder que les abilities autorisées * - transform la config restante en Object Abilities * @param {string} configPath * @return {Array} */ getAbilitiesByConfig(configPath: string): Array { let abilitiesByConfig: Array = [] try { const doc = YamlDenormalizer.denormalize({path: configPath}) const abilitiesAvailable = doc.abilities const abilitiesFiltered = this.abilitiesAvailableFilter(abilitiesAvailable) abilitiesByConfig = this.transformAbilitiesConfigToAbility(abilitiesFiltered) } catch (e: any) { throw new Error(e.message) } return abilitiesByConfig } /** * Filtre toutes les abilities possible suivant si l'utilisateur est autorisé ou non à les posséder * * @param {AnyJson} abilitiesAvailable * @return {AnyJson} */ abilitiesAvailableFilter(abilitiesAvailable: AnyJson): AnyJson { return usePickBy(abilitiesAvailable, (ability: any) => { const services = ability.services return this.canHaveTheAbility(services) }) } /** * Transform une config d'abilities en un tableau d'Abilities * * @param {AnyJson} abilitiesAvailable * @return {Array} */ transformAbilitiesConfigToAbility(abilitiesAvailable: AnyJson): Array { const abilitiesByConfig: Array = [] useEach(abilitiesAvailable, (ability, subject) => { const myAbility: AbilitiesType = { action: ability.action, subject } abilitiesByConfig.push(myAbility) }) return abilitiesByConfig } /** * Parcourt les fonctions par services et établit si oui ou non l'habilité est autorisée * * @return {boolean} * @param functionByservices */ canHaveTheAbility(functionByservices: AnyJson) { let hasAbility: boolean = true; useEach(functionByservices, (functions, service) => { if (hasAbility) { const nbFunctions: number = functions.length let cmpt: number = 0 while (hasAbility && nbFunctions > cmpt) { const f: string = functions[cmpt]['function']; const parameters: any = functions[cmpt]['parameters'] ?? null; const result: boolean = functions[cmpt]['result'] ?? true; hasAbility = result !== null ? this.factory[service].handler()[f](parameters) == result : this.factory[service].handler()[f](parameters) cmpt++ } } }) return hasAbility } } export const $abilitiesUtils = (ability: MongoAbility) => new AbilitiesUtils(ability)