From b50de435561b70b8403bddf75aaf58eb9111e31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Mon, 25 Jul 2022 09:56:03 +0100 Subject: [PATCH] [EC-320] Add organization vault export to event logs (#3136) * Added organizationId to EventData and EventRequest * Added EventType Organization_ClientExportedVault * Sending organizationId on Organization Export event * Checking that the user belongs to the organization * Added organizationExportResponse model * Added API method to get Organization vault export data * Updated getOrganizationDecryptedExport to use new API method --- .../import-export/org-export.component.ts | 9 ++- apps/web/src/app/services/event.service.ts | 8 +-- libs/common/src/abstractions/api.service.ts | 2 + libs/common/src/abstractions/event.service.ts | 7 ++- libs/common/src/enums/eventType.ts | 2 +- libs/common/src/models/data/eventData.ts | 1 + .../common/src/models/request/eventRequest.ts | 1 + .../response/organizationExportResponse.ts | 15 +++++ libs/common/src/services/api.service.ts | 12 ++++ libs/common/src/services/event.service.ts | 10 +++- libs/common/src/services/export.service.ts | 59 ++++++++++--------- 11 files changed, 88 insertions(+), 38 deletions(-) create mode 100644 libs/common/src/models/response/organizationExportResponse.ts diff --git a/apps/web/src/app/organizations/tools/import-export/org-export.component.ts b/apps/web/src/app/organizations/tools/import-export/org-export.component.ts index d06165daf23..4a9c5bd2656 100644 --- a/apps/web/src/app/organizations/tools/import-export/org-export.component.ts +++ b/apps/web/src/app/organizations/tools/import-export/org-export.component.ts @@ -11,6 +11,7 @@ import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy.service"; import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; +import { EventType } from "@bitwarden/common/enums/eventType"; import { ExportComponent } from "../../../tools/import-export/export.component"; @@ -66,7 +67,11 @@ export class OrganizationExportComponent extends ExportComponent { } async collectEvent(): Promise { - // TODO - // await this.eventService.collect(EventType.Organization_ClientExportedVault); + await this.eventService.collect( + EventType.Organization_ClientExportedVault, + null, + null, + this.organizationId + ); } } diff --git a/apps/web/src/app/services/event.service.ts b/apps/web/src/app/services/event.service.ts index e72c9c82d02..2627c6b6a82 100644 --- a/apps/web/src/app/services/event.service.ts +++ b/apps/web/src/app/services/event.service.ts @@ -301,11 +301,9 @@ export class EventService { case EventType.Organization_PurgedVault: msg = humanReadableMsg = this.i18nService.t("purgedOrganizationVault"); break; - /* - case EventType.Organization_ClientExportedVault: - msg = this.i18nService.t('exportedOrganizationVault'); - break; - */ + case EventType.Organization_ClientExportedVault: + msg = humanReadableMsg = this.i18nService.t("exportedOrganizationVault"); + break; case EventType.Organization_VaultAccessed: msg = humanReadableMsg = this.i18nService.t("vaultAccessedByProvider"); break; diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 2fc4985f4ad..8bab03aec5c 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -130,6 +130,7 @@ import { OrganizationConnectionConfigApis, OrganizationConnectionResponse, } from "../models/response/organizationConnectionResponse"; +import { OrganizationExportResponse } from "../models/response/organizationExportResponse"; import { OrganizationKeysResponse } from "../models/response/organizationKeysResponse"; import { OrganizationResponse } from "../models/response/organizationResponse"; import { OrganizationSponsorshipSyncStatusResponse } from "../models/response/organizationSponsorshipSyncStatusResponse"; @@ -734,4 +735,5 @@ export abstract class ApiService { request: KeyConnectorUserKeyRequest ) => Promise; getKeyConnectorAlive: (keyConnectorUrl: string) => Promise; + getOrganizationExport: (organizationId: string) => Promise; } diff --git a/libs/common/src/abstractions/event.service.ts b/libs/common/src/abstractions/event.service.ts index 2f7660fb516..d774eb5e2e6 100644 --- a/libs/common/src/abstractions/event.service.ts +++ b/libs/common/src/abstractions/event.service.ts @@ -1,7 +1,12 @@ import { EventType } from "../enums/eventType"; export abstract class EventService { - collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise; + collect: ( + eventType: EventType, + cipherId?: string, + uploadImmediately?: boolean, + organizationId?: string + ) => Promise; uploadEvents: (userId?: string) => Promise; clearEvents: (userId?: string) => Promise; } diff --git a/libs/common/src/enums/eventType.ts b/libs/common/src/enums/eventType.ts index be18c81ecb9..5d1e4785fbe 100644 --- a/libs/common/src/enums/eventType.ts +++ b/libs/common/src/enums/eventType.ts @@ -53,7 +53,7 @@ export enum EventType { Organization_Updated = 1600, Organization_PurgedVault = 1601, - // Organization_ClientExportedVault = 1602, + Organization_ClientExportedVault = 1602, Organization_VaultAccessed = 1603, Organization_EnabledSso = 1604, Organization_DisabledSso = 1605, diff --git a/libs/common/src/models/data/eventData.ts b/libs/common/src/models/data/eventData.ts index c0e38ddc389..69031a8cd0f 100644 --- a/libs/common/src/models/data/eventData.ts +++ b/libs/common/src/models/data/eventData.ts @@ -4,4 +4,5 @@ export class EventData { type: EventType; cipherId: string; date: string; + organizationId: string; } diff --git a/libs/common/src/models/request/eventRequest.ts b/libs/common/src/models/request/eventRequest.ts index a6228fd6731..6b9bef44190 100644 --- a/libs/common/src/models/request/eventRequest.ts +++ b/libs/common/src/models/request/eventRequest.ts @@ -4,4 +4,5 @@ export class EventRequest { type: EventType; cipherId: string; date: string; + organizationId: string; } diff --git a/libs/common/src/models/response/organizationExportResponse.ts b/libs/common/src/models/response/organizationExportResponse.ts new file mode 100644 index 00000000000..847fcab96be --- /dev/null +++ b/libs/common/src/models/response/organizationExportResponse.ts @@ -0,0 +1,15 @@ +import { BaseResponse } from "./baseResponse"; +import { CipherResponse } from "./cipherResponse"; +import { CollectionResponse } from "./collectionResponse"; +import { ListResponse } from "./listResponse"; + +export class OrganizationExportResponse extends BaseResponse { + collections: ListResponse; + ciphers: ListResponse; + + constructor(response: any) { + super(response); + this.collections = this.getResponseProperty("Collections"); + this.ciphers = this.getResponseProperty("Ciphers"); + } +} diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 684d4580609..dcc396cfd2b 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -139,6 +139,7 @@ import { OrganizationConnectionConfigApis, OrganizationConnectionResponse, } from "../models/response/organizationConnectionResponse"; +import { OrganizationExportResponse } from "../models/response/organizationExportResponse"; import { OrganizationKeysResponse } from "../models/response/organizationKeysResponse"; import { OrganizationResponse } from "../models/response/organizationResponse"; import { OrganizationSponsorshipSyncStatusResponse } from "../models/response/organizationSponsorshipSyncStatusResponse"; @@ -2323,6 +2324,17 @@ export class ApiService implements ApiServiceAbstraction { } } + async getOrganizationExport(organizationId: string): Promise { + const r = await this.send( + "GET", + "/organizations/" + organizationId + "/export", + null, + true, + true + ); + return new OrganizationExportResponse(r); + } + // Helpers async getActiveBearerToken(): Promise { diff --git a/libs/common/src/services/event.service.ts b/libs/common/src/services/event.service.ts index c0024d49ea5..9169bfe696b 100644 --- a/libs/common/src/services/event.service.ts +++ b/libs/common/src/services/event.service.ts @@ -34,7 +34,8 @@ export class EventService implements EventServiceAbstraction { async collect( eventType: EventType, cipherId: string = null, - uploadImmediately = false + uploadImmediately = false, + organizationId: string = null ): Promise { const authed = await this.stateService.getIsAuthenticated(); if (!authed) { @@ -54,6 +55,11 @@ export class EventService implements EventServiceAbstraction { return; } } + if (organizationId != null) { + if (!orgIds.has(organizationId)) { + return; + } + } let eventCollection = await this.stateService.getEventCollection(); if (eventCollection == null) { eventCollection = []; @@ -62,6 +68,7 @@ export class EventService implements EventServiceAbstraction { event.type = eventType; event.cipherId = cipherId; event.date = new Date().toISOString(); + event.organizationId = organizationId; eventCollection.push(event); await this.stateService.setEventCollection(eventCollection); if (uploadImmediately) { @@ -83,6 +90,7 @@ export class EventService implements EventServiceAbstraction { req.type = e.type; req.cipherId = e.cipherId; req.date = e.date; + req.organizationId = e.organizationId; return req; }); try { diff --git a/libs/common/src/services/export.service.ts b/libs/common/src/services/export.service.ts index 11695936196..0fdd1e332de 100644 --- a/libs/common/src/services/export.service.ts +++ b/libs/common/src/services/export.service.ts @@ -245,38 +245,41 @@ export class ExportService implements ExportServiceAbstraction { const promises = []; promises.push( - this.apiService.getCollections(organizationId).then((collections) => { - const collectionPromises: any = []; - if (collections != null && collections.data != null && collections.data.length > 0) { - collections.data.forEach((c) => { - const collection = new Collection(new CollectionData(c as CollectionDetailsResponse)); - collectionPromises.push( - collection.decrypt().then((decCol) => { - decCollections.push(decCol); - }) - ); - }); - } - return Promise.all(collectionPromises); - }) - ); - - promises.push( - this.apiService.getCiphersOrganization(organizationId).then((ciphers) => { - const cipherPromises: any = []; - if (ciphers != null && ciphers.data != null && ciphers.data.length > 0) { - ciphers.data - .filter((c) => c.deletedDate === null) - .forEach((c) => { - const cipher = new Cipher(new CipherData(c)); - cipherPromises.push( - cipher.decrypt().then((decCipher) => { - decCiphers.push(decCipher); + this.apiService.getOrganizationExport(organizationId).then((exportData) => { + const exportPromises: any = []; + if (exportData != null) { + if ( + exportData.collections != null && + exportData.collections.data != null && + exportData.collections.data.length > 0 + ) { + exportData.collections.data.forEach((c) => { + const collection = new Collection(new CollectionData(c as CollectionDetailsResponse)); + exportPromises.push( + collection.decrypt().then((decCol) => { + decCollections.push(decCol); }) ); }); + } + if ( + exportData.ciphers != null && + exportData.ciphers.data != null && + exportData.ciphers.data.length > 0 + ) { + exportData.ciphers.data + .filter((c) => c.deletedDate === null) + .forEach((c) => { + const cipher = new Cipher(new CipherData(c)); + exportPromises.push( + cipher.decrypt().then((decCipher) => { + decCiphers.push(decCipher); + }) + ); + }); + } } - return Promise.all(cipherPromises); + return Promise.all(exportPromises); }) );