GetModelPermissions.ts 4.8 KB

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