abilityBuilder.ts 10.0 KB

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