diff --git a/apps/browser/src/autofill/services/abstractions/autofill.service.ts b/apps/browser/src/autofill/services/abstractions/autofill.service.ts index 07caa06d79..b3ad26f0a0 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill.service.ts @@ -1,4 +1,3 @@ -import { UriMatchType } from "@bitwarden/common/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import AutofillField from "../../models/autofill-field"; @@ -40,9 +39,4 @@ export abstract class AutofillService { fromCommand: boolean ) => Promise; doAutoFillActiveTab: (pageDetails: PageDetail[], fromCommand: boolean) => Promise; - iframeUrlMatches: ( - pageUrl: string, - loginItem: CipherView, - defaultUriMatch: UriMatchType - ) => boolean; } diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index a90b134654..29f9421766 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -3,13 +3,11 @@ 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 { EventType, FieldType, UriMatchType } from "@bitwarden/common/enums"; -import { Utils } from "@bitwarden/common/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.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 { BrowserStateService } from "../../services/abstractions/browser-state.service"; @@ -349,9 +347,7 @@ export default class AutofillService implements AutofillServiceInterface { fillScript.savedUrls = 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); + fillScript.untrustedIframe = this.inUntrustedIframe(pageDetails.url, options); if (!login.password || login.password === "") { // No password for this login. Maybe they just wanted to auto-fill some custom fields? @@ -787,81 +783,28 @@ export default class AutofillService implements AutofillServiceInterface { } /** - * Determines whether to warn the user about filling an iframe + * Determines whether an iframe is potentially dangerous ("untrusted") to autofill * @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 + * @param options The GenerateFillScript options + * @returns `true` if the iframe is untrusted and a warning should be shown, `false` otherwise */ - iframeUrlMatches(pageUrl: string, loginItem: CipherView, defaultUriMatch: UriMatchType): boolean { + private inUntrustedIframe(pageUrl: string, options: GenerateFillScriptOptions): boolean { + // If the pageUrl (from the content script) matches the tabUrl (from the sender tab), we are not in an iframe + // This also avoids a false positive if no URI is saved and the user triggers auto-fill anyway + if (pageUrl === options.tabUrl) { + return false; + } + // 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) + // Remember: if we are in this function, the tabUrl already matches a saved URI for the login. + // We need to verify the pageUrl also matches. + const equivalentDomains = this.settingsService.getEquivalentDomains(pageUrl); + const matchesUri = options.cipher.login.matchesUri( + pageUrl, + equivalentDomains, + options.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; + return !matchesUri; } private fieldAttrsContain(field: AutofillField, containsVal: string) { diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 3ddad63fc0..7b1e3f9505 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -309,7 +309,6 @@ export default class MainBackground { this.apiService, this.i18nService, () => this.searchService, - this.logService, this.stateService, this.encryptService, this.cipherFileUploadService diff --git a/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts b/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts index fb677ff115..42e2ab879c 100644 --- a/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts +++ b/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts @@ -27,10 +27,6 @@ import { i18nServiceFactory, I18nServiceInitOptions, } from "../../../background/service_factories/i18n-service.factory"; -import { - logServiceFactory, - LogServiceInitOptions, -} from "../../../background/service_factories/log-service.factory"; import { SettingsServiceInitOptions, settingsServiceFactory, @@ -52,7 +48,6 @@ export type CipherServiceInitOptions = CipherServiceFactoryOptions & ApiServiceInitOptions & CipherFileUploadServiceInitOptions & I18nServiceInitOptions & - LogServiceInitOptions & StateServiceInitOptions & EncryptServiceInitOptions; @@ -73,7 +68,6 @@ export function cipherServiceFactory( opts.cipherServiceOptions?.searchServiceFactory === undefined ? () => cache.searchService as SearchService : opts.cipherServiceOptions.searchServiceFactory, - await logServiceFactory(cache, opts), await stateServiceFactory(cache, opts), await encryptServiceFactory(cache, opts), await cipherFileUploadServiceFactory(cache, opts) diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 5f5fa495f0..7245c33f3d 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -245,7 +245,6 @@ export class Main { this.apiService, this.i18nService, null, - this.logService, this.stateService, this.encryptService, this.cipherFileUploadService diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 5fa205d3ac..e2f2e50275 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -252,7 +252,6 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; apiService: ApiServiceAbstraction, i18nService: I18nServiceAbstraction, injector: Injector, - logService: LogService, stateService: StateServiceAbstraction, encryptService: EncryptService, fileUploadService: CipherFileUploadServiceAbstraction @@ -263,7 +262,6 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; apiService, i18nService, () => injector.get(SearchServiceAbstraction), - logService, stateService, encryptService, fileUploadService @@ -274,7 +272,6 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; ApiServiceAbstraction, I18nServiceAbstraction, Injector, // TODO: Get rid of this circular dependency! - LogService, StateServiceAbstraction, EncryptService, CipherFileUploadServiceAbstraction, diff --git a/libs/common/spec/misc/utils.spec.ts b/libs/common/spec/misc/utils.spec.ts index 84ce28f4b2..9c4c817789 100644 --- a/libs/common/spec/misc/utils.spec.ts +++ b/libs/common/spec/misc/utils.spec.ts @@ -346,4 +346,14 @@ describe("Utils Service", () => { ).toBe("api/sends/access/sendkey"); }); }); + + describe("getUrl", () => { + it("assumes a http protocol if no protocol is specified", () => { + const urlString = "www.exampleapp.com.au:4000"; + + const actual = Utils.getUrl(urlString); + + expect(actual.protocol).toBe("http:"); + }); + }); }); diff --git a/libs/common/spec/services/settings.service.spec.ts b/libs/common/spec/services/settings.service.spec.ts index 59c4715dda..7ef088798e 100644 --- a/libs/common/spec/services/settings.service.spec.ts +++ b/libs/common/spec/services/settings.service.spec.ts @@ -17,6 +17,12 @@ describe("SettingsService", () => { let activeAccount: BehaviorSubject; let activeAccountUnlocked: BehaviorSubject; + const mockEquivalentDomains = [ + ["example.com", "exampleapp.com", "example.co.uk", "ejemplo.es"], + ["bitwarden.com", "bitwarden.co.uk", "sm-bitwarden.com"], + ["example.co.uk", "exampleapp.co.uk"], + ]; + beforeEach(() => { cryptoService = Substitute.for(); encryptService = Substitute.for(); @@ -24,7 +30,7 @@ describe("SettingsService", () => { activeAccount = new BehaviorSubject("123"); activeAccountUnlocked = new BehaviorSubject(true); - stateService.getSettings().resolves({ equivalentDomains: [["test"], ["domains"]] }); + stateService.getSettings().resolves({ equivalentDomains: mockEquivalentDomains }); stateService.activeAccount$.returns(activeAccount); stateService.activeAccountUnlocked$.returns(activeAccountUnlocked); (window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService); @@ -38,12 +44,21 @@ describe("SettingsService", () => { }); describe("getEquivalentDomains", () => { - it("returns value", async () => { - const result = await firstValueFrom(settingsService.settings$); + it("returns all equivalent domains for a URL", async () => { + const actual = settingsService.getEquivalentDomains("example.co.uk"); + const expected = new Set([ + "example.com", + "exampleapp.com", + "example.co.uk", + "ejemplo.es", + "exampleapp.co.uk", + ]); + expect(actual).toEqual(expected); + }); - expect(result).toEqual({ - equivalentDomains: [["test"], ["domains"]], - }); + it("returns an empty set if there are no equivalent domains", () => { + const actual = settingsService.getEquivalentDomains("asdf"); + expect(actual).toEqual(new Set()); }); }); diff --git a/libs/common/src/abstractions/settings.service.ts b/libs/common/src/abstractions/settings.service.ts index 3283702279..0432bfd434 100644 --- a/libs/common/src/abstractions/settings.service.ts +++ b/libs/common/src/abstractions/settings.service.ts @@ -6,6 +6,6 @@ export abstract class SettingsService { settings$: Observable; setEquivalentDomains: (equivalentDomains: string[][]) => Promise; - getEquivalentDomains: (url: string) => string[]; + getEquivalentDomains: (url: string) => Set; clear: (userId?: string) => Promise; } diff --git a/libs/common/src/misc/utils.ts b/libs/common/src/misc/utils.ts index fd7a5e337b..5fdcaf1f71 100644 --- a/libs/common/src/misc/utils.ts +++ b/libs/common/src/misc/utils.ts @@ -379,15 +379,7 @@ export class Utils { uriString = uriString.trim(); - let url = Utils.getUrlObject(uriString); - if (url == null) { - const hasHttpProtocol = - uriString.indexOf("http://") === 0 || uriString.indexOf("https://") === 0; - if (!hasHttpProtocol && uriString.indexOf(".") > -1) { - url = Utils.getUrlObject("http://" + uriString); - } - } - return url; + return Utils.getUrlObject(uriString); } static camelToPascalCase(s: string) { @@ -542,22 +534,21 @@ export class Utils { } private static getUrlObject(uriString: string): URL { + // All the methods below require a protocol to properly parse a URL string + // Assume http if no other protocol is present + const hasProtocol = uriString.indexOf("://") > -1; + if (!hasProtocol && uriString.indexOf(".") > -1) { + uriString = "http://" + uriString; + } else if (!hasProtocol) { + return null; + } + try { if (nodeURL != null) { return new nodeURL.URL(uriString); - } else if (typeof URL === "function") { - return new URL(uriString); - } else if (typeof window !== "undefined") { - const hasProtocol = uriString.indexOf("://") > -1; - if (!hasProtocol && uriString.indexOf(".") > -1) { - uriString = "http://" + uriString; - } else if (!hasProtocol) { - return null; - } - const anchor = window.document.createElement("a"); - anchor.href = uriString; - return anchor as any; } + + return new URL(uriString); } catch (e) { // Ignore error } diff --git a/libs/common/src/services/settings.service.ts b/libs/common/src/services/settings.service.ts index b1ed84124a..026746cbad 100644 --- a/libs/common/src/services/settings.service.ts +++ b/libs/common/src/services/settings.service.ts @@ -40,10 +40,10 @@ export class SettingsService implements SettingsServiceAbstraction { await this.stateService.setSettings(settings); } - getEquivalentDomains(url: string): string[] { + getEquivalentDomains(url: string): Set { const domain = Utils.getDomain(url); if (domain == null) { - return null; + return new Set(); } const settings = this._settings.getValue(); @@ -58,7 +58,7 @@ export class SettingsService implements SettingsServiceAbstraction { }); } - return result; + return new Set(result); } async clear(userId?: string): Promise { diff --git a/libs/common/src/vault/models/view/login-uri-view.spec.ts b/libs/common/src/vault/models/view/login-uri-view.spec.ts index 1467b228c7..974846a570 100644 --- a/libs/common/src/vault/models/view/login-uri-view.spec.ts +++ b/libs/common/src/vault/models/view/login-uri-view.spec.ts @@ -1,4 +1,5 @@ import { UriMatchType } from "../../../enums"; +import { Utils } from "../../../misc/utils"; import { LoginUriView } from "./login-uri.view"; @@ -25,6 +26,18 @@ const testData = [ }, ]; +const exampleUris = { + standard: "https://www.exampleapp.com.au:4000/userauth/login.html", + standardRegex: "https://www.exampleapp.com.au:[0-9]*/[A-Za-z]+/login.html", + standardNotMatching: "https://www.exampleapp.com.au:4000/userauth123/login.html", + subdomain: "https://www.auth.exampleapp.com.au", + differentDomain: "https://www.exampleapp.co.uk/subpage", + differentHost: "https://www.exampleapp.com.au/userauth/login.html", + equivalentDomains: () => + new Set(["exampleapp.com.au", "exampleapp.com", "exampleapp.co.uk", "example.com"]), + noEquivalentDomains: () => new Set(), +}; + describe("LoginUriView", () => { it("isWebsite() given an invalid domain should return false", async () => { const uri = new LoginUriView(); @@ -63,4 +76,119 @@ describe("LoginUriView", () => { Object.assign(uri, { match: UriMatchType.Host, uri: "someprotocol://bitwarden.com" }); expect(uri.canLaunch).toBe(false); }); + + describe("uri matching", () => { + describe("using domain matching", () => { + it("matches the same domain", () => { + const uri = uriFactory(UriMatchType.Domain, exampleUris.standard); + const actual = uri.matchesUri(exampleUris.subdomain, exampleUris.noEquivalentDomains()); + expect(actual).toBe(true); + }); + + it("matches equivalent domains", () => { + const uri = uriFactory(UriMatchType.Domain, exampleUris.standard); + const actual = uri.matchesUri(exampleUris.differentDomain, exampleUris.equivalentDomains()); + expect(actual).toBe(true); + }); + + it("does not match a different domain", () => { + const uri = uriFactory(UriMatchType.Domain, exampleUris.standard); + const actual = uri.matchesUri( + exampleUris.differentDomain, + exampleUris.noEquivalentDomains() + ); + expect(actual).toBe(false); + }); + + // Actual integration test with the real blacklist, not ideal + it("does not match domains that are blacklisted", () => { + const googleEquivalentDomains = new Set(["google.com", "script.google.com"]); + const uri = uriFactory(UriMatchType.Domain, "google.com"); + + const actual = uri.matchesUri("script.google.com", googleEquivalentDomains); + + expect(actual).toBe(false); + }); + }); + + describe("using host matching", () => { + it("matches the same host", () => { + const uri = uriFactory(UriMatchType.Host, Utils.getHost(exampleUris.standard)); + const actual = uri.matchesUri(exampleUris.standard, exampleUris.noEquivalentDomains()); + expect(actual).toBe(true); + }); + + it("does not match a different host", () => { + const uri = uriFactory(UriMatchType.Host, Utils.getHost(exampleUris.differentDomain)); + const actual = uri.matchesUri(exampleUris.standard, exampleUris.noEquivalentDomains()); + expect(actual).toBe(false); + }); + }); + + describe("using exact matching", () => { + it("matches if both uris are the same", () => { + const uri = uriFactory(UriMatchType.Exact, exampleUris.standard); + const actual = uri.matchesUri(exampleUris.standard, exampleUris.noEquivalentDomains()); + expect(actual).toBe(true); + }); + + it("does not match if the uris are different", () => { + const uri = uriFactory(UriMatchType.Exact, exampleUris.standard); + const actual = uri.matchesUri( + exampleUris.standard + "#", + exampleUris.noEquivalentDomains() + ); + expect(actual).toBe(false); + }); + }); + + describe("using startsWith matching", () => { + it("matches if the target URI starts with the saved URI", () => { + const uri = uriFactory(UriMatchType.StartsWith, exampleUris.standard); + const actual = uri.matchesUri( + exampleUris.standard + "#bookmark", + exampleUris.noEquivalentDomains() + ); + expect(actual).toBe(true); + }); + + it("does not match if the start of the uri is not the same", () => { + const uri = uriFactory(UriMatchType.StartsWith, exampleUris.standard); + const actual = uri.matchesUri( + exampleUris.standard.slice(1), + exampleUris.noEquivalentDomains() + ); + expect(actual).toBe(false); + }); + }); + + describe("using regular expression matching", () => { + it("matches if the regular expression matches", () => { + const uri = uriFactory(UriMatchType.RegularExpression, exampleUris.standard); + const actual = uri.matchesUri(exampleUris.standardRegex, exampleUris.noEquivalentDomains()); + expect(actual).toBe(false); + }); + + it("does not match if the regular expression does not match", () => { + const uri = uriFactory(UriMatchType.RegularExpression, exampleUris.standardNotMatching); + const actual = uri.matchesUri(exampleUris.standardRegex, exampleUris.noEquivalentDomains()); + expect(actual).toBe(false); + }); + }); + + describe("using never matching", () => { + it("does not match even if uris are identical", () => { + const uri = uriFactory(UriMatchType.Never, exampleUris.standard); + const actual = uri.matchesUri(exampleUris.standard, exampleUris.noEquivalentDomains()); + expect(actual).toBe(false); + }); + }); + }); }); + +function uriFactory(match: UriMatchType, uri: string) { + const loginUri = new LoginUriView(); + loginUri.match = match; + loginUri.uri = uri; + return loginUri; +} diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts index 6dcc810036..8b06366f80 100644 --- a/libs/common/src/vault/models/view/login-uri.view.ts +++ b/libs/common/src/vault/models/view/login-uri.view.ts @@ -129,4 +129,60 @@ export class LoginUriView implements View { static fromJSON(obj: Partial>): LoginUriView { return Object.assign(new LoginUriView(), obj); } + + matchesUri( + targetUri: string, + equivalentDomains: Set, + defaultUriMatch: UriMatchType = null + ): boolean { + if (!this.uri || !targetUri) { + return false; + } + + let matchType = this.match ?? defaultUriMatch; + matchType ??= UriMatchType.Domain; + + const targetDomain = Utils.getDomain(targetUri); + const matchDomains = equivalentDomains.add(targetDomain); + + switch (matchType) { + case UriMatchType.Domain: + return this.matchesDomain(targetUri, matchDomains); + case UriMatchType.Host: { + const urlHost = Utils.getHost(targetUri); + return urlHost != null && urlHost === Utils.getHost(this.uri); + } + case UriMatchType.Exact: + return targetUri === this.uri; + case UriMatchType.StartsWith: + return targetUri.startsWith(this.uri); + case UriMatchType.RegularExpression: + try { + const regex = new RegExp(this.uri, "i"); + return regex.test(targetUri); + } catch (e) { + // Invalid regex + return false; + } + case UriMatchType.Never: + return false; + default: + break; + } + + return false; + } + + private matchesDomain(targetUri: string, matchDomains: Set) { + if (targetUri == null || this.domain == null || !matchDomains.has(this.domain)) { + return false; + } + + if (Utils.DomainMatchBlacklist.has(this.domain)) { + const domainUrlHost = Utils.getHost(targetUri); + return !Utils.DomainMatchBlacklist.get(this.domain).has(domainUrlHost); + } + + return true; + } } diff --git a/libs/common/src/vault/models/view/login.view.ts b/libs/common/src/vault/models/view/login.view.ts index c8ba4b2c6a..e1685b1125 100644 --- a/libs/common/src/vault/models/view/login.view.ts +++ b/libs/common/src/vault/models/view/login.view.ts @@ -1,6 +1,6 @@ import { Jsonify } from "type-fest"; -import { LoginLinkedId as LinkedId } from "../../../enums"; +import { LoginLinkedId as LinkedId, UriMatchType } from "../../../enums"; import { linkedFieldOption } from "../../../misc/linkedFieldOption.decorator"; import { Utils } from "../../../misc/utils"; import { Login } from "../domain/login"; @@ -63,6 +63,18 @@ export class LoginView extends ItemView { return this.uris != null && this.uris.length > 0; } + matchesUri( + targetUri: string, + equivalentDomains: Set, + defaultUriMatch: UriMatchType = null + ): boolean { + if (this.uris == null) { + return false; + } + + return this.uris.some((uri) => uri.matchesUri(targetUri, equivalentDomains, defaultUriMatch)); + } + static fromJSON(obj: Partial>): LoginView { const passwordRevisionDate = obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index 0a56748ece..3be54778d6 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -5,7 +5,6 @@ import { ApiService } from "../../abstractions/api.service"; import { CryptoService } from "../../abstractions/crypto.service"; import { EncryptService } from "../../abstractions/encrypt.service"; import { I18nService } from "../../abstractions/i18n.service"; -import { LogService } from "../../abstractions/log.service"; import { SearchService } from "../../abstractions/search.service"; import { SettingsService } from "../../abstractions/settings.service"; import { StateService } from "../../abstractions/state.service"; @@ -28,7 +27,6 @@ describe("Cipher Service", () => { let cipherFileUploadService: SubstituteOf; let i18nService: SubstituteOf; let searchService: SubstituteOf; - let logService: SubstituteOf; let encryptService: SubstituteOf; let cipherService: CipherService; @@ -41,7 +39,6 @@ describe("Cipher Service", () => { cipherFileUploadService = Substitute.for(); i18nService = Substitute.for(); searchService = Substitute.for(); - logService = Substitute.for(); encryptService = Substitute.for(); cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES); @@ -53,7 +50,6 @@ describe("Cipher Service", () => { apiService, i18nService, () => searchService, - logService, stateService, encryptService, cipherFileUploadService diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 533683e858..c2a3a239e8 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -1,17 +1,13 @@ -import { firstValueFrom } from "rxjs"; - import { ApiService } from "../../abstractions/api.service"; import { CryptoService } from "../../abstractions/crypto.service"; import { EncryptService } from "../../abstractions/encrypt.service"; import { I18nService } from "../../abstractions/i18n.service"; -import { LogService } from "../../abstractions/log.service"; import { SearchService } from "../../abstractions/search.service"; import { SettingsService } from "../../abstractions/settings.service"; import { StateService } from "../../abstractions/state.service"; import { FieldType, UriMatchType } from "../../enums"; import { sequentialize } from "../../misc/sequentialize"; import { Utils } from "../../misc/utils"; -import { AccountSettingsSettings } from "../../models/domain/account"; import Domain from "../../models/domain/domain-base"; import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; import { EncString } from "../../models/domain/enc-string"; @@ -58,7 +54,6 @@ export class CipherService implements CipherServiceAbstraction { private apiService: ApiService, private i18nService: I18nService, private searchService: () => SearchService, - private logService: LogService, private stateService: StateService, private encryptService: EncryptService, private cipherFileUploadService: CipherFileUploadService @@ -399,37 +394,9 @@ export class CipherService implements CipherServiceAbstraction { return Promise.resolve([]); } - const domain = Utils.getDomain(url); - const eqDomainsPromise = - domain == null - ? Promise.resolve([]) - : firstValueFrom(this.settingsService.settings$).then( - (settings: AccountSettingsSettings) => { - let matches: any[] = []; - settings?.equivalentDomains?.forEach((eqDomain: any) => { - if (eqDomain.length && eqDomain.indexOf(domain) >= 0) { - matches = matches.concat(eqDomain); - } - }); - - if (!matches.length) { - matches.push(domain); - } - - return matches; - } - ); - - const result = await Promise.all([eqDomainsPromise, this.getAllDecrypted()]); - const matchingDomains = result[0]; - const ciphers = result[1]; - - if (defaultMatch == null) { - defaultMatch = await this.stateService.getDefaultUriMatch(); - if (defaultMatch == null) { - defaultMatch = UriMatchType.Domain; - } - } + const equivalentDomains = this.settingsService.getEquivalentDomains(url); + const ciphers = await this.getAllDecrypted(); + defaultMatch ??= await this.stateService.getDefaultUriMatch(); return ciphers.filter((cipher) => { if (cipher.deletedDate != null) { @@ -439,59 +406,8 @@ export class CipherService implements CipherServiceAbstraction { return true; } - if (url != null && cipher.type === CipherType.Login && cipher.login.uris != null) { - for (let i = 0; i < cipher.login.uris.length; i++) { - const u = cipher.login.uris[i]; - if (u.uri == null) { - continue; - } - - const match = u.match == null ? defaultMatch : u.match; - switch (match) { - case UriMatchType.Domain: - if (domain != null && u.domain != null && matchingDomains.indexOf(u.domain) > -1) { - if (Utils.DomainMatchBlacklist.has(u.domain)) { - const domainUrlHost = Utils.getHost(url); - if (!Utils.DomainMatchBlacklist.get(u.domain).has(domainUrlHost)) { - return true; - } - } else { - return true; - } - } - break; - case UriMatchType.Host: { - const urlHost = Utils.getHost(url); - if (urlHost != null && urlHost === Utils.getHost(u.uri)) { - return true; - } - break; - } - case UriMatchType.Exact: - if (url === u.uri) { - return true; - } - break; - case UriMatchType.StartsWith: - if (url.startsWith(u.uri)) { - return true; - } - break; - case UriMatchType.RegularExpression: - try { - const regex = new RegExp(u.uri, "i"); - if (regex.test(url)) { - return true; - } - } catch (e) { - this.logService.error(e); - } - break; - case UriMatchType.Never: - default: - break; - } - } + if (cipher.type === CipherType.Login && cipher.login !== null) { + return cipher.login.matchesUri(url, equivalentDomains, defaultMatch); } return false;