[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
This commit is contained in:
Rui Tomé 2022-07-25 09:56:03 +01:00 committed by GitHub
parent 0f44789d0f
commit b50de43556
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 88 additions and 38 deletions

View File

@ -11,6 +11,7 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service"; import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { EventType } from "@bitwarden/common/enums/eventType";
import { ExportComponent } from "../../../tools/import-export/export.component"; import { ExportComponent } from "../../../tools/import-export/export.component";
@ -66,7 +67,11 @@ export class OrganizationExportComponent extends ExportComponent {
} }
async collectEvent(): Promise<any> { async collectEvent(): Promise<any> {
// TODO await this.eventService.collect(
// await this.eventService.collect(EventType.Organization_ClientExportedVault); EventType.Organization_ClientExportedVault,
null,
null,
this.organizationId
);
} }
} }

View File

@ -301,11 +301,9 @@ export class EventService {
case EventType.Organization_PurgedVault: case EventType.Organization_PurgedVault:
msg = humanReadableMsg = this.i18nService.t("purgedOrganizationVault"); msg = humanReadableMsg = this.i18nService.t("purgedOrganizationVault");
break; break;
/* case EventType.Organization_ClientExportedVault:
case EventType.Organization_ClientExportedVault: msg = humanReadableMsg = this.i18nService.t("exportedOrganizationVault");
msg = this.i18nService.t('exportedOrganizationVault'); break;
break;
*/
case EventType.Organization_VaultAccessed: case EventType.Organization_VaultAccessed:
msg = humanReadableMsg = this.i18nService.t("vaultAccessedByProvider"); msg = humanReadableMsg = this.i18nService.t("vaultAccessedByProvider");
break; break;

View File

@ -130,6 +130,7 @@ import {
OrganizationConnectionConfigApis, OrganizationConnectionConfigApis,
OrganizationConnectionResponse, OrganizationConnectionResponse,
} from "../models/response/organizationConnectionResponse"; } from "../models/response/organizationConnectionResponse";
import { OrganizationExportResponse } from "../models/response/organizationExportResponse";
import { OrganizationKeysResponse } from "../models/response/organizationKeysResponse"; import { OrganizationKeysResponse } from "../models/response/organizationKeysResponse";
import { OrganizationResponse } from "../models/response/organizationResponse"; import { OrganizationResponse } from "../models/response/organizationResponse";
import { OrganizationSponsorshipSyncStatusResponse } from "../models/response/organizationSponsorshipSyncStatusResponse"; import { OrganizationSponsorshipSyncStatusResponse } from "../models/response/organizationSponsorshipSyncStatusResponse";
@ -734,4 +735,5 @@ export abstract class ApiService {
request: KeyConnectorUserKeyRequest request: KeyConnectorUserKeyRequest
) => Promise<void>; ) => Promise<void>;
getKeyConnectorAlive: (keyConnectorUrl: string) => Promise<void>; getKeyConnectorAlive: (keyConnectorUrl: string) => Promise<void>;
getOrganizationExport: (organizationId: string) => Promise<OrganizationExportResponse>;
} }

View File

@ -1,7 +1,12 @@
import { EventType } from "../enums/eventType"; import { EventType } from "../enums/eventType";
export abstract class EventService { export abstract class EventService {
collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise<any>; collect: (
eventType: EventType,
cipherId?: string,
uploadImmediately?: boolean,
organizationId?: string
) => Promise<any>;
uploadEvents: (userId?: string) => Promise<any>; uploadEvents: (userId?: string) => Promise<any>;
clearEvents: (userId?: string) => Promise<any>; clearEvents: (userId?: string) => Promise<any>;
} }

View File

@ -53,7 +53,7 @@ export enum EventType {
Organization_Updated = 1600, Organization_Updated = 1600,
Organization_PurgedVault = 1601, Organization_PurgedVault = 1601,
// Organization_ClientExportedVault = 1602, Organization_ClientExportedVault = 1602,
Organization_VaultAccessed = 1603, Organization_VaultAccessed = 1603,
Organization_EnabledSso = 1604, Organization_EnabledSso = 1604,
Organization_DisabledSso = 1605, Organization_DisabledSso = 1605,

View File

@ -4,4 +4,5 @@ export class EventData {
type: EventType; type: EventType;
cipherId: string; cipherId: string;
date: string; date: string;
organizationId: string;
} }

View File

@ -4,4 +4,5 @@ export class EventRequest {
type: EventType; type: EventType;
cipherId: string; cipherId: string;
date: string; date: string;
organizationId: string;
} }

View File

@ -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<CollectionResponse>;
ciphers: ListResponse<CipherResponse>;
constructor(response: any) {
super(response);
this.collections = this.getResponseProperty("Collections");
this.ciphers = this.getResponseProperty("Ciphers");
}
}

View File

@ -139,6 +139,7 @@ import {
OrganizationConnectionConfigApis, OrganizationConnectionConfigApis,
OrganizationConnectionResponse, OrganizationConnectionResponse,
} from "../models/response/organizationConnectionResponse"; } from "../models/response/organizationConnectionResponse";
import { OrganizationExportResponse } from "../models/response/organizationExportResponse";
import { OrganizationKeysResponse } from "../models/response/organizationKeysResponse"; import { OrganizationKeysResponse } from "../models/response/organizationKeysResponse";
import { OrganizationResponse } from "../models/response/organizationResponse"; import { OrganizationResponse } from "../models/response/organizationResponse";
import { OrganizationSponsorshipSyncStatusResponse } from "../models/response/organizationSponsorshipSyncStatusResponse"; import { OrganizationSponsorshipSyncStatusResponse } from "../models/response/organizationSponsorshipSyncStatusResponse";
@ -2323,6 +2324,17 @@ export class ApiService implements ApiServiceAbstraction {
} }
} }
async getOrganizationExport(organizationId: string): Promise<OrganizationExportResponse> {
const r = await this.send(
"GET",
"/organizations/" + organizationId + "/export",
null,
true,
true
);
return new OrganizationExportResponse(r);
}
// Helpers // Helpers
async getActiveBearerToken(): Promise<string> { async getActiveBearerToken(): Promise<string> {

View File

@ -34,7 +34,8 @@ export class EventService implements EventServiceAbstraction {
async collect( async collect(
eventType: EventType, eventType: EventType,
cipherId: string = null, cipherId: string = null,
uploadImmediately = false uploadImmediately = false,
organizationId: string = null
): Promise<any> { ): Promise<any> {
const authed = await this.stateService.getIsAuthenticated(); const authed = await this.stateService.getIsAuthenticated();
if (!authed) { if (!authed) {
@ -54,6 +55,11 @@ export class EventService implements EventServiceAbstraction {
return; return;
} }
} }
if (organizationId != null) {
if (!orgIds.has(organizationId)) {
return;
}
}
let eventCollection = await this.stateService.getEventCollection(); let eventCollection = await this.stateService.getEventCollection();
if (eventCollection == null) { if (eventCollection == null) {
eventCollection = []; eventCollection = [];
@ -62,6 +68,7 @@ export class EventService implements EventServiceAbstraction {
event.type = eventType; event.type = eventType;
event.cipherId = cipherId; event.cipherId = cipherId;
event.date = new Date().toISOString(); event.date = new Date().toISOString();
event.organizationId = organizationId;
eventCollection.push(event); eventCollection.push(event);
await this.stateService.setEventCollection(eventCollection); await this.stateService.setEventCollection(eventCollection);
if (uploadImmediately) { if (uploadImmediately) {
@ -83,6 +90,7 @@ export class EventService implements EventServiceAbstraction {
req.type = e.type; req.type = e.type;
req.cipherId = e.cipherId; req.cipherId = e.cipherId;
req.date = e.date; req.date = e.date;
req.organizationId = e.organizationId;
return req; return req;
}); });
try { try {

View File

@ -245,38 +245,41 @@ export class ExportService implements ExportServiceAbstraction {
const promises = []; const promises = [];
promises.push( promises.push(
this.apiService.getCollections(organizationId).then((collections) => { this.apiService.getOrganizationExport(organizationId).then((exportData) => {
const collectionPromises: any = []; const exportPromises: any = [];
if (collections != null && collections.data != null && collections.data.length > 0) { if (exportData != null) {
collections.data.forEach((c) => { if (
const collection = new Collection(new CollectionData(c as CollectionDetailsResponse)); exportData.collections != null &&
collectionPromises.push( exportData.collections.data != null &&
collection.decrypt().then((decCol) => { exportData.collections.data.length > 0
decCollections.push(decCol); ) {
}) exportData.collections.data.forEach((c) => {
); const collection = new Collection(new CollectionData(c as CollectionDetailsResponse));
}); exportPromises.push(
} collection.decrypt().then((decCol) => {
return Promise.all(collectionPromises); decCollections.push(decCol);
})
);
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);
}) })
); );
}); });
}
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);
}) })
); );