1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-09 05:00:10 +00:00

support url path matching for targeting rules

This commit is contained in:
Jonathan Prusik
2025-10-20 18:14:03 -04:00
parent 7c07cbb145
commit d531f2f57e
2 changed files with 67 additions and 39 deletions

View File

@@ -21,7 +21,6 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { AutofillTargetingRulesByDomain } from "@bitwarden/common/autofill/types";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import {
ButtonModule,
CardComponent,
@@ -185,46 +184,44 @@ export class AutofillTargetingRulesComponent implements AfterViewInit, OnDestroy
const formGroup = control as FormGroup;
const domain = formGroup.get("domain")?.value;
if (domain && domain !== "") {
const validatedHost = Utils.getHostname(domain);
const normalizedURI = this.domainSettingsService.normalizeAutofillTargetingURI(domain);
if (!validatedHost) {
this.toastService.showToast({
message: this.i18nService.t("blockedDomainsInvalidDomain", domain),
title: "",
variant: "error",
});
return;
}
if (!normalizedURI) {
this.toastService.showToast({
message: this.i18nService.t("blockedDomainsInvalidDomain", domain),
title: "",
variant: "error",
});
return;
}
const enteredUsername = formGroup.get("username")?.value;
const enteredPassword = formGroup.get("password")?.value;
const enteredTotp = formGroup.get("totp")?.value;
const enteredUsername = formGroup.get("username")?.value;
const enteredPassword = formGroup.get("password")?.value;
const enteredTotp = formGroup.get("totp")?.value;
if (!enteredUsername && !enteredPassword && !enteredTotp) {
this.toastService.showToast({
message: "No targeting rules were specified for the URL",
title: "",
variant: "error",
});
if (!enteredUsername && !enteredPassword && !enteredTotp) {
this.toastService.showToast({
message: "No targeting rules were specified for the URL",
title: "",
variant: "error",
});
return;
}
return;
}
newUriTargetingRulesSaveState[validatedHost] = {};
newUriTargetingRulesSaveState[normalizedURI] = {};
// Only add the property to the object if it has a value
if (enteredUsername) {
newUriTargetingRulesSaveState[validatedHost].username = enteredUsername;
}
// Only add the property to the object if it has a value
if (enteredUsername) {
newUriTargetingRulesSaveState[normalizedURI].username = enteredUsername;
}
if (enteredPassword) {
newUriTargetingRulesSaveState[validatedHost].password = enteredPassword;
}
if (enteredPassword) {
newUriTargetingRulesSaveState[normalizedURI].password = enteredPassword;
}
if (enteredTotp) {
newUriTargetingRulesSaveState[validatedHost].totp = enteredTotp;
}
if (enteredTotp) {
newUriTargetingRulesSaveState[normalizedURI].totp = enteredTotp;
}
});

View File

@@ -123,6 +123,9 @@ export abstract class DomainSettingsService {
* Helper function for the common resolution of a given URL against equivalent domains
*/
getUrlEquivalentDomains: (url: string) => Observable<Set<string>>;
/** URI normalization for Autofill Targeting rules URLs to ensure state is doing safe, consistent internal comparisons */
normalizeAutofillTargetingURI: (url: chrome.tabs.Tab["url"]) => chrome.tabs.Tab["url"] | null;
}
export class DefaultDomainSettingsService implements DomainSettingsService {
@@ -205,7 +208,19 @@ export class DefaultDomainSettingsService implements DomainSettingsService {
}
async setAutofillTargetingRules(newValue: AutofillTargetingRulesByDomain): Promise<void> {
await this.autofillTargetingRulesState.update(() => newValue);
await this.autofillTargetingRulesState.update(() => {
const validatedNewValue = Object.keys(newValue || {}).reduce((updatingValue, valueKey) => {
const normalizedURI = this.normalizeAutofillTargetingURI(valueKey);
if (normalizedURI) {
return { ...updatingValue, [normalizedURI]: newValue[valueKey] };
}
return updatingValue;
}, {});
return validatedNewValue;
});
}
async setShowFavicons(newValue: boolean): Promise<void> {
@@ -245,17 +260,33 @@ export class DefaultDomainSettingsService implements DomainSettingsService {
return domains$;
}
getUrlAutofillTargetingRules$(url: string): Observable<AutofillTargetingRules> {
getUrlAutofillTargetingRules$(url: chrome.tabs.Tab["url"]): Observable<AutofillTargetingRules> {
const normalizedURI = this.normalizeAutofillTargetingURI(url);
return this.autofillTargetingRules$.pipe(
map((autofillTargetingRules) => {
const domain = Utils.getHostname(url);
if (domain == null) {
if (!normalizedURI) {
return {};
}
return autofillTargetingRules?.[domain] || {};
return autofillTargetingRules?.[normalizedURI] || {};
}),
);
}
normalizeAutofillTargetingURI(url: chrome.tabs.Tab["url"]) {
if (!Utils.isNullOrWhitespace(url)) {
const uriParts = Utils.getUrl(url);
const validatedHostname = Utils.getHostname(url);
// Prevent directory traversal from malicious paths
const pathParts = uriParts.pathname.split("?");
const normalizedURI =
uriParts.protocol + "//" + validatedHostname + Utils.normalizePath(pathParts[0]);
return normalizedURI;
}
return null;
}
}