mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[PM-25503] Use org export api on encrypted and unencrypted org exports (#16290)
* Introduce a new vault-export-api.service to replace the existing getOrganizationExport method in apiService
* Use new vault-export-api.service instead of the ApiService to retrieve organizational export data
* Remove unused method from apiService
* Register VaultExportApiService on browser
* Fxi linting issue by executing `npm run prettier`
* Rename abstraction and implementation of VaultExportApiService
* Use undefined instead of null
* Rename file of default impl of vault-export-api-service
* Fix test broken with 1bcdd80eea
* Define type for exportPromises
---------
Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
200fc71c5c
commit
ba817f0389
@@ -256,6 +256,8 @@ import {
|
||||
IndividualVaultExportServiceAbstraction,
|
||||
OrganizationVaultExportService,
|
||||
OrganizationVaultExportServiceAbstraction,
|
||||
DefaultVaultExportApiService,
|
||||
VaultExportApiService,
|
||||
VaultExportService,
|
||||
VaultExportServiceAbstraction,
|
||||
} from "@bitwarden/vault-export-core";
|
||||
@@ -362,6 +364,7 @@ export default class MainBackground {
|
||||
loginEmailService: LoginEmailServiceAbstraction;
|
||||
importApiService: ImportApiServiceAbstraction;
|
||||
importService: ImportServiceAbstraction;
|
||||
exportApiService: VaultExportApiService;
|
||||
exportService: VaultExportServiceAbstraction;
|
||||
searchService: SearchServiceAbstraction;
|
||||
serverNotificationsService: ServerNotificationsService;
|
||||
@@ -1100,9 +1103,11 @@ export default class MainBackground {
|
||||
this.restrictedItemTypesService,
|
||||
);
|
||||
|
||||
this.exportApiService = new DefaultVaultExportApiService(this.apiService);
|
||||
|
||||
this.organizationVaultExportService = new OrganizationVaultExportService(
|
||||
this.cipherService,
|
||||
this.apiService,
|
||||
this.exportApiService,
|
||||
this.pinService,
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
|
||||
@@ -179,10 +179,12 @@ import { SerializedMemoryStorageService } from "@bitwarden/storage-core";
|
||||
import {
|
||||
IndividualVaultExportService,
|
||||
IndividualVaultExportServiceAbstraction,
|
||||
VaultExportApiService,
|
||||
OrganizationVaultExportService,
|
||||
OrganizationVaultExportServiceAbstraction,
|
||||
VaultExportService,
|
||||
VaultExportServiceAbstraction,
|
||||
DefaultVaultExportApiService,
|
||||
} from "@bitwarden/vault-export-core";
|
||||
|
||||
import { CliBiometricsService } from "../key-management/cli-biometrics-service";
|
||||
@@ -241,6 +243,7 @@ export class ServiceContainer {
|
||||
importService: ImportServiceAbstraction;
|
||||
importApiService: ImportApiServiceAbstraction;
|
||||
exportService: VaultExportServiceAbstraction;
|
||||
vaultExportApiService: VaultExportApiService;
|
||||
individualExportService: IndividualVaultExportServiceAbstraction;
|
||||
organizationExportService: OrganizationVaultExportServiceAbstraction;
|
||||
searchService: SearchService;
|
||||
@@ -844,9 +847,11 @@ export class ServiceContainer {
|
||||
this.restrictedItemTypesService,
|
||||
);
|
||||
|
||||
this.vaultExportApiService = new DefaultVaultExportApiService(this.apiService);
|
||||
|
||||
this.organizationExportService = new OrganizationVaultExportService(
|
||||
this.cipherService,
|
||||
this.apiService,
|
||||
this.vaultExportApiService,
|
||||
this.pinService,
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
|
||||
@@ -343,6 +343,8 @@ import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
import {
|
||||
IndividualVaultExportService,
|
||||
IndividualVaultExportServiceAbstraction,
|
||||
DefaultVaultExportApiService,
|
||||
VaultExportApiService,
|
||||
OrganizationVaultExportService,
|
||||
OrganizationVaultExportServiceAbstraction,
|
||||
VaultExportService,
|
||||
@@ -887,12 +889,17 @@ const safeProviders: SafeProvider[] = [
|
||||
RestrictedItemTypesService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: VaultExportApiService,
|
||||
useClass: DefaultVaultExportApiService,
|
||||
deps: [ApiServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: OrganizationVaultExportServiceAbstraction,
|
||||
useClass: OrganizationVaultExportService,
|
||||
deps: [
|
||||
CipherServiceAbstraction,
|
||||
ApiServiceAbstraction,
|
||||
VaultExportApiService,
|
||||
PinServiceAbstraction,
|
||||
KeyService,
|
||||
EncryptService,
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
OrganizationConnectionConfigApis,
|
||||
OrganizationConnectionResponse,
|
||||
} from "../admin-console/models/response/organization-connection.response";
|
||||
import { OrganizationExportResponse } from "../admin-console/models/response/organization-export.response";
|
||||
import { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response";
|
||||
import { PreValidateSponsorshipResponse } from "../admin-console/models/response/pre-validate-sponsorship.response";
|
||||
import {
|
||||
@@ -552,5 +551,4 @@ export abstract class ApiService {
|
||||
request: KeyConnectorUserKeyRequest,
|
||||
): Promise<void>;
|
||||
abstract getKeyConnectorAlive(keyConnectorUrl: string): Promise<void>;
|
||||
abstract getOrganizationExport(organizationId: string): Promise<OrganizationExportResponse>;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
OrganizationConnectionConfigApis,
|
||||
OrganizationConnectionResponse,
|
||||
} from "../admin-console/models/response/organization-connection.response";
|
||||
import { OrganizationExportResponse } from "../admin-console/models/response/organization-export.response";
|
||||
import { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response";
|
||||
import { PreValidateSponsorshipResponse } from "../admin-console/models/response/pre-validate-sponsorship.response";
|
||||
import {
|
||||
@@ -1528,17 +1527,6 @@ 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
|
||||
|
||||
async getActiveBearerToken(userId: UserId): Promise<string> {
|
||||
|
||||
@@ -7,3 +7,4 @@ export * from "./services/org-vault-export.service";
|
||||
export * from "./services/individual-vault-export.service.abstraction";
|
||||
export * from "./services/individual-vault-export.service";
|
||||
export * from "./services/export-helper";
|
||||
export * from "./services/api";
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationExportResponse } from "@bitwarden/common/admin-console/models/response/organization-export.response";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { VaultExportApiService } from "./vault-export-api.service.abstraction";
|
||||
|
||||
/**
|
||||
* Service for handling vault export API interactions.
|
||||
* @param apiService - An instance of {@link ApiService} used to make HTTP requests.
|
||||
*/
|
||||
export class DefaultVaultExportApiService implements VaultExportApiService {
|
||||
constructor(private apiService: ApiService) {}
|
||||
|
||||
async getOrganizationExport(organizationId: OrganizationId): Promise<OrganizationExportResponse> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/organizations/" + organizationId + "/export",
|
||||
undefined,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
return new OrganizationExportResponse(r);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./vault-export-api.service.abstraction";
|
||||
export * from "./default-vault-export-api.service";
|
||||
@@ -0,0 +1,13 @@
|
||||
import { OrganizationExportResponse } from "@bitwarden/common/admin-console/models/response/organization-export.response";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
export abstract class VaultExportApiService {
|
||||
/**
|
||||
* Retrieves the export data for a specific organization.
|
||||
* @param organizationId The ID of the organization to export.
|
||||
* @returns A promise that resolves to the organization export response.
|
||||
*/
|
||||
abstract getOrganizationExport(
|
||||
organizationId: OrganizationId,
|
||||
): Promise<OrganizationExportResponse>;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { DefaultVaultExportApiService } from "./default-vault-export-api.service";
|
||||
import { VaultExportApiService } from "./vault-export-api.service.abstraction";
|
||||
|
||||
describe("VaultExportApiService", () => {
|
||||
let apiServiceMock: MockProxy<ApiService>;
|
||||
let sut: VaultExportApiService;
|
||||
|
||||
beforeEach(() => {
|
||||
apiServiceMock = mock<ApiService>();
|
||||
sut = new DefaultVaultExportApiService(apiServiceMock);
|
||||
});
|
||||
|
||||
it("should call apiService.send with correct parameters", async () => {
|
||||
const orgId: OrganizationId = "test-org-id" as OrganizationId;
|
||||
const apiResponse = { foo: "bar" };
|
||||
apiServiceMock.send.mockResolvedValue(apiResponse);
|
||||
|
||||
await sut.getOrganizationExport(orgId);
|
||||
|
||||
expect(apiServiceMock.send).toHaveBeenCalledWith(
|
||||
"GET",
|
||||
`/organizations/${orgId}/export`,
|
||||
undefined,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
CollectionDetailsResponse,
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
@@ -34,6 +33,7 @@ import {
|
||||
ExportedVaultAsString,
|
||||
} from "../types";
|
||||
|
||||
import { VaultExportApiService } from "./api/vault-export-api.service.abstraction";
|
||||
import { BaseVaultExportService } from "./base-vault-export.service";
|
||||
import { ExportHelper } from "./export-helper";
|
||||
import { OrganizationVaultExportServiceAbstraction } from "./org-vault-export.service.abstraction";
|
||||
@@ -45,7 +45,7 @@ export class OrganizationVaultExportService
|
||||
{
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private apiService: ApiService,
|
||||
private vaultExportApiService: VaultExportApiService,
|
||||
pinService: PinServiceAbstraction,
|
||||
private keyService: KeyService,
|
||||
encryptService: EncryptService,
|
||||
@@ -138,44 +138,49 @@ export class OrganizationVaultExportService
|
||||
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
|
||||
|
||||
promises.push(
|
||||
this.apiService.getOrganizationExport(organizationId).then((exportData) => {
|
||||
const exportPromises: any = [];
|
||||
if (exportData != null) {
|
||||
if (exportData.collections != null && exportData.collections.length > 0) {
|
||||
exportData.collections.forEach((c) => {
|
||||
const collection = Collection.fromCollectionData(
|
||||
new CollectionData(c as CollectionDetailsResponse),
|
||||
);
|
||||
exportPromises.push(
|
||||
firstValueFrom(this.keyService.activeUserOrgKeys$)
|
||||
.then((keys) =>
|
||||
collection.decrypt(keys[organizationId as OrganizationId], this.encryptService),
|
||||
)
|
||||
.then((decCol) => {
|
||||
decCollections.push(decCol);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
if (exportData.ciphers != null && exportData.ciphers.length > 0) {
|
||||
exportData.ciphers
|
||||
.filter((c) => c.deletedDate === null)
|
||||
.forEach(async (c) => {
|
||||
const cipher = new Cipher(new CipherData(c));
|
||||
this.vaultExportApiService
|
||||
.getOrganizationExport(organizationId as OrganizationId)
|
||||
.then((exportData) => {
|
||||
const exportPromises: Promise<void>[] = [];
|
||||
if (exportData != null) {
|
||||
if (exportData.collections != null && exportData.collections.length > 0) {
|
||||
exportData.collections.forEach((c) => {
|
||||
const collection = Collection.fromCollectionData(
|
||||
new CollectionData(c as CollectionDetailsResponse),
|
||||
);
|
||||
exportPromises.push(
|
||||
this.cipherService.decrypt(cipher, activeUserId).then((decCipher) => {
|
||||
if (
|
||||
!this.restrictedItemTypesService.isCipherRestricted(decCipher, restrictions)
|
||||
) {
|
||||
decCiphers.push(decCipher);
|
||||
}
|
||||
}),
|
||||
firstValueFrom(this.keyService.activeUserOrgKeys$)
|
||||
.then((keys) =>
|
||||
collection.decrypt(
|
||||
keys[organizationId as OrganizationId],
|
||||
this.encryptService,
|
||||
),
|
||||
)
|
||||
.then((decCol) => {
|
||||
decCollections.push(decCol);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
if (exportData.ciphers != null && exportData.ciphers.length > 0) {
|
||||
exportData.ciphers
|
||||
.filter((c) => c.deletedDate === null)
|
||||
.forEach(async (c) => {
|
||||
const cipher = new Cipher(new CipherData(c));
|
||||
exportPromises.push(
|
||||
this.cipherService.decrypt(cipher, activeUserId).then((decCipher) => {
|
||||
if (
|
||||
!this.restrictedItemTypesService.isCipherRestricted(decCipher, restrictions)
|
||||
) {
|
||||
decCiphers.push(decCipher);
|
||||
}
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.all(exportPromises);
|
||||
}),
|
||||
return Promise.all(exportPromises);
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
@@ -188,39 +193,37 @@ export class OrganizationVaultExportService
|
||||
|
||||
private async getOrganizationEncryptedExport(organizationId: string): Promise<string> {
|
||||
const collections: Collection[] = [];
|
||||
let ciphers: Cipher[] = [];
|
||||
const promises = [];
|
||||
|
||||
promises.push(
|
||||
this.apiService.getCollections(organizationId).then((c) => {
|
||||
if (c != null && c.data != null && c.data.length > 0) {
|
||||
c.data.forEach((r) => {
|
||||
const collection = Collection.fromCollectionData(
|
||||
new CollectionData(r as CollectionDetailsResponse),
|
||||
);
|
||||
collections.push(collection);
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
const ciphers: Cipher[] = [];
|
||||
|
||||
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
|
||||
|
||||
promises.push(
|
||||
this.apiService.getCiphersOrganization(organizationId).then((c) => {
|
||||
if (c != null && c.data != null && c.data.length > 0) {
|
||||
ciphers = c.data
|
||||
.filter((item) => item.deletedDate === null)
|
||||
.map((item) => new Cipher(new CipherData(item)))
|
||||
.filter(
|
||||
(cipher) => !this.restrictedItemTypesService.isCipherRestricted(cipher, restrictions),
|
||||
);
|
||||
}
|
||||
}),
|
||||
const exportData = await this.vaultExportApiService.getOrganizationExport(
|
||||
organizationId as OrganizationId,
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
if (exportData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (exportData.collections != null && exportData.collections.length > 0) {
|
||||
exportData.collections.forEach((c) => {
|
||||
const collection = Collection.fromCollectionData(
|
||||
new CollectionData(c as CollectionDetailsResponse),
|
||||
);
|
||||
collections.push(collection);
|
||||
});
|
||||
}
|
||||
|
||||
if (exportData.ciphers != null && exportData.ciphers.length > 0) {
|
||||
exportData.ciphers
|
||||
.filter((c) => c.deletedDate === null)
|
||||
.forEach((c) => {
|
||||
const cipher = new Cipher(new CipherData(c));
|
||||
if (!this.restrictedItemTypesService.isCipherRestricted(cipher, restrictions)) {
|
||||
ciphers.push(cipher);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this.BuildEncryptedExport(organizationId, collections, ciphers);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user