@oneuptime/common 12.0.11 → 12.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
- package/Models/DatabaseModels/NetworkDevice.ts +64 -0
- package/Models/DatabaseModels/StatusPage.ts +59 -0
- package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
- package/Server/API/AIChatAPI.ts +55 -56
- package/Server/API/CommonAPI.ts +89 -0
- package/Server/API/MicrosoftTeamsAPI.ts +20 -9
- package/Server/API/SlackAPI.ts +13 -10
- package/Server/API/StatusPageAPI.ts +2197 -2128
- package/Server/API/TelemetryAPI.ts +72 -3
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/UserAuthorization.ts +11 -2
- package/Server/Services/ApiKeyPermissionService.ts +116 -2
- package/Server/Services/DatabaseService.ts +17 -6
- package/Server/Services/LogAggregationService.ts +34 -0
- package/Server/Services/LogService.ts +21 -0
- package/Server/Services/NetworkSiteService.ts +12 -0
- package/Server/Services/SpanService.ts +21 -0
- package/Server/Services/TraceAggregationService.ts +15 -0
- package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
- package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
- package/Server/Types/Workflow/Components/Conditions/IfElse.ts +9 -0
- package/Server/Types/Workflow/Components/JavaScript.ts +9 -0
- package/Server/Utils/APIKey/AccessPermission.ts +16 -40
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
- package/Server/Utils/LogRedaction.ts +576 -0
- package/Server/Utils/Logger.ts +123 -46
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +12 -0
- package/Server/Utils/Monitor/MonitorResource.ts +29 -9
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
- package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
- package/Server/Utils/StartServer.ts +30 -9
- package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
- package/Server/Utils/VM/VMRunner.ts +693 -874
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
- package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
- package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
- package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
- package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
- package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
- package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
- package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
- package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
- package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
- package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
- package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
- package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
- package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
- package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
- package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
- package/Tests/Server/Types/Workflow/Components/CustomCodeScriptError.test.ts +121 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
- package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
- package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
- package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorJavascriptExpression.test.ts +92 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
- package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
- package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
- package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
- package/Tests/Server/Utils/VM/VMRunnerIsolation.test.ts +385 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +3 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
- package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
- package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
- package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
- package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
- package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
- package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
- package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
- package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
- package/Tests/UI/Components/MasterPage.test.tsx +36 -1
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
- package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
- package/Types/API/Hostname.ts +79 -0
- package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
- package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
- package/Types/Database/DatabaseProperty.ts +38 -0
- package/Types/Database/TableColumn.ts +34 -7
- package/Types/Events/Recurring.ts +16 -1
- package/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse.ts +14 -2
- package/Types/Monitor/MonitorStep.ts +13 -1
- package/Types/Monitor/MonitorType.ts +345 -40
- package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
- package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
- package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
- package/UI/Components/CardSelect/CardSelect.tsx +714 -105
- package/UI/Components/EmptyState/EmptyState.tsx +8 -1
- package/UI/Components/Footer/Footer.tsx +32 -6
- package/UI/Components/Forms/Fields/FormField.tsx +3 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Loader/PageLoader.tsx +29 -8
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
- package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
- package/UI/Components/MasterPage/MasterPage.tsx +24 -11
- package/Utils/Monitor/MonitorMetricType.ts +16 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
- package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
- package/Utils/Telemetry/CrossSignalScope.ts +75 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +48 -35
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +59 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +13 -10
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +745 -673
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +34 -3
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.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/UserAuthorization.js +6 -2
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +15 -6
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +2 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/LogService.js +16 -0
- package/build/dist/Server/Services/LogService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +12 -0
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/SpanService.js +16 -0
- package/build/dist/Server/Services/SpanService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +2 -0
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.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/Types/Database/Permissions/AccessControlPermission.js +21 -13
- package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +8 -0
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js +8 -0
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/LogRedaction.js +449 -0
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
- package/build/dist/Server/Utils/Logger.js +98 -45
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +12 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +10 -7
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMRunner.js +402 -525
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Hostname.js +63 -0
- package/build/dist/Types/API/Hostname.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
- package/build/dist/Types/Database/DatabaseProperty.js +38 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js +24 -6
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +13 -1
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +9 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +330 -31
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
- package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
- package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
- package/build/dist/UI/Components/Footer/Footer.js +7 -4
- package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
- package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/package.json +1 -1
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
ConfigurationBotFrameworkAuthentication,
|
|
73
73
|
TeamsActivityHandler,
|
|
74
74
|
TeamsInfo,
|
|
75
|
+
TeamDetails,
|
|
75
76
|
TeamsChannelAccount,
|
|
76
77
|
TeamsPagedMembersResult,
|
|
77
78
|
TurnContext,
|
|
@@ -125,6 +126,20 @@ export interface MicrosoftTeamsTenantResolution {
|
|
|
125
126
|
candidateProjectIds: Array<ObjectID>;
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
/*
|
|
130
|
+
* Whether the OneUptime Teams app is installed in a given team.
|
|
131
|
+
*
|
|
132
|
+
* Unknown is a first-class outcome, not an error: checking installation needs a
|
|
133
|
+
* Graph permission that deployments connected before it was documented have not
|
|
134
|
+
* consented to, and a tenant that answers "I won't tell you" must never be
|
|
135
|
+
* reported to an admin as "the app is not installed".
|
|
136
|
+
*/
|
|
137
|
+
export enum MicrosoftTeamsAppInstallState {
|
|
138
|
+
Installed = "Installed",
|
|
139
|
+
NotInstalled = "NotInstalled",
|
|
140
|
+
Unknown = "Unknown",
|
|
141
|
+
}
|
|
142
|
+
|
|
128
143
|
/*
|
|
129
144
|
* Microsoft's wording when the Bot Framework refuses a proactive post because
|
|
130
145
|
* the app is not a member of the target conversation. Matched case-insensitively
|
|
@@ -1443,6 +1458,14 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
1443
1458
|
logger.debug(`Team ID: ${data.teamId}`);
|
|
1444
1459
|
logger.debug(`Adaptive card: ${JSON.stringify(data.adaptiveCard)}`);
|
|
1445
1460
|
|
|
1461
|
+
/*
|
|
1462
|
+
* Declared out here so the catch block can tell a rejection we predicted
|
|
1463
|
+
* from one we could not, and word the error accordingly.
|
|
1464
|
+
*/
|
|
1465
|
+
let installState: MicrosoftTeamsAppInstallState =
|
|
1466
|
+
MicrosoftTeamsAppInstallState.Unknown;
|
|
1467
|
+
let installStateCameFromGraph: boolean = false;
|
|
1468
|
+
|
|
1446
1469
|
try {
|
|
1447
1470
|
// Get project auth to retrieve bot ID
|
|
1448
1471
|
const projectAuth: WorkspaceProjectAuthToken | null =
|
|
@@ -1493,30 +1516,42 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
1493
1516
|
}
|
|
1494
1517
|
|
|
1495
1518
|
/*
|
|
1496
|
-
* Preflight
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1499
|
-
* regardless of installation — so reaching this point proves nothing
|
|
1500
|
-
* about whether we can actually post.
|
|
1519
|
+
* Preflight. Channels are discovered with tenant-wide Graph application
|
|
1520
|
+
* permissions, which see every team regardless of installation — so
|
|
1521
|
+
* reaching this point proves nothing about whether we can actually post.
|
|
1501
1522
|
*
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1523
|
+
* An install we recorded from a bot activity is the cheap positive
|
|
1524
|
+
* signal, and it also carries the serviceUrl this send needs. When we
|
|
1525
|
+
* have no record we ask Graph rather than assuming: absence from
|
|
1526
|
+
* installedTeams used to be reported to admins as "the app is not
|
|
1527
|
+
* installed", which is a claim install events alone cannot support (they
|
|
1528
|
+
* only arrive for installs that happened while OneUptime was reachable).
|
|
1529
|
+
* Only a verified negative refuses the send; anything we cannot verify
|
|
1530
|
+
* goes to Microsoft, which is the real authority.
|
|
1507
1531
|
*/
|
|
1508
|
-
const installedTeams: Record<string, MicrosoftTeamsInstalledTeam> =
|
|
1509
|
-
miscData.installedTeams || {};
|
|
1510
1532
|
const installedTeam: MicrosoftTeamsInstalledTeam | undefined =
|
|
1511
|
-
installedTeams[
|
|
1533
|
+
this.indexInstalledTeamsByGraphTeamId(miscData.installedTeams)[
|
|
1534
|
+
data.teamId
|
|
1535
|
+
];
|
|
1512
1536
|
|
|
1513
|
-
if (
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1537
|
+
if (installedTeam) {
|
|
1538
|
+
installState = MicrosoftTeamsAppInstallState.Installed;
|
|
1539
|
+
} else {
|
|
1540
|
+
installState = await this.isAppInstalledInTeam({
|
|
1541
|
+
authToken: data.authToken,
|
|
1542
|
+
projectId: data.projectId,
|
|
1543
|
+
teamId: data.teamId,
|
|
1544
|
+
});
|
|
1545
|
+
installStateCameFromGraph = true;
|
|
1546
|
+
|
|
1547
|
+
if (installState === MicrosoftTeamsAppInstallState.NotInstalled) {
|
|
1548
|
+
throw new BadDataException(
|
|
1549
|
+
this.getBotNotInTeamMessage({
|
|
1550
|
+
channelName: data.workspaceChannel.name,
|
|
1551
|
+
membershipType: data.workspaceChannel.membershipType,
|
|
1552
|
+
}),
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1520
1555
|
}
|
|
1521
1556
|
|
|
1522
1557
|
// Get Bot Framework adapter
|
|
@@ -1592,14 +1627,47 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
1592
1627
|
|
|
1593
1628
|
/*
|
|
1594
1629
|
* Microsoft's roster rejection is meaningless to an admin ("The bot is
|
|
1595
|
-
* not part of the conversation roster"). Replace it with
|
|
1596
|
-
*
|
|
1630
|
+
* not part of the conversation roster"). Replace it with something
|
|
1631
|
+
* actionable — but only claim the app is missing from the team when we
|
|
1632
|
+
* checked and it is. Otherwise the app can be installed exactly as
|
|
1633
|
+
* documented and still be told to install it, which is how this error
|
|
1634
|
+
* sent admins in circles.
|
|
1597
1635
|
*/
|
|
1598
1636
|
if (this.isBotNotInConversationRosterError(error)) {
|
|
1637
|
+
/*
|
|
1638
|
+
* A local install record got us past the preflight without asking
|
|
1639
|
+
* Graph, and it can be stale — the app may have been removed from the
|
|
1640
|
+
* team since, and an uninstall event only reaches us if OneUptime was
|
|
1641
|
+
* reachable at the time. So confirm before wording the error. This is
|
|
1642
|
+
* the failure path, so the extra call costs nothing in normal operation.
|
|
1643
|
+
*/
|
|
1644
|
+
let verifiedInstallState: MicrosoftTeamsAppInstallState = installState;
|
|
1645
|
+
|
|
1646
|
+
if (!installStateCameFromGraph) {
|
|
1647
|
+
verifiedInstallState = await this.isAppInstalledInTeam({
|
|
1648
|
+
authToken: data.authToken,
|
|
1649
|
+
projectId: data.projectId,
|
|
1650
|
+
teamId: data.teamId,
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
if (
|
|
1655
|
+
verifiedInstallState === MicrosoftTeamsAppInstallState.NotInstalled
|
|
1656
|
+
) {
|
|
1657
|
+
throw new BadDataException(
|
|
1658
|
+
this.getBotNotInTeamMessage({
|
|
1659
|
+
channelName: data.workspaceChannel.name,
|
|
1660
|
+
membershipType: data.workspaceChannel.membershipType,
|
|
1661
|
+
}),
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1599
1665
|
throw new BadDataException(
|
|
1600
|
-
this.
|
|
1666
|
+
this.getRosterRejectionMessage({
|
|
1601
1667
|
channelName: data.workspaceChannel.name,
|
|
1602
1668
|
membershipType: data.workspaceChannel.membershipType,
|
|
1669
|
+
installState: verifiedInstallState,
|
|
1670
|
+
microsoftError: error,
|
|
1603
1671
|
}),
|
|
1604
1672
|
);
|
|
1605
1673
|
}
|
|
@@ -1642,6 +1710,138 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
1642
1710
|
return `The OneUptime app is not installed in the Microsoft Teams team that owns "${data.channelName}". In Microsoft Teams, click the "..." next to the team name, then Manage team > Apps > More apps, and add OneUptime. Installing OneUptime for yourself or in a chat is not the same as adding it to the team.`;
|
|
1643
1711
|
}
|
|
1644
1712
|
|
|
1713
|
+
/*
|
|
1714
|
+
* Microsoft rejected the post and we could not confirm why. Everything here
|
|
1715
|
+
* is a real cause of that rejection for an app that IS present in the team's
|
|
1716
|
+
* app list, which is the case getBotNotInTeamMessage got wrong: the app tile
|
|
1717
|
+
* showing up in Manage team > Apps does not mean the bot behind it is the one
|
|
1718
|
+
* this deployment authenticates as. Microsoft's own wording is included so
|
|
1719
|
+
* the raw error is not lost in the rewrite.
|
|
1720
|
+
*/
|
|
1721
|
+
public static getRosterRejectionMessage(data: {
|
|
1722
|
+
channelName: string;
|
|
1723
|
+
membershipType?: string | undefined;
|
|
1724
|
+
installState: MicrosoftTeamsAppInstallState;
|
|
1725
|
+
microsoftError?: unknown;
|
|
1726
|
+
}): string {
|
|
1727
|
+
const causes: Array<string> = [];
|
|
1728
|
+
|
|
1729
|
+
if (data.membershipType === "private") {
|
|
1730
|
+
causes.push(
|
|
1731
|
+
`"${data.channelName}" is a private channel, which needs the app installed into the channel itself (channel "..." > Manage channel > Apps > Add an app) — a team-level install does not cover it`,
|
|
1732
|
+
);
|
|
1733
|
+
} else if (data.installState === MicrosoftTeamsAppInstallState.Installed) {
|
|
1734
|
+
causes.push(
|
|
1735
|
+
"the app is installed in this team, so the bot behind it may not be the one this OneUptime deployment uses — check that the Teams app package was built from this deployment (Project Settings > Workspace > Microsoft Teams) and that its bot id matches MICROSOFT_TEAMS_APP_CLIENT_ID",
|
|
1736
|
+
);
|
|
1737
|
+
} else {
|
|
1738
|
+
causes.push(
|
|
1739
|
+
'the OneUptime app has not been added to this team (team "..." > Manage team > Apps > More apps), or the app that was added is a different package whose bot id is not this deployment\'s MICROSOFT_TEAMS_APP_CLIENT_ID',
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
causes.push(
|
|
1744
|
+
"the Azure Bot resource for this deployment does not have the Microsoft Teams channel enabled",
|
|
1745
|
+
);
|
|
1746
|
+
|
|
1747
|
+
const microsoftMessage: string =
|
|
1748
|
+
data.microsoftError instanceof Error
|
|
1749
|
+
? data.microsoftError.message
|
|
1750
|
+
: String(data.microsoftError || "");
|
|
1751
|
+
|
|
1752
|
+
const suffix: string = microsoftMessage
|
|
1753
|
+
? ` Microsoft's response was: "${microsoftMessage}"`
|
|
1754
|
+
: "";
|
|
1755
|
+
|
|
1756
|
+
return `Microsoft Teams refused the message to "${data.channelName}" because the OneUptime bot is not a member of that conversation. Likely causes: ${causes.join("; ")}.${suffix}`;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/*
|
|
1760
|
+
* Asks Graph whether this deployment's Teams app is installed in a team.
|
|
1761
|
+
*
|
|
1762
|
+
* Matching is on teamsApp.externalId, which is the manifest id — and the
|
|
1763
|
+
* manifest OneUptime generates sets both that and the bot id to
|
|
1764
|
+
* MICROSOFT_TEAMS_APP_CLIENT_ID. So this answers the question that actually
|
|
1765
|
+
* matters ("is the app THIS deployment authenticates as installed here?"),
|
|
1766
|
+
* not the weaker one an admin can answer by eye ("is something called
|
|
1767
|
+
* OneUptime in the app list?").
|
|
1768
|
+
*
|
|
1769
|
+
* Every failure is Unknown. The call needs TeamsAppInstallation.ReadForTeam.All,
|
|
1770
|
+
* which deployments connected before it was documented have not granted, and a
|
|
1771
|
+
* missing permission must not be reported as a missing install.
|
|
1772
|
+
*/
|
|
1773
|
+
@CaptureSpan()
|
|
1774
|
+
public static async isAppInstalledInTeam(data: {
|
|
1775
|
+
authToken: string;
|
|
1776
|
+
projectId: ObjectID;
|
|
1777
|
+
teamId: string;
|
|
1778
|
+
}): Promise<MicrosoftTeamsAppInstallState> {
|
|
1779
|
+
if (!MicrosoftTeamsAppClientId) {
|
|
1780
|
+
return MicrosoftTeamsAppInstallState.Unknown;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
try {
|
|
1784
|
+
const accessToken: string = await this.getValidAccessToken({
|
|
1785
|
+
authToken: data.authToken,
|
|
1786
|
+
projectId: data.projectId,
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
let nextLink: string | null =
|
|
1790
|
+
`https://graph.microsoft.com/v1.0/teams/${data.teamId}/installedApps?$expand=teamsApp`;
|
|
1791
|
+
let pageCount: number = 0;
|
|
1792
|
+
|
|
1793
|
+
while (nextLink) {
|
|
1794
|
+
pageCount++;
|
|
1795
|
+
|
|
1796
|
+
if (pageCount > MICROSOFT_TEAMS_MAX_PAGES) {
|
|
1797
|
+
logger.warn(
|
|
1798
|
+
`Stopped paginating installed apps for team ${data.teamId} after ${MICROSOFT_TEAMS_MAX_PAGES} pages.`,
|
|
1799
|
+
);
|
|
1800
|
+
return MicrosoftTeamsAppInstallState.Unknown;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
|
1804
|
+
await API.get({
|
|
1805
|
+
url: URL.fromString(nextLink),
|
|
1806
|
+
headers: {
|
|
1807
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1808
|
+
"Content-Type": "application/json",
|
|
1809
|
+
},
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
if (response instanceof HTTPErrorResponse) {
|
|
1813
|
+
logger.debug(
|
|
1814
|
+
`Could not read installed apps for team ${data.teamId}; treating installation as unknown.`,
|
|
1815
|
+
);
|
|
1816
|
+
logger.debug(response);
|
|
1817
|
+
return MicrosoftTeamsAppInstallState.Unknown;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
const installedApps: Array<JSONObject> =
|
|
1821
|
+
(response.data["value"] as Array<JSONObject>) || [];
|
|
1822
|
+
|
|
1823
|
+
for (const installedApp of installedApps) {
|
|
1824
|
+
const teamsApp: JSONObject =
|
|
1825
|
+
(installedApp["teamsApp"] as JSONObject) || {};
|
|
1826
|
+
|
|
1827
|
+
if (teamsApp["externalId"] === MicrosoftTeamsAppClientId) {
|
|
1828
|
+
return MicrosoftTeamsAppInstallState.Installed;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
nextLink = (response.data["@odata.nextLink"] as string) || null;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
return MicrosoftTeamsAppInstallState.NotInstalled;
|
|
1836
|
+
} catch (err) {
|
|
1837
|
+
logger.debug(
|
|
1838
|
+
`Error checking whether the OneUptime app is installed in team ${data.teamId}; treating installation as unknown.`,
|
|
1839
|
+
);
|
|
1840
|
+
logger.debug(err);
|
|
1841
|
+
return MicrosoftTeamsAppInstallState.Unknown;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1645
1845
|
/*
|
|
1646
1846
|
* Sends an adaptive card to a Teams group chat or personal (1:1) chat via a
|
|
1647
1847
|
* proactive Bot Framework message. The OneUptime app must have been added to
|
|
@@ -2357,6 +2557,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
2357
2557
|
await this.captureTeamFromBotActivity({
|
|
2358
2558
|
activity: data.activity,
|
|
2359
2559
|
turnContext: data.turnContext,
|
|
2560
|
+
onlyIfMissingOrStale: true,
|
|
2360
2561
|
});
|
|
2361
2562
|
}
|
|
2362
2563
|
|
|
@@ -4246,10 +4447,121 @@ All monitoring checks are passing normally.`;
|
|
|
4246
4447
|
* (channels are not chats), so before this existed we threw away the only
|
|
4247
4448
|
* signal that tells us a proactive channel post will be accepted.
|
|
4248
4449
|
*/
|
|
4450
|
+
/*
|
|
4451
|
+
* The AAD group id for a team, which is what Graph calls the team id and what
|
|
4452
|
+
* every notification rule stores. Teams normally puts it on the activity as
|
|
4453
|
+
* channelData.team.aadGroupId; when it does not, TeamsInfo.getTeamDetails
|
|
4454
|
+
* fetches it. Returns undefined rather than throwing — a missing group id
|
|
4455
|
+
* degrades the record, it does not invalidate the install.
|
|
4456
|
+
*/
|
|
4457
|
+
public static async resolveGraphTeamIdFromBotActivity(data: {
|
|
4458
|
+
team: JSONObject | undefined;
|
|
4459
|
+
turnContext: TurnContext;
|
|
4460
|
+
}): Promise<string | undefined> {
|
|
4461
|
+
const aadGroupIdOnActivity: string =
|
|
4462
|
+
(data.team?.["aadGroupId"] as string) || "";
|
|
4463
|
+
|
|
4464
|
+
if (aadGroupIdOnActivity) {
|
|
4465
|
+
return aadGroupIdOnActivity;
|
|
4466
|
+
}
|
|
4467
|
+
|
|
4468
|
+
try {
|
|
4469
|
+
const teamDetails: TeamDetails = await TeamsInfo.getTeamDetails(
|
|
4470
|
+
data.turnContext,
|
|
4471
|
+
);
|
|
4472
|
+
|
|
4473
|
+
return teamDetails?.aadGroupId || undefined;
|
|
4474
|
+
} catch (err) {
|
|
4475
|
+
/*
|
|
4476
|
+
* Uninstall activities are the common case here: the bot is already out
|
|
4477
|
+
* of the team by the time we ask, so the call fails. Uninstall matches on
|
|
4478
|
+
* the thread id anyway, so this is not worth an error-level log.
|
|
4479
|
+
*/
|
|
4480
|
+
logger.debug(
|
|
4481
|
+
"Could not resolve the Graph team id for a Microsoft Teams install:",
|
|
4482
|
+
);
|
|
4483
|
+
logger.debug(err);
|
|
4484
|
+
return undefined;
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
|
|
4488
|
+
/*
|
|
4489
|
+
* installedTeams re-keyed by Graph team id, which is the id every caller
|
|
4490
|
+
* outside the bot handlers actually holds. Records that could not be resolved
|
|
4491
|
+
* to a group id are dropped: they cannot answer "is this team installed?" for
|
|
4492
|
+
* a notification rule, and including them under their thread id would only
|
|
4493
|
+
* invite the same confusion this replaced.
|
|
4494
|
+
*/
|
|
4495
|
+
public static indexInstalledTeamsByGraphTeamId(
|
|
4496
|
+
installedTeams: Record<string, MicrosoftTeamsInstalledTeam> | undefined,
|
|
4497
|
+
): Record<string, MicrosoftTeamsInstalledTeam> {
|
|
4498
|
+
const index: Record<string, MicrosoftTeamsInstalledTeam> = {};
|
|
4499
|
+
|
|
4500
|
+
for (const installedTeam of Object.values(installedTeams || {})) {
|
|
4501
|
+
const graphTeamId: string | undefined =
|
|
4502
|
+
installedTeam?.graphTeamId || undefined;
|
|
4503
|
+
|
|
4504
|
+
if (!graphTeamId) {
|
|
4505
|
+
continue;
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4508
|
+
index[graphTeamId] = installedTeam;
|
|
4509
|
+
}
|
|
4510
|
+
|
|
4511
|
+
return index;
|
|
4512
|
+
}
|
|
4513
|
+
|
|
4514
|
+
/*
|
|
4515
|
+
* The stored record for a team in a tenant, if any, matched on either id.
|
|
4516
|
+
* Used to keep the message-driven backfill cheap.
|
|
4517
|
+
*/
|
|
4518
|
+
public static async getInstalledTeamForTenant(data: {
|
|
4519
|
+
tenantId: string;
|
|
4520
|
+
teamsThreadId?: string | undefined;
|
|
4521
|
+
graphTeamId?: string | undefined;
|
|
4522
|
+
}): Promise<MicrosoftTeamsInstalledTeam | null> {
|
|
4523
|
+
const projectAuths: Array<WorkspaceProjectAuthToken> =
|
|
4524
|
+
await WorkspaceProjectAuthTokenService.findBy({
|
|
4525
|
+
query: {
|
|
4526
|
+
workspaceType: WorkspaceType.MicrosoftTeams,
|
|
4527
|
+
workspaceProjectId: data.tenantId,
|
|
4528
|
+
},
|
|
4529
|
+
select: {
|
|
4530
|
+
_id: true,
|
|
4531
|
+
miscData: true,
|
|
4532
|
+
},
|
|
4533
|
+
limit: LIMIT_MAX,
|
|
4534
|
+
skip: 0,
|
|
4535
|
+
props: {
|
|
4536
|
+
isRoot: true,
|
|
4537
|
+
},
|
|
4538
|
+
});
|
|
4539
|
+
|
|
4540
|
+
for (const projectAuth of projectAuths) {
|
|
4541
|
+
const miscData: MicrosoftTeamsMiscData =
|
|
4542
|
+
(projectAuth.miscData as MicrosoftTeamsMiscData) || {};
|
|
4543
|
+
|
|
4544
|
+
for (const existingTeam of Object.values(miscData.installedTeams || {})) {
|
|
4545
|
+
if (
|
|
4546
|
+
this.isSameInstalledTeam({
|
|
4547
|
+
team: existingTeam,
|
|
4548
|
+
graphTeamId: data.graphTeamId,
|
|
4549
|
+
teamsThreadId: data.teamsThreadId,
|
|
4550
|
+
})
|
|
4551
|
+
) {
|
|
4552
|
+
return existingTeam;
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
|
|
4557
|
+
return null;
|
|
4558
|
+
}
|
|
4559
|
+
|
|
4249
4560
|
@CaptureSpan()
|
|
4250
4561
|
public static async captureTeamFromBotActivity(data: {
|
|
4251
4562
|
activity: JSONObject;
|
|
4252
4563
|
turnContext: TurnContext;
|
|
4564
|
+
onlyIfMissingOrStale?: boolean | undefined;
|
|
4253
4565
|
}): Promise<void> {
|
|
4254
4566
|
try {
|
|
4255
4567
|
const channelData: JSONObject =
|
|
@@ -4259,9 +4571,9 @@ All monitoring checks are passing normally.`;
|
|
|
4259
4571
|
| JSONObject
|
|
4260
4572
|
| undefined;
|
|
4261
4573
|
|
|
4262
|
-
const
|
|
4574
|
+
const teamsThreadId: string = (team?.["id"] as string) || "";
|
|
4263
4575
|
|
|
4264
|
-
if (!
|
|
4576
|
+
if (!teamsThreadId) {
|
|
4265
4577
|
// Not a team-scoped activity (personal or group chat). Nothing to do.
|
|
4266
4578
|
return;
|
|
4267
4579
|
}
|
|
@@ -4274,13 +4586,55 @@ All monitoring checks are passing normally.`;
|
|
|
4274
4586
|
return;
|
|
4275
4587
|
}
|
|
4276
4588
|
|
|
4589
|
+
const activityServiceUrl: string | undefined =
|
|
4590
|
+
(data.activity["serviceUrl"] as string) ||
|
|
4591
|
+
data.turnContext.activity.serviceUrl ||
|
|
4592
|
+
undefined;
|
|
4593
|
+
|
|
4594
|
+
/*
|
|
4595
|
+
* The backfill runs on every inbound channel message, so it must be cheap
|
|
4596
|
+
* once the team is already on record: one read, then nothing. Without this
|
|
4597
|
+
* every message meant a write, and now potentially a Bot Framework call to
|
|
4598
|
+
* resolve the group id as well. A record that is missing the group id, or
|
|
4599
|
+
* whose serviceUrl has moved, is still worth redoing.
|
|
4600
|
+
*/
|
|
4601
|
+
if (data.onlyIfMissingOrStale) {
|
|
4602
|
+
const existingTeam: MicrosoftTeamsInstalledTeam | null =
|
|
4603
|
+
await this.getInstalledTeamForTenant({
|
|
4604
|
+
tenantId: tenantId,
|
|
4605
|
+
teamsThreadId: teamsThreadId,
|
|
4606
|
+
graphTeamId: (team?.["aadGroupId"] as string) || undefined,
|
|
4607
|
+
});
|
|
4608
|
+
|
|
4609
|
+
if (
|
|
4610
|
+
existingTeam &&
|
|
4611
|
+
existingTeam.graphTeamId &&
|
|
4612
|
+
existingTeam.serviceUrl === activityServiceUrl
|
|
4613
|
+
) {
|
|
4614
|
+
return;
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4618
|
+
/*
|
|
4619
|
+
* The send path looks installs up by Graph team id, so resolving it here
|
|
4620
|
+
* is what makes the record usable at all. Teams puts it on the activity
|
|
4621
|
+
* as aadGroupId most of the time; when it does not, one Bot Framework
|
|
4622
|
+
* call gets it. A record without it is still worth keeping for the
|
|
4623
|
+
* serviceUrl and for uninstall matching, it just cannot be matched to a
|
|
4624
|
+
* notification rule.
|
|
4625
|
+
*/
|
|
4626
|
+
const graphTeamId: string | undefined =
|
|
4627
|
+
await this.resolveGraphTeamIdFromBotActivity({
|
|
4628
|
+
team: team,
|
|
4629
|
+
turnContext: data.turnContext,
|
|
4630
|
+
});
|
|
4631
|
+
|
|
4277
4632
|
const installedTeam: MicrosoftTeamsInstalledTeam = {
|
|
4278
|
-
id:
|
|
4633
|
+
id: graphTeamId || teamsThreadId,
|
|
4634
|
+
graphTeamId: graphTeamId,
|
|
4635
|
+
teamsThreadId: teamsThreadId,
|
|
4279
4636
|
name: (team?.["name"] as string) || undefined,
|
|
4280
|
-
serviceUrl:
|
|
4281
|
-
(data.activity["serviceUrl"] as string) ||
|
|
4282
|
-
data.turnContext.activity.serviceUrl ||
|
|
4283
|
-
undefined,
|
|
4637
|
+
serviceUrl: activityServiceUrl,
|
|
4284
4638
|
addedAt: OneUptimeDate.getCurrentDate().toISOString(),
|
|
4285
4639
|
};
|
|
4286
4640
|
|
|
@@ -4290,7 +4644,7 @@ All monitoring checks are passing normally.`;
|
|
|
4290
4644
|
});
|
|
4291
4645
|
|
|
4292
4646
|
logger.debug(
|
|
4293
|
-
`Captured Microsoft Teams team install ${
|
|
4647
|
+
`Captured Microsoft Teams team install ${installedTeam.id} (thread ${teamsThreadId}, graph ${graphTeamId || "unresolved"}) for tenant ${tenantId}`,
|
|
4294
4648
|
);
|
|
4295
4649
|
} catch (err) {
|
|
4296
4650
|
logger.error("Error capturing Microsoft Teams team from bot activity:");
|
|
@@ -4306,22 +4660,33 @@ All monitoring checks are passing normally.`;
|
|
|
4306
4660
|
const channelData: JSONObject =
|
|
4307
4661
|
(data.activity["channelData"] as JSONObject) || {};
|
|
4308
4662
|
|
|
4309
|
-
const
|
|
4310
|
-
|
|
4663
|
+
const team: JSONObject | undefined = channelData["team"] as
|
|
4664
|
+
| JSONObject
|
|
4665
|
+
| undefined;
|
|
4666
|
+
|
|
4667
|
+
const teamsThreadId: string = (team?.["id"] as string) || "";
|
|
4311
4668
|
const tenantId: string =
|
|
4312
4669
|
((channelData["tenant"] as JSONObject)?.["id"] as string) || "";
|
|
4313
4670
|
|
|
4314
|
-
if (!
|
|
4671
|
+
if (!teamsThreadId || !tenantId) {
|
|
4315
4672
|
return;
|
|
4316
4673
|
}
|
|
4317
4674
|
|
|
4675
|
+
/*
|
|
4676
|
+
* Uninstall matches on both ids because we cannot know which one the
|
|
4677
|
+
* record was keyed by: the bot is already out of the team, so
|
|
4678
|
+
* getTeamDetails will not answer, and records written before this shipped
|
|
4679
|
+
* only have a thread id. aadGroupId is still on the activity often enough
|
|
4680
|
+
* to be worth passing through.
|
|
4681
|
+
*/
|
|
4318
4682
|
await this.removeTeamFromProjectAuthTokens({
|
|
4319
4683
|
tenantId: tenantId,
|
|
4320
|
-
|
|
4684
|
+
teamsThreadId: teamsThreadId,
|
|
4685
|
+
graphTeamId: (team?.["aadGroupId"] as string) || undefined,
|
|
4321
4686
|
});
|
|
4322
4687
|
|
|
4323
4688
|
logger.debug(
|
|
4324
|
-
`Removed Microsoft Teams team install ${
|
|
4689
|
+
`Removed Microsoft Teams team install ${teamsThreadId} for tenant ${tenantId}`,
|
|
4325
4690
|
);
|
|
4326
4691
|
} catch (err) {
|
|
4327
4692
|
logger.error("Error removing Microsoft Teams team from bot activity:");
|
|
@@ -4361,10 +4726,32 @@ All monitoring checks are passing normally.`;
|
|
|
4361
4726
|
...((projectAuth.miscData as MicrosoftTeamsMiscData) || {}),
|
|
4362
4727
|
} as MicrosoftTeamsMiscData;
|
|
4363
4728
|
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4729
|
+
/*
|
|
4730
|
+
* Drop any other record describing the same team before inserting. A
|
|
4731
|
+
* team captured before the group id was resolvable sits under its thread
|
|
4732
|
+
* id, and re-capturing it under the group id would otherwise leave two
|
|
4733
|
+
* records for one team — one of which can never be matched or removed.
|
|
4734
|
+
*/
|
|
4735
|
+
const installedTeams: Record<string, MicrosoftTeamsInstalledTeam> = {};
|
|
4736
|
+
|
|
4737
|
+
for (const [key, existingTeam] of Object.entries(
|
|
4738
|
+
miscData.installedTeams || {},
|
|
4739
|
+
)) {
|
|
4740
|
+
if (
|
|
4741
|
+
this.isSameInstalledTeam({
|
|
4742
|
+
team: existingTeam,
|
|
4743
|
+
graphTeamId: data.team.graphTeamId,
|
|
4744
|
+
teamsThreadId: data.team.teamsThreadId,
|
|
4745
|
+
})
|
|
4746
|
+
) {
|
|
4747
|
+
continue;
|
|
4748
|
+
}
|
|
4749
|
+
|
|
4750
|
+
installedTeams[key] = existingTeam;
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4753
|
+
installedTeams[data.team.id] = data.team;
|
|
4754
|
+
miscData.installedTeams = installedTeams;
|
|
4368
4755
|
|
|
4369
4756
|
await WorkspaceProjectAuthTokenService.updateOneById({
|
|
4370
4757
|
id: projectAuth.id!,
|
|
@@ -4378,10 +4765,45 @@ All monitoring checks are passing normally.`;
|
|
|
4378
4765
|
}
|
|
4379
4766
|
}
|
|
4380
4767
|
|
|
4768
|
+
/*
|
|
4769
|
+
* Whether a stored record describes the team identified by either id. Matches
|
|
4770
|
+
* on the record's explicit ids and on its key, so records written before
|
|
4771
|
+
* teamsThreadId existed — where the thread id is only in `id` — still match.
|
|
4772
|
+
* Ids are compared case-sensitively, as Microsoft returns them.
|
|
4773
|
+
*/
|
|
4774
|
+
public static isSameInstalledTeam(data: {
|
|
4775
|
+
team: MicrosoftTeamsInstalledTeam | undefined;
|
|
4776
|
+
graphTeamId?: string | undefined;
|
|
4777
|
+
teamsThreadId?: string | undefined;
|
|
4778
|
+
}): boolean {
|
|
4779
|
+
if (!data.team) {
|
|
4780
|
+
return false;
|
|
4781
|
+
}
|
|
4782
|
+
|
|
4783
|
+
if (
|
|
4784
|
+
data.graphTeamId &&
|
|
4785
|
+
(data.team.graphTeamId === data.graphTeamId ||
|
|
4786
|
+
data.team.id === data.graphTeamId)
|
|
4787
|
+
) {
|
|
4788
|
+
return true;
|
|
4789
|
+
}
|
|
4790
|
+
|
|
4791
|
+
if (
|
|
4792
|
+
data.teamsThreadId &&
|
|
4793
|
+
(data.team.teamsThreadId === data.teamsThreadId ||
|
|
4794
|
+
data.team.id === data.teamsThreadId)
|
|
4795
|
+
) {
|
|
4796
|
+
return true;
|
|
4797
|
+
}
|
|
4798
|
+
|
|
4799
|
+
return false;
|
|
4800
|
+
}
|
|
4801
|
+
|
|
4381
4802
|
@CaptureSpan()
|
|
4382
4803
|
public static async removeTeamFromProjectAuthTokens(data: {
|
|
4383
4804
|
tenantId: string;
|
|
4384
|
-
|
|
4805
|
+
teamsThreadId?: string | undefined;
|
|
4806
|
+
graphTeamId?: string | undefined;
|
|
4385
4807
|
}): Promise<void> {
|
|
4386
4808
|
const projectAuths: Array<WorkspaceProjectAuthToken> =
|
|
4387
4809
|
await WorkspaceProjectAuthTokenService.findBy({
|
|
@@ -4405,14 +4827,34 @@ All monitoring checks are passing normally.`;
|
|
|
4405
4827
|
...((projectAuth.miscData as MicrosoftTeamsMiscData) || {}),
|
|
4406
4828
|
} as MicrosoftTeamsMiscData;
|
|
4407
4829
|
|
|
4408
|
-
if (!miscData.installedTeams
|
|
4830
|
+
if (!miscData.installedTeams) {
|
|
4831
|
+
continue;
|
|
4832
|
+
}
|
|
4833
|
+
|
|
4834
|
+
const installedTeams: Record<string, MicrosoftTeamsInstalledTeam> = {};
|
|
4835
|
+
let removedAny: boolean = false;
|
|
4836
|
+
|
|
4837
|
+
for (const [key, existingTeam] of Object.entries(
|
|
4838
|
+
miscData.installedTeams,
|
|
4839
|
+
)) {
|
|
4840
|
+
if (
|
|
4841
|
+
this.isSameInstalledTeam({
|
|
4842
|
+
team: existingTeam,
|
|
4843
|
+
graphTeamId: data.graphTeamId,
|
|
4844
|
+
teamsThreadId: data.teamsThreadId,
|
|
4845
|
+
})
|
|
4846
|
+
) {
|
|
4847
|
+
removedAny = true;
|
|
4848
|
+
continue;
|
|
4849
|
+
}
|
|
4850
|
+
|
|
4851
|
+
installedTeams[key] = existingTeam;
|
|
4852
|
+
}
|
|
4853
|
+
|
|
4854
|
+
if (!removedAny) {
|
|
4409
4855
|
continue;
|
|
4410
4856
|
}
|
|
4411
4857
|
|
|
4412
|
-
const installedTeams: Record<string, MicrosoftTeamsInstalledTeam> = {
|
|
4413
|
-
...miscData.installedTeams,
|
|
4414
|
-
};
|
|
4415
|
-
delete installedTeams[data.teamId];
|
|
4416
4858
|
miscData.installedTeams = installedTeams;
|
|
4417
4859
|
|
|
4418
4860
|
await WorkspaceProjectAuthTokenService.updateOneById({
|