mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +00:00
[PM-14571] At Risk Passwords - Badge Update (#15983)
* add exclamation badge for at risk passwords on tab
* add berry icon for the badge when pending tasks are present
* remove integration wtih autofill for pending task badge
* add ability to override Never match strategy
- This is helpful for non-autofill purposes but cipher matching is still needed. This will default to the domain.
* add at-risk-cipher badge updater service
* Revert "add exclamation badge for at risk passwords on tab"
This reverts commit a9643c03d5.
* remove nullish-coalescing
* ensure that all user related observables use the same user.id
---------
Co-authored-by: Shane Melton <smelton@bitwarden.com>
This commit is contained in:
@@ -302,6 +302,7 @@ import { OffscreenStorageService } from "../platform/storage/offscreen-storage.s
|
|||||||
import { SyncServiceListener } from "../platform/sync/sync-service.listener";
|
import { SyncServiceListener } from "../platform/sync/sync-service.listener";
|
||||||
import { BrowserSystemNotificationService } from "../platform/system-notifications/browser-system-notification.service";
|
import { BrowserSystemNotificationService } from "../platform/system-notifications/browser-system-notification.service";
|
||||||
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
|
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
|
||||||
|
import { AtRiskCipherBadgeUpdaterService } from "../vault/services/at-risk-cipher-badge-updater.service";
|
||||||
|
|
||||||
import CommandsBackground from "./commands.background";
|
import CommandsBackground from "./commands.background";
|
||||||
import IdleBackground from "./idle.background";
|
import IdleBackground from "./idle.background";
|
||||||
@@ -433,6 +434,7 @@ export default class MainBackground {
|
|||||||
badgeService: BadgeService;
|
badgeService: BadgeService;
|
||||||
authStatusBadgeUpdaterService: AuthStatusBadgeUpdaterService;
|
authStatusBadgeUpdaterService: AuthStatusBadgeUpdaterService;
|
||||||
autofillBadgeUpdaterService: AutofillBadgeUpdaterService;
|
autofillBadgeUpdaterService: AutofillBadgeUpdaterService;
|
||||||
|
atRiskCipherUpdaterService: AtRiskCipherBadgeUpdaterService;
|
||||||
|
|
||||||
onUpdatedRan: boolean;
|
onUpdatedRan: boolean;
|
||||||
onReplacedRan: boolean;
|
onReplacedRan: boolean;
|
||||||
@@ -1838,6 +1840,14 @@ export default class MainBackground {
|
|||||||
this.logService,
|
this.logService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.atRiskCipherUpdaterService = new AtRiskCipherBadgeUpdaterService(
|
||||||
|
this.badgeService,
|
||||||
|
this.accountService,
|
||||||
|
this.cipherService,
|
||||||
|
this.logService,
|
||||||
|
this.taskService,
|
||||||
|
);
|
||||||
|
|
||||||
this.tabsBackground = new TabsBackground(
|
this.tabsBackground = new TabsBackground(
|
||||||
this,
|
this,
|
||||||
this.notificationBackground,
|
this.notificationBackground,
|
||||||
@@ -1847,6 +1857,7 @@ export default class MainBackground {
|
|||||||
await this.overlayBackground.init();
|
await this.overlayBackground.init();
|
||||||
await this.tabsBackground.init();
|
await this.tabsBackground.init();
|
||||||
await this.autofillBadgeUpdaterService.init();
|
await this.autofillBadgeUpdaterService.init();
|
||||||
|
await this.atRiskCipherUpdaterService.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
generatePassword = async (): Promise<string> => {
|
generatePassword = async (): Promise<string> => {
|
||||||
|
|||||||
BIN
apps/browser/src/images/berry19.png
Normal file
BIN
apps/browser/src/images/berry19.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
apps/browser/src/images/berry38.png
Normal file
BIN
apps/browser/src/images/berry38.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,4 +1,8 @@
|
|||||||
export const BadgeIcon = {
|
export const BadgeIcon = {
|
||||||
|
Berry: {
|
||||||
|
19: "/images/berry19.png",
|
||||||
|
38: "/images/berry38.png",
|
||||||
|
},
|
||||||
LoggedOut: {
|
LoggedOut: {
|
||||||
19: "/images/icon19_gray.png",
|
19: "/images/icon19_gray.png",
|
||||||
38: "/images/icon38_gray.png",
|
38: "/images/icon38_gray.png",
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { SecurityTask, TaskService } from "@bitwarden/common/vault/tasks";
|
||||||
|
import { LogService } from "@bitwarden/logging";
|
||||||
|
import { UserId } from "@bitwarden/user-core";
|
||||||
|
|
||||||
|
import { BadgeService } from "../../platform/badge/badge.service";
|
||||||
|
import { BadgeIcon } from "../../platform/badge/icon";
|
||||||
|
import { BadgeStatePriority } from "../../platform/badge/priority";
|
||||||
|
import { Unset } from "../../platform/badge/state";
|
||||||
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||||
|
|
||||||
|
import { AtRiskCipherBadgeUpdaterService } from "./at-risk-cipher-badge-updater.service";
|
||||||
|
|
||||||
|
describe("AtRiskCipherBadgeUpdaterService", () => {
|
||||||
|
let service: AtRiskCipherBadgeUpdaterService;
|
||||||
|
|
||||||
|
let setState: jest.Mock;
|
||||||
|
let clearState: jest.Mock;
|
||||||
|
let warning: jest.Mock;
|
||||||
|
let getAllDecryptedForUrl: jest.Mock;
|
||||||
|
let getTab: jest.Mock;
|
||||||
|
let addListener: jest.Mock;
|
||||||
|
|
||||||
|
const activeAccount$ = new BehaviorSubject({ id: "test-account-id" });
|
||||||
|
const cipherViews$ = new BehaviorSubject([]);
|
||||||
|
const pendingTasks$ = new BehaviorSubject<SecurityTask[]>([]);
|
||||||
|
const userId = "test-user-id" as UserId;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
setState = jest.fn().mockResolvedValue(undefined);
|
||||||
|
clearState = jest.fn().mockResolvedValue(undefined);
|
||||||
|
warning = jest.fn();
|
||||||
|
getAllDecryptedForUrl = jest.fn().mockResolvedValue([]);
|
||||||
|
getTab = jest.fn();
|
||||||
|
addListener = jest.fn();
|
||||||
|
|
||||||
|
jest.spyOn(BrowserApi, "addListener").mockImplementation(addListener);
|
||||||
|
jest.spyOn(BrowserApi, "getTab").mockImplementation(getTab);
|
||||||
|
|
||||||
|
service = new AtRiskCipherBadgeUpdaterService(
|
||||||
|
{ setState, clearState } as unknown as BadgeService,
|
||||||
|
{ activeAccount$ } as unknown as AccountService,
|
||||||
|
{ cipherViews$, getAllDecryptedForUrl } as unknown as CipherService,
|
||||||
|
{ warning } as unknown as LogService,
|
||||||
|
{ pendingTasks$ } as unknown as TaskService,
|
||||||
|
);
|
||||||
|
|
||||||
|
await service.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears the tab state when there are no ciphers and no pending tasks", async () => {
|
||||||
|
const tab = { id: 1 } as chrome.tabs.Tab;
|
||||||
|
|
||||||
|
await service["setTabState"](tab, userId, []);
|
||||||
|
|
||||||
|
expect(clearState).toHaveBeenCalledWith("at-risk-cipher-badge-1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets state when there are pending tasks for the tab", async () => {
|
||||||
|
const tab = { id: 3, url: "https://bitwarden.com" } as chrome.tabs.Tab;
|
||||||
|
const pendingTasks: SecurityTask[] = [{ id: "task1", cipherId: "cipher1" } as SecurityTask];
|
||||||
|
getAllDecryptedForUrl.mockResolvedValueOnce([{ id: "cipher1" }]);
|
||||||
|
|
||||||
|
await service["setTabState"](tab, userId, pendingTasks);
|
||||||
|
|
||||||
|
expect(setState).toHaveBeenCalledWith(
|
||||||
|
"at-risk-cipher-badge-3",
|
||||||
|
BadgeStatePriority.High,
|
||||||
|
{
|
||||||
|
icon: BadgeIcon.Berry,
|
||||||
|
text: Unset,
|
||||||
|
backgroundColor: Unset,
|
||||||
|
},
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import { combineLatest, map, mergeMap, of, Subject, switchMap } from "rxjs";
|
||||||
|
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { SecurityTask, SecurityTaskType, TaskService } from "@bitwarden/common/vault/tasks";
|
||||||
|
import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities";
|
||||||
|
|
||||||
|
import { BadgeService } from "../../platform/badge/badge.service";
|
||||||
|
import { BadgeIcon } from "../../platform/badge/icon";
|
||||||
|
import { BadgeStatePriority } from "../../platform/badge/priority";
|
||||||
|
import { Unset } from "../../platform/badge/state";
|
||||||
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||||
|
|
||||||
|
const StateName = (tabId: number) => `at-risk-cipher-badge-${tabId}`;
|
||||||
|
|
||||||
|
export class AtRiskCipherBadgeUpdaterService {
|
||||||
|
private tabReplaced$ = new Subject<{ addedTab: chrome.tabs.Tab; removedTabId: number }>();
|
||||||
|
private tabUpdated$ = new Subject<chrome.tabs.Tab>();
|
||||||
|
private tabRemoved$ = new Subject<number>();
|
||||||
|
private tabActivated$ = new Subject<chrome.tabs.Tab>();
|
||||||
|
|
||||||
|
private activeUserData$ = this.accountService.activeAccount$.pipe(
|
||||||
|
filterOutNullish(),
|
||||||
|
switchMap((user) =>
|
||||||
|
combineLatest([
|
||||||
|
of(user.id),
|
||||||
|
this.taskService
|
||||||
|
.pendingTasks$(user.id)
|
||||||
|
.pipe(
|
||||||
|
map((tasks) => tasks.filter((t) => t.type === SecurityTaskType.UpdateAtRiskCredential)),
|
||||||
|
),
|
||||||
|
this.cipherService.cipherViews$(user.id).pipe(filterOutNullish()),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private badgeService: BadgeService,
|
||||||
|
private accountService: AccountService,
|
||||||
|
private cipherService: CipherService,
|
||||||
|
private logService: LogService,
|
||||||
|
private taskService: TaskService,
|
||||||
|
) {
|
||||||
|
combineLatest({
|
||||||
|
replaced: this.tabReplaced$,
|
||||||
|
activeUserData: this.activeUserData$,
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
mergeMap(async ({ replaced, activeUserData: [userId, pendingTasks] }) => {
|
||||||
|
await this.clearTabState(replaced.removedTabId);
|
||||||
|
await this.setTabState(replaced.addedTab, userId, pendingTasks);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe(() => {});
|
||||||
|
|
||||||
|
combineLatest({
|
||||||
|
tab: this.tabActivated$,
|
||||||
|
activeUserData: this.activeUserData$,
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
mergeMap(async ({ tab, activeUserData: [userId, pendingTasks] }) => {
|
||||||
|
await this.setTabState(tab, userId, pendingTasks);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
|
||||||
|
combineLatest({
|
||||||
|
tab: this.tabUpdated$,
|
||||||
|
activeUserData: this.activeUserData$,
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
mergeMap(async ({ tab, activeUserData: [userId, pendingTasks] }) => {
|
||||||
|
await this.setTabState(tab, userId, pendingTasks);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
|
||||||
|
this.tabRemoved$
|
||||||
|
.pipe(
|
||||||
|
mergeMap(async (tabId) => {
|
||||||
|
await this.clearTabState(tabId);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
BrowserApi.addListener(chrome.tabs.onReplaced, async (addedTabId, removedTabId) => {
|
||||||
|
const newTab = await BrowserApi.getTab(addedTabId);
|
||||||
|
if (!newTab) {
|
||||||
|
this.logService.warning(
|
||||||
|
`Tab replaced event received but new tab not found (id: ${addedTabId})`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tabReplaced$.next({
|
||||||
|
removedTabId,
|
||||||
|
addedTab: newTab,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
BrowserApi.addListener(chrome.tabs.onUpdated, (_, changeInfo, tab) => {
|
||||||
|
if (changeInfo.url) {
|
||||||
|
this.tabUpdated$.next(tab);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
BrowserApi.addListener(chrome.tabs.onActivated, async (activeInfo) => {
|
||||||
|
const tab = await BrowserApi.getTab(activeInfo.tabId);
|
||||||
|
if (!tab) {
|
||||||
|
this.logService.warning(
|
||||||
|
`Tab activated event received but tab not found (id: ${activeInfo.tabId})`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tabActivated$.next(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
BrowserApi.addListener(chrome.tabs.onRemoved, (tabId, _) => this.tabRemoved$.next(tabId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the pending task state for the tab */
|
||||||
|
private async setTabState(tab: chrome.tabs.Tab, userId: UserId, pendingTasks: SecurityTask[]) {
|
||||||
|
if (!tab.id) {
|
||||||
|
this.logService.warning("Tab event received but tab id is undefined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphers = tab.url
|
||||||
|
? await this.cipherService.getAllDecryptedForUrl(tab.url, userId, [], undefined, true)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const hasPendingTasksForTab = pendingTasks.some((task) =>
|
||||||
|
ciphers.some((cipher) => cipher.id === task.cipherId && !cipher.isDeleted),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasPendingTasksForTab) {
|
||||||
|
await this.clearTabState(tab.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.badgeService.setState(
|
||||||
|
StateName(tab.id),
|
||||||
|
BadgeStatePriority.High,
|
||||||
|
{
|
||||||
|
icon: BadgeIcon.Berry,
|
||||||
|
// Unset text and background color to use default badge appearance
|
||||||
|
text: Unset,
|
||||||
|
backgroundColor: Unset,
|
||||||
|
},
|
||||||
|
tab.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clears the pending task state from a tab */
|
||||||
|
private async clearTabState(tabId: number) {
|
||||||
|
await this.badgeService.clearState(StateName(tabId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,12 +65,16 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
|
|||||||
userId: UserId,
|
userId: UserId,
|
||||||
includeOtherTypes?: CipherType[],
|
includeOtherTypes?: CipherType[],
|
||||||
defaultMatch?: UriMatchStrategySetting,
|
defaultMatch?: UriMatchStrategySetting,
|
||||||
|
/** When true, will override the match strategy for the cipher if it is Never. */
|
||||||
|
overrideNeverMatchStrategy?: true,
|
||||||
): Promise<CipherView[]>;
|
): Promise<CipherView[]>;
|
||||||
abstract filterCiphersForUrl<C extends CipherViewLike = CipherView>(
|
abstract filterCiphersForUrl<C extends CipherViewLike = CipherView>(
|
||||||
ciphers: C[],
|
ciphers: C[],
|
||||||
url: string,
|
url: string,
|
||||||
includeOtherTypes?: CipherType[],
|
includeOtherTypes?: CipherType[],
|
||||||
defaultMatch?: UriMatchStrategySetting,
|
defaultMatch?: UriMatchStrategySetting,
|
||||||
|
/** When true, will override the match strategy for the cipher if it is Never. */
|
||||||
|
overrideNeverMatchStrategy?: true,
|
||||||
): Promise<C[]>;
|
): Promise<C[]>;
|
||||||
abstract getAllFromApiForOrganization(organizationId: string): Promise<CipherView[]>;
|
abstract getAllFromApiForOrganization(organizationId: string): Promise<CipherView[]>;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -111,6 +111,33 @@ describe("LoginUriView", () => {
|
|||||||
|
|
||||||
expect(actual).toBe(false);
|
expect(actual).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("overrides Never match strategy with Domain when parameter is set", () => {
|
||||||
|
const loginUri = new LoginUriView();
|
||||||
|
loginUri.uri = "https://example.org";
|
||||||
|
loginUri.match = UriMatchStrategy.Never;
|
||||||
|
|
||||||
|
expect(loginUri.matchesUri("https://example.org", new Set(), undefined, true)).toBe(true);
|
||||||
|
expect(loginUri.matchesUri("https://example.org", new Set(), undefined)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("overrides Never match strategy when passed in as default strategy", () => {
|
||||||
|
const loginUriNoMatch = new LoginUriView();
|
||||||
|
loginUriNoMatch.uri = "https://example.org";
|
||||||
|
|
||||||
|
expect(
|
||||||
|
loginUriNoMatch.matchesUri(
|
||||||
|
"https://example.org",
|
||||||
|
new Set(),
|
||||||
|
UriMatchStrategy.Never,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
loginUriNoMatch.matchesUri("https://example.org", new Set(), UriMatchStrategy.Never),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("using host matching", () => {
|
describe("using host matching", () => {
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ export class LoginUriView implements View {
|
|||||||
targetUri: string,
|
targetUri: string,
|
||||||
equivalentDomains: Set<string>,
|
equivalentDomains: Set<string>,
|
||||||
defaultUriMatch: UriMatchStrategySetting = null,
|
defaultUriMatch: UriMatchStrategySetting = null,
|
||||||
|
/** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */
|
||||||
|
overrideNeverMatchStrategy?: true,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!this.uri || !targetUri) {
|
if (!this.uri || !targetUri) {
|
||||||
return false;
|
return false;
|
||||||
@@ -150,6 +152,12 @@ export class LoginUriView implements View {
|
|||||||
let matchType = this.match ?? defaultUriMatch;
|
let matchType = this.match ?? defaultUriMatch;
|
||||||
matchType ??= UriMatchStrategy.Domain;
|
matchType ??= UriMatchStrategy.Domain;
|
||||||
|
|
||||||
|
// Override the match strategy with `Domain` when it is `Never` and `overrideNeverMatchStrategy` is true.
|
||||||
|
// This is useful in scenarios when the cipher should be matched to rely other information other than autofill.
|
||||||
|
if (overrideNeverMatchStrategy && matchType === UriMatchStrategy.Never) {
|
||||||
|
matchType = UriMatchStrategy.Domain;
|
||||||
|
}
|
||||||
|
|
||||||
const targetDomain = Utils.getDomain(targetUri);
|
const targetDomain = Utils.getDomain(targetUri);
|
||||||
const matchDomains = equivalentDomains.add(targetDomain);
|
const matchDomains = equivalentDomains.add(targetDomain);
|
||||||
|
|
||||||
|
|||||||
@@ -82,12 +82,16 @@ export class LoginView extends ItemView {
|
|||||||
targetUri: string,
|
targetUri: string,
|
||||||
equivalentDomains: Set<string>,
|
equivalentDomains: Set<string>,
|
||||||
defaultUriMatch: UriMatchStrategySetting = null,
|
defaultUriMatch: UriMatchStrategySetting = null,
|
||||||
|
/** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */
|
||||||
|
overrideNeverMatchStrategy?: true,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (this.uris == null) {
|
if (this.uris == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.uris.some((uri) => uri.matchesUri(targetUri, equivalentDomains, defaultUriMatch));
|
return this.uris.some((uri) =>
|
||||||
|
uri.matchesUri(targetUri, equivalentDomains, defaultUriMatch, overrideNeverMatchStrategy),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON(obj: Partial<DeepJsonify<LoginView>>): LoginView {
|
static fromJSON(obj: Partial<DeepJsonify<LoginView>>): LoginView {
|
||||||
|
|||||||
@@ -601,6 +601,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
userId: UserId,
|
userId: UserId,
|
||||||
includeOtherTypes?: CipherType[],
|
includeOtherTypes?: CipherType[],
|
||||||
defaultMatch: UriMatchStrategySetting = null,
|
defaultMatch: UriMatchStrategySetting = null,
|
||||||
|
overrideNeverMatchStrategy?: true,
|
||||||
): Promise<CipherView[]> {
|
): Promise<CipherView[]> {
|
||||||
return await firstValueFrom(
|
return await firstValueFrom(
|
||||||
this.cipherViews$(userId).pipe(
|
this.cipherViews$(userId).pipe(
|
||||||
@@ -612,6 +613,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
url,
|
url,
|
||||||
includeOtherTypes,
|
includeOtherTypes,
|
||||||
defaultMatch,
|
defaultMatch,
|
||||||
|
overrideNeverMatchStrategy,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -623,6 +625,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
url: string,
|
url: string,
|
||||||
includeOtherTypes?: CipherType[],
|
includeOtherTypes?: CipherType[],
|
||||||
defaultMatch: UriMatchStrategySetting = null,
|
defaultMatch: UriMatchStrategySetting = null,
|
||||||
|
overrideNeverMatchStrategy?: true,
|
||||||
): Promise<C[]> {
|
): Promise<C[]> {
|
||||||
if (url == null && includeOtherTypes == null) {
|
if (url == null && includeOtherTypes == null) {
|
||||||
return [];
|
return [];
|
||||||
@@ -647,7 +650,13 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cipherIsLogin) {
|
if (cipherIsLogin) {
|
||||||
return CipherViewLikeUtils.matchesUri(cipher, url, equivalentDomains, defaultMatch);
|
return CipherViewLikeUtils.matchesUri(
|
||||||
|
cipher,
|
||||||
|
url,
|
||||||
|
equivalentDomains,
|
||||||
|
defaultMatch,
|
||||||
|
overrideNeverMatchStrategy,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -174,13 +174,19 @@ export class CipherViewLikeUtils {
|
|||||||
targetUri: string,
|
targetUri: string,
|
||||||
equivalentDomains: Set<string>,
|
equivalentDomains: Set<string>,
|
||||||
defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain,
|
defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain,
|
||||||
|
overrideNeverMatchStrategy?: true,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (CipherViewLikeUtils.getType(cipher) !== CipherType.Login) {
|
if (CipherViewLikeUtils.getType(cipher) !== CipherType.Login) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isCipherListView(cipher)) {
|
if (!this.isCipherListView(cipher)) {
|
||||||
return cipher.login.matchesUri(targetUri, equivalentDomains, defaultUriMatch);
|
return cipher.login.matchesUri(
|
||||||
|
targetUri,
|
||||||
|
equivalentDomains,
|
||||||
|
defaultUriMatch,
|
||||||
|
overrideNeverMatchStrategy,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const login = this.getLogin(cipher);
|
const login = this.getLogin(cipher);
|
||||||
@@ -198,7 +204,7 @@ export class CipherViewLikeUtils {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return loginUriViews.some((uriView) =>
|
return loginUriViews.some((uriView) =>
|
||||||
uriView.matchesUri(targetUri, equivalentDomains, defaultUriMatch),
|
uriView.matchesUri(targetUri, equivalentDomains, defaultUriMatch, overrideNeverMatchStrategy),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user