abilityBuilder.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import type { MongoAbility } from '@casl/ability/dist/types/Ability'
  2. // eslint-disable-next-line import/default
  3. import yaml from 'yaml-import'
  4. import * as _ from 'lodash-es'
  5. import RoleUtils from '~/services/rights/roleUtils'
  6. import type { AbilitiesType, AccessProfile } from '~/types/interfaces'
  7. import { ABILITIES } from '~/types/enum/enums'
  8. import type OrganizationProfile from '~/models/Organization/OrganizationProfile'
  9. interface ConditionParameters {
  10. action: string
  11. subject: string
  12. }
  13. interface Condition {
  14. function: string
  15. parameters?: Array<string | ConditionParameters>
  16. expectedResult?: boolean
  17. }
  18. /**
  19. * Classe permettant de mener des opérations sur les habilités
  20. */
  21. class AbilityBuilder {
  22. private readonly ability: MongoAbility = {} as MongoAbility
  23. private readonly accessProfile: AccessProfile
  24. private readonly organizationProfile: OrganizationProfile
  25. private readonly configFile = './config/abilities/config.yaml'
  26. private abilities: Array<AbilitiesType> = []
  27. /**
  28. * @constructor
  29. */
  30. constructor(
  31. ability: MongoAbility,
  32. accessProfile: AccessProfile,
  33. organizationProfile: OrganizationProfile,
  34. ) {
  35. this.ability = ability
  36. this.accessProfile = accessProfile
  37. this.organizationProfile = organizationProfile
  38. }
  39. /**
  40. * Construit et renvoie l'ensemble des habilités de l'utilisateur, qu'elles soient issues de ses roles
  41. * ou de la configuration
  42. *
  43. * @return {Array<AbilitiesType>}
  44. */
  45. buildAbilities(): Array<AbilitiesType> {
  46. // Build from roles
  47. this.abilities = this.buildAbilitiesFromRoles()
  48. this.ability.update(this.abilities)
  49. // Build from config
  50. this.abilities = this.abilities.concat(this.buildAbilitiesFromConfig())
  51. this.ability.update(this.abilities)
  52. return this.abilities
  53. }
  54. /**
  55. * Adaptation et transformations des roles symfony en abilities Casl
  56. */
  57. buildAbilitiesFromRoles() {
  58. return RoleUtils.rolesToAbilities(this.accessProfile.roles)
  59. }
  60. /**
  61. * Charge les habilités depuis les fichiers de configuration
  62. */
  63. buildAbilitiesFromConfig() {
  64. const abilitiesByConfig: Array<AbilitiesType> = []
  65. const doc = yaml.read(this.configFile)
  66. const fromConfig = doc.abilities
  67. _.each(
  68. fromConfig,
  69. (
  70. ability: { action: ABILITIES; conditions: Array<Condition> },
  71. subject: string,
  72. ) => {
  73. // eslint-disable-next-line prefer-const
  74. let { action, conditions } = ability
  75. if (!Array.isArray(conditions)) {
  76. // Special: la denormalization ne produit pas une array s'il n'y a qu'un seul élément
  77. conditions = [conditions]
  78. }
  79. if (this.hasConfigAbility(conditions as Array<Condition>, subject)) {
  80. abilitiesByConfig.push({ action, subject })
  81. }
  82. },
  83. )
  84. return abilitiesByConfig
  85. }
  86. /**
  87. * Parcourt les services définis dans la configuration, et établit si oui ou non l'habilité est autorisée
  88. *
  89. * @return {boolean}
  90. * @param conditions Les conditions à l'obtention de l'habileté, telles que définies dans les fichiers de config
  91. * @param subject For debugging purpose only
  92. */
  93. hasConfigAbility(conditions: Array<Condition>, subject: string = '') {
  94. return conditions.every((condition) =>
  95. this.execAndValidateCondition(condition, subject),
  96. )
  97. }
  98. // noinspection JSUnusedGlobalSymbols
  99. /**
  100. * Correspondances entre les noms des fonctions définies dans les conditions des fichiers de configuration et
  101. * les méthodes correspondantes
  102. *
  103. * TODO: voir pourquoi on a besoin d'accepter un param null pour le hasProfile?
  104. */
  105. handlerMap: Record<string, (parameters: Array<AbilitiesType>) => boolean> = {
  106. accessHasAllRoleAbilities: (parameters: Array<AbilitiesType>) =>
  107. this.hasAllRoleAbilities(parameters),
  108. accessHasAnyRoleAbility: (parameters: Array<AbilitiesType>) =>
  109. this.hasAnyRoleAbility(parameters),
  110. accessHasAnyProfile: (parameters: Array<AbilitiesType>) =>
  111. parameters === null || this.hasAnyProfile(parameters),
  112. organizationHasAllModules: (parameters: Array<AbilitiesType>) =>
  113. this.hasAllModules(parameters),
  114. organizationHasAnyModule: (parameters: Array<AbilitiesType>) =>
  115. this.hasAnyModule(parameters),
  116. accessIsAdminAccount: (_) => this.accessProfile.isAdminAccount,
  117. organizationIsSchool: (_) => this.organizationProfile.isSchool,
  118. organizationIsArtist: (_) => this.organizationProfile.isArtist,
  119. organizationIsManagerProduct: (_) =>
  120. this.organizationProfile.isManagerProduct,
  121. organizationHasChildren: (_) => this.organizationProfile.hasChildren,
  122. organizationIsAssociation: (_) => this.organizationProfile.isAssociation,
  123. organizationIsShowAdherentList: (_) =>
  124. this.organizationProfile.isShowAdherentList,
  125. organizationIsCmf: (_) => this.organizationProfile.isCmf,
  126. organizationHasWebsite: (_) => this.organizationProfile.getWebsite,
  127. }
  128. /**
  129. * Exécute la fonction associée à la condition, et compare le résultat obtenu au résultat attendu (true par défaut)
  130. *
  131. * @param condition Un condition à la possession d'une habilité, telle que définie dans les fichiers de config
  132. * @param subject For debugging purpose only
  133. * @private
  134. */
  135. protected execAndValidateCondition(
  136. condition: Condition,
  137. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  138. subject: string = '',
  139. ) {
  140. const expectedResult: boolean = condition.expectedResult ?? true
  141. const parameters = condition.parameters ?? []
  142. if (!(condition.function in this.handlerMap)) {
  143. throw new Error('unknown condition function : ' + condition.function)
  144. }
  145. const actualResult = this.handlerMap[condition.function](parameters ?? null)
  146. return actualResult === expectedResult
  147. }
  148. /**
  149. * Est-ce que l'utilisateur possède l'habilité en paramètre
  150. *
  151. * @return {boolean}
  152. * @param ability
  153. */
  154. hasRoleAbility(ability: AbilitiesType): boolean {
  155. return this.ability.can(ability.action, ability.subject)
  156. }
  157. /**
  158. * Est-ce que l'utilisateur possède toutes les habilités passées en paramètre
  159. *
  160. * @param {Array<AbilitiesType>} abilities Habilités à tester
  161. * @return {boolean}
  162. */
  163. hasAllRoleAbilities(abilities: Array<AbilitiesType>): boolean {
  164. return abilities.every((ability) => this.hasRoleAbility(ability))
  165. }
  166. /**
  167. * Est-ce que l'utilisateur possède au moins l'une des habilités passées en paramètre
  168. *
  169. * @param {Array<AbilitiesType>} abilities Habilités à tester
  170. * @return {boolean}
  171. */
  172. hasAnyRoleAbility(abilities: Array<AbilitiesType>): boolean {
  173. return abilities.some((ability) => this.hasRoleAbility(ability))
  174. }
  175. /**
  176. * Teste si l'utilisateur possède le profil donné
  177. *
  178. * @param {string} profile Profil à tester
  179. * @return {boolean}
  180. */
  181. hasProfile(profile: string): boolean {
  182. return (
  183. {
  184. admin: this.accessProfile.isAdmin,
  185. administratifManager: this.accessProfile.isAdministratifManager,
  186. pedagogicManager: this.accessProfile.isPedagogicManager,
  187. financialManager: this.accessProfile.isFinancialManager,
  188. caMember: this.accessProfile.isCaMember,
  189. student: this.accessProfile.isStudent,
  190. teacher: this.accessProfile.isTeacher,
  191. member: this.accessProfile.isMember,
  192. other: this.accessProfile.isOther,
  193. guardian: this.accessProfile.isGuardian,
  194. payor: this.accessProfile.isPayer,
  195. }[profile] ?? false
  196. )
  197. }
  198. /**
  199. * Retourne vrai si l'utilisateur connecté possède l'un des profils passés en paramètre
  200. *
  201. * @param {Array<string>} profiles Profils à tester
  202. * @return {boolean}
  203. */
  204. hasAnyProfile(profiles: Array<string>): boolean {
  205. return profiles.some((p) => this.hasProfile(p))
  206. }
  207. /**
  208. * Retourne vrai si l'utilisateur connecté possède tous les profils passés en paramètre
  209. *
  210. * @param {Array<string>} profiles Profils à tester
  211. * @return {boolean}
  212. */
  213. hasAllProfiles(profiles: Array<string>): boolean {
  214. return profiles.every((p) => this.hasProfile(p))
  215. }
  216. /**
  217. * Est-ce que l'utilisateur possède le rôle donné ?
  218. *
  219. * @return {boolean}
  220. * @param role
  221. */
  222. hasRole(role: string): boolean {
  223. return this.accessProfile.hasRole(role)
  224. }
  225. /**
  226. * L'utilisateur possède-t-il au moins l'un des rôles donnés
  227. *
  228. * @return {boolean}
  229. * @param roles
  230. */
  231. hasAnyRole(roles: Array<string>): boolean {
  232. return roles.some((r) => this.hasRole(r))
  233. }
  234. /**
  235. * L'utilisateur possède-t-il tous les rôles donnés
  236. *
  237. * @return {boolean}
  238. * @param roles
  239. */
  240. hasAllRoles(roles: Array<string>): boolean {
  241. return roles.every((r) => this.hasRole(r))
  242. }
  243. /**
  244. * Est-ce que l'organisation possède le module donné
  245. *
  246. * @return {boolean}
  247. * @param module
  248. */
  249. hasModule(module: string): boolean {
  250. return this.organizationProfile.hasModule(module)
  251. }
  252. /**
  253. * Est-ce que l'organisation possède au moins un des modules donnés
  254. *
  255. * @param modules
  256. * @return {boolean}
  257. */
  258. hasAnyModule(modules: Array<string>): boolean {
  259. return modules.some((r) => this.hasModule(r))
  260. }
  261. /**
  262. * Est-ce que l'organisation possède-t-il tous les modules donnés
  263. *
  264. * @param modules
  265. * @return {boolean}
  266. */
  267. hasAllModules(modules: Array<string>): boolean {
  268. return modules.every((r) => this.hasModule(r))
  269. }
  270. }
  271. export default AbilityBuilder