mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
[PM-18038] Fix safari using outdated biometrics protocol (#13287)
* Fix safari using outdated biometrics protocol * Remove logging * Remove log * Move canEnableBiometricUnlock to biometric service * Fix build * Add tests * Fix type error * Attempt to fix build * Fix build * Fix test failure
This commit is contained in:
@@ -233,11 +233,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
|
|||||||
.pipe(
|
.pipe(
|
||||||
switchMap(async () => {
|
switchMap(async () => {
|
||||||
const status = await this.biometricsService.getBiometricsStatusForUser(activeAccount.id);
|
const status = await this.biometricsService.getBiometricsStatusForUser(activeAccount.id);
|
||||||
const biometricSettingAvailable =
|
const biometricSettingAvailable = await this.biometricsService.canEnableBiometricUnlock();
|
||||||
!(await BrowserApi.permissionsGranted(["nativeMessaging"])) ||
|
|
||||||
(status !== BiometricsStatus.DesktopDisconnected &&
|
|
||||||
status !== BiometricsStatus.NotEnabledInConnectedDesktopApp) ||
|
|
||||||
(await this.vaultTimeoutSettingsService.isBiometricLockSet());
|
|
||||||
if (!biometricSettingAvailable) {
|
if (!biometricSettingAvailable) {
|
||||||
this.form.controls.biometric.disable({ emitEvent: false });
|
this.form.controls.biometric.disable({ emitEvent: false });
|
||||||
} else {
|
} else {
|
||||||
@@ -256,6 +252,13 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
|
|||||||
"biometricsStatusHelptextNotEnabledInDesktop",
|
"biometricsStatusHelptextNotEnabledInDesktop",
|
||||||
activeAccount.email,
|
activeAccount.email,
|
||||||
);
|
);
|
||||||
|
} else if (
|
||||||
|
status === BiometricsStatus.HardwareUnavailable &&
|
||||||
|
!biometricSettingAvailable
|
||||||
|
) {
|
||||||
|
this.biometricUnavailabilityReason = this.i18nService.t(
|
||||||
|
"biometricsStatusHelptextHardwareUnavailable",
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.biometricUnavailabilityReason = "";
|
this.biometricUnavailabilityReason = "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -672,14 +672,6 @@ export default class MainBackground {
|
|||||||
this.kdfConfigService,
|
this.kdfConfigService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.biometricsService = new BackgroundBrowserBiometricsService(
|
|
||||||
runtimeNativeMessagingBackground,
|
|
||||||
this.logService,
|
|
||||||
this.keyService,
|
|
||||||
this.biometricStateService,
|
|
||||||
this.messagingService,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.appIdService = new AppIdService(this.storageService, this.logService);
|
this.appIdService = new AppIdService(this.storageService, this.logService);
|
||||||
|
|
||||||
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
|
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
|
||||||
@@ -699,6 +691,15 @@ export default class MainBackground {
|
|||||||
VaultTimeoutStringType.OnRestart, // default vault timeout
|
VaultTimeoutStringType.OnRestart, // default vault timeout
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.biometricsService = new BackgroundBrowserBiometricsService(
|
||||||
|
runtimeNativeMessagingBackground,
|
||||||
|
this.logService,
|
||||||
|
this.keyService,
|
||||||
|
this.biometricStateService,
|
||||||
|
this.messagingService,
|
||||||
|
this.vaultTimeoutSettingsService,
|
||||||
|
);
|
||||||
|
|
||||||
this.apiService = new ApiService(
|
this.apiService = new ApiService(
|
||||||
this.tokenService,
|
this.tokenService,
|
||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
|
|||||||
@@ -120,9 +120,15 @@ export class NativeMessagingBackground {
|
|||||||
this.connecting = true;
|
this.connecting = true;
|
||||||
|
|
||||||
const connectedCallback = () => {
|
const connectedCallback = () => {
|
||||||
|
if (!this.platformUtilsService.isSafari()) {
|
||||||
this.logService.info(
|
this.logService.info(
|
||||||
"[Native Messaging IPC] Connection to Bitwarden Desktop app established!",
|
"[Native Messaging IPC] Connection to Bitwarden Desktop app established!",
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
this.logService.info(
|
||||||
|
"[Native Messaging IPC] Connection to Safari swift module established!",
|
||||||
|
);
|
||||||
|
}
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
this.connecting = false;
|
this.connecting = false;
|
||||||
resolve();
|
resolve();
|
||||||
@@ -131,6 +137,7 @@ export class NativeMessagingBackground {
|
|||||||
// Safari has a bundled native component which is always available, no need to
|
// Safari has a bundled native component which is always available, no need to
|
||||||
// check if the desktop app is running.
|
// check if the desktop app is running.
|
||||||
if (this.platformUtilsService.isSafari()) {
|
if (this.platformUtilsService.isSafari()) {
|
||||||
|
this.isConnectedToOutdatedDesktopClient = false;
|
||||||
connectedCallback();
|
connectedCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,7 +435,9 @@ export class NativeMessagingBackground {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.callbacks.has(messageId)) {
|
if (this.callbacks.has(messageId)) {
|
||||||
this.callbacks.get(messageId)!.resolver(message);
|
const callback = this.callbacks!.get(messageId)!;
|
||||||
|
this.callbacks.delete(messageId);
|
||||||
|
callback.resolver(message);
|
||||||
} else {
|
} else {
|
||||||
this.logService.info("[Native Messaging IPC] Received message without a callback", message);
|
this.logService.info("[Native Messaging IPC] Received message without a callback", message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export default class RuntimeBackground {
|
|||||||
BiometricsCommands.GetBiometricsStatus,
|
BiometricsCommands.GetBiometricsStatus,
|
||||||
BiometricsCommands.UnlockWithBiometricsForUser,
|
BiometricsCommands.UnlockWithBiometricsForUser,
|
||||||
BiometricsCommands.GetBiometricsStatusForUser,
|
BiometricsCommands.GetBiometricsStatusForUser,
|
||||||
|
BiometricsCommands.CanEnableBiometricUnlock,
|
||||||
"getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag",
|
"getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag",
|
||||||
"getInlineMenuFieldQualificationFeatureFlag",
|
"getInlineMenuFieldQualificationFeatureFlag",
|
||||||
"getUserPremiumStatus",
|
"getUserPremiumStatus",
|
||||||
@@ -201,6 +202,9 @@ export default class RuntimeBackground {
|
|||||||
case BiometricsCommands.GetBiometricsStatusForUser: {
|
case BiometricsCommands.GetBiometricsStatusForUser: {
|
||||||
return await this.main.biometricsService.getBiometricsStatusForUser(msg.userId);
|
return await this.main.biometricsService.getBiometricsStatusForUser(msg.userId);
|
||||||
}
|
}
|
||||||
|
case BiometricsCommands.CanEnableBiometricUnlock: {
|
||||||
|
return await this.main.biometricsService.canEnableBiometricUnlock();
|
||||||
|
}
|
||||||
case "getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag": {
|
case "getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag": {
|
||||||
return await this.configService.getFeatureFlag(
|
return await this.configService.getFeatureFlag(
|
||||||
FeatureFlag.UseTreeWalkerApiForPageDetailsCollection,
|
FeatureFlag.UseTreeWalkerApiForPageDetailsCollection,
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout";
|
||||||
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
import { KeyService, BiometricStateService, BiometricsStatus } from "@bitwarden/key-management";
|
||||||
|
|
||||||
|
import { NativeMessagingBackground } from "../../background/nativeMessaging.background";
|
||||||
|
|
||||||
|
import { BackgroundBrowserBiometricsService } from "./background-browser-biometrics.service";
|
||||||
|
|
||||||
|
describe("background browser biometrics service tests", function () {
|
||||||
|
let service: BackgroundBrowserBiometricsService;
|
||||||
|
|
||||||
|
const nativeMessagingBackground = mock<NativeMessagingBackground>();
|
||||||
|
const logService = mock<LogService>();
|
||||||
|
const keyService = mock<KeyService>();
|
||||||
|
const biometricStateService = mock<BiometricStateService>();
|
||||||
|
const messagingService = mock<MessagingService>();
|
||||||
|
const vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
service = new BackgroundBrowserBiometricsService(
|
||||||
|
() => nativeMessagingBackground,
|
||||||
|
logService,
|
||||||
|
keyService,
|
||||||
|
biometricStateService,
|
||||||
|
messagingService,
|
||||||
|
vaultTimeoutSettingsService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("canEnableBiometricUnlock", () => {
|
||||||
|
const table: [BiometricsStatus, boolean, boolean][] = [
|
||||||
|
// status, already enabled, expected
|
||||||
|
|
||||||
|
// if the setting is not already on, it should only be possible to enable it if biometrics are available
|
||||||
|
[BiometricsStatus.Available, false, true],
|
||||||
|
[BiometricsStatus.HardwareUnavailable, false, false],
|
||||||
|
[BiometricsStatus.NotEnabledInConnectedDesktopApp, false, false],
|
||||||
|
[BiometricsStatus.DesktopDisconnected, false, false],
|
||||||
|
|
||||||
|
// if the setting is already on, it should always be possible to disable it
|
||||||
|
[BiometricsStatus.Available, true, true],
|
||||||
|
[BiometricsStatus.HardwareUnavailable, true, true],
|
||||||
|
[BiometricsStatus.NotEnabledInConnectedDesktopApp, true, true],
|
||||||
|
[BiometricsStatus.DesktopDisconnected, true, true],
|
||||||
|
];
|
||||||
|
test.each(table)(
|
||||||
|
"status: %s, already enabled: %s, expected: %s",
|
||||||
|
async (status, alreadyEnabled, expected) => {
|
||||||
|
service.getBiometricsStatus = jest.fn().mockResolvedValue(status);
|
||||||
|
vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(alreadyEnabled);
|
||||||
|
const result = await service.canEnableBiometricUnlock();
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
@@ -25,6 +26,7 @@ export class BackgroundBrowserBiometricsService extends BiometricsService {
|
|||||||
private keyService: KeyService,
|
private keyService: KeyService,
|
||||||
private biometricStateService: BiometricStateService,
|
private biometricStateService: BiometricStateService,
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
|
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -169,4 +171,14 @@ export class BackgroundBrowserBiometricsService extends BiometricsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setShouldAutopromptNow(value: boolean): Promise<void> {}
|
async setShouldAutopromptNow(value: boolean): Promise<void> {}
|
||||||
|
async canEnableBiometricUnlock(): Promise<boolean> {
|
||||||
|
const status = await this.getBiometricsStatus();
|
||||||
|
const isBiometricsAlreadyEnabled = await this.vaultTimeoutSettingsService.isBiometricLockSet();
|
||||||
|
const statusAllowsBiometric =
|
||||||
|
status !== BiometricsStatus.DesktopDisconnected &&
|
||||||
|
status !== BiometricsStatus.NotEnabledInConnectedDesktopApp &&
|
||||||
|
status !== BiometricsStatus.HardwareUnavailable;
|
||||||
|
|
||||||
|
return statusAllowsBiometric || isBiometricsAlreadyEnabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||||
|
|
||||||
|
import { ForegroundBrowserBiometricsService } from "./foreground-browser-biometrics";
|
||||||
|
|
||||||
|
jest.mock("../../platform/browser/browser-api", () => ({
|
||||||
|
BrowserApi: {
|
||||||
|
sendMessageWithResponse: jest.fn(),
|
||||||
|
permissionsGranted: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("foreground browser biometrics service tests", function () {
|
||||||
|
const platformUtilsService = mock<PlatformUtilsService>();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("canEnableBiometricUnlock", () => {
|
||||||
|
const table: [boolean, boolean, boolean, boolean][] = [
|
||||||
|
// canEnableBiometricUnlock from background, native permission granted, isSafari, expected
|
||||||
|
|
||||||
|
// needs permission prompt; always allowed
|
||||||
|
[true, false, false, true],
|
||||||
|
[false, false, false, true],
|
||||||
|
|
||||||
|
// is safari; depends on the status that the background service reports
|
||||||
|
[false, false, true, false],
|
||||||
|
[true, false, true, true],
|
||||||
|
|
||||||
|
// native permissions granted; depends on the status that the background service reports
|
||||||
|
[false, true, false, false],
|
||||||
|
[true, true, false, true],
|
||||||
|
|
||||||
|
// should never happen since safari does not use the permissions
|
||||||
|
[false, true, true, false],
|
||||||
|
[true, true, true, true],
|
||||||
|
];
|
||||||
|
test.each(table)(
|
||||||
|
"canEnableBiometric: %s, native permission granted: %s, isSafari: %s, expected: %s",
|
||||||
|
async (canEnableBiometricUnlockBackground, granted, isSafari, expected) => {
|
||||||
|
const service = new ForegroundBrowserBiometricsService(platformUtilsService);
|
||||||
|
|
||||||
|
(BrowserApi.permissionsGranted as jest.Mock).mockResolvedValue(granted);
|
||||||
|
(BrowserApi.sendMessageWithResponse as jest.Mock).mockResolvedValue({
|
||||||
|
result: canEnableBiometricUnlockBackground,
|
||||||
|
});
|
||||||
|
platformUtilsService.isSafari.mockReturnValue(isSafari);
|
||||||
|
|
||||||
|
const result = await service.canEnableBiometricUnlock();
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { UserKey } from "@bitwarden/common/types/key";
|
import { UserKey } from "@bitwarden/common/types/key";
|
||||||
@@ -8,6 +9,10 @@ import { BrowserApi } from "../../platform/browser/browser-api";
|
|||||||
export class ForegroundBrowserBiometricsService extends BiometricsService {
|
export class ForegroundBrowserBiometricsService extends BiometricsService {
|
||||||
shouldAutopromptNow = true;
|
shouldAutopromptNow = true;
|
||||||
|
|
||||||
|
constructor(private platformUtilsService: PlatformUtilsService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
async authenticateWithBiometrics(): Promise<boolean> {
|
async authenticateWithBiometrics(): Promise<boolean> {
|
||||||
const response = await BrowserApi.sendMessageWithResponse<{
|
const response = await BrowserApi.sendMessageWithResponse<{
|
||||||
result: boolean;
|
result: boolean;
|
||||||
@@ -52,4 +57,19 @@ export class ForegroundBrowserBiometricsService extends BiometricsService {
|
|||||||
async setShouldAutopromptNow(value: boolean): Promise<void> {
|
async setShouldAutopromptNow(value: boolean): Promise<void> {
|
||||||
this.shouldAutopromptNow = value;
|
this.shouldAutopromptNow = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async canEnableBiometricUnlock(): Promise<boolean> {
|
||||||
|
const needsPermissionPrompt =
|
||||||
|
!(await BrowserApi.permissionsGranted(["nativeMessaging"])) &&
|
||||||
|
!this.platformUtilsService.isSafari();
|
||||||
|
return (
|
||||||
|
needsPermissionPrompt ||
|
||||||
|
(
|
||||||
|
await BrowserApi.sendMessageWithResponse<{
|
||||||
|
result: boolean;
|
||||||
|
error: string;
|
||||||
|
}>(BiometricsCommands.CanEnableBiometricUnlock)
|
||||||
|
).result
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -316,10 +316,8 @@ const safeProviders: SafeProvider[] = [
|
|||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: BiometricsService,
|
provide: BiometricsService,
|
||||||
useFactory: () => {
|
useClass: ForegroundBrowserBiometricsService,
|
||||||
return new ForegroundBrowserBiometricsService();
|
deps: [PlatformUtilsService],
|
||||||
},
|
|
||||||
deps: [],
|
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: SyncService,
|
provide: SyncService,
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
|||||||
response.userInfo = [
|
response.userInfo = [
|
||||||
SFExtensionMessageKey: [
|
SFExtensionMessageKey: [
|
||||||
"message": [
|
"message": [
|
||||||
"command": "biometricUnlock",
|
"command": "unlockWithBiometricsForUser",
|
||||||
"response": false,
|
"response": false,
|
||||||
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
||||||
"messageId": messageId,
|
"messageId": messageId,
|
||||||
@@ -177,7 +177,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
|||||||
response.userInfo = [
|
response.userInfo = [
|
||||||
SFExtensionMessageKey: [
|
SFExtensionMessageKey: [
|
||||||
"message": [
|
"message": [
|
||||||
"command": "biometricUnlock",
|
"command": "unlockWithBiometricsForUser",
|
||||||
"response": false,
|
"response": false,
|
||||||
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
||||||
"messageId": messageId,
|
"messageId": messageId,
|
||||||
@@ -209,7 +209,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
|||||||
|
|
||||||
response.userInfo = [ SFExtensionMessageKey: [
|
response.userInfo = [ SFExtensionMessageKey: [
|
||||||
"message": [
|
"message": [
|
||||||
"command": "biometricUnlock",
|
"command": "unlockWithBiometricsForUser",
|
||||||
"response": true,
|
"response": true,
|
||||||
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
||||||
"userKeyB64": result!.replacingOccurrences(of: "\"", with: ""),
|
"userKeyB64": result!.replacingOccurrences(of: "\"", with: ""),
|
||||||
@@ -220,7 +220,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
|||||||
response.userInfo = [
|
response.userInfo = [
|
||||||
SFExtensionMessageKey: [
|
SFExtensionMessageKey: [
|
||||||
"message": [
|
"message": [
|
||||||
"command": "biometricUnlock",
|
"command": "unlockWithBiometricsForUser",
|
||||||
"response": true,
|
"response": true,
|
||||||
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
||||||
"messageId": messageId,
|
"messageId": messageId,
|
||||||
|
|||||||
@@ -24,4 +24,7 @@ export class CliBiometricsService extends BiometricsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setShouldAutopromptNow(value: boolean): Promise<void> {}
|
async setShouldAutopromptNow(value: boolean): Promise<void> {}
|
||||||
|
async canEnableBiometricUnlock(): Promise<boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ describe("SettingsComponent", () => {
|
|||||||
describe("biometrics enabled", () => {
|
describe("biometrics enabled", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
desktopBiometricsService.getBiometricsStatus.mockResolvedValue(BiometricsStatus.Available);
|
desktopBiometricsService.getBiometricsStatus.mockResolvedValue(BiometricsStatus.Available);
|
||||||
|
desktopBiometricsService.canEnableBiometricUnlock.mockResolvedValue(true);
|
||||||
vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(true);
|
vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -388,24 +388,12 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.supportsBiometric = this.shouldAllowBiometricSetup(
|
this.supportsBiometric = await this.biometricsService.canEnableBiometricUnlock();
|
||||||
await this.biometricsService.getBiometricsStatus(),
|
|
||||||
);
|
|
||||||
this.timerId = setInterval(async () => {
|
this.timerId = setInterval(async () => {
|
||||||
this.supportsBiometric = this.shouldAllowBiometricSetup(
|
this.supportsBiometric = await this.biometricsService.canEnableBiometricUnlock();
|
||||||
await this.biometricsService.getBiometricsStatus(),
|
|
||||||
);
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private shouldAllowBiometricSetup(biometricStatus: BiometricsStatus): boolean {
|
|
||||||
return [
|
|
||||||
BiometricsStatus.Available,
|
|
||||||
BiometricsStatus.AutoSetupNeeded,
|
|
||||||
BiometricsStatus.ManualSetupNeeded,
|
|
||||||
].includes(biometricStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveVaultTimeout(newValue: VaultTimeout) {
|
async saveVaultTimeout(newValue: VaultTimeout) {
|
||||||
if (newValue === VaultTimeoutStringType.Never) {
|
if (newValue === VaultTimeoutStringType.Never) {
|
||||||
const confirmed = await this.dialogService.openSimpleDialog({
|
const confirmed = await this.dialogService.openSimpleDialog({
|
||||||
|
|||||||
@@ -163,4 +163,8 @@ export class MainBiometricsService extends DesktopBiometricsService {
|
|||||||
async getShouldAutopromptNow(): Promise<boolean> {
|
async getShouldAutopromptNow(): Promise<boolean> {
|
||||||
return this.shouldAutoPrompt;
|
return this.shouldAutoPrompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async canEnableBiometricUnlock(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { BiometricsStatus } from "@bitwarden/key-management";
|
||||||
|
|
||||||
|
import { RendererBiometricsService } from "./renderer-biometrics.service";
|
||||||
|
|
||||||
|
describe("renderer biometrics service tests", function () {
|
||||||
|
beforeEach(() => {
|
||||||
|
(global as any).ipc = {
|
||||||
|
keyManagement: {
|
||||||
|
biometric: {
|
||||||
|
authenticateWithBiometrics: jest.fn(),
|
||||||
|
getBiometricsStatus: jest.fn(),
|
||||||
|
unlockWithBiometricsForUser: jest.fn(),
|
||||||
|
getBiometricsStatusForUser: jest.fn(),
|
||||||
|
deleteBiometricUnlockKeyForUser: jest.fn(),
|
||||||
|
setupBiometrics: jest.fn(),
|
||||||
|
setClientKeyHalfForUser: jest.fn(),
|
||||||
|
getShouldAutoprompt: jest.fn(),
|
||||||
|
setShouldAutoprompt: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("canEnableBiometricUnlock", () => {
|
||||||
|
const table: [BiometricsStatus, boolean][] = [
|
||||||
|
[BiometricsStatus.Available, true],
|
||||||
|
[BiometricsStatus.AutoSetupNeeded, true],
|
||||||
|
[BiometricsStatus.ManualSetupNeeded, true],
|
||||||
|
|
||||||
|
[BiometricsStatus.UnlockNeeded, false],
|
||||||
|
[BiometricsStatus.HardwareUnavailable, false],
|
||||||
|
[BiometricsStatus.PlatformUnsupported, false],
|
||||||
|
[BiometricsStatus.NotEnabledLocally, false],
|
||||||
|
];
|
||||||
|
test.each(table)("canEnableBiometricUnlock(%s) === %s", async (status, expected) => {
|
||||||
|
const service = new RendererBiometricsService();
|
||||||
|
(global as any).ipc.keyManagement.biometric.getBiometricsStatus.mockResolvedValue(status);
|
||||||
|
|
||||||
|
const result = await service.canEnableBiometricUnlock();
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -51,4 +51,13 @@ export class RendererBiometricsService extends DesktopBiometricsService {
|
|||||||
async setShouldAutopromptNow(value: boolean): Promise<void> {
|
async setShouldAutopromptNow(value: boolean): Promise<void> {
|
||||||
return await ipc.keyManagement.biometric.setShouldAutoprompt(value);
|
return await ipc.keyManagement.biometric.setShouldAutoprompt(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async canEnableBiometricUnlock(): Promise<boolean> {
|
||||||
|
const biometricStatus = await this.getBiometricsStatus();
|
||||||
|
return [
|
||||||
|
BiometricsStatus.Available,
|
||||||
|
BiometricsStatus.AutoSetupNeeded,
|
||||||
|
BiometricsStatus.ManualSetupNeeded,
|
||||||
|
].includes(biometricStatus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,8 @@ export class WebBiometricsService extends BiometricsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setShouldAutopromptNow(value: boolean): Promise<void> {}
|
async setShouldAutopromptNow(value: boolean): Promise<void> {}
|
||||||
|
|
||||||
|
async canEnableBiometricUnlock(): Promise<boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,4 +39,5 @@ export abstract class BiometricsService {
|
|||||||
|
|
||||||
abstract getShouldAutopromptNow(): Promise<boolean>;
|
abstract getShouldAutopromptNow(): Promise<boolean>;
|
||||||
abstract setShouldAutopromptNow(value: boolean): Promise<void>;
|
abstract setShouldAutopromptNow(value: boolean): Promise<void>;
|
||||||
|
abstract canEnableBiometricUnlock(): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ export enum BiometricsCommands {
|
|||||||
/** Get biometric status for a specific user account. This includes both information about availability of cryptographic material (is the user configured for biometric unlock? is a masterpassword unlock needed? But also information about the biometric system's availability in a single status) */
|
/** Get biometric status for a specific user account. This includes both information about availability of cryptographic material (is the user configured for biometric unlock? is a masterpassword unlock needed? But also information about the biometric system's availability in a single status) */
|
||||||
GetBiometricsStatusForUser = "getBiometricsStatusForUser",
|
GetBiometricsStatusForUser = "getBiometricsStatusForUser",
|
||||||
|
|
||||||
|
/** Checks whether the biometric unlock can be enabled. */
|
||||||
|
CanEnableBiometricUnlock = "canEnableBiometricUnlock",
|
||||||
|
|
||||||
// legacy
|
// legacy
|
||||||
Unlock = "biometricUnlock",
|
Unlock = "biometricUnlock",
|
||||||
IsAvailable = "biometricUnlockAvailable",
|
IsAvailable = "biometricUnlockAvailable",
|
||||||
|
|||||||
Reference in New Issue
Block a user