@oneuptime/common 13.0.8 → 14.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.
- package/Models/AnalyticsModels/AuditLog.ts +23 -15
- package/Models/DatabaseModels/GlobalConfig.ts +24 -0
- package/Models/DatabaseModels/Monitor.ts +3 -2
- package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
- package/Scripts/generate-service-worker.js +65 -4
- package/Server/API/BaseAPI.ts +26 -5
- package/Server/API/GlobalConfigAPI.ts +228 -462
- package/Server/API/ProjectOIDC.ts +25 -16
- package/Server/API/ProjectSSO.ts +24 -16
- package/Server/API/StatusPageAPI.ts +100 -42
- package/Server/API/TeamMemberAPI.ts +10 -1
- package/Server/Enterprise/EnterpriseEdition.ts +649 -0
- package/Server/Enterprise/EnterpriseFeature.ts +48 -0
- package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
- package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
- package/Server/EnvironmentConfig.ts +147 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Middleware/UserAuthorization.ts +38 -16
- package/Server/Services/AuditLogService.ts +84 -1079
- package/Server/Services/EnterpriseLicenseService.ts +38 -0
- package/Server/Services/GlobalConfigService.ts +80 -1
- package/Server/Services/MonitorProbeService.ts +116 -20
- package/Server/Services/MonitorService.ts +56 -4
- package/Server/Services/MonitorStatusTimelineService.ts +284 -0
- package/Server/Services/MonitorTemplateService.ts +26 -0
- package/Server/Services/ProjectService.ts +284 -0
- package/Server/Services/RumSessionPinService.ts +25 -6
- package/Server/Services/StatusPagePrivateUserService.ts +7 -3
- package/Server/Services/StatusPageService.ts +78 -1
- package/Server/Services/TeamMemberService.ts +11 -0
- package/Server/Services/TeamService.ts +10 -0
- package/Server/Services/UserService.ts +10 -17
- package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
- package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
- package/Server/Types/Database/Permissions/TablePermission.ts +10 -3
- package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
- package/Server/Types/Markdown.ts +41 -0
- package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
- package/Server/Utils/EditionEnforcement.ts +233 -0
- package/Server/Utils/FrontendEnvironment.ts +41 -2
- package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
- package/Server/Utils/Monitor/AffectedResourceList.ts +190 -0
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +543 -333
- package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
- package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
- package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
- package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
- package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
- package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
- package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
- package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
- package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
- package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
- package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
- package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
- package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
- package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
- package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +45 -9
- package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
- package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
- package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -83
- package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
- package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
- package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
- package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
- package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
- package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
- package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
- package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
- package/Tests/Server/API/GlobalConfigLicense.test.ts +742 -497
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +35 -909
- package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
- package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
- package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
- package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
- package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
- package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
- package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
- package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
- package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
- package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
- package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
- package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
- package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
- package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
- package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
- package/Tests/Server/Services/AuditLogService.test.ts +460 -990
- package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
- package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
- package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
- package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
- package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
- package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
- package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
- package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
- package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
- package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
- package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
- package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
- package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
- package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
- package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
- package/Tests/Server/Types/Markdown.test.ts +181 -0
- package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
- package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
- package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
- package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
- package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
- package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +934 -96
- package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
- package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
- package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
- package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
- package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
- package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
- package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
- package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +84 -178
- package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
- package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
- package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
- package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
- package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
- package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
- package/Tests/Utils/HostAddressUtil.test.ts +138 -0
- package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
- package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
- package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
- package/Types/API/Hostname.ts +63 -16
- package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
- package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
- package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
- package/Types/Database/CanonicalModelInstance.ts +102 -0
- package/Types/Database/TableColumn.ts +20 -6
- package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
- package/Types/IP/IPv6.ts +6 -1
- package/Types/Monitor/MonitorStep.ts +6 -1
- package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
- package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
- package/UI/Components/EditionLabel/EditionLabel.tsx +796 -1195
- package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
- package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
- package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
- package/UI/Components/ModelList/ModelList.tsx +11 -1
- package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
- package/UI/esbuild-enterprise.js +263 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
- package/Utils/HostAddressUtil.ts +94 -0
- package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
- package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
- package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
- package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
- package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +26 -5
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/GlobalConfigAPI.js +148 -344
- package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
- package/build/dist/Server/API/ProjectOIDC.js +24 -16
- package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
- package/build/dist/Server/API/ProjectSSO.js +24 -16
- package/build/dist/Server/API/ProjectSSO.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +93 -43
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +8 -1
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
- package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
- package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
- package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
- package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +84 -2
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +30 -11
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AuditLogService.js +71 -740
- package/build/dist/Server/Services/AuditLogService.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/GlobalConfigService.js +63 -0
- package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
- package/build/dist/Server/Services/MonitorProbeService.js +90 -17
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +45 -4
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +214 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RumSessionPinService.js +24 -6
- package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +71 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +10 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TeamService.js +9 -0
- package/build/dist/Server/Services/TeamService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +10 -16
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js +10 -3
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +31 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
- package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
- package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
- package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
- package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +96 -0
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +449 -261
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
- package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Types/API/Hostname.js +59 -11
- package/build/dist/Types/API/Hostname.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
- package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
- package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
- package/build/dist/Types/Database/TableColumn.js +19 -6
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
- package/build/dist/Types/IP/IPv6.js +6 -1
- package/build/dist/Types/IP/IPv6.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +5 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
- package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
- package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +414 -585
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
- package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
- package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
- package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
- package/build/dist/Utils/HostAddressUtil.js +80 -0
- package/build/dist/Utils/HostAddressUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
- package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
- package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
- package/jest.config.json +3 -1
- package/package.json +1 -1
- package/test-setup.sh +103 -34
- package/tsconfig.json +13 -0
- package/Server/API/EnterpriseLicenseAPI.ts +0 -925
- package/Server/API/TeamComplianceAPI.ts +0 -158
- package/Server/Middleware/SCIMAuthorization.ts +0 -141
- package/Server/Services/TeamComplianceService.ts +0 -733
- package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
- package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
- package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
- package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
- package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +0 -761
- package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
- package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
- package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +0 -321
- package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
- package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
- package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
- package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
- package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
- package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
- package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
- package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
- package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
- package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
- package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
- package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
- package/build/dist/Server/Services/TeamComplianceService.js +0 -537
- package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
- package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
- package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
import EditionEnforcement from "../../../Server/Utils/EditionEnforcement";
|
|
2
|
+
import EnterpriseEdition from "../../../Server/Enterprise/EnterpriseEdition";
|
|
3
|
+
import ProjectService, {
|
|
4
|
+
PROJECT_SSO_REQUIREMENT_COLUMNS,
|
|
5
|
+
} from "../../../Server/Services/ProjectService";
|
|
6
|
+
import StatusPageService, {
|
|
7
|
+
STATUS_PAGE_SSO_REQUIREMENT_COLUMNS,
|
|
8
|
+
} from "../../../Server/Services/StatusPageService";
|
|
9
|
+
import ModelPermission from "../../../Server/Types/Database/Permissions/Index";
|
|
10
|
+
import UpdateBy from "../../../Server/Types/Database/UpdateBy";
|
|
11
|
+
import logger from "../../../Server/Utils/Logger";
|
|
12
|
+
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
13
|
+
import PaymentRequiredException from "../../../Types/Exception/PaymentRequiredException";
|
|
14
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
15
|
+
import { UserGlobalAccessPermission } from "../../../Types/Permission";
|
|
16
|
+
import Project from "../../../Models/DatabaseModels/Project";
|
|
17
|
+
import StatusPage from "../../../Models/DatabaseModels/StatusPage";
|
|
18
|
+
import FakeEnterpriseModule, {
|
|
19
|
+
createEditionStateCases,
|
|
20
|
+
createLicenseSnapshotWithStatus,
|
|
21
|
+
EditionStateCase,
|
|
22
|
+
installFakeEnterpriseModule,
|
|
23
|
+
uninstallEnterpriseModule,
|
|
24
|
+
} from "../Enterprise/FakeEnterpriseModule";
|
|
25
|
+
import { setTestBillingEnabled } from "../Enterprise/TestBillingFlag";
|
|
26
|
+
import { getJestSpyOn } from "../../Spy";
|
|
27
|
+
import {
|
|
28
|
+
afterEach,
|
|
29
|
+
beforeEach,
|
|
30
|
+
describe,
|
|
31
|
+
expect,
|
|
32
|
+
jest,
|
|
33
|
+
test,
|
|
34
|
+
} from "@jest/globals";
|
|
35
|
+
|
|
36
|
+
jest.mock("../../../Server/EnvironmentConfig", () => {
|
|
37
|
+
const billingFlag: typeof import("../Enterprise/TestBillingFlag") =
|
|
38
|
+
jest.requireActual(
|
|
39
|
+
"../Enterprise/TestBillingFlag",
|
|
40
|
+
) as typeof import("../Enterprise/TestBillingFlag");
|
|
41
|
+
|
|
42
|
+
return billingFlag.withLiveBillingFlag(
|
|
43
|
+
jest.requireActual("../../../Server/EnvironmentConfig") as Record<
|
|
44
|
+
string,
|
|
45
|
+
unknown
|
|
46
|
+
>,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/*
|
|
51
|
+
* The write half of the SSO requirement read mask
|
|
52
|
+
* (EditionEnforcement.guardSsoRequirementWrite, run by ProjectService and
|
|
53
|
+
* StatusPageService onBeforeUpdate).
|
|
54
|
+
*
|
|
55
|
+
* While SSO is not active - the Community Edition, or an Enterprise install
|
|
56
|
+
* whose license lapsed - reads made for a caller report "Require SSO for
|
|
57
|
+
* login" as off and no required provider (SsoRequirementReadMasking.test.ts).
|
|
58
|
+
* The ee SSO settings card (and any API client doing a read-modify-write)
|
|
59
|
+
* then sends that masked value back when it saves. Before this guard, saving
|
|
60
|
+
* wrote requireSsoForLogin=false over a stored true, so when the license was
|
|
61
|
+
* renewed SSO was silently no longer required.
|
|
62
|
+
*
|
|
63
|
+
* Now, while the caller's reads are masked:
|
|
64
|
+
* - a write of the masked value is dropped and the stored value is kept;
|
|
65
|
+
* - a write that would set a requirement is refused (402);
|
|
66
|
+
* - a write left with nothing to save is refused with the same message.
|
|
67
|
+
* Root writes and every write while SSO is active are untouched.
|
|
68
|
+
*
|
|
69
|
+
* Billing and the edition are pinned in every test. Runs without ee/.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
73
|
+
"11111111-1111-4111-8111-111111111111",
|
|
74
|
+
);
|
|
75
|
+
const STATUS_PAGE_ID: ObjectID = new ObjectID(
|
|
76
|
+
"77777777-7777-4777-8777-777777777777",
|
|
77
|
+
);
|
|
78
|
+
const PROVIDER_ID: ObjectID = new ObjectID(
|
|
79
|
+
"88888888-8888-4888-8888-888888888888",
|
|
80
|
+
);
|
|
81
|
+
const OTHER_PROVIDER_ID: ObjectID = new ObjectID(
|
|
82
|
+
"99999999-9999-4999-8999-999999999999",
|
|
83
|
+
);
|
|
84
|
+
const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
85
|
+
|
|
86
|
+
const ownerProps: () => DatabaseCommonInteractionProps =
|
|
87
|
+
(): DatabaseCommonInteractionProps => {
|
|
88
|
+
return {
|
|
89
|
+
userId: USER_ID,
|
|
90
|
+
tenantId: PROJECT_ID,
|
|
91
|
+
userGlobalAccessPermission: {
|
|
92
|
+
projectIds: [PROJECT_ID],
|
|
93
|
+
} as unknown as UserGlobalAccessPermission,
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const masterAdminProps: () => DatabaseCommonInteractionProps =
|
|
98
|
+
(): DatabaseCommonInteractionProps => {
|
|
99
|
+
return { userId: USER_ID, isMasterAdmin: true };
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const installLapsedLicense: () => FakeEnterpriseModule =
|
|
103
|
+
(): FakeEnterpriseModule => {
|
|
104
|
+
return installFakeEnterpriseModule({
|
|
105
|
+
snapshot: createLicenseSnapshotWithStatus("expired"),
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const guard: (
|
|
110
|
+
data: Record<string, unknown>,
|
|
111
|
+
props?: DatabaseCommonInteractionProps | null,
|
|
112
|
+
columns?: ReadonlyArray<string>,
|
|
113
|
+
) => void = (
|
|
114
|
+
data: Record<string, unknown>,
|
|
115
|
+
props?: DatabaseCommonInteractionProps | null,
|
|
116
|
+
columns?: ReadonlyArray<string>,
|
|
117
|
+
): void => {
|
|
118
|
+
EditionEnforcement.guardSsoRequirementWrite({
|
|
119
|
+
props: props === undefined ? ownerProps() : props,
|
|
120
|
+
data,
|
|
121
|
+
columns: columns || PROJECT_SSO_REQUIREMENT_COLUMNS,
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const catchError: (run: () => unknown) => unknown = (
|
|
126
|
+
run: () => unknown,
|
|
127
|
+
): unknown => {
|
|
128
|
+
try {
|
|
129
|
+
run();
|
|
130
|
+
} catch (err) {
|
|
131
|
+
return err;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return null;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
beforeEach(() => {
|
|
138
|
+
setTestBillingEnabled(false);
|
|
139
|
+
uninstallEnterpriseModule();
|
|
140
|
+
getJestSpyOn(logger, "warn").mockImplementation((): void => {
|
|
141
|
+
return undefined;
|
|
142
|
+
});
|
|
143
|
+
getJestSpyOn(logger, "info").mockImplementation((): void => {
|
|
144
|
+
return undefined;
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
afterEach(() => {
|
|
149
|
+
uninstallEnterpriseModule();
|
|
150
|
+
setTestBillingEnabled(false);
|
|
151
|
+
jest.restoreAllMocks();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("EditionEnforcement.guardSsoRequirementWrite", () => {
|
|
155
|
+
test("the columns it guards are the ones the reads mask", () => {
|
|
156
|
+
expect([...PROJECT_SSO_REQUIREMENT_COLUMNS].sort()).toEqual([
|
|
157
|
+
"requireSsoForLogin",
|
|
158
|
+
"requireSsoWithSsoProviderId",
|
|
159
|
+
]);
|
|
160
|
+
expect([...STATUS_PAGE_SSO_REQUIREMENT_COLUMNS]).toEqual([
|
|
161
|
+
"requireSsoForLogin",
|
|
162
|
+
]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("in every deployment state, it acts exactly when reads are masked", () => {
|
|
166
|
+
const states: Array<EditionStateCase> = createEditionStateCases();
|
|
167
|
+
|
|
168
|
+
test.each(
|
|
169
|
+
states.map((state: EditionStateCase): [string, EditionStateCase] => {
|
|
170
|
+
return [state.label, state];
|
|
171
|
+
}),
|
|
172
|
+
)("%s", (_label: string, state: EditionStateCase) => {
|
|
173
|
+
state.apply();
|
|
174
|
+
|
|
175
|
+
const isMasked: boolean =
|
|
176
|
+
EditionEnforcement.shouldMaskSsoRequirementOnRead(ownerProps());
|
|
177
|
+
|
|
178
|
+
// SSO runs exactly where the state says it does, and only then is nothing masked.
|
|
179
|
+
expect(isMasked).toBe(!state.isActive);
|
|
180
|
+
|
|
181
|
+
const data: Record<string, unknown> = {
|
|
182
|
+
requireSsoForLogin: false,
|
|
183
|
+
requireSsoWithSsoProviderId: null,
|
|
184
|
+
name: "Renamed",
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
guard(data);
|
|
188
|
+
|
|
189
|
+
if (isMasked) {
|
|
190
|
+
expect(data).toEqual({ name: "Renamed" });
|
|
191
|
+
} else {
|
|
192
|
+
expect(data).toEqual({
|
|
193
|
+
requireSsoForLogin: false,
|
|
194
|
+
requireSsoWithSsoProviderId: null,
|
|
195
|
+
name: "Renamed",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const turningOn: Record<string, unknown> = { requireSsoForLogin: true };
|
|
200
|
+
const error: unknown = catchError((): void => {
|
|
201
|
+
guard(turningOn);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (isMasked) {
|
|
205
|
+
expect(error).toBeInstanceOf(PaymentRequiredException);
|
|
206
|
+
expect(turningOn).toEqual({ requireSsoForLogin: true });
|
|
207
|
+
} else {
|
|
208
|
+
expect(error).toBeNull();
|
|
209
|
+
expect(turningOn).toEqual({ requireSsoForLogin: true });
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("while SSO is not active (license lapsed)", () => {
|
|
215
|
+
beforeEach(() => {
|
|
216
|
+
installLapsedLicense();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("drops a write of the masked value and keeps the rest of the write", () => {
|
|
220
|
+
const data: Record<string, unknown> = {
|
|
221
|
+
requireSsoForLogin: false,
|
|
222
|
+
requireSsoWithSsoProviderId: null,
|
|
223
|
+
name: "Renamed",
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
guard(data);
|
|
227
|
+
|
|
228
|
+
expect(data).toEqual({ name: "Renamed" });
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("does the same for a master admin, whose reads are masked too", () => {
|
|
232
|
+
const data: Record<string, unknown> = {
|
|
233
|
+
requireSsoForLogin: false,
|
|
234
|
+
name: "Renamed",
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
guard(data, masterAdminProps());
|
|
238
|
+
|
|
239
|
+
expect(data).toEqual({ name: "Renamed" });
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("refuses the settings form's save, which carries nothing but the masked values", () => {
|
|
243
|
+
const data: Record<string, unknown> = {
|
|
244
|
+
requireSsoForLogin: false,
|
|
245
|
+
requireSsoWithSsoProviderId: null,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const error: unknown = catchError((): void => {
|
|
249
|
+
guard(data);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
expect(error).toBeInstanceOf(PaymentRequiredException);
|
|
253
|
+
expect((error as Error).message).toBe(
|
|
254
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test.each([
|
|
259
|
+
["switching the requirement on", { requireSsoForLogin: true }],
|
|
260
|
+
[
|
|
261
|
+
"choosing a required provider",
|
|
262
|
+
{ requireSsoWithSsoProviderId: OTHER_PROVIDER_ID },
|
|
263
|
+
],
|
|
264
|
+
[
|
|
265
|
+
"choosing a provider next to the masked flag",
|
|
266
|
+
{
|
|
267
|
+
requireSsoForLogin: false,
|
|
268
|
+
requireSsoWithSsoProviderId: PROVIDER_ID,
|
|
269
|
+
name: "Renamed",
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
["an odd value a database reads as true", { requireSsoForLogin: "true" }],
|
|
273
|
+
["a number", { requireSsoForLogin: 1 }],
|
|
274
|
+
] as Array<[string, Record<string, unknown>]>)(
|
|
275
|
+
"refuses %s, and leaves the write untouched",
|
|
276
|
+
(_label: string, write: Record<string, unknown>) => {
|
|
277
|
+
const data: Record<string, unknown> = { ...write };
|
|
278
|
+
|
|
279
|
+
const error: unknown = catchError((): void => {
|
|
280
|
+
guard(data);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
expect(error).toBeInstanceOf(PaymentRequiredException);
|
|
284
|
+
expect(data).toEqual(write);
|
|
285
|
+
},
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
test("leaves a write that does not touch the requirement alone", () => {
|
|
289
|
+
const data: Record<string, unknown> = { name: "Renamed" };
|
|
290
|
+
|
|
291
|
+
guard(data);
|
|
292
|
+
|
|
293
|
+
expect(data).toEqual({ name: "Renamed" });
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("counts a key left undefined as nothing to save", () => {
|
|
297
|
+
expect(
|
|
298
|
+
catchError((): void => {
|
|
299
|
+
guard({ requireSsoForLogin: false, name: undefined });
|
|
300
|
+
}),
|
|
301
|
+
).toBeInstanceOf(PaymentRequiredException);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("never touches an internal (root) write", () => {
|
|
305
|
+
const switchingOff: Record<string, unknown> = {
|
|
306
|
+
requireSsoForLogin: false,
|
|
307
|
+
};
|
|
308
|
+
const switchingOn: Record<string, unknown> = { requireSsoForLogin: true };
|
|
309
|
+
|
|
310
|
+
guard(switchingOff, { isRoot: true });
|
|
311
|
+
guard(switchingOn, { isRoot: true });
|
|
312
|
+
guard(switchingOff, null);
|
|
313
|
+
|
|
314
|
+
expect(switchingOff).toEqual({ requireSsoForLogin: false });
|
|
315
|
+
expect(switchingOn).toEqual({ requireSsoForLogin: true });
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("a renewal lifts it without a restart", () => {
|
|
319
|
+
const fake: FakeEnterpriseModule = installLapsedLicense();
|
|
320
|
+
|
|
321
|
+
const lapsedWrite: Record<string, unknown> = {
|
|
322
|
+
requireSsoForLogin: false,
|
|
323
|
+
name: "Renamed",
|
|
324
|
+
};
|
|
325
|
+
guard(lapsedWrite);
|
|
326
|
+
expect(lapsedWrite).toEqual({ name: "Renamed" });
|
|
327
|
+
|
|
328
|
+
fake.setSnapshot(createLicenseSnapshotWithStatus("valid"));
|
|
329
|
+
|
|
330
|
+
const renewedWrite: Record<string, unknown> = {
|
|
331
|
+
requireSsoForLogin: false,
|
|
332
|
+
name: "Renamed",
|
|
333
|
+
};
|
|
334
|
+
guard(renewedWrite);
|
|
335
|
+
expect(renewedWrite).toEqual({
|
|
336
|
+
requireSsoForLogin: false,
|
|
337
|
+
name: "Renamed",
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe("the refusal explains itself", () => {
|
|
343
|
+
test("on a lapsed Enterprise install it names the license and where to renew it", () => {
|
|
344
|
+
installLapsedLicense();
|
|
345
|
+
|
|
346
|
+
const error: unknown = catchError((): void => {
|
|
347
|
+
guard({ requireSsoForLogin: true });
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
expect((error as Error).message).toBe(
|
|
351
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
|
|
352
|
+
);
|
|
353
|
+
expect(
|
|
354
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
|
|
355
|
+
).toContain("edition label in the Admin Dashboard header");
|
|
356
|
+
expect(
|
|
357
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
|
|
358
|
+
).toContain("The saved setting is kept");
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("on the Community Edition it names the edition", () => {
|
|
362
|
+
const error: unknown = catchError((): void => {
|
|
363
|
+
guard({ requireSsoForLogin: true });
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
expect(error).toBeInstanceOf(PaymentRequiredException);
|
|
367
|
+
expect((error as Error).message).toBe(
|
|
368
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_COMMUNITY_MESSAGE,
|
|
369
|
+
);
|
|
370
|
+
expect(
|
|
371
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_COMMUNITY_MESSAGE,
|
|
372
|
+
).toContain("Community Edition");
|
|
373
|
+
expect(
|
|
374
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_COMMUNITY_MESSAGE,
|
|
375
|
+
).toContain(
|
|
376
|
+
"with a valid license (or during its 14-day trial, or the 30-day grace period after a license expires)",
|
|
377
|
+
);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("neither message claims anything keeps working", () => {
|
|
381
|
+
for (const message of [
|
|
382
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
|
|
383
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_COMMUNITY_MESSAGE,
|
|
384
|
+
]) {
|
|
385
|
+
expect(message).not.toMatch(/keeps? working|keeps? running/i);
|
|
386
|
+
expect(message).toContain("is not enforced");
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("an edition check that throws still refuses, with the license message", () => {
|
|
391
|
+
installLapsedLicense();
|
|
392
|
+
getJestSpyOn(logger, "error").mockImplementation((): void => {
|
|
393
|
+
return undefined;
|
|
394
|
+
});
|
|
395
|
+
getJestSpyOn(EnterpriseEdition, "isLoaded").mockImplementation(
|
|
396
|
+
(): boolean => {
|
|
397
|
+
throw new Error("edition check exploded");
|
|
398
|
+
},
|
|
399
|
+
);
|
|
400
|
+
getJestSpyOn(
|
|
401
|
+
EditionEnforcement,
|
|
402
|
+
"shouldMaskSsoRequirementOnRead",
|
|
403
|
+
).mockReturnValue(true);
|
|
404
|
+
|
|
405
|
+
const error: unknown = catchError((): void => {
|
|
406
|
+
guard({ requireSsoForLogin: true });
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
expect(error).toBeInstanceOf(PaymentRequiredException);
|
|
410
|
+
expect((error as Error).message).toBe(
|
|
411
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
|
|
412
|
+
);
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
/*
|
|
418
|
+
* The services run the guard from onBeforeUpdate. These go through the real
|
|
419
|
+
* DatabaseService update path; only the repository, the permission checks
|
|
420
|
+
* and the side effects after the write (workflow, realtime, audit) are
|
|
421
|
+
* stubbed.
|
|
422
|
+
*/
|
|
423
|
+
describe("updates through the services keep a stored requirement while SSO is not active", () => {
|
|
424
|
+
type StoredRow = Record<string, unknown>;
|
|
425
|
+
|
|
426
|
+
let storedProject: StoredRow;
|
|
427
|
+
let storedStatusPage: StoredRow;
|
|
428
|
+
let projectWrites: Array<Record<string, unknown>>;
|
|
429
|
+
let statusPageWrites: Array<Record<string, unknown>>;
|
|
430
|
+
|
|
431
|
+
const stubService: (
|
|
432
|
+
service: typeof ProjectService | typeof StatusPageService,
|
|
433
|
+
id: ObjectID,
|
|
434
|
+
stored: () => StoredRow,
|
|
435
|
+
writes: () => Array<Record<string, unknown>>,
|
|
436
|
+
createItem: () => Project | StatusPage,
|
|
437
|
+
) => void = (
|
|
438
|
+
service: typeof ProjectService | typeof StatusPageService,
|
|
439
|
+
id: ObjectID,
|
|
440
|
+
stored: () => StoredRow,
|
|
441
|
+
writes: () => Array<Record<string, unknown>>,
|
|
442
|
+
createItem: () => Project | StatusPage,
|
|
443
|
+
): void => {
|
|
444
|
+
getJestSpyOn(service, "_findBy").mockImplementation(
|
|
445
|
+
async (): Promise<Array<Project | StatusPage>> => {
|
|
446
|
+
const item: Project | StatusPage = createItem();
|
|
447
|
+
item._id = id.toString();
|
|
448
|
+
return [item];
|
|
449
|
+
},
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
getJestSpyOn(service, "getRepository").mockReturnValue({
|
|
453
|
+
update: async (
|
|
454
|
+
_where: unknown,
|
|
455
|
+
set: Record<string, unknown>,
|
|
456
|
+
): Promise<{ affected: number }> => {
|
|
457
|
+
const written: Record<string, unknown> = { ...set };
|
|
458
|
+
delete written["version"];
|
|
459
|
+
writes().push(written);
|
|
460
|
+
Object.assign(stored(), written);
|
|
461
|
+
return { affected: 1 };
|
|
462
|
+
},
|
|
463
|
+
save: async (): Promise<never> => {
|
|
464
|
+
throw new Error("this update must not go through save()");
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
getJestSpyOn(service, "onTriggerWorkflow").mockResolvedValue(undefined);
|
|
469
|
+
getJestSpyOn(service, "onTriggerRealtime").mockResolvedValue(undefined);
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const updateProject: (
|
|
473
|
+
data: Record<string, unknown>,
|
|
474
|
+
props?: DatabaseCommonInteractionProps,
|
|
475
|
+
) => Promise<number> = (
|
|
476
|
+
data: Record<string, unknown>,
|
|
477
|
+
props?: DatabaseCommonInteractionProps,
|
|
478
|
+
): Promise<number> => {
|
|
479
|
+
return ProjectService.updateOneById({
|
|
480
|
+
id: PROJECT_ID,
|
|
481
|
+
data: data as UpdateBy<Project>["data"],
|
|
482
|
+
props: props || ownerProps(),
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const updateStatusPage: (
|
|
487
|
+
data: Record<string, unknown>,
|
|
488
|
+
props?: DatabaseCommonInteractionProps,
|
|
489
|
+
) => Promise<number> = (
|
|
490
|
+
data: Record<string, unknown>,
|
|
491
|
+
props?: DatabaseCommonInteractionProps,
|
|
492
|
+
): Promise<number> => {
|
|
493
|
+
return StatusPageService.updateOneById({
|
|
494
|
+
id: STATUS_PAGE_ID,
|
|
495
|
+
data: data as UpdateBy<StatusPage>["data"],
|
|
496
|
+
props: props || ownerProps(),
|
|
497
|
+
});
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
beforeEach(() => {
|
|
501
|
+
storedProject = {
|
|
502
|
+
name: "Acme",
|
|
503
|
+
requireSsoForLogin: true,
|
|
504
|
+
requireSsoWithSsoProviderId: PROVIDER_ID,
|
|
505
|
+
};
|
|
506
|
+
storedStatusPage = { name: "Customer Status", requireSsoForLogin: true };
|
|
507
|
+
projectWrites = [];
|
|
508
|
+
statusPageWrites = [];
|
|
509
|
+
|
|
510
|
+
getJestSpyOn(
|
|
511
|
+
ModelPermission,
|
|
512
|
+
"checkUpdatePermissionByModel",
|
|
513
|
+
).mockResolvedValue(undefined);
|
|
514
|
+
getJestSpyOn(
|
|
515
|
+
ModelPermission,
|
|
516
|
+
"checkUpdateQueryPermissions",
|
|
517
|
+
).mockImplementation(
|
|
518
|
+
async (_modelType: unknown, query: unknown): Promise<unknown> => {
|
|
519
|
+
return query;
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
const auditLogService: { recordUpdate: () => Promise<void> } =
|
|
524
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
525
|
+
require("../../../Server/Services/AuditLogService").default;
|
|
526
|
+
getJestSpyOn(auditLogService, "recordUpdate").mockResolvedValue(undefined);
|
|
527
|
+
|
|
528
|
+
stubService(
|
|
529
|
+
ProjectService,
|
|
530
|
+
PROJECT_ID,
|
|
531
|
+
(): StoredRow => {
|
|
532
|
+
return storedProject;
|
|
533
|
+
},
|
|
534
|
+
(): Array<Record<string, unknown>> => {
|
|
535
|
+
return projectWrites;
|
|
536
|
+
},
|
|
537
|
+
(): Project => {
|
|
538
|
+
return new Project();
|
|
539
|
+
},
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
stubService(
|
|
543
|
+
StatusPageService,
|
|
544
|
+
STATUS_PAGE_ID,
|
|
545
|
+
(): StoredRow => {
|
|
546
|
+
return storedStatusPage;
|
|
547
|
+
},
|
|
548
|
+
(): Array<Record<string, unknown>> => {
|
|
549
|
+
return statusPageWrites;
|
|
550
|
+
},
|
|
551
|
+
(): StatusPage => {
|
|
552
|
+
return new StatusPage();
|
|
553
|
+
},
|
|
554
|
+
);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
describe("Project", () => {
|
|
558
|
+
test("license lapsed: saving the masked value with another change keeps the stored requirement", async () => {
|
|
559
|
+
installLapsedLicense();
|
|
560
|
+
|
|
561
|
+
await expect(
|
|
562
|
+
updateProject({
|
|
563
|
+
requireSsoForLogin: false,
|
|
564
|
+
requireSsoWithSsoProviderId: null,
|
|
565
|
+
name: "Renamed",
|
|
566
|
+
}),
|
|
567
|
+
).resolves.toBe(1);
|
|
568
|
+
|
|
569
|
+
expect(projectWrites).toHaveLength(1);
|
|
570
|
+
expect(projectWrites[0]).not.toHaveProperty("requireSsoForLogin");
|
|
571
|
+
expect(projectWrites[0]).not.toHaveProperty(
|
|
572
|
+
"requireSsoWithSsoProviderId",
|
|
573
|
+
);
|
|
574
|
+
expect(storedProject).toEqual({
|
|
575
|
+
name: "Renamed",
|
|
576
|
+
requireSsoForLogin: true,
|
|
577
|
+
requireSsoWithSsoProviderId: PROVIDER_ID,
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
test("license lapsed: the SSO settings card's save is refused and writes nothing", async () => {
|
|
582
|
+
installLapsedLicense();
|
|
583
|
+
|
|
584
|
+
await expect(
|
|
585
|
+
updateProject({
|
|
586
|
+
requireSsoForLogin: false,
|
|
587
|
+
requireSsoWithSsoProviderId: null,
|
|
588
|
+
}),
|
|
589
|
+
).rejects.toThrow(
|
|
590
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
expect(projectWrites).toEqual([]);
|
|
594
|
+
expect(storedProject["requireSsoForLogin"]).toBe(true);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test("license lapsed: an update written as a model instance is guarded the same way", async () => {
|
|
598
|
+
installLapsedLicense();
|
|
599
|
+
|
|
600
|
+
const onlyMasked: Project = new Project();
|
|
601
|
+
onlyMasked.requireSsoForLogin = false;
|
|
602
|
+
|
|
603
|
+
await expect(
|
|
604
|
+
ProjectService.updateOneById({
|
|
605
|
+
id: PROJECT_ID,
|
|
606
|
+
data: onlyMasked as unknown as UpdateBy<Project>["data"],
|
|
607
|
+
props: ownerProps(),
|
|
608
|
+
}),
|
|
609
|
+
).rejects.toBeInstanceOf(PaymentRequiredException);
|
|
610
|
+
|
|
611
|
+
const withName: Project = new Project();
|
|
612
|
+
withName.requireSsoForLogin = false;
|
|
613
|
+
withName.name = "Renamed";
|
|
614
|
+
|
|
615
|
+
await ProjectService.updateOneById({
|
|
616
|
+
id: PROJECT_ID,
|
|
617
|
+
data: withName as unknown as UpdateBy<Project>["data"],
|
|
618
|
+
props: ownerProps(),
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
expect(projectWrites).toHaveLength(1);
|
|
622
|
+
expect(storedProject["name"]).toBe("Renamed");
|
|
623
|
+
expect(storedProject["requireSsoForLogin"]).toBe(true);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
test("license lapsed: a master admin cannot switch it off either", async () => {
|
|
627
|
+
installLapsedLicense();
|
|
628
|
+
|
|
629
|
+
await updateProject(
|
|
630
|
+
{ requireSsoForLogin: false, name: "Renamed" },
|
|
631
|
+
masterAdminProps(),
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
expect(storedProject["requireSsoForLogin"]).toBe(true);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test("license lapsed: switching it on is refused", async () => {
|
|
638
|
+
storedProject["requireSsoForLogin"] = false;
|
|
639
|
+
installLapsedLicense();
|
|
640
|
+
|
|
641
|
+
await expect(
|
|
642
|
+
updateProject({ requireSsoForLogin: true }),
|
|
643
|
+
).rejects.toBeInstanceOf(PaymentRequiredException);
|
|
644
|
+
|
|
645
|
+
expect(projectWrites).toEqual([]);
|
|
646
|
+
expect(storedProject["requireSsoForLogin"]).toBe(false);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
test("Community Edition: the same save keeps the stored requirement", async () => {
|
|
650
|
+
uninstallEnterpriseModule();
|
|
651
|
+
|
|
652
|
+
await updateProject({ requireSsoForLogin: false, name: "Renamed" });
|
|
653
|
+
|
|
654
|
+
expect(storedProject["requireSsoForLogin"]).toBe(true);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("SSO active: an administrator can still switch the requirement off", async () => {
|
|
658
|
+
installFakeEnterpriseModule({
|
|
659
|
+
snapshot: createLicenseSnapshotWithStatus("valid"),
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
await updateProject({
|
|
663
|
+
requireSsoForLogin: false,
|
|
664
|
+
requireSsoWithSsoProviderId: null,
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
expect(projectWrites).toHaveLength(1);
|
|
668
|
+
expect(storedProject["requireSsoForLogin"]).toBe(false);
|
|
669
|
+
expect(storedProject["requireSsoWithSsoProviderId"]).toBeNull();
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
test("internal root writes are never guarded", async () => {
|
|
673
|
+
installLapsedLicense();
|
|
674
|
+
|
|
675
|
+
await ProjectService.updateOneById({
|
|
676
|
+
id: PROJECT_ID,
|
|
677
|
+
data: { requireSsoForLogin: false } as UpdateBy<Project>["data"],
|
|
678
|
+
props: { isRoot: true },
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
expect(storedProject["requireSsoForLogin"]).toBe(false);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test("negative control: without the guard, the masked save switches the stored requirement off", async () => {
|
|
685
|
+
installLapsedLicense();
|
|
686
|
+
getJestSpyOn(
|
|
687
|
+
EditionEnforcement,
|
|
688
|
+
"guardSsoRequirementWrite",
|
|
689
|
+
).mockImplementation((): void => {
|
|
690
|
+
return undefined;
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
await updateProject({
|
|
694
|
+
requireSsoForLogin: false,
|
|
695
|
+
requireSsoWithSsoProviderId: null,
|
|
696
|
+
name: "Renamed",
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
// This is the bug the guard prevents: renewal would find SSO not required.
|
|
700
|
+
expect(storedProject["requireSsoForLogin"]).toBe(false);
|
|
701
|
+
expect(storedProject["requireSsoWithSsoProviderId"]).toBeNull();
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
describe("StatusPage", () => {
|
|
706
|
+
test("license lapsed: saving the masked value with another change keeps the stored requirement", async () => {
|
|
707
|
+
installLapsedLicense();
|
|
708
|
+
|
|
709
|
+
await expect(
|
|
710
|
+
updateStatusPage({ requireSsoForLogin: false, name: "Renamed" }),
|
|
711
|
+
).resolves.toBe(1);
|
|
712
|
+
|
|
713
|
+
expect(statusPageWrites).toHaveLength(1);
|
|
714
|
+
expect(statusPageWrites[0]).not.toHaveProperty("requireSsoForLogin");
|
|
715
|
+
expect(storedStatusPage).toEqual({
|
|
716
|
+
name: "Renamed",
|
|
717
|
+
requireSsoForLogin: true,
|
|
718
|
+
});
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
test("license lapsed: the SSO settings card's save is refused and writes nothing", async () => {
|
|
722
|
+
installLapsedLicense();
|
|
723
|
+
|
|
724
|
+
await expect(
|
|
725
|
+
updateStatusPage({ requireSsoForLogin: false }),
|
|
726
|
+
).rejects.toThrow(
|
|
727
|
+
EditionEnforcement.SSO_REQUIREMENT_UNCHANGEABLE_LICENSE_MESSAGE,
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
expect(statusPageWrites).toEqual([]);
|
|
731
|
+
expect(storedStatusPage["requireSsoForLogin"]).toBe(true);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
test("license lapsed: switching it on is refused", async () => {
|
|
735
|
+
storedStatusPage["requireSsoForLogin"] = false;
|
|
736
|
+
installLapsedLicense();
|
|
737
|
+
|
|
738
|
+
await expect(
|
|
739
|
+
updateStatusPage({ requireSsoForLogin: true }),
|
|
740
|
+
).rejects.toBeInstanceOf(PaymentRequiredException);
|
|
741
|
+
|
|
742
|
+
expect(storedStatusPage["requireSsoForLogin"]).toBe(false);
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
test("SSO active: an administrator can still switch the requirement off", async () => {
|
|
746
|
+
installFakeEnterpriseModule({
|
|
747
|
+
snapshot: createLicenseSnapshotWithStatus("valid"),
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
await updateStatusPage({ requireSsoForLogin: false });
|
|
751
|
+
|
|
752
|
+
expect(storedStatusPage["requireSsoForLogin"]).toBe(false);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
test("negative control: without the guard, the masked save switches the stored requirement off", async () => {
|
|
756
|
+
installLapsedLicense();
|
|
757
|
+
getJestSpyOn(
|
|
758
|
+
EditionEnforcement,
|
|
759
|
+
"guardSsoRequirementWrite",
|
|
760
|
+
).mockImplementation((): void => {
|
|
761
|
+
return undefined;
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
await updateStatusPage({ requireSsoForLogin: false, name: "Renamed" });
|
|
765
|
+
|
|
766
|
+
expect(storedStatusPage["requireSsoForLogin"]).toBe(false);
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
});
|