1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-01 23:01:28 +01:00

[EC-598] feat: add user confirmation test to assertion

also rewrite to use cipher views in tests
This commit is contained in:
Andreas Coroiu 2023-03-29 16:23:19 +02:00
parent c2ec87a3f3
commit 597bc0b197
No known key found for this signature in database
GPG Key ID: E70B5FFC81DFEC1A
2 changed files with 76 additions and 40 deletions

View File

@ -7,8 +7,8 @@ import { Utils } from "../../misc/utils";
import { CipherService } from "../../vault/abstractions/cipher.service";
import { CipherType } from "../../vault/enums/cipher-type";
import { Cipher } from "../../vault/models/domain/cipher";
import { Login } from "../../vault/models/domain/login";
import { CipherView } from "../../vault/models/view/cipher.view";
import { LoginView } from "../../vault/models/view/login.view";
import {
Fido2AutenticatorErrorCode,
Fido2AuthenticatorGetAssertionParams,
@ -19,7 +19,7 @@ import {
NewCredentialParams,
} from "../abstractions/fido2-user-interface.service.abstraction";
import { Fido2Utils } from "../abstractions/fido2-utils";
import { Fido2Key } from "../models/domain/fido2-key";
import { Fido2KeyView } from "../models/view/fido2-key.view";
import { AAGUID, Fido2AuthenticatorService } from "./fido2-authenticator.service";
@ -93,25 +93,26 @@ describe("FidoAuthenticatorService", () => {
describe.skip("when extensions parameter is present", () => undefined);
describe("vault contains excluded non-discoverable credential", () => {
let excludedCipherView: CipherView;
let excludedCipher: CipherView;
let params: Fido2AuthenticatorMakeCredentialsParams;
beforeEach(async () => {
const excludedCipher = createCipher({ type: CipherType.Login });
excludedCipherView = await excludedCipher.decrypt();
excludedCipherView.fido2Key.nonDiscoverableId = Utils.newGuid();
excludedCipher = createCipherView(
{ type: CipherType.Login },
{ nonDiscoverableId: Utils.newGuid() }
);
params = await createParams({
excludeCredentialDescriptorList: [
{
id: Utils.guidToRawFormat(excludedCipherView.fido2Key.nonDiscoverableId),
id: Utils.guidToRawFormat(excludedCipher.fido2Key.nonDiscoverableId),
type: "public-key",
},
],
});
cipherService.get.mockImplementation(async (id) =>
id === excludedCipher.id ? excludedCipher : undefined
id === excludedCipher.id ? ({ decrypt: () => excludedCipher } as any) : undefined
);
cipherService.getAllDecrypted.mockResolvedValue([excludedCipherView]);
cipherService.getAllDecrypted.mockResolvedValue([excludedCipher]);
});
/**
@ -161,8 +162,8 @@ describe("FidoAuthenticatorService", () => {
let params: Fido2AuthenticatorMakeCredentialsParams;
beforeEach(async () => {
const excludedCipher = createCipher();
excludedCipherView = await excludedCipher.decrypt();
const excludedCipher = createCipherView();
excludedCipherView = await excludedCipher;
params = await createParams({
excludeCredentialDescriptorList: [
{ id: Utils.guidToRawFormat(excludedCipher.id), type: "public-key" },
@ -300,19 +301,16 @@ describe("FidoAuthenticatorService", () => {
});
describe("creation of non-discoverable credential", () => {
let existingCipherView: CipherView;
let existingCipher: CipherView;
let params: Fido2AuthenticatorMakeCredentialsParams;
beforeEach(async () => {
const existingCipher = createCipher({ type: CipherType.Login });
existingCipher.login = new Login();
existingCipher.fido2Key = undefined;
existingCipherView = await existingCipher.decrypt();
existingCipher = createCipherView({ type: CipherType.Login });
params = await createParams();
cipherService.get.mockImplementation(async (id) =>
id === existingCipher.id ? existingCipher : undefined
id === existingCipher.id ? ({ decrypt: () => existingCipher } as any) : undefined
);
cipherService.getAllDecrypted.mockResolvedValue([existingCipherView]);
cipherService.getAllDecrypted.mockResolvedValue([existingCipher]);
});
/**
@ -320,7 +318,7 @@ describe("FidoAuthenticatorService", () => {
* Deviation: Only `rpEntity.name` and `userEntity.name` is shown.
* */
it("should request confirmation from user", async () => {
userInterface.confirmNewNonDiscoverableCredential.mockResolvedValue(existingCipherView.id);
userInterface.confirmNewNonDiscoverableCredential.mockResolvedValue(existingCipher.id);
await authenticator.makeCredential(params);
@ -332,7 +330,7 @@ describe("FidoAuthenticatorService", () => {
it("should save credential to vault if request confirmed by user", async () => {
const encryptedCipher = Symbol();
userInterface.confirmNewNonDiscoverableCredential.mockResolvedValue(existingCipherView.id);
userInterface.confirmNewNonDiscoverableCredential.mockResolvedValue(existingCipher.id);
cipherService.encrypt.mockResolvedValue(encryptedCipher as unknown as Cipher);
await authenticator.makeCredential(params);
@ -341,7 +339,7 @@ describe("FidoAuthenticatorService", () => {
expect(saved).toEqual(
expect.objectContaining({
type: CipherType.Login,
name: existingCipherView.name,
name: existingCipher.name,
fido2Key: expect.objectContaining({
nonDiscoverableId: expect.anything(),
@ -372,7 +370,7 @@ describe("FidoAuthenticatorService", () => {
/** Spec: If any error occurred while creating the new credential object, return an error code equivalent to "UnknownError" and terminate the operation. */
it("should throw unkown error if creation fails", async () => {
const encryptedCipher = Symbol();
userInterface.confirmNewNonDiscoverableCredential.mockResolvedValue(existingCipherView.id);
userInterface.confirmNewNonDiscoverableCredential.mockResolvedValue(existingCipher.id);
cipherService.encrypt.mockResolvedValue(encryptedCipher as unknown as Cipher);
cipherService.updateWithServer.mockRejectedValue(new Error("Internal error"));
@ -399,14 +397,14 @@ describe("FidoAuthenticatorService", () => {
let params: Fido2AuthenticatorMakeCredentialsParams;
beforeEach(async () => {
const cipher = createCipher({ id: cipherId, type: CipherType.Login });
const cipher = createCipherView({ id: cipherId, type: CipherType.Login });
params = await createParams({ requireResidentKey });
userInterface.confirmNewNonDiscoverableCredential.mockResolvedValue(cipherId);
userInterface.confirmNewCredential.mockResolvedValue(true);
cipherService.get.mockImplementation(async (cipherId) =>
cipherId === cipher.id ? cipher : undefined
cipherId === cipher.id ? ({ decrypt: () => cipher } as any) : undefined
);
cipherService.getAllDecrypted.mockResolvedValue([await cipher.decrypt()]);
cipherService.getAllDecrypted.mockResolvedValue([await cipher]);
cipherService.encrypt.mockImplementation(async (cipher) => {
cipher.fido2Key.nonDiscoverableId = nonDiscoverableId; // Replace id for testability
return {} as any;
@ -537,14 +535,14 @@ describe("FidoAuthenticatorService", () => {
});
describe("vault is missing non-discoverable credential", () => {
let excludedId: string;
let credentialId: string;
let params: Fido2AuthenticatorGetAssertionParams;
beforeEach(async () => {
excludedId = Utils.newGuid();
credentialId = Utils.newGuid();
params = await createParams({
allowCredentialDescriptorList: [
{ id: Utils.guidToRawFormat(excludedId), type: "public-key" },
{ id: Utils.guidToRawFormat(credentialId), type: "public-key" },
],
rpId: RpId,
});
@ -560,8 +558,8 @@ describe("FidoAuthenticatorService", () => {
});
it("should throw error if credential exists but rpId does not match", async () => {
const cipher = await createCipher({ type: CipherType.Login }).decrypt();
cipher.fido2Key.nonDiscoverableId = excludedId;
const cipher = await createCipherView({ type: CipherType.Login });
cipher.fido2Key.nonDiscoverableId = credentialId;
cipher.fido2Key.rpId = "mismatch-rpid";
cipherService.getAllDecrypted.mockResolvedValue([cipher]);
@ -590,6 +588,36 @@ describe("FidoAuthenticatorService", () => {
});
});
describe("assertion of non-discoverable credential", () => {
let credentialIds: string[];
let ciphers: CipherView[];
let params: Fido2AuthenticatorGetAssertionParams;
beforeEach(async () => {
credentialIds = [Utils.newGuid(), Utils.newGuid()];
ciphers = await Promise.all(
credentialIds.map((id) =>
createCipherView({ type: CipherType.Login }, { nonDiscoverableId: id, rpId: RpId })
)
);
params = await createParams({
allowCredentialDescriptorList: credentialIds.map((credentialId) => ({
id: Utils.guidToRawFormat(credentialId),
type: "public-key",
})),
rpId: RpId,
});
cipherService.getAllDecrypted.mockResolvedValue(ciphers);
});
/** Spec: Prompt the user to select a public key credential source selectedCredential from credentialOptions. */
it("should request confirmation from the user", async () => {
await authenticator.getAssertion(params);
expect(userInterface.pickCredential).toHaveBeenCalledWith(ciphers.map((c) => c.id));
});
});
async function createParams(
params: Partial<Fido2AuthenticatorGetAssertionParams> = {}
): Promise<Fido2AuthenticatorGetAssertionParams> {
@ -616,12 +644,17 @@ describe("FidoAuthenticatorService", () => {
});
});
function createCipher(data: Partial<Cipher> = {}): Cipher {
const cipher = new Cipher();
function createCipherView(
data: Partial<Omit<CipherView, "fido2Key">> = {},
fido2Key: Partial<Fido2KeyView> = {}
): CipherView {
const cipher = new CipherView();
cipher.id = data.id ?? Utils.newGuid();
cipher.type = data.type ?? CipherType.Fido2Key;
cipher.login = data.type ?? data.type === CipherType.Login ? new Login() : null;
cipher.fido2Key = data.fido2Key ?? new Fido2Key();
cipher.login = data.type ?? data.type === CipherType.Login ? new LoginView() : null;
cipher.fido2Key = new Fido2KeyView();
cipher.fido2Key.nonDiscoverableId = fido2Key.nonDiscoverableId;
cipher.fido2Key.rpId = fido2Key.rpId;
return cipher;
}

View File

@ -146,25 +146,28 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.Constraint);
}
let credentialOptions: Fido2KeyView[];
let credentialOptions: CipherView[];
// eslint-disable-next-line no-empty
if (params.allowCredentialDescriptorList?.length > 0) {
const ciphers = await this.findNonDiscoverableCredentials(
credentialOptions = await this.findNonDiscoverableCredentials(
params.allowCredentialDescriptorList,
params.rpId
);
credentialOptions = ciphers.map((c) => c.fido2Key);
} else {
const ciphers = await this.findDiscoverableCredentials(params.rpId);
credentialOptions = ciphers.map((c) => c.fido2Key);
credentialOptions = await this.findDiscoverableCredentials(params.rpId);
}
if (credentialOptions.length === 0) {
throw new Fido2AutenticatorError(Fido2AutenticatorErrorCode.NotAllowed);
}
throw new Error("Not implemented");
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const selectedCredential = await this.userInterface.pickCredential(
credentialOptions.map((cipher) => cipher.id)
);
return null;
}
private async vaultContainsCredentials(