-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add persistent storage for ignored phishing sites #17463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| distinctUntilChanged, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| EMPTY, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| filter, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| firstValueFrom, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| map, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| merge, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| of, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -18,6 +19,7 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { CommandDefinition, MessageListener } from "@bitwarden/messaging"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { GlobalStateProvider, KeyDefinition, PHISHING_DETECTION_DISK } from "@bitwarden/state"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { BrowserApi } from "../../../platform/browser/browser-api"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -44,6 +46,17 @@ export const PHISHING_DETECTION_CANCEL_COMMAND = new CommandDefinition<{ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tabId: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }>("phishing-detection-cancel"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Key definition for storing hostnames that the user has chosen to ignore | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const IGNORED_PHISHING_HOSTNAMES_KEY = new KeyDefinition<string[]>( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PHISHING_DETECTION_DISK, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ignoredPhishingHostnames", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deserializer: (value: string[]) => value ?? [], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+48
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ Missing documentation: Please add a comment explaining:
Suggested change
Comment on lines
+51
to
+57
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion(non-blocking): you can also use the array helper
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class PhishingDetectionService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static _tabUpdated$ = new Subject<PhishingDetectionNavigationEvent>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static _ignoredHostnames = new Set<string>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -56,6 +69,7 @@ export class PhishingDetectionService { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logService: LogService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| phishingDataService: PhishingDataService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| messageListener: MessageListener, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| globalStateProvider: GlobalStateProvider, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (this._didInit) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logService.debug("[PhishingDetectionService] Initialize already called. Aborting."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -64,16 +78,48 @@ export class PhishingDetectionService { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logService.debug("[PhishingDetectionService] Initialize called. Checking prerequisites..."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Load previously ignored hostnames from storage (synchronously to avoid race conditions) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ignoredHostnamesState = globalStateProvider.get(IGNORED_PHISHING_HOSTNAMES_KEY); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| firstValueFrom(ignoredHostnamesState.state$.pipe(map((hostnames) => hostnames ?? []))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .then((initialHostnames) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this._ignoredHostnames = new Set(initialHostnames); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logService.debug( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `[PhishingDetectionService] Loaded ${initialHostnames.length} ignored hostnames from storage`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .catch((error) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logService.error("[PhishingDetectionService] Failed to load ignored hostnames", error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Load previously ignored hostnames from storage (synchronously to avoid race conditions) | |
| const ignoredHostnamesState = globalStateProvider.get(IGNORED_PHISHING_HOSTNAMES_KEY); | |
| firstValueFrom(ignoredHostnamesState.state$.pipe(map((hostnames) => hostnames ?? []))) | |
| .then((initialHostnames) => { | |
| this._ignoredHostnames = new Set(initialHostnames); | |
| logService.debug( | |
| `[PhishingDetectionService] Loaded ${initialHostnames.length} ignored hostnames from storage`, | |
| ); | |
| }) | |
| .catch((error) => { | |
| logService.error("[PhishingDetectionService] Failed to load ignored hostnames", error); | |
| }); | |
| // Load previously ignored hostnames from storage (await to avoid race conditions) | |
| const ignoredHostnamesState = globalStateProvider.get(IGNORED_PHISHING_HOSTNAMES_KEY); | |
| try { | |
| const initialHostnames = await firstValueFrom( | |
| ignoredHostnamesState.state$.pipe(map((hostnames) => hostnames ?? [])) | |
| ); | |
| this._ignoredHostnames = new Set(initialHostnames); | |
| logService.debug( | |
| `[PhishingDetectionService] Loaded ${initialHostnames.length} ignored hostnames from storage`, | |
| ); | |
| } catch (error) { | |
| logService.error("[PhishingDetectionService] Failed to load ignored hostnames", error); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: this is susceptible to race-conditions because awaiting ignoredHostnamesState.update can cause the execution of this function to be pre-empted by e.g. an emission of ignoredHostnamesState.state$ which would cause this._ignoredHostnames to get overwritten before the anonymous function inside update(...) has a chance to fetch the value
Either stash a copy of the Set locally or, better, don't keep a copy of state data outside of state providers and subscribe to it instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
โ Insufficient test coverage (30.43%): This test file only verifies that initialization doesn't throw. Critical functionality is completely untested:
Please add tests covering the main user flows and error scenarios.