import {$accessProfile} from "@/services/profile/accessProfile" import {$organizationProfile} from "@/services/profile/organizationProfile" import {$roleUtils} from "~/services/rights/roleUtils"; import {AbilitiesType, AnyJson, AnyStore} from "~/types/interfaces"; import {Ability} from "@casl/ability"; import * as _ from "lodash"; import Serializer from "~/services/serializer/serializer"; import {DENORMALIZER_TYPE} from "~/types/enums"; /** * @category Services/droits * @class AbilitiesUtils * Classe permettant de mener des opérations sur les abilités */ class AbilitiesUtils { private $store: AnyStore = {} as AnyStore private $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(this.$store, this.$ability), organization: $organizationProfile(this.$store) } } /** * Set les abilities de l'utilisateur à chaque fois qu'on update son profile */ setAbilities(){ //Nécessaire pour que l'update des abilité soit correct 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 = 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 quelles soient par Roles ou par Config. * @return {Array} */ getAbilities():Array { const abilitiesByRoles = 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); } /** * - Parcours 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 serializer = new Serializer() 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{ let abilitiesByConfig: Array = [] _.each(abilitiesAvailable, (ability, subject) => { let myAbility: AbilitiesType = { action: ability['action'], subject: subject } abilitiesByConfig.push(myAbility) }) return abilitiesByConfig; } /** * Parcours les fonctions par services et établit si oui ou non l'abilité est autorisée * @param {AnyJson} functionByservices * @return {boolean} */ canHaveTheAbility(functionByservices: AnyJson) { let hasAbility = true; _.each(functionByservices, (functions, service) => { if (hasAbility) { const nbFunctions = functions.length let cmpt = 0 while (hasAbility && nbFunctions > cmpt) { const f = functions[cmpt]['function']; const parameters = functions[cmpt]['parameters'] ?? null; const result = functions[cmpt]['result'] ?? null; 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);