mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[PM-22408] Remove setMasterKeyEncryptedUserKey from KeyService (#15087)
* Swap consumers to masterPasswordService.setMasterKeyEncryptedUserKey * Remove setMasterKeyEncryptedUserKey from keyService * unit tests
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
import { KdfType } from "@bitwarden/key-management";
|
||||
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { EncString } from "../../../platform/models/domain/enc-string";
|
||||
|
||||
import { MasterPasswordPolicyResponse } from "./master-password-policy.response";
|
||||
import { UserDecryptionOptionsResponse } from "./user-decryption-options/user-decryption-options.response";
|
||||
@@ -17,7 +18,7 @@ export class IdentityTokenResponse extends BaseResponse {
|
||||
|
||||
resetMasterPassword: boolean;
|
||||
privateKey: string;
|
||||
key: string;
|
||||
key?: EncString;
|
||||
twoFactorToken: string;
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
@@ -39,7 +40,10 @@ export class IdentityTokenResponse extends BaseResponse {
|
||||
|
||||
this.resetMasterPassword = this.getResponseProperty("ResetMasterPassword");
|
||||
this.privateKey = this.getResponseProperty("PrivateKey");
|
||||
this.key = this.getResponseProperty("Key");
|
||||
const key = this.getResponseProperty("Key");
|
||||
if (key) {
|
||||
this.key = new EncString(key);
|
||||
}
|
||||
this.twoFactorToken = this.getResponseProperty("TwoFactorToken");
|
||||
this.kdf = this.getResponseProperty("Kdf");
|
||||
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
||||
|
||||
@@ -5,21 +5,23 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
||||
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
|
||||
// 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 { KeyService } from "@bitwarden/key-management";
|
||||
import { KdfType, KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec";
|
||||
import { ApiService } from "../../../abstractions/api.service";
|
||||
import { OrganizationData } from "../../../admin-console/models/data/organization.data";
|
||||
import { Organization } from "../../../admin-console/models/domain/organization";
|
||||
import { ProfileOrganizationResponse } from "../../../admin-console/models/response/profile-organization.response";
|
||||
import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response";
|
||||
import { KeyConnectorUserKeyResponse } from "../../../auth/models/response/key-connector-user-key.response";
|
||||
import { TokenService } from "../../../auth/services/token.service";
|
||||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { EncString } from "../../../platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { KeyGenerationService } from "../../../platform/services/key-generation.service";
|
||||
import { OrganizationId, UserId } from "../../../types/guid";
|
||||
import { MasterKey } from "../../../types/key";
|
||||
import { MasterKey, UserKey } from "../../../types/key";
|
||||
import { FakeMasterPasswordService } from "../../master-password/services/fake-master-password.service";
|
||||
import { KeyConnectorUserKeyRequest } from "../models/key-connector-user-key.request";
|
||||
|
||||
@@ -50,7 +52,7 @@ describe("KeyConnectorService", () => {
|
||||
const keyConnectorUrl = "https://key-connector-url.com";
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetAllMocks();
|
||||
|
||||
masterPasswordService = new FakeMasterPasswordService();
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
@@ -403,6 +405,106 @@ describe("KeyConnectorService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("convertNewSsoUserToKeyConnector", () => {
|
||||
const tokenResponse = mock<IdentityTokenResponse>();
|
||||
const passwordKey = new SymmetricCryptoKey(new Uint8Array(64));
|
||||
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
|
||||
const mockEmail = "test@example.com";
|
||||
const mockMasterKey = getMockMasterKey();
|
||||
let mockMakeUserKeyResult: [UserKey, EncString];
|
||||
|
||||
beforeEach(() => {
|
||||
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
|
||||
const mockKeyPair = ["mockPubKey", new EncString("mockEncryptedPrivKey")] as [
|
||||
string,
|
||||
EncString,
|
||||
];
|
||||
const encString = new EncString("mockEncryptedString");
|
||||
mockMakeUserKeyResult = [mockUserKey, encString] as [UserKey, EncString];
|
||||
|
||||
tokenResponse.kdf = KdfType.PBKDF2_SHA256;
|
||||
tokenResponse.kdfIterations = 100000;
|
||||
tokenResponse.kdfMemory = 16;
|
||||
tokenResponse.kdfParallelism = 4;
|
||||
tokenResponse.keyConnectorUrl = keyConnectorUrl;
|
||||
|
||||
keyGenerationService.createKey.mockResolvedValue(passwordKey);
|
||||
keyService.makeMasterKey.mockResolvedValue(mockMasterKey);
|
||||
keyService.makeUserKey.mockResolvedValue(mockMakeUserKeyResult);
|
||||
keyService.makeKeyPair.mockResolvedValue(mockKeyPair);
|
||||
tokenService.getEmail.mockResolvedValue(mockEmail);
|
||||
});
|
||||
|
||||
it("sets up a new SSO user with key connector", async () => {
|
||||
await keyConnectorService.convertNewSsoUserToKeyConnector(
|
||||
tokenResponse,
|
||||
mockOrgId,
|
||||
mockUserId,
|
||||
);
|
||||
|
||||
expect(keyGenerationService.createKey).toHaveBeenCalledWith(512);
|
||||
expect(keyService.makeMasterKey).toHaveBeenCalledWith(
|
||||
passwordKey.keyB64,
|
||||
mockEmail,
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(
|
||||
mockMasterKey,
|
||||
mockUserId,
|
||||
);
|
||||
expect(keyService.makeUserKey).toHaveBeenCalledWith(mockMasterKey);
|
||||
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, mockUserId);
|
||||
expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(
|
||||
mockMakeUserKeyResult[1],
|
||||
mockUserId,
|
||||
);
|
||||
expect(keyService.makeKeyPair).toHaveBeenCalledWith(mockMakeUserKeyResult[0]);
|
||||
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
|
||||
tokenResponse.keyConnectorUrl,
|
||||
expect.any(KeyConnectorUserKeyRequest),
|
||||
);
|
||||
expect(apiService.postSetKeyConnectorKey).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles api error", async () => {
|
||||
apiService.postUserKeyToKeyConnector.mockRejectedValue(new Error("API error"));
|
||||
|
||||
try {
|
||||
await keyConnectorService.convertNewSsoUserToKeyConnector(
|
||||
tokenResponse,
|
||||
mockOrgId,
|
||||
mockUserId,
|
||||
);
|
||||
} catch (error: any) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error?.message).toBe("Key Connector error");
|
||||
}
|
||||
|
||||
expect(keyGenerationService.createKey).toHaveBeenCalledWith(512);
|
||||
expect(keyService.makeMasterKey).toHaveBeenCalledWith(
|
||||
passwordKey.keyB64,
|
||||
mockEmail,
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(
|
||||
mockMasterKey,
|
||||
mockUserId,
|
||||
);
|
||||
expect(keyService.makeUserKey).toHaveBeenCalledWith(mockMasterKey);
|
||||
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, mockUserId);
|
||||
expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(
|
||||
mockMakeUserKeyResult[1],
|
||||
mockUserId,
|
||||
);
|
||||
expect(keyService.makeKeyPair).toHaveBeenCalledWith(mockMakeUserKeyResult[0]);
|
||||
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
|
||||
tokenResponse.keyConnectorUrl,
|
||||
expect.any(KeyConnectorUserKeyRequest),
|
||||
);
|
||||
expect(apiService.postSetKeyConnectorKey).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function organizationData(
|
||||
usesKeyConnector: boolean,
|
||||
keyConnectorEnabled: boolean,
|
||||
|
||||
@@ -160,7 +160,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
|
||||
const userKey = await this.keyService.makeUserKey(masterKey);
|
||||
await this.keyService.setUserKey(userKey[0], userId);
|
||||
await this.keyService.setMasterKeyEncryptedUserKey(userKey[1].encryptedString, userId);
|
||||
await this.masterPasswordService.setMasterKeyEncryptedUserKey(userKey[1], userId);
|
||||
|
||||
const [pubKey, privKey] = await this.keyService.makeKeyPair(userKey[0]);
|
||||
|
||||
|
||||
@@ -153,4 +153,41 @@ describe("MasterPasswordService", () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("setMasterKeyEncryptedUserKey", () => {
|
||||
test.each([null as unknown as EncString, undefined as unknown as EncString])(
|
||||
"throws when the provided encryptedKey is %s",
|
||||
async (encryptedKey) => {
|
||||
await expect(sut.setMasterKeyEncryptedUserKey(encryptedKey, userId)).rejects.toThrow(
|
||||
"Encrypted Key is required.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("throws an error if encryptedKey is malformed null", async () => {
|
||||
await expect(
|
||||
sut.setMasterKeyEncryptedUserKey(new EncString(null as unknown as string), userId),
|
||||
).rejects.toThrow("Encrypted Key is required.");
|
||||
});
|
||||
|
||||
test.each([null as unknown as UserId, undefined as unknown as UserId])(
|
||||
"throws when the provided userId is %s",
|
||||
async (userId) => {
|
||||
await expect(
|
||||
sut.setMasterKeyEncryptedUserKey(new EncString(testMasterKeyEncryptedKey), userId),
|
||||
).rejects.toThrow("User ID is required.");
|
||||
},
|
||||
);
|
||||
|
||||
it("calls stateProvider with the provided encryptedKey and user ID", async () => {
|
||||
const encryptedKey = new EncString(testMasterKeyEncryptedKey);
|
||||
|
||||
await sut.setMasterKeyEncryptedUserKey(encryptedKey, userId);
|
||||
|
||||
expect(stateProvider.getUser).toHaveBeenCalled();
|
||||
expect(mockUserState.update).toHaveBeenCalled();
|
||||
const updateFn = mockUserState.update.mock.calls[0][0];
|
||||
expect(updateFn(null)).toEqual(encryptedKey.toJSON());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -130,7 +130,7 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr
|
||||
}
|
||||
|
||||
async setMasterKeyEncryptedUserKey(encryptedKey: EncString, userId: UserId): Promise<void> {
|
||||
if (encryptedKey == null) {
|
||||
if (encryptedKey == null || encryptedKey.encryptedString == null) {
|
||||
throw new Error("Encrypted Key is required.");
|
||||
}
|
||||
if (userId == null) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ProfileOrganizationResponse } from "../../admin-console/models/response/profile-organization.response";
|
||||
import { ProfileProviderOrganizationResponse } from "../../admin-console/models/response/profile-provider-organization.response";
|
||||
import { ProfileProviderResponse } from "../../admin-console/models/response/profile-provider.response";
|
||||
import { EncString } from "../../platform/models/domain/enc-string";
|
||||
import { UserId } from "../../types/guid";
|
||||
|
||||
import { BaseResponse } from "./base.response";
|
||||
@@ -14,7 +15,7 @@ export class ProfileResponse extends BaseResponse {
|
||||
premiumFromOrganization: boolean;
|
||||
culture: string;
|
||||
twoFactorEnabled: boolean;
|
||||
key: string;
|
||||
key?: EncString;
|
||||
avatarColor: string;
|
||||
creationDate: string;
|
||||
privateKey: string;
|
||||
@@ -36,7 +37,10 @@ export class ProfileResponse extends BaseResponse {
|
||||
this.premiumFromOrganization = this.getResponseProperty("PremiumFromOrganization");
|
||||
this.culture = this.getResponseProperty("Culture");
|
||||
this.twoFactorEnabled = this.getResponseProperty("TwoFactorEnabled");
|
||||
this.key = this.getResponseProperty("Key");
|
||||
const key = this.getResponseProperty("Key");
|
||||
if (key) {
|
||||
this.key = new EncString(key);
|
||||
}
|
||||
this.avatarColor = this.getResponseProperty("AvatarColor");
|
||||
this.creationDate = this.getResponseProperty("CreationDate");
|
||||
this.privateKey = this.getResponseProperty("PrivateKey");
|
||||
|
||||
@@ -225,7 +225,10 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
throw new Error("Stamp has changed");
|
||||
}
|
||||
|
||||
await this.keyService.setMasterKeyEncryptedUserKey(response.key, response.id);
|
||||
// Users with no master password will not have a key.
|
||||
if (response?.key) {
|
||||
await this.masterPasswordService.setMasterKeyEncryptedUserKey(response.key, response.id);
|
||||
}
|
||||
await this.keyService.setPrivateKey(response.privateKey, response.id);
|
||||
await this.keyService.setProviderKeys(response.providers, response.id);
|
||||
await this.keyService.setOrgKeys(
|
||||
|
||||
Reference in New Issue
Block a user