mirror of
https://github.com/bitwarden/browser
synced 2026-02-27 10:03:23 +00:00
Merge branch 'main' of https://github.com/bitwarden/clients into vault/pm-30304/cipher-sdk-get
This commit is contained in:
@@ -27,6 +27,10 @@ export class UserDecryptionOptionsResponse extends BaseResponse {
|
||||
masterPasswordUnlock?: MasterPasswordUnlockResponse;
|
||||
trustedDeviceOption?: TrustedDeviceUserDecryptionOptionResponse;
|
||||
keyConnectorOption?: KeyConnectorUserDecryptionOptionResponse;
|
||||
/**
|
||||
* The IdTokenresponse only returns a single WebAuthn PRF option.
|
||||
* To support immediate unlock after logging in with the same PRF passkey.
|
||||
*/
|
||||
webAuthnPrfOption?: WebAuthnPrfDecryptionOptionResponse;
|
||||
|
||||
constructor(response: IUserDecryptionOptionsServerResponse) {
|
||||
|
||||
@@ -6,19 +6,30 @@ import { BaseResponse } from "../../../../models/response/base.response";
|
||||
export interface IWebAuthnPrfDecryptionOptionServerResponse {
|
||||
EncryptedPrivateKey: string;
|
||||
EncryptedUserKey: string;
|
||||
CredentialId: string;
|
||||
Transports: string[];
|
||||
}
|
||||
|
||||
export class WebAuthnPrfDecryptionOptionResponse extends BaseResponse {
|
||||
encryptedPrivateKey: EncString;
|
||||
encryptedUserKey: EncString;
|
||||
credentialId: string;
|
||||
transports: string[];
|
||||
|
||||
constructor(response: IWebAuthnPrfDecryptionOptionServerResponse) {
|
||||
super(response);
|
||||
if (response.EncryptedPrivateKey) {
|
||||
this.encryptedPrivateKey = new EncString(this.getResponseProperty("EncryptedPrivateKey"));
|
||||
|
||||
const encPrivateKey = this.getResponseProperty("EncryptedPrivateKey");
|
||||
if (encPrivateKey) {
|
||||
this.encryptedPrivateKey = new EncString(encPrivateKey);
|
||||
}
|
||||
if (response.EncryptedUserKey) {
|
||||
this.encryptedUserKey = new EncString(this.getResponseProperty("EncryptedUserKey"));
|
||||
|
||||
const encUserKey = this.getResponseProperty("EncryptedUserKey");
|
||||
if (encUserKey) {
|
||||
this.encryptedUserKey = new EncString(encUserKey);
|
||||
}
|
||||
|
||||
this.credentialId = this.getResponseProperty("CredentialId");
|
||||
this.transports = this.getResponseProperty("Transports") || [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ export enum FeatureFlag {
|
||||
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
|
||||
EnrollAeadOnKeyRotation = "enroll-aead-on-key-rotation",
|
||||
ForceUpdateKDFSettings = "pm-18021-force-update-kdf-settings",
|
||||
PM25174_DisableType0Decryption = "pm-25174-disable-type-0-decryption",
|
||||
LinuxBiometricsV2 = "pm-26340-linux-biometrics-v2",
|
||||
NoLogoutOnKdfChange = "pm-23995-no-logout-on-kdf-change",
|
||||
PasskeyUnlock = "pm-2035-passkey-unlock",
|
||||
DataRecoveryTool = "pm-28813-data-recovery-tool",
|
||||
ConsolidatedSessionTimeoutComponent = "pm-26056-consolidated-session-timeout-component",
|
||||
PM27279_V2RegistrationTdeJit = "pm-27279-v2-registration-tde-jit",
|
||||
@@ -57,6 +57,7 @@ export enum FeatureFlag {
|
||||
|
||||
/* DIRT */
|
||||
EventManagementForDataDogAndCrowdStrike = "event-management-for-datadog-and-crowdstrike",
|
||||
EventManagementForHuntress = "event-management-for-huntress",
|
||||
PhishingDetection = "phishing-detection",
|
||||
|
||||
/* Vault */
|
||||
@@ -64,8 +65,6 @@ export enum FeatureFlag {
|
||||
PM22134SdkCipherListView = "pm-22134-sdk-cipher-list-view",
|
||||
PM22136_SdkCipherEncryption = "pm-22136-sdk-cipher-encryption",
|
||||
CipherKeyEncryption = "cipher-key-encryption",
|
||||
RiskInsightsForPremium = "pm-23904-risk-insights-for-premium",
|
||||
VaultLoadingSkeletons = "pm-25081-vault-skeleton-loaders",
|
||||
BrowserPremiumSpotlight = "pm-23384-browser-premium-spotlight",
|
||||
MigrateMyVaultToMyItems = "pm-20558-migrate-myvault-to-myitems",
|
||||
PM27632_SdkCipherCrudOperations = "pm-27632-cipher-crud-operations-to-sdk",
|
||||
@@ -121,6 +120,7 @@ export const DefaultFeatureFlagValue = {
|
||||
|
||||
/* DIRT */
|
||||
[FeatureFlag.EventManagementForDataDogAndCrowdStrike]: FALSE,
|
||||
[FeatureFlag.EventManagementForHuntress]: FALSE,
|
||||
[FeatureFlag.PhishingDetection]: FALSE,
|
||||
|
||||
/* Vault */
|
||||
@@ -128,8 +128,6 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.PM19941MigrateCipherDomainToSdk]: FALSE,
|
||||
[FeatureFlag.PM22134SdkCipherListView]: FALSE,
|
||||
[FeatureFlag.PM22136_SdkCipherEncryption]: FALSE,
|
||||
[FeatureFlag.RiskInsightsForPremium]: FALSE,
|
||||
[FeatureFlag.VaultLoadingSkeletons]: FALSE,
|
||||
[FeatureFlag.BrowserPremiumSpotlight]: FALSE,
|
||||
[FeatureFlag.PM27632_SdkCipherCrudOperations]: FALSE,
|
||||
[FeatureFlag.MigrateMyVaultToMyItems]: FALSE,
|
||||
@@ -152,9 +150,9 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
|
||||
[FeatureFlag.EnrollAeadOnKeyRotation]: FALSE,
|
||||
[FeatureFlag.ForceUpdateKDFSettings]: FALSE,
|
||||
[FeatureFlag.PM25174_DisableType0Decryption]: FALSE,
|
||||
[FeatureFlag.LinuxBiometricsV2]: FALSE,
|
||||
[FeatureFlag.NoLogoutOnKdfChange]: FALSE,
|
||||
[FeatureFlag.PasskeyUnlock]: FALSE,
|
||||
[FeatureFlag.DataRecoveryTool]: FALSE,
|
||||
[FeatureFlag.ConsolidatedSessionTimeoutComponent]: FALSE,
|
||||
[FeatureFlag.PM27279_V2RegistrationTdeJit]: FALSE,
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { EncString } from "../models/enc-string";
|
||||
|
||||
export abstract class EncryptService {
|
||||
/**
|
||||
* A temporary init method to make the encrypt service listen to feature-flag changes.
|
||||
* This will be removed once the feature flag has been rolled out.
|
||||
*/
|
||||
abstract init(configService: ConfigService): void;
|
||||
|
||||
/**
|
||||
* Encrypts a string to an EncString
|
||||
* @param plainValue - The value to encrypt
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||
import { EncryptionType } from "@bitwarden/common/platform/enums";
|
||||
@@ -15,28 +13,12 @@ import { PureCrypto } from "@bitwarden/sdk-internal";
|
||||
import { EncryptService } from "../abstractions/encrypt.service";
|
||||
|
||||
export class EncryptServiceImplementation implements EncryptService {
|
||||
private disableType0Decryption = false;
|
||||
|
||||
constructor(
|
||||
protected cryptoFunctionService: CryptoFunctionService,
|
||||
protected logService: LogService,
|
||||
protected logMacFailures: boolean,
|
||||
) {}
|
||||
|
||||
init(configService: ConfigService): void {
|
||||
configService.serverConfig$.subscribe((newConfig) => {
|
||||
if (newConfig != null) {
|
||||
this.setDisableType0Decryption(
|
||||
newConfig.featureStates[FeatureFlag.PM25174_DisableType0Decryption] === true,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setDisableType0Decryption(disable: boolean): void {
|
||||
this.disableType0Decryption = disable;
|
||||
}
|
||||
|
||||
async encryptString(plainValue: string, key: SymmetricCryptoKey): Promise<EncString> {
|
||||
if (plainValue == null) {
|
||||
this.logService.warning(
|
||||
@@ -60,7 +42,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
}
|
||||
|
||||
async decryptString(encString: EncString, key: SymmetricCryptoKey): Promise<string> {
|
||||
if (this.disableType0Decryption && encString.encryptionType === EncryptionType.AesCbc256_B64) {
|
||||
if (encString.encryptionType === EncryptionType.AesCbc256_B64) {
|
||||
throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled.");
|
||||
}
|
||||
await SdkLoadService.Ready;
|
||||
@@ -68,7 +50,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
}
|
||||
|
||||
async decryptBytes(encString: EncString, key: SymmetricCryptoKey): Promise<Uint8Array> {
|
||||
if (this.disableType0Decryption && encString.encryptionType === EncryptionType.AesCbc256_B64) {
|
||||
if (encString.encryptionType === EncryptionType.AesCbc256_B64) {
|
||||
throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled.");
|
||||
}
|
||||
await SdkLoadService.Ready;
|
||||
@@ -76,7 +58,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
}
|
||||
|
||||
async decryptFileData(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise<Uint8Array> {
|
||||
if (this.disableType0Decryption && encBuffer.encryptionType === EncryptionType.AesCbc256_B64) {
|
||||
if (encBuffer.encryptionType === EncryptionType.AesCbc256_B64) {
|
||||
throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled.");
|
||||
}
|
||||
await SdkLoadService.Ready;
|
||||
@@ -148,10 +130,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
throw new Error("No wrappingKey provided for unwrapping.");
|
||||
}
|
||||
|
||||
if (
|
||||
this.disableType0Decryption &&
|
||||
wrappedDecapsulationKey.encryptionType === EncryptionType.AesCbc256_B64
|
||||
) {
|
||||
if (wrappedDecapsulationKey.encryptionType === EncryptionType.AesCbc256_B64) {
|
||||
throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled.");
|
||||
}
|
||||
|
||||
@@ -171,10 +150,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
if (wrappingKey == null) {
|
||||
throw new Error("No wrappingKey provided for unwrapping.");
|
||||
}
|
||||
if (
|
||||
this.disableType0Decryption &&
|
||||
wrappedEncapsulationKey.encryptionType === EncryptionType.AesCbc256_B64
|
||||
) {
|
||||
if (wrappedEncapsulationKey.encryptionType === EncryptionType.AesCbc256_B64) {
|
||||
throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled.");
|
||||
}
|
||||
|
||||
@@ -194,10 +170,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
if (wrappingKey == null) {
|
||||
throw new Error("No wrappingKey provided for unwrapping.");
|
||||
}
|
||||
if (
|
||||
this.disableType0Decryption &&
|
||||
keyToBeUnwrapped.encryptionType === EncryptionType.AesCbc256_B64
|
||||
) {
|
||||
if (keyToBeUnwrapped.encryptionType === EncryptionType.AesCbc256_B64) {
|
||||
throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled.");
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ describe("EncryptService", () => {
|
||||
describe("decryptString", () => {
|
||||
it("is a proxy to PureCrypto", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const encString = new EncString("encrypted_string");
|
||||
const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "encrypted_string");
|
||||
const result = await encryptService.decryptString(encString, key);
|
||||
expect(result).toEqual("decrypted_string");
|
||||
expect(PureCrypto.symmetric_decrypt_string).toHaveBeenCalledWith(
|
||||
@@ -172,8 +172,7 @@ describe("EncryptService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => {
|
||||
encryptService.setDisableType0Decryption(true);
|
||||
it("throws if type is AesCbc256_B64", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const encString = new EncString(EncryptionType.AesCbc256_B64, "encrypted_string");
|
||||
await expect(encryptService.decryptString(encString, key)).rejects.toThrow(
|
||||
@@ -185,7 +184,7 @@ describe("EncryptService", () => {
|
||||
describe("decryptBytes", () => {
|
||||
it("is a proxy to PureCrypto", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const encString = new EncString("encrypted_bytes");
|
||||
const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "encrypted_bytes");
|
||||
const result = await encryptService.decryptBytes(encString, key);
|
||||
expect(result).toEqual(new Uint8Array(3));
|
||||
expect(PureCrypto.symmetric_decrypt_bytes).toHaveBeenCalledWith(
|
||||
@@ -194,8 +193,7 @@ describe("EncryptService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => {
|
||||
encryptService.setDisableType0Decryption(true);
|
||||
it("throws if type is AesCbc256_B64", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const encString = new EncString(EncryptionType.AesCbc256_B64, "encrypted_bytes");
|
||||
await expect(encryptService.decryptBytes(encString, key)).rejects.toThrow(
|
||||
@@ -216,8 +214,7 @@ describe("EncryptService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => {
|
||||
encryptService.setDisableType0Decryption(true);
|
||||
it("throws if type is AesCbc256_B64", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const encBuffer = EncArrayBuffer.fromParts(
|
||||
EncryptionType.AesCbc256_B64,
|
||||
@@ -234,7 +231,10 @@ describe("EncryptService", () => {
|
||||
describe("unwrapDecapsulationKey", () => {
|
||||
it("is a proxy to PureCrypto", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const encString = new EncString("wrapped_decapsulation_key");
|
||||
const encString = new EncString(
|
||||
EncryptionType.AesCbc256_HmacSha256_B64,
|
||||
"wrapped_decapsulation_key",
|
||||
);
|
||||
const result = await encryptService.unwrapDecapsulationKey(encString, key);
|
||||
expect(result).toEqual(new Uint8Array(4));
|
||||
expect(PureCrypto.unwrap_decapsulation_key).toHaveBeenCalledWith(
|
||||
@@ -242,8 +242,7 @@ describe("EncryptService", () => {
|
||||
key.toEncoded(),
|
||||
);
|
||||
});
|
||||
it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => {
|
||||
encryptService.setDisableType0Decryption(true);
|
||||
it("throws if type is AesCbc256_B64", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const encString = new EncString(EncryptionType.AesCbc256_B64, "wrapped_decapsulation_key");
|
||||
await expect(encryptService.unwrapDecapsulationKey(encString, key)).rejects.toThrow(
|
||||
@@ -267,7 +266,10 @@ describe("EncryptService", () => {
|
||||
describe("unwrapEncapsulationKey", () => {
|
||||
it("is a proxy to PureCrypto", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const encString = new EncString("wrapped_encapsulation_key");
|
||||
const encString = new EncString(
|
||||
EncryptionType.AesCbc256_HmacSha256_B64,
|
||||
"wrapped_encapsulation_key",
|
||||
);
|
||||
const result = await encryptService.unwrapEncapsulationKey(encString, key);
|
||||
expect(result).toEqual(new Uint8Array(5));
|
||||
expect(PureCrypto.unwrap_encapsulation_key).toHaveBeenCalledWith(
|
||||
@@ -275,8 +277,7 @@ describe("EncryptService", () => {
|
||||
key.toEncoded(),
|
||||
);
|
||||
});
|
||||
it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => {
|
||||
encryptService.setDisableType0Decryption(true);
|
||||
it("throws if type is AesCbc256_B64", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const encString = new EncString(EncryptionType.AesCbc256_B64, "wrapped_encapsulation_key");
|
||||
await expect(encryptService.unwrapEncapsulationKey(encString, key)).rejects.toThrow(
|
||||
@@ -300,7 +301,10 @@ describe("EncryptService", () => {
|
||||
describe("unwrapSymmetricKey", () => {
|
||||
it("is a proxy to PureCrypto", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const encString = new EncString("wrapped_symmetric_key");
|
||||
const encString = new EncString(
|
||||
EncryptionType.AesCbc256_HmacSha256_B64,
|
||||
"wrapped_symmetric_key",
|
||||
);
|
||||
const result = await encryptService.unwrapSymmetricKey(encString, key);
|
||||
expect(result).toEqual(new SymmetricCryptoKey(new Uint8Array(64)));
|
||||
expect(PureCrypto.unwrap_symmetric_key).toHaveBeenCalledWith(
|
||||
@@ -308,8 +312,7 @@ describe("EncryptService", () => {
|
||||
key.toEncoded(),
|
||||
);
|
||||
});
|
||||
it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => {
|
||||
encryptService.setDisableType0Decryption(true);
|
||||
it("throws if type is AesCbc256_B64", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const encString = new EncString(EncryptionType.AesCbc256_B64, "wrapped_symmetric_key");
|
||||
await expect(encryptService.unwrapSymmetricKey(encString, key)).rejects.toThrow(
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { WebAuthnPrfDecryptionOptionResponse } from "../../../auth/models/response/user-decryption-options/webauthn-prf-decryption-option.response";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { MasterPasswordUnlockResponse } from "../../master-password/models/response/master-password-unlock.response";
|
||||
|
||||
export class UserDecryptionResponse extends BaseResponse {
|
||||
masterPasswordUnlock?: MasterPasswordUnlockResponse;
|
||||
|
||||
/**
|
||||
* The sync service returns an array of WebAuthn PRF options.
|
||||
*/
|
||||
webAuthnPrfOptions?: WebAuthnPrfDecryptionOptionResponse[];
|
||||
|
||||
constructor(response: unknown) {
|
||||
super(response);
|
||||
|
||||
@@ -11,5 +17,12 @@ export class UserDecryptionResponse extends BaseResponse {
|
||||
if (masterPasswordUnlock != null && typeof masterPasswordUnlock === "object") {
|
||||
this.masterPasswordUnlock = new MasterPasswordUnlockResponse(masterPasswordUnlock);
|
||||
}
|
||||
|
||||
const webAuthnPrfOptions = this.getResponseProperty("WebAuthnPrfOptions");
|
||||
if (webAuthnPrfOptions != null && Array.isArray(webAuthnPrfOptions)) {
|
||||
this.webAuthnPrfOptions = webAuthnPrfOptions.map(
|
||||
(option) => new WebAuthnPrfDecryptionOptionResponse(option),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,6 +260,13 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
});
|
||||
|
||||
describe("getVaultTimeoutByUserId$", () => {
|
||||
beforeEach(() => {
|
||||
// Return the input value unchanged
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockImplementation(
|
||||
async (timeout) => timeout,
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw an error if no user id is provided", async () => {
|
||||
expect(() => vaultTimeoutSettingsService.getVaultTimeoutByUserId$(null)).toThrow(
|
||||
"User id required. Cannot get vault timeout.",
|
||||
@@ -277,6 +284,9 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
defaultVaultTimeout,
|
||||
);
|
||||
expect(result).toBe(defaultVaultTimeout);
|
||||
});
|
||||
|
||||
@@ -299,8 +309,31 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
vaultTimeout,
|
||||
);
|
||||
expect(result).toBe(vaultTimeout);
|
||||
});
|
||||
|
||||
it("promotes timeout when unavailable on client", async () => {
|
||||
const determinedTimeout = VaultTimeoutNumberType.OnMinute;
|
||||
const promotedValue = VaultTimeoutStringType.OnRestart;
|
||||
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(promotedValue);
|
||||
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true }));
|
||||
policyService.policiesByType$.mockReturnValue(of([]));
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, determinedTimeout, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
determinedTimeout,
|
||||
);
|
||||
expect(result).toBe(promotedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy type: custom", () => {
|
||||
@@ -327,6 +360,9 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
policyMinutes,
|
||||
);
|
||||
expect(result).toBe(policyMinutes);
|
||||
},
|
||||
);
|
||||
@@ -345,6 +381,9 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
vaultTimeout,
|
||||
);
|
||||
expect(result).toBe(vaultTimeout);
|
||||
},
|
||||
);
|
||||
@@ -365,8 +404,36 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
);
|
||||
expect(result).toBe(VaultTimeoutNumberType.Immediately);
|
||||
});
|
||||
|
||||
it("promotes policy minutes when unavailable on client", async () => {
|
||||
const promotedValue = VaultTimeoutStringType.Never;
|
||||
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(promotedValue);
|
||||
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true }));
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "custom", minutes: policyMinutes } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(
|
||||
VAULT_TIMEOUT,
|
||||
VaultTimeoutNumberType.EightHours,
|
||||
mockUserId,
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
policyMinutes,
|
||||
);
|
||||
expect(result).toBe(promotedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy type: immediately", () => {
|
||||
@@ -383,7 +450,6 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
"when current timeout is %s, returns immediately or promoted value",
|
||||
async (currentTimeout) => {
|
||||
const expectedTimeout = VaultTimeoutNumberType.Immediately;
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(expectedTimeout);
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "immediately" } }] as unknown as Policy[]),
|
||||
);
|
||||
@@ -400,6 +466,26 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
expect(result).toBe(expectedTimeout);
|
||||
},
|
||||
);
|
||||
|
||||
it("promotes immediately when unavailable on client", async () => {
|
||||
const promotedValue = VaultTimeoutNumberType.OnMinute;
|
||||
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(promotedValue);
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "immediately" } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, VaultTimeoutStringType.Never, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
);
|
||||
expect(result).toBe(promotedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy type: onSystemLock", () => {
|
||||
@@ -413,7 +499,6 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
"when current timeout is %s, returns onLocked or promoted value",
|
||||
async (currentTimeout) => {
|
||||
const expectedTimeout = VaultTimeoutStringType.OnLocked;
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(expectedTimeout);
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "onSystemLock" } }] as unknown as Policy[]),
|
||||
);
|
||||
@@ -446,9 +531,31 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).not.toHaveBeenCalled();
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
currentTimeout,
|
||||
);
|
||||
expect(result).toBe(currentTimeout);
|
||||
});
|
||||
|
||||
it("promotes onLocked when unavailable on client", async () => {
|
||||
const promotedValue = VaultTimeoutStringType.OnRestart;
|
||||
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(promotedValue);
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "onSystemLock" } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, VaultTimeoutStringType.Never, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
);
|
||||
expect(result).toBe(promotedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy type: onAppRestart", () => {
|
||||
@@ -468,7 +575,9 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).not.toHaveBeenCalled();
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
);
|
||||
expect(result).toBe(VaultTimeoutStringType.OnRestart);
|
||||
});
|
||||
|
||||
@@ -488,32 +597,40 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).not.toHaveBeenCalled();
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
currentTimeout,
|
||||
);
|
||||
expect(result).toBe(currentTimeout);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy type: never", () => {
|
||||
it("when current timeout is never, returns never or promoted value", async () => {
|
||||
const expectedTimeout = VaultTimeoutStringType.Never;
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(expectedTimeout);
|
||||
it("promotes onRestart when unavailable on client", async () => {
|
||||
const promotedValue = VaultTimeoutStringType.Never;
|
||||
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(promotedValue);
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "never" } }] as unknown as Policy[]),
|
||||
of([{ data: { type: "onAppRestart" } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, VaultTimeoutStringType.Never, mockUserId);
|
||||
await stateProvider.setUserState(
|
||||
VAULT_TIMEOUT,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
mockUserId,
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
);
|
||||
expect(result).toBe(expectedTimeout);
|
||||
expect(result).toBe(promotedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy type: never", () => {
|
||||
it.each([
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
@@ -532,9 +649,32 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).not.toHaveBeenCalled();
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
currentTimeout,
|
||||
);
|
||||
expect(result).toBe(currentTimeout);
|
||||
});
|
||||
|
||||
it("promotes timeout when unavailable on client", async () => {
|
||||
const determinedTimeout = VaultTimeoutStringType.Never;
|
||||
const promotedValue = VaultTimeoutStringType.OnRestart;
|
||||
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(promotedValue);
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "never" } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, determinedTimeout, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
determinedTimeout,
|
||||
);
|
||||
expect(result).toBe(promotedValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -179,7 +179,20 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
private async determineVaultTimeout(
|
||||
currentVaultTimeout: VaultTimeout | null,
|
||||
maxSessionTimeoutPolicyData: MaximumSessionTimeoutPolicyData | null,
|
||||
): Promise<VaultTimeout | null> {
|
||||
): Promise<VaultTimeout> {
|
||||
const determinedTimeout = await this.determineVaultTimeoutInternal(
|
||||
currentVaultTimeout,
|
||||
maxSessionTimeoutPolicyData,
|
||||
);
|
||||
|
||||
// Ensures the timeout is available on this client
|
||||
return await this.sessionTimeoutTypeService.getOrPromoteToAvailable(determinedTimeout);
|
||||
}
|
||||
|
||||
private async determineVaultTimeoutInternal(
|
||||
currentVaultTimeout: VaultTimeout | null,
|
||||
maxSessionTimeoutPolicyData: MaximumSessionTimeoutPolicyData | null,
|
||||
): Promise<VaultTimeout> {
|
||||
// if current vault timeout is null, apply the client specific default
|
||||
currentVaultTimeout = currentVaultTimeout ?? this.defaultVaultTimeout;
|
||||
|
||||
@@ -190,9 +203,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
|
||||
switch (maxSessionTimeoutPolicyData.type) {
|
||||
case "immediately":
|
||||
return await this.sessionTimeoutTypeService.getOrPromoteToAvailable(
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
);
|
||||
return VaultTimeoutNumberType.Immediately;
|
||||
case "custom":
|
||||
case null:
|
||||
case undefined:
|
||||
@@ -211,9 +222,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
currentVaultTimeout === VaultTimeoutStringType.OnIdle ||
|
||||
currentVaultTimeout === VaultTimeoutStringType.OnSleep
|
||||
) {
|
||||
return await this.sessionTimeoutTypeService.getOrPromoteToAvailable(
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
);
|
||||
return VaultTimeoutStringType.OnLocked;
|
||||
}
|
||||
break;
|
||||
case "onAppRestart":
|
||||
@@ -227,11 +236,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
}
|
||||
break;
|
||||
case "never":
|
||||
if (currentVaultTimeout === VaultTimeoutStringType.Never) {
|
||||
return await this.sessionTimeoutTypeService.getOrPromoteToAvailable(
|
||||
VaultTimeoutStringType.Never,
|
||||
);
|
||||
}
|
||||
// Policy doesn't override user preference for "never"
|
||||
break;
|
||||
}
|
||||
return currentVaultTimeout;
|
||||
|
||||
@@ -42,6 +42,7 @@ export class Utils {
|
||||
static readonly validHosts: string[] = ["localhost"];
|
||||
static readonly originalMinimumPasswordLength = 8;
|
||||
static readonly minimumPasswordLength = 12;
|
||||
static readonly maximumPasswordLength = 128;
|
||||
static readonly DomainMatchBlacklist = new Map<string, Set<string>>([
|
||||
["google.com", new Set(["script.google.com"])],
|
||||
]);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
LogoutReason,
|
||||
UserDecryptionOptions,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
@@ -68,7 +68,7 @@ describe("DefaultSyncService", () => {
|
||||
let folderApiService: MockProxy<FolderApiServiceAbstraction>;
|
||||
let organizationService: MockProxy<InternalOrganizationServiceAbstraction>;
|
||||
let sendApiService: MockProxy<SendApiService>;
|
||||
let userDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
|
||||
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||
let avatarService: MockProxy<AvatarService>;
|
||||
let logoutCallback: jest.Mock<Promise<void>, [logoutReason: LogoutReason, userId?: UserId]>;
|
||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||
|
||||
@@ -6,8 +6,8 @@ import { firstValueFrom, map } from "rxjs";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
CollectionDetailsResponse,
|
||||
CollectionData,
|
||||
CollectionDetailsResponse,
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
|
||||
import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service";
|
||||
@@ -15,9 +15,13 @@ import { SecurityStateService } from "@bitwarden/common/key-management/security-
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
|
||||
|
||||
// FIXME: remove `src` and fix import
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { UserDecryptionOptionsServiceAbstraction } from "../../../../auth/src/common/abstractions";
|
||||
import {
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
UserDecryptionOptions,
|
||||
WebAuthnPrfUserDecryptionOption,
|
||||
} from "../../../../auth/src/common";
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LogoutReason } from "../../../../auth/src/common/types";
|
||||
@@ -93,7 +97,7 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
folderApiService: FolderApiServiceAbstraction,
|
||||
private organizationService: InternalOrganizationServiceAbstraction,
|
||||
sendApiService: SendApiService,
|
||||
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
private userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
private avatarService: AvatarService,
|
||||
private logoutCallback: (logoutReason: LogoutReason, userId?: UserId) => Promise<void>,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
@@ -450,5 +454,43 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
);
|
||||
await this.kdfConfigService.setKdfConfig(userId, masterPasswordUnlockData.kdf);
|
||||
}
|
||||
|
||||
// Update WebAuthn PRF options if present
|
||||
if (userDecryption.webAuthnPrfOptions != null && userDecryption.webAuthnPrfOptions.length > 0) {
|
||||
try {
|
||||
// Only update if this is the active user, since setUserDecryptionOptions()
|
||||
// operates on the active user's state
|
||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||
|
||||
if (activeAccount?.id !== userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current options without blocking if they don't exist yet
|
||||
const currentUserDecryptionOptions = await firstValueFrom(
|
||||
this.userDecryptionOptionsService.userDecryptionOptionsById$(userId),
|
||||
).catch((): UserDecryptionOptions | null => {
|
||||
return null;
|
||||
});
|
||||
|
||||
if (currentUserDecryptionOptions != null) {
|
||||
// Update the PRF options while preserving other decryption options
|
||||
const updatedOptions = Object.assign(
|
||||
new UserDecryptionOptions(),
|
||||
currentUserDecryptionOptions,
|
||||
);
|
||||
updatedOptions.webAuthnPrfOptions = userDecryption.webAuthnPrfOptions
|
||||
.map((option) => WebAuthnPrfUserDecryptionOption.fromResponse(option))
|
||||
.filter((option) => option !== undefined);
|
||||
|
||||
await this.userDecryptionOptionsService.setUserDecryptionOptionsById(
|
||||
activeAccount.id,
|
||||
updatedOptions,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error("[Sync] Failed to update WebAuthn PRF options:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,13 +230,13 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
|
||||
abstract clear(userId?: string): Promise<void>;
|
||||
abstract moveManyWithServer(ids: string[], folderId: string, userId: UserId): Promise<any>;
|
||||
abstract delete(id: string | string[], userId: UserId): Promise<any>;
|
||||
abstract deleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<any>;
|
||||
abstract deleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<void>;
|
||||
abstract deleteManyWithServer(
|
||||
ids: string[],
|
||||
userId: UserId,
|
||||
asAdmin?: boolean,
|
||||
orgId?: OrganizationId,
|
||||
): Promise<any>;
|
||||
): Promise<void>;
|
||||
abstract deleteAttachment(
|
||||
id: string,
|
||||
revisionDate: string,
|
||||
@@ -252,19 +252,19 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
|
||||
abstract sortCiphersByLastUsed(a: CipherViewLike, b: CipherViewLike): number;
|
||||
abstract sortCiphersByLastUsedThenName(a: CipherViewLike, b: CipherViewLike): number;
|
||||
abstract getLocaleSortingFunction(): (a: CipherViewLike, b: CipherViewLike) => number;
|
||||
abstract softDelete(id: string | string[], userId: UserId): Promise<any>;
|
||||
abstract softDeleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<any>;
|
||||
abstract softDelete(id: string | string[], userId: UserId): Promise<void>;
|
||||
abstract softDeleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<void>;
|
||||
abstract softDeleteManyWithServer(
|
||||
ids: string[],
|
||||
userId: UserId,
|
||||
asAdmin?: boolean,
|
||||
orgId?: OrganizationId,
|
||||
): Promise<any>;
|
||||
): Promise<void>;
|
||||
abstract restore(
|
||||
cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[],
|
||||
userId: UserId,
|
||||
): Promise<any>;
|
||||
abstract restoreWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<any>;
|
||||
): Promise<void>;
|
||||
abstract restoreWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<void>;
|
||||
abstract restoreManyWithServer(ids: string[], userId: UserId, orgId?: string): Promise<void>;
|
||||
abstract getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise<any>;
|
||||
abstract setAddEditCipherInfo(value: AddEditCipherInfo, userId: UserId): Promise<void>;
|
||||
|
||||
@@ -224,6 +224,9 @@ describe("Cipher Service", () => {
|
||||
});
|
||||
|
||||
it("should call apiService.postCipherAdmin when orgAdmin param is true and the cipher orgId != null", async () => {
|
||||
configService.getFeatureFlag
|
||||
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
|
||||
.mockResolvedValue(false);
|
||||
const spy = jest
|
||||
.spyOn(apiService, "postCipherAdmin")
|
||||
.mockImplementation(() => Promise.resolve<any>(encryptionContext.cipher.toCipherData()));
|
||||
@@ -236,6 +239,9 @@ describe("Cipher Service", () => {
|
||||
});
|
||||
|
||||
it("should call apiService.postCipher when orgAdmin param is true and the cipher orgId is null", async () => {
|
||||
configService.getFeatureFlag
|
||||
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
|
||||
.mockResolvedValue(false);
|
||||
encryptionContext.cipher.organizationId = null!;
|
||||
const spy = jest
|
||||
.spyOn(apiService, "postCipher")
|
||||
@@ -249,6 +255,9 @@ describe("Cipher Service", () => {
|
||||
});
|
||||
|
||||
it("should call apiService.postCipherCreate if collectionsIds != null", async () => {
|
||||
configService.getFeatureFlag
|
||||
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
|
||||
.mockResolvedValue(false);
|
||||
encryptionContext.cipher.collectionIds = ["123"];
|
||||
const spy = jest
|
||||
.spyOn(apiService, "postCipherCreate")
|
||||
@@ -262,6 +271,9 @@ describe("Cipher Service", () => {
|
||||
});
|
||||
|
||||
it("should call apiService.postCipher when orgAdmin and collectionIds logic is false", async () => {
|
||||
configService.getFeatureFlag
|
||||
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
|
||||
.mockResolvedValue(false);
|
||||
const spy = jest
|
||||
.spyOn(apiService, "postCipher")
|
||||
.mockImplementation(() => Promise.resolve<any>(encryptionContext.cipher.toCipherData()));
|
||||
@@ -328,6 +340,9 @@ describe("Cipher Service", () => {
|
||||
});
|
||||
|
||||
it("should call apiService.putCipher if cipher.edit is true", async () => {
|
||||
configService.getFeatureFlag
|
||||
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
|
||||
.mockResolvedValue(false);
|
||||
encryptionContext.cipher.edit = true;
|
||||
const spy = jest
|
||||
.spyOn(apiService, "putCipher")
|
||||
@@ -341,6 +356,9 @@ describe("Cipher Service", () => {
|
||||
});
|
||||
|
||||
it("should call apiService.putPartialCipher when orgAdmin, and edit are false", async () => {
|
||||
configService.getFeatureFlag
|
||||
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
|
||||
.mockResolvedValue(false);
|
||||
encryptionContext.cipher.edit = false;
|
||||
const spy = jest
|
||||
.spyOn(apiService, "putPartialCipher")
|
||||
|
||||
@@ -1446,10 +1446,12 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
await this.encryptedCiphersState(userId).update(() => ciphers);
|
||||
}
|
||||
|
||||
async deleteWithServer(id: string, userId: UserId, asAdmin = false): Promise<any> {
|
||||
async deleteWithServer(id: string, userId: UserId, asAdmin = false): Promise<void> {
|
||||
const useSdk = await firstValueFrom(this.sdkCipherCrudEnabled$);
|
||||
if (useSdk) {
|
||||
return this.deleteWithServerUsingSdk(id, userId, asAdmin);
|
||||
await this.cipherSdkService.deleteWithServer(id, userId, asAdmin);
|
||||
await this.clearCache(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (asAdmin) {
|
||||
@@ -1461,24 +1463,17 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
await this.delete(id, userId);
|
||||
}
|
||||
|
||||
private async deleteWithServerUsingSdk(
|
||||
id: string,
|
||||
userId: UserId,
|
||||
asAdmin = false,
|
||||
): Promise<any> {
|
||||
await this.cipherSdkService.deleteWithServer(id, userId, asAdmin);
|
||||
await this.clearCache(userId);
|
||||
}
|
||||
|
||||
async deleteManyWithServer(
|
||||
ids: string[],
|
||||
userId: UserId,
|
||||
asAdmin = false,
|
||||
orgId?: OrganizationId,
|
||||
): Promise<any> {
|
||||
): Promise<void> {
|
||||
const useSdk = await firstValueFrom(this.sdkCipherCrudEnabled$);
|
||||
if (useSdk) {
|
||||
return this.deleteManyWithServerUsingSdk(ids, userId, asAdmin, orgId);
|
||||
await this.cipherSdkService.deleteManyWithServer(ids, userId, asAdmin, orgId);
|
||||
await this.clearCache(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
const request = new CipherBulkDeleteRequest(ids);
|
||||
@@ -1490,16 +1485,6 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
await this.delete(ids, userId);
|
||||
}
|
||||
|
||||
private async deleteManyWithServerUsingSdk(
|
||||
ids: string[],
|
||||
userId: UserId,
|
||||
asAdmin = false,
|
||||
orgId?: OrganizationId,
|
||||
): Promise<any> {
|
||||
await this.cipherSdkService.deleteManyWithServer(ids, userId, asAdmin, orgId);
|
||||
await this.clearCache(userId);
|
||||
}
|
||||
|
||||
async deleteAttachment(
|
||||
id: string,
|
||||
revisionDate: string,
|
||||
@@ -1630,7 +1615,7 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
};
|
||||
}
|
||||
|
||||
async softDelete(id: string | string[], userId: UserId): Promise<any> {
|
||||
async softDelete(id: string | string[], userId: UserId): Promise<void> {
|
||||
let ciphers = await firstValueFrom(this.ciphers$(userId));
|
||||
if (ciphers == null) {
|
||||
return;
|
||||
@@ -1658,10 +1643,12 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
});
|
||||
}
|
||||
|
||||
async softDeleteWithServer(id: string, userId: UserId, asAdmin = false): Promise<any> {
|
||||
async softDeleteWithServer(id: string, userId: UserId, asAdmin = false): Promise<void> {
|
||||
const useSdk = await firstValueFrom(this.sdkCipherCrudEnabled$);
|
||||
if (useSdk) {
|
||||
return this.softDeleteWithServerUsingSdk(id, userId, asAdmin);
|
||||
await this.cipherSdkService.softDeleteWithServer(id, userId, asAdmin);
|
||||
await this.clearCache(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (asAdmin) {
|
||||
@@ -1673,24 +1660,17 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
await this.softDelete(id, userId);
|
||||
}
|
||||
|
||||
private async softDeleteWithServerUsingSdk(
|
||||
id: string,
|
||||
userId: UserId,
|
||||
asAdmin = false,
|
||||
): Promise<any> {
|
||||
await this.cipherSdkService.softDeleteWithServer(id, userId, asAdmin);
|
||||
await this.clearCache(userId);
|
||||
}
|
||||
|
||||
async softDeleteManyWithServer(
|
||||
ids: string[],
|
||||
userId: UserId,
|
||||
asAdmin = false,
|
||||
orgId?: OrganizationId,
|
||||
): Promise<any> {
|
||||
): Promise<void> {
|
||||
const useSdk = await firstValueFrom(this.sdkCipherCrudEnabled$);
|
||||
if (useSdk) {
|
||||
return this.softDeleteManyWithServerUsingSdk(ids, userId, asAdmin, orgId);
|
||||
await this.cipherSdkService.softDeleteManyWithServer(ids, userId, asAdmin, orgId);
|
||||
await this.clearCache(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
const request = new CipherBulkDeleteRequest(ids);
|
||||
@@ -1703,16 +1683,6 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
await this.softDelete(ids, userId);
|
||||
}
|
||||
|
||||
private async softDeleteManyWithServerUsingSdk(
|
||||
ids: string[],
|
||||
userId: UserId,
|
||||
asAdmin = false,
|
||||
orgId?: OrganizationId,
|
||||
): Promise<any> {
|
||||
await this.cipherSdkService.softDeleteManyWithServer(ids, userId, asAdmin, orgId);
|
||||
await this.clearCache(userId);
|
||||
}
|
||||
|
||||
async restore(
|
||||
cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[],
|
||||
userId: UserId,
|
||||
@@ -1746,10 +1716,12 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
});
|
||||
}
|
||||
|
||||
async restoreWithServer(id: string, userId: UserId, asAdmin = false): Promise<any> {
|
||||
async restoreWithServer(id: string, userId: UserId, asAdmin = false): Promise<void> {
|
||||
const useSdk = await firstValueFrom(this.sdkCipherCrudEnabled$);
|
||||
if (useSdk) {
|
||||
return await this.restoreWithServerUsingSdk(id, userId, asAdmin);
|
||||
await this.cipherSdkService.restoreWithServer(id, userId, asAdmin);
|
||||
await this.clearCache(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
let response;
|
||||
@@ -1762,15 +1734,6 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
await this.restore({ id: id, revisionDate: response.revisionDate }, userId);
|
||||
}
|
||||
|
||||
private async restoreWithServerUsingSdk(
|
||||
id: string,
|
||||
userId: UserId,
|
||||
asAdmin = false,
|
||||
): Promise<any> {
|
||||
await this.cipherSdkService.restoreWithServer(id, userId, asAdmin);
|
||||
await this.clearCache(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* No longer using an asAdmin Param. Org Vault bulkRestore will assess if an item is unassigned or editable
|
||||
* The Org Vault will pass those ids an array as well as the orgId when calling bulkRestore
|
||||
@@ -1778,7 +1741,9 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
async restoreManyWithServer(ids: string[], userId: UserId, orgId?: string): Promise<void> {
|
||||
const useSdk = await firstValueFrom(this.sdkCipherCrudEnabled$);
|
||||
if (useSdk) {
|
||||
return await this.restoreManyWithServerUsingSdk(ids, userId, orgId);
|
||||
await this.cipherSdkService.restoreManyWithServer(ids, userId, orgId);
|
||||
await this.clearCache(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
let response;
|
||||
@@ -1798,15 +1763,6 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
await this.restore(restores, userId);
|
||||
}
|
||||
|
||||
private async restoreManyWithServerUsingSdk(
|
||||
ids: string[],
|
||||
userId: UserId,
|
||||
orgId?: string,
|
||||
): Promise<void> {
|
||||
await this.cipherSdkService.restoreManyWithServer(ids, userId, orgId);
|
||||
await this.clearCache(userId);
|
||||
}
|
||||
|
||||
async getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise<UserKey | OrgKey> {
|
||||
if (cipher.organizationId == null) {
|
||||
return await firstValueFrom(this.keyService.userKey$(userId));
|
||||
|
||||
Reference in New Issue
Block a user