mirror of
https://github.com/bitwarden/browser
synced 2026-02-14 23:45:37 +00:00
Merge remote-tracking branch 'origin/main' into rename-tsconfig
This commit is contained in:
@@ -1579,252 +1579,6 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
return [expectedDateFormat, dateFormatPatterns];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the autofill script for the specified page details and identify cipher item.
|
||||
* @param {AutofillScript} fillScript
|
||||
* @param {AutofillPageDetails} pageDetails
|
||||
* @param {{[p: string]: AutofillField}} filledFields
|
||||
* @param {GenerateFillScriptOptions} options
|
||||
* @returns {AutofillScript}
|
||||
* @private
|
||||
*/
|
||||
private async generateIdentityFillScript(
|
||||
fillScript: AutofillScript,
|
||||
pageDetails: AutofillPageDetails,
|
||||
filledFields: { [id: string]: AutofillField },
|
||||
options: GenerateFillScriptOptions,
|
||||
): Promise<AutofillScript> {
|
||||
if (await this.configService.getFeatureFlag(FeatureFlag.GenerateIdentityFillScriptRefactor)) {
|
||||
return this._generateIdentityFillScript(fillScript, pageDetails, filledFields, options);
|
||||
}
|
||||
|
||||
if (!options.cipher.identity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fillFields: { [id: string]: AutofillField } = {};
|
||||
|
||||
pageDetails.fields.forEach((f) => {
|
||||
if (
|
||||
AutofillService.isExcludedFieldType(f, AutoFillConstants.ExcludedAutofillTypes) ||
|
||||
["current-password", "new-password"].includes(f.autoCompleteType)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < IdentityAutoFillConstants.IdentityAttributes.length; i++) {
|
||||
const attr = IdentityAutoFillConstants.IdentityAttributes[i];
|
||||
// eslint-disable-next-line
|
||||
if (!f.hasOwnProperty(attr) || !f[attr] || !f.viewable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ref https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill
|
||||
// ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/
|
||||
if (
|
||||
!fillFields.name &&
|
||||
AutofillService.isFieldMatch(
|
||||
f[attr],
|
||||
IdentityAutoFillConstants.FullNameFieldNames,
|
||||
IdentityAutoFillConstants.FullNameFieldNameValues,
|
||||
)
|
||||
) {
|
||||
fillFields.name = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.firstName &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames)
|
||||
) {
|
||||
fillFields.firstName = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.middleName &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames)
|
||||
) {
|
||||
fillFields.middleName = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.lastName &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames)
|
||||
) {
|
||||
fillFields.lastName = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.title &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames)
|
||||
) {
|
||||
fillFields.title = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.email &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames)
|
||||
) {
|
||||
fillFields.email = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.address1 &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames)
|
||||
) {
|
||||
fillFields.address1 = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.address2 &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames)
|
||||
) {
|
||||
fillFields.address2 = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.address3 &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames)
|
||||
) {
|
||||
fillFields.address3 = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.address &&
|
||||
AutofillService.isFieldMatch(
|
||||
f[attr],
|
||||
IdentityAutoFillConstants.AddressFieldNames,
|
||||
IdentityAutoFillConstants.AddressFieldNameValues,
|
||||
)
|
||||
) {
|
||||
fillFields.address = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.postalCode &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames)
|
||||
) {
|
||||
fillFields.postalCode = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.city &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames)
|
||||
) {
|
||||
fillFields.city = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.state &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames)
|
||||
) {
|
||||
fillFields.state = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.country &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames)
|
||||
) {
|
||||
fillFields.country = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.phone &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames)
|
||||
) {
|
||||
fillFields.phone = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.username &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames)
|
||||
) {
|
||||
fillFields.username = f;
|
||||
break;
|
||||
} else if (
|
||||
!fillFields.company &&
|
||||
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames)
|
||||
) {
|
||||
fillFields.company = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const identity = options.cipher.identity;
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "title");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "firstName");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "middleName");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "lastName");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address1");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address2");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address3");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "city");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "postalCode");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "company");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "email");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "phone");
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "username");
|
||||
|
||||
let filledState = false;
|
||||
if (fillFields.state && identity.state && identity.state.length > 2) {
|
||||
const stateLower = identity.state.toLowerCase();
|
||||
const isoState =
|
||||
IdentityAutoFillConstants.IsoStates[stateLower] ||
|
||||
IdentityAutoFillConstants.IsoProvinces[stateLower];
|
||||
if (isoState) {
|
||||
filledState = true;
|
||||
this.makeScriptActionWithValue(fillScript, isoState, fillFields.state, filledFields);
|
||||
}
|
||||
}
|
||||
|
||||
if (!filledState) {
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "state");
|
||||
}
|
||||
|
||||
let filledCountry = false;
|
||||
if (fillFields.country && identity.country && identity.country.length > 2) {
|
||||
const countryLower = identity.country.toLowerCase();
|
||||
const isoCountry = IdentityAutoFillConstants.IsoCountries[countryLower];
|
||||
if (isoCountry) {
|
||||
filledCountry = true;
|
||||
this.makeScriptActionWithValue(fillScript, isoCountry, fillFields.country, filledFields);
|
||||
}
|
||||
}
|
||||
|
||||
if (!filledCountry) {
|
||||
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "country");
|
||||
}
|
||||
|
||||
if (fillFields.name && (identity.firstName || identity.lastName)) {
|
||||
let fullName = "";
|
||||
if (AutofillService.hasValue(identity.firstName)) {
|
||||
fullName = identity.firstName;
|
||||
}
|
||||
if (AutofillService.hasValue(identity.middleName)) {
|
||||
if (fullName !== "") {
|
||||
fullName += " ";
|
||||
}
|
||||
fullName += identity.middleName;
|
||||
}
|
||||
if (AutofillService.hasValue(identity.lastName)) {
|
||||
if (fullName !== "") {
|
||||
fullName += " ";
|
||||
}
|
||||
fullName += identity.lastName;
|
||||
}
|
||||
|
||||
this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields);
|
||||
}
|
||||
|
||||
if (fillFields.address && AutofillService.hasValue(identity.address1)) {
|
||||
let address = "";
|
||||
if (AutofillService.hasValue(identity.address1)) {
|
||||
address = identity.address1;
|
||||
}
|
||||
if (AutofillService.hasValue(identity.address2)) {
|
||||
if (address !== "") {
|
||||
address += ", ";
|
||||
}
|
||||
address += identity.address2;
|
||||
}
|
||||
if (AutofillService.hasValue(identity.address3)) {
|
||||
if (address !== "") {
|
||||
address += ", ";
|
||||
}
|
||||
address += identity.address3;
|
||||
}
|
||||
|
||||
this.makeScriptActionWithValue(fillScript, address, fillFields.address, filledFields);
|
||||
}
|
||||
|
||||
return fillScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the autofill script for the specified page details and identity cipher item.
|
||||
*
|
||||
@@ -1833,7 +1587,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
* @param filledFields - The fields that have already been filled, passed between method references
|
||||
* @param options - Contains data used to fill cipher items
|
||||
*/
|
||||
private _generateIdentityFillScript(
|
||||
private generateIdentityFillScript(
|
||||
fillScript: AutofillScript,
|
||||
pageDetails: AutofillPageDetails,
|
||||
filledFields: { [id: string]: AutofillField },
|
||||
|
||||
@@ -7,13 +7,13 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
|
||||
import MainBackground from "../../background/main.background";
|
||||
import IconDetails from "../../vault/background/models/icon-details";
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
import { BrowserPlatformUtilsService } from "../services/platform-utils/browser-platform-utils.service";
|
||||
|
||||
export type BadgeOptions = {
|
||||
tab?: chrome.tabs.Tab;
|
||||
@@ -28,6 +28,7 @@ export class UpdateBadge {
|
||||
private badgeAction: typeof chrome.action | typeof chrome.browserAction;
|
||||
private sidebarAction: OperaSidebarAction | FirefoxSidebarAction;
|
||||
private win: Window & typeof globalThis;
|
||||
private platformUtilsService: PlatformUtilsService;
|
||||
|
||||
constructor(win: Window & typeof globalThis, services: MainBackground) {
|
||||
this.badgeAction = BrowserApi.getBrowserAction();
|
||||
@@ -38,6 +39,7 @@ export class UpdateBadge {
|
||||
this.authService = services.authService;
|
||||
this.cipherService = services.cipherService;
|
||||
this.accountService = services.accountService;
|
||||
this.platformUtilsService = services.platformUtilsService;
|
||||
}
|
||||
|
||||
async run(opts?: { tabId?: number; windowId?: number }): Promise<void> {
|
||||
@@ -129,7 +131,7 @@ export class UpdateBadge {
|
||||
38: "/images/icon38" + iconSuffix + ".png",
|
||||
},
|
||||
};
|
||||
if (windowId && BrowserPlatformUtilsService.isFirefox()) {
|
||||
if (windowId && this.platformUtilsService.isFirefox()) {
|
||||
options.windowId = windowId;
|
||||
}
|
||||
|
||||
@@ -204,9 +206,7 @@ export class UpdateBadge {
|
||||
}
|
||||
|
||||
private get useSyncApiCalls() {
|
||||
return (
|
||||
BrowserPlatformUtilsService.isFirefox() || BrowserPlatformUtilsService.isSafari(this.win)
|
||||
);
|
||||
return this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari();
|
||||
}
|
||||
|
||||
private isOperaSidebar(
|
||||
|
||||
@@ -126,12 +126,11 @@ describe("Browser Utils Service", () => {
|
||||
configurable: true,
|
||||
value: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0",
|
||||
});
|
||||
jest.spyOn(BrowserPlatformUtilsService, "isFirefox");
|
||||
|
||||
browserPlatformUtilsService.getDevice();
|
||||
|
||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension);
|
||||
expect(BrowserPlatformUtilsService.isFirefox).toHaveBeenCalledTimes(1);
|
||||
expect(browserPlatformUtilsService.isFirefox()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -60,10 +60,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
||||
return ClientType.Browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not call this directly, use getDevice() instead
|
||||
*/
|
||||
static isFirefox(): boolean {
|
||||
private static isFirefox(): boolean {
|
||||
return (
|
||||
navigator.userAgent.indexOf(" Firefox/") !== -1 ||
|
||||
navigator.userAgent.indexOf(" Gecko/") !== -1
|
||||
@@ -74,9 +71,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
||||
return this.getDevice() === DeviceType.FirefoxExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not call this directly, use getDevice() instead
|
||||
*/
|
||||
private static isChrome(globalContext: Window | ServiceWorkerGlobalScope): boolean {
|
||||
return globalContext.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1;
|
||||
}
|
||||
@@ -85,9 +79,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
||||
return this.getDevice() === DeviceType.ChromeExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not call this directly, use getDevice() instead
|
||||
*/
|
||||
private static isEdge(): boolean {
|
||||
return navigator.userAgent.indexOf(" Edg/") !== -1;
|
||||
}
|
||||
@@ -96,9 +87,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
||||
return this.getDevice() === DeviceType.EdgeExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not call this directly, use getDevice() instead
|
||||
*/
|
||||
private static isOpera(globalContext: Window | ServiceWorkerGlobalScope): boolean {
|
||||
return (
|
||||
!!globalContext.opr?.addons ||
|
||||
@@ -111,9 +99,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
||||
return this.getDevice() === DeviceType.OperaExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not call this directly, use getDevice() instead
|
||||
*/
|
||||
private static isVivaldi(): boolean {
|
||||
return navigator.userAgent.indexOf(" Vivaldi/") !== -1;
|
||||
}
|
||||
@@ -122,10 +107,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
||||
return this.getDevice() === DeviceType.VivaldiExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not call this directly, use getDevice() instead
|
||||
*/
|
||||
static isSafari(globalContext: Window | ServiceWorkerGlobalScope): boolean {
|
||||
private static isSafari(globalContext: Window | ServiceWorkerGlobalScope): boolean {
|
||||
// Opera masquerades as Safari, so make sure we're not there first
|
||||
return (
|
||||
!BrowserPlatformUtilsService.isOpera(globalContext) &&
|
||||
@@ -137,6 +119,10 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
||||
return navigator.userAgent.match("Version/([0-9.]*)")?.[1];
|
||||
}
|
||||
|
||||
isSafari(): boolean {
|
||||
return this.getDevice() === DeviceType.SafariExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safari previous to version 16.1 had a bug which caused artifacts on hover in large extension popups.
|
||||
* https://bugs.webkit.org/show_bug.cgi?id=218704
|
||||
@@ -151,10 +137,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
||||
return parts?.[0] < 16 || (parts?.[0] === 16 && parts?.[1] === 0);
|
||||
}
|
||||
|
||||
isSafari(): boolean {
|
||||
return this.getDevice() === DeviceType.SafariExtension;
|
||||
}
|
||||
|
||||
isIE(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { Component, NgZone } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { Subject, Subscription, debounceTime, filter } from "rxjs";
|
||||
import { Subject, Subscription, debounceTime, distinctUntilChanged, filter } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { SearchModule } from "@bitwarden/components";
|
||||
@@ -22,13 +22,16 @@ export class VaultV2SearchComponent {
|
||||
|
||||
private searchText$ = new Subject<string>();
|
||||
|
||||
constructor(private vaultPopupItemsService: VaultPopupItemsService) {
|
||||
constructor(
|
||||
private vaultPopupItemsService: VaultPopupItemsService,
|
||||
private ngZone: NgZone,
|
||||
) {
|
||||
this.subscribeToLatestSearchText();
|
||||
this.subscribeToApplyFilter();
|
||||
}
|
||||
|
||||
onSearchTextChanged() {
|
||||
this.vaultPopupItemsService.applyFilter(this.searchText);
|
||||
this.searchText$.next(this.searchText);
|
||||
}
|
||||
|
||||
subscribeToLatestSearchText(): Subscription {
|
||||
@@ -44,9 +47,13 @@ export class VaultV2SearchComponent {
|
||||
|
||||
subscribeToApplyFilter(): Subscription {
|
||||
return this.searchText$
|
||||
.pipe(debounceTime(SearchTextDebounceInterval), takeUntilDestroyed())
|
||||
.pipe(debounceTime(SearchTextDebounceInterval), distinctUntilChanged(), takeUntilDestroyed())
|
||||
.subscribe((data) => {
|
||||
this.vaultPopupItemsService.applyFilter(data);
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
this.ngZone.run(() => {
|
||||
this.vaultPopupItemsService.applyFilter(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
4
apps/desktop/desktop_native/Cargo.lock
generated
4
apps/desktop/desktop_native/Cargo.lock
generated
@@ -498,9 +498,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.9.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
|
||||
@@ -18,7 +18,7 @@ base64 = "=0.22.1"
|
||||
bindgen = "=0.71.1"
|
||||
bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "3d48f140fd506412d186203238993163a8c4e536" }
|
||||
byteorder = "=1.5.0"
|
||||
bytes = "=1.9.0"
|
||||
bytes = "=1.10.1"
|
||||
cbc = "=0.1.2"
|
||||
core-foundation = "=0.10.0"
|
||||
dirs = "=6.0.0"
|
||||
|
||||
@@ -10,7 +10,7 @@ import { autostart } from "@bitwarden/desktop-napi";
|
||||
|
||||
import { Main } from "../main";
|
||||
import { DesktopSettingsService } from "../platform/services/desktop-settings.service";
|
||||
import { isFlatpak } from "../utils";
|
||||
import { isFlatpak, isLinux, isSnapStore } from "../utils";
|
||||
|
||||
import { MenuUpdateRequest } from "./menu/menu.updater";
|
||||
|
||||
@@ -26,8 +26,11 @@ export class MessagingMain {
|
||||
|
||||
async init() {
|
||||
this.scheduleNextSync();
|
||||
if (process.platform === "linux") {
|
||||
await this.desktopSettingsService.setOpenAtLogin(fs.existsSync(this.linuxStartupFile()));
|
||||
if (isLinux()) {
|
||||
// Flatpak and snap don't have access to or use the startup file. On flatpak, the autostart portal is used
|
||||
if (!isFlatpak() && !isSnapStore()) {
|
||||
await this.desktopSettingsService.setOpenAtLogin(fs.existsSync(this.linuxStartupFile()));
|
||||
}
|
||||
} else {
|
||||
const loginSettings = app.getLoginItemSettings();
|
||||
await this.desktopSettingsService.setOpenAtLogin(loginSettings.openAtLogin);
|
||||
|
||||
@@ -188,13 +188,10 @@ export class DuckDuckGoMessageHandlerService {
|
||||
}
|
||||
|
||||
try {
|
||||
let decryptedResult = await this.encryptService.decryptString(
|
||||
const decryptedResult = await this.decryptDuckDuckGoEncString(
|
||||
message.encryptedCommand as EncString,
|
||||
this.duckduckgoSharedSecret,
|
||||
);
|
||||
|
||||
decryptedResult = this.trimNullCharsFromMessage(decryptedResult);
|
||||
|
||||
return JSON.parse(decryptedResult);
|
||||
} catch {
|
||||
this.sendResponse({
|
||||
@@ -237,7 +234,46 @@ export class DuckDuckGoMessageHandlerService {
|
||||
ipc.platform.nativeMessaging.sendReply(response);
|
||||
}
|
||||
|
||||
// Trim all null bytes padded at the end of messages. This happens with C encryption libraries.
|
||||
/*
|
||||
* Bitwarden type 2 (AES256-CBC-HMAC256) uses PKCS7 padding.
|
||||
* DuckDuckGo does not use PKCS7 padding; and instead fills the last CBC block with null bytes.
|
||||
* ref: https://github.com/duckduckgo/apple-browsers/blob/04d678b447869c3a640714718a466b36407db8b6/macOS/DuckDuckGo/PasswordManager/Bitwarden/Services/BWEncryption.m#L141
|
||||
*
|
||||
* This is incompatible which means the default encryptService cannot be used to decrypt the message,
|
||||
* a custom EncString decrypt operation is needed.
|
||||
*
|
||||
* This function also trims null characters that are a result of the null-padding from the end of the message.
|
||||
*/
|
||||
private async decryptDuckDuckGoEncString(
|
||||
encString: EncString,
|
||||
key: SymmetricCryptoKey,
|
||||
): Promise<string> {
|
||||
const fastParams = this.cryptoFunctionService.aesDecryptFastParameters(
|
||||
encString.data,
|
||||
encString.iv,
|
||||
encString.mac,
|
||||
key,
|
||||
);
|
||||
|
||||
const computedMac = await this.cryptoFunctionService.hmacFast(
|
||||
fastParams.macData,
|
||||
fastParams.macKey,
|
||||
"sha256",
|
||||
);
|
||||
const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac);
|
||||
if (!macsEqual) {
|
||||
return null;
|
||||
}
|
||||
const decryptedPaddedString = await this.cryptoFunctionService.aesDecryptFast({
|
||||
mode: "cbc",
|
||||
parameters: fastParams,
|
||||
});
|
||||
return this.trimNullCharsFromMessage(decryptedPaddedString);
|
||||
}
|
||||
|
||||
// DuckDuckGo does not use PKCS7 padding, but instead leaves the values as null,
|
||||
// so null characters need to be trimmed from the end of the message for the last
|
||||
// CBC-block.
|
||||
private trimNullCharsFromMessage(message: string): string {
|
||||
const charNull = 0;
|
||||
const charRightCurlyBrace = 125;
|
||||
|
||||
@@ -37,6 +37,31 @@ export function getNestedCollectionTree(
|
||||
return nodes;
|
||||
}
|
||||
|
||||
export function getNestedCollectionTree_vNext(
|
||||
collections: (CollectionView | CollectionAdminView)[],
|
||||
): TreeNode<CollectionView | CollectionAdminView>[] {
|
||||
if (!collections) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Collections need to be cloned because ServiceUtils.nestedTraverse actively
|
||||
// modifies the names of collections.
|
||||
// These changes risk affecting collections store in StateService.
|
||||
const clonedCollections = collections
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map(cloneCollection);
|
||||
|
||||
const nodes: TreeNode<CollectionView | CollectionAdminView>[] = [];
|
||||
clonedCollections.forEach((collection) => {
|
||||
const parts =
|
||||
collection.name != null
|
||||
? collection.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter)
|
||||
: [];
|
||||
ServiceUtils.nestedTraverse_vNext(nodes, 0, parts, collection, null, NestingDelimiter);
|
||||
});
|
||||
return nodes;
|
||||
}
|
||||
|
||||
export function getFlatCollectionTree(
|
||||
nodes: TreeNode<CollectionAdminView>[],
|
||||
): CollectionAdminView[];
|
||||
|
||||
@@ -125,7 +125,11 @@ import {
|
||||
BulkCollectionsDialogResult,
|
||||
} from "./bulk-collections-dialog";
|
||||
import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component";
|
||||
import { getNestedCollectionTree, getFlatCollectionTree } from "./utils";
|
||||
import {
|
||||
getNestedCollectionTree,
|
||||
getFlatCollectionTree,
|
||||
getNestedCollectionTree_vNext,
|
||||
} from "./utils";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
import { VaultHeaderComponent } from "./vault-header/vault-header.component";
|
||||
|
||||
@@ -420,9 +424,16 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}),
|
||||
);
|
||||
|
||||
const nestedCollections$ = allCollections$.pipe(
|
||||
map((collections) => getNestedCollectionTree(collections)),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
const nestedCollections$ = combineLatest([
|
||||
this.allCollectionsWithoutUnassigned$,
|
||||
this.configService.getFeatureFlag$(FeatureFlag.OptimizeNestedTraverseTypescript),
|
||||
]).pipe(
|
||||
map(
|
||||
([collections, shouldOptimize]) =>
|
||||
(shouldOptimize
|
||||
? getNestedCollectionTree_vNext(collections)
|
||||
: getNestedCollectionTree(collections)) as TreeNode<CollectionAdminView>[],
|
||||
),
|
||||
);
|
||||
|
||||
const collections$ = combineLatest([
|
||||
|
||||
@@ -110,8 +110,10 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
protected rowHeight = 69;
|
||||
protected rowHeightClass = `tw-h-[69px]`;
|
||||
|
||||
private organizationUsersCount = 0;
|
||||
|
||||
get occupiedSeatCount(): number {
|
||||
return this.dataSource.activeUserCount;
|
||||
return this.organizationUsersCount;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@@ -218,6 +220,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
);
|
||||
|
||||
this.orgIsOnSecretsManagerStandalone = billingMetadata.isOnSecretsManagerStandalone;
|
||||
this.organizationUsersCount = billingMetadata.organizationOccupiedSeats;
|
||||
|
||||
await this.load();
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<div class="tabbed-header">
|
||||
<h1>{{ "changeMasterPassword" | i18n }}</h1>
|
||||
</div>
|
||||
<h1 class="tw-mt-6 tw-mb-2 tw-pb-2.5">{{ "changeMasterPassword" | i18n }}</h1>
|
||||
|
||||
<div class="tw-max-w-lg tw-mb-12">
|
||||
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
|
||||
|
||||
@@ -49,7 +49,9 @@ import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
@@ -82,6 +84,7 @@ import {
|
||||
import {
|
||||
getNestedCollectionTree,
|
||||
getFlatCollectionTree,
|
||||
getNestedCollectionTree_vNext,
|
||||
} from "../../admin-console/organizations/collections";
|
||||
import {
|
||||
CollectionDialogAction,
|
||||
@@ -270,6 +273,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
private trialFlowService: TrialFlowService,
|
||||
private organizationBillingService: OrganizationBillingServiceAbstraction,
|
||||
private billingNotificationService: BillingNotificationService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -326,8 +330,15 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
const filter$ = this.routedVaultFilterService.filter$;
|
||||
const allCollections$ = this.collectionService.decryptedCollections$;
|
||||
const nestedCollections$ = allCollections$.pipe(
|
||||
map((collections) => getNestedCollectionTree(collections)),
|
||||
const nestedCollections$ = combineLatest([
|
||||
allCollections$,
|
||||
this.configService.getFeatureFlag$(FeatureFlag.OptimizeNestedTraverseTypescript),
|
||||
]).pipe(
|
||||
map(([collections, shouldOptimize]) =>
|
||||
shouldOptimize
|
||||
? getNestedCollectionTree_vNext(collections)
|
||||
: getNestedCollectionTree(collections),
|
||||
),
|
||||
);
|
||||
|
||||
this.searchText$
|
||||
|
||||
Reference in New Issue
Block a user