@oneuptime/common 12.0.34 → 13.0.0

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 (259) hide show
  1. package/Models/DatabaseModels/GlobalConfig.ts +12 -12
  2. package/Models/DatabaseModels/InventoryItem.ts +44 -1
  3. package/Models/DatabaseModels/NetworkDevice.ts +126 -0
  4. package/Server/EnvironmentConfig.ts +47 -16
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Infrastructure/Redis.ts +8 -8
  8. package/Server/Infrastructure/Status.ts +10 -4
  9. package/Server/Services/DatabaseService.ts +65 -13
  10. package/Server/Services/InventoryItemService.ts +31 -1
  11. package/Server/Services/MonitorTemplateService.ts +140 -19
  12. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
  13. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
  14. package/Server/Services/NetworkDeviceService.ts +69 -0
  15. package/Server/Services/UserNotificationRuleService.ts +76 -25
  16. package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
  17. package/Server/Types/Database/QueryUtil.ts +56 -0
  18. package/Server/Types/Markdown.ts +93 -1
  19. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
  20. package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
  21. package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
  22. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
  23. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
  24. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
  25. package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
  26. package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
  27. package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
  28. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
  29. package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
  30. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
  31. package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
  32. package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
  33. package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
  34. package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
  35. package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
  36. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
  37. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
  38. package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
  39. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
  40. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
  41. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
  42. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
  43. package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
  44. package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
  45. package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
  46. package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
  47. package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
  48. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
  49. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
  50. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
  51. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
  52. package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
  53. package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
  54. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
  55. package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
  56. package/Tests/Models/InventoryItemStatus.test.ts +147 -0
  57. package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
  58. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
  59. package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
  60. package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
  61. package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
  62. package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
  63. package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
  64. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
  65. package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
  66. package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
  67. package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
  68. package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
  69. package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
  70. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
  71. package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
  72. package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
  73. package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
  74. package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
  75. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
  76. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
  77. package/Tests/Server/Types/Markdown.test.ts +194 -1
  78. package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
  79. package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
  80. package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
  81. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
  82. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
  83. package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
  84. package/Tests/Server/Utils/EventLoop.test.ts +359 -0
  85. package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
  86. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
  87. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
  88. package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
  89. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
  90. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
  91. package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
  92. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
  93. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
  94. package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
  95. package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
  96. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
  97. package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
  98. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
  99. package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
  100. package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
  101. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
  102. package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
  103. package/Tests/Types/JSONFunctions.test.ts +40 -0
  104. package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
  105. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
  106. package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
  107. package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
  108. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
  109. package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
  110. package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
  111. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
  112. package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
  113. package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
  114. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
  115. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
  116. package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
  117. package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
  118. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
  119. package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
  120. package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
  121. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
  122. package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
  123. package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
  124. package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
  125. package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
  126. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  127. package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
  128. package/Types/Dashboard/DashboardVariable.ts +8 -0
  129. package/Types/JSON.ts +4 -0
  130. package/Types/JSONFunctions.ts +16 -0
  131. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  132. package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
  133. package/Types/SerializableObjectDictionary.ts +4 -0
  134. package/Types/Telemetry/InventoryLiveness.ts +56 -0
  135. package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
  136. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
  137. package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
  138. package/UI/esbuild-config.js +6 -0
  139. package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
  140. package/Utils/Dashboard/LabelVariable.ts +152 -0
  141. package/Utils/Dashboard/VariableUrlState.ts +27 -5
  142. package/Utils/MetricUnitUtil.ts +34 -0
  143. package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
  144. package/Utils/Monitor/MetricValueFormatter.ts +342 -0
  145. package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
  146. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
  147. package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
  148. package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
  149. package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
  150. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
  152. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
  154. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  155. package/build/dist/Server/EnvironmentConfig.js +44 -12
  156. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  157. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
  158. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
  159. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  160. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  161. package/build/dist/Server/Infrastructure/Redis.js +8 -8
  162. package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
  163. package/build/dist/Server/Infrastructure/Status.js +10 -4
  164. package/build/dist/Server/Infrastructure/Status.js.map +1 -1
  165. package/build/dist/Server/Services/DatabaseService.js +58 -13
  166. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  167. package/build/dist/Server/Services/InventoryItemService.js +28 -1
  168. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  169. package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
  170. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  171. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
  172. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  173. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
  174. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  175. package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
  176. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  177. package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
  178. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  179. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
  180. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  181. package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
  182. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  183. package/build/dist/Server/Types/Markdown.js +75 -0
  184. package/build/dist/Server/Types/Markdown.js.map +1 -1
  185. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
  186. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  187. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
  188. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
  189. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
  190. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
  192. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  193. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
  194. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  195. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
  196. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  197. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
  198. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
  199. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
  200. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
  201. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
  202. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
  203. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
  204. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  205. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
  206. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
  207. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
  208. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  209. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
  210. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  211. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
  212. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  213. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
  214. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
  215. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  216. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  217. package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
  218. package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
  219. package/build/dist/Types/JSON.js +1 -0
  220. package/build/dist/Types/JSON.js.map +1 -1
  221. package/build/dist/Types/JSONFunctions.js +13 -0
  222. package/build/dist/Types/JSONFunctions.js.map +1 -1
  223. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
  224. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
  225. package/build/dist/Types/SerializableObjectDictionary.js +4 -0
  226. package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
  227. package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
  228. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
  229. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
  230. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
  231. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
  232. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  233. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
  234. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
  235. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
  236. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
  237. package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
  238. package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
  239. package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
  240. package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
  241. package/build/dist/Utils/MetricUnitUtil.js +26 -0
  242. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  243. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
  244. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
  245. package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
  246. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
  247. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
  248. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
  249. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
  250. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  251. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
  252. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  253. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
  254. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
  255. package/jest.config.json +2 -0
  256. package/package.json +1 -1
  257. package/test-setup.sh +9 -9
  258. package/tsconfig.json +51 -11
  259. package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
@@ -0,0 +1,525 @@
1
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
2
+ import SCIMMiddleware from "../../../Server/Middleware/SCIMAuthorization";
3
+ import ProjectSCIMService from "../../../Server/Services/ProjectSCIMService";
4
+ import StatusPageSCIMService from "../../../Server/Services/StatusPageSCIMService";
5
+ import {
6
+ ExpressRequest,
7
+ ExpressResponse,
8
+ OneUptimeRequest,
9
+ } from "../../../Server/Utils/Express";
10
+ import ProjectSCIM from "../../../Models/DatabaseModels/ProjectSCIM";
11
+ import StatusPageSCIM from "../../../Models/DatabaseModels/StatusPageSCIM";
12
+ import BadRequestException from "../../../Types/Exception/BadRequestException";
13
+ import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
14
+ import ObjectID from "../../../Types/ObjectID";
15
+
16
+ /*
17
+ * This middleware is the entire authentication boundary for SCIM. Everything
18
+ * behind it creates, updates and DEPROVISIONS users in a project, driven by an
19
+ * identity provider that OneUptime never sees the inside of - so a caller that
20
+ * gets past this gate can delete a customer's staff.
21
+ *
22
+ * The gate is one lookup, and the shape of that lookup is the security
23
+ * property. The bearer token is part of the QUERY, not something compared
24
+ * afterwards: a valid SCIM configuration id presented with the wrong token
25
+ * matches no row, so there is no code path on which a correct id and an
26
+ * incorrect token can meet. Tests that check "wrong token is rejected" against
27
+ * a stubbed lookup would prove nothing about that, so the assertions below are
28
+ * on the QUERY the middleware issues.
29
+ *
30
+ * The other half is Express 4 mechanics. This is an async middleware, and
31
+ * Express 4 does not catch a rejected promise from one - a throw here would
32
+ * hang the request rather than answer it. Every failure has to arrive through
33
+ * next(err), and the middleware must never reject.
34
+ */
35
+
36
+ const SCIM_ID: string = "11111111-1111-4111-8111-111111111111";
37
+ const PROJECT_ID: ObjectID = new ObjectID(
38
+ "22222222-2222-4222-8222-222222222222",
39
+ );
40
+ const STATUS_PAGE_ID: ObjectID = new ObjectID(
41
+ "33333333-3333-4333-8333-333333333333",
42
+ );
43
+ const TOKEN: string = "the-identity-provider-secret";
44
+
45
+ interface FindSpy {
46
+ mock: { calls: Array<Array<unknown>> };
47
+ }
48
+
49
+ type BuildRequestFunction = (data?: {
50
+ params?: Record<string, string>;
51
+ authorization?: string | undefined;
52
+ noHeaders?: boolean;
53
+ }) => OneUptimeRequest;
54
+
55
+ const buildRequest: BuildRequestFunction = (data?: {
56
+ params?: Record<string, string>;
57
+ authorization?: string | undefined;
58
+ noHeaders?: boolean;
59
+ }): OneUptimeRequest => {
60
+ const headers: Record<string, string> = {};
61
+
62
+ if (data?.authorization !== undefined) {
63
+ headers["authorization"] = data.authorization;
64
+ }
65
+
66
+ return {
67
+ params: data?.params ?? { projectScimId: SCIM_ID },
68
+ headers: data?.noHeaders ? undefined : headers,
69
+ method: "GET",
70
+ url: "/scim/v2/Users",
71
+ } as unknown as OneUptimeRequest;
72
+ };
73
+
74
+ type RunFunction = (req: OneUptimeRequest) => Promise<unknown>;
75
+
76
+ const run: RunFunction = async (req: OneUptimeRequest): Promise<unknown> => {
77
+ let passedToNext: unknown = "NEVER-CALLED";
78
+
79
+ await SCIMMiddleware.isAuthorizedSCIMRequest(
80
+ req as unknown as ExpressRequest,
81
+ {} as ExpressResponse,
82
+ (err?: unknown): void => {
83
+ passedToNext = err;
84
+ },
85
+ );
86
+
87
+ return passedToNext;
88
+ };
89
+
90
+ type StubProjectFunction = (config: ProjectSCIM | null) => FindSpy;
91
+
92
+ const stubProjectLookup: StubProjectFunction = (
93
+ config: ProjectSCIM | null,
94
+ ): FindSpy => {
95
+ return jest
96
+ .spyOn(ProjectSCIMService, "findOneBy")
97
+ .mockResolvedValue(config as never) as unknown as FindSpy;
98
+ };
99
+
100
+ type StubStatusPageFunction = (config: StatusPageSCIM | null) => FindSpy;
101
+
102
+ const stubStatusPageLookup: StubStatusPageFunction = (
103
+ config: StatusPageSCIM | null,
104
+ ): FindSpy => {
105
+ return jest
106
+ .spyOn(StatusPageSCIMService, "findOneBy")
107
+ .mockResolvedValue(config as never) as unknown as FindSpy;
108
+ };
109
+
110
+ type BuildProjectConfigFunction = () => ProjectSCIM;
111
+
112
+ const buildProjectConfig: BuildProjectConfigFunction = (): ProjectSCIM => {
113
+ const config: ProjectSCIM = new ProjectSCIM();
114
+ config.id = new ObjectID(SCIM_ID);
115
+ config.projectId = PROJECT_ID;
116
+ config.autoProvisionUsers = true;
117
+ config.autoDeprovisionUsers = false;
118
+
119
+ return config;
120
+ };
121
+
122
+ type BuildStatusPageConfigFunction = () => StatusPageSCIM;
123
+
124
+ const buildStatusPageConfig: BuildStatusPageConfigFunction =
125
+ (): StatusPageSCIM => {
126
+ const config: StatusPageSCIM = new StatusPageSCIM();
127
+ config.id = new ObjectID(SCIM_ID);
128
+ config.projectId = PROJECT_ID;
129
+ config.statusPageId = STATUS_PAGE_ID;
130
+
131
+ return config;
132
+ };
133
+
134
+ describe("a request with no usable credential never reaches a lookup", () => {
135
+ afterEach(() => {
136
+ jest.restoreAllMocks();
137
+ });
138
+
139
+ test("refuses a route that carries no SCIM id", async () => {
140
+ const projectSpy: FindSpy = stubProjectLookup(buildProjectConfig());
141
+ const statusPageSpy: FindSpy = stubStatusPageLookup(null);
142
+
143
+ const err: unknown = await run(
144
+ buildRequest({ params: {}, authorization: `Bearer ${TOKEN}` }),
145
+ );
146
+
147
+ expect(err).toBeInstanceOf(BadRequestException);
148
+ expect(projectSpy.mock.calls.length).toBe(0);
149
+ expect(statusPageSpy.mock.calls.length).toBe(0);
150
+ });
151
+
152
+ test("refuses a request with no Authorization header at all", async () => {
153
+ const projectSpy: FindSpy = stubProjectLookup(buildProjectConfig());
154
+
155
+ const err: unknown = await run(buildRequest());
156
+
157
+ expect(err).toBeInstanceOf(NotAuthorizedException);
158
+ expect(projectSpy.mock.calls.length).toBe(0);
159
+ });
160
+
161
+ test("refuses a request with no headers object whatsoever", async () => {
162
+ stubProjectLookup(buildProjectConfig());
163
+
164
+ expect(await run(buildRequest({ noHeaders: true }))).toBeInstanceOf(
165
+ NotAuthorizedException,
166
+ );
167
+ });
168
+
169
+ test("refuses an empty Authorization header", async () => {
170
+ stubProjectLookup(buildProjectConfig());
171
+
172
+ expect(await run(buildRequest({ authorization: "" }))).toBeInstanceOf(
173
+ NotAuthorizedException,
174
+ );
175
+ });
176
+
177
+ /*
178
+ * The scheme is required. A raw token pasted into the header - the mistake
179
+ * every identity-provider configuration screen invites - must be refused
180
+ * rather than quietly treated as a credential.
181
+ */
182
+ test("refuses a bare token with no Bearer scheme", async () => {
183
+ const projectSpy: FindSpy = stubProjectLookup(buildProjectConfig());
184
+
185
+ expect(await run(buildRequest({ authorization: TOKEN }))).toBeInstanceOf(
186
+ NotAuthorizedException,
187
+ );
188
+ expect(projectSpy.mock.calls.length).toBe(0);
189
+ });
190
+
191
+ test("refuses a different auth scheme carrying the same token", async () => {
192
+ stubProjectLookup(buildProjectConfig());
193
+
194
+ expect(
195
+ await run(buildRequest({ authorization: `Basic ${TOKEN}` })),
196
+ ).toBeInstanceOf(NotAuthorizedException);
197
+ });
198
+
199
+ /*
200
+ * "bearer" lowercase is what several HTTP clients emit. It is not accepted
201
+ * here, and that is worth recording rather than discovering: the refusal is
202
+ * a 401 an operator can act on, whereas silently accepting it would make
203
+ * the scheme check decorative.
204
+ */
205
+ test("requires the scheme spelled with a capital B", async () => {
206
+ stubProjectLookup(buildProjectConfig());
207
+
208
+ expect(
209
+ await run(buildRequest({ authorization: `bearer ${TOKEN}` })),
210
+ ).toBeInstanceOf(NotAuthorizedException);
211
+ });
212
+
213
+ test("refuses the scheme with nothing after it", async () => {
214
+ stubProjectLookup(buildProjectConfig());
215
+
216
+ expect(
217
+ await run(buildRequest({ authorization: "Bearer " })),
218
+ ).toBeInstanceOf(NotAuthorizedException);
219
+ });
220
+ });
221
+
222
+ describe("the lookup itself is the security boundary", () => {
223
+ afterEach(() => {
224
+ jest.restoreAllMocks();
225
+ });
226
+
227
+ /*
228
+ * The token is a query TERM. That is the whole property: there is no branch
229
+ * on which a correct id and an incorrect token can meet, because a wrong
230
+ * token simply matches no row. A refactor that read the row by id and
231
+ * compared the token afterwards would still pass a "wrong token is
232
+ * rejected" test while introducing a timing oracle and a place to get the
233
+ * comparison wrong.
234
+ */
235
+ test("queries on the id AND the token together", async () => {
236
+ const spy: FindSpy = stubProjectLookup(buildProjectConfig());
237
+
238
+ await run(buildRequest({ authorization: `Bearer ${TOKEN}` }));
239
+
240
+ const query: Record<string, unknown> = (
241
+ spy.mock.calls[0]![0] as Record<string, Record<string, unknown>>
242
+ )["query"]!;
243
+
244
+ expect(String(query["_id"])).toBe(SCIM_ID);
245
+ expect(query["bearerToken"]).toBe(TOKEN);
246
+ });
247
+
248
+ test("passes the token through verbatim, without trimming or decoding it", async () => {
249
+ const spy: FindSpy = stubProjectLookup(buildProjectConfig());
250
+
251
+ await run(buildRequest({ authorization: "Bearer spaced+token/=" }));
252
+
253
+ const query: Record<string, unknown> = (
254
+ spy.mock.calls[0]![0] as Record<string, Record<string, unknown>>
255
+ )["query"]!;
256
+
257
+ expect(query["bearerToken"]).toBe(" spaced+token/=");
258
+ });
259
+
260
+ /*
261
+ * The SCIM caller holds no OneUptime permissions of its own - it is an
262
+ * identity provider, not a project member - so the lookup has to run as
263
+ * root. Anything narrower would find no configuration and lock out every
264
+ * correctly configured customer.
265
+ */
266
+ test("reads as root, because the caller holds no project permissions", async () => {
267
+ const spy: FindSpy = stubProjectLookup(buildProjectConfig());
268
+
269
+ await run(buildRequest({ authorization: `Bearer ${TOKEN}` }));
270
+
271
+ expect((spy.mock.calls[0]![0] as Record<string, unknown>)["props"]).toEqual(
272
+ { isRoot: true },
273
+ );
274
+ });
275
+
276
+ test("never selects the bearer token back out of the row", async () => {
277
+ const spy: FindSpy = stubProjectLookup(buildProjectConfig());
278
+
279
+ await run(buildRequest({ authorization: `Bearer ${TOKEN}` }));
280
+
281
+ const select: Record<string, unknown> = (
282
+ spy.mock.calls[0]![0] as Record<string, Record<string, unknown>>
283
+ )["select"]!;
284
+
285
+ expect(select["bearerToken"]).toBeUndefined();
286
+ });
287
+
288
+ test("refuses when no configuration matches the id and token", async () => {
289
+ stubProjectLookup(null);
290
+ stubStatusPageLookup(null);
291
+
292
+ const err: unknown = await run(
293
+ buildRequest({ authorization: `Bearer ${TOKEN}` }),
294
+ );
295
+
296
+ expect(err).toBeInstanceOf(NotAuthorizedException);
297
+ });
298
+
299
+ test("the refusal does not disclose whether the id or the token was wrong", async () => {
300
+ stubProjectLookup(null);
301
+ stubStatusPageLookup(null);
302
+
303
+ const err: unknown = await run(
304
+ buildRequest({ authorization: `Bearer ${TOKEN}` }),
305
+ );
306
+
307
+ expect((err as Error).message).toBe(
308
+ "Invalid bearer token or SCIM configuration not found",
309
+ );
310
+ });
311
+ });
312
+
313
+ describe("a project SCIM caller that authenticates", () => {
314
+ afterEach(() => {
315
+ jest.restoreAllMocks();
316
+ });
317
+
318
+ test("is let through with no error", async () => {
319
+ stubProjectLookup(buildProjectConfig());
320
+
321
+ expect(
322
+ await run(buildRequest({ authorization: `Bearer ${TOKEN}` })),
323
+ ).toBeUndefined();
324
+ });
325
+
326
+ test("carries its project, its configuration and its type onward", async () => {
327
+ stubProjectLookup(buildProjectConfig());
328
+
329
+ const req: OneUptimeRequest = buildRequest({
330
+ authorization: `Bearer ${TOKEN}`,
331
+ });
332
+
333
+ await run(req);
334
+
335
+ const data: Record<string, unknown> =
336
+ req.bearerTokenData as unknown as Record<string, unknown>;
337
+
338
+ expect(data["type"]).toBe("project-scim");
339
+ expect(String(data["projectId"])).toBe(PROJECT_ID.toString());
340
+ expect(String(data["projectScimId"])).toBe(SCIM_ID);
341
+ expect(data["scimConfig"]).toBeInstanceOf(ProjectSCIM);
342
+ });
343
+
344
+ test("does not go on to try the status page lookup", async () => {
345
+ stubProjectLookup(buildProjectConfig());
346
+ const statusPageSpy: FindSpy = stubStatusPageLookup(
347
+ buildStatusPageConfig(),
348
+ );
349
+
350
+ await run(buildRequest({ authorization: `Bearer ${TOKEN}` }));
351
+
352
+ expect(statusPageSpy.mock.calls.length).toBe(0);
353
+ });
354
+ });
355
+
356
+ describe("a status page SCIM caller that authenticates", () => {
357
+ afterEach(() => {
358
+ jest.restoreAllMocks();
359
+ });
360
+
361
+ test("is reached only after the project lookup misses", async () => {
362
+ stubProjectLookup(null);
363
+ const statusPageSpy: FindSpy = stubStatusPageLookup(
364
+ buildStatusPageConfig(),
365
+ );
366
+
367
+ const err: unknown = await run(
368
+ buildRequest({
369
+ params: { statusPageScimId: SCIM_ID },
370
+ authorization: `Bearer ${TOKEN}`,
371
+ }),
372
+ );
373
+
374
+ expect(err).toBeUndefined();
375
+ expect(statusPageSpy.mock.calls.length).toBe(1);
376
+ });
377
+
378
+ test("carries its status page and its type onward", async () => {
379
+ stubProjectLookup(null);
380
+ stubStatusPageLookup(buildStatusPageConfig());
381
+
382
+ const req: OneUptimeRequest = buildRequest({
383
+ params: { statusPageScimId: SCIM_ID },
384
+ authorization: `Bearer ${TOKEN}`,
385
+ });
386
+
387
+ await run(req);
388
+
389
+ const data: Record<string, unknown> =
390
+ req.bearerTokenData as unknown as Record<string, unknown>;
391
+
392
+ expect(data["type"]).toBe("status-page-scim");
393
+ expect(String(data["statusPageId"])).toBe(STATUS_PAGE_ID.toString());
394
+ expect(String(data["statusPageScimId"])).toBe(SCIM_ID);
395
+ expect(data["projectId"]).toBeDefined();
396
+ });
397
+
398
+ test("is looked up on the id and the token together too", async () => {
399
+ stubProjectLookup(null);
400
+ const spy: FindSpy = stubStatusPageLookup(buildStatusPageConfig());
401
+
402
+ await run(
403
+ buildRequest({
404
+ params: { statusPageScimId: SCIM_ID },
405
+ authorization: `Bearer ${TOKEN}`,
406
+ }),
407
+ );
408
+
409
+ const query: Record<string, unknown> = (
410
+ spy.mock.calls[0]![0] as Record<string, Record<string, unknown>>
411
+ )["query"]!;
412
+
413
+ expect(String(query["_id"])).toBe(SCIM_ID);
414
+ expect(query["bearerToken"]).toBe(TOKEN);
415
+ });
416
+
417
+ test("also reads as root", async () => {
418
+ stubProjectLookup(null);
419
+ const spy: FindSpy = stubStatusPageLookup(buildStatusPageConfig());
420
+
421
+ await run(
422
+ buildRequest({
423
+ params: { statusPageScimId: SCIM_ID },
424
+ authorization: `Bearer ${TOKEN}`,
425
+ }),
426
+ );
427
+
428
+ expect((spy.mock.calls[0]![0] as Record<string, unknown>)["props"]).toEqual(
429
+ { isRoot: true },
430
+ );
431
+ });
432
+ });
433
+
434
+ describe("Express 4 does not catch a rejected async middleware", () => {
435
+ afterEach(() => {
436
+ jest.restoreAllMocks();
437
+ });
438
+
439
+ /*
440
+ * So every failure has to arrive through next(err). A middleware that threw
441
+ * would leave the request hanging until the client gave up, which reads to
442
+ * an identity provider as a timeout rather than as a rejected credential -
443
+ * and identity providers retry timeouts.
444
+ */
445
+ test("a database failure is handed to next, not thrown", async () => {
446
+ jest
447
+ .spyOn(ProjectSCIMService, "findOneBy")
448
+ .mockRejectedValue(new Error("connection refused") as never);
449
+
450
+ const err: unknown = await run(
451
+ buildRequest({ authorization: `Bearer ${TOKEN}` }),
452
+ );
453
+
454
+ expect((err as Error).message).toBe("connection refused");
455
+ });
456
+
457
+ test("a failure in the status page lookup is handed to next too", async () => {
458
+ stubProjectLookup(null);
459
+ jest
460
+ .spyOn(StatusPageSCIMService, "findOneBy")
461
+ .mockRejectedValue(new Error("clickhouse is unhappy") as never);
462
+
463
+ const err: unknown = await run(
464
+ buildRequest({
465
+ params: { statusPageScimId: SCIM_ID },
466
+ authorization: `Bearer ${TOKEN}`,
467
+ }),
468
+ );
469
+
470
+ expect((err as Error).message).toBe("clickhouse is unhappy");
471
+ });
472
+
473
+ test("the middleware itself never rejects", async () => {
474
+ jest
475
+ .spyOn(ProjectSCIMService, "findOneBy")
476
+ .mockRejectedValue(new Error("boom") as never);
477
+
478
+ await expect(
479
+ SCIMMiddleware.isAuthorizedSCIMRequest(
480
+ buildRequest({
481
+ authorization: `Bearer ${TOKEN}`,
482
+ }) as unknown as ExpressRequest,
483
+ {} as ExpressResponse,
484
+ (): void => {},
485
+ ),
486
+ ).resolves.toBeUndefined();
487
+ });
488
+
489
+ test("next is called exactly once on the refusal path", async () => {
490
+ stubProjectLookup(null);
491
+ stubStatusPageLookup(null);
492
+
493
+ let calls: number = 0;
494
+
495
+ await SCIMMiddleware.isAuthorizedSCIMRequest(
496
+ buildRequest({
497
+ authorization: `Bearer ${TOKEN}`,
498
+ }) as unknown as ExpressRequest,
499
+ {} as ExpressResponse,
500
+ (): void => {
501
+ calls++;
502
+ },
503
+ );
504
+
505
+ expect(calls).toBe(1);
506
+ });
507
+
508
+ test("next is called exactly once on the success path", async () => {
509
+ stubProjectLookup(buildProjectConfig());
510
+
511
+ let calls: number = 0;
512
+
513
+ await SCIMMiddleware.isAuthorizedSCIMRequest(
514
+ buildRequest({
515
+ authorization: `Bearer ${TOKEN}`,
516
+ }) as unknown as ExpressRequest,
517
+ {} as ExpressResponse,
518
+ (): void => {
519
+ calls++;
520
+ },
521
+ );
522
+
523
+ expect(calls).toBe(1);
524
+ });
525
+ });