abilityBuilder.ts 9.9 KB

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