|
|
@@ -0,0 +1,308 @@
|
|
|
+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 {useEach} from "#imports";
|
|
|
+import {ABILITIES} from "~/types/enum/enums";
|
|
|
+
|
|
|
+interface Condition {
|
|
|
+ function: string
|
|
|
+ parameters?: Array<any>
|
|
|
+ expectedResult?: any
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Classe permettant de mener des opérations sur les habilités
|
|
|
+ */
|
|
|
+class AbilityBuilder {
|
|
|
+ private readonly ability: MongoAbility = {} as MongoAbility
|
|
|
+ private readonly accessProfile: any
|
|
|
+ private readonly organizationProfile: any
|
|
|
+
|
|
|
+ private readonly configDir = './config/abilities/config.yaml'
|
|
|
+
|
|
|
+ private abilities: Array<AbilitiesType> = []
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @constructor
|
|
|
+ */
|
|
|
+ constructor(
|
|
|
+ ability: MongoAbility,
|
|
|
+ accessProfile: any,
|
|
|
+ organizationProfile: any,
|
|
|
+ ) {
|
|
|
+ this.ability = ability
|
|
|
+ this.accessProfile = accessProfile
|
|
|
+ this.organizationProfile = organizationProfile
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Construit les habilités de l'utilisateur selon son profil et met à jour MongoAbility en fonction
|
|
|
+ */
|
|
|
+ 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 construit les habilités
|
|
|
+ this.buildAbilities();
|
|
|
+
|
|
|
+ //On les store puis on update le service ability pour le mettre à jour.
|
|
|
+ this.accessProfile.abilities = this.abilities
|
|
|
+
|
|
|
+ // Unsubscribe pour éviter les memory leaks
|
|
|
+ unsubscribe()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Construit et renvoie l'ensemble des habilités de l'utilisateur, qu'elles soient issues de ses roles
|
|
|
+ * ou de la configuration
|
|
|
+ *
|
|
|
+ * @return {Array<AbilitiesType>}
|
|
|
+ */
|
|
|
+ buildAbilities() {
|
|
|
+ // Build from roles
|
|
|
+ this.abilities = this.buildAbilitiesFromRoles()
|
|
|
+ this.ability.update(this.abilities)
|
|
|
+
|
|
|
+ // Build from config
|
|
|
+ this.abilities = this.abilities.concat(this.buildAbilitiesFromConfig())
|
|
|
+ this.ability.update(this.abilities)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Adaptation et transformations des roles symfony en abilities Casl
|
|
|
+ */
|
|
|
+ buildAbilitiesFromRoles() {
|
|
|
+ return RoleUtils.rolesToAbilities(this.accessProfile.roles)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Charge les habilités depuis les fichiers de configuration
|
|
|
+ */
|
|
|
+ buildAbilitiesFromConfig() {
|
|
|
+ const abilitiesByConfig: Array<AbilitiesType> = []
|
|
|
+
|
|
|
+ const doc = YamlDenormalizer.denormalize({path: this.configDir})
|
|
|
+ const fromConfig = doc.abilities
|
|
|
+
|
|
|
+ useEach(fromConfig, (ability: { action: ABILITIES, conditions: Array<Condition> }, subject: string) => {
|
|
|
+ let { action, conditions } = ability
|
|
|
+
|
|
|
+ if (!Array.isArray(conditions)) {
|
|
|
+ // Special: la denormalization ne produit pas une array s'il n'y a qu'un seul élément
|
|
|
+ conditions = [conditions]
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.hasConfigAbility(conditions as Array<Condition>, subject)) {
|
|
|
+ abilitiesByConfig.push({ action, subject })
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ return abilitiesByConfig
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Parcourt les services définis dans la configuration, et établit si oui ou non l'habilité est autorisée
|
|
|
+ *
|
|
|
+ * @return {boolean}
|
|
|
+ * @param conditions Les conditions à l'obtention de l'habileté, telles que définies dans les fichiers de config
|
|
|
+ * @param subject For debugging purpose only
|
|
|
+ */
|
|
|
+ hasConfigAbility(conditions: Array<Condition>, subject: string = '') {
|
|
|
+ return conditions.every((condition) => this.execAndValidateCondition(condition, subject))
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Correspondances entre les noms des fonctions définies dans les conditions des fichiers de configuration et
|
|
|
+ * les méthodes correspondantes
|
|
|
+ *
|
|
|
+ * TODO: voir pourquoi on a besoin d'accepter un param null pour le hasProfile?
|
|
|
+ */
|
|
|
+ handlerMap: any = {
|
|
|
+ accessHasAllRoleAbilities: (parameters: any) => this.hasAllRoleAbilities(parameters),
|
|
|
+ accessHasAnyRoleAbility: (parameters: any) => this.hasAnyRoleAbility(parameters),
|
|
|
+ accessHasAnyProfile: (parameters: any) => parameters === null || this.hasAnyProfile(parameters),
|
|
|
+ accessHasAllModules: (parameters: any) => this.hasAllModules(parameters),
|
|
|
+ organizationHasAnyModule: (parameters: any) => this.hasAnyModule(parameters),
|
|
|
+ accessIsAdminAccount: (parameters: any) => this.accessProfile.isAdminAccount,
|
|
|
+ organizationIsSchool: (parameters: any) => this.organizationProfile.isSchool,
|
|
|
+ organizationIsArtist: (parameters: any) => this.organizationProfile.isArtist,
|
|
|
+ organizationIsManagerProduct: (parameters: any) => this.organizationProfile.isManagerProduct,
|
|
|
+ organizationHasChildren: (parameters: any) => this.organizationProfile.hasChildren,
|
|
|
+ organizationIsAssociation: (parameters: any) => this.organizationProfile.isAssociation,
|
|
|
+ organizationIsShowAdherentList: (parameters: any) => this.organizationProfile.isShowAdherentList,
|
|
|
+ organizationIsCmf: (parameters: any) => this.organizationProfile.isCmf,
|
|
|
+ organizationHasWebsite: (parameters: any) => this.organizationProfile.getWebsite,
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Exécute la fonction associée à la condition, et compare le résultat obtenu au résultat attendu (true par défaut)
|
|
|
+ *
|
|
|
+ * @param condition Un condition à la possession d'une habilité, telle que définie dans les fichiers de config
|
|
|
+ * @param subject For debugging purpose only
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ private execAndValidateCondition(
|
|
|
+ condition: Condition,
|
|
|
+ subject: string = ''
|
|
|
+ ) {
|
|
|
+ const expectedResult: boolean = condition.expectedResult ?? true;
|
|
|
+ const parameters = condition.parameters ?? []
|
|
|
+
|
|
|
+ if (!(condition.function in this.handlerMap)) {
|
|
|
+ throw new Error('unknown condition function : ' + condition.function)
|
|
|
+ }
|
|
|
+ const actualResult = this.handlerMap[condition.function](parameters ?? null)
|
|
|
+
|
|
|
+ return actualResult === expectedResult
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Est-ce que l'utilisateur possède l'habilité en paramètre
|
|
|
+ *
|
|
|
+ * @return {boolean}
|
|
|
+ * @param ability
|
|
|
+ */
|
|
|
+ hasRoleAbility(ability: AbilitiesType): boolean {
|
|
|
+ return this.ability.can(ability.action, ability.subject)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Est-ce que l'utilisateur possède toutes les habilités passées en paramètre
|
|
|
+ *
|
|
|
+ * @param {Array<AbilitiesType>} abilities Habilités à tester
|
|
|
+ * @return {boolean}
|
|
|
+ */
|
|
|
+ hasAllRoleAbilities(abilities: Array<AbilitiesType>): boolean {
|
|
|
+ return abilities.every(ability => this.hasRoleAbility(ability))
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Est-ce que l'utilisateur possède au moins l'une des habilités passées en paramètre
|
|
|
+ *
|
|
|
+ * @param {Array<AbilitiesType>} abilities Habilités à tester
|
|
|
+ * @return {boolean}
|
|
|
+ */
|
|
|
+ hasAnyRoleAbility(abilities: Array<AbilitiesType>): boolean {
|
|
|
+ return abilities.some(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<string>} profiles Profils à tester
|
|
|
+ * @return {boolean}
|
|
|
+ */
|
|
|
+ hasAnyProfile (profiles: Array<string>): 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<string>} profiles Profils à tester
|
|
|
+ * @return {boolean}
|
|
|
+ */
|
|
|
+ hasAllProfiles (profiles: Array<string>): 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<string>): 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<string>): 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<string>): 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<string>): boolean {
|
|
|
+ return modules.every(r => this.hasModule(r))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export default AbilityBuilder
|