mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
Add premium guard to phishing detection service (#16602)
This commit is contained in:
@@ -1415,12 +1415,14 @@ export default class MainBackground {
|
|||||||
this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
||||||
|
|
||||||
PhishingDetectionService.initialize(
|
PhishingDetectionService.initialize(
|
||||||
this.configService,
|
this.accountService,
|
||||||
this.auditService,
|
this.auditService,
|
||||||
|
this.billingAccountProfileStateService,
|
||||||
|
this.configService,
|
||||||
|
this.eventCollectionService,
|
||||||
this.logService,
|
this.logService,
|
||||||
this.storageService,
|
this.storageService,
|
||||||
this.taskSchedulerService,
|
this.taskSchedulerService,
|
||||||
this.eventCollectionService,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.ipcContentScriptManagerService = new IpcContentScriptManagerService(this.configService);
|
this.ipcContentScriptManagerService = new IpcContentScriptManagerService(this.configService);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { of } from "rxjs";
|
|||||||
|
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
@@ -10,35 +12,109 @@ import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling/task
|
|||||||
import { PhishingDetectionService } from "./phishing-detection.service";
|
import { PhishingDetectionService } from "./phishing-detection.service";
|
||||||
|
|
||||||
describe("PhishingDetectionService", () => {
|
describe("PhishingDetectionService", () => {
|
||||||
|
let accountService: AccountService;
|
||||||
let auditService: AuditService;
|
let auditService: AuditService;
|
||||||
|
let billingAccountProfileStateService: BillingAccountProfileStateService;
|
||||||
|
let configService: ConfigService;
|
||||||
|
let eventCollectionService: EventCollectionService;
|
||||||
let logService: LogService;
|
let logService: LogService;
|
||||||
let storageService: AbstractStorageService;
|
let storageService: AbstractStorageService;
|
||||||
let taskSchedulerService: TaskSchedulerService;
|
let taskSchedulerService: TaskSchedulerService;
|
||||||
let configService: ConfigService;
|
|
||||||
let eventCollectionService: EventCollectionService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
accountService = { getAccount$: jest.fn(() => of(null)) } as any;
|
||||||
auditService = { getKnownPhishingDomains: jest.fn() } as any;
|
auditService = { getKnownPhishingDomains: jest.fn() } as any;
|
||||||
|
billingAccountProfileStateService = {} as any;
|
||||||
|
configService = { getFeatureFlag$: jest.fn(() => of(false)) } as any;
|
||||||
|
eventCollectionService = {} as any;
|
||||||
logService = { info: jest.fn(), debug: jest.fn(), warning: jest.fn(), error: jest.fn() } as any;
|
logService = { info: jest.fn(), debug: jest.fn(), warning: jest.fn(), error: jest.fn() } as any;
|
||||||
storageService = { get: jest.fn(), save: jest.fn() } as any;
|
storageService = { get: jest.fn(), save: jest.fn() } as any;
|
||||||
taskSchedulerService = { registerTaskHandler: jest.fn(), setInterval: jest.fn() } as any;
|
taskSchedulerService = { registerTaskHandler: jest.fn(), setInterval: jest.fn() } as any;
|
||||||
configService = { getFeatureFlag$: jest.fn(() => of(false)) } as any;
|
|
||||||
eventCollectionService = {} as any;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should initialize without errors", () => {
|
it("should initialize without errors", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
PhishingDetectionService.initialize(
|
PhishingDetectionService.initialize(
|
||||||
configService,
|
accountService,
|
||||||
auditService,
|
auditService,
|
||||||
|
billingAccountProfileStateService,
|
||||||
|
configService,
|
||||||
|
eventCollectionService,
|
||||||
logService,
|
logService,
|
||||||
storageService,
|
storageService,
|
||||||
taskSchedulerService,
|
taskSchedulerService,
|
||||||
eventCollectionService,
|
|
||||||
);
|
);
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should enable phishing detection for premium account", (done) => {
|
||||||
|
const premiumAccount = { id: "user1" };
|
||||||
|
accountService = { activeAccount$: of(premiumAccount) } as any;
|
||||||
|
configService = { getFeatureFlag$: jest.fn(() => of(true)) } as any;
|
||||||
|
billingAccountProfileStateService = {
|
||||||
|
hasPremiumFromAnySource$: jest.fn(() => of(true)),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Patch _setup to call done
|
||||||
|
const setupSpy = jest
|
||||||
|
.spyOn(PhishingDetectionService as any, "_setup")
|
||||||
|
.mockImplementation(async () => {
|
||||||
|
expect(setupSpy).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run the initialization
|
||||||
|
PhishingDetectionService.initialize(
|
||||||
|
accountService,
|
||||||
|
auditService,
|
||||||
|
billingAccountProfileStateService,
|
||||||
|
configService,
|
||||||
|
eventCollectionService,
|
||||||
|
logService,
|
||||||
|
storageService,
|
||||||
|
taskSchedulerService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not enable phishing detection for non-premium account", (done) => {
|
||||||
|
const nonPremiumAccount = { id: "user2" };
|
||||||
|
accountService = { activeAccount$: of(nonPremiumAccount) } as any;
|
||||||
|
configService = { getFeatureFlag$: jest.fn(() => of(true)) } as any;
|
||||||
|
billingAccountProfileStateService = {
|
||||||
|
hasPremiumFromAnySource$: jest.fn(() => of(false)),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Patch _setup to fail if called
|
||||||
|
// [FIXME] This test needs to check if the setupSpy fails or is called
|
||||||
|
// Refactor initialize in PhishingDetectionService to return a Promise or Observable that resolves/completes when initialization is done
|
||||||
|
// So that spy setups can be properly verified after initialization
|
||||||
|
// const setupSpy = jest
|
||||||
|
// .spyOn(PhishingDetectionService as any, "_setup")
|
||||||
|
// .mockImplementation(async () => {
|
||||||
|
// throw new Error("Should not call _setup");
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Patch _cleanup to call done
|
||||||
|
const cleanupSpy = jest
|
||||||
|
.spyOn(PhishingDetectionService as any, "_cleanup")
|
||||||
|
.mockImplementation(() => {
|
||||||
|
expect(cleanupSpy).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run the initialization
|
||||||
|
PhishingDetectionService.initialize(
|
||||||
|
accountService,
|
||||||
|
auditService,
|
||||||
|
billingAccountProfileStateService,
|
||||||
|
configService,
|
||||||
|
eventCollectionService,
|
||||||
|
logService,
|
||||||
|
storageService,
|
||||||
|
taskSchedulerService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("should detect phishing domains", () => {
|
it("should detect phishing domains", () => {
|
||||||
PhishingDetectionService["_knownPhishingDomains"].add("phishing.com");
|
PhishingDetectionService["_knownPhishingDomains"].add("phishing.com");
|
||||||
const url = new URL("https://phishing.com");
|
const url = new URL("https://phishing.com");
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
import { concatMap, delay, Subject, Subscription } from "rxjs";
|
import {
|
||||||
|
combineLatest,
|
||||||
|
concatMap,
|
||||||
|
delay,
|
||||||
|
EMPTY,
|
||||||
|
map,
|
||||||
|
Subject,
|
||||||
|
Subscription,
|
||||||
|
switchMap,
|
||||||
|
} from "rxjs";
|
||||||
|
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
@@ -41,31 +52,44 @@ export class PhishingDetectionService {
|
|||||||
private static _lastUpdateTime: number = 0;
|
private static _lastUpdateTime: number = 0;
|
||||||
|
|
||||||
static initialize(
|
static initialize(
|
||||||
configService: ConfigService,
|
accountService: AccountService,
|
||||||
auditService: AuditService,
|
auditService: AuditService,
|
||||||
|
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||||
|
configService: ConfigService,
|
||||||
|
eventCollectionService: EventCollectionService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
storageService: AbstractStorageService,
|
storageService: AbstractStorageService,
|
||||||
taskSchedulerService: TaskSchedulerService,
|
taskSchedulerService: TaskSchedulerService,
|
||||||
eventCollectionService: EventCollectionService,
|
|
||||||
): void {
|
): void {
|
||||||
this._auditService = auditService;
|
this._auditService = auditService;
|
||||||
this._logService = logService;
|
this._logService = logService;
|
||||||
this._storageService = storageService;
|
this._storageService = storageService;
|
||||||
this._taskSchedulerService = taskSchedulerService;
|
this._taskSchedulerService = taskSchedulerService;
|
||||||
|
|
||||||
logService.info("[PhishingDetectionService] Initialize called");
|
logService.info("[PhishingDetectionService] Initialize called. Checking prerequisites...");
|
||||||
|
|
||||||
configService
|
combineLatest([
|
||||||
.getFeatureFlag$(FeatureFlag.PhishingDetection)
|
accountService.activeAccount$,
|
||||||
|
configService.getFeatureFlag$(FeatureFlag.PhishingDetection),
|
||||||
|
])
|
||||||
.pipe(
|
.pipe(
|
||||||
concatMap(async (enabled) => {
|
switchMap(([account, featureEnabled]) => {
|
||||||
if (!enabled) {
|
if (!account) {
|
||||||
|
logService.info("[PhishingDetectionService] No active account.");
|
||||||
|
this._cleanup();
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
return billingAccountProfileStateService
|
||||||
|
.hasPremiumFromAnySource$(account.id)
|
||||||
|
.pipe(map((hasPremium) => ({ hasPremium, featureEnabled })));
|
||||||
|
}),
|
||||||
|
concatMap(async ({ hasPremium, featureEnabled }) => {
|
||||||
|
if (!hasPremium || !featureEnabled) {
|
||||||
logService.info(
|
logService.info(
|
||||||
"[PhishingDetectionService] Phishing detection feature flag is disabled.",
|
"[PhishingDetectionService] User does not have access to phishing detection service.",
|
||||||
);
|
);
|
||||||
this._cleanup();
|
this._cleanup();
|
||||||
} else {
|
} else {
|
||||||
// Enable phishing detection service
|
|
||||||
logService.info("[PhishingDetectionService] Enabling phishing detection service");
|
logService.info("[PhishingDetectionService] Enabling phishing detection service");
|
||||||
await this._setup();
|
await this._setup();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user