@oneuptime/common 13.0.4 → 13.0.5
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/DatabaseModels/Index.ts +4 -0
- package/Models/DatabaseModels/RumApplication.ts +1 -1
- package/Models/DatabaseModels/SecurityEventConnection.ts +457 -0
- package/Models/DatabaseModels/SecurityEventConnectionRun.ts +263 -0
- package/Models/DatabaseModels/TelemetryIngestionKey.ts +2 -2
- package/Server/API/AIInvestigationAPI.ts +484 -24
- package/Server/API/GoogleSecOpsConnectionAPI.ts +249 -8
- package/Server/API/SecurityEventConnectionAPI.ts +250 -0
- package/Server/API/TelemetryAPI.ts +402 -265
- package/Server/Infrastructure/Postgres/SchemaMigrations/1792700000000-AddSecurityEventConnections.ts +99 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Middleware/TelemetryIngest.ts +101 -6
- package/Server/Services/ExceptionAggregationService.ts +7 -16
- package/Server/Services/GoogleSecOpsConnectionService.ts +15 -0
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/InventoryItemService.ts +28 -0
- package/Server/Services/LogAggregationService.ts +17 -23
- package/Server/Services/MetricAggregationService.ts +4 -14
- package/Server/Services/SecurityEventConnectionRunService.ts +10 -0
- package/Server/Services/SecurityEventConnectionService.ts +538 -0
- package/Server/Services/TraceAggregationService.ts +77 -28
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +13 -1
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +15 -1
- package/Server/Utils/AI/SRE/InvestigationEvidence.ts +658 -0
- package/Server/Utils/AI/SRE/InvestigationReferences.ts +324 -0
- package/Server/Utils/AI/Toolbox/Index.ts +38 -0
- package/Server/Utils/AI/Toolbox/TimelineTools.ts +9 -2
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +80 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +5 -3
- package/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.ts +1108 -0
- package/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.ts +732 -0
- package/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.ts +495 -0
- package/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.ts +689 -0
- package/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.ts +767 -0
- package/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.ts +571 -0
- package/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.ts +827 -0
- package/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.ts +610 -0
- package/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.ts +549 -0
- package/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.ts +830 -0
- package/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.ts +684 -0
- package/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.ts +487 -0
- package/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.ts +778 -0
- package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.ts +1002 -0
- package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.ts +556 -0
- package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.ts +275 -0
- package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.ts +53 -0
- package/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.ts +709 -0
- package/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.ts +596 -0
- package/Server/Utils/SecurityEvent/Connectors/Types.ts +191 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +353 -65
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.ts +777 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +747 -148
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.ts +48 -2
- package/Server/Utils/SecurityEvent/SecurityEventDedupe.ts +81 -0
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +741 -40
- package/Server/Utils/StartServer.ts +10 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +8 -0
- package/Server/Utils/Telemetry/KubernetesResourceAttributes.ts +108 -0
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +174 -93
- package/Server/Utils/Telemetry/ResourceFacetPlanner.ts +251 -0
- package/Server/Utils/Telemetry/ResourceFacetResolver.ts +248 -413
- package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +668 -0
- package/Server/Utils/Telemetry/TraceContextPropagation.ts +348 -0
- package/Server/Utils/Telemetry.ts +18 -0
- package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +413 -0
- package/Tests/App/Dashboard/AIRootCauseFeedItem.test.tsx +285 -0
- package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +242 -0
- package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +601 -15
- package/Tests/App/Dashboard/ChangeAlertStateAIHeader.test.tsx +835 -0
- package/Tests/App/Dashboard/CloudResourceTelemetryScope.test.ts +41 -0
- package/Tests/App/Dashboard/ConnectionTestModal.test.tsx +604 -0
- package/Tests/App/Dashboard/ConnectorTestReportView.test.tsx +738 -0
- package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +153 -6
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +165 -0
- package/Tests/App/Dashboard/EpisodeChangeState.test.tsx +1069 -0
- package/Tests/App/Dashboard/EpisodeMembersCard.test.tsx +772 -0
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +2354 -0
- package/Tests/App/Dashboard/EventOverviewSkeleton.test.tsx +210 -0
- package/Tests/App/Dashboard/EventStatBar.test.tsx +392 -0
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +247 -0
- package/Tests/App/Dashboard/ExceptionAIAssistance.test.tsx +606 -0
- package/Tests/App/Dashboard/ExceptionBreadcrumbTimeline.test.tsx +273 -0
- package/Tests/App/Dashboard/ExceptionDetailSideMenu.test.tsx +176 -0
- package/Tests/App/Dashboard/ExceptionOverviewCards.test.tsx +366 -0
- package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +371 -0
- package/Tests/App/Dashboard/ExceptionStackFrameViewer.test.tsx +440 -0
- package/Tests/App/Dashboard/ExceptionSummary.test.tsx +381 -0
- package/Tests/App/Dashboard/ExceptionTriage.test.tsx +306 -0
- package/Tests/App/Dashboard/ExceptionsResourceFacets.test.tsx +604 -0
- package/Tests/App/Dashboard/ExceptionsViewerEntityChips.test.tsx +706 -0
- package/Tests/App/Dashboard/FeedOrdering.test.ts +32 -0
- package/Tests/App/Dashboard/GoogleSecOpsConnectionForm.test.tsx +838 -0
- package/Tests/App/Dashboard/GoogleSecOpsConnectionsErrors.test.tsx +18 -0
- package/Tests/App/Dashboard/GoogleSecOpsDiagnostics.test.tsx +297 -20
- package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +618 -13
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +254 -207
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +303 -135
- package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +1251 -0
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +303 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +876 -8
- package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +35 -0
- package/Tests/App/Dashboard/InvestigationReferenceLink.test.tsx +177 -0
- package/Tests/App/Dashboard/InvestigationReportView.test.tsx +1295 -0
- package/Tests/App/Dashboard/KubernetesImageReferenceView.test.tsx +114 -0
- package/Tests/App/Dashboard/KubernetesLogsTabChips.test.tsx +285 -0
- package/Tests/App/Dashboard/LogsEntityChipNames.test.tsx +316 -0
- package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +361 -0
- package/Tests/App/Dashboard/ProfileTableEntityNames.test.tsx +432 -0
- package/Tests/App/Dashboard/ProfileViewEntityName.test.tsx +250 -0
- package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +10 -0
- package/Tests/App/Dashboard/ResourceLogsTabsBareLayout.test.tsx +700 -0
- package/Tests/App/Dashboard/ResourcePageEntityNameProps.test.tsx +803 -0
- package/Tests/App/Dashboard/RumSessionReplaySideMenu.test.tsx +11 -1
- package/Tests/App/Dashboard/ScheduledMaintenanceChangeState.test.tsx +1026 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +436 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +1195 -0
- package/Tests/App/Dashboard/SecurityEventConnectionFormModal.test.tsx +1022 -0
- package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +905 -0
- package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +247 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +375 -274
- package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +410 -0
- package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +774 -0
- package/Tests/App/Dashboard/TopErrorsPanelResourceNames.test.tsx +316 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +30 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +88 -24
- package/Tests/App/Dashboard/TraceExplorer.test.tsx +1590 -0
- package/Tests/App/Dashboard/TraceWaterfall.test.tsx +488 -0
- package/Tests/App/Dashboard/TracesAnalyticsSeriesLabels.test.tsx +367 -0
- package/Tests/App/Dashboard/TracesEntityNames.test.tsx +585 -0
- package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +613 -0
- package/Tests/App/Dashboard/UseServiceNames.test.tsx +359 -0
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +7 -0
- package/Tests/Models/InventoryItemNaming.test.ts +132 -16
- package/Tests/Models/SecurityDomainAccessControl.test.ts +61 -17
- package/Tests/Server/API/AIInvestigationAPI.test.ts +611 -2
- package/Tests/Server/API/AIInvestigationEvidenceAPI.test.ts +1098 -0
- package/Tests/Server/API/GoogleSecOpsConnectionAPI.test.ts +409 -0
- package/Tests/Server/API/SecurityEventConnectionAPI.test.ts +856 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +528 -1
- package/Tests/Server/API/TelemetryFacetsAPI.test.ts +810 -0
- package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +240 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +229 -0
- package/Tests/Server/Services/InventoryItemDisplayName.test.ts +66 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +28 -1
- package/Tests/Server/Services/SecurityEventConnectionService.test.ts +1233 -0
- package/Tests/Server/Services/TelemetryIngestionKeyValidation.test.ts +53 -0
- package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +218 -0
- package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +155 -1
- package/Tests/Server/Services/TraceAggregationExceptionScope.test.ts +142 -0
- package/Tests/Server/Services/TraceAnalyticsResourceDisplayNames.test.ts +85 -24
- package/Tests/Server/Types/Database/Permissions/SecurityRoleAccess.test.ts +33 -0
- package/Tests/Server/Utils/AI/InvestigationEvidence.test.ts +1395 -0
- package/Tests/Server/Utils/AI/InvestigationReferences.test.ts +616 -0
- package/Tests/Server/Utils/AI/TimelineTools.test.ts +119 -0
- package/Tests/Server/Utils/AI/Toolbox/AIToolbox.test.ts +151 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +65 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorExceptionScope.test.ts +224 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPerSeriesFanout.test.ts +55 -2
- package/Tests/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.test.ts +1240 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.test.ts +1853 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.test.ts +1129 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.test.ts +1066 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.test.ts +1568 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.test.ts +930 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.test.ts +1409 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.test.ts +763 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.test.ts +1296 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.test.ts +1102 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.test.ts +1174 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.test.ts +1032 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.test.ts +1558 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.test.ts +3041 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.test.ts +1168 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.test.ts +906 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.test.ts +1068 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.test.ts +1004 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.test.ts +784 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.test.ts +113 -1
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +5 -1
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsDiagnostics.test.ts +93 -13
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsHttpIntegration.test.ts +23 -1
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +25 -3
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +22 -8
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +82 -16
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSearchDetections.test.ts +730 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsThreePassPoller.test.ts +1577 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayOriginAllowlist.test.ts +41 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +1453 -18
- package/Tests/Server/Utils/Telemetry/KubernetesResourceAttributes.test.ts +162 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +460 -13
- package/Tests/Server/Utils/Telemetry/ResourceFacetPlanner.test.ts +678 -0
- package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +348 -2
- package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +525 -0
- package/Tests/Server/Utils/Telemetry/TraceContextPropagation.test.ts +420 -0
- package/Tests/Types/Rum/SessionReplayApiContracts.test.ts +73 -0
- package/Tests/Types/Rum/SessionReplayTransportContracts.test.ts +95 -0
- package/Tests/Types/SecurityEvent/Connectors/ConnectorDiagnostics.test.ts +82 -0
- package/Tests/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.test.ts +314 -0
- package/Tests/Types/Telemetry/ExceptionSpanScope.test.ts +89 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +96 -0
- package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +304 -0
- package/Tests/UI/Components/ActiveFilterChipsLockedFilters.test.tsx +357 -0
- package/Tests/UI/Components/Card.test.tsx +312 -1
- package/Tests/UI/Components/CardModelDetailHeaderLayout.test.tsx +253 -0
- package/Tests/UI/Components/CardModelDetailRefresher.test.tsx +342 -0
- package/Tests/UI/Components/Checkbox.test.tsx +79 -0
- package/Tests/UI/Components/CopyTextButton.test.tsx +142 -0
- package/Tests/UI/Components/DetailCompactStyle.test.tsx +621 -0
- package/Tests/UI/Components/ErrorBoundary.test.tsx +60 -0
- package/Tests/UI/Components/FacetSections.test.tsx +581 -0
- package/Tests/UI/Components/FacetSidebarControls.test.tsx +812 -0
- package/Tests/UI/Components/FacetVisibility.test.ts +429 -0
- package/Tests/UI/Components/LockedFilterActions.test.tsx +396 -0
- package/Tests/UI/Components/LockedFilterChip.test.tsx +651 -0
- package/Tests/UI/Components/LogsEntityNames.test.ts +1769 -0
- package/Tests/UI/Components/LogsEntityNamesComponents.test.tsx +696 -0
- package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +600 -0
- package/Tests/UI/Components/LogsViewerEntityNames.test.tsx +713 -0
- package/Tests/UI/Components/MarkdownInlineReferences.test.tsx +2079 -0
- package/Tests/UI/Components/MemberRoleAssignment.test.tsx +446 -0
- package/Tests/UI/Components/ModelDetailRefetch.test.tsx +397 -0
- package/Tests/UI/Components/ModelListRefetch.test.tsx +233 -0
- package/Tests/UI/Components/ModelPageModelChange.test.tsx +409 -0
- package/Tests/UI/Components/PaginationIntegration.test.tsx +25 -0
- package/Tests/UI/Components/ResourceFacetConfigs.test.ts +172 -0
- package/Tests/UI/Components/SavedViewsSidebarIntegration.test.tsx +32 -14
- package/Tests/UI/Components/TableSortAndMobileColumns.test.tsx +44 -1
- package/Tests/UI/Components/TelemetryActiveFilterChipsLockedFilters.test.tsx +304 -0
- package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +718 -0
- package/Tests/UI/Monitor/EvaluationLogList.test.tsx +303 -0
- package/Tests/UI/Monitor/MonitorSummarySnapshotRender.test.tsx +55 -1
- package/Tests/UI/Rum/FidelityNotices.test.ts +22 -0
- package/Tests/UI/Rum/RecordingHealthCard.test.tsx +120 -136
- package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +988 -0
- package/Tests/UI/Rum/ReplayCard.test.tsx +32 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +709 -12
- package/Tests/UI/Rum/ReplayHeader.test.tsx +50 -1
- package/Tests/UI/Rum/ReplayPinControl.test.tsx +67 -10
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +41 -12
- package/Tests/UI/Rum/ReplayStage.test.tsx +663 -1
- package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +173 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +189 -2
- package/Tests/UI/Rum/ReplayUserSessionsMenu.test.tsx +91 -0
- package/Tests/UI/Rum/SessionReplayEmptyState.test.tsx +1 -1
- package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +585 -0
- package/Tests/UI/Rum/SessionReplayTable.test.tsx +844 -51
- package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +29 -1
- package/Tests/UI/Telemetry/TelemetryDetailPanel.test.tsx +288 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +31 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +6 -0
- package/Tests/UI/Utils/Clipboard.test.ts +154 -0
- package/Tests/UI/Utils/Navigation.test.ts +24 -0
- package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +2205 -0
- package/Tests/UI/Utils/Telemetry/UseTelemetryEntityNames.test.tsx +844 -0
- package/Tests/Utils/AI/InvestigationReport.test.ts +2430 -0
- package/Tests/Utils/APIOutgoingRequestTracer.test.ts +137 -0
- package/Tests/Utils/Rum/SessionReplayHealthDiagnosis.test.ts +40 -0
- package/Tests/Utils/Rum/SessionReplayRecordingEnded.test.ts +619 -0
- package/Tests/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.test.ts +631 -0
- package/Tests/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.test.ts +479 -0
- package/Tests/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.test.ts +507 -0
- package/Tests/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.test.ts +686 -0
- package/Tests/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.test.ts +474 -0
- package/Tests/Utils/SecurityEvent/Connectors/OktaNormalizer.test.ts +736 -0
- package/Tests/Utils/SecurityEvent/Connectors/SplunkNormalizer.test.ts +407 -0
- package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.test.ts +129 -0
- package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +57 -1
- package/Tests/Utils/Telemetry/EntityRelationship.test.ts +36 -0
- package/Tests/Utils/Telemetry/LockedFilterSearch.test.ts +600 -0
- package/Tests/Utils/Telemetry/NetworkHost.test.ts +48 -0
- package/Tests/Utils/Telemetry/OriginAllowList.test.ts +154 -0
- package/Tests/Utils/Traces/CriticalPath.test.ts +25 -0
- package/Types/AI/AIChatTypes.ts +8 -0
- package/Types/AI/InvestigationEvidence.ts +96 -0
- package/Types/Monitor/MonitorEvaluationSummary.ts +5 -0
- package/Types/Permission.ts +2 -2
- package/Types/Rum/SessionReplay.ts +109 -0
- package/Types/Rum/SessionReplayApi.ts +31 -0
- package/Types/SecurityEvent/Connectors/ConnectorDiagnostics.ts +111 -0
- package/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.ts +87 -0
- package/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.ts +492 -0
- package/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.ts +41 -0
- package/Types/SecurityEvent/GoogleSecOpsDiagnostics.ts +56 -1
- package/Types/SecurityEvent/GoogleSecOpsRegion.ts +34 -0
- package/Types/Telemetry/EntityType.ts +14 -0
- package/Types/Telemetry/ExceptionSpanScope.ts +83 -0
- package/Types/Telemetry/LockedFilterDetail.ts +46 -0
- package/Types/Telemetry/ResourceEntityFacet.ts +17 -10
- package/Types/Telemetry/ResourceFacetCatalog.ts +171 -0
- package/UI/Components/Card/Card.tsx +118 -83
- package/UI/Components/Checkbox/Checkbox.tsx +8 -5
- package/UI/Components/CopyTextButton/CopyTextButton.tsx +7 -1
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +4 -1
- package/UI/Components/Detail/Detail.tsx +137 -22
- package/UI/Components/ErrorBoundary.tsx +38 -0
- package/UI/Components/LogsViewer/LogsEntityNames.ts +912 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +156 -77
- package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +28 -11
- package/UI/Components/LogsViewer/components/FacetSection.tsx +66 -79
- package/UI/Components/LogsViewer/components/FacetValueRow.tsx +17 -2
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +45 -5
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +100 -19
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +156 -39
- package/UI/Components/LogsViewer/components/LogsTable.tsx +30 -8
- package/UI/Components/LogsViewer/types.ts +6 -0
- package/UI/Components/Markdown.tsx/InlineReferences.tsx +628 -0
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +385 -312
- package/UI/Components/MemberRoleAssignment/MemberRoleAssignment.tsx +50 -22
- package/UI/Components/ModelDetail/CardModelDetail.tsx +15 -0
- package/UI/Components/ModelDetail/ModelDetail.tsx +64 -19
- package/UI/Components/ModelList/ModelList.tsx +33 -8
- package/UI/Components/Page/ModelPage.tsx +119 -54
- package/UI/Components/Table/Table.tsx +8 -0
- package/UI/Components/Table/TableRow.tsx +13 -5
- package/UI/Components/TelemetryViewer/FacetVisibility.ts +229 -0
- package/UI/Components/TelemetryViewer/ResourceFacetConfigs.ts +46 -0
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +11 -0
- package/UI/Components/TelemetryViewer/components/FacetSearchInput.tsx +79 -0
- package/UI/Components/TelemetryViewer/components/FacetSectionHeader.tsx +69 -0
- package/UI/Components/TelemetryViewer/components/FacetShowMoreButton.tsx +36 -0
- package/UI/Components/TelemetryViewer/components/HiddenFacetsFooter.tsx +88 -0
- package/UI/Components/TelemetryViewer/components/LockedFilterActions.tsx +205 -0
- package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +496 -0
- package/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.tsx +30 -10
- package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +203 -10
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSection.tsx +60 -73
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +94 -7
- package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +17 -2
- package/UI/Components/TelemetryViewer/types.ts +19 -0
- package/UI/Components/TelemetryViewer/useFacetSearchExemptions.ts +158 -0
- package/UI/Components/TelemetryViewer/useFacetSectionSearch.ts +102 -0
- package/UI/Utils/Clipboard.ts +68 -2
- package/UI/Utils/Navigation.ts +17 -2
- package/UI/Utils/Telemetry/TelemetryEntityNames.ts +620 -0
- package/UI/Utils/Telemetry/UseTelemetryEntityNames.ts +151 -0
- package/Utils/AI/InvestigationReport.ts +1789 -0
- package/Utils/API.ts +90 -1
- package/Utils/Rum/SessionReplayHealth.ts +11 -0
- package/Utils/Rum/SessionReplayRecordingEnded.ts +134 -0
- package/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.ts +427 -0
- package/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.ts +197 -0
- package/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.ts +464 -0
- package/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.ts +567 -0
- package/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.ts +299 -0
- package/Utils/SecurityEvent/Connectors/OktaNormalizer.ts +467 -0
- package/Utils/SecurityEvent/Connectors/SplunkNormalizer.ts +439 -0
- package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +124 -12
- package/Utils/Telemetry/CrossSignalScope.ts +22 -8
- package/Utils/Telemetry/EntityRelationship.ts +8 -0
- package/Utils/Telemetry/LockedFilterSearch.ts +412 -0
- package/Utils/Telemetry/NetworkHost.ts +43 -0
- package/Utils/Telemetry/OriginAllowList.ts +139 -4
- package/Utils/Traces/CriticalPath.ts +10 -4
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/SecurityEventConnection.js +490 -0
- package/build/dist/Models/DatabaseModels/SecurityEventConnection.js.map +1 -0
- package/build/dist/Models/DatabaseModels/SecurityEventConnectionRun.js +287 -0
- package/build/dist/Models/DatabaseModels/SecurityEventConnectionRun.js.map +1 -0
- package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js +2 -2
- package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +336 -27
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js +182 -7
- package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js.map +1 -1
- package/build/dist/Server/API/SecurityEventConnectionAPI.js +171 -0
- package/build/dist/Server/API/SecurityEventConnectionAPI.js.map +1 -0
- package/build/dist/Server/API/TelemetryAPI.js +248 -170
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792700000000-AddSecurityEventConnections.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792700000000-AddSecurityEventConnections.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/TelemetryIngest.js +57 -4
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/ExceptionAggregationService.js +7 -16
- package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
- package/build/dist/Server/Services/GoogleSecOpsConnectionService.js +8 -0
- package/build/dist/Server/Services/GoogleSecOpsConnectionService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +22 -0
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +8 -17
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +4 -14
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/SecurityEventConnectionRunService.js +9 -0
- package/build/dist/Server/Services/SecurityEventConnectionRunService.js.map +1 -0
- package/build/dist/Server/Services/SecurityEventConnectionService.js +356 -0
- package/build/dist/Server/Services/SecurityEventConnectionService.js.map +1 -0
- package/build/dist/Server/Services/TraceAggregationService.js +51 -25
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +14 -7
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationEvidence.js +468 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationEvidence.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationReferences.js +230 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationReferences.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +32 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js +6 -2
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +59 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +5 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.js +752 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.js +493 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.js +352 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.js +405 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.js +466 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.js +344 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.js +511 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.js +406 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.js +336 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.js +474 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.js +417 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.js +314 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.js +494 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.js +719 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.js +404 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.js +209 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.js +40 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.js +494 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.js +379 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Types.js +27 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Types.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +234 -61
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.js +537 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +572 -115
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.js +35 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/SecurityEventDedupe.js +52 -0
- package/build/dist/Server/Utils/SecurityEvent/SecurityEventDedupe.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +521 -32
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +10 -0
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +8 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/KubernetesResourceAttributes.js +80 -0
- package/build/dist/Server/Utils/Telemetry/KubernetesResourceAttributes.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +122 -67
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceFacetPlanner.js +146 -0
- package/build/dist/Server/Utils/Telemetry/ResourceFacetPlanner.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +174 -248
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +492 -0
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TraceContextPropagation.js +259 -0
- package/build/dist/Server/Utils/Telemetry/TraceContextPropagation.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry.js +11 -5
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Types/AI/InvestigationEvidence.js +2 -0
- package/build/dist/Types/AI/InvestigationEvidence.js.map +1 -0
- package/build/dist/Types/Permission.js +2 -2
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +95 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -1
- package/build/dist/Types/SecurityEvent/Connectors/ConnectorDiagnostics.js +18 -0
- package/build/dist/Types/SecurityEvent/Connectors/ConnectorDiagnostics.js.map +1 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.js +2 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.js.map +1 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js +374 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js.map +1 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.js +35 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.js.map +1 -0
- package/build/dist/Types/SecurityEvent/GoogleSecOpsRegion.js +35 -0
- package/build/dist/Types/SecurityEvent/GoogleSecOpsRegion.js.map +1 -0
- package/build/dist/Types/Telemetry/EntityType.js +14 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/ExceptionSpanScope.js +59 -0
- package/build/dist/Types/Telemetry/ExceptionSpanScope.js.map +1 -0
- package/build/dist/Types/Telemetry/LockedFilterDetail.js +16 -0
- package/build/dist/Types/Telemetry/LockedFilterDetail.js.map +1 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js +16 -10
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -1
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +119 -0
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -0
- package/build/dist/UI/Components/Card/Card.js +34 -17
- package/build/dist/UI/Components/Card/Card.js.map +1 -1
- package/build/dist/UI/Components/Checkbox/Checkbox.js +6 -4
- package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +5 -1
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Detail/Detail.js +63 -13
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/ErrorBoundary.js +34 -0
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsEntityNames.js +535 -0
- package/build/dist/UI/Components/LogsViewer/LogsEntityNames.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +117 -57
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +5 -7
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetSection.js +31 -44
- package/build/dist/UI/Components/LogsViewer/components/FacetSection.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +17 -3
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +19 -3
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +64 -18
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +100 -41
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +13 -5
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/InlineReferences.js +403 -0
- package/build/dist/UI/Components/Markdown.tsx/InlineReferences.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +197 -179
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js +30 -21
- package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +12 -0
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js +46 -12
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelList/ModelList.js +26 -7
- package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
- package/build/dist/UI/Components/Page/ModelPage.js +62 -34
- package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +3 -1
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +8 -1
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js +111 -0
- package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/ResourceFacetConfigs.js +22 -0
- package/build/dist/UI/Components/TelemetryViewer/ResourceFacetConfigs.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/FacetSearchInput.js +36 -0
- package/build/dist/UI/Components/TelemetryViewer/components/FacetSearchInput.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/FacetSectionHeader.js +21 -0
- package/build/dist/UI/Components/TelemetryViewer/components/FacetSectionHeader.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/FacetShowMoreButton.js +14 -0
- package/build/dist/UI/Components/TelemetryViewer/components/FacetShowMoreButton.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/HiddenFacetsFooter.js +27 -0
- package/build/dist/UI/Components/TelemetryViewer/components/HiddenFacetsFooter.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterActions.js +88 -0
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterActions.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +228 -0
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js +6 -6
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +111 -12
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js +27 -40
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +70 -10
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +17 -3
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/types.js +0 -4
- package/build/dist/UI/Components/TelemetryViewer/types.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/useFacetSearchExemptions.js +70 -0
- package/build/dist/UI/Components/TelemetryViewer/useFacetSearchExemptions.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/useFacetSectionSearch.js +52 -0
- package/build/dist/UI/Components/TelemetryViewer/useFacetSectionSearch.js.map +1 -0
- package/build/dist/UI/Utils/Clipboard.js +59 -2
- package/build/dist/UI/Utils/Clipboard.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +11 -2
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +471 -0
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -0
- package/build/dist/UI/Utils/Telemetry/UseTelemetryEntityNames.js +113 -0
- package/build/dist/UI/Utils/Telemetry/UseTelemetryEntityNames.js.map +1 -0
- package/build/dist/Utils/AI/InvestigationReport.js +1241 -0
- package/build/dist/Utils/AI/InvestigationReport.js.map +1 -0
- package/build/dist/Utils/API.js +25 -0
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Rum/SessionReplayHealth.js +10 -0
- package/build/dist/Utils/Rum/SessionReplayHealth.js.map +1 -1
- package/build/dist/Utils/Rum/SessionReplayRecordingEnded.js +54 -0
- package/build/dist/Utils/Rum/SessionReplayRecordingEnded.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.js +311 -0
- package/build/dist/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.js +150 -0
- package/build/dist/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.js +328 -0
- package/build/dist/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.js +405 -0
- package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.js +212 -0
- package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/OktaNormalizer.js +351 -0
- package/build/dist/Utils/SecurityEvent/Connectors/OktaNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/SplunkNormalizer.js +348 -0
- package/build/dist/Utils/SecurityEvent/Connectors/SplunkNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +103 -10
- package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +16 -5
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/build/dist/Utils/Telemetry/EntityRelationship.js +7 -0
- package/build/dist/Utils/Telemetry/EntityRelationship.js.map +1 -1
- package/build/dist/Utils/Telemetry/LockedFilterSearch.js +277 -0
- package/build/dist/Utils/Telemetry/LockedFilterSearch.js.map +1 -0
- package/build/dist/Utils/Telemetry/NetworkHost.js +39 -0
- package/build/dist/Utils/Telemetry/NetworkHost.js.map +1 -0
- package/build/dist/Utils/Telemetry/OriginAllowList.js +105 -4
- package/build/dist/Utils/Telemetry/OriginAllowList.js.map +1 -1
- package/build/dist/Utils/Traces/CriticalPath.js +9 -4
- package/build/dist/Utils/Traces/CriticalPath.js.map +1 -1
- package/package.json +1 -1
- package/Tests/UI/Rum/RecordingHealthStrip.test.tsx +0 -600
- package/UI/Components/GanttChart/Bar/BarLabel.tsx +0 -17
- package/UI/Components/GanttChart/Bar/Index.tsx +0 -149
- package/UI/Components/GanttChart/ChartContainer.tsx +0 -50
- package/UI/Components/GanttChart/Index.tsx +0 -90
- package/UI/Components/GanttChart/Row/Index.tsx +0 -22
- package/UI/Components/GanttChart/Row/Row.tsx +0 -231
- package/UI/Components/GanttChart/Row/RowLabel.tsx +0 -30
- package/UI/Components/GanttChart/Rows.tsx +0 -59
- package/UI/Components/GanttChart/Timeline/Index.tsx +0 -46
- package/UI/Components/GanttChart/Timeline/TimelineInterval.tsx +0 -33
- package/UI/Components/GanttChart/Timeline/TimelineIntervalMarks.tsx +0 -43
- package/build/dist/UI/Components/GanttChart/Bar/BarLabel.js +0 -8
- package/build/dist/UI/Components/GanttChart/Bar/BarLabel.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Bar/Index.js +0 -77
- package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/ChartContainer.js +0 -33
- package/build/dist/UI/Components/GanttChart/ChartContainer.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Index.js +0 -39
- package/build/dist/UI/Components/GanttChart/Index.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Row/Index.js +0 -7
- package/build/dist/UI/Components/GanttChart/Row/Index.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Row/Row.js +0 -106
- package/build/dist/UI/Components/GanttChart/Row/Row.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Row/RowLabel.js +0 -10
- package/build/dist/UI/Components/GanttChart/Row/RowLabel.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Rows.js +0 -19
- package/build/dist/UI/Components/GanttChart/Rows.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Timeline/Index.js +0 -16
- package/build/dist/UI/Components/GanttChart/Timeline/Index.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Timeline/TimelineInterval.js +0 -13
- package/build/dist/UI/Components/GanttChart/Timeline/TimelineInterval.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Timeline/TimelineIntervalMarks.js +0 -13
- package/build/dist/UI/Components/GanttChart/Timeline/TimelineIntervalMarks.js.map +0 -1
|
@@ -0,0 +1,1558 @@
|
|
|
1
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
import OktaConnector from "../../../../../../Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector";
|
|
3
|
+
import {
|
|
4
|
+
OKTA_DEFAULT_EVENT_FILTER,
|
|
5
|
+
OKTA_MAX_PAGE_SIZE,
|
|
6
|
+
} from "../../../../../../Server/Utils/SecurityEvent/Connectors/Okta/OktaClient";
|
|
7
|
+
import {
|
|
8
|
+
ConnectorFetchResult,
|
|
9
|
+
SecurityConnectorSettings,
|
|
10
|
+
} from "../../../../../../Server/Utils/SecurityEvent/Connectors/Types";
|
|
11
|
+
import {
|
|
12
|
+
DataSourceHttpRequest,
|
|
13
|
+
DataSourceHttpResponse,
|
|
14
|
+
} from "../../../../../../Server/Utils/DataSource/HttpFetch";
|
|
15
|
+
import OktaNormalizer from "../../../../../../Utils/SecurityEvent/Connectors/OktaNormalizer";
|
|
16
|
+
import APIException from "../../../../../../Types/Exception/ApiException";
|
|
17
|
+
import BadDataException from "../../../../../../Types/Exception/BadDataException";
|
|
18
|
+
import Dictionary from "../../../../../../Types/Dictionary";
|
|
19
|
+
import { JSONObject } from "../../../../../../Types/JSON";
|
|
20
|
+
import { SecurityConnectorCheck } from "../../../../../../Types/SecurityEvent/Connectors/ConnectorDiagnostics";
|
|
21
|
+
import SecurityEventConnectorProvider from "../../../../../../Types/SecurityEvent/Connectors/SecurityEventConnectorProvider";
|
|
22
|
+
import {
|
|
23
|
+
SecurityEventConnectorDefinition,
|
|
24
|
+
getSecurityEventConnectorDefinition,
|
|
25
|
+
} from "../../../../../../Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog";
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* The Okta connector as the framework calls it: settings validation keyed
|
|
29
|
+
* by the catalog's field titles, the three test checks and their
|
|
30
|
+
* remediation per failure mode, and a published-time fetch that follows
|
|
31
|
+
* the Link header page by page, respects the event and request bounds,
|
|
32
|
+
* marks itself incomplete when a bound stops it, and never leaks the
|
|
33
|
+
* SSWS token.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const ORG_URL: string = "https://acme.okta.com";
|
|
37
|
+
const API_TOKEN: string = "00Zx9QkLm3VpR7tWyB2nHc5JdF8gS4aE6uK1oP0iNv";
|
|
38
|
+
const START: Date = new Date("2026-09-12T10:00:00.000Z");
|
|
39
|
+
const END: Date = new Date("2026-09-13T10:00:00.000Z");
|
|
40
|
+
const CUSTOM_FILTER: string = 'eventType sw "user.session"';
|
|
41
|
+
|
|
42
|
+
type Responder = (
|
|
43
|
+
request: DataSourceHttpRequest,
|
|
44
|
+
) => DataSourceHttpResponse | Promise<DataSourceHttpResponse>;
|
|
45
|
+
|
|
46
|
+
interface Harness {
|
|
47
|
+
requests: Array<DataSourceHttpRequest>;
|
|
48
|
+
connector: OktaConnector;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function settings(
|
|
52
|
+
overrides: Partial<SecurityConnectorSettings> = {},
|
|
53
|
+
): SecurityConnectorSettings {
|
|
54
|
+
return {
|
|
55
|
+
provider: SecurityEventConnectorProvider.OktaSystemLog,
|
|
56
|
+
config: { orgUrl: ORG_URL, filter: "" },
|
|
57
|
+
secrets: { apiToken: API_TOKEN },
|
|
58
|
+
alertingOnly: false,
|
|
59
|
+
...overrides,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function text(
|
|
64
|
+
code: number,
|
|
65
|
+
body: string,
|
|
66
|
+
headers: Dictionary<string> = {},
|
|
67
|
+
): DataSourceHttpResponse {
|
|
68
|
+
let bodyJson: unknown = undefined;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
bodyJson = JSON.parse(body);
|
|
72
|
+
} catch {
|
|
73
|
+
bodyJson = undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { statusCode: code, bodyText: body, bodyJson, headers };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function oktaError(
|
|
80
|
+
code: number,
|
|
81
|
+
errorCode: string,
|
|
82
|
+
errorSummary: string,
|
|
83
|
+
): DataSourceHttpResponse {
|
|
84
|
+
return text(
|
|
85
|
+
code,
|
|
86
|
+
JSON.stringify({
|
|
87
|
+
errorCode,
|
|
88
|
+
errorSummary,
|
|
89
|
+
errorLink: errorCode,
|
|
90
|
+
errorId: "oaeQ9pJ1bK2SxW3yLm4nO5pQ6",
|
|
91
|
+
errorCauses: [],
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function logEvent(
|
|
97
|
+
uuid: string,
|
|
98
|
+
index: number,
|
|
99
|
+
overrides: JSONObject = {},
|
|
100
|
+
): JSONObject {
|
|
101
|
+
return {
|
|
102
|
+
uuid,
|
|
103
|
+
published: `2026-09-12T1${index}:15:00.123Z`,
|
|
104
|
+
eventType: "user.session.start",
|
|
105
|
+
version: "0",
|
|
106
|
+
severity: "INFO",
|
|
107
|
+
legacyEventType: "core.user_auth.login_success",
|
|
108
|
+
displayMessage: "User login to Okta",
|
|
109
|
+
actor: {
|
|
110
|
+
id: "00u1abcd2EFGHijkl3m4",
|
|
111
|
+
type: "User",
|
|
112
|
+
alternateId: `user${index}@example.com`,
|
|
113
|
+
displayName: `User ${index}`,
|
|
114
|
+
detailEntry: null,
|
|
115
|
+
},
|
|
116
|
+
client: {
|
|
117
|
+
userAgent: {
|
|
118
|
+
rawUserAgent: "Mozilla/5.0",
|
|
119
|
+
os: "Mac OS X",
|
|
120
|
+
browser: "CHROME",
|
|
121
|
+
},
|
|
122
|
+
zone: "null",
|
|
123
|
+
device: "Computer",
|
|
124
|
+
id: null,
|
|
125
|
+
ipAddress: `203.0.113.${index}`,
|
|
126
|
+
geographicalContext: null,
|
|
127
|
+
},
|
|
128
|
+
device: null,
|
|
129
|
+
outcome: { result: "SUCCESS", reason: null },
|
|
130
|
+
target: null,
|
|
131
|
+
transaction: { type: "WEB", id: `tx-${index}`, detail: {} },
|
|
132
|
+
debugContext: { debugData: { requestUri: "/idp/idx/identify" } },
|
|
133
|
+
authenticationContext: {
|
|
134
|
+
credentialType: "PASSWORD",
|
|
135
|
+
authenticationStep: 0,
|
|
136
|
+
},
|
|
137
|
+
securityContext: { asNumber: 64496, isProxy: false },
|
|
138
|
+
request: { ipChain: [{ ip: `203.0.113.${index}`, version: "V4" }] },
|
|
139
|
+
...overrides,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function nextLink(after: string): string {
|
|
144
|
+
return `${ORG_URL}/api/v1/logs?since=${encodeURIComponent(
|
|
145
|
+
START.toISOString(),
|
|
146
|
+
)}&until=${encodeURIComponent(
|
|
147
|
+
END.toISOString(),
|
|
148
|
+
)}&sortOrder=ASCENDING&limit=1000&after=${after}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function page(
|
|
152
|
+
events: Array<JSONObject>,
|
|
153
|
+
nextUrl: string | null = null,
|
|
154
|
+
): DataSourceHttpResponse {
|
|
155
|
+
const self: string = `<${ORG_URL}/api/v1/logs?limit=1000>; rel="self"`;
|
|
156
|
+
|
|
157
|
+
return text(200, JSON.stringify(events), {
|
|
158
|
+
link: nextUrl ? `${self}, <${nextUrl}>; rel="next"` : self,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isProbe(request: DataSourceHttpRequest): boolean {
|
|
163
|
+
const url: URL = new URL(request.url);
|
|
164
|
+
return !url.searchParams.has("since") && !url.searchParams.has("after");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/*
|
|
168
|
+
* The probe (limit=1, no bounds) has its own responder; every bounded
|
|
169
|
+
* request — the one-event read, the two counts, the fetch pages — is
|
|
170
|
+
* served from the queue in order.
|
|
171
|
+
*/
|
|
172
|
+
function buildHarness(options: {
|
|
173
|
+
probe?: Responder;
|
|
174
|
+
responders?: Array<Responder>;
|
|
175
|
+
}): Harness {
|
|
176
|
+
const requests: Array<DataSourceHttpRequest> = [];
|
|
177
|
+
const responders: Array<Responder> = [...(options.responders || [])];
|
|
178
|
+
|
|
179
|
+
const connector: OktaConnector = new OktaConnector(
|
|
180
|
+
async (request: DataSourceHttpRequest): Promise<DataSourceHttpResponse> => {
|
|
181
|
+
requests.push(request);
|
|
182
|
+
|
|
183
|
+
if (isProbe(request)) {
|
|
184
|
+
return (
|
|
185
|
+
options.probe ||
|
|
186
|
+
((): DataSourceHttpResponse => {
|
|
187
|
+
return page([logEvent("probe-1", 0)]);
|
|
188
|
+
})
|
|
189
|
+
)(request);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const responder: Responder | undefined = responders.shift();
|
|
193
|
+
|
|
194
|
+
if (!responder) {
|
|
195
|
+
throw new Error(`Unexpected request to ${request.url}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return responder(request);
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
return { requests, connector };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function checkByKey(
|
|
206
|
+
checks: Array<SecurityConnectorCheck>,
|
|
207
|
+
key: string,
|
|
208
|
+
): SecurityConnectorCheck {
|
|
209
|
+
const check: SecurityConnectorCheck | undefined = checks.find(
|
|
210
|
+
(candidate: SecurityConnectorCheck): boolean => {
|
|
211
|
+
return candidate.key === key;
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (!check) {
|
|
216
|
+
throw new Error(`Missing check ${key}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return check;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function statuses(checks: Array<SecurityConnectorCheck>): Array<string> {
|
|
223
|
+
return checks.map((check: SecurityConnectorCheck): string => {
|
|
224
|
+
return `${check.key}:${check.status}`;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function sortedParamKeys(url: string): Array<string> {
|
|
229
|
+
return Array.from(new URL(url).searchParams.keys()).sort();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function param(request: DataSourceHttpRequest, key: string): string | null {
|
|
233
|
+
return new URL(request.url).searchParams.get(key);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function fetchOptions(
|
|
237
|
+
overrides: Partial<{
|
|
238
|
+
maxRequests: number;
|
|
239
|
+
maxEvents: number;
|
|
240
|
+
requestTimeoutInMs: number;
|
|
241
|
+
sampleLimit: number;
|
|
242
|
+
}> = {},
|
|
243
|
+
): {
|
|
244
|
+
maxRequests: number;
|
|
245
|
+
maxEvents: number;
|
|
246
|
+
requestTimeoutInMs: number;
|
|
247
|
+
sampleLimit: number;
|
|
248
|
+
} {
|
|
249
|
+
return {
|
|
250
|
+
maxRequests: 20,
|
|
251
|
+
maxEvents: 100,
|
|
252
|
+
requestTimeoutInMs: 30000,
|
|
253
|
+
sampleLimit: 5,
|
|
254
|
+
...overrides,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
describe("OktaConnector", () => {
|
|
259
|
+
const definition: SecurityEventConnectorDefinition =
|
|
260
|
+
getSecurityEventConnectorDefinition(
|
|
261
|
+
SecurityEventConnectorProvider.OktaSystemLog,
|
|
262
|
+
)!;
|
|
263
|
+
|
|
264
|
+
afterEach(() => {
|
|
265
|
+
jest.restoreAllMocks();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("registers under the catalog's provider key", () => {
|
|
269
|
+
expect(new OktaConnector().provider).toBe("okta");
|
|
270
|
+
expect(definition.provider).toBe(new OktaConnector().provider);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe("validateSettings", () => {
|
|
274
|
+
test("accepts an org URL with a trailing slash, preview and EMEA cells, and an empty filter", () => {
|
|
275
|
+
const connector: OktaConnector = new OktaConnector();
|
|
276
|
+
|
|
277
|
+
for (const orgUrl of [
|
|
278
|
+
ORG_URL,
|
|
279
|
+
"https://acme.okta.com/",
|
|
280
|
+
"https://acme.oktapreview.com",
|
|
281
|
+
"https://acme.okta-emea.com",
|
|
282
|
+
"https://login.example.com",
|
|
283
|
+
]) {
|
|
284
|
+
expect(() => {
|
|
285
|
+
return connector.validateSettings(settings({ config: { orgUrl } }));
|
|
286
|
+
}).not.toThrow();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
expect(() => {
|
|
290
|
+
return connector.validateSettings(
|
|
291
|
+
settings({ config: { orgUrl: ORG_URL, filter: CUSTOM_FILTER } }),
|
|
292
|
+
);
|
|
293
|
+
}).not.toThrow();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("names the catalog field titles in every rejection", () => {
|
|
297
|
+
const connector: OktaConnector = new OktaConnector();
|
|
298
|
+
|
|
299
|
+
expect(() => {
|
|
300
|
+
return connector.validateSettings(settings({ config: {} }));
|
|
301
|
+
}).toThrow("Okta organization URL is required.");
|
|
302
|
+
|
|
303
|
+
expect(() => {
|
|
304
|
+
return connector.validateSettings(
|
|
305
|
+
settings({ config: { orgUrl: "acme okta" } }),
|
|
306
|
+
);
|
|
307
|
+
}).toThrow(/^Okta organization URL must be an absolute URL/);
|
|
308
|
+
|
|
309
|
+
expect(() => {
|
|
310
|
+
return connector.validateSettings(
|
|
311
|
+
settings({ config: { orgUrl: "http://acme.okta.com" } }),
|
|
312
|
+
);
|
|
313
|
+
}).toThrow("Okta organization URL must use https.");
|
|
314
|
+
|
|
315
|
+
expect(() => {
|
|
316
|
+
return connector.validateSettings(
|
|
317
|
+
settings({ config: { orgUrl: "https://acme.okta.com/?x=1" } }),
|
|
318
|
+
);
|
|
319
|
+
}).toThrow(/^Okta organization URL must not contain a query string/);
|
|
320
|
+
|
|
321
|
+
expect(() => {
|
|
322
|
+
return connector.validateSettings(
|
|
323
|
+
settings({ config: { orgUrl: "https://acme.okta.com/api/v1/logs" } }),
|
|
324
|
+
);
|
|
325
|
+
}).toThrow(
|
|
326
|
+
/^Okta organization URL must be the org's base URL without a path/,
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
expect(() => {
|
|
330
|
+
return connector.validateSettings(
|
|
331
|
+
settings({ config: { orgUrl: "https://acme-admin.okta.com" } }),
|
|
332
|
+
);
|
|
333
|
+
}).toThrow(/not the Admin Console's -admin URL/);
|
|
334
|
+
|
|
335
|
+
expect(() => {
|
|
336
|
+
return connector.validateSettings(
|
|
337
|
+
settings({
|
|
338
|
+
config: { orgUrl: ORG_URL, filter: "x".repeat(2001) },
|
|
339
|
+
}),
|
|
340
|
+
);
|
|
341
|
+
}).toThrow("Event filter must be at most 2000 characters.");
|
|
342
|
+
|
|
343
|
+
expect(() => {
|
|
344
|
+
return connector.validateSettings(
|
|
345
|
+
settings({
|
|
346
|
+
config: {
|
|
347
|
+
orgUrl: ORG_URL,
|
|
348
|
+
filter: 'eventType eq "a"\nor eventType eq "b"',
|
|
349
|
+
},
|
|
350
|
+
}),
|
|
351
|
+
);
|
|
352
|
+
}).toThrow("Event filter must be a single-line filter expression.");
|
|
353
|
+
|
|
354
|
+
expect(() => {
|
|
355
|
+
return connector.validateSettings(
|
|
356
|
+
settings({
|
|
357
|
+
config: {
|
|
358
|
+
orgUrl: ORG_URL,
|
|
359
|
+
filter: 'published gt "2026-01-01T00:00:00.000Z"',
|
|
360
|
+
},
|
|
361
|
+
}),
|
|
362
|
+
);
|
|
363
|
+
}).toThrow(/^Event filter must not filter on the published attribute/);
|
|
364
|
+
|
|
365
|
+
expect(() => {
|
|
366
|
+
return connector.validateSettings(settings({ secrets: {} }));
|
|
367
|
+
}).toThrow("API token is required.");
|
|
368
|
+
|
|
369
|
+
expect(() => {
|
|
370
|
+
return connector.validateSettings(
|
|
371
|
+
settings({ secrets: { apiToken: `SSWS ${API_TOKEN}` } }),
|
|
372
|
+
);
|
|
373
|
+
}).toThrow("API token must be pasted without the 'SSWS' prefix.");
|
|
374
|
+
|
|
375
|
+
expect(() => {
|
|
376
|
+
return connector.validateSettings(
|
|
377
|
+
settings({ secrets: { apiToken: "00abc def" } }),
|
|
378
|
+
);
|
|
379
|
+
}).toThrow("API token must be the token value alone, without spaces.");
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("throws BadDataException and never quotes the token", () => {
|
|
383
|
+
const connector: OktaConnector = new OktaConnector();
|
|
384
|
+
|
|
385
|
+
for (const bad of [
|
|
386
|
+
settings({ config: {} }),
|
|
387
|
+
settings({ secrets: { apiToken: `SSWS ${API_TOKEN}` } }),
|
|
388
|
+
settings({ secrets: { apiToken: `${API_TOKEN} extra` } }),
|
|
389
|
+
]) {
|
|
390
|
+
let thrown: unknown = undefined;
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
connector.validateSettings(bad);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
thrown = error;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
expect(thrown).toBeInstanceOf(BadDataException);
|
|
399
|
+
expect((thrown as Error).message).not.toContain(API_TOKEN);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("accepts a filter that mentions published as part of another attribute", () => {
|
|
404
|
+
expect(() => {
|
|
405
|
+
return new OktaConnector().validateSettings(
|
|
406
|
+
settings({
|
|
407
|
+
config: { orgUrl: ORG_URL, filter: 'target.published_by eq "x"' },
|
|
408
|
+
}),
|
|
409
|
+
);
|
|
410
|
+
}).not.toThrow();
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe("effectiveFilter", () => {
|
|
415
|
+
test("uses the default event families when the tenant set no filter", () => {
|
|
416
|
+
expect(OktaConnector.effectiveFilter("")).toBe(OKTA_DEFAULT_EVENT_FILTER);
|
|
417
|
+
expect(OktaConnector.effectiveFilter(" ")).toBe(
|
|
418
|
+
OKTA_DEFAULT_EVENT_FILTER,
|
|
419
|
+
);
|
|
420
|
+
expect(OktaConnector.effectiveFilter(` ${CUSTOM_FILTER} `)).toBe(
|
|
421
|
+
CUSTOM_FILTER,
|
|
422
|
+
);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe("testConnection", () => {
|
|
427
|
+
test("passes all three checks and reports the event type, the filter in use and bounded counts", async () => {
|
|
428
|
+
const harness: Harness = buildHarness({
|
|
429
|
+
responders: [
|
|
430
|
+
(): DataSourceHttpResponse => {
|
|
431
|
+
return page([logEvent("A1", 0)], nextLink("cursor-1"));
|
|
432
|
+
},
|
|
433
|
+
(): DataSourceHttpResponse => {
|
|
434
|
+
return page([
|
|
435
|
+
logEvent("A1", 0),
|
|
436
|
+
logEvent("B2", 1),
|
|
437
|
+
logEvent("C3", 2),
|
|
438
|
+
]);
|
|
439
|
+
},
|
|
440
|
+
(): DataSourceHttpResponse => {
|
|
441
|
+
return page([logEvent("A1", 0), logEvent("B2", 1)], nextLink("c"));
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const before: number = Date.now();
|
|
447
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
448
|
+
await harness.connector.testConnection(settings(), {
|
|
449
|
+
requestTimeoutInMs: 15000,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
expect(statuses(checks)).toEqual([
|
|
453
|
+
"authentication:pass",
|
|
454
|
+
"read-permission:pass",
|
|
455
|
+
"detections-available:pass",
|
|
456
|
+
]);
|
|
457
|
+
|
|
458
|
+
const authentication: SecurityConnectorCheck = checkByKey(
|
|
459
|
+
checks,
|
|
460
|
+
"authentication",
|
|
461
|
+
);
|
|
462
|
+
expect(authentication.name).toBe("Authenticate with Okta");
|
|
463
|
+
expect(authentication.message).toBe(
|
|
464
|
+
"Okta at acme.okta.com accepted the API token for the System Log.",
|
|
465
|
+
);
|
|
466
|
+
expect(authentication.details).toEqual({ probeReturnedEvents: 1 });
|
|
467
|
+
|
|
468
|
+
const read: SecurityConnectorCheck = checkByKey(
|
|
469
|
+
checks,
|
|
470
|
+
"read-permission",
|
|
471
|
+
);
|
|
472
|
+
expect(read.name).toBe("Read System Log events");
|
|
473
|
+
expect(read.message).toContain("through the default event filter");
|
|
474
|
+
expect(read.message).toContain('returned a "user.session.start" event');
|
|
475
|
+
expect(read.details).toEqual({ usingDefaultFilter: true });
|
|
476
|
+
|
|
477
|
+
const available: SecurityConnectorCheck = checkByKey(
|
|
478
|
+
checks,
|
|
479
|
+
"detections-available",
|
|
480
|
+
);
|
|
481
|
+
expect(available.name).toBe("Events available to import");
|
|
482
|
+
expect(available.message).toBe(
|
|
483
|
+
"3 events published in the last 24 hours and 2+ in the last 7 days.",
|
|
484
|
+
);
|
|
485
|
+
expect(available.remediation).toBeUndefined();
|
|
486
|
+
expect(available.details).toEqual({
|
|
487
|
+
createdLast24h: 3,
|
|
488
|
+
createdLast7d: 2,
|
|
489
|
+
hasMoreLast24h: false,
|
|
490
|
+
hasMoreLast7d: true,
|
|
491
|
+
usingDefaultFilter: true,
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// One probe, a one-event read, then two one-page counts.
|
|
495
|
+
expect(harness.requests).toHaveLength(4);
|
|
496
|
+
|
|
497
|
+
const probe: DataSourceHttpRequest = harness.requests[0]!;
|
|
498
|
+
expect(new URL(probe.url).pathname).toBe("/api/v1/logs");
|
|
499
|
+
expect(sortedParamKeys(probe.url)).toEqual(["limit"]);
|
|
500
|
+
expect(param(probe, "limit")).toBe("1");
|
|
501
|
+
|
|
502
|
+
const readRequest: DataSourceHttpRequest = harness.requests[1]!;
|
|
503
|
+
expect(sortedParamKeys(readRequest.url)).toEqual([
|
|
504
|
+
"filter",
|
|
505
|
+
"limit",
|
|
506
|
+
"since",
|
|
507
|
+
"sortOrder",
|
|
508
|
+
"until",
|
|
509
|
+
]);
|
|
510
|
+
expect(param(readRequest, "limit")).toBe("1");
|
|
511
|
+
expect(param(readRequest, "sortOrder")).toBe("ASCENDING");
|
|
512
|
+
expect(param(readRequest, "filter")).toBe(OKTA_DEFAULT_EVENT_FILTER);
|
|
513
|
+
|
|
514
|
+
const readSince: number = new Date(
|
|
515
|
+
param(readRequest, "since")!,
|
|
516
|
+
).getTime();
|
|
517
|
+
const readUntil: number = new Date(
|
|
518
|
+
param(readRequest, "until")!,
|
|
519
|
+
).getTime();
|
|
520
|
+
expect(readUntil - readSince).toBe(24 * 60 * 60 * 1000);
|
|
521
|
+
expect(readUntil).toBeGreaterThanOrEqual(before);
|
|
522
|
+
expect(readUntil).toBeLessThanOrEqual(Date.now());
|
|
523
|
+
|
|
524
|
+
const count24h: DataSourceHttpRequest = harness.requests[2]!;
|
|
525
|
+
const count7d: DataSourceHttpRequest = harness.requests[3]!;
|
|
526
|
+
expect(param(count24h, "limit")).toBe(String(OKTA_MAX_PAGE_SIZE));
|
|
527
|
+
expect(param(count7d, "limit")).toBe(String(OKTA_MAX_PAGE_SIZE));
|
|
528
|
+
expect(
|
|
529
|
+
new Date(param(count7d, "until")!).getTime() -
|
|
530
|
+
new Date(param(count7d, "since")!).getTime(),
|
|
531
|
+
).toBe(7 * 24 * 60 * 60 * 1000);
|
|
532
|
+
expect(param(count7d, "filter")).toBe(OKTA_DEFAULT_EVENT_FILTER);
|
|
533
|
+
|
|
534
|
+
for (const request of harness.requests) {
|
|
535
|
+
expect(request.method).toBe("GET");
|
|
536
|
+
expect(request.headers?.["Authorization"]).toBe(`SSWS ${API_TOKEN}`);
|
|
537
|
+
expect(request.timeoutInMs).toBe(15000);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
for (const check of checks) {
|
|
541
|
+
expect(JSON.stringify(check)).not.toContain(API_TOKEN);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test("uses the tenant's Event filter for the read and the counts and says so", async () => {
|
|
546
|
+
const harness: Harness = buildHarness({
|
|
547
|
+
responders: [
|
|
548
|
+
(): DataSourceHttpResponse => {
|
|
549
|
+
return page([]);
|
|
550
|
+
},
|
|
551
|
+
(): DataSourceHttpResponse => {
|
|
552
|
+
return page([logEvent("A1", 0)]);
|
|
553
|
+
},
|
|
554
|
+
(): DataSourceHttpResponse => {
|
|
555
|
+
return page([logEvent("A1", 0)]);
|
|
556
|
+
},
|
|
557
|
+
],
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
561
|
+
await harness.connector.testConnection(
|
|
562
|
+
settings({ config: { orgUrl: ORG_URL, filter: CUSTOM_FILTER } }),
|
|
563
|
+
{ requestTimeoutInMs: 1000 },
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
const read: SecurityConnectorCheck = checkByKey(
|
|
567
|
+
checks,
|
|
568
|
+
"read-permission",
|
|
569
|
+
);
|
|
570
|
+
expect(read.status).toBe("pass");
|
|
571
|
+
expect(read.message).toBe(
|
|
572
|
+
"The System Log is readable through the connection's Event filter. No matching event was published in the last 24 hours.",
|
|
573
|
+
);
|
|
574
|
+
expect(read.details).toEqual({ usingDefaultFilter: false });
|
|
575
|
+
|
|
576
|
+
const available: SecurityConnectorCheck = checkByKey(
|
|
577
|
+
checks,
|
|
578
|
+
"detections-available",
|
|
579
|
+
);
|
|
580
|
+
expect(available.message).toBe(
|
|
581
|
+
"1 event published in the last 24 hours and 1 in the last 7 days.",
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
for (const request of harness.requests.slice(1)) {
|
|
585
|
+
expect(param(request, "filter")).toBe(CUSTOM_FILTER);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test("warns instead of passing when nothing was published in the last 7 days", async () => {
|
|
590
|
+
const harness: Harness = buildHarness({
|
|
591
|
+
responders: [
|
|
592
|
+
(): DataSourceHttpResponse => {
|
|
593
|
+
return page([]);
|
|
594
|
+
},
|
|
595
|
+
(): DataSourceHttpResponse => {
|
|
596
|
+
return page([]);
|
|
597
|
+
},
|
|
598
|
+
(): DataSourceHttpResponse => {
|
|
599
|
+
return page([]);
|
|
600
|
+
},
|
|
601
|
+
],
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
605
|
+
await harness.connector.testConnection(settings(), {
|
|
606
|
+
requestTimeoutInMs: 1000,
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
expect(statuses(checks)).toEqual([
|
|
610
|
+
"authentication:pass",
|
|
611
|
+
"read-permission:pass",
|
|
612
|
+
"detections-available:warn",
|
|
613
|
+
]);
|
|
614
|
+
|
|
615
|
+
const available: SecurityConnectorCheck = checkByKey(
|
|
616
|
+
checks,
|
|
617
|
+
"detections-available",
|
|
618
|
+
);
|
|
619
|
+
expect(available.message).toContain(
|
|
620
|
+
"No matching events were published in the last 7 days",
|
|
621
|
+
);
|
|
622
|
+
expect(available.remediation).toContain(
|
|
623
|
+
"Check the Okta organization URL",
|
|
624
|
+
);
|
|
625
|
+
expect(available.details).toEqual({
|
|
626
|
+
createdLast24h: 0,
|
|
627
|
+
createdLast7d: 0,
|
|
628
|
+
hasMoreLast24h: false,
|
|
629
|
+
hasMoreLast7d: false,
|
|
630
|
+
usingDefaultFilter: true,
|
|
631
|
+
});
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test("blames the Event filter when a custom filter matched nothing in 7 days", async () => {
|
|
635
|
+
const harness: Harness = buildHarness({
|
|
636
|
+
responders: [
|
|
637
|
+
(): DataSourceHttpResponse => {
|
|
638
|
+
return page([]);
|
|
639
|
+
},
|
|
640
|
+
(): DataSourceHttpResponse => {
|
|
641
|
+
return page([]);
|
|
642
|
+
},
|
|
643
|
+
(): DataSourceHttpResponse => {
|
|
644
|
+
return page([]);
|
|
645
|
+
},
|
|
646
|
+
],
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
650
|
+
await harness.connector.testConnection(
|
|
651
|
+
settings({ config: { orgUrl: ORG_URL, filter: CUSTOM_FILTER } }),
|
|
652
|
+
{ requestTimeoutInMs: 1000 },
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
const available: SecurityConnectorCheck = checkByKey(
|
|
656
|
+
checks,
|
|
657
|
+
"detections-available",
|
|
658
|
+
);
|
|
659
|
+
expect(available.status).toBe("warn");
|
|
660
|
+
expect(available.remediation).toContain(
|
|
661
|
+
"The connection's Event filter matched nothing",
|
|
662
|
+
);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test("fails authentication on an invalid setting without contacting Okta", async () => {
|
|
666
|
+
const harness: Harness = buildHarness({});
|
|
667
|
+
|
|
668
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
669
|
+
await harness.connector.testConnection(settings({ secrets: {} }), {
|
|
670
|
+
requestTimeoutInMs: 1000,
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
expect(statuses(checks)).toEqual([
|
|
674
|
+
"authentication:fail",
|
|
675
|
+
"read-permission:skip",
|
|
676
|
+
"detections-available:skip",
|
|
677
|
+
]);
|
|
678
|
+
expect(checks[0]!.message).toBe("API token is required.");
|
|
679
|
+
expect(checks[0]!.remediation).toContain("Nothing was contacted");
|
|
680
|
+
expect(checks[1]!.message).toContain("configuration is invalid");
|
|
681
|
+
expect(harness.requests).toHaveLength(0);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test("fails authentication on a 401, skips the rest, and points at Security > API > Tokens", async () => {
|
|
685
|
+
const harness: Harness = buildHarness({
|
|
686
|
+
probe: (): DataSourceHttpResponse => {
|
|
687
|
+
return oktaError(
|
|
688
|
+
401,
|
|
689
|
+
"E0000011",
|
|
690
|
+
`Invalid token provided: SSWS ${API_TOKEN}`,
|
|
691
|
+
);
|
|
692
|
+
},
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
696
|
+
await harness.connector.testConnection(settings(), {
|
|
697
|
+
requestTimeoutInMs: 1000,
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
expect(statuses(checks)).toEqual([
|
|
701
|
+
"authentication:fail",
|
|
702
|
+
"read-permission:skip",
|
|
703
|
+
"detections-available:skip",
|
|
704
|
+
]);
|
|
705
|
+
expect(checks[0]!.message).toMatch(
|
|
706
|
+
/^Okta System Log events request failed \(HTTP 401\): /,
|
|
707
|
+
);
|
|
708
|
+
expect(checks[0]!.message).toContain("Invalid token provided");
|
|
709
|
+
expect(checks[0]!.message).not.toContain(API_TOKEN);
|
|
710
|
+
expect(checks[0]!.remediation).toContain("Security > API > Tokens");
|
|
711
|
+
expect(checks[0]!.remediation).toContain("Update credentials");
|
|
712
|
+
expect(checks[1]!.message).toContain("authentication did not succeed");
|
|
713
|
+
expect(harness.requests).toHaveLength(1);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
test("fails authentication on a 403 with the admin role remediation", async () => {
|
|
717
|
+
const harness: Harness = buildHarness({
|
|
718
|
+
probe: (): DataSourceHttpResponse => {
|
|
719
|
+
return oktaError(
|
|
720
|
+
403,
|
|
721
|
+
"E0000006",
|
|
722
|
+
"You do not have permission to perform the requested action",
|
|
723
|
+
);
|
|
724
|
+
},
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
728
|
+
await harness.connector.testConnection(settings(), {
|
|
729
|
+
requestTimeoutInMs: 1000,
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
expect(checks[0]!.status).toBe("fail");
|
|
733
|
+
expect(checks[0]!.message).toMatch(/\(HTTP 403\)/);
|
|
734
|
+
expect(checks[0]!.remediation).toContain("Read-only Administrator");
|
|
735
|
+
expect(checks[0]!.remediation).toContain("network zone");
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
test("fails authentication on a 404 with the org URL remediation", async () => {
|
|
739
|
+
const harness: Harness = buildHarness({
|
|
740
|
+
probe: (): DataSourceHttpResponse => {
|
|
741
|
+
return oktaError(404, "E0000007", "Not found: Resource not found");
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
746
|
+
await harness.connector.testConnection(settings(), {
|
|
747
|
+
requestTimeoutInMs: 1000,
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
expect(checks[0]!.status).toBe("fail");
|
|
751
|
+
expect(checks[0]!.remediation).toContain(
|
|
752
|
+
"Check the Okta organization URL",
|
|
753
|
+
);
|
|
754
|
+
expect(checks[0]!.remediation).toContain("-admin");
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
test("fails authentication on a 400 from the probe with the URL remediation", async () => {
|
|
758
|
+
const harness: Harness = buildHarness({
|
|
759
|
+
probe: (): DataSourceHttpResponse => {
|
|
760
|
+
return oktaError(
|
|
761
|
+
400,
|
|
762
|
+
"E0000003",
|
|
763
|
+
"The request body was not well-formed.",
|
|
764
|
+
);
|
|
765
|
+
},
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
769
|
+
await harness.connector.testConnection(settings(), {
|
|
770
|
+
requestTimeoutInMs: 1000,
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
expect(checks[0]!.status).toBe("fail");
|
|
774
|
+
expect(checks[0]!.remediation).toContain(
|
|
775
|
+
"Okta rejected the probe request",
|
|
776
|
+
);
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
test("fails authentication with the connectivity hint on a timeout", async () => {
|
|
780
|
+
const harness: Harness = buildHarness({
|
|
781
|
+
probe: (): DataSourceHttpResponse => {
|
|
782
|
+
throw new BadDataException(
|
|
783
|
+
"Could not reach data source: timeout of 1000ms exceeded",
|
|
784
|
+
);
|
|
785
|
+
},
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
789
|
+
await harness.connector.testConnection(settings(), {
|
|
790
|
+
requestTimeoutInMs: 1000,
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
expect(statuses(checks)).toEqual([
|
|
794
|
+
"authentication:fail",
|
|
795
|
+
"read-permission:skip",
|
|
796
|
+
"detections-available:skip",
|
|
797
|
+
]);
|
|
798
|
+
expect(checks[0]!.message).toBe(
|
|
799
|
+
"Okta System Log events request did not complete: Could not reach data source: timeout of 1000ms exceeded",
|
|
800
|
+
);
|
|
801
|
+
expect(checks[0]!.remediation).toContain(
|
|
802
|
+
"reachable from the OneUptime API and worker processes",
|
|
803
|
+
);
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
test("fails authentication when the URL answers HTML instead of JSON", async () => {
|
|
807
|
+
const harness: Harness = buildHarness({
|
|
808
|
+
probe: (): DataSourceHttpResponse => {
|
|
809
|
+
return text(
|
|
810
|
+
200,
|
|
811
|
+
"<!DOCTYPE html><html><head><title>Sign In</title></head></html>",
|
|
812
|
+
);
|
|
813
|
+
},
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
817
|
+
await harness.connector.testConnection(settings(), {
|
|
818
|
+
requestTimeoutInMs: 1000,
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
expect(checks[0]!.status).toBe("fail");
|
|
822
|
+
expect(checks[0]!.message).toBe(
|
|
823
|
+
"Okta System Log events request returned a non-JSON body.",
|
|
824
|
+
);
|
|
825
|
+
expect(checks[0]!.remediation).toContain("not behind a proxy");
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
test("fails read-permission on a 403 with the role remediation and skips availability", async () => {
|
|
829
|
+
const harness: Harness = buildHarness({
|
|
830
|
+
responders: [
|
|
831
|
+
(): DataSourceHttpResponse => {
|
|
832
|
+
return oktaError(
|
|
833
|
+
403,
|
|
834
|
+
"E0000006",
|
|
835
|
+
"You do not have permission to perform the requested action",
|
|
836
|
+
);
|
|
837
|
+
},
|
|
838
|
+
],
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
842
|
+
await harness.connector.testConnection(settings(), {
|
|
843
|
+
requestTimeoutInMs: 1000,
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
expect(statuses(checks)).toEqual([
|
|
847
|
+
"authentication:pass",
|
|
848
|
+
"read-permission:fail",
|
|
849
|
+
"detections-available:skip",
|
|
850
|
+
]);
|
|
851
|
+
const read: SecurityConnectorCheck = checkByKey(
|
|
852
|
+
checks,
|
|
853
|
+
"read-permission",
|
|
854
|
+
);
|
|
855
|
+
expect(read.message).toMatch(
|
|
856
|
+
/^Okta System Log events request failed \(HTTP 403\): /,
|
|
857
|
+
);
|
|
858
|
+
expect(read.remediation).toContain("Read-only Administrator");
|
|
859
|
+
expect(checks[2]!.message).toContain("could not be read");
|
|
860
|
+
expect(harness.requests).toHaveLength(2);
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
test("fails read-permission on a 400 with the Event filter remediation", async () => {
|
|
864
|
+
const harness: Harness = buildHarness({
|
|
865
|
+
responders: [
|
|
866
|
+
(): DataSourceHttpResponse => {
|
|
867
|
+
return oktaError(
|
|
868
|
+
400,
|
|
869
|
+
"E0000053",
|
|
870
|
+
"Invalid filter expression: unknown attribute 'evenType'",
|
|
871
|
+
);
|
|
872
|
+
},
|
|
873
|
+
],
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
877
|
+
await harness.connector.testConnection(
|
|
878
|
+
settings({
|
|
879
|
+
config: { orgUrl: ORG_URL, filter: 'evenType sw "user."' },
|
|
880
|
+
}),
|
|
881
|
+
{ requestTimeoutInMs: 1000 },
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
const read: SecurityConnectorCheck = checkByKey(
|
|
885
|
+
checks,
|
|
886
|
+
"read-permission",
|
|
887
|
+
);
|
|
888
|
+
expect(read.status).toBe("fail");
|
|
889
|
+
expect(read.message).toContain("unknown attribute 'evenType'");
|
|
890
|
+
expect(read.remediation).toContain(
|
|
891
|
+
"Event filter is not a valid System Log filter expression",
|
|
892
|
+
);
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
test("fails read-permission with the filter hint when the filtered read times out after a good probe", async () => {
|
|
896
|
+
const harness: Harness = buildHarness({
|
|
897
|
+
responders: [
|
|
898
|
+
(): DataSourceHttpResponse => {
|
|
899
|
+
throw new BadDataException(
|
|
900
|
+
"Could not reach data source: timeout of 1000ms exceeded",
|
|
901
|
+
);
|
|
902
|
+
},
|
|
903
|
+
],
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
907
|
+
await harness.connector.testConnection(settings(), {
|
|
908
|
+
requestTimeoutInMs: 1000,
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
const read: SecurityConnectorCheck = checkByKey(
|
|
912
|
+
checks,
|
|
913
|
+
"read-permission",
|
|
914
|
+
);
|
|
915
|
+
expect(read.status).toBe("fail");
|
|
916
|
+
expect(read.remediation).toContain("simplify the Event filter");
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
test("fails detections-available on a 429 with the rate limit remediation", async () => {
|
|
920
|
+
const harness: Harness = buildHarness({
|
|
921
|
+
responders: [
|
|
922
|
+
(): DataSourceHttpResponse => {
|
|
923
|
+
return page([logEvent("A1", 0)]);
|
|
924
|
+
},
|
|
925
|
+
(): DataSourceHttpResponse => {
|
|
926
|
+
return oktaError(
|
|
927
|
+
429,
|
|
928
|
+
"E0000047",
|
|
929
|
+
"API call exceeded rate limit. Consult docs.",
|
|
930
|
+
);
|
|
931
|
+
},
|
|
932
|
+
],
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
936
|
+
await harness.connector.testConnection(settings(), {
|
|
937
|
+
requestTimeoutInMs: 1000,
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
expect(statuses(checks)).toEqual([
|
|
941
|
+
"authentication:pass",
|
|
942
|
+
"read-permission:pass",
|
|
943
|
+
"detections-available:fail",
|
|
944
|
+
]);
|
|
945
|
+
const available: SecurityConnectorCheck = checkByKey(
|
|
946
|
+
checks,
|
|
947
|
+
"detections-available",
|
|
948
|
+
);
|
|
949
|
+
expect(available.message).toMatch(
|
|
950
|
+
/^Okta System Log events request failed \(HTTP 429\): /,
|
|
951
|
+
);
|
|
952
|
+
expect(available.remediation).toContain("rate limiting this token");
|
|
953
|
+
expect(harness.requests).toHaveLength(3);
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
test("fails detections-available on a 500 with the Okta status remediation", async () => {
|
|
957
|
+
const harness: Harness = buildHarness({
|
|
958
|
+
responders: [
|
|
959
|
+
(): DataSourceHttpResponse => {
|
|
960
|
+
return page([logEvent("A1", 0)]);
|
|
961
|
+
},
|
|
962
|
+
(): DataSourceHttpResponse => {
|
|
963
|
+
return page([logEvent("A1", 0)]);
|
|
964
|
+
},
|
|
965
|
+
(): DataSourceHttpResponse => {
|
|
966
|
+
return oktaError(500, "E0000009", "Internal Server Error");
|
|
967
|
+
},
|
|
968
|
+
],
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
972
|
+
await harness.connector.testConnection(settings(), {
|
|
973
|
+
requestTimeoutInMs: 1000,
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
const available: SecurityConnectorCheck = checkByKey(
|
|
977
|
+
checks,
|
|
978
|
+
"detections-available",
|
|
979
|
+
);
|
|
980
|
+
expect(available.status).toBe("fail");
|
|
981
|
+
expect(available.message).toMatch(/\(HTTP 500\)/);
|
|
982
|
+
expect(available.remediation).toContain("status.okta.com");
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
test("describes an unrecognized first record without failing the read", async () => {
|
|
986
|
+
const harness: Harness = buildHarness({
|
|
987
|
+
responders: [
|
|
988
|
+
(): DataSourceHttpResponse => {
|
|
989
|
+
return page([{ foo: "bar" }]);
|
|
990
|
+
},
|
|
991
|
+
(): DataSourceHttpResponse => {
|
|
992
|
+
return page([]);
|
|
993
|
+
},
|
|
994
|
+
(): DataSourceHttpResponse => {
|
|
995
|
+
return page([]);
|
|
996
|
+
},
|
|
997
|
+
],
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
const checks: Array<SecurityConnectorCheck> =
|
|
1001
|
+
await harness.connector.testConnection(settings(), {
|
|
1002
|
+
requestTimeoutInMs: 1000,
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
const read: SecurityConnectorCheck = checkByKey(
|
|
1006
|
+
checks,
|
|
1007
|
+
"read-permission",
|
|
1008
|
+
);
|
|
1009
|
+
expect(read.status).toBe("pass");
|
|
1010
|
+
expect(read.message).toContain('returned a "unrecognized record" event');
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
describe("fetchEvents", () => {
|
|
1015
|
+
test("reads the window by published time, normalizes with the catalog's vendor and product, and builds samples", async () => {
|
|
1016
|
+
const harness: Harness = buildHarness({
|
|
1017
|
+
responders: [
|
|
1018
|
+
(): DataSourceHttpResponse => {
|
|
1019
|
+
return page([
|
|
1020
|
+
logEvent("A1", 0),
|
|
1021
|
+
logEvent("B2", 1, {
|
|
1022
|
+
eventType: "user.account.update_password",
|
|
1023
|
+
displayMessage: "User update password for Okta",
|
|
1024
|
+
target: [
|
|
1025
|
+
{
|
|
1026
|
+
id: "00u9",
|
|
1027
|
+
type: "User",
|
|
1028
|
+
alternateId: "bob@example.com",
|
|
1029
|
+
displayName: "Bob",
|
|
1030
|
+
},
|
|
1031
|
+
],
|
|
1032
|
+
}),
|
|
1033
|
+
logEvent("C3", 2, {
|
|
1034
|
+
eventType: "security.threat.detected",
|
|
1035
|
+
severity: "WARN",
|
|
1036
|
+
displayMessage: "Threat detected",
|
|
1037
|
+
}),
|
|
1038
|
+
]);
|
|
1039
|
+
},
|
|
1040
|
+
],
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1044
|
+
settings(),
|
|
1045
|
+
{ startTime: START, endTime: END },
|
|
1046
|
+
fetchOptions({ sampleLimit: 2 }),
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
expect(result.complete).toBe(true);
|
|
1050
|
+
// A complete read names no resume point; the poller uses the window end.
|
|
1051
|
+
expect(result.resumeAfter).toBeUndefined();
|
|
1052
|
+
expect(result.warnings).toEqual([]);
|
|
1053
|
+
expect(result.requestCount).toBe(1);
|
|
1054
|
+
expect(result.fetchedCount).toBe(3);
|
|
1055
|
+
expect(result.rejectedCount).toBe(0);
|
|
1056
|
+
expect(result.failedCount).toBe(0);
|
|
1057
|
+
expect(result.events).toHaveLength(3);
|
|
1058
|
+
|
|
1059
|
+
for (const event of result.events) {
|
|
1060
|
+
expect(event.vendorName).toBe(definition.vendorName);
|
|
1061
|
+
expect(event.productName).toBe(definition.productName);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
expect(
|
|
1065
|
+
result.events.map((event: { eventUid: string }): string => {
|
|
1066
|
+
return event.eventUid;
|
|
1067
|
+
}),
|
|
1068
|
+
).toEqual(["A1", "B2", "C3"]);
|
|
1069
|
+
expect(
|
|
1070
|
+
result.events.map((event: { classUid: number }): number => {
|
|
1071
|
+
return event.classUid;
|
|
1072
|
+
}),
|
|
1073
|
+
).toEqual([3002, 3001, 2004]);
|
|
1074
|
+
expect(result.events[1]!.targetUser).toBe("bob@example.com");
|
|
1075
|
+
|
|
1076
|
+
expect(result.samples).toHaveLength(2);
|
|
1077
|
+
expect(result.samples[0]).toEqual({
|
|
1078
|
+
id: "A1",
|
|
1079
|
+
title: "User login to Okta",
|
|
1080
|
+
severity: "Informational",
|
|
1081
|
+
createdTime: "2026-09-12T10:15:00.123Z",
|
|
1082
|
+
eventTime: "2026-09-12T10:15:00.123Z",
|
|
1083
|
+
});
|
|
1084
|
+
expect(result.samples[1]!.id).toBe("B2");
|
|
1085
|
+
|
|
1086
|
+
const request: DataSourceHttpRequest = harness.requests[0]!;
|
|
1087
|
+
const url: URL = new URL(request.url);
|
|
1088
|
+
expect(request.method).toBe("GET");
|
|
1089
|
+
expect(url.origin).toBe(ORG_URL);
|
|
1090
|
+
expect(url.pathname).toBe("/api/v1/logs");
|
|
1091
|
+
expect(sortedParamKeys(request.url)).toEqual([
|
|
1092
|
+
"filter",
|
|
1093
|
+
"limit",
|
|
1094
|
+
"since",
|
|
1095
|
+
"sortOrder",
|
|
1096
|
+
"until",
|
|
1097
|
+
]);
|
|
1098
|
+
expect(url.searchParams.get("since")).toBe(START.toISOString());
|
|
1099
|
+
expect(url.searchParams.get("until")).toBe(END.toISOString());
|
|
1100
|
+
expect(url.searchParams.get("sortOrder")).toBe("ASCENDING");
|
|
1101
|
+
expect(url.searchParams.get("limit")).toBe("100");
|
|
1102
|
+
expect(url.searchParams.get("filter")).toBe(OKTA_DEFAULT_EVENT_FILTER);
|
|
1103
|
+
expect(request.headers?.["Authorization"]).toBe(`SSWS ${API_TOKEN}`);
|
|
1104
|
+
expect(request.timeoutInMs).toBe(30000);
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
test('follows the Link rel="next" header until an empty page and drops events re-sent across pages', async () => {
|
|
1108
|
+
const harness: Harness = buildHarness({
|
|
1109
|
+
responders: [
|
|
1110
|
+
(): DataSourceHttpResponse => {
|
|
1111
|
+
return page([logEvent("A1", 0), logEvent("B2", 1)], nextLink("p1"));
|
|
1112
|
+
},
|
|
1113
|
+
(): DataSourceHttpResponse => {
|
|
1114
|
+
return page([logEvent("B2", 1), logEvent("C3", 2)], nextLink("p2"));
|
|
1115
|
+
},
|
|
1116
|
+
(): DataSourceHttpResponse => {
|
|
1117
|
+
return page([], nextLink("p3"));
|
|
1118
|
+
},
|
|
1119
|
+
],
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1123
|
+
settings(),
|
|
1124
|
+
{ startTime: START, endTime: END },
|
|
1125
|
+
fetchOptions(),
|
|
1126
|
+
);
|
|
1127
|
+
|
|
1128
|
+
expect(result.complete).toBe(true);
|
|
1129
|
+
expect(result.resumeAfter).toBeUndefined();
|
|
1130
|
+
expect(result.requestCount).toBe(3);
|
|
1131
|
+
expect(result.fetchedCount).toBe(3);
|
|
1132
|
+
expect(
|
|
1133
|
+
result.events.map((event: { eventUid: string }): string => {
|
|
1134
|
+
return event.eventUid;
|
|
1135
|
+
}),
|
|
1136
|
+
).toEqual(["A1", "B2", "C3"]);
|
|
1137
|
+
expect(harness.requests).toHaveLength(3);
|
|
1138
|
+
expect(harness.requests[1]!.url).toBe(nextLink("p1"));
|
|
1139
|
+
expect(harness.requests[2]!.url).toBe(nextLink("p2"));
|
|
1140
|
+
expect(harness.requests[1]!.headers).toEqual(
|
|
1141
|
+
harness.requests[0]!.headers,
|
|
1142
|
+
);
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
test("stops at the last page when Okta sends no next link", async () => {
|
|
1146
|
+
const harness: Harness = buildHarness({
|
|
1147
|
+
responders: [
|
|
1148
|
+
(): DataSourceHttpResponse => {
|
|
1149
|
+
return page([logEvent("A1", 0)], nextLink("p1"));
|
|
1150
|
+
},
|
|
1151
|
+
(): DataSourceHttpResponse => {
|
|
1152
|
+
return page([logEvent("B2", 1)]);
|
|
1153
|
+
},
|
|
1154
|
+
],
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1158
|
+
settings(),
|
|
1159
|
+
{ startTime: START, endTime: END },
|
|
1160
|
+
fetchOptions(),
|
|
1161
|
+
);
|
|
1162
|
+
|
|
1163
|
+
expect(result.complete).toBe(true);
|
|
1164
|
+
expect(result.requestCount).toBe(2);
|
|
1165
|
+
expect(result.events).toHaveLength(2);
|
|
1166
|
+
expect(harness.requests).toHaveLength(2);
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
test("sends the tenant's Event filter and clamps the page size to Okta's maximum", async () => {
|
|
1170
|
+
const harness: Harness = buildHarness({
|
|
1171
|
+
responders: [
|
|
1172
|
+
(): DataSourceHttpResponse => {
|
|
1173
|
+
return page([]);
|
|
1174
|
+
},
|
|
1175
|
+
],
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
await harness.connector.fetchEvents(
|
|
1179
|
+
settings({ config: { orgUrl: ORG_URL, filter: CUSTOM_FILTER } }),
|
|
1180
|
+
{ startTime: START, endTime: END },
|
|
1181
|
+
fetchOptions({ maxEvents: 10000 }),
|
|
1182
|
+
);
|
|
1183
|
+
|
|
1184
|
+
expect(param(harness.requests[0]!, "filter")).toBe(CUSTOM_FILTER);
|
|
1185
|
+
expect(param(harness.requests[0]!, "limit")).toBe("1000");
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
/*
|
|
1189
|
+
* Review finding bound-hit-window-never-advances: the bound tests used
|
|
1190
|
+
* to assert "cursor is held", and the held cursor re-read the same
|
|
1191
|
+
* oldest events on every poll so nothing newer was ever imported.
|
|
1192
|
+
* Bounded requests are ordered by `published`, so a bound now reports
|
|
1193
|
+
* the `published` time of the last event read as the resume point.
|
|
1194
|
+
*/
|
|
1195
|
+
test("stops at the request bound, marks the fetch incomplete and reports the last published time read as the resume point", async () => {
|
|
1196
|
+
const harness: Harness = buildHarness({
|
|
1197
|
+
responders: [
|
|
1198
|
+
(): DataSourceHttpResponse => {
|
|
1199
|
+
return page([logEvent("A1", 0)], nextLink("p1"));
|
|
1200
|
+
},
|
|
1201
|
+
(): DataSourceHttpResponse => {
|
|
1202
|
+
return page([logEvent("B2", 1)], nextLink("p2"));
|
|
1203
|
+
},
|
|
1204
|
+
],
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1208
|
+
settings(),
|
|
1209
|
+
{ startTime: START, endTime: END },
|
|
1210
|
+
fetchOptions({ maxRequests: 2 }),
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
expect(result.complete).toBe(false);
|
|
1214
|
+
expect(result.requestCount).toBe(2);
|
|
1215
|
+
expect(result.events).toHaveLength(2);
|
|
1216
|
+
expect(result.resumeAfter).toEqual(new Date("2026-09-12T11:15:00.123Z"));
|
|
1217
|
+
expect(result.warnings).toEqual([
|
|
1218
|
+
`Stopped after 2 requests with events still unread in the window ${START.toISOString()} to ${END.toISOString()}. The last event read was published at 2026-09-12T11:15:00.123Z.`,
|
|
1219
|
+
]);
|
|
1220
|
+
expect(result.warnings[0]).not.toContain("cursor is held");
|
|
1221
|
+
expect(harness.requests).toHaveLength(2);
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
test("refuses to read with a zero request budget and reports it instead of exceeding it", async () => {
|
|
1225
|
+
const harness: Harness = buildHarness({});
|
|
1226
|
+
|
|
1227
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1228
|
+
settings(),
|
|
1229
|
+
{ startTime: START, endTime: END },
|
|
1230
|
+
fetchOptions({ maxRequests: 0 }),
|
|
1231
|
+
);
|
|
1232
|
+
|
|
1233
|
+
expect(result.complete).toBe(false);
|
|
1234
|
+
// Nothing was read, so there is no progress to report.
|
|
1235
|
+
expect(result.resumeAfter).toBeUndefined();
|
|
1236
|
+
expect(result.requestCount).toBe(0);
|
|
1237
|
+
expect(result.events).toEqual([]);
|
|
1238
|
+
expect(result.warnings[0]).toContain("Stopped after 0 requests");
|
|
1239
|
+
expect(harness.requests).toHaveLength(0);
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
test("stops at the event bound, marks the fetch incomplete and resumes after the last event kept", async () => {
|
|
1243
|
+
const harness: Harness = buildHarness({
|
|
1244
|
+
responders: [
|
|
1245
|
+
(): DataSourceHttpResponse => {
|
|
1246
|
+
return page(
|
|
1247
|
+
[logEvent("A1", 0), logEvent("B2", 1), logEvent("C3", 2)],
|
|
1248
|
+
nextLink("p1"),
|
|
1249
|
+
);
|
|
1250
|
+
},
|
|
1251
|
+
],
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1255
|
+
settings(),
|
|
1256
|
+
{ startTime: START, endTime: END },
|
|
1257
|
+
fetchOptions({ maxEvents: 2 }),
|
|
1258
|
+
);
|
|
1259
|
+
|
|
1260
|
+
expect(result.complete).toBe(false);
|
|
1261
|
+
expect(result.events).toHaveLength(2);
|
|
1262
|
+
expect(result.fetchedCount).toBe(2);
|
|
1263
|
+
expect(result.requestCount).toBe(1);
|
|
1264
|
+
expect(result.warnings).toHaveLength(1);
|
|
1265
|
+
expect(result.warnings[0]).toBe(
|
|
1266
|
+
"Stopped after collecting 2 events; the window holds more. The last event read was published at 2026-09-12T11:15:00.123Z.",
|
|
1267
|
+
);
|
|
1268
|
+
// C3 (published 12:15) was not collected, so it is not the resume point.
|
|
1269
|
+
expect(result.resumeAfter).toEqual(new Date("2026-09-12T11:15:00.123Z"));
|
|
1270
|
+
expect(param(harness.requests[0]!, "limit")).toBe("2");
|
|
1271
|
+
// The next link was not followed once the bound was hit.
|
|
1272
|
+
expect(harness.requests).toHaveLength(1);
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
test("finds the event bound on the next page and resumes after the last event of the page before it", async () => {
|
|
1276
|
+
const harness: Harness = buildHarness({
|
|
1277
|
+
responders: [
|
|
1278
|
+
(): DataSourceHttpResponse => {
|
|
1279
|
+
return page([logEvent("A1", 0), logEvent("B2", 1)], nextLink("p1"));
|
|
1280
|
+
},
|
|
1281
|
+
(): DataSourceHttpResponse => {
|
|
1282
|
+
// B2 is re-sent across the page boundary and must not count.
|
|
1283
|
+
return page([logEvent("B2", 1), logEvent("C3", 2)], nextLink("p2"));
|
|
1284
|
+
},
|
|
1285
|
+
],
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1289
|
+
settings(),
|
|
1290
|
+
{ startTime: START, endTime: END },
|
|
1291
|
+
fetchOptions({ maxEvents: 2 }),
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
expect(result.complete).toBe(false);
|
|
1295
|
+
expect(result.fetchedCount).toBe(2);
|
|
1296
|
+
expect(result.requestCount).toBe(2);
|
|
1297
|
+
expect(result.resumeAfter).toEqual(new Date("2026-09-12T11:15:00.123Z"));
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
test("counts rejected records toward the resume point and ignores a missing or unparseable published time", async () => {
|
|
1301
|
+
const harness: Harness = buildHarness({
|
|
1302
|
+
responders: [
|
|
1303
|
+
(): DataSourceHttpResponse => {
|
|
1304
|
+
return page(
|
|
1305
|
+
[
|
|
1306
|
+
logEvent("A1", 0),
|
|
1307
|
+
// Not an event the normalizer recognizes, but it was read.
|
|
1308
|
+
{ uuid: "no-type", published: "2026-09-12T12:30:00.000Z" },
|
|
1309
|
+
logEvent("B2", 3, { published: "yesterday-ish" }),
|
|
1310
|
+
logEvent("C3", 4, { published: null }),
|
|
1311
|
+
],
|
|
1312
|
+
nextLink("p1"),
|
|
1313
|
+
);
|
|
1314
|
+
},
|
|
1315
|
+
],
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1319
|
+
settings(),
|
|
1320
|
+
{ startTime: START, endTime: END },
|
|
1321
|
+
fetchOptions({ maxRequests: 1 }),
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
expect(result.complete).toBe(false);
|
|
1325
|
+
expect(result.fetchedCount).toBe(4);
|
|
1326
|
+
expect(result.resumeAfter).toEqual(new Date("2026-09-12T12:30:00.000Z"));
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
test("reports no resume point when no event read carried a readable published time", async () => {
|
|
1330
|
+
const harness: Harness = buildHarness({
|
|
1331
|
+
responders: [
|
|
1332
|
+
(): DataSourceHttpResponse => {
|
|
1333
|
+
return page([logEvent("A1", 0, { published: "" })], nextLink("p1"));
|
|
1334
|
+
},
|
|
1335
|
+
],
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1339
|
+
settings(),
|
|
1340
|
+
{ startTime: START, endTime: END },
|
|
1341
|
+
fetchOptions({ maxRequests: 1 }),
|
|
1342
|
+
);
|
|
1343
|
+
|
|
1344
|
+
expect(result.complete).toBe(false);
|
|
1345
|
+
expect(result.resumeAfter).toBeUndefined();
|
|
1346
|
+
expect(result.warnings[result.warnings.length - 1]).toContain(
|
|
1347
|
+
"No event read carried a readable published time, so no resume point is reported.",
|
|
1348
|
+
);
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
test("counts records the normalizer does not recognize as rejected", async () => {
|
|
1352
|
+
const harness: Harness = buildHarness({
|
|
1353
|
+
responders: [
|
|
1354
|
+
(): DataSourceHttpResponse => {
|
|
1355
|
+
return page([
|
|
1356
|
+
logEvent("A1", 0),
|
|
1357
|
+
{ errorCode: "E0000011", errorSummary: "Invalid token provided" },
|
|
1358
|
+
{ uuid: "no-type", published: "2026-09-12T10:15:00.123Z" },
|
|
1359
|
+
]);
|
|
1360
|
+
},
|
|
1361
|
+
],
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1365
|
+
settings(),
|
|
1366
|
+
{ startTime: START, endTime: END },
|
|
1367
|
+
fetchOptions(),
|
|
1368
|
+
);
|
|
1369
|
+
|
|
1370
|
+
expect(result.fetchedCount).toBe(3);
|
|
1371
|
+
expect(result.rejectedCount).toBe(2);
|
|
1372
|
+
expect(result.failedCount).toBe(0);
|
|
1373
|
+
expect(result.events).toHaveLength(1);
|
|
1374
|
+
expect(result.warnings).toEqual([]);
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
test("counts a record that throws during normalization as failed and warns with the error redacted", async () => {
|
|
1378
|
+
/*
|
|
1379
|
+
* The normalizer only ever sees the event, never the token, so the
|
|
1380
|
+
* quoted error is run through the generic credential redaction
|
|
1381
|
+
* (header dumps, bearer tokens) rather than the client's literal
|
|
1382
|
+
* token scrub.
|
|
1383
|
+
*/
|
|
1384
|
+
jest
|
|
1385
|
+
.spyOn(OktaNormalizer, "normalize")
|
|
1386
|
+
.mockImplementationOnce((): never => {
|
|
1387
|
+
throw new Error(
|
|
1388
|
+
"boom while reading Authorization: Bearer abcdefghij0123456789",
|
|
1389
|
+
);
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
const harness: Harness = buildHarness({
|
|
1393
|
+
responders: [
|
|
1394
|
+
(): DataSourceHttpResponse => {
|
|
1395
|
+
return page([logEvent("A1", 0), logEvent("B2", 1)]);
|
|
1396
|
+
},
|
|
1397
|
+
],
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1401
|
+
settings(),
|
|
1402
|
+
{ startTime: START, endTime: END },
|
|
1403
|
+
fetchOptions(),
|
|
1404
|
+
);
|
|
1405
|
+
|
|
1406
|
+
expect(result.fetchedCount).toBe(2);
|
|
1407
|
+
expect(result.failedCount).toBe(1);
|
|
1408
|
+
expect(result.events).toHaveLength(1);
|
|
1409
|
+
expect(result.complete).toBe(true);
|
|
1410
|
+
expect(result.warnings).toHaveLength(1);
|
|
1411
|
+
expect(result.warnings[0]).toMatch(/^Event A1 could not be normalized: /);
|
|
1412
|
+
expect(result.warnings[0]).toContain("boom while reading");
|
|
1413
|
+
expect(result.warnings[0]).not.toContain("abcdefghij0123456789");
|
|
1414
|
+
expect(result.warnings[0]).toContain("[REDACTED]");
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
test("keys events without a uuid by content so an identical copy on a later page is not stored twice", async () => {
|
|
1418
|
+
const noId: JSONObject = logEvent("", 0);
|
|
1419
|
+
delete noId["uuid"];
|
|
1420
|
+
|
|
1421
|
+
const harness: Harness = buildHarness({
|
|
1422
|
+
responders: [
|
|
1423
|
+
(): DataSourceHttpResponse => {
|
|
1424
|
+
return page([noId], nextLink("p1"));
|
|
1425
|
+
},
|
|
1426
|
+
(): DataSourceHttpResponse => {
|
|
1427
|
+
return page([{ ...noId }]);
|
|
1428
|
+
},
|
|
1429
|
+
],
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
const result: ConnectorFetchResult = await harness.connector.fetchEvents(
|
|
1433
|
+
settings(),
|
|
1434
|
+
{ startTime: START, endTime: END },
|
|
1435
|
+
fetchOptions(),
|
|
1436
|
+
);
|
|
1437
|
+
|
|
1438
|
+
expect(result.fetchedCount).toBe(1);
|
|
1439
|
+
expect(result.events).toHaveLength(1);
|
|
1440
|
+
expect(result.events[0]!.eventUid).toMatch(/^sha256:/);
|
|
1441
|
+
});
|
|
1442
|
+
|
|
1443
|
+
test("throws with the step prefix for authentication, permission, throttling and server failures", async () => {
|
|
1444
|
+
for (const status of [401, 403, 429, 500]) {
|
|
1445
|
+
const harness: Harness = buildHarness({
|
|
1446
|
+
responders: [
|
|
1447
|
+
(): DataSourceHttpResponse => {
|
|
1448
|
+
return oktaError(
|
|
1449
|
+
status,
|
|
1450
|
+
"E0000000",
|
|
1451
|
+
`rejected Authorization: SSWS ${API_TOKEN}`,
|
|
1452
|
+
);
|
|
1453
|
+
},
|
|
1454
|
+
],
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
let thrown: unknown = undefined;
|
|
1458
|
+
|
|
1459
|
+
try {
|
|
1460
|
+
await harness.connector.fetchEvents(
|
|
1461
|
+
settings(),
|
|
1462
|
+
{ startTime: START, endTime: END },
|
|
1463
|
+
fetchOptions(),
|
|
1464
|
+
);
|
|
1465
|
+
} catch (error) {
|
|
1466
|
+
thrown = error;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
expect(thrown).toBeInstanceOf(APIException);
|
|
1470
|
+
expect((thrown as APIException).message).toMatch(
|
|
1471
|
+
new RegExp(
|
|
1472
|
+
`^Okta System Log events request failed \\(HTTP ${status}\\): `,
|
|
1473
|
+
),
|
|
1474
|
+
);
|
|
1475
|
+
expect((thrown as APIException).message).not.toContain(API_TOKEN);
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1478
|
+
|
|
1479
|
+
test("throws with the step prefix when the transport times out", async () => {
|
|
1480
|
+
const harness: Harness = buildHarness({
|
|
1481
|
+
responders: [
|
|
1482
|
+
(): DataSourceHttpResponse => {
|
|
1483
|
+
throw new BadDataException(
|
|
1484
|
+
"Could not reach data source: timeout of 30000ms exceeded",
|
|
1485
|
+
);
|
|
1486
|
+
},
|
|
1487
|
+
],
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
await expect(
|
|
1491
|
+
harness.connector.fetchEvents(
|
|
1492
|
+
settings(),
|
|
1493
|
+
{ startTime: START, endTime: END },
|
|
1494
|
+
fetchOptions(),
|
|
1495
|
+
),
|
|
1496
|
+
).rejects.toThrow(
|
|
1497
|
+
"Okta System Log events request did not complete: Could not reach data source: timeout of 30000ms exceeded",
|
|
1498
|
+
);
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
test("throws when a 200 body is not a JSON array rather than advancing past it", async () => {
|
|
1502
|
+
const harness: Harness = buildHarness({
|
|
1503
|
+
responders: [
|
|
1504
|
+
(): DataSourceHttpResponse => {
|
|
1505
|
+
return text(200, "<html>Sign in</html>");
|
|
1506
|
+
},
|
|
1507
|
+
],
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
await expect(
|
|
1511
|
+
harness.connector.fetchEvents(
|
|
1512
|
+
settings(),
|
|
1513
|
+
{ startTime: START, endTime: END },
|
|
1514
|
+
fetchOptions(),
|
|
1515
|
+
),
|
|
1516
|
+
).rejects.toThrow(
|
|
1517
|
+
"Okta System Log events request returned a non-JSON body.",
|
|
1518
|
+
);
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
test("throws and stops when the next link points outside the org", async () => {
|
|
1522
|
+
const harness: Harness = buildHarness({
|
|
1523
|
+
responders: [
|
|
1524
|
+
(): DataSourceHttpResponse => {
|
|
1525
|
+
return page(
|
|
1526
|
+
[logEvent("A1", 0)],
|
|
1527
|
+
"https://evil.example.net/api/v1/logs?after=1",
|
|
1528
|
+
);
|
|
1529
|
+
},
|
|
1530
|
+
],
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
await expect(
|
|
1534
|
+
harness.connector.fetchEvents(
|
|
1535
|
+
settings(),
|
|
1536
|
+
{ startTime: START, endTime: END },
|
|
1537
|
+
fetchOptions(),
|
|
1538
|
+
),
|
|
1539
|
+
).rejects.toThrow(
|
|
1540
|
+
"Okta System Log events request returned a next link outside the Okta organization URL (evil.example.net); it was not followed.",
|
|
1541
|
+
);
|
|
1542
|
+
expect(harness.requests).toHaveLength(1);
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
test("throws a BadDataException for invalid settings before any request", async () => {
|
|
1546
|
+
const harness: Harness = buildHarness({});
|
|
1547
|
+
|
|
1548
|
+
await expect(
|
|
1549
|
+
harness.connector.fetchEvents(
|
|
1550
|
+
settings({ secrets: {} }),
|
|
1551
|
+
{ startTime: START, endTime: END },
|
|
1552
|
+
fetchOptions(),
|
|
1553
|
+
),
|
|
1554
|
+
).rejects.toBeInstanceOf(BadDataException);
|
|
1555
|
+
expect(harness.requests).toHaveLength(0);
|
|
1556
|
+
});
|
|
1557
|
+
});
|
|
1558
|
+
});
|