@oneuptime/common 12.0.12 → 12.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
  2. package/Models/DatabaseModels/NetworkDevice.ts +64 -0
  3. package/Models/DatabaseModels/StatusPage.ts +59 -0
  4. package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
  5. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
  6. package/Server/API/AIChatAPI.ts +55 -56
  7. package/Server/API/CommonAPI.ts +89 -0
  8. package/Server/API/MicrosoftTeamsAPI.ts +20 -9
  9. package/Server/API/SlackAPI.ts +13 -10
  10. package/Server/API/StatusPageAPI.ts +2197 -2128
  11. package/Server/API/TelemetryAPI.ts +72 -3
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  15. package/Server/Middleware/UserAuthorization.ts +11 -2
  16. package/Server/Services/ApiKeyPermissionService.ts +116 -2
  17. package/Server/Services/DatabaseService.ts +17 -6
  18. package/Server/Services/LogAggregationService.ts +34 -0
  19. package/Server/Services/LogService.ts +21 -0
  20. package/Server/Services/NetworkSiteService.ts +12 -0
  21. package/Server/Services/SpanService.ts +21 -0
  22. package/Server/Services/TraceAggregationService.ts +15 -0
  23. package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
  24. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
  25. package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
  26. package/Server/Utils/APIKey/AccessPermission.ts +16 -40
  27. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
  28. package/Server/Utils/LogRedaction.ts +576 -0
  29. package/Server/Utils/Logger.ts +123 -46
  30. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
  31. package/Server/Utils/Monitor/MonitorResource.ts +29 -9
  32. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
  33. package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
  34. package/Server/Utils/StartServer.ts +30 -9
  35. package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
  36. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
  37. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
  38. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
  39. package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
  40. package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
  41. package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
  42. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
  43. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
  44. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
  45. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
  46. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
  47. package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
  48. package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
  49. package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
  50. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
  51. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
  52. package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
  53. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
  54. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
  55. package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
  56. package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
  57. package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
  58. package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
  59. package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
  60. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
  61. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
  62. package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
  63. package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
  64. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
  65. package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
  66. package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
  67. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
  68. package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
  69. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
  70. package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
  71. package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
  72. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
  73. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
  74. package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
  75. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
  76. package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
  77. package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
  78. package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
  79. package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
  80. package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
  81. package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
  82. package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
  83. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
  84. package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
  85. package/Tests/UI/Components/MasterPage.test.tsx +36 -1
  86. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
  87. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
  88. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
  89. package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
  90. package/Types/API/Hostname.ts +79 -0
  91. package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
  92. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
  93. package/Types/Database/DatabaseProperty.ts +38 -0
  94. package/Types/Database/TableColumn.ts +34 -7
  95. package/Types/Events/Recurring.ts +16 -1
  96. package/Types/Monitor/MonitorStep.ts +13 -1
  97. package/Types/Monitor/MonitorType.ts +345 -40
  98. package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
  99. package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
  100. package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
  101. package/UI/Components/CardSelect/CardSelect.tsx +714 -105
  102. package/UI/Components/EmptyState/EmptyState.tsx +8 -1
  103. package/UI/Components/Footer/Footer.tsx +32 -6
  104. package/UI/Components/Forms/Fields/FormField.tsx +3 -0
  105. package/UI/Components/Forms/Types/Field.ts +8 -0
  106. package/UI/Components/Loader/PageLoader.tsx +29 -8
  107. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
  108. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
  109. package/UI/Components/MasterPage/MasterPage.tsx +24 -11
  110. package/Utils/Monitor/MonitorMetricType.ts +16 -1
  111. package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
  112. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
  113. package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
  114. package/Utils/Telemetry/CrossSignalScope.ts +75 -4
  115. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
  116. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  117. package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
  118. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  119. package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
  120. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  121. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
  122. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  123. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  124. package/build/dist/Server/API/AIChatAPI.js +48 -35
  125. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  126. package/build/dist/Server/API/CommonAPI.js +59 -0
  127. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  128. package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
  129. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  130. package/build/dist/Server/API/SlackAPI.js +13 -10
  131. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  132. package/build/dist/Server/API/StatusPageAPI.js +745 -673
  133. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  134. package/build/dist/Server/API/TelemetryAPI.js +34 -3
  135. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  136. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
  137. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
  138. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
  139. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
  140. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  141. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  142. package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
  143. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  144. package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
  145. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  146. package/build/dist/Server/Services/DatabaseService.js +15 -6
  147. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  148. package/build/dist/Server/Services/LogAggregationService.js +2 -0
  149. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  150. package/build/dist/Server/Services/LogService.js +16 -0
  151. package/build/dist/Server/Services/LogService.js.map +1 -1
  152. package/build/dist/Server/Services/NetworkSiteService.js +12 -0
  153. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  154. package/build/dist/Server/Services/SpanService.js +16 -0
  155. package/build/dist/Server/Services/SpanService.js.map +1 -1
  156. package/build/dist/Server/Services/TraceAggregationService.js +2 -0
  157. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  158. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
  159. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  160. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
  161. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  162. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
  163. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
  164. package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
  165. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  166. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
  167. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  168. package/build/dist/Server/Utils/LogRedaction.js +449 -0
  169. package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
  170. package/build/dist/Server/Utils/Logger.js +98 -45
  171. package/build/dist/Server/Utils/Logger.js.map +1 -1
  172. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
  173. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
  174. package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
  175. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  176. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
  177. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  178. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
  179. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
  180. package/build/dist/Server/Utils/StartServer.js +10 -7
  181. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  182. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
  183. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
  184. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
  185. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
  186. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
  187. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  188. package/build/dist/Types/API/Hostname.js +63 -0
  189. package/build/dist/Types/API/Hostname.js.map +1 -1
  190. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
  191. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  192. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
  193. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  194. package/build/dist/Types/Database/DatabaseProperty.js +38 -0
  195. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  196. package/build/dist/Types/Database/TableColumn.js +24 -6
  197. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  198. package/build/dist/Types/Events/Recurring.js +13 -1
  199. package/build/dist/Types/Events/Recurring.js.map +1 -1
  200. package/build/dist/Types/Monitor/MonitorStep.js +9 -1
  201. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  202. package/build/dist/Types/Monitor/MonitorType.js +330 -31
  203. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  204. package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
  205. package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
  206. package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
  207. package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
  208. package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
  209. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  210. package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
  211. package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
  212. package/build/dist/UI/Components/Footer/Footer.js +7 -4
  213. package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
  214. package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
  215. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  216. package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
  217. package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
  218. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
  219. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  220. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
  221. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  222. package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
  223. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  224. package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
  225. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  226. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
  227. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  228. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
  229. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
  230. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
  231. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
  232. package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
  233. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
  234. package/package.json +1 -1
@@ -252,228 +252,44 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
252
252
  throw new BadDataException((err as Error)?.message);
253
253
  }
254
254
  }
255
+ }
255
256
 
256
- // post message
257
-
258
- for (const createdChannel of createdChannels) {
259
- try {
260
- const responses: Array<WorkspaceSendMessageResponse> =
261
- await WorkspaceUtil.postMessageToAllWorkspaceChannelsAsBot({
262
- projectId: data.projectId,
263
- messagePayloadsByWorkspace: messageBlocksByWorkspaceTypes.map(
264
- (
265
- messageBlocksByWorkspaceType: MessageBlocksByWorkspaceType,
266
- ) => {
267
- const payload: WorkspaceMessagePayload = {
268
- _type: "WorkspaceMessagePayload",
269
- workspaceType: messageBlocksByWorkspaceType.workspaceType,
270
- messageBlocks: messageBlocksByWorkspaceType.messageBlocks,
271
- channelNames: [],
272
- channelIds: [createdChannel.id],
273
- teamId: notificationRule.existingTeam,
274
- };
275
-
276
- return payload;
277
- },
278
- ),
279
- });
280
-
281
- // Log results for test sends (created channels)
282
- const getMessageSummary: (wt: WorkspaceType) => string = (
283
- wt: WorkspaceType,
284
- ): string => {
285
- const blocks: Array<WorkspaceMessageBlock> | undefined =
286
- messageBlocksByWorkspaceTypes.find(
287
- (b: MessageBlocksByWorkspaceType) => {
288
- return b.workspaceType === wt;
289
- },
290
- )?.messageBlocks;
291
- if (!blocks) {
292
- return "";
293
- }
294
- const texts: Array<string> = [];
295
- for (const block of blocks) {
296
- if (
297
- (block as WorkspacePayloadMarkdown)._type ===
298
- "WorkspacePayloadMarkdown"
299
- ) {
300
- texts.push((block as WorkspacePayloadMarkdown).text);
301
- }
302
- }
303
- const joined: string = texts.join(" \n").trim();
304
- return joined;
305
- };
306
-
307
- for (const res of responses) {
308
- const messageSummary: string = getMessageSummary(res.workspaceType);
309
-
310
- // Check for errors in the response
311
- if (res.errors && res.errors.length > 0) {
312
- /*
313
- * Record the failure before throwing. Test sends used to throw
314
- * straight out of here, so a failed test left no trace in
315
- * Notification Logs and support had no way to see what Microsoft
316
- * or Slack actually said.
317
- */
318
- await this.logTestSendFailures({
319
- projectId: data.projectId,
320
- workspaceType: res.workspaceType,
321
- errors: res.errors,
322
- message: messageSummary,
323
- userId: data.testByUserId,
324
- });
325
-
326
- const errorMessages: Array<string> = res.errors.map(
327
- (error: { channel: WorkspaceChannel; error: string }) => {
328
- return `Channel ${error.channel.name}: ${error.error}`;
329
- },
330
- );
331
- throw new BadDataException(
332
- `Failed to send test message to some channels: ${errorMessages.join(
333
- "; ",
334
- )}`,
335
- );
336
- }
337
-
338
- for (const thread of res.threads) {
339
- const log: WorkspaceNotificationLog =
340
- new WorkspaceNotificationLog();
341
- log.projectId = data.projectId;
342
- log.workspaceType = res.workspaceType;
343
- log.channelId = thread.channel.id;
344
- log.channelName = thread.channel.name;
345
- log.threadId = thread.threadId;
346
- log.message = messageSummary;
347
- log.status = WorkspaceNotificationStatus.Success;
348
- log.statusMessage = "Test message posted to workspace channel";
349
- log.userId = data.testByUserId;
350
- log.actionType = WorkspaceNotificationActionType.SendMessage;
351
-
352
- await WorkspaceNotificationLogService.create({
353
- data: log,
354
- props: { isRoot: true },
355
- });
356
- }
357
- }
358
- } catch (err) {
359
- throw new BadDataException(
360
- "Cannot post message to channel. " + (err as Error)?.message,
361
- );
362
- }
363
- }
364
-
365
- for (const channel of existingChannels) {
366
- try {
367
- const responses: Array<WorkspaceSendMessageResponse> =
368
- await WorkspaceUtil.postMessageToAllWorkspaceChannelsAsBot({
369
- projectId: data.projectId,
370
- messagePayloadsByWorkspace: messageBlocksByWorkspaceTypes.map(
371
- (
372
- messageBlocksByWorkspaceType: MessageBlocksByWorkspaceType,
373
- ) => {
374
- const payload: WorkspaceMessagePayload = {
375
- _type: "WorkspaceMessagePayload",
376
- workspaceType: messageBlocksByWorkspaceType.workspaceType,
377
- messageBlocks: messageBlocksByWorkspaceType.messageBlocks,
378
- channelNames: [channel.name],
379
- channelIds: [],
380
- };
381
-
382
- if (
383
- messageBlocksByWorkspaceType.workspaceType ===
384
- WorkspaceType.MicrosoftTeams
385
- ) {
386
- payload.teamId = channel.teamId;
387
- }
388
-
389
- return payload;
390
- },
391
- ),
392
- });
393
-
394
- // Log results for test sends (existing channels)
395
- const getMessageSummary: (wt: WorkspaceType) => string = (
396
- wt: WorkspaceType,
397
- ): string => {
398
- const blocks: Array<WorkspaceMessageBlock> | undefined =
399
- messageBlocksByWorkspaceTypes.find(
400
- (b: MessageBlocksByWorkspaceType) => {
401
- return b.workspaceType === wt;
402
- },
403
- )?.messageBlocks;
404
- if (!blocks) {
405
- return "";
406
- }
407
- const texts: Array<string> = [];
408
- for (const block of blocks) {
409
- if (
410
- (block as WorkspacePayloadMarkdown)._type ===
411
- "WorkspacePayloadMarkdown"
412
- ) {
413
- texts.push((block as WorkspacePayloadMarkdown).text);
414
- }
415
- }
416
- const joined: string = texts.join(" \n").trim();
417
- return joined;
418
- };
419
-
420
- for (const res of responses) {
421
- const messageSummary: string = getMessageSummary(res.workspaceType);
422
-
423
- // Check for errors in the response
424
- if (res.errors && res.errors.length > 0) {
425
- /*
426
- * Record the failure before throwing. Test sends used to throw
427
- * straight out of here, so a failed test left no trace in
428
- * Notification Logs and support had no way to see what Microsoft
429
- * or Slack actually said.
430
- */
431
- await this.logTestSendFailures({
432
- projectId: data.projectId,
433
- workspaceType: res.workspaceType,
434
- errors: res.errors,
435
- message: messageSummary,
436
- userId: data.testByUserId,
437
- });
438
-
439
- const errorMessages: Array<string> = res.errors.map(
440
- (error: { channel: WorkspaceChannel; error: string }) => {
441
- return `Channel ${error.channel.name}: ${error.error}`;
442
- },
443
- );
444
- throw new BadDataException(
445
- `Failed to send test message to some channels: ${errorMessages.join(
446
- "; ",
447
- )}`,
448
- );
449
- }
257
+ /*
258
+ * Post to the channels this test just created.
259
+ *
260
+ * This loop used to sit inside the shouldPostToExistingChannel block, so a
261
+ * rule that only creates a channel created it, invited nobody (a no-op on
262
+ * Teams) and reported success without ever attempting a send. The one
263
+ * action an admin runs to prove the bot can post proved nothing, and a
264
+ * passing test was read as evidence that the workspace app was wired up
265
+ * correctly when it had never been exercised.
266
+ */
267
+ for (const createdChannel of createdChannels) {
268
+ await this.postTestMessageToChannels({
269
+ projectId: data.projectId,
270
+ messageBlocksByWorkspaceTypes: messageBlocksByWorkspaceTypes,
271
+ testByUserId: data.testByUserId,
272
+ channelNames: [],
273
+ channelIds: [createdChannel.id],
274
+ /*
275
+ * The team the channel was actually created in. This used to pass
276
+ * existingTeam, which is a different field on a different rule — for a
277
+ * create-only rule it is undefined, and when both are set they can name
278
+ * two different teams.
279
+ */
280
+ teamId: createdChannel.teamId,
281
+ });
282
+ }
450
283
 
451
- for (const thread of res.threads) {
452
- const log: WorkspaceNotificationLog =
453
- new WorkspaceNotificationLog();
454
- log.projectId = data.projectId;
455
- log.workspaceType = res.workspaceType;
456
- log.channelId = thread.channel.id;
457
- log.channelName = thread.channel.name;
458
- log.threadId = thread.threadId;
459
- log.message = messageSummary;
460
- log.status = WorkspaceNotificationStatus.Success;
461
- log.statusMessage = "Test message posted to workspace channel";
462
- log.userId = data.testByUserId;
463
- log.actionType = WorkspaceNotificationActionType.SendMessage;
464
-
465
- await WorkspaceNotificationLogService.create({
466
- data: log,
467
- props: { isRoot: true },
468
- });
469
- }
470
- }
471
- } catch (err) {
472
- throw new BadDataException(
473
- "Cannot post message to channel. " + (err as Error)?.message,
474
- );
475
- }
476
- }
284
+ for (const channel of existingChannels) {
285
+ await this.postTestMessageToChannels({
286
+ projectId: data.projectId,
287
+ messageBlocksByWorkspaceTypes: messageBlocksByWorkspaceTypes,
288
+ testByUserId: data.testByUserId,
289
+ channelNames: [channel.name],
290
+ channelIds: [],
291
+ teamId: channel.teamId,
292
+ });
477
293
  }
478
294
 
479
295
  // Post test message to Microsoft Teams chats.
@@ -568,6 +384,142 @@ export class Service extends DatabaseService<WorkspaceNotificationRule> {
568
384
  }
569
385
  }
570
386
 
387
+ /*
388
+ * The markdown of a test message, for the Notification Log entry.
389
+ */
390
+ private getTestMessageSummary(data: {
391
+ messageBlocksByWorkspaceTypes: Array<MessageBlocksByWorkspaceType>;
392
+ workspaceType: WorkspaceType;
393
+ }): string {
394
+ const blocks: Array<WorkspaceMessageBlock> | undefined =
395
+ data.messageBlocksByWorkspaceTypes.find(
396
+ (b: MessageBlocksByWorkspaceType) => {
397
+ return b.workspaceType === data.workspaceType;
398
+ },
399
+ )?.messageBlocks;
400
+
401
+ if (!blocks) {
402
+ return "";
403
+ }
404
+
405
+ const texts: Array<string> = [];
406
+
407
+ for (const block of blocks) {
408
+ if (
409
+ (block as WorkspacePayloadMarkdown)._type === "WorkspacePayloadMarkdown"
410
+ ) {
411
+ texts.push((block as WorkspacePayloadMarkdown).text);
412
+ }
413
+ }
414
+
415
+ return texts.join(" \n").trim();
416
+ }
417
+
418
+ /*
419
+ * Post one test message to one set of destinations, then record the outcome in
420
+ * Notification Logs.
421
+ *
422
+ * Created channels and existing channels ran two byte-identical copies of
423
+ * this, which is how the created-channel copy ended up nested in the wrong
424
+ * conditional without anyone noticing. One path now serves both.
425
+ */
426
+ @CaptureSpan()
427
+ private async postTestMessageToChannels(data: {
428
+ projectId: ObjectID;
429
+ messageBlocksByWorkspaceTypes: Array<MessageBlocksByWorkspaceType>;
430
+ testByUserId: ObjectID;
431
+ channelNames: Array<string>;
432
+ channelIds: Array<string>;
433
+ teamId?: string | undefined;
434
+ }): Promise<void> {
435
+ try {
436
+ const responses: Array<WorkspaceSendMessageResponse> =
437
+ await WorkspaceUtil.postMessageToAllWorkspaceChannelsAsBot({
438
+ projectId: data.projectId,
439
+ messagePayloadsByWorkspace: data.messageBlocksByWorkspaceTypes.map(
440
+ (messageBlocksByWorkspaceType: MessageBlocksByWorkspaceType) => {
441
+ const payload: WorkspaceMessagePayload = {
442
+ _type: "WorkspaceMessagePayload",
443
+ workspaceType: messageBlocksByWorkspaceType.workspaceType,
444
+ messageBlocks: messageBlocksByWorkspaceType.messageBlocks,
445
+ channelNames: data.channelNames,
446
+ channelIds: data.channelIds,
447
+ };
448
+
449
+ // teamId is a Microsoft Teams concept; Slack has no equivalent.
450
+ if (
451
+ messageBlocksByWorkspaceType.workspaceType ===
452
+ WorkspaceType.MicrosoftTeams
453
+ ) {
454
+ payload.teamId = data.teamId;
455
+ }
456
+
457
+ return payload;
458
+ },
459
+ ),
460
+ });
461
+
462
+ for (const res of responses) {
463
+ const messageSummary: string = this.getTestMessageSummary({
464
+ messageBlocksByWorkspaceTypes: data.messageBlocksByWorkspaceTypes,
465
+ workspaceType: res.workspaceType,
466
+ });
467
+
468
+ // Check for errors in the response
469
+ if (res.errors && res.errors.length > 0) {
470
+ /*
471
+ * Record the failure before throwing. Test sends used to throw
472
+ * straight out of here, so a failed test left no trace in
473
+ * Notification Logs and support had no way to see what Microsoft
474
+ * or Slack actually said.
475
+ */
476
+ await this.logTestSendFailures({
477
+ projectId: data.projectId,
478
+ workspaceType: res.workspaceType,
479
+ errors: res.errors,
480
+ message: messageSummary,
481
+ userId: data.testByUserId,
482
+ });
483
+
484
+ const errorMessages: Array<string> = res.errors.map(
485
+ (error: { channel: WorkspaceChannel; error: string }) => {
486
+ return `Channel ${error.channel.name}: ${error.error}`;
487
+ },
488
+ );
489
+
490
+ throw new BadDataException(
491
+ `Failed to send test message to some channels: ${errorMessages.join(
492
+ "; ",
493
+ )}`,
494
+ );
495
+ }
496
+
497
+ for (const thread of res.threads) {
498
+ const log: WorkspaceNotificationLog = new WorkspaceNotificationLog();
499
+ log.projectId = data.projectId;
500
+ log.workspaceType = res.workspaceType;
501
+ log.channelId = thread.channel.id;
502
+ log.channelName = thread.channel.name;
503
+ log.threadId = thread.threadId;
504
+ log.message = messageSummary;
505
+ log.status = WorkspaceNotificationStatus.Success;
506
+ log.statusMessage = "Test message posted to workspace channel";
507
+ log.userId = data.testByUserId;
508
+ log.actionType = WorkspaceNotificationActionType.SendMessage;
509
+
510
+ await WorkspaceNotificationLogService.create({
511
+ data: log,
512
+ props: { isRoot: true },
513
+ });
514
+ }
515
+ }
516
+ } catch (err) {
517
+ throw new BadDataException(
518
+ "Cannot post message to channel. " + (err as Error)?.message,
519
+ );
520
+ }
521
+ }
522
+
571
523
  /*
572
524
  * Persist per-destination failures from a test send so they show up in
573
525
  * Settings > Notification Logs alongside real notification failures. Real
@@ -366,6 +366,15 @@ export default class ModelPermission {
366
366
  * never widens access (authorization stays on primaryEntityId).
367
367
  */
368
368
  "entityScope",
369
+ /*
370
+ * Synthetic query key compiled by StatementGenerator to an AND of
371
+ * per-resource-facet OR groups (primaryEntityId / entityKeys /
372
+ * resource attribute). Like entityScope it is not a real column and
373
+ * only ever narrows rows — authorization still runs on
374
+ * primaryEntityId. Written by the telemetry services' onBeforeFind
375
+ * from the client-sent `resourceFilters` ids.
376
+ */
377
+ "resourceEntityScopes",
369
378
  ];
370
379
 
371
380
  return returnArr;
@@ -23,6 +23,17 @@ import { combineWithPrivacyClause } from "../../../Utils/PrivacyFilterUtil";
23
23
  import QueryHelper from "../QueryHelper";
24
24
  import QueryUtil from "../QueryUtil";
25
25
  import CaptureSpan from "../../../Utils/Telemetry/CaptureSpan";
26
+ import Dictionary from "../../../../Types/Dictionary";
27
+
28
+ /*
29
+ * Built once per invocation and threaded through the per-column loop; never
30
+ * cached on props or module scope (the multi-tenant path re-enters with
31
+ * different per-tenant props and must rebuild fresh).
32
+ */
33
+ type AccessControlPermissionContext = {
34
+ nonAccessControlPermissions: Array<Permission>;
35
+ accessControlPermissions: Array<UserPermission>;
36
+ };
26
37
 
27
38
  export default class AccessControlPermission {
28
39
  @CaptureSpan()
@@ -290,7 +301,10 @@ export default class AccessControlPermission {
290
301
  modelType: DatabaseBaseModelType,
291
302
  props: DatabaseCommonInteractionProps,
292
303
  type: DatabaseRequestType,
304
+ context?: AccessControlPermissionContext,
293
305
  ): Array<ObjectID> {
306
+ context = context ?? this.buildPermissionContext(props);
307
+
294
308
  let labelIds: Array<ObjectID> = [];
295
309
 
296
310
  // check model level permissions.
@@ -299,7 +313,7 @@ export default class AccessControlPermission {
299
313
  TablePermission.getTablePermission(modelType, type);
300
314
 
301
315
  const modelLevelLabelIds: Array<ObjectID> =
302
- this.getAccessControlIdsByPermissions(modelLevelPermissions, props);
316
+ this.getAccessControlIdsByPermissions(modelLevelPermissions, context);
303
317
 
304
318
  labelIds = [...labelIds, ...modelLevelLabelIds];
305
319
 
@@ -331,11 +345,23 @@ export default class AccessControlPermission {
331
345
  ];
332
346
  }
333
347
 
334
- labelIds = this.getAccessControlIdsForModel(modelType, props, type);
348
+ // Build the permission context once — not per column.
349
+ const context: AccessControlPermissionContext =
350
+ this.buildPermissionContext(props);
351
+
352
+ labelIds = this.getAccessControlIdsForModel(
353
+ modelType,
354
+ props,
355
+ type,
356
+ context,
357
+ );
358
+
359
+ const columnAccessControlDictionary: Dictionary<ColumnAccessControl> =
360
+ model.getColumnAccessControlForAllColumns();
335
361
 
336
362
  for (const column of columnsToCheckPermissionFor) {
337
363
  const accessControl: ColumnAccessControl | null =
338
- model.getColumnAccessControlFor(column);
364
+ columnAccessControlDictionary[column] || null;
339
365
 
340
366
  if (!accessControl) {
341
367
  continue;
@@ -343,21 +369,21 @@ export default class AccessControlPermission {
343
369
 
344
370
  if (type === DatabaseRequestType.Read && accessControl.read) {
345
371
  const columnReadLabelIds: Array<ObjectID> =
346
- this.getAccessControlIdsByPermissions(accessControl.read, props);
372
+ this.getAccessControlIdsByPermissions(accessControl.read, context);
347
373
 
348
374
  labelIds = [...labelIds, ...columnReadLabelIds];
349
375
  }
350
376
 
351
377
  if (type === DatabaseRequestType.Create && accessControl.create) {
352
378
  const columnCreateLabelIds: Array<ObjectID> =
353
- this.getAccessControlIdsByPermissions(accessControl.create, props);
379
+ this.getAccessControlIdsByPermissions(accessControl.create, context);
354
380
 
355
381
  labelIds = [...labelIds, ...columnCreateLabelIds];
356
382
  }
357
383
 
358
384
  if (type === DatabaseRequestType.Update && accessControl.update) {
359
385
  const columnUpdateLabelIds: Array<ObjectID> =
360
- this.getAccessControlIdsByPermissions(accessControl.update, props);
386
+ this.getAccessControlIdsByPermissions(accessControl.update, context);
361
387
 
362
388
  labelIds = [...labelIds, ...columnUpdateLabelIds];
363
389
  }
@@ -369,35 +395,40 @@ export default class AccessControlPermission {
369
395
  return distinctLabelIds;
370
396
  }
371
397
 
372
- private static getAccessControlIdsByPermissions(
373
- permissions: Array<Permission>,
398
+ private static buildPermissionContext(
374
399
  props: DatabaseCommonInteractionProps,
375
- ): Array<ObjectID> {
400
+ ): AccessControlPermissionContext {
376
401
  const userPermissions: Array<UserPermission> =
377
402
  DatabaseCommonInteractionPropsUtil.getUserPermissions(
378
403
  props,
379
404
  PermissionType.Allow,
380
405
  );
381
406
 
382
- const nonAccessControlPermissionPermission: Array<Permission> =
383
- PermissionHelper.getNonAccessControlPermissions(userPermissions);
384
-
385
- const accessControlPermissions: Array<UserPermission> =
386
- PermissionHelper.getAccessControlPermissions(userPermissions);
407
+ return {
408
+ nonAccessControlPermissions:
409
+ PermissionHelper.getNonAccessControlPermissions(userPermissions),
410
+ accessControlPermissions:
411
+ PermissionHelper.getAccessControlPermissions(userPermissions),
412
+ };
413
+ }
387
414
 
415
+ private static getAccessControlIdsByPermissions(
416
+ permissions: Array<Permission>,
417
+ context: AccessControlPermissionContext,
418
+ ): Array<ObjectID> {
388
419
  let labelIds: Array<ObjectID> = [];
389
420
 
390
421
  if (
391
422
  PermissionHelper.doesPermissionsIntersect(
392
423
  permissions,
393
- nonAccessControlPermissionPermission,
424
+ context.nonAccessControlPermissions,
394
425
  )
395
426
  ) {
396
427
  return []; // if this is intersecting, then return empty array. We dont need to check for access control.
397
428
  }
398
429
 
399
430
  for (const permission of permissions) {
400
- for (const accessControlPermission of accessControlPermissions) {
431
+ for (const accessControlPermission of context.accessControlPermissions) {
401
432
  if (
402
433
  accessControlPermission.permission === permission &&
403
434
  accessControlPermission.labelIds.length > 0
@@ -1,13 +1,12 @@
1
- import APIKeyPermission from "../../../Models/DatabaseModels/ApiKeyPermission";
2
- import Label from "../../../Models/DatabaseModels/Label";
3
- import LIMIT_MAX from "../../../Types/Database/LimitMax";
4
1
  import ObjectID from "../../../Types/ObjectID";
5
2
  import Permission, {
6
3
  UserGlobalAccessPermission,
7
4
  UserPermission,
8
5
  UserTenantAccessPermission,
9
6
  } from "../../../Types/Permission";
10
- import ApiKeyPermissionService from "../../Services/ApiKeyPermissionService";
7
+ import ApiKeyPermissionService, {
8
+ ApiKeyPermissionRow,
9
+ } from "../../Services/ApiKeyPermissionService";
11
10
  import CaptureSpan from "../Telemetry/CaptureSpan";
12
11
  import UserPermissionUtil from "../UserPermission/UserPermission";
13
12
 
@@ -68,43 +67,20 @@ export default class APIKeyAccessPermission {
68
67
  projectId: ObjectID,
69
68
  apiKeyId: ObjectID,
70
69
  ): Promise<UserTenantAccessPermission> {
71
- // get team permissions.
72
- const apiKeyPermission: Array<APIKeyPermission> =
73
- await ApiKeyPermissionService.findBy({
74
- query: {
75
- apiKeyId: apiKeyId,
76
- },
77
- select: {
78
- permission: true,
79
- labels: {
80
- _id: true,
81
- },
82
- isBlockPermission: true,
83
- },
70
+ // get team permissions (cached — see ApiKeyPermissionService).
71
+ const apiKeyPermissionRows: Array<ApiKeyPermissionRow> =
72
+ await ApiKeyPermissionService.findPermissionsByApiKeyId(apiKeyId);
84
73
 
85
- limit: LIMIT_MAX,
86
- skip: 0,
87
- props: {
88
- isRoot: true,
89
- },
90
- });
91
-
92
- const userPermissions: Array<UserPermission> = [];
93
-
94
- for (const apiPermission of apiKeyPermission) {
95
- if (!apiPermission.labels) {
96
- apiPermission.labels = [];
97
- }
98
-
99
- userPermissions.push({
100
- permission: apiPermission.permission!,
101
- labelIds: apiPermission.labels.map((label: Label) => {
102
- return label.id!;
103
- }),
104
- isBlockPermission: apiPermission.isBlockPermission,
105
- _type: "UserPermission",
106
- });
107
- }
74
+ const userPermissions: Array<UserPermission> = apiKeyPermissionRows.map(
75
+ (row: ApiKeyPermissionRow): UserPermission => {
76
+ return {
77
+ permission: row.permission,
78
+ labelIds: row.labelIds,
79
+ isBlockPermission: row.isBlockPermission,
80
+ _type: "UserPermission",
81
+ };
82
+ },
83
+ );
108
84
 
109
85
  const permission: UserTenantAccessPermission =
110
86
  UserPermissionUtil.getDefaultUserTenantAccessPermission(projectId);