mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +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,
|
IndividualVaultExportServiceAbstraction,
|
||||||
OrganizationVaultExportService,
|
OrganizationVaultExportService,
|
||||||
OrganizationVaultExportServiceAbstraction,
|
OrganizationVaultExportServiceAbstraction,
|
||||||
|
DefaultVaultExportApiService,
|
||||||
|
VaultExportApiService,
|
||||||
VaultExportService,
|
VaultExportService,
|
||||||
VaultExportServiceAbstraction,
|
VaultExportServiceAbstraction,
|
||||||
} from "@bitwarden/vault-export-core";
|
} from "@bitwarden/vault-export-core";
|
||||||
@@ -362,6 +364,7 @@ export default class MainBackground {
|
|||||||
loginEmailService: LoginEmailServiceAbstraction;
|
loginEmailService: LoginEmailServiceAbstraction;
|
||||||
importApiService: ImportApiServiceAbstraction;
|
importApiService: ImportApiServiceAbstraction;
|
||||||
importService: ImportServiceAbstraction;
|
importService: ImportServiceAbstraction;
|
||||||
|
exportApiService: VaultExportApiService;
|
||||||
exportService: VaultExportServiceAbstraction;
|
exportService: VaultExportServiceAbstraction;
|
||||||
searchService: SearchServiceAbstraction;
|
searchService: SearchServiceAbstraction;
|
||||||
serverNotificationsService: ServerNotificationsService;
|
serverNotificationsService: ServerNotificationsService;
|
||||||
@@ -1100,9 +1103,11 @@ export default class MainBackground {
|
|||||||
this.restrictedItemTypesService,
|
this.restrictedItemTypesService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.exportApiService = new DefaultVaultExportApiService(this.apiService);
|
||||||
|
|
||||||
this.organizationVaultExportService = new OrganizationVaultExportService(
|
this.organizationVaultExportService = new OrganizationVaultExportService(
|
||||||
this.cipherService,
|
this.cipherService,
|
||||||
this.apiService,
|
this.exportApiService,
|
||||||
this.pinService,
|
this.pinService,
|
||||||
this.keyService,
|
this.keyService,
|
||||||
this.encryptService,
|
this.encryptService,
|
||||||
|
|||||||
@@ -179,10 +179,12 @@ import { SerializedMemoryStorageService } from "@bitwarden/storage-core";
|
|||||||
import {
|
import {
|
||||||
IndividualVaultExportService,
|
IndividualVaultExportService,
|
||||||
IndividualVaultExportServiceAbstraction,
|
IndividualVaultExportServiceAbstraction,
|
||||||
|
VaultExportApiService,
|
||||||
OrganizationVaultExportService,
|
OrganizationVaultExportService,
|
||||||
OrganizationVaultExportServiceAbstraction,
|
OrganizationVaultExportServiceAbstraction,
|
||||||
VaultExportService,
|
VaultExportService,
|
||||||
VaultExportServiceAbstraction,
|
VaultExportServiceAbstraction,
|
||||||
|
DefaultVaultExportApiService,
|
||||||
} from "@bitwarden/vault-export-core";
|
} from "@bitwarden/vault-export-core";
|
||||||
|
|
||||||
import { CliBiometricsService } from "../key-management/cli-biometrics-service";
|
import { CliBiometricsService } from "../key-management/cli-biometrics-service";
|
||||||
@@ -241,6 +243,7 @@ export class ServiceContainer {
|
|||||||
importService: ImportServiceAbstraction;
|
importService: ImportServiceAbstraction;
|
||||||
importApiService: ImportApiServiceAbstraction;
|
importApiService: ImportApiServiceAbstraction;
|
||||||
exportService: VaultExportServiceAbstraction;
|
exportService: VaultExportServiceAbstraction;
|
||||||
|
vaultExportApiService: VaultExportApiService;
|
||||||
individualExportService: IndividualVaultExportServiceAbstraction;
|
individualExportService: IndividualVaultExportServiceAbstraction;
|
||||||
organizationExportService: OrganizationVaultExportServiceAbstraction;
|
organizationExportService: OrganizationVaultExportServiceAbstraction;
|
||||||
searchService: SearchService;
|
searchService: SearchService;
|
||||||
@@ -844,9 +847,11 @@ export class ServiceContainer {
|
|||||||
this.restrictedItemTypesService,
|
this.restrictedItemTypesService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.vaultExportApiService = new DefaultVaultExportApiService(this.apiService);
|
||||||
|
|
||||||
this.organizationExportService = new OrganizationVaultExportService(
|
this.organizationExportService = new OrganizationVaultExportService(
|
||||||
this.cipherService,
|
this.cipherService,
|
||||||
this.apiService,
|
this.vaultExportApiService,
|
||||||
this.pinService,
|
this.pinService,
|
||||||
this.keyService,
|
this.keyService,
|
||||||
this.encryptService,
|
this.encryptService,
|
||||||
|
|||||||
@@ -343,6 +343,8 @@ import { PasswordRepromptService } from "@bitwarden/vault";
|
|||||||
import {
|
import {
|
||||||
IndividualVaultExportService,
|
IndividualVaultExportService,
|
||||||
IndividualVaultExportServiceAbstraction,
|
IndividualVaultExportServiceAbstraction,
|
||||||
|
DefaultVaultExportApiService,
|
||||||
|
VaultExportApiService,
|
||||||
OrganizationVaultExportService,
|
OrganizationVaultExportService,
|
||||||
OrganizationVaultExportServiceAbstraction,
|
OrganizationVaultExportServiceAbstraction,
|
||||||
VaultExportService,
|
VaultExportService,
|
||||||
@@ -887,12 +889,17 @@ const safeProviders: SafeProvider[] = [
|
|||||||
RestrictedItemTypesService,
|
RestrictedItemTypesService,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: VaultExportApiService,
|
||||||
|
useClass: DefaultVaultExportApiService,
|
||||||
|
deps: [ApiServiceAbstraction],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: OrganizationVaultExportServiceAbstraction,
|
provide: OrganizationVaultExportServiceAbstraction,
|
||||||
useClass: OrganizationVaultExportService,
|
useClass: OrganizationVaultExportService,
|
||||||
deps: [
|
deps: [
|
||||||
CipherServiceAbstraction,
|
CipherServiceAbstraction,
|
||||||
ApiServiceAbstraction,
|
VaultExportApiService,
|
||||||
PinServiceAbstraction,
|
PinServiceAbstraction,
|
||||||
KeyService,
|
KeyService,
|
||||||
EncryptService,
|
EncryptService,
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import {
|
|||||||
OrganizationConnectionConfigApis,
|
OrganizationConnectionConfigApis,
|
||||||
OrganizationConnectionResponse,
|
OrganizationConnectionResponse,
|
||||||
} from "../admin-console/models/response/organization-connection.response";
|
} 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 { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response";
|
||||||
import { PreValidateSponsorshipResponse } from "../admin-console/models/response/pre-validate-sponsorship.response";
|
import { PreValidateSponsorshipResponse } from "../admin-console/models/response/pre-validate-sponsorship.response";
|
||||||
import {
|
import {
|
||||||
@@ -552,5 +551,4 @@ export abstract class ApiService {
|
|||||||
request: KeyConnectorUserKeyRequest,
|
request: KeyConnectorUserKeyRequest,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
abstract getKeyConnectorAlive(keyConnectorUrl: string): Promise<void>;
|
abstract getKeyConnectorAlive(keyConnectorUrl: string): Promise<void>;
|
||||||
abstract getOrganizationExport(organizationId: string): Promise<OrganizationExportResponse>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import {
|
|||||||
OrganizationConnectionConfigApis,
|
OrganizationConnectionConfigApis,
|
||||||
OrganizationConnectionResponse,
|
OrganizationConnectionResponse,
|
||||||
} from "../admin-console/models/response/organization-connection.response";
|
} 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 { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response";
|
||||||
import { PreValidateSponsorshipResponse } from "../admin-console/models/response/pre-validate-sponsorship.response";
|
import { PreValidateSponsorshipResponse } from "../admin-console/models/response/pre-validate-sponsorship.response";
|
||||||
import {
|
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
|
// Helpers
|
||||||
|
|
||||||
async getActiveBearerToken(userId: UserId): Promise<string> {
|
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.abstraction";
|
||||||
export * from "./services/individual-vault-export.service";
|
export * from "./services/individual-vault-export.service";
|
||||||
export * from "./services/export-helper";
|
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,
|
CollectionDetailsResponse,
|
||||||
CollectionView,
|
CollectionView,
|
||||||
} from "@bitwarden/admin-console/common";
|
} from "@bitwarden/admin-console/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||||
@@ -34,6 +33,7 @@ import {
|
|||||||
ExportedVaultAsString,
|
ExportedVaultAsString,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
|
import { VaultExportApiService } from "./api/vault-export-api.service.abstraction";
|
||||||
import { BaseVaultExportService } from "./base-vault-export.service";
|
import { BaseVaultExportService } from "./base-vault-export.service";
|
||||||
import { ExportHelper } from "./export-helper";
|
import { ExportHelper } from "./export-helper";
|
||||||
import { OrganizationVaultExportServiceAbstraction } from "./org-vault-export.service.abstraction";
|
import { OrganizationVaultExportServiceAbstraction } from "./org-vault-export.service.abstraction";
|
||||||
@@ -45,7 +45,7 @@ export class OrganizationVaultExportService
|
|||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private apiService: ApiService,
|
private vaultExportApiService: VaultExportApiService,
|
||||||
pinService: PinServiceAbstraction,
|
pinService: PinServiceAbstraction,
|
||||||
private keyService: KeyService,
|
private keyService: KeyService,
|
||||||
encryptService: EncryptService,
|
encryptService: EncryptService,
|
||||||
@@ -138,8 +138,10 @@ export class OrganizationVaultExportService
|
|||||||
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
|
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.apiService.getOrganizationExport(organizationId).then((exportData) => {
|
this.vaultExportApiService
|
||||||
const exportPromises: any = [];
|
.getOrganizationExport(organizationId as OrganizationId)
|
||||||
|
.then((exportData) => {
|
||||||
|
const exportPromises: Promise<void>[] = [];
|
||||||
if (exportData != null) {
|
if (exportData != null) {
|
||||||
if (exportData.collections != null && exportData.collections.length > 0) {
|
if (exportData.collections != null && exportData.collections.length > 0) {
|
||||||
exportData.collections.forEach((c) => {
|
exportData.collections.forEach((c) => {
|
||||||
@@ -149,7 +151,10 @@ export class OrganizationVaultExportService
|
|||||||
exportPromises.push(
|
exportPromises.push(
|
||||||
firstValueFrom(this.keyService.activeUserOrgKeys$)
|
firstValueFrom(this.keyService.activeUserOrgKeys$)
|
||||||
.then((keys) =>
|
.then((keys) =>
|
||||||
collection.decrypt(keys[organizationId as OrganizationId], this.encryptService),
|
collection.decrypt(
|
||||||
|
keys[organizationId as OrganizationId],
|
||||||
|
this.encryptService,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.then((decCol) => {
|
.then((decCol) => {
|
||||||
decCollections.push(decCol);
|
decCollections.push(decCol);
|
||||||
@@ -188,39 +193,37 @@ export class OrganizationVaultExportService
|
|||||||
|
|
||||||
private async getOrganizationEncryptedExport(organizationId: string): Promise<string> {
|
private async getOrganizationEncryptedExport(organizationId: string): Promise<string> {
|
||||||
const collections: Collection[] = [];
|
const collections: Collection[] = [];
|
||||||
let ciphers: Cipher[] = [];
|
const ciphers: Cipher[] = [];
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
promises.push(
|
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
|
||||||
this.apiService.getCollections(organizationId).then((c) => {
|
|
||||||
if (c != null && c.data != null && c.data.length > 0) {
|
const exportData = await this.vaultExportApiService.getOrganizationExport(
|
||||||
c.data.forEach((r) => {
|
organizationId as OrganizationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (exportData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportData.collections != null && exportData.collections.length > 0) {
|
||||||
|
exportData.collections.forEach((c) => {
|
||||||
const collection = Collection.fromCollectionData(
|
const collection = Collection.fromCollectionData(
|
||||||
new CollectionData(r as CollectionDetailsResponse),
|
new CollectionData(c as CollectionDetailsResponse),
|
||||||
);
|
);
|
||||||
collections.push(collection);
|
collections.push(collection);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
|
if (exportData.ciphers != null && exportData.ciphers.length > 0) {
|
||||||
|
exportData.ciphers
|
||||||
promises.push(
|
.filter((c) => c.deletedDate === null)
|
||||||
this.apiService.getCiphersOrganization(organizationId).then((c) => {
|
.forEach((c) => {
|
||||||
if (c != null && c.data != null && c.data.length > 0) {
|
const cipher = new Cipher(new CipherData(c));
|
||||||
ciphers = c.data
|
if (!this.restrictedItemTypesService.isCipherRestricted(cipher, restrictions)) {
|
||||||
.filter((item) => item.deletedDate === null)
|
ciphers.push(cipher);
|
||||||
.map((item) => new Cipher(new CipherData(item)))
|
}
|
||||||
.filter(
|
});
|
||||||
(cipher) => !this.restrictedItemTypesService.isCipherRestricted(cipher, restrictions),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
return this.BuildEncryptedExport(organizationId, collections, ciphers);
|
return this.BuildEncryptedExport(organizationId, collections, ciphers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user