import { $accessProfile } from '@/services/profile/accessProfile' import { $organizationProfile } from '@/services/profile/organizationProfile' import { Ability } from '@casl/ability' import * as _ from 'lodash' import { $roleUtils } from '~/services/rights/roleUtils' import { AbilitiesType, AnyJson, AnyStore } from '~/types/interfaces' import Serializer from '~/services/serializer/serializer' import { DENORMALIZER_TYPE } from '~/types/enums' /** * Classe permettant de mener des opérations sur les habilités */ class AbilitiesUtils { private readonly $store: AnyStore = {} as AnyStore private readonly $ability: Ability = {} as Ability private factory: AnyJson = {} /** * @constructor */ constructor (store: AnyStore, ability: Ability) { this.$store = store 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.$store) } this.setAbilitiesAndStore() } /** * Initialise les Abilities pour le service AccessProfile */ setAbilitiesAndStore(){ this.factory.access.setAbility(this.$ability, this.$store) this.factory.access.setStore(this.$store) } /** * Définit les abilities de l'utilisateur à chaque fois qu'on met à jour son profile */ setAbilities () { // Nécessaire pour que l'update des habilités soit correcte après la phase SSR this.$ability.update(this.$store.state.profile.access.abilities) // Au moment où l'on effectue un SetProfile via le store Organization, il faut aller récupérer // les différentes abilitées que l'utilisateur peut effectuer. (Tout cela se passe en SSR) const unsubscribe = this.$store.subscribeAction({ after: (action, _state) => { switch (action.type) { case 'profile/organization/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. this.$store.commit('profile/access/setAbilities', abilities) this.$ability.update(abilities) // Unsubscribe pour éviter les memory leaks unsubscribe() break } } }) } /** * Récupération de l'ensemble des abilities, qu'elles soient par Roles ou par Config * * @return {Array} */ getAbilities():Array { const abilitiesByRoles: Array = this.getAbilitiesByRoles(this.$store.state.profile.access.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 = Serializer.denormalize({ path: configPath }, DENORMALIZER_TYPE.YAML) const abilitiesAvailable = doc.abilities const abilitiesFiltered = this.abilitiesAvailableFilter(abilitiesAvailable) abilitiesByConfig = this.transformAbilitiesConfigToAbility(abilitiesFiltered) } catch (e) { 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 _.pickBy(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 = [] _.each(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 * * @param {AnyJson} functionByServices * @return {boolean} */ canHaveTheAbility(functionByservices: AnyJson) { let hasAbility: boolean = true; _.each(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 = (store: AnyStore, ability:Ability) => new AbilitiesUtils(store, ability)