permissions.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import { Action, ResourceType, UserPermission } from 'generated-sources';
  2. export type RolesType = UserPermission[];
  3. export type RolesModifiedTypes = Map<string, Map<ResourceType, RolesType>>;
  4. const ResourceExemptList: ResourceType[] = [
  5. ResourceType.KSQL,
  6. ResourceType.CLUSTERCONFIG,
  7. ResourceType.APPLICATIONCONFIG,
  8. ResourceType.ACL,
  9. ResourceType.AUDIT,
  10. ];
  11. export function modifyRolesData(
  12. data?: RolesType
  13. ): Map<string, Map<ResourceType, RolesType>> {
  14. const map = new Map<string, Map<ResourceType, RolesType>>();
  15. data?.forEach((item) => {
  16. item.clusters.forEach((name) => {
  17. const cluster = map.get(name);
  18. if (cluster) {
  19. const { resource } = item;
  20. const resourceItem = cluster.get(resource);
  21. if (resourceItem) {
  22. cluster.set(resource, resourceItem.concat(item));
  23. return;
  24. }
  25. cluster.set(resource, [item]);
  26. return;
  27. }
  28. map.set(name, new Map().set(item.resource, [item]));
  29. });
  30. });
  31. return map;
  32. }
  33. interface IsPermittedConfig {
  34. roles?: RolesModifiedTypes;
  35. resource: ResourceType;
  36. action: Action | Array<Action>;
  37. clusterName: string;
  38. value?: string;
  39. rbacFlag: boolean;
  40. }
  41. const valueMatches = (regexp: string | undefined, val: string | undefined) => {
  42. if (!val) return false;
  43. if (!regexp) return true;
  44. return new RegExp(regexp).test(val);
  45. };
  46. /**
  47. * @description it the logic behind depending on the roles whether a certain action
  48. * is permitted or not the philosophy is inspired from Headless UI libraries where
  49. * you separate the logic from the renderer besides the Creation process which is handled by isPermittedToCreate
  50. *
  51. * Algorithm: we Mapped the cluster name and the resource name , because all the actions in them are
  52. * constant and limited and hence faster lookup approach
  53. *
  54. * @example you can use this in the hook format where it used in , or if you want to calculate it dynamically
  55. * you can call this dynamically in your component but the render is on you from that point on
  56. *
  57. * Don't use this anywhere , use the hook version in the component for declarative purposes
  58. *
  59. * Array action approach bear in mind they should be from the same resource with the same name restrictions, then the logic it
  60. * will try to find every element from the given array inside the permissions data
  61. *
  62. * DON'T use the array approach until it is necessary to do so
  63. *
  64. * */
  65. export function isPermitted({
  66. roles,
  67. resource,
  68. action,
  69. clusterName,
  70. value,
  71. rbacFlag,
  72. }: {
  73. roles?: RolesModifiedTypes;
  74. resource: ResourceType;
  75. action: Action | Array<Action>;
  76. clusterName: string;
  77. value?: string;
  78. rbacFlag: boolean;
  79. }) {
  80. if (!rbacFlag) return true;
  81. // short circuit
  82. if (!roles || roles.size === 0) return false;
  83. // short circuit
  84. const clusterMap = roles.get(clusterName);
  85. if (!clusterMap) return false;
  86. // short circuit
  87. const resourcePermissions = clusterMap.get(resource);
  88. if (!resourcePermissions) return false;
  89. const actions = Array.isArray(action) ? action : [action];
  90. return actions.every((a) => {
  91. return resourcePermissions.some((item) => {
  92. if (!item.actions.includes(a)) return false;
  93. if (ResourceExemptList.includes(resource)) return true;
  94. return valueMatches(item.value, value);
  95. });
  96. });
  97. }
  98. /**
  99. * @description it the logic behind depending on create roles, since create has extra custom permission logic that is why
  100. * it is seperated from the others
  101. *
  102. * Algorithm: we Mapped the cluster name and the resource name , because all the actions in them are
  103. * constant and limited and hence faster lookup approach
  104. *
  105. * @example you can use this in the hook format where it used in , or if you want to calculate it dynamically
  106. * you can call this dynamically in your component but the render is on you from that point on
  107. *
  108. * Don't use this anywhere , use the hook version in the component for declarative purposes
  109. *
  110. * */
  111. export function isPermittedToCreate({
  112. roles,
  113. resource,
  114. clusterName,
  115. rbacFlag,
  116. }: Omit<IsPermittedConfig, 'value' | 'action'>) {
  117. if (!rbacFlag) return true;
  118. // short circuit
  119. if (!roles || roles.size === 0) return false;
  120. // short circuit
  121. const clusterMap = roles.get(clusterName);
  122. if (!clusterMap) return false;
  123. // short circuit
  124. const resourceData = clusterMap.get(resource);
  125. if (!resourceData) return false;
  126. const action = Action.CREATE;
  127. return resourceData.some((item) => {
  128. return item.actions.includes(action);
  129. });
  130. }