mirror of
https://github.com/bitwarden/browser
synced 2026-02-12 06:23:38 +00:00
Merge remote-tracking branch 'origin/main' into playwright
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -12,7 +12,7 @@ import { KdfConfig, KdfConfigService, KdfType } from "@bitwarden/key-management"
|
||||
import { BitwardenCsvExportType, BitwardenPasswordProtectedFileFormat } from "../types";
|
||||
export class BaseVaultExportService {
|
||||
constructor(
|
||||
protected pinService: PinServiceAbstraction,
|
||||
protected keyGenerationService: KeyGenerationService,
|
||||
protected encryptService: EncryptService,
|
||||
private cryptoFunctionService: CryptoFunctionService,
|
||||
private kdfConfigService: KdfConfigService,
|
||||
@@ -26,7 +26,8 @@ export class BaseVaultExportService {
|
||||
const kdfConfig: KdfConfig = await this.kdfConfigService.getKdfConfig(userId);
|
||||
|
||||
const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16));
|
||||
const key = await this.pinService.makePinKey(password, salt, kdfConfig);
|
||||
|
||||
const key = await this.keyGenerationService.deriveVaultExportKey(password, salt, kdfConfig);
|
||||
|
||||
const encKeyValidation = await this.encryptService.encryptString(Utils.newGuid(), key);
|
||||
const encText = await this.encryptService.encryptString(clearText, key);
|
||||
|
||||
@@ -3,13 +3,13 @@ import * as JSZip from "jszip";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import {
|
||||
EncryptedString,
|
||||
EncString,
|
||||
} from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherId, emptyGuid, UserId } from "@bitwarden/common/types/guid";
|
||||
@@ -169,7 +169,7 @@ describe("VaultExportService", () => {
|
||||
let exportService: IndividualVaultExportService;
|
||||
let cryptoFunctionService: MockProxy<CryptoFunctionService>;
|
||||
let cipherService: MockProxy<CipherService>;
|
||||
let pinService: MockProxy<PinServiceAbstraction>;
|
||||
let keyGenerationService: MockProxy<KeyGenerationService>;
|
||||
let folderService: MockProxy<FolderService>;
|
||||
let keyService: MockProxy<KeyService>;
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
@@ -184,7 +184,7 @@ describe("VaultExportService", () => {
|
||||
beforeEach(() => {
|
||||
cryptoFunctionService = mock<CryptoFunctionService>();
|
||||
cipherService = mock<CipherService>();
|
||||
pinService = mock<PinServiceAbstraction>();
|
||||
keyGenerationService = mock<KeyGenerationService>();
|
||||
folderService = mock<FolderService>();
|
||||
keyService = mock<KeyService>();
|
||||
encryptService = mock<EncryptService>();
|
||||
@@ -220,7 +220,7 @@ describe("VaultExportService", () => {
|
||||
exportService = new IndividualVaultExportService(
|
||||
folderService,
|
||||
cipherService,
|
||||
pinService,
|
||||
keyGenerationService,
|
||||
keyService,
|
||||
encryptService,
|
||||
cryptoFunctionService,
|
||||
@@ -525,6 +525,20 @@ describe("VaultExportService", () => {
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
expectEqualFolders(UserFolders, exportedData.data);
|
||||
});
|
||||
|
||||
it("does not export the key property in unencrypted exports", async () => {
|
||||
// Create a cipher with a key property
|
||||
const cipherWithKey = generateCipherView(false);
|
||||
(cipherWithKey as any).key = "shouldBeDeleted";
|
||||
cipherService.getAllDecrypted.mockResolvedValue([cipherWithKey]);
|
||||
|
||||
const actual = await exportService.getExport(userId, "json");
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
const parsed = JSON.parse(exportedData.data);
|
||||
expect(parsed.items.length).toBe(1);
|
||||
expect(parsed.items[0].key).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
export class FolderResponse {
|
||||
|
||||
@@ -5,9 +5,9 @@ import * as papa from "papaparse";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||
@@ -42,7 +42,7 @@ export class IndividualVaultExportService
|
||||
constructor(
|
||||
private folderService: FolderService,
|
||||
private cipherService: CipherService,
|
||||
pinService: PinServiceAbstraction,
|
||||
keyGenerationService: KeyGenerationService,
|
||||
private keyService: KeyService,
|
||||
encryptService: EncryptService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
@@ -50,7 +50,7 @@ export class IndividualVaultExportService
|
||||
private apiService: ApiService,
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
) {
|
||||
super(pinService, encryptService, cryptoFunctionService, kdfConfigService);
|
||||
super(keyGenerationService, encryptService, cryptoFunctionService, kdfConfigService);
|
||||
}
|
||||
|
||||
/** Creates an export of an individual vault (My Vault). Based on the provided format it will either be unencrypted, encrypted or password protected and in case zip is selected will include attachments
|
||||
@@ -317,6 +317,7 @@ export class IndividualVaultExportService
|
||||
const cipher = new CipherWithIdExport();
|
||||
cipher.build(c);
|
||||
cipher.collectionIds = null;
|
||||
delete cipher.key;
|
||||
jsonDoc.items.push(cipher);
|
||||
});
|
||||
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
import * as papa from "papaparse";
|
||||
import { filter, firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
CollectionService,
|
||||
CollectionData,
|
||||
Collection,
|
||||
CollectionDetailsResponse,
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
CollectionDetailsResponse,
|
||||
Collection,
|
||||
CollectionData,
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
@@ -46,7 +46,7 @@ export class OrganizationVaultExportService
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private vaultExportApiService: VaultExportApiService,
|
||||
pinService: PinServiceAbstraction,
|
||||
keyGenerationService: KeyGenerationService,
|
||||
private keyService: KeyService,
|
||||
encryptService: EncryptService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
@@ -54,7 +54,7 @@ export class OrganizationVaultExportService
|
||||
kdfConfigService: KdfConfigService,
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
) {
|
||||
super(pinService, encryptService, cryptoFunctionService, kdfConfigService);
|
||||
super(keyGenerationService, encryptService, cryptoFunctionService, kdfConfigService);
|
||||
}
|
||||
|
||||
/** Creates a password protected export of an organizational vault.
|
||||
@@ -383,6 +383,7 @@ export class OrganizationVaultExportService
|
||||
decCiphers.forEach((c) => {
|
||||
const cipher = new CipherWithIdExport();
|
||||
cipher.build(c);
|
||||
delete cipher.key;
|
||||
jsonDoc.items.push(cipher);
|
||||
});
|
||||
return JSON.stringify(jsonDoc, null, " ");
|
||||
|
||||
@@ -5,42 +5,48 @@ import {
|
||||
} from "@bitwarden/common/models/export";
|
||||
|
||||
// Base
|
||||
export type BitwardenJsonExport = {
|
||||
encrypted: boolean;
|
||||
items: CipherWithIdExport[];
|
||||
};
|
||||
export type BitwardenJsonExport = BitwardenUnEncryptedJsonExport | BitwardenEncryptedJsonExport;
|
||||
|
||||
// Decrypted
|
||||
export type BitwardenUnEncryptedJsonExport = BitwardenJsonExport & {
|
||||
encrypted: false;
|
||||
};
|
||||
export type BitwardenUnEncryptedJsonExport =
|
||||
| BitwardenUnEncryptedIndividualJsonExport
|
||||
| BitwardenUnEncryptedOrgJsonExport;
|
||||
|
||||
export type BitwardenUnEncryptedIndividualJsonExport = BitwardenUnEncryptedJsonExport & {
|
||||
export type BitwardenUnEncryptedIndividualJsonExport = {
|
||||
encrypted: false;
|
||||
items: CipherWithIdExport[];
|
||||
folders: FolderWithIdExport[];
|
||||
};
|
||||
|
||||
export type BitwardenUnEncryptedOrgJsonExport = BitwardenUnEncryptedJsonExport & {
|
||||
export type BitwardenUnEncryptedOrgJsonExport = {
|
||||
encrypted: false;
|
||||
items: CipherWithIdExport[];
|
||||
collections: CollectionWithIdExport[];
|
||||
};
|
||||
|
||||
// Account-encrypted
|
||||
export type BitwardenEncryptedJsonExport = BitwardenJsonExport & {
|
||||
export type BitwardenEncryptedJsonExport =
|
||||
| BitwardenEncryptedIndividualJsonExport
|
||||
| BitwardenEncryptedOrgJsonExport;
|
||||
|
||||
export type BitwardenEncryptedIndividualJsonExport = {
|
||||
encrypted: true;
|
||||
encKeyValidation_DO_NOT_EDIT: string;
|
||||
};
|
||||
|
||||
export type BitwardenEncryptedIndividualJsonExport = BitwardenEncryptedJsonExport & {
|
||||
items: CipherWithIdExport[];
|
||||
folders: FolderWithIdExport[];
|
||||
};
|
||||
|
||||
export type BitwardenEncryptedOrgJsonExport = BitwardenEncryptedJsonExport & {
|
||||
export type BitwardenEncryptedOrgJsonExport = {
|
||||
encrypted: true;
|
||||
encKeyValidation_DO_NOT_EDIT: string;
|
||||
items: CipherWithIdExport[];
|
||||
collections: CollectionWithIdExport[];
|
||||
};
|
||||
|
||||
// Password-protected
|
||||
export type BitwardenPasswordProtectedFileFormat = {
|
||||
encrypted: boolean;
|
||||
passwordProtected: boolean;
|
||||
encrypted: true;
|
||||
passwordProtected: true;
|
||||
salt: string;
|
||||
kdfIterations: number;
|
||||
kdfMemory?: number;
|
||||
@@ -49,3 +55,50 @@ export type BitwardenPasswordProtectedFileFormat = {
|
||||
encKeyValidation_DO_NOT_EDIT: string;
|
||||
data: string;
|
||||
};
|
||||
|
||||
// Unencrypted type guards
|
||||
export function isUnencrypted(
|
||||
data: BitwardenJsonExport | null | undefined,
|
||||
): data is BitwardenUnEncryptedJsonExport {
|
||||
return data != null && (data as { encrypted?: unknown }).encrypted !== true;
|
||||
}
|
||||
|
||||
export function isIndividualUnEncrypted(
|
||||
data: BitwardenJsonExport | null | undefined,
|
||||
): data is BitwardenUnEncryptedIndividualJsonExport {
|
||||
return isUnencrypted(data) && (data as { folders?: unknown }).folders != null;
|
||||
}
|
||||
|
||||
export function isOrgUnEncrypted(
|
||||
data: BitwardenJsonExport | null | undefined,
|
||||
): data is BitwardenUnEncryptedOrgJsonExport {
|
||||
return isUnencrypted(data) && (data as { collections?: unknown }).collections != null;
|
||||
}
|
||||
|
||||
// Encrypted type guards
|
||||
export function isEncrypted(
|
||||
data: BitwardenJsonExport | null | undefined,
|
||||
): data is BitwardenEncryptedJsonExport {
|
||||
return data != null && (data as { encrypted?: unknown }).encrypted === true;
|
||||
}
|
||||
export function isPasswordProtected(
|
||||
data: BitwardenPasswordProtectedFileFormat | BitwardenJsonExport | null | undefined,
|
||||
): data is BitwardenPasswordProtectedFileFormat {
|
||||
return (
|
||||
data != null &&
|
||||
(data as { encrypted?: unknown }).encrypted === true &&
|
||||
(data as { passwordProtected?: unknown }).passwordProtected === true
|
||||
);
|
||||
}
|
||||
|
||||
export function isIndividualEncrypted(
|
||||
data: BitwardenJsonExport | null | undefined,
|
||||
): data is BitwardenEncryptedIndividualJsonExport {
|
||||
return isEncrypted(data) && (data as { folders?: unknown }).folders != null;
|
||||
}
|
||||
|
||||
export function isOrgEncrypted(
|
||||
data: BitwardenJsonExport | null | undefined,
|
||||
): data is BitwardenEncryptedOrgJsonExport {
|
||||
return isEncrypted(data) && (data as { collections?: unknown }).collections != null;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
firstValueFrom,
|
||||
from,
|
||||
map,
|
||||
merge,
|
||||
Observable,
|
||||
@@ -43,8 +42,6 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ClientType, EventType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -100,7 +97,6 @@ import { ExportScopeCalloutComponent } from "./export-scope-callout.component";
|
||||
})
|
||||
export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
private _organizationId$ = new BehaviorSubject<OrganizationId | undefined>(undefined);
|
||||
private createDefaultLocationFlagEnabled$: Observable<boolean>;
|
||||
private _showExcludeMyItems = false;
|
||||
|
||||
/**
|
||||
@@ -259,13 +255,11 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
protected organizationService: OrganizationService,
|
||||
private accountService: AccountService,
|
||||
private collectionService: CollectionService,
|
||||
private configService: ConfigService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
@Optional() private router?: Router,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.observeFeatureFlags();
|
||||
this.observeFormState();
|
||||
this.observePolicyStatus();
|
||||
this.observeFormSelections();
|
||||
@@ -286,12 +280,6 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
this.setupPolicyBasedFormState();
|
||||
}
|
||||
|
||||
private observeFeatureFlags(): void {
|
||||
this.createDefaultLocationFlagEnabled$ = from(
|
||||
this.configService.getFeatureFlag(FeatureFlag.CreateDefaultLocation),
|
||||
).pipe(shareReplay({ bufferSize: 1, refCount: true }));
|
||||
}
|
||||
|
||||
private observeFormState(): void {
|
||||
this.exportForm.statusChanges.pipe(takeUntil(this.destroy$)).subscribe((c) => {
|
||||
this.formDisabled.emit(c === "DISABLED");
|
||||
@@ -380,32 +368,24 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
/**
|
||||
* Determine value of showExcludeMyItems. Returns true when:
|
||||
* CreateDefaultLocation feature flag is on
|
||||
* AND organizationDataOwnershipPolicy is enabled for the selected organization
|
||||
* organizationDataOwnershipPolicy is enabled for the selected organization
|
||||
* AND a valid OrganizationId is present (not exporting from individual vault)
|
||||
*/
|
||||
private observeMyItemsExclusionCriteria(): void {
|
||||
combineLatest({
|
||||
createDefaultLocationFlagEnabled: this.createDefaultLocationFlagEnabled$,
|
||||
organizationDataOwnershipPolicyEnabledForOrg:
|
||||
this.organizationDataOwnershipPolicyEnabledForOrg$,
|
||||
organizationId: this._organizationId$,
|
||||
})
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(
|
||||
({
|
||||
createDefaultLocationFlagEnabled,
|
||||
organizationDataOwnershipPolicyEnabledForOrg,
|
||||
organizationId,
|
||||
}) => {
|
||||
if (!createDefaultLocationFlagEnabled || !organizationId) {
|
||||
this._showExcludeMyItems = false;
|
||||
return;
|
||||
}
|
||||
.subscribe(({ organizationDataOwnershipPolicyEnabledForOrg, organizationId }) => {
|
||||
if (!organizationId) {
|
||||
this._showExcludeMyItems = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._showExcludeMyItems = organizationDataOwnershipPolicyEnabledForOrg;
|
||||
},
|
||||
);
|
||||
this._showExcludeMyItems = organizationDataOwnershipPolicyEnabledForOrg;
|
||||
});
|
||||
}
|
||||
|
||||
// Setup validator adjustments based on format and encryption type changes
|
||||
@@ -620,7 +600,7 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
title: "confirmVaultExport",
|
||||
bodyText: confirmDescription,
|
||||
confirmButtonOptions: {
|
||||
text: "exportVault",
|
||||
text: "continue",
|
||||
type: "primary",
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user