roleUtils.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import * as _ from 'lodash-es'
  2. import type { AbilitiesType } from '~/types/interfaces'
  3. import type { AnyJson } from '~/types/data'
  4. import type { ABILITIES } from '~/types/enum/enums'
  5. // TODO: peut-être passer ces constantes dans la config?
  6. const rolesByFunction: Array<string> = [
  7. 'ROLE_SUPER_ADMIN',
  8. 'ROLE_ADMIN',
  9. 'ROLE_ADMIN_CORE',
  10. 'ROLE_ADMINISTRATIF_MANAGER',
  11. 'ROLE_ADMINISTRATIF_MANAGER_CORE',
  12. 'ROLE_PEDAGOGICS_MANAGER',
  13. 'ROLE_PEDAGOGICS_MANAGER_CORE',
  14. 'ROLE_FINANCIAL_MANAGER',
  15. 'ROLE_FINANCIAL_MANAGER_CORE',
  16. 'ROLE_CA',
  17. 'ROLE_CA_CORE',
  18. 'ROLE_STUDENT',
  19. 'ROLE_STUDENT_CORE',
  20. 'ROLE_TEACHER',
  21. 'ROLE_TEACHER_CORE',
  22. 'ROLE_MEMBER',
  23. 'ROLE_MEMBER_CORE',
  24. 'ROLE_OTHER',
  25. 'ROLE_OTHER_CORE',
  26. ]
  27. const actions = ['VIEW', 'REFERENCE', 'CORE']
  28. const actionMap: AnyJson = {
  29. '': 'manage',
  30. VIEW: 'read',
  31. REFERENCE: null,
  32. CORE: null,
  33. }
  34. interface Role {
  35. subject: string
  36. action: 'VIEW' | 'CORE' | 'REFERENCE' | ''
  37. }
  38. /**
  39. * Classe permettant de mener des opérations sur les rôles
  40. */
  41. class RoleUtils {
  42. // Private constructor to prevent instantiation
  43. private constructor() {
  44. // This utility class is not meant to be instantiated
  45. }
  46. /**
  47. * Teste si une personne possède un profil suivant ses rôles
  48. *
  49. * @param {string} profileName
  50. * @param {Array<string>} roles
  51. * @return {boolean}
  52. */
  53. static isA(profileName: string, roles: Array<string>): boolean {
  54. profileName = profileName.toUpperCase()
  55. if (!profileName.match(/^[A-Z_]+$/)) {
  56. throw new Error('invalid role name')
  57. }
  58. // TODO: actuellement, passer un profil ne correspondant à aucun rôle ne lèvera aucune erreur, et se contentera de
  59. // retourner false; ce serait pas mal de lever une erreur, ce ne serait pas normal de demander un rôle inexistant
  60. return roles.includes('ROLE_' + profileName + '_CORE')
  61. }
  62. /**
  63. * Filtre les rôles afin d'en exclure les "Roles fonctions"
  64. *
  65. * @param {Array<string>} roles
  66. * @return {Array<string>}
  67. */
  68. static filterFunctionRoles(roles: Array<string>): Array<string> {
  69. return roles.filter((role) => {
  70. return !rolesByFunction.includes(role)
  71. })
  72. }
  73. /**
  74. * Parse une chaine de caractère décrivant un rôle applicatif
  75. *
  76. * @param role
  77. */
  78. static parseRole(role: string): Role {
  79. const parts = role.split('_')
  80. if (parts[0] !== 'ROLE') {
  81. throw new Error('can not parse role')
  82. }
  83. parts.shift()
  84. let action: 'VIEW' | 'CORE' | 'REFERENCE' | '' = ''
  85. if (actions.includes(parts.at(-1) ?? '')) {
  86. // @ts-expect-error The previous 'if' guarantees the correct type
  87. action = parts.pop() ?? ''
  88. }
  89. const subject = parts.join('-')
  90. return { subject, action }
  91. }
  92. static roleToString(role: Role) {
  93. // TODO: est-ce qu'il faut retransformer les - en _ ? (si oui, attention à maj les tests)
  94. return ['ROLE', role.subject, role.action]
  95. .filter((s: string) => s !== null && s.length > 0)
  96. .join('_')
  97. }
  98. /**
  99. * Construit une habilité à partir du rôle en paramètre.
  100. * Retourne null si le role ne donne droit à aucune habilité
  101. * @param role
  102. */
  103. static roleToAbility(role: Role): AbilitiesType | null {
  104. const mappedAction = actionMap[role.action] as ABILITIES
  105. if (mappedAction === null) {
  106. return null
  107. }
  108. return {
  109. action: mappedAction,
  110. subject: role.subject.toLowerCase(),
  111. }
  112. }
  113. /**
  114. * On transforme les ROLES Symfony en Abilities
  115. *
  116. * Ex:
  117. *
  118. * "ROLE_ORGANIZATION" => { subject: 'organization', action: 'manage'}
  119. * "ROLE_PLACE_VIEW" => { subject: 'place', action: 'read'}
  120. *
  121. * @param {Array<string>} roles
  122. * @return {Array<AbilitiesType>}
  123. */
  124. static rolesToAbilities(roles: Array<string>): [] | Array<AbilitiesType> {
  125. const abilities: Array<AbilitiesType> = []
  126. _.each(roles, (role: string) => {
  127. const parsed: Role | null = RoleUtils.parseRole(role)
  128. const ability = RoleUtils.roleToAbility(parsed)
  129. if (
  130. ability !== null &&
  131. ability.subject &&
  132. typeof ability.action !== 'undefined'
  133. ) {
  134. abilities.push(ability)
  135. }
  136. })
  137. return abilities
  138. }
  139. }
  140. export default RoleUtils