mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 22:33:35 +00:00
[PM-1397] Display a warning when a user attempts to auto-fill an iframe (#4994)
* add settingsService.getEquivalentDomains * check that an iframe URL matches cipher.login.uris before autofilling * disable autofill on page load if it doesn't match * show a warning to the user on regular autofill if it doesn't match --------- Co-authored-by: Todd Martin <106564991+trmartin4@users.noreply.github.com> Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
This commit is contained in:
@@ -15,6 +15,10 @@ import {
|
|||||||
logServiceFactory,
|
logServiceFactory,
|
||||||
LogServiceInitOptions,
|
LogServiceInitOptions,
|
||||||
} from "../../../background/service_factories/log-service.factory";
|
} from "../../../background/service_factories/log-service.factory";
|
||||||
|
import {
|
||||||
|
settingsServiceFactory,
|
||||||
|
SettingsServiceInitOptions,
|
||||||
|
} from "../../../background/service_factories/settings-service.factory";
|
||||||
import {
|
import {
|
||||||
stateServiceFactory,
|
stateServiceFactory,
|
||||||
StateServiceInitOptions,
|
StateServiceInitOptions,
|
||||||
@@ -33,7 +37,8 @@ export type AutoFillServiceInitOptions = AutoFillServiceOptions &
|
|||||||
StateServiceInitOptions &
|
StateServiceInitOptions &
|
||||||
TotpServiceInitOptions &
|
TotpServiceInitOptions &
|
||||||
EventCollectionServiceInitOptions &
|
EventCollectionServiceInitOptions &
|
||||||
LogServiceInitOptions;
|
LogServiceInitOptions &
|
||||||
|
SettingsServiceInitOptions;
|
||||||
|
|
||||||
export function autofillServiceFactory(
|
export function autofillServiceFactory(
|
||||||
cache: { autofillService?: AbstractAutoFillService } & CachedServices,
|
cache: { autofillService?: AbstractAutoFillService } & CachedServices,
|
||||||
@@ -49,7 +54,8 @@ export function autofillServiceFactory(
|
|||||||
await stateServiceFactory(cache, opts),
|
await stateServiceFactory(cache, opts),
|
||||||
await totpServiceFactory(cache, opts),
|
await totpServiceFactory(cache, opts),
|
||||||
await eventCollectionServiceFactory(cache, opts),
|
await eventCollectionServiceFactory(cache, opts),
|
||||||
await logServiceFactory(cache, opts)
|
await logServiceFactory(cache, opts),
|
||||||
|
await settingsServiceFactory(cache, opts)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -524,7 +524,6 @@
|
|||||||
title: theDoc.title,
|
title: theDoc.title,
|
||||||
url: theView.location.href,
|
url: theView.location.href,
|
||||||
documentUrl: theDoc.location.href,
|
documentUrl: theDoc.location.href,
|
||||||
tabUrl: theView.location.href,
|
|
||||||
forms: function (forms) {
|
forms: function (forms) {
|
||||||
var formObj = {};
|
var formObj = {};
|
||||||
forms.forEach(function (f) {
|
forms.forEach(function (f) {
|
||||||
@@ -912,6 +911,19 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fillScript.untrustedIframe) {
|
||||||
|
// confirm() is blocked by sandboxed iframes, but we don't want to fill sandboxed iframes anyway.
|
||||||
|
// If this occurs, confirm() returns false without displaying the dialog box, and autofill will be aborted.
|
||||||
|
// The browser may print a message to the console, but this is not a standard error that we can handle.
|
||||||
|
var acceptedIframeWarning = confirm("The form is hosted by a different domain than the URI " +
|
||||||
|
"of your saved login. Choose OK to auto-fill anyway, or Cancel to stop. " +
|
||||||
|
"To prevent this warning in the future, save this URI, " +
|
||||||
|
window.location.hostname + ", to your login.");
|
||||||
|
if (!acceptedIframeWarning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
doOperation = function (ops, theOperation) {
|
doOperation = function (ops, theOperation) {
|
||||||
var op = ops[0];
|
var op = ops[0];
|
||||||
if (void 0 === op) {
|
if (void 0 === op) {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default class AutofillPageDetails {
|
|||||||
title: string;
|
title: string;
|
||||||
url: string;
|
url: string;
|
||||||
documentUrl: string;
|
documentUrl: string;
|
||||||
tabUrl: string;
|
|
||||||
/**
|
/**
|
||||||
* A collection of all of the forms in the page DOM, keyed by their `opid`
|
* A collection of all of the forms in the page DOM, keyed by their `opid`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export default class AutofillScript {
|
|||||||
metadata: any = {};
|
metadata: any = {};
|
||||||
autosubmit: any = null;
|
autosubmit: any = null;
|
||||||
savedUrls: string[];
|
savedUrls: string[];
|
||||||
|
untrustedIframe: boolean;
|
||||||
|
|
||||||
constructor(documentUUID: string) {
|
constructor(documentUUID: string) {
|
||||||
this.documentUUID = documentUUID;
|
this.documentUUID = documentUUID;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
import AutofillField from "../../models/autofill-field";
|
import AutofillField from "../../models/autofill-field";
|
||||||
@@ -20,6 +21,7 @@ export interface AutoFillOptions {
|
|||||||
onlyVisibleFields?: boolean;
|
onlyVisibleFields?: boolean;
|
||||||
fillNewPassword?: boolean;
|
fillNewPassword?: boolean;
|
||||||
skipLastUsed?: boolean;
|
skipLastUsed?: boolean;
|
||||||
|
allowUntrustedIframe?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
@@ -38,4 +40,9 @@ export abstract class AutofillService {
|
|||||||
fromCommand: boolean
|
fromCommand: boolean
|
||||||
) => Promise<string>;
|
) => Promise<string>;
|
||||||
doAutoFillActiveTab: (pageDetails: PageDetail[], fromCommand: boolean) => Promise<string>;
|
doAutoFillActiveTab: (pageDetails: PageDetail[], fromCommand: boolean) => Promise<string>;
|
||||||
|
iframeUrlMatches: (
|
||||||
|
pageUrl: string,
|
||||||
|
loginItem: CipherView,
|
||||||
|
defaultUriMatch: UriMatchType
|
||||||
|
) => boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||||
|
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
||||||
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
|
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
|
||||||
import { EventType } from "@bitwarden/common/enums/eventType";
|
import { EventType } from "@bitwarden/common/enums/eventType";
|
||||||
import { FieldType } from "@bitwarden/common/enums/fieldType";
|
import { FieldType } from "@bitwarden/common/enums/fieldType";
|
||||||
import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";
|
import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";
|
||||||
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
|
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
|
||||||
|
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
|
||||||
|
|
||||||
import { BrowserApi } from "../../browser/browserApi";
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
import { BrowserStateService } from "../../services/abstractions/browser-state.service";
|
import { BrowserStateService } from "../../services/abstractions/browser-state.service";
|
||||||
@@ -34,6 +37,8 @@ export interface GenerateFillScriptOptions {
|
|||||||
onlyVisibleFields: boolean;
|
onlyVisibleFields: boolean;
|
||||||
fillNewPassword: boolean;
|
fillNewPassword: boolean;
|
||||||
cipher: CipherView;
|
cipher: CipherView;
|
||||||
|
tabUrl: string;
|
||||||
|
defaultUriMatch: UriMatchType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AutofillService implements AutofillServiceInterface {
|
export default class AutofillService implements AutofillServiceInterface {
|
||||||
@@ -42,7 +47,8 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
private stateService: BrowserStateService,
|
private stateService: BrowserStateService,
|
||||||
private totpService: TotpService,
|
private totpService: TotpService,
|
||||||
private eventCollectionService: EventCollectionService,
|
private eventCollectionService: EventCollectionService,
|
||||||
private logService: LogService
|
private logService: LogService,
|
||||||
|
private settingsService: SettingsService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
getFormsWithPasswordFields(pageDetails: AutofillPageDetails): FormData[] {
|
getFormsWithPasswordFields(pageDetails: AutofillPageDetails): FormData[] {
|
||||||
@@ -84,7 +90,12 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
return formData;
|
return formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doAutoFill(options: AutoFillOptions) {
|
/**
|
||||||
|
* Autofills a given tab with a given login item
|
||||||
|
* @param options Instructions about the autofill operation, including tab and login item
|
||||||
|
* @returns The TOTP code of the successfully autofilled login, if any
|
||||||
|
*/
|
||||||
|
async doAutoFill(options: AutoFillOptions): Promise<string> {
|
||||||
const tab = options.tab;
|
const tab = options.tab;
|
||||||
if (!tab || !options.cipher || !options.pageDetails || !options.pageDetails.length) {
|
if (!tab || !options.cipher || !options.pageDetails || !options.pageDetails.length) {
|
||||||
throw new Error("Nothing to auto-fill.");
|
throw new Error("Nothing to auto-fill.");
|
||||||
@@ -93,6 +104,8 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
let totpPromise: Promise<string> = null;
|
let totpPromise: Promise<string> = null;
|
||||||
|
|
||||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||||
|
const defaultUriMatch = (await this.stateService.getDefaultUriMatch()) ?? UriMatchType.Domain;
|
||||||
|
|
||||||
let didAutofill = false;
|
let didAutofill = false;
|
||||||
options.pageDetails.forEach((pd) => {
|
options.pageDetails.forEach((pd) => {
|
||||||
// make sure we're still on correct tab
|
// make sure we're still on correct tab
|
||||||
@@ -106,12 +119,23 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
onlyVisibleFields: options.onlyVisibleFields || false,
|
onlyVisibleFields: options.onlyVisibleFields || false,
|
||||||
fillNewPassword: options.fillNewPassword || false,
|
fillNewPassword: options.fillNewPassword || false,
|
||||||
cipher: options.cipher,
|
cipher: options.cipher,
|
||||||
|
tabUrl: tab.url,
|
||||||
|
defaultUriMatch: defaultUriMatch,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!fillScript || !fillScript.script || !fillScript.script.length) {
|
if (!fillScript || !fillScript.script || !fillScript.script.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
fillScript.untrustedIframe &&
|
||||||
|
options.allowUntrustedIframe != undefined &&
|
||||||
|
!options.allowUntrustedIframe
|
||||||
|
) {
|
||||||
|
this.logService.info("Auto-fill on page load was blocked due to an untrusted iframe.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Add a small delay between operations
|
// Add a small delay between operations
|
||||||
fillScript.properties.delay_between_operations = 20;
|
fillScript.properties.delay_between_operations = 20;
|
||||||
|
|
||||||
@@ -159,7 +183,18 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async doAutoFillOnTab(pageDetails: PageDetail[], tab: chrome.tabs.Tab, fromCommand: boolean) {
|
/**
|
||||||
|
* Autofills the specified tab with the next login item from the cache
|
||||||
|
* @param pageDetails The data scraped from the page
|
||||||
|
* @param tab The tab to be autofilled
|
||||||
|
* @param fromCommand Whether the autofill is triggered by a keyboard shortcut (`true`) or autofill on page load (`false`)
|
||||||
|
* @returns The TOTP code of the successfully autofilled login, if any
|
||||||
|
*/
|
||||||
|
async doAutoFillOnTab(
|
||||||
|
pageDetails: PageDetail[],
|
||||||
|
tab: chrome.tabs.Tab,
|
||||||
|
fromCommand: boolean
|
||||||
|
): Promise<string> {
|
||||||
let cipher: CipherView;
|
let cipher: CipherView;
|
||||||
if (fromCommand) {
|
if (fromCommand) {
|
||||||
cipher = await this.cipherService.getNextCipherForUrl(tab.url);
|
cipher = await this.cipherService.getNextCipherForUrl(tab.url);
|
||||||
@@ -188,6 +223,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
onlyEmptyFields: !fromCommand,
|
onlyEmptyFields: !fromCommand,
|
||||||
onlyVisibleFields: !fromCommand,
|
onlyVisibleFields: !fromCommand,
|
||||||
fillNewPassword: fromCommand,
|
fillNewPassword: fromCommand,
|
||||||
|
allowUntrustedIframe: fromCommand,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update last used index as autofill has succeed
|
// Update last used index as autofill has succeed
|
||||||
@@ -198,7 +234,13 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
return totpCode;
|
return totpCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doAutoFillActiveTab(pageDetails: PageDetail[], fromCommand: boolean) {
|
/**
|
||||||
|
* Autofills the active tab with the next login item from the cache
|
||||||
|
* @param pageDetails The data scraped from the page
|
||||||
|
* @param fromCommand Whether the autofill is triggered by a keyboard shortcut (`true`) or autofill on page load (`false`)
|
||||||
|
* @returns The TOTP code of the successfully autofilled login, if any
|
||||||
|
*/
|
||||||
|
async doAutoFillActiveTab(pageDetails: PageDetail[], fromCommand: boolean): Promise<string> {
|
||||||
const tab = await this.getActiveTab();
|
const tab = await this.getActiveTab();
|
||||||
if (!tab || !tab.url) {
|
if (!tab || !tab.url) {
|
||||||
return;
|
return;
|
||||||
@@ -309,6 +351,10 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
fillScript.savedUrls =
|
fillScript.savedUrls =
|
||||||
login?.uris?.filter((u) => u.match != UriMatchType.Never).map((u) => u.uri) ?? [];
|
login?.uris?.filter((u) => u.match != UriMatchType.Never).map((u) => u.uri) ?? [];
|
||||||
|
|
||||||
|
const inIframe = pageDetails.url !== options.tabUrl;
|
||||||
|
fillScript.untrustedIframe =
|
||||||
|
inIframe && !this.iframeUrlMatches(pageDetails.url, options.cipher, options.defaultUriMatch);
|
||||||
|
|
||||||
if (!login.password || login.password === "") {
|
if (!login.password || login.password === "") {
|
||||||
// No password for this login. Maybe they just wanted to auto-fill some custom fields?
|
// No password for this login. Maybe they just wanted to auto-fill some custom fields?
|
||||||
fillScript = AutofillService.setFillScriptForFocus(filledFields, fillScript);
|
fillScript = AutofillService.setFillScriptForFocus(filledFields, fillScript);
|
||||||
@@ -742,6 +788,84 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
return fillScript;
|
return fillScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether to warn the user about filling an iframe
|
||||||
|
* @param pageUrl The url of the page/iframe, usually from AutofillPageDetails
|
||||||
|
* @param tabUrl The url of the tab, usually from the message sender (should not come from a content script because
|
||||||
|
* that is likely to be incorrect in the case of iframes)
|
||||||
|
* @param loginItem The cipher to be filled
|
||||||
|
* @returns `true` if the iframe is untrusted and the warning should be shown, `false` otherwise
|
||||||
|
*/
|
||||||
|
iframeUrlMatches(pageUrl: string, loginItem: CipherView, defaultUriMatch: UriMatchType): boolean {
|
||||||
|
// Check the pageUrl against cipher URIs using the configured match detection.
|
||||||
|
// If we are in this function at all, it is assumed that the tabUrl already matches a URL for `loginItem`,
|
||||||
|
// need to verify the pageUrl also matches one of the saved URIs using the match detection selected.
|
||||||
|
const uriMatched = loginItem.login.uris?.some((uri) =>
|
||||||
|
this.uriMatches(uri, pageUrl, defaultUriMatch)
|
||||||
|
);
|
||||||
|
|
||||||
|
return uriMatched;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO should this be put in a common place (Utils maybe?) to be used both here and by CipherService?
|
||||||
|
private uriMatches(uri: LoginUriView, url: string, defaultUriMatch: UriMatchType): boolean {
|
||||||
|
const matchType = uri.match ?? defaultUriMatch;
|
||||||
|
|
||||||
|
const matchDomains = [Utils.getDomain(url)];
|
||||||
|
const equivalentDomains = this.settingsService.getEquivalentDomains(url);
|
||||||
|
if (equivalentDomains != null) {
|
||||||
|
matchDomains.push(...equivalentDomains);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (matchType) {
|
||||||
|
case UriMatchType.Domain:
|
||||||
|
if (url != null && uri.domain != null && matchDomains.includes(uri.domain)) {
|
||||||
|
if (Utils.DomainMatchBlacklist.has(uri.domain)) {
|
||||||
|
const domainUrlHost = Utils.getHost(url);
|
||||||
|
if (!Utils.DomainMatchBlacklist.get(uri.domain).has(domainUrlHost)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UriMatchType.Host: {
|
||||||
|
const urlHost = Utils.getHost(url);
|
||||||
|
if (urlHost != null && urlHost === Utils.getHost(uri.uri)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UriMatchType.Exact:
|
||||||
|
if (url === uri.uri) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UriMatchType.StartsWith:
|
||||||
|
if (url.startsWith(uri.uri)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UriMatchType.RegularExpression:
|
||||||
|
try {
|
||||||
|
const regex = new RegExp(uri.uri, "i");
|
||||||
|
if (regex.test(url)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UriMatchType.Never:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private fieldAttrsContain(field: AutofillField, containsVal: string) {
|
private fieldAttrsContain(field: AutofillField, containsVal: string) {
|
||||||
if (!field) {
|
if (!field) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -443,7 +443,8 @@ export default class MainBackground {
|
|||||||
this.stateService,
|
this.stateService,
|
||||||
this.totpService,
|
this.totpService,
|
||||||
this.eventCollectionService,
|
this.eventCollectionService,
|
||||||
this.logService
|
this.logService,
|
||||||
|
this.settingsService
|
||||||
);
|
);
|
||||||
this.containerService = new ContainerService(this.cryptoService, this.encryptService);
|
this.containerService = new ContainerService(this.cryptoService, this.encryptService);
|
||||||
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
|
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ export abstract class SettingsService {
|
|||||||
settings$: Observable<AccountSettingsSettings>;
|
settings$: Observable<AccountSettingsSettings>;
|
||||||
|
|
||||||
setEquivalentDomains: (equivalentDomains: string[][]) => Promise<any>;
|
setEquivalentDomains: (equivalentDomains: string[][]) => Promise<any>;
|
||||||
|
getEquivalentDomains: (url: string) => string[];
|
||||||
clear: (userId?: string) => Promise<void>;
|
clear: (userId?: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
setEntityType: (value: string, options?: StorageOptions) => Promise<void>;
|
setEntityType: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
getEnvironmentUrls: (options?: StorageOptions) => Promise<EnvironmentUrls>;
|
getEnvironmentUrls: (options?: StorageOptions) => Promise<EnvironmentUrls>;
|
||||||
setEnvironmentUrls: (value: EnvironmentUrls, options?: StorageOptions) => Promise<void>;
|
setEnvironmentUrls: (value: EnvironmentUrls, options?: StorageOptions) => Promise<void>;
|
||||||
getEquivalentDomains: (options?: StorageOptions) => Promise<any>;
|
getEquivalentDomains: (options?: StorageOptions) => Promise<string[][]>;
|
||||||
setEquivalentDomains: (value: string, options?: StorageOptions) => Promise<void>;
|
setEquivalentDomains: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
getEventCollection: (options?: StorageOptions) => Promise<EventData[]>;
|
getEventCollection: (options?: StorageOptions) => Promise<EventData[]>;
|
||||||
setEventCollection: (value: EventData[], options?: StorageOptions) => Promise<void>;
|
setEventCollection: (value: EventData[], options?: StorageOptions) => Promise<void>;
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ export class Utils {
|
|||||||
/(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])/g;
|
/(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])/g;
|
||||||
static readonly validHosts: string[] = ["localhost"];
|
static readonly validHosts: string[] = ["localhost"];
|
||||||
static readonly minimumPasswordLength = 12;
|
static readonly minimumPasswordLength = 12;
|
||||||
|
static readonly DomainMatchBlacklist = new Map<string, Set<string>>([
|
||||||
|
["google.com", new Set(["script.google.com"])],
|
||||||
|
]);
|
||||||
|
|
||||||
static init() {
|
static init() {
|
||||||
if (Utils.inited) {
|
if (Utils.inited) {
|
||||||
|
|||||||
@@ -40,6 +40,27 @@ export class SettingsService implements SettingsServiceAbstraction {
|
|||||||
await this.stateService.setSettings(settings);
|
await this.stateService.setSettings(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEquivalentDomains(url: string): string[] {
|
||||||
|
const domain = Utils.getDomain(url);
|
||||||
|
if (domain == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = this._settings.getValue();
|
||||||
|
|
||||||
|
let result: string[] = [];
|
||||||
|
|
||||||
|
if (settings?.equivalentDomains != null) {
|
||||||
|
settings.equivalentDomains
|
||||||
|
.filter((ed) => ed.length > 0 && ed.includes(domain))
|
||||||
|
.forEach((ed) => {
|
||||||
|
result = result.concat(ed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
async clear(userId?: string): Promise<void> {
|
async clear(userId?: string): Promise<void> {
|
||||||
if (userId == null || userId == (await this.stateService.getUserId())) {
|
if (userId == null || userId == (await this.stateService.getUserId())) {
|
||||||
this._settings.next({});
|
this._settings.next({});
|
||||||
|
|||||||
@@ -1585,7 +1585,7 @@ export class StateService<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEquivalentDomains(options?: StorageOptions): Promise<any> {
|
async getEquivalentDomains(options?: StorageOptions): Promise<string[][]> {
|
||||||
return (
|
return (
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
)?.settings?.equivalentDomains;
|
)?.settings?.equivalentDomains;
|
||||||
|
|||||||
@@ -49,10 +49,6 @@ import { CipherView } from "../models/view/cipher.view";
|
|||||||
import { FieldView } from "../models/view/field.view";
|
import { FieldView } from "../models/view/field.view";
|
||||||
import { PasswordHistoryView } from "../models/view/password-history.view";
|
import { PasswordHistoryView } from "../models/view/password-history.view";
|
||||||
|
|
||||||
const DomainMatchBlacklist = new Map<string, Set<string>>([
|
|
||||||
["google.com", new Set(["script.google.com"])],
|
|
||||||
]);
|
|
||||||
|
|
||||||
export class CipherService implements CipherServiceAbstraction {
|
export class CipherService implements CipherServiceAbstraction {
|
||||||
private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache(
|
private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache(
|
||||||
this.sortCiphersByLastUsed
|
this.sortCiphersByLastUsed
|
||||||
@@ -456,9 +452,9 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
switch (match) {
|
switch (match) {
|
||||||
case UriMatchType.Domain:
|
case UriMatchType.Domain:
|
||||||
if (domain != null && u.domain != null && matchingDomains.indexOf(u.domain) > -1) {
|
if (domain != null && u.domain != null && matchingDomains.indexOf(u.domain) > -1) {
|
||||||
if (DomainMatchBlacklist.has(u.domain)) {
|
if (Utils.DomainMatchBlacklist.has(u.domain)) {
|
||||||
const domainUrlHost = Utils.getHost(url);
|
const domainUrlHost = Utils.getHost(url);
|
||||||
if (!DomainMatchBlacklist.get(u.domain).has(domainUrlHost)) {
|
if (!Utils.DomainMatchBlacklist.get(u.domain).has(domainUrlHost)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user