@oneuptime/common 12.0.28 → 12.0.29
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/DashboardDomain.ts +43 -0
- package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
- package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
- package/Server/API/DashboardDomainAPI.ts +74 -0
- package/Server/API/MicrosoftTeamsAPI.ts +81 -180
- package/Server/API/StatusPageDomainAPI.ts +74 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/ProjectAuthorization.ts +6 -1
- package/Server/Services/DashboardDomainService.ts +131 -0
- package/Server/Services/LogPipelineProcessorService.ts +88 -0
- package/Server/Services/MonitorTemplateService.ts +70 -11
- package/Server/Services/StatusPageDomainService.ts +163 -3
- package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
- package/Server/Types/Domain.ts +17 -0
- package/Server/Utils/APIKey/AccessPermission.ts +24 -0
- package/Server/Utils/Greenlock/Greenlock.ts +156 -58
- package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
- package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
- package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
- package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
- package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
- package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
- package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
- package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
- package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
- package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
- package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
- package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
- package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
- package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
- package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
- package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
- package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
- package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
- package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
- package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
- package/Tests/Types/API/HTTPResponse.test.ts +175 -0
- package/Tests/Types/API/Route.test.ts +36 -4
- package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
- package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
- package/Tests/Types/DateLocalTimeString.test.ts +259 -0
- package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
- package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
- package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
- package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
- package/Tests/UI/Utils/Navigation.test.ts +82 -0
- package/Tests/Utils/Array.test.ts +100 -0
- package/Tests/Utils/CertificateReissue.test.ts +225 -0
- package/Tests/Utils/Grok/Grok.test.ts +492 -0
- package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
- package/Types/API/Route.ts +20 -5
- package/Types/Date.ts +143 -13
- package/Types/Rum/SessionReplay.ts +62 -1
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
- package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
- package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
- package/UI/Utils/Navigation.ts +42 -0
- package/Utils/Array.ts +37 -0
- package/Utils/CertificateReissue.ts +128 -0
- package/Utils/Grok/Grok.ts +461 -0
- package/Utils/Grok/GrokPatterns.ts +118 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
- package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
- package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/DashboardDomainService.js +104 -0
- package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
- package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
- package/build/dist/Server/Types/Domain.js +16 -0
- package/build/dist/Server/Types/Domain.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
- package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Route.js +16 -5
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/Date.js +115 -13
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +38 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +30 -0
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/Utils/Array.js +25 -0
- package/build/dist/Utils/Array.js.map +1 -1
- package/build/dist/Utils/CertificateReissue.js +97 -0
- package/build/dist/Utils/CertificateReissue.js.map +1 -0
- package/build/dist/Utils/Grok/Grok.js +324 -0
- package/build/dist/Utils/Grok/Grok.js.map +1 -0
- package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
- package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/package.json +1 -1
- package/UI/Utils/JsonWebToken.ts +0 -14
- package/build/dist/UI/Utils/JsonWebToken.js +0 -10
- package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
|
@@ -16,7 +16,7 @@ import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
|
16
16
|
import ConnectorErrorMessage from "../ConnectorErrorMessage";
|
|
17
17
|
import { buildSecurityEventDbRow } from "../SecurityEventRow";
|
|
18
18
|
import ThreatIntelEnricher from "../ThreatIntel/ThreatIntelEnricher";
|
|
19
|
-
import GoogleSecOpsClient from "./GoogleSecOpsClient";
|
|
19
|
+
import GoogleSecOpsClient, { FetchAlertsResult } from "./GoogleSecOpsClient";
|
|
20
20
|
|
|
21
21
|
const SECOPS_SERVICE_NAME: string = "Google SecOps";
|
|
22
22
|
|
|
@@ -31,6 +31,10 @@ const MAX_LOOKBACK_IN_MINUTES: number = 24 * 60;
|
|
|
31
31
|
/*
|
|
32
32
|
* Overlap between windows so boundary alerts are never missed; the
|
|
33
33
|
* eventUid content hash keeps redelivered alerts deduplicatable.
|
|
34
|
+
*
|
|
35
|
+
* Still open (out of scope here): the overlap does produce genuine
|
|
36
|
+
* duplicate rows, because SecurityEvent is a plain MergeTree with
|
|
37
|
+
* eventUid outside the sort key, so nothing collapses them at write time.
|
|
34
38
|
*/
|
|
35
39
|
const WINDOW_OVERLAP_IN_MINUTES: number = 1;
|
|
36
40
|
|
|
@@ -80,7 +84,18 @@ export default class GoogleSecOpsPoller {
|
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
try {
|
|
83
|
-
await this.pollConnection(connection);
|
|
87
|
+
const ingested: number = await this.pollConnection(connection);
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
* The count used to be returned and dropped on the floor, which
|
|
91
|
+
* left "polling healthy but quiet" and "polling silently broken"
|
|
92
|
+
* looking identical from outside the process. At info rather than
|
|
93
|
+
* debug for that reason — a number nobody sees at the default log
|
|
94
|
+
* level answers the question no better than not having it.
|
|
95
|
+
*/
|
|
96
|
+
logger.info(
|
|
97
|
+
`GoogleSecOpsPoller: connection ${connection.id?.toString()} ingested ${ingested} security events.`,
|
|
98
|
+
);
|
|
84
99
|
} catch (error) {
|
|
85
100
|
logger.error(
|
|
86
101
|
`GoogleSecOpsPoller: error polling connection ${connection.id?.toString()}:`,
|
|
@@ -134,6 +149,8 @@ export default class GoogleSecOpsPoller {
|
|
|
134
149
|
);
|
|
135
150
|
}
|
|
136
151
|
|
|
152
|
+
const connectionId: string = connection.id.toString();
|
|
153
|
+
|
|
137
154
|
const client: GoogleSecOpsClient =
|
|
138
155
|
clientOverride ||
|
|
139
156
|
new GoogleSecOpsClient({
|
|
@@ -149,26 +166,96 @@ export default class GoogleSecOpsPoller {
|
|
|
149
166
|
-MAX_LOOKBACK_IN_MINUTES,
|
|
150
167
|
);
|
|
151
168
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
169
|
+
const defaultStartTime: Date = OneUptimeDate.addRemoveMinutes(
|
|
170
|
+
endTime,
|
|
171
|
+
-DEFAULT_LOOKBACK_IN_MINUTES,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
let startTime: Date = defaultStartTime;
|
|
175
|
+
|
|
176
|
+
if (connection.cursor) {
|
|
177
|
+
const cursorTime: Date = new Date(connection.cursor);
|
|
178
|
+
|
|
179
|
+
if (Number.isNaN(cursorTime.getTime())) {
|
|
180
|
+
/*
|
|
181
|
+
* An unreadable cursor used to share a branch with a stale one and
|
|
182
|
+
* open the full 24 hour window — the widest possible blast radius
|
|
183
|
+
* for the least trustworthy input. It means "no usable cursor",
|
|
184
|
+
* which is what a first poll means too.
|
|
185
|
+
*/
|
|
186
|
+
logger.warn(
|
|
187
|
+
`GoogleSecOpsPoller: connection ${connectionId} has an unreadable cursor ${JSON.stringify(
|
|
188
|
+
connection.cursor,
|
|
189
|
+
)}; polling the default ${DEFAULT_LOOKBACK_IN_MINUTES} minute window instead.`,
|
|
190
|
+
);
|
|
191
|
+
} else {
|
|
192
|
+
startTime = OneUptimeDate.addRemoveMinutes(
|
|
193
|
+
cursorTime,
|
|
155
194
|
-WINDOW_OVERLAP_IN_MINUTES,
|
|
156
|
-
)
|
|
157
|
-
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (OneUptimeDate.isBefore(startTime, earliestAllowed)) {
|
|
200
|
+
/*
|
|
201
|
+
* The alerts between the cursor and the cap are skipped and will
|
|
202
|
+
* never be fetched again, so the gap has to be stated. Silently
|
|
203
|
+
* truncating it is how a connector comes back from an outage
|
|
204
|
+
* looking healthy while a day of detections is simply missing.
|
|
205
|
+
*/
|
|
206
|
+
logger.warn(
|
|
207
|
+
`GoogleSecOpsPoller: connection ${connectionId} would poll from ${startTime.toISOString()}, ${OneUptimeDate.getMinutesBetweenTwoDates(
|
|
208
|
+
startTime,
|
|
209
|
+
endTime,
|
|
210
|
+
)} minutes back and past the ${MAX_LOOKBACK_IN_MINUTES} minute cap; alerts before ${earliestAllowed.toISOString()} are skipped and will not be fetched again.`,
|
|
211
|
+
);
|
|
158
212
|
|
|
159
|
-
if (
|
|
160
|
-
Number.isNaN(startTime.getTime()) ||
|
|
161
|
-
OneUptimeDate.isBefore(startTime, earliestAllowed)
|
|
162
|
-
) {
|
|
163
213
|
startTime = earliestAllowed;
|
|
164
214
|
}
|
|
165
215
|
|
|
166
|
-
|
|
216
|
+
if (!OneUptimeDate.isBefore(startTime, endTime)) {
|
|
217
|
+
/*
|
|
218
|
+
* A cursor in the future — clock skew on the writer, or a restored
|
|
219
|
+
* backup — used to send Chronicle an inverted timeRange, which is
|
|
220
|
+
* neither an error the operator sees nor a window that returns
|
|
221
|
+
* anything.
|
|
222
|
+
*/
|
|
223
|
+
logger.warn(
|
|
224
|
+
`GoogleSecOpsPoller: connection ${connectionId} cursor resolves to ${startTime.toISOString()}, at or after the current time; polling the default ${DEFAULT_LOOKBACK_IN_MINUTES} minute window instead.`,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
startTime = defaultStartTime;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const fetched: FetchAlertsResult = await client.fetchDetectionAlerts({
|
|
167
231
|
startTime,
|
|
168
232
|
endTime,
|
|
169
233
|
});
|
|
170
234
|
|
|
171
|
-
|
|
235
|
+
if (fetched.truncatedByCount) {
|
|
236
|
+
logger.warn(
|
|
237
|
+
`GoogleSecOpsPoller: connection ${connectionId} window ${startTime.toISOString()} to ${endTime.toISOString()} matched more alerts than Chronicle will return, and this endpoint has no pagination — alerts in this window were dropped. Lower pollIntervalInMinutes.`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (fetched.truncatedByBytes) {
|
|
242
|
+
logger.warn(
|
|
243
|
+
`GoogleSecOpsPoller: connection ${connectionId} window ${startTime.toISOString()} to ${endTime.toISOString()} was truncated by Chronicle's memory limit — alerts in this window were dropped. Lower pollIntervalInMinutes.`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const alerts: Array<JSONObject> = fetched.alerts;
|
|
248
|
+
|
|
249
|
+
const normalizedAlerts: Array<NormalizedSecurityEvent> = [];
|
|
250
|
+
|
|
251
|
+
/*
|
|
252
|
+
* Objects the alert gate refused, and objects that blew up on the way
|
|
253
|
+
* through it. They are kept apart because they mean opposite things
|
|
254
|
+
* for the cursor: a refusal is deterministic and re-fetching it would
|
|
255
|
+
* refuse it again forever, a failure may well succeed next time.
|
|
256
|
+
*/
|
|
257
|
+
let rejectedCount: number = 0;
|
|
258
|
+
let failedCount: number = 0;
|
|
172
259
|
|
|
173
260
|
if (alerts.length > 0) {
|
|
174
261
|
const serviceMetadata: TelemetryServiceMetadata =
|
|
@@ -185,17 +272,34 @@ export default class GoogleSecOpsPoller {
|
|
|
185
272
|
projectRetentionInDays: serviceMetadata.projectRetentionInDays,
|
|
186
273
|
});
|
|
187
274
|
|
|
188
|
-
const normalizedAlerts: Array<NormalizedSecurityEvent> = [];
|
|
189
|
-
|
|
190
275
|
for (const alert of alerts) {
|
|
191
276
|
try {
|
|
277
|
+
/*
|
|
278
|
+
* The normalizer is a total function whose class, category,
|
|
279
|
+
* vendor and product are constants, so anything handed to it
|
|
280
|
+
* comes back out as a plausible Detection Finding row. The gate
|
|
281
|
+
* is what keeps a stray envelope chunk from being stored as a
|
|
282
|
+
* severity-Unknown detection nobody can tell from a real one.
|
|
283
|
+
*/
|
|
284
|
+
if (!GoogleSecOpsAlertNormalizer.isGoogleSecOpsAlert(alert)) {
|
|
285
|
+
rejectedCount++;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
192
289
|
normalizedAlerts.push(GoogleSecOpsAlertNormalizer.normalize(alert));
|
|
193
290
|
} catch (normalizeError) {
|
|
291
|
+
failedCount++;
|
|
194
292
|
logger.error("GoogleSecOpsPoller: error normalizing alert");
|
|
195
293
|
logger.error(normalizeError);
|
|
196
294
|
}
|
|
197
295
|
}
|
|
198
296
|
|
|
297
|
+
if (rejectedCount > 0) {
|
|
298
|
+
logger.warn(
|
|
299
|
+
`GoogleSecOpsPoller: connection ${connectionId} discarded ${rejectedCount} of ${alerts.length} fetched objects that do not look like Google SecOps alerts.`,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
199
303
|
/*
|
|
200
304
|
* Threat-intel enrichment before row building, same seam as the
|
|
201
305
|
* HTTP ingest path — polled alerts get the same threat.* stamps.
|
|
@@ -218,15 +322,43 @@ export default class GoogleSecOpsPoller {
|
|
|
218
322
|
|
|
219
323
|
if (rows.length > 0) {
|
|
220
324
|
await SecurityEventService.insertJsonRows(rows);
|
|
221
|
-
ingested = rows.length;
|
|
222
325
|
}
|
|
223
326
|
}
|
|
224
327
|
|
|
328
|
+
const ingested: number = normalizedAlerts.length;
|
|
329
|
+
|
|
330
|
+
/*
|
|
331
|
+
* Nothing storable came out of a window that did contain alerts, and
|
|
332
|
+
* the reason was a failure rather than a refusal — so the alerts still
|
|
333
|
+
* exist at Chronicle and the window has to be re-fetched. Advancing
|
|
334
|
+
* here is what turned a normalizer fault into permanent, silent data
|
|
335
|
+
* loss: the insert-failure path leaves the cursor alone by throwing,
|
|
336
|
+
* and this path did not.
|
|
337
|
+
*/
|
|
338
|
+
const advanceCursor: boolean = ingested > 0 || failedCount === 0;
|
|
339
|
+
|
|
340
|
+
if (!advanceCursor) {
|
|
341
|
+
logger.warn(
|
|
342
|
+
`GoogleSecOpsPoller: connection ${connectionId} normalized none of its ${alerts.length} fetched alerts; holding the cursor so the window is polled again rather than skipped.`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const cursorUpdate: { cursor?: string } = advanceCursor
|
|
347
|
+
? {
|
|
348
|
+
/*
|
|
349
|
+
* Still open (out of scope here): this stores the end of the
|
|
350
|
+
* window rather than the newest detection timestamp actually
|
|
351
|
+
* ingested, which is what the column documents itself as.
|
|
352
|
+
*/
|
|
353
|
+
cursor: endTime.toISOString(),
|
|
354
|
+
}
|
|
355
|
+
: {};
|
|
356
|
+
|
|
225
357
|
await GoogleSecOpsConnectionService.updateOneById({
|
|
226
358
|
id: connection.id,
|
|
227
359
|
data: {
|
|
228
360
|
lastPolledAt: endTime,
|
|
229
|
-
|
|
361
|
+
...cursorUpdate,
|
|
230
362
|
lastError: null as unknown as string,
|
|
231
363
|
},
|
|
232
364
|
props: {
|
|
@@ -234,6 +366,10 @@ export default class GoogleSecOpsPoller {
|
|
|
234
366
|
},
|
|
235
367
|
});
|
|
236
368
|
|
|
369
|
+
logger.debug(
|
|
370
|
+
`GoogleSecOpsPoller: connection ${connectionId} fetched ${alerts.length} alerts and ingested ${ingested} security events for ${startTime.toISOString()} to ${endTime.toISOString()}.`,
|
|
371
|
+
);
|
|
372
|
+
|
|
237
373
|
return ingested;
|
|
238
374
|
}
|
|
239
375
|
}
|
|
@@ -18,6 +18,8 @@ import AlertStateService from "../../../../Services/AlertStateService";
|
|
|
18
18
|
import UserNotificationEventType from "../../../../../Types/UserNotification/UserNotificationEventType";
|
|
19
19
|
import OnCallDutyPolicy from "../../../../../Models/DatabaseModels/OnCallDutyPolicy";
|
|
20
20
|
import AlertState from "../../../../../Models/DatabaseModels/AlertState";
|
|
21
|
+
import DatabaseCommonInteractionProps from "../../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
22
|
+
import MicrosoftTeamsActionAuthorization from "./Authorization";
|
|
21
23
|
|
|
22
24
|
export default class MicrosoftTeamsAlertActions {
|
|
23
25
|
@CaptureSpan()
|
|
@@ -255,6 +257,7 @@ export default class MicrosoftTeamsAlertActions {
|
|
|
255
257
|
value: JSONObject;
|
|
256
258
|
projectId: ObjectID;
|
|
257
259
|
oneUptimeUserId: ObjectID;
|
|
260
|
+
databaseProps: DatabaseCommonInteractionProps;
|
|
258
261
|
turnContext: TurnContext;
|
|
259
262
|
}): Promise<void> {
|
|
260
263
|
const {
|
|
@@ -263,6 +266,7 @@ export default class MicrosoftTeamsAlertActions {
|
|
|
263
266
|
value,
|
|
264
267
|
projectId,
|
|
265
268
|
oneUptimeUserId,
|
|
269
|
+
databaseProps,
|
|
266
270
|
turnContext,
|
|
267
271
|
} = data;
|
|
268
272
|
|
|
@@ -274,10 +278,15 @@ export default class MicrosoftTeamsAlertActions {
|
|
|
274
278
|
return;
|
|
275
279
|
}
|
|
276
280
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
const alertId: ObjectID = new ObjectID(actionValue);
|
|
282
|
+
|
|
283
|
+
await MicrosoftTeamsActionAuthorization.assertCanUpdateAlert({
|
|
284
|
+
alertId: alertId,
|
|
285
|
+
projectId: projectId,
|
|
286
|
+
props: databaseProps,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
await AlertService.acknowledgeAlert(alertId, oneUptimeUserId);
|
|
281
290
|
await turnContext.sendActivity("✅ Alert acknowledged.");
|
|
282
291
|
return;
|
|
283
292
|
}
|
|
@@ -288,10 +297,15 @@ export default class MicrosoftTeamsAlertActions {
|
|
|
288
297
|
return;
|
|
289
298
|
}
|
|
290
299
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
300
|
+
const alertId: ObjectID = new ObjectID(actionValue);
|
|
301
|
+
|
|
302
|
+
await MicrosoftTeamsActionAuthorization.assertCanUpdateAlert({
|
|
303
|
+
alertId: alertId,
|
|
304
|
+
projectId: projectId,
|
|
305
|
+
props: databaseProps,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
await AlertService.resolveAlert(alertId, oneUptimeUserId);
|
|
295
309
|
await turnContext.sendActivity("✅ Alert resolved.");
|
|
296
310
|
return;
|
|
297
311
|
}
|
|
@@ -506,9 +520,7 @@ export default class MicrosoftTeamsAlertActions {
|
|
|
506
520
|
data: {
|
|
507
521
|
currentAlertStateId: new ObjectID(alertStateId.toString()),
|
|
508
522
|
},
|
|
509
|
-
props:
|
|
510
|
-
isRoot: true,
|
|
511
|
-
},
|
|
523
|
+
props: databaseProps,
|
|
512
524
|
});
|
|
513
525
|
|
|
514
526
|
await turnContext.sendActivity("✅ Alert state changed successfully.");
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import { ExpressRequest } from "../../../Express";
|
|
2
1
|
import { JSONObject } from "../../../../../Types/JSON";
|
|
3
2
|
import ObjectID from "../../../../../Types/ObjectID";
|
|
4
|
-
import WorkspaceProjectAuthToken from "../../../../../Models/DatabaseModels/WorkspaceProjectAuthToken";
|
|
5
|
-
import WorkspaceProjectAuthTokenService from "../../../../Services/WorkspaceProjectAuthTokenService";
|
|
6
3
|
import WorkspaceType from "../../../../../Types/Workspace/WorkspaceType";
|
|
7
4
|
import logger from "../../../Logger";
|
|
8
5
|
import CaptureSpan from "../../../Telemetry/CaptureSpan";
|
|
@@ -30,173 +27,6 @@ export interface MicrosoftTeamsRequest {
|
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
export default class MicrosoftTeamsAuthAction {
|
|
33
|
-
@CaptureSpan()
|
|
34
|
-
public static async isAuthorized(data: {
|
|
35
|
-
req: ExpressRequest;
|
|
36
|
-
}): Promise<MicrosoftTeamsRequest> {
|
|
37
|
-
const req: ExpressRequest = data.req;
|
|
38
|
-
let payload: JSONObject = {};
|
|
39
|
-
let tenantId: string = "";
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
payload = req.body as JSONObject;
|
|
43
|
-
logger.debug("Microsoft Teams payload received:");
|
|
44
|
-
logger.debug(payload);
|
|
45
|
-
} catch (error) {
|
|
46
|
-
logger.debug("Failed to parse Microsoft Teams payload:");
|
|
47
|
-
logger.debug(error);
|
|
48
|
-
return {
|
|
49
|
-
isAuthorized: false,
|
|
50
|
-
projectId: new ObjectID(""),
|
|
51
|
-
authToken: "",
|
|
52
|
-
payloadType: "unknown",
|
|
53
|
-
tenantId: tenantId,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Microsoft Teams sends different types of activities
|
|
58
|
-
const activityType: string = payload["type"] as string;
|
|
59
|
-
const from: JSONObject = payload["from"] as JSONObject;
|
|
60
|
-
const conversation: JSONObject = payload["conversation"] as JSONObject;
|
|
61
|
-
const channelData: JSONObject = payload["channelData"] as JSONObject;
|
|
62
|
-
|
|
63
|
-
let teamId: string = "";
|
|
64
|
-
let channelId: string = "";
|
|
65
|
-
let userId: string = "";
|
|
66
|
-
|
|
67
|
-
// Extract team and channel information
|
|
68
|
-
if (channelData && channelData["team"]) {
|
|
69
|
-
teamId = (channelData["team"] as JSONObject)["id"] as string;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (conversation && conversation["id"]) {
|
|
73
|
-
// For channel messages, conversation ID contains channel info
|
|
74
|
-
channelId = conversation["id"] as string;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (from && from["id"]) {
|
|
78
|
-
userId = from["id"] as string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (channelData && channelData["tenant"]) {
|
|
82
|
-
tenantId = (channelData["tenant"] as JSONObject)["id"] as string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Handle different activity types
|
|
86
|
-
if (activityType === "installationUpdate") {
|
|
87
|
-
// Bot was installed or uninstalled
|
|
88
|
-
const action: string = payload["action"] as string;
|
|
89
|
-
if (action === "remove") {
|
|
90
|
-
return {
|
|
91
|
-
isAuthorized: true,
|
|
92
|
-
projectId: new ObjectID(""), // We'll need to find the project
|
|
93
|
-
authToken: "",
|
|
94
|
-
payloadType: "app_uninstall",
|
|
95
|
-
teamId: teamId,
|
|
96
|
-
tenantId: tenantId,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Find the project associated with this team
|
|
102
|
-
if (!teamId) {
|
|
103
|
-
logger.debug("No team ID found in payload", {
|
|
104
|
-
tenantId: tenantId,
|
|
105
|
-
});
|
|
106
|
-
return {
|
|
107
|
-
isAuthorized: false,
|
|
108
|
-
projectId: new ObjectID(""),
|
|
109
|
-
authToken: "",
|
|
110
|
-
payloadType: activityType,
|
|
111
|
-
tenantId: tenantId,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
let projectAuthToken: WorkspaceProjectAuthToken | null = null;
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
// Find project auth token by checking if miscData contains the teamId
|
|
119
|
-
projectAuthToken = await WorkspaceProjectAuthTokenService.findOneBy({
|
|
120
|
-
query: {
|
|
121
|
-
workspaceType: WorkspaceType.MicrosoftTeams,
|
|
122
|
-
workspaceProjectId: tenantId,
|
|
123
|
-
},
|
|
124
|
-
select: {
|
|
125
|
-
_id: true,
|
|
126
|
-
projectId: true,
|
|
127
|
-
authToken: true,
|
|
128
|
-
miscData: true,
|
|
129
|
-
},
|
|
130
|
-
props: {
|
|
131
|
-
isRoot: true,
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
} catch (error) {
|
|
135
|
-
logger.debug("Error finding project auth token:", {
|
|
136
|
-
teamId: teamId,
|
|
137
|
-
tenantId: tenantId,
|
|
138
|
-
});
|
|
139
|
-
logger.debug(error);
|
|
140
|
-
return {
|
|
141
|
-
isAuthorized: false,
|
|
142
|
-
projectId: new ObjectID(""),
|
|
143
|
-
authToken: "",
|
|
144
|
-
payloadType: activityType,
|
|
145
|
-
tenantId: tenantId,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (!projectAuthToken) {
|
|
150
|
-
logger.debug(`No project auth token found for team: ${teamId}`, {
|
|
151
|
-
teamId: teamId,
|
|
152
|
-
tenantId: tenantId,
|
|
153
|
-
});
|
|
154
|
-
return {
|
|
155
|
-
isAuthorized: false,
|
|
156
|
-
projectId: new ObjectID(""),
|
|
157
|
-
authToken: "",
|
|
158
|
-
payloadType: activityType,
|
|
159
|
-
tenantId: tenantId,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Parse actions from the payload
|
|
164
|
-
const actions: Array<MicrosoftTeamsAction> = [];
|
|
165
|
-
|
|
166
|
-
if (activityType === "invoke") {
|
|
167
|
-
// Handle adaptive card button clicks
|
|
168
|
-
const value: JSONObject = payload["value"] as JSONObject;
|
|
169
|
-
if (value && value["action"]) {
|
|
170
|
-
actions.push({
|
|
171
|
-
actionType: value["action"] as string,
|
|
172
|
-
actionValue: value["actionValue"] as string,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
} else if (activityType === "message") {
|
|
176
|
-
// Handle text messages to the bot
|
|
177
|
-
const text: string = (payload["text"] as string) || "";
|
|
178
|
-
if (text.toLowerCase().includes("help")) {
|
|
179
|
-
actions.push({
|
|
180
|
-
actionType: "help",
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
isAuthorized: true,
|
|
187
|
-
projectId: projectAuthToken.projectId!,
|
|
188
|
-
authToken: projectAuthToken.authToken!,
|
|
189
|
-
payloadType: activityType,
|
|
190
|
-
actions: actions,
|
|
191
|
-
userId: userId,
|
|
192
|
-
teamId: teamId,
|
|
193
|
-
tenantId: tenantId,
|
|
194
|
-
channelId: channelId,
|
|
195
|
-
messageId: payload["id"] as string,
|
|
196
|
-
payload: payload,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
30
|
@CaptureSpan()
|
|
201
31
|
public static async getOneUptimeUserIdFromTeamsUserId(data: {
|
|
202
32
|
teamsUserId: string;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import Alert from "../../../../../Models/DatabaseModels/Alert";
|
|
2
|
+
import Incident from "../../../../../Models/DatabaseModels/Incident";
|
|
3
|
+
import DatabaseCommonInteractionProps from "../../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
4
|
+
import NotAuthorizedException from "../../../../../Types/Exception/NotAuthorizedException";
|
|
5
|
+
import ObjectID from "../../../../../Types/ObjectID";
|
|
6
|
+
import AlertService from "../../../../Services/AlertService";
|
|
7
|
+
import IncidentService from "../../../../Services/IncidentService";
|
|
8
|
+
import ModelPermission from "../../../../Types/Database/Permissions/Index";
|
|
9
|
+
import Query from "../../../../Types/Database/Query";
|
|
10
|
+
import { applyAlertSelfPrivacyFilter } from "../../../Alert/AlertPrivacyFilter";
|
|
11
|
+
import { applyIncidentSelfPrivacyFilter } from "../../../Incident/IncidentPrivacyFilter";
|
|
12
|
+
import CaptureSpan from "../../../Telemetry/CaptureSpan";
|
|
13
|
+
|
|
14
|
+
export default class MicrosoftTeamsActionAuthorization {
|
|
15
|
+
@CaptureSpan()
|
|
16
|
+
public static async assertCanUpdateIncident(data: {
|
|
17
|
+
incidentId: ObjectID;
|
|
18
|
+
projectId: ObjectID;
|
|
19
|
+
props: DatabaseCommonInteractionProps;
|
|
20
|
+
}): Promise<void> {
|
|
21
|
+
const incident: Incident | null = await IncidentService.findOneBy({
|
|
22
|
+
query: {
|
|
23
|
+
_id: data.incidentId,
|
|
24
|
+
projectId: data.projectId,
|
|
25
|
+
},
|
|
26
|
+
select: {
|
|
27
|
+
_id: true,
|
|
28
|
+
labels: {
|
|
29
|
+
_id: true,
|
|
30
|
+
name: true,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
props: {
|
|
34
|
+
isRoot: true,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!incident) {
|
|
39
|
+
throw new NotAuthorizedException(
|
|
40
|
+
"You do not have permission to update this incident.",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await ModelPermission.checkUpdatePermissionByModel({
|
|
45
|
+
modelType: Incident,
|
|
46
|
+
fetchModelWithAccessControlIds: async (): Promise<Incident> => {
|
|
47
|
+
return incident;
|
|
48
|
+
},
|
|
49
|
+
props: data.props,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const permittedQuery: Query<Incident> =
|
|
53
|
+
await ModelPermission.checkUpdateQueryPermissions(
|
|
54
|
+
Incident,
|
|
55
|
+
{
|
|
56
|
+
_id: data.incidentId,
|
|
57
|
+
projectId: data.projectId,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
currentIncidentStateId: ObjectID.getZeroObjectID(),
|
|
61
|
+
},
|
|
62
|
+
data.props,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const privacyScopedQuery: Query<Incident> = applyIncidentSelfPrivacyFilter(
|
|
66
|
+
permittedQuery,
|
|
67
|
+
data.props,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const permittedIncident: Incident | null = await IncidentService.findOneBy({
|
|
71
|
+
query: privacyScopedQuery,
|
|
72
|
+
select: {
|
|
73
|
+
_id: true,
|
|
74
|
+
},
|
|
75
|
+
props: {
|
|
76
|
+
isRoot: true,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!permittedIncident) {
|
|
81
|
+
throw new NotAuthorizedException(
|
|
82
|
+
"You do not have permission to update this incident.",
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@CaptureSpan()
|
|
88
|
+
public static async assertCanUpdateAlert(data: {
|
|
89
|
+
alertId: ObjectID;
|
|
90
|
+
projectId: ObjectID;
|
|
91
|
+
props: DatabaseCommonInteractionProps;
|
|
92
|
+
}): Promise<void> {
|
|
93
|
+
const alert: Alert | null = await AlertService.findOneBy({
|
|
94
|
+
query: {
|
|
95
|
+
_id: data.alertId,
|
|
96
|
+
projectId: data.projectId,
|
|
97
|
+
},
|
|
98
|
+
select: {
|
|
99
|
+
_id: true,
|
|
100
|
+
labels: {
|
|
101
|
+
_id: true,
|
|
102
|
+
name: true,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
props: {
|
|
106
|
+
isRoot: true,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!alert) {
|
|
111
|
+
throw new NotAuthorizedException(
|
|
112
|
+
"You do not have permission to update this alert.",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await ModelPermission.checkUpdatePermissionByModel({
|
|
117
|
+
modelType: Alert,
|
|
118
|
+
fetchModelWithAccessControlIds: async (): Promise<Alert> => {
|
|
119
|
+
return alert;
|
|
120
|
+
},
|
|
121
|
+
props: data.props,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const permittedQuery: Query<Alert> =
|
|
125
|
+
await ModelPermission.checkUpdateQueryPermissions(
|
|
126
|
+
Alert,
|
|
127
|
+
{
|
|
128
|
+
_id: data.alertId,
|
|
129
|
+
projectId: data.projectId,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
currentAlertStateId: ObjectID.getZeroObjectID(),
|
|
133
|
+
},
|
|
134
|
+
data.props,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const privacyScopedQuery: Query<Alert> = applyAlertSelfPrivacyFilter(
|
|
138
|
+
permittedQuery,
|
|
139
|
+
data.props,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const permittedAlert: Alert | null = await AlertService.findOneBy({
|
|
143
|
+
query: privacyScopedQuery,
|
|
144
|
+
select: {
|
|
145
|
+
_id: true,
|
|
146
|
+
},
|
|
147
|
+
props: {
|
|
148
|
+
isRoot: true,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (!permittedAlert) {
|
|
153
|
+
throw new NotAuthorizedException(
|
|
154
|
+
"You do not have permission to update this alert.",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|