GetModelPermissions.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import { isObjectIdOrHexString } from "mongoose";
  2. import { forEachIn } from "@common/utils/forEachIn";
  3. import CacheModule from "@/modules/CacheModule";
  4. import DataModule from "@/modules/DataModule";
  5. import ModuleManager from "@/ModuleManager";
  6. import GetPermissions, { GetPermissionsResult } from "./GetPermissions";
  7. import DataModuleJob from "@/modules/DataModule/DataModuleJob";
  8. export type GetSingleModelPermissionsResult = Record<string, boolean>; // Returned when getting permissions for a single modelId
  9. export type GetMultipleModelPermissionsResult = Record<
  10. string,
  11. Record<string, boolean>
  12. >; // Returned when getting permissions for several modelIds
  13. export type GetModelPermissionsResult =
  14. | GetSingleModelPermissionsResult
  15. | GetMultipleModelPermissionsResult;
  16. export default class GetModelPermissions extends DataModuleJob {
  17. protected static _modelName = "users";
  18. protected static _hasPermission = true;
  19. protected override async _validate() {
  20. if (typeof this._payload !== "object" || this._payload === null)
  21. throw new Error("Payload must be an object");
  22. if (typeof this._payload.modelName !== "string")
  23. throw new Error("Model name must be a string");
  24. if (
  25. !isObjectIdOrHexString(this._payload.modelId) &&
  26. typeof this._payload.modelId !== "undefined" &&
  27. this._payload.modelId !== null
  28. )
  29. throw new Error("Model Id must be an ObjectId or undefined");
  30. }
  31. protected override async _authorize() {}
  32. protected async _execute(): Promise<GetModelPermissionsResult> {
  33. const { modelName, modelId, modelIds } = this._payload;
  34. const user = await this._context.getUser().catch(() => null);
  35. const permissions = (await this._context.executeJob(
  36. GetPermissions
  37. )) as GetPermissionsResult;
  38. const Model = await DataModule.getModel(modelName);
  39. if (!Model) throw new Error("Model not found");
  40. if (!modelId && (!modelIds || modelIds.length === 0)) {
  41. const cacheKey = this._getCacheKey(user, modelName);
  42. const cached = await CacheModule.get(cacheKey);
  43. if (cached) return cached;
  44. const modelPermissions = await this._getPermissionsForModel(
  45. user,
  46. permissions,
  47. modelName,
  48. modelId
  49. );
  50. await CacheModule.set(cacheKey, modelPermissions, 360);
  51. return modelPermissions;
  52. }
  53. if (modelId) {
  54. const cacheKey = this._getCacheKey(user, modelName, modelId);
  55. const cached = await CacheModule.get(cacheKey);
  56. if (cached) return cached;
  57. const model = await Model.findById(modelId);
  58. if (!model) throw new Error("Model not found");
  59. const modelPermissions = await this._getPermissionsForModel(
  60. user,
  61. permissions,
  62. modelName,
  63. modelId,
  64. model
  65. );
  66. await CacheModule.set(cacheKey, modelPermissions, 360);
  67. return modelPermissions;
  68. }
  69. const result: any = {};
  70. const uncachedModelIds: any = [];
  71. await forEachIn(modelIds, async modelId => {
  72. const cacheKey = this._getCacheKey(user, modelName, modelId);
  73. const cached = await CacheModule.get(cacheKey);
  74. if (cached) {
  75. result[modelId] = cached;
  76. return;
  77. }
  78. uncachedModelIds.push(modelId);
  79. });
  80. const uncachedModels = await Model.find({ _id: uncachedModelIds });
  81. await forEachIn(uncachedModelIds, async modelId => {
  82. const model = uncachedModels.find(
  83. model => model._id.toString() === modelId.toString()
  84. );
  85. if (!model) throw new Error(`No model found for ${modelId}.`);
  86. const modelPermissions = await this._getPermissionsForModel(
  87. user,
  88. permissions,
  89. modelName,
  90. modelId,
  91. model
  92. );
  93. const cacheKey = this._getCacheKey(user, modelName, modelId);
  94. await CacheModule.set(cacheKey, modelPermissions, 360);
  95. result[modelId] = modelPermissions;
  96. });
  97. return result;
  98. }
  99. protected async _getPermissionsForModel(
  100. user: any,
  101. permissions: GetPermissionsResult,
  102. modelName: string,
  103. modelId: string,
  104. model?: any
  105. ) {
  106. const modelPermissions = Object.fromEntries(
  107. Object.entries(permissions).filter(
  108. ([permission]) =>
  109. permission.startsWith(`data.${modelName}.`) ||
  110. permission.startsWith(`event.data.${modelName}.`)
  111. )
  112. );
  113. await forEachIn(
  114. Object.entries(
  115. ModuleManager.getModule("data")?.getJobs() ?? {}
  116. ).filter(
  117. ([jobName]) =>
  118. jobName.startsWith(modelName.toString()) &&
  119. (modelId ? true : !jobName.endsWith("ById"))
  120. ) as [string, typeof DataModuleJob][],
  121. async ([jobName, Job]) => {
  122. jobName = `data.${jobName}`;
  123. let hasPermission = permissions[jobName];
  124. if (!hasPermission && modelId)
  125. hasPermission =
  126. permissions[`${jobName}.*`] ||
  127. permissions[`${jobName}.${modelId}`];
  128. if (hasPermission) {
  129. modelPermissions[jobName] = true;
  130. return;
  131. }
  132. if (typeof Job.hasPermission === "function") {
  133. hasPermission = await Job.hasPermission(model, user);
  134. }
  135. modelPermissions[jobName] = !!hasPermission;
  136. }
  137. );
  138. return modelPermissions;
  139. }
  140. protected _getCacheKey(user: any, modelName: string, modelId?: string) {
  141. let cacheKey = `model-permissions.${modelName}`;
  142. if (modelId) cacheKey += `.${modelId}`;
  143. if (user) cacheKey += `.user.${user._id}`;
  144. else cacheKey += `.guest`;
  145. return cacheKey;
  146. }
  147. }