From cab1156d63901d5c61a633bb388ae69f5da28535 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:17:00 +0100 Subject: [PATCH 01/74] [deps] Platform: Update Rust crate thiserror to v1.0.69 (#11944) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 8 ++++---- apps/desktop/desktop_native/core/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index a1ff96ec65d..53d151397f6 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -2431,18 +2431,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 7ed708fc577..4f6fdb47fdf 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -54,7 +54,7 @@ bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", br tokio = { version = "=1.40.0", features = ["io-util", "sync", "macros", "net"] } tokio-stream = { version = "=0.1.15", features = ["net"] } tokio-util = "=0.7.12" -thiserror = "=1.0.68" +thiserror = "=1.0.69" typenum = "=1.17.0" rand_chacha = "=0.3.1" pkcs8 = { version = "=0.10.2", features = ["alloc", "encryption", "pem"] } From ac0e008e3ce634a882a6971b7f7c9788741dca14 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:07:42 +0100 Subject: [PATCH 02/74] [PM-8506] Create importer for csv-export from Netwrix Password Secure (#9446) * Create an importer for csv-export from Netwrix Password Secure * Wire the new importer into the clients * Add instructions to export from Netwrix Password Secure * Mark method as private * Remove line which disables linting * Add docs to importer --------- Co-authored-by: Daniel James Smith Co-authored-by: Matt Bishop --- ...etwrix-passwordsecure-csv-importer.spec.ts | 91 +++++++++++++++++++ .../test-data/netwrix-csv/login-export.csv.ts | 4 + .../src/components/import.component.html | 4 + libs/importer/src/importers/index.ts | 1 + libs/importer/src/importers/netwrix/index.ts | 1 + .../netwrix-passwordsecure-csv-importer.ts | 69 ++++++++++++++ .../netwrix-passwordsecure-csv-types.ts | 18 ++++ libs/importer/src/models/import-options.ts | 1 + libs/importer/src/services/import.service.ts | 3 + 9 files changed, 192 insertions(+) create mode 100644 libs/importer/spec/netwrix-passwordsecure-csv-importer.spec.ts create mode 100644 libs/importer/spec/test-data/netwrix-csv/login-export.csv.ts create mode 100644 libs/importer/src/importers/netwrix/index.ts create mode 100644 libs/importer/src/importers/netwrix/netwrix-passwordsecure-csv-importer.ts create mode 100644 libs/importer/src/importers/netwrix/netwrix-passwordsecure-csv-types.ts diff --git a/libs/importer/spec/netwrix-passwordsecure-csv-importer.spec.ts b/libs/importer/spec/netwrix-passwordsecure-csv-importer.spec.ts new file mode 100644 index 00000000000..ab893dbc56c --- /dev/null +++ b/libs/importer/spec/netwrix-passwordsecure-csv-importer.spec.ts @@ -0,0 +1,91 @@ +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +import { NetwrixPasswordSecureCsvImporter } from "../src/importers"; + +import { credentialsData } from "./test-data/netwrix-csv/login-export.csv"; + +describe("Netwrix Password Secure CSV Importer", () => { + let importer: NetwrixPasswordSecureCsvImporter; + beforeEach(() => { + importer = new NetwrixPasswordSecureCsvImporter(); + }); + + it("passing invalid data returns false", async () => { + const result = await importer.parse(""); + expect(result != null).toBe(true); + expect(result.success).toBe(false); + }); + + it("should parse login records", async () => { + const result = await importer.parse(credentialsData); + expect(result != null).toBe(true); + + let cipher = result.ciphers.shift(); + expect(cipher.name).toEqual("Test Entry 1"); + expect(cipher.login.username).toEqual("someUser"); + expect(cipher.login.password).toEqual("somePassword"); + expect(cipher.login.totp).toEqual("someTOTPSeed"); + expect(cipher.login.uris.length).toEqual(1); + let uriView = cipher.login.uris.shift(); + expect(uriView.uri).toEqual("https://www.example.com"); + expect(cipher.notes).toEqual("some note for example.com"); + + cipher = result.ciphers.shift(); + expect(cipher.name).toEqual("Test Entry 2"); + expect(cipher.login.username).toEqual("jdoe"); + expect(cipher.login.password).toEqual("})9+Kg2fz_O#W1§H1-0Zio"); + expect(cipher.login.totp).toEqual("anotherTOTP"); + expect(cipher.login.uris.length).toEqual(1); + uriView = cipher.login.uris.shift(); + expect(uriView.uri).toEqual("http://www.123.com"); + expect(cipher.notes).toEqual("Description123"); + + cipher = result.ciphers.shift(); + expect(cipher.name).toEqual("Test Entry 3"); + expect(cipher.login.username).toEqual("username"); + expect(cipher.login.password).toEqual("password"); + expect(cipher.login.totp).toBeNull(); + expect(cipher.login.uris.length).toEqual(1); + uriView = cipher.login.uris.shift(); + expect(uriView.uri).toEqual("http://www.internetsite.com"); + expect(cipher.notes).toEqual("Information"); + }); + + it("should add any unmapped fields as custom fields", async () => { + const result = await importer.parse(credentialsData); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.fields.length).toBe(1); + const field = cipher.fields.shift(); + expect(field.name).toEqual("DataTags"); + expect(field.value).toEqual("tag1, tag2, tag3"); + }); + + it("should parse an item and create a folder", async () => { + const result = await importer.parse(credentialsData); + + expect(result).not.toBeNull(); + expect(result.success).toBe(true); + expect(result.folders.length).toBe(2); + expect(result.folders[0].name).toBe("folderOrCollection1"); + expect(result.folders[1].name).toBe("folderOrCollection2"); + expect(result.folderRelationships[0]).toEqual([0, 0]); + expect(result.folderRelationships[1]).toEqual([1, 1]); + expect(result.folderRelationships[2]).toEqual([2, 0]); + }); + + it("should parse an item and create a collection when importing into an organization", async () => { + importer.organizationId = Utils.newGuid(); + const result = await importer.parse(credentialsData); + + expect(result).not.toBeNull(); + expect(result.success).toBe(true); + expect(result.collections.length).toBe(2); + expect(result.collections[0].name).toBe("folderOrCollection1"); + expect(result.collections[1].name).toBe("folderOrCollection2"); + expect(result.collectionRelationships[0]).toEqual([0, 0]); + expect(result.collectionRelationships[1]).toEqual([1, 1]); + expect(result.collectionRelationships[2]).toEqual([2, 0]); + }); +}); diff --git a/libs/importer/spec/test-data/netwrix-csv/login-export.csv.ts b/libs/importer/spec/test-data/netwrix-csv/login-export.csv.ts new file mode 100644 index 00000000000..715dd8e0074 --- /dev/null +++ b/libs/importer/spec/test-data/netwrix-csv/login-export.csv.ts @@ -0,0 +1,4 @@ +export const credentialsData = `"Organisationseinheit";"DataTags";"Beschreibung";"Benutzername";"Passwort";"Internetseite";"Informationen";"One-Time Passwort" +"folderOrCollection1";"tag1, tag2, tag3";"Test Entry 1";"someUser";"somePassword";"https://www.example.com";"some note for example.com";"someTOTPSeed" +"folderOrCollection2";"tag2";"Test Entry 2";"jdoe";"})9+Kg2fz_O#W1§H1-0Zio";"www.123.com";"Description123";"anotherTOTP" +"folderOrCollection1";"someTag";"Test Entry 3";"username";"password";"www.internetsite.com";"Information";""`; diff --git a/libs/importer/src/components/import.component.html b/libs/importer/src/components/import.component.html index 91ad7dbfc0a..5b67fc47a78 100644 --- a/libs/importer/src/components/import.component.html +++ b/libs/importer/src/components/import.component.html @@ -380,6 +380,10 @@ In the ProtonPass browser extension, go to Settings > Export. Export without PGP encryption and save the zip file. + + Open the FullClient, go to the Main Menu and select Export. Start the export passwords + wizard and follow the instructions to export a CSV file. + { + const result = new ImportResult(); + const results = this.parseCsv(data, true); + if (results == null) { + result.success = false; + return Promise.resolve(result); + } + + results.forEach((row: LoginRecord) => { + this.processFolder(result, row.Organisationseinheit); + const cipher = this.initLoginCipher(); + + const notes = this.getValueOrDefault(row.Informationen); + if (notes) { + cipher.notes = `${notes}\n`; + } + + cipher.name = this.getValueOrDefault(row.Beschreibung, "--"); + cipher.login.username = this.getValueOrDefault(row.Benutzername); + cipher.login.password = this.getValueOrDefault(row.Passwort); + cipher.login.uris = this.makeUriArray(row.Internetseite); + + cipher.login.totp = this.getValueOrDefault(row["One-Time Passwort"]); + + this.importUnmappedFields(cipher, row, _mappedColumns); + + this.cleanupCipher(cipher); + result.ciphers.push(cipher); + }); + + if (this.organization) { + this.moveFoldersToCollections(result); + } + + result.success = true; + return Promise.resolve(result); + } + + private importUnmappedFields(cipher: CipherView, row: any, mappedValues: Set) { + const unmappedFields = Object.keys(row).filter((x) => !mappedValues.has(x)); + unmappedFields.forEach((key) => { + const item = row as any; + this.processKvp(cipher, key, item[key]); + }); + } +} diff --git a/libs/importer/src/importers/netwrix/netwrix-passwordsecure-csv-types.ts b/libs/importer/src/importers/netwrix/netwrix-passwordsecure-csv-types.ts new file mode 100644 index 00000000000..63a4255805e --- /dev/null +++ b/libs/importer/src/importers/netwrix/netwrix-passwordsecure-csv-types.ts @@ -0,0 +1,18 @@ +export class LoginRecord { + /** Organization unit / folder / collection */ + Organisationseinheit: string; + /** Tags? */ + DataTags: string; + /** Description/title */ + Beschreibung: string; + /** Username */ + Benutzername: string; + /** Password */ + Passwort: string; + /** URL */ + Internetseite: string; + /** Notes/additional information */ + Informationen: string; + /** TOTP */ + "One-Time Passwort": string; +} diff --git a/libs/importer/src/models/import-options.ts b/libs/importer/src/models/import-options.ts index 64546cc57b3..f656c728ffd 100644 --- a/libs/importer/src/models/import-options.ts +++ b/libs/importer/src/models/import-options.ts @@ -70,6 +70,7 @@ export const regularImportOptions = [ { id: "nordpasscsv", name: "Nordpass (csv)" }, { id: "psonojson", name: "Psono (json)" }, { id: "passkyjson", name: "Passky (json)" }, + { id: "netwrixpasswordsecure", name: "Netwrix Password Secure (csv)" }, ] as const; export type ImportType = diff --git a/libs/importer/src/services/import.service.ts b/libs/importer/src/services/import.service.ts index 17695c29d57..6bfc5d5ce99 100644 --- a/libs/importer/src/services/import.service.ts +++ b/libs/importer/src/services/import.service.ts @@ -54,6 +54,7 @@ import { MSecureCsvImporter, MeldiumCsvImporter, MykiCsvImporter, + NetwrixPasswordSecureCsvImporter, NordPassCsvImporter, OnePassword1PifImporter, OnePassword1PuxImporter, @@ -335,6 +336,8 @@ export class ImportService implements ImportServiceAbstraction { return new PasskyJsonImporter(); case "protonpass": return new ProtonPassJsonImporter(this.i18nService); + case "netwrixpasswordsecure": + return new NetwrixPasswordSecureCsvImporter(); default: return null; } From 40f2e15cecb1d15c21436a252a5a8581d504e46b Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Tue, 12 Nov 2024 10:38:48 -0500 Subject: [PATCH 03/74] [PM-13895] Autofocus on Vault Search Browser (#11888) * use appAutoFocus directive for browser vault v2 search --- .../vault-v2/vault-search/vault-v2-search.component.html | 1 + .../vault/popup/components/vault/vault-v2.component.html | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.html index 55674aa83e5..898d93da32c 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.html @@ -3,6 +3,7 @@ [placeholder]="'search' | i18n" [(ngModel)]="searchText" (ngModelChange)="onSearchTextChanged()" + appAutofocus > diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault/vault-v2.component.html index e402e131436..04b6bed469b 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault/vault-v2.component.html @@ -23,7 +23,11 @@ -
+
From aa04d84c11f8092077586352f4b2ca91b69c29e7 Mon Sep 17 00:00:00 2001 From: SHASHI KUMAR KASTURI <59004150+kshashikumar@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:47:25 -0500 Subject: [PATCH 04/74] [PM-14627] Import TOTP with ZohoVault CSV importer (#11912) * totp secret is assigned to cipher object in zohovalut-csv-importer to populate when importing keys from zoho vault fixes #11872 closes #11872 * fixed issue#11872 * assigned full totp url to cipher object and also implemented unit tests for zohovault importer * Add test to when no data is passed to the importer * Fix import of folders - Replace "Chambername" with "Folder Name" - Add tests for importing folders and collections --------- Co-authored-by: Daniel James Smith Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- .../zohovault/sample-zohovault-data.csv.ts | 5 ++ .../spec/zohovault-csv-importer.spec.ts | 88 +++++++++++++++++++ .../src/importers/zohovault-csv-importer.ts | 4 +- 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 libs/importer/spec/test-data/zohovault/sample-zohovault-data.csv.ts create mode 100644 libs/importer/spec/zohovault-csv-importer.spec.ts diff --git a/libs/importer/spec/test-data/zohovault/sample-zohovault-data.csv.ts b/libs/importer/spec/test-data/zohovault/sample-zohovault-data.csv.ts new file mode 100644 index 00000000000..a95178ffba3 --- /dev/null +++ b/libs/importer/spec/test-data/zohovault/sample-zohovault-data.csv.ts @@ -0,0 +1,5 @@ +export const data = `"Password Name","Description","Password URL","SecretData","Notes","CustomData","Tags","Classification","Favorite","login_totp","Folder Name" +XYZ Test,,https://abc.xyz.de:5001/#/login,"SecretType:Web Account +User Name:email@domain.de +Password:PcY_IQEXIjKGj8YW +",,"",,E,0,otpauth://totp?secret=PI2XO5TW0DF0SHTYOVZXOOBVHFEWM6JU&algorithm=SHA1&period=30&digits=6,folderName`; diff --git a/libs/importer/spec/zohovault-csv-importer.spec.ts b/libs/importer/spec/zohovault-csv-importer.spec.ts new file mode 100644 index 00000000000..28318945291 --- /dev/null +++ b/libs/importer/spec/zohovault-csv-importer.spec.ts @@ -0,0 +1,88 @@ +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; +import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; + +import { ZohoVaultCsvImporter } from "../src/importers"; + +import { data as samplezohovaultcsvdata } from "./test-data/zohovault/sample-zohovault-data.csv"; + +const CipherData = [ + { + title: "should parse Zoho Vault CSV format", + csv: samplezohovaultcsvdata, + expected: Object.assign(new CipherView(), { + id: null, + organizationId: null, + folderId: null, + name: "XYZ Test", + login: Object.assign(new LoginView(), { + username: "email@domain.de", + password: "PcY_IQEXIjKGj8YW", + uris: [ + Object.assign(new LoginUriView(), { + uri: "https://abc.xyz.de:5001/#/login", + }), + ], + totp: "otpauth://totp?secret=PI2XO5TW0DF0SHTYOVZXOOBVHFEWM6JU&algorithm=SHA1&period=30&digits=6", + }), + type: 1, + favorite: false, + }), + }, +]; + +describe("Zoho Vault CSV Importer", () => { + it("should not succeed given no data", async () => { + const importer = new ZohoVaultCsvImporter(); + const result = await importer.parse(""); + expect(result != null).toBe(true); + expect(result.success).toBe(false); + }); + + CipherData.forEach((data) => { + it(data.title, async () => { + const importer = new ZohoVaultCsvImporter(); + const result = await importer.parse(data.csv); + expect(result != null).toBe(true); + expect(result.ciphers.length).toBeGreaterThan(0); + + const cipher = result.ciphers.shift(); + let property: keyof typeof data.expected; + for (property in data.expected) { + // eslint-disable-next-line + if (data.expected.hasOwnProperty(property)) { + // eslint-disable-next-line + expect(cipher.hasOwnProperty(property)).toBe(true); + expect(cipher[property]).toEqual(data.expected[property]); + } + } + }); + }); + + it("should create folder and assign ciphers", async () => { + const importer = new ZohoVaultCsvImporter(); + const result = await importer.parse(samplezohovaultcsvdata); + expect(result != null).toBe(true); + expect(result.success).toBe(true); + expect(result.ciphers.length).toBeGreaterThan(0); + + const folder = result.folders.shift(); + expect(folder.name).toBe("folderName"); + + expect(result.folderRelationships[0]).toEqual([0, 0]); + }); + + it("should create collection and assign ciphers when importing into an organization", async () => { + const importer = new ZohoVaultCsvImporter(); + importer.organizationId = "someOrgId"; + const result = await importer.parse(samplezohovaultcsvdata); + expect(result != null).toBe(true); + expect(result.success).toBe(true); + expect(result.ciphers.length).toBeGreaterThan(0); + + const collection = result.collections.shift(); + expect(collection.name).toBe("folderName"); + + expect(result.collectionRelationships[0]).toEqual([0, 0]); + }); +}); diff --git a/libs/importer/src/importers/zohovault-csv-importer.ts b/libs/importer/src/importers/zohovault-csv-importer.ts index 6ae4b6a88a3..6a34179e868 100644 --- a/libs/importer/src/importers/zohovault-csv-importer.ts +++ b/libs/importer/src/importers/zohovault-csv-importer.ts @@ -13,7 +13,6 @@ export class ZohoVaultCsvImporter extends BaseImporter implements Importer { result.success = false; return Promise.resolve(result); } - results.forEach((value) => { if ( this.isNullOrWhitespace(value["Password Name"]) && @@ -21,7 +20,7 @@ export class ZohoVaultCsvImporter extends BaseImporter implements Importer { ) { return; } - this.processFolder(result, this.getValueOrDefault(value.ChamberName)); + this.processFolder(result, this.getValueOrDefault(value["Folder Name"])); const cipher = this.initLoginCipher(); cipher.favorite = this.getValueOrDefault(value.Favorite, "0") === "1"; cipher.notes = this.getValueOrDefault(value.Notes); @@ -32,6 +31,7 @@ export class ZohoVaultCsvImporter extends BaseImporter implements Importer { cipher.login.uris = this.makeUriArray( this.getValueOrDefault(value["Password URL"], this.getValueOrDefault(value["Secret URL"])), ); + cipher.login.totp = this.getValueOrDefault(value["login_totp"]); this.parseData(cipher, value.SecretData); this.parseData(cipher, value.CustomData); this.convertToNoteIfNeeded(cipher); From 9ec6f45803d94b7d09cf1171bf87370efa9a965b Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Tue, 12 Nov 2024 12:56:25 -0500 Subject: [PATCH 05/74] [PM-8682] Add Flags for New Device Verification Notice (#11968) --- libs/common/src/enums/feature-flag.enum.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index d36aea241d5..bb96e9b3ee1 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -40,6 +40,8 @@ export enum FeatureFlag { CriticalApps = "pm-14466-risk-insights-critical-application", TrialPaymentOptional = "PM-8163-trial-payment", SecurityTasks = "security-tasks", + NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss", + NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss", } export type AllowedFeatureFlagTypes = boolean | number | string; @@ -90,6 +92,8 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.CriticalApps]: FALSE, [FeatureFlag.TrialPaymentOptional]: FALSE, [FeatureFlag.SecurityTasks]: FALSE, + [FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE, + [FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE, } satisfies Record; export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue; From ea2f95e2268bd1a94e6b99b853b4aa51f228bc44 Mon Sep 17 00:00:00 2001 From: Chandra Mauli Sharma Date: Wed, 13 Nov 2024 01:07:44 +0530 Subject: [PATCH 06/74] fix: Add new item should set item type (bitwarden#10994) (#11049) Co-authored-by: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> --- apps/desktop/src/vault/app/vault/vault.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index b6357b35d7b..54d8eb833f4 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -428,7 +428,7 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - this.addType = type; + this.addType = type || this.activeFilter.cipherType; this.action = "add"; this.cipherId = null; this.prefillNewCipherFromFilter(); From e32bfce09447381f8bf415174048f1e3ef469b18 Mon Sep 17 00:00:00 2001 From: Jared McCannon Date: Tue, 12 Nov 2024 13:52:11 -0600 Subject: [PATCH 07/74] [PM-12479] Updating retrieval of groups (#11800) * Renamed group service to group api service * Updating models in various components. * Updating internal service name. clean up. --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Matt Bishop --- ...{group.service.ts => group-api.service.ts} | 28 ++++++++++--- .../organizations/core/services/index.ts | 2 +- .../core/views/add-edit-group-detail.ts | 10 +++++ .../core/views/group-details.view.ts | 20 ++++++++++ .../organizations/core/views/group.view.ts | 13 +------ .../organizations/core/views/index.ts | 1 + .../manage/group-add-edit.component.ts | 39 ++++++++++++------- .../organizations/manage/groups.component.ts | 6 +-- .../member-dialog/member-dialog.component.ts | 16 ++++---- .../members/members.component.ts | 4 +- .../collection-dialog.component.ts | 4 +- .../bulk-collections-dialog.component.ts | 4 +- .../app/vault/org-vault/vault.component.ts | 4 +- 13 files changed, 99 insertions(+), 52 deletions(-) rename apps/web/src/app/admin-console/organizations/core/services/group/{group.service.ts => group-api.service.ts} (78%) create mode 100644 apps/web/src/app/admin-console/organizations/core/views/add-edit-group-detail.ts create mode 100644 apps/web/src/app/admin-console/organizations/core/views/group-details.view.ts diff --git a/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts b/apps/web/src/app/admin-console/organizations/core/services/group/group-api.service.ts similarity index 78% rename from apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts rename to apps/web/src/app/admin-console/organizations/core/services/group/group-api.service.ts index e06a9aa8dc7..3b933ab9854 100644 --- a/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts +++ b/apps/web/src/app/admin-console/organizations/core/services/group/group-api.service.ts @@ -6,8 +6,10 @@ import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CoreOrganizationModule } from "../../core-organization.module"; +import { GroupDetailsView } from "../../views/group-details.view"; import { GroupView } from "../../views/group.view"; +import { AddEditGroupDetail } from "./../../views/add-edit-group-detail"; import { GroupRequest } from "./requests/group.request"; import { OrganizationGroupBulkRequest } from "./requests/organization-group-bulk.request"; import { GroupDetailsResponse, GroupResponse } from "./responses/group.response"; @@ -15,13 +17,13 @@ import { GroupDetailsResponse, GroupResponse } from "./responses/group.response" @Injectable({ providedIn: "root", }) -export class GroupService { +export class GroupApiService { constructor( protected apiService: ApiService, protected configService: ConfigService, ) {} - async get(orgId: string, groupId: string): Promise { + async get(orgId: string, groupId: string): Promise { const r = await this.apiService.send( "GET", "/organizations/" + orgId + "/groups/" + groupId + "/details", @@ -30,7 +32,7 @@ export class GroupService { true, ); - return GroupView.fromResponse(new GroupDetailsResponse(r)); + return GroupDetailsView.fromResponse(new GroupDetailsResponse(r)); } async getAll(orgId: string): Promise { @@ -44,12 +46,26 @@ export class GroupService { const listResponse = new ListResponse(r, GroupDetailsResponse); - return Promise.all(listResponse.data?.map((gr) => GroupView.fromResponse(gr))) ?? []; + return listResponse.data.map((gr) => GroupView.fromResponse(gr)); + } + + async getAllDetails(orgId: string): Promise { + const r = await this.apiService.send( + "GET", + "/organizations/" + orgId + "/groups/details", + null, + true, + true, + ); + + const listResponse = new ListResponse(r, GroupDetailsResponse); + + return listResponse.data.map((gr) => GroupDetailsView.fromResponse(gr)); } } @Injectable({ providedIn: CoreOrganizationModule }) -export class InternalGroupService extends GroupService { +export class InternalGroupApiService extends GroupApiService { constructor( protected apiService: ApiService, protected configService: ConfigService, @@ -77,7 +93,7 @@ export class InternalGroupService extends GroupService { ); } - async save(group: GroupView): Promise { + async save(group: AddEditGroupDetail): Promise { const request = new GroupRequest(); request.name = group.name; request.users = group.members; diff --git a/apps/web/src/app/admin-console/organizations/core/services/index.ts b/apps/web/src/app/admin-console/organizations/core/services/index.ts index 627cb2416ae..88cd6f8763c 100644 --- a/apps/web/src/app/admin-console/organizations/core/services/index.ts +++ b/apps/web/src/app/admin-console/organizations/core/services/index.ts @@ -1,2 +1,2 @@ -export * from "./group/group.service"; +export * from "./group/group-api.service"; export * from "./user-admin.service"; diff --git a/apps/web/src/app/admin-console/organizations/core/views/add-edit-group-detail.ts b/apps/web/src/app/admin-console/organizations/core/views/add-edit-group-detail.ts new file mode 100644 index 00000000000..83fe65c07a9 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/core/views/add-edit-group-detail.ts @@ -0,0 +1,10 @@ +import { CollectionAccessSelectionView } from "@bitwarden/admin-console/common"; + +export interface AddEditGroupDetail { + id: string; + organizationId: string; + name: string; + externalId: string; + collections: CollectionAccessSelectionView[]; + members: string[]; +} diff --git a/apps/web/src/app/admin-console/organizations/core/views/group-details.view.ts b/apps/web/src/app/admin-console/organizations/core/views/group-details.view.ts new file mode 100644 index 00000000000..efa6b9daf79 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/core/views/group-details.view.ts @@ -0,0 +1,20 @@ +import { CollectionAccessSelectionView } from "@bitwarden/admin-console/common"; +import { View } from "@bitwarden/common/models/view/view"; + +import { GroupDetailsResponse } from "../services/group/responses/group.response"; + +export class GroupDetailsView implements View { + id: string; + organizationId: string; + name: string; + externalId: string; + collections: CollectionAccessSelectionView[] = []; + + static fromResponse(response: GroupDetailsResponse): GroupDetailsView { + const view: GroupDetailsView = Object.assign(new GroupDetailsView(), response); + + view.collections = response.collections.map((c) => new CollectionAccessSelectionView(c)); + + return view; + } +} diff --git a/apps/web/src/app/admin-console/organizations/core/views/group.view.ts b/apps/web/src/app/admin-console/organizations/core/views/group.view.ts index 2566e4c4bfa..10ec61142ce 100644 --- a/apps/web/src/app/admin-console/organizations/core/views/group.view.ts +++ b/apps/web/src/app/admin-console/organizations/core/views/group.view.ts @@ -1,23 +1,14 @@ -import { CollectionAccessSelectionView } from "@bitwarden/admin-console/common"; import { View } from "@bitwarden/common/models/view/view"; -import { GroupDetailsResponse, GroupResponse } from "../services/group/responses/group.response"; +import { GroupResponse } from "../services/group/responses/group.response"; export class GroupView implements View { id: string; organizationId: string; name: string; externalId: string; - collections: CollectionAccessSelectionView[] = []; - members: string[] = []; static fromResponse(response: GroupResponse): GroupView { - const view: GroupView = Object.assign(new GroupView(), response) as GroupView; - - if (response instanceof GroupDetailsResponse && response.collections != undefined) { - view.collections = response.collections.map((c) => new CollectionAccessSelectionView(c)); - } - - return view; + return Object.assign(new GroupView(), response); } } diff --git a/apps/web/src/app/admin-console/organizations/core/views/index.ts b/apps/web/src/app/admin-console/organizations/core/views/index.ts index 9408d7757c3..847b2271766 100644 --- a/apps/web/src/app/admin-console/organizations/core/views/index.ts +++ b/apps/web/src/app/admin-console/organizations/core/views/index.ts @@ -1,3 +1,4 @@ export * from "./group.view"; +export * from "./group-details.view"; export * from "./organization-user.view"; export * from "./organization-user-admin-view"; diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index 643e76e4c38..c16b2e57241 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -30,7 +30,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { UserId } from "@bitwarden/common/types/guid"; import { DialogService, ToastService } from "@bitwarden/components"; -import { InternalGroupService as GroupService, GroupView } from "../core"; +import { InternalGroupApiService as GroupService } from "../core"; import { AccessItemType, AccessItemValue, @@ -40,6 +40,8 @@ import { PermissionMode, } from "../shared/components/access-selector"; +import { AddEditGroupDetail } from "./../core/views/add-edit-group-detail"; + /** * Indices for the available tabs in the dialog */ @@ -105,7 +107,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { title: string; collections: AccessItemView[] = []; members: Array = []; - group: GroupView; + group: AddEditGroupDetail; groupForm = this.formBuilder.group({ name: ["", [Validators.required, Validators.maxLength(100)]], @@ -149,7 +151,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { ); } - private groupDetails$: Observable = of(this.editMode).pipe( + private groupDetails$: Observable = of(this.editMode).pipe( concatMap((editMode) => { if (!editMode) { return of(undefined); @@ -159,9 +161,11 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { this.groupService.get(this.organizationId, this.groupId), this.apiService.getGroupUsers(this.organizationId, this.groupId), ]).pipe( - map(([groupView, users]) => { - groupView.members = users; - return groupView; + map(([groupView, users]): AddEditGroupDetail => { + return { + ...groupView, + members: users, + }; }), catchError((e: unknown) => { if (e instanceof ErrorResponse) { @@ -295,14 +299,16 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { return; } - const groupView = new GroupView(); - groupView.id = this.groupId; - groupView.organizationId = this.organizationId; - const formValue = this.groupForm.value; - groupView.name = formValue.name; - groupView.members = formValue.members?.map((m) => m.id) ?? []; - groupView.collections = formValue.collections.map((c) => convertToSelectionView(c)); + + const groupView: AddEditGroupDetail = { + id: this.groupId, + organizationId: this.organizationId, + name: formValue.name, + members: formValue.members?.map((m) => m.id) ?? [], + collections: formValue.collections.map((c) => convertToSelectionView(c)), + externalId: formValue.externalId, + }; await this.groupService.save(groupView); @@ -346,7 +352,10 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { /** * Maps the group's current collection access to AccessItemValues to populate the access-selector's FormControl */ -function mapToAccessSelections(group: GroupView, items: AccessItemView[]): AccessItemValue[] { +function mapToAccessSelections( + group: AddEditGroupDetail, + items: AccessItemView[], +): AccessItemValue[] { return ( group.collections // The FormControl value only represents editable collection access - exclude readonly access selections @@ -365,7 +374,7 @@ function mapToAccessSelections(group: GroupView, items: AccessItemView[]): Acces function mapToAccessItemViews( collections: CollectionAdminView[], organization: Organization, - group?: GroupView, + group?: AddEditGroupDetail, ): AccessItemView[] { return ( collections diff --git a/apps/web/src/app/admin-console/organizations/manage/groups.component.ts b/apps/web/src/app/admin-console/organizations/manage/groups.component.ts index 7d660682a2d..dd0cde70e67 100644 --- a/apps/web/src/app/admin-console/organizations/manage/groups.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/groups.component.ts @@ -28,7 +28,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { DialogService, TableDataSource, ToastService } from "@bitwarden/components"; -import { InternalGroupService as GroupService, GroupView } from "../core"; +import { GroupDetailsView, InternalGroupApiService as GroupService } from "../core"; import { GroupAddEditDialogResultType, @@ -40,7 +40,7 @@ type GroupDetailsRow = { /** * Details used for displaying group information */ - details: GroupView; + details: GroupDetailsView; /** * True if the group is selected in the table @@ -108,7 +108,7 @@ export class GroupsComponent { ), // groups this.refreshGroups$.pipe( - switchMap(() => this.groupService.getAll(this.organizationId)), + switchMap(() => this.groupService.getAllDetails(this.organizationId)), ), ]), ), diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 75c943fe579..04df2957f91 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -35,8 +35,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { DialogService, ToastService } from "@bitwarden/components"; import { - GroupService, - GroupView, + GroupApiService, + GroupDetailsView, OrganizationUserAdminView, UserAdminService, } from "../../../core"; @@ -144,7 +144,7 @@ export class MemberDialogComponent implements OnDestroy { private formBuilder: FormBuilder, // TODO: We should really look into consolidating naming conventions for these services private collectionAdminService: CollectionAdminService, - private groupService: GroupService, + private groupService: GroupApiService, private userService: UserAdminService, private organizationUserApiService: OrganizationUserApiService, private dialogService: DialogService, @@ -171,8 +171,8 @@ export class MemberDialogComponent implements OnDestroy { const groups$ = this.organization$.pipe( switchMap((organization) => organization.useGroups - ? this.groupService.getAll(this.params.organizationId) - : of([] as GroupView[]), + ? this.groupService.getAllDetails(this.params.organizationId) + : of([] as GroupDetailsView[]), ), ); @@ -278,7 +278,7 @@ export class MemberDialogComponent implements OnDestroy { private loadOrganizationUser( userDetails: OrganizationUserAdminView, - groups: GroupView[], + groups: GroupDetailsView[], collections: CollectionAdminView[], organization: Organization, ) { @@ -635,7 +635,7 @@ function mapCollectionToAccessItemView( collection: CollectionAdminView, organization: Organization, accessSelection?: CollectionAccessSelectionView, - group?: GroupView, + group?: GroupDetailsView, ): AccessItemView { return { type: AccessItemType.Collection, @@ -648,7 +648,7 @@ function mapCollectionToAccessItemView( }; } -function mapGroupToAccessItemView(group: GroupView): AccessItemView { +function mapGroupToAccessItemView(group: GroupDetailsView): AccessItemView { return { type: AccessItemType.Group, id: group.id, diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index f49fb13d6c8..f1cd505de0a 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -56,7 +56,7 @@ import { } from "../../../billing/organizations/change-plan-dialog.component"; import { BaseMembersComponent } from "../../common/base-members.component"; import { PeopleTableDataSource } from "../../common/people-table-data-source"; -import { GroupService } from "../core"; +import { GroupApiService } from "../core"; import { OrganizationUserView } from "../core/views/organization-user.view"; import { openEntityEventsDialog } from "../manage/entity-events.component"; @@ -129,7 +129,7 @@ export class MembersComponent extends BaseMembersComponent private organizationApiService: OrganizationApiServiceAbstraction, private organizationUserApiService: OrganizationUserApiService, private router: Router, - private groupService: GroupService, + private groupService: GroupApiService, private collectionService: CollectionService, private billingApiService: BillingApiServiceAbstraction, private modalService: ModalService, diff --git a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts index a51d408bc74..514d0f8b625 100644 --- a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts @@ -29,7 +29,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { Utils } from "@bitwarden/common/platform/misc/utils"; import { BitValidators, DialogService } from "@bitwarden/components"; -import { GroupService, GroupView } from "../../../admin-console/organizations/core"; +import { GroupApiService, GroupView } from "../../../admin-console/organizations/core"; import { PermissionMode } from "../../../admin-console/organizations/shared/components/access-selector/access-selector.component"; import { AccessItemType, @@ -101,7 +101,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private dialogRef: DialogRef, private organizationService: OrganizationService, - private groupService: GroupService, + private groupService: GroupApiService, private collectionAdminService: CollectionAdminService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, diff --git a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts b/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts index c8c9b83efcb..f86a321dfbd 100644 --- a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts +++ b/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts @@ -14,7 +14,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService } from "@bitwarden/components"; -import { GroupService, GroupView } from "../../../admin-console/organizations/core"; +import { GroupApiService, GroupView } from "../../../admin-console/organizations/core"; import { AccessItemType, AccessItemValue, @@ -61,7 +61,7 @@ export class BulkCollectionsDialogComponent implements OnDestroy { private dialogRef: DialogRef, private formBuilder: FormBuilder, private organizationService: OrganizationService, - private groupService: GroupService, + private groupService: GroupApiService, private organizationUserApiService: OrganizationUserApiService, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 743e080befe..dee584f3a42 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -81,7 +81,7 @@ import { PasswordRepromptService, } from "@bitwarden/vault"; -import { GroupService, GroupView } from "../../admin-console/organizations/core"; +import { GroupApiService, GroupView } from "../../admin-console/organizations/core"; import { openEntityEventsDialog } from "../../admin-console/organizations/manage/entity-events.component"; import { TrialFlowService } from "../../billing/services/trial-flow.service"; import { FreeTrial } from "../../core/types/free-trial"; @@ -234,7 +234,7 @@ export class VaultComponent implements OnInit, OnDestroy { private collectionAdminService: CollectionAdminService, private searchService: SearchService, private searchPipe: SearchPipe, - private groupService: GroupService, + private groupService: GroupApiService, private logService: LogService, private eventCollectionService: EventCollectionService, private totpService: TotpService, From d40dedf2b31487935ea01cef50e17246975939ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:00:07 -0500 Subject: [PATCH 08/74] [deps] Platform: Update @types/node to v22 (#11951) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../package-lock.json | 10 +++++----- .../native-messaging-test-runner/package.json | 2 +- package-lock.json | 20 ++++++++++++++----- package.json | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index f57f067907a..f51c0a32d9d 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -18,7 +18,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "20.17.1", + "@types/node": "22.9.0", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" } @@ -106,12 +106,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.1.tgz", - "integrity": "sha512-j2VlPv1NnwPJbaCNv69FO/1z4lId0QmGvpT41YxitRtWlg96g/j8qcv2RKsLKe2F6OJgyXhupN1Xo17b2m139Q==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/node-ipc": { diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index ed2c4bb29cf..23b1dbdafba 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -23,7 +23,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "20.17.1", + "@types/node": "22.9.0", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" }, diff --git a/package-lock.json b/package-lock.json index e71e8c387d7..d1c86b07071 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,7 +111,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "20.17.1", + "@types/node": "22.9.0", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", @@ -9770,13 +9770,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.1.tgz", - "integrity": "sha512-j2VlPv1NnwPJbaCNv69FO/1z4lId0QmGvpT41YxitRtWlg96g/j8qcv2RKsLKe2F6OJgyXhupN1Xo17b2m139Q==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/node-fetch": { @@ -16588,6 +16588,16 @@ "node": ">=10" } }, + "node_modules/electron/node_modules/@types/node": { + "version": "20.17.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.6.tgz", + "integrity": "sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, "node_modules/emitter-component": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.2.tgz", diff --git a/package.json b/package.json index 282a63f2351..1cd370b9778 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "20.17.1", + "@types/node": "22.9.0", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", From c49679207bb8418dd3da3514bba07c395671557d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:03:53 -0500 Subject: [PATCH 09/74] [deps] Autofill: Update concurrently to v9.1.0 (#11949) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d1c86b07071..84ff0b47c1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -128,7 +128,7 @@ "base64-loader": "1.0.0", "browserslist": "4.23.2", "chromatic": "11.10.2", - "concurrently": "9.0.1", + "concurrently": "9.1.0", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "7.1.2", @@ -14538,9 +14538,9 @@ } }, "node_modules/concurrently": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz", - "integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.0.tgz", + "integrity": "sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1cd370b9778..7e70df5b515 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "base64-loader": "1.0.0", "browserslist": "4.23.2", "chromatic": "11.10.2", - "concurrently": "9.0.1", + "concurrently": "9.1.0", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "7.1.2", From 031a9bcae810d7dad9bcf0301587c3721197a728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Wed, 13 Nov 2024 13:02:34 +0100 Subject: [PATCH 10/74] Bump browser version (#11980) --- apps/browser/package.json | 2 +- apps/browser/src/manifest.json | 2 +- apps/browser/src/manifest.v3.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index 4a749522545..f79d646d851 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2024.11.0", + "version": "2024.11.1", "scripts": { "build": "cross-env MANIFEST_VERSION=3 webpack", "build:mv2": "webpack", diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 0d9a4189578..46aa3cefd70 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2024.11.0", + "version": "2024.11.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index f805b701551..8118c212fc7 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2024.11.0", + "version": "2024.11.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", From 84b2b02f127dcd6db0aefe55e55c676bdb5f8ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Wed, 13 Nov 2024 13:14:52 +0100 Subject: [PATCH 11/74] Bump browser version in package-lock.json (#11981) --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 84ff0b47c1d..e2b5e336ffb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -194,7 +194,7 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2024.11.0" + "version": "2024.11.1" }, "apps/cli": { "name": "@bitwarden/cli", From 24ca942cd6f3f9809c0caac77ff481641d4f87a4 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:28:40 +0100 Subject: [PATCH 12/74] [PM-14861]Vault items fail to load (#11974) * Resolve the vault items fail to load * Remove the hasSubscription * Replace with hasSubscription from metadata * Resolve the failing popup --- .../vault/individual-vault/vault.component.ts | 20 +++++++++++++++---- .../app/vault/org-vault/vault.component.ts | 17 ++++++++++++---- .../organization-billing-metadata.response.ts | 2 ++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 688cbfbf9f1..ce61cd51fcd 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -16,6 +16,7 @@ import { from, lastValueFrom, Observable, + of, Subject, } from "rxjs"; import { @@ -184,12 +185,17 @@ export class VaultComponent implements OnInit, OnDestroy { private refresh$ = new BehaviorSubject(null); private destroy$ = new Subject(); private extensionRefreshEnabled: boolean; + private hasSubscription$ = new BehaviorSubject(false); private vaultItemDialogRef?: DialogRef | undefined; private readonly unpaidSubscriptionDialog$ = this.organizationService.organizations$.pipe( filter((organizations) => organizations.length === 1), - switchMap(([organization]) => + map(([organization]) => organization), + switchMap((organization) => from(this.billingApiService.getOrganizationBillingMetadata(organization.id)).pipe( + tap((organizationMetaData) => { + this.hasSubscription$.next(organizationMetaData.hasSubscription); + }), switchMap((organizationMetaData) => from( this.trialFlowService.handleUnpaidSubscriptionDialog( @@ -417,11 +423,17 @@ export class VaultComponent implements OnInit, OnDestroy { this.unpaidSubscriptionDialog$.pipe(takeUntil(this.destroy$)).subscribe(); - const organizationsPaymentStatus$ = this.organizationService.organizations$.pipe( - switchMap((allOrganizations) => { + const organizationsPaymentStatus$ = combineLatest([ + this.organizationService.organizations$, + this.hasSubscription$, + ]).pipe( + switchMap(([allOrganizations, hasSubscription]) => { + if (!allOrganizations || allOrganizations.length === 0 || !hasSubscription) { + return of([]); + } return combineLatest( allOrganizations - .filter((org) => org.isOwner) + .filter((org) => org.isOwner && hasSubscription) .map((org) => combineLatest([ this.organizationApiService.getSubscription(org.id), diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index dee584f3a42..64318047b9e 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -178,6 +178,7 @@ export class VaultComponent implements OnInit, OnDestroy { protected selectedCollection: TreeNode | undefined; protected isEmpty: boolean; protected showCollectionAccessRestricted: boolean; + private hasSubscription$ = new BehaviorSubject(false); protected currentSearchText$: Observable; protected freeTrial$: Observable; /** @@ -197,10 +198,15 @@ export class VaultComponent implements OnInit, OnDestroy { protected addAccessStatus$ = new BehaviorSubject(0); private extensionRefreshEnabled: boolean; private vaultItemDialogRef?: DialogRef | undefined; + private readonly unpaidSubscriptionDialog$ = this.organizationService.organizations$.pipe( filter((organizations) => organizations.length === 1), - switchMap(([organization]) => + map(([organization]) => organization), + switchMap((organization) => from(this.billingApiService.getOrganizationBillingMetadata(organization.id)).pipe( + tap((organizationMetaData) => { + this.hasSubscription$.next(organizationMetaData.hasSubscription); + }), switchMap((organizationMetaData) => from( this.trialFlowService.handleUnpaidSubscriptionDialog( @@ -580,9 +586,12 @@ export class VaultComponent implements OnInit, OnDestroy { this.unpaidSubscriptionDialog$.pipe(takeUntil(this.destroy$)).subscribe(); - this.freeTrial$ = organization$.pipe( - filter((org) => org.isOwner), - switchMap((org) => + this.freeTrial$ = combineLatest([ + organization$, + this.hasSubscription$.pipe(filter((hasSubscription) => hasSubscription !== null)), + ]).pipe( + filter(([org, hasSubscription]) => org.isOwner && hasSubscription), + switchMap(([org]) => combineLatest([ of(org), this.organizationApiService.getSubscription(org.id), diff --git a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts index ae6d1ac92c1..d9733aa80f2 100644 --- a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts +++ b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts @@ -5,6 +5,7 @@ export class OrganizationBillingMetadataResponse extends BaseResponse { isManaged: boolean; isOnSecretsManagerStandalone: boolean; isSubscriptionUnpaid: boolean; + hasSubscription: boolean; constructor(response: any) { super(response); @@ -12,5 +13,6 @@ export class OrganizationBillingMetadataResponse extends BaseResponse { this.isManaged = this.getResponseProperty("IsManaged"); this.isOnSecretsManagerStandalone = this.getResponseProperty("IsOnSecretsManagerStandalone"); this.isSubscriptionUnpaid = this.getResponseProperty("IsSubscriptionUnpaid"); + this.hasSubscription = this.getResponseProperty("HasSubscription"); } } From 3508b4631dd2ff0c5ddc1f6a38df142998dab669 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 13 Nov 2024 14:06:57 +0100 Subject: [PATCH 13/74] Fix snap protocol handler (#11932) Co-authored-by: Matt Bishop --- apps/desktop/resources/memory-dump-wrapper.sh | 6 ++++++ apps/desktop/src/main/window.main.ts | 12 ++---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/desktop/resources/memory-dump-wrapper.sh b/apps/desktop/resources/memory-dump-wrapper.sh index b62c050683a..6737cc312f4 100644 --- a/apps/desktop/resources/memory-dump-wrapper.sh +++ b/apps/desktop/resources/memory-dump-wrapper.sh @@ -7,6 +7,12 @@ ulimit -c 0 RAW_PATH=$(readlink -f "$0") APP_PATH=$(dirname $RAW_PATH) +# force use of base image libdus in snap +if [ -e "/usr/lib/x86_64-linux-gnu/libdbus-1.so.3" ] +then + export LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libdbus-1.so.3" +fi + # pass through all args $APP_PATH/bitwarden-app "$@" diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index 6d42e519d82..fb9ab033cf4 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -12,15 +12,7 @@ import { BiometricStateService } from "@bitwarden/key-management"; import { WindowState } from "../platform/models/domain/window-state"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; -import { - cleanUserAgent, - isDev, - isLinux, - isMac, - isMacAppStore, - isSnapStore, - isWindows, -} from "../utils"; +import { cleanUserAgent, isDev, isLinux, isMac, isMacAppStore, isWindows } from "../utils"; const mainWindowSizeKey = "mainWindowSize"; const WindowEventHandlingDelay = 100; @@ -84,7 +76,7 @@ export class WindowMain { return new Promise((resolve, reject) => { try { - if (!isMacAppStore() && !isSnapStore()) { + if (!isMacAppStore()) { const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); From 88cbee9b0aeb1ad6b06ba997704a8b1ce3558020 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Wed, 13 Nov 2024 09:01:22 -0500 Subject: [PATCH 14/74] Remove consolidated billing feature flag (#11969) --- .../layouts/organization-layout.component.ts | 13 ++----- .../guards/organization-is-unmanaged.guard.ts | 11 ------ ...ganization-subscription-cloud.component.ts | 8 +---- .../providers/clients/clients.component.ts | 11 +++--- .../providers/providers-layout.component.html | 2 +- .../providers/providers-layout.component.ts | 12 +++---- .../providers/setup/setup.component.html | 6 ++-- .../providers/setup/setup.component.ts | 36 +++++++------------ .../clients/manage-clients.component.ts | 11 +++--- .../guards/has-consolidated-billing.guard.ts | 13 +------ libs/common/src/billing/abstractions/index.ts | 1 - .../provider-billing.service.abstraction.ts | 23 ------------ libs/common/src/enums/feature-flag.enum.ts | 2 -- 13 files changed, 34 insertions(+), 115 deletions(-) delete mode 100644 libs/common/src/billing/abstractions/provider-billing.service.abstraction.ts diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts index 6813a59ce41..c4ed44ab0cb 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts @@ -55,10 +55,6 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { private _destroy = new Subject(); - protected consolidatedBillingEnabled$ = this.configService.getFeatureFlag$( - FeatureFlag.EnableConsolidatedBilling, - ); - constructor( private route: ActivatedRoute, private organizationService: OrganizationService, @@ -101,14 +97,9 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { switchMap((organization) => this.providerService.get$(organization.providerId)), ); - this.organizationIsUnmanaged$ = combineLatest([ - this.consolidatedBillingEnabled$, - this.organization$, - provider$, - ]).pipe( + this.organizationIsUnmanaged$ = combineLatest([this.organization$, provider$]).pipe( map( - ([consolidatedBillingEnabled, organization, provider]) => - !consolidatedBillingEnabled || + ([organization, provider]) => !organization.hasProvider || !provider || provider.providerStatus !== ProviderStatusType.Billable, diff --git a/apps/web/src/app/billing/guards/organization-is-unmanaged.guard.ts b/apps/web/src/app/billing/guards/organization-is-unmanaged.guard.ts index a915d8f8a6c..0f6baa5f322 100644 --- a/apps/web/src/app/billing/guards/organization-is-unmanaged.guard.ts +++ b/apps/web/src/app/billing/guards/organization-is-unmanaged.guard.ts @@ -4,22 +4,11 @@ import { ActivatedRouteSnapshot, CanActivateFn } from "@angular/router"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderStatusType } from "@bitwarden/common/admin-console/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; export const organizationIsUnmanaged: CanActivateFn = async (route: ActivatedRouteSnapshot) => { - const configService = inject(ConfigService); const organizationService = inject(OrganizationService); const providerService = inject(ProviderService); - const consolidatedBillingEnabled = await configService.getFeatureFlag( - FeatureFlag.EnableConsolidatedBilling, - ); - - if (!consolidatedBillingEnabled) { - return true; - } - const organization = await organizationService.get(route.params.organizationId); if (!organization.hasProvider) { diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index d4d11d91e01..f5cc89c86b6 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -60,10 +60,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy protected readonly subscriptionHiddenIcon = SubscriptionHiddenIcon; protected readonly teamsStarter = ProductTierType.TeamsStarter; - protected enableConsolidatedBilling$ = this.configService.getFeatureFlag$( - FeatureFlag.EnableConsolidatedBilling, - ); - protected enableUpgradePasswordManagerSub$ = this.configService.getFeatureFlag$( FeatureFlag.EnableUpgradePasswordManagerSub, ); @@ -124,8 +120,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy this.locale = await firstValueFrom(this.i18nService.locale$); this.userOrg = await this.organizationService.get(this.organizationId); - const consolidatedBillingEnabled = await firstValueFrom(this.enableConsolidatedBilling$); - const isIndependentOrganizationOwner = !this.userOrg.hasProvider && this.userOrg.isOwner; const isResoldOrganizationOwner = this.userOrg.hasReseller && this.userOrg.isOwner; const isMSPUser = this.userOrg.hasProvider && this.userOrg.isProviderUser; @@ -135,7 +129,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy ); this.organizationIsManagedByConsolidatedBillingMSP = - consolidatedBillingEnabled && this.userOrg.hasProvider && metadata.isManaged; + this.userOrg.hasProvider && metadata.isManaged; this.showSubscription = isIndependentOrganizationOwner || diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts index 88f125a65a1..e397c7ed8e1 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts @@ -8,11 +8,9 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; +import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { hasConsolidatedBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction"; import { PlanType } from "@bitwarden/common/billing/enums"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { DialogService, ToastService } from "@bitwarden/components"; @@ -46,7 +44,6 @@ export class ClientsComponent extends BaseClientsComponent implements OnInit, On private apiService: ApiService, private organizationService: OrganizationService, private organizationApiService: OrganizationApiServiceAbstraction, - private configService: ConfigService, activatedRoute: ActivatedRoute, dialogService: DialogService, i18nService: I18nService, @@ -72,9 +69,9 @@ export class ClientsComponent extends BaseClientsComponent implements OnInit, On switchMap((params) => { this.providerId = params.providerId; return this.providerService.get$(this.providerId).pipe( - hasConsolidatedBilling(this.configService), - map((hasConsolidatedBilling) => { - if (hasConsolidatedBilling) { + map((provider) => provider?.providerStatus === ProviderStatusType.Billable), + map((isBillable) => { + if (isBillable) { return from( this.router.navigate(["../manage-client-organizations"], { relativeTo: this.activatedRoute, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html index 0536221cafd..bd21e70a07d 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html @@ -5,7 +5,7 @@ (); protected provider$: Observable; - protected hasConsolidatedBilling$: Observable; + protected isBillable: Observable; protected canAccessBilling$: Observable; protected showProviderClientVaultPrivacyWarningBanner$ = this.configService.getFeatureFlag$( @@ -58,12 +58,12 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy { takeUntil(this.destroy$), ); - this.hasConsolidatedBilling$ = this.provider$.pipe( - hasConsolidatedBilling(this.configService), + this.isBillable = this.provider$.pipe( + map((provider) => provider?.providerStatus === ProviderStatusType.Billable), takeUntil(this.destroy$), ); - this.canAccessBilling$ = combineLatest([this.hasConsolidatedBilling$, this.provider$]).pipe( + this.canAccessBilling$ = combineLatest([this.isBillable, this.provider$]).pipe( map( ([hasConsolidatedBilling, provider]) => hasConsolidatedBilling && provider.isProviderAdmin, ), diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html index 482b85b7127..33a20444c2b 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html @@ -24,13 +24,11 @@ {{ "billingEmail" | i18n }} - {{ - "providerBillingEmailHint" | i18n - }} + {{ "providerBillingEmailHint" | i18n }}
- + diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index aaad0ce4578..72d954e8cdc 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -1,14 +1,13 @@ import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, Subject, switchMap } from "rxjs"; +import { Subject, switchMap } from "rxjs"; import { first, takeUntil } from "rxjs/operators"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request"; import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; @@ -34,10 +33,6 @@ export class SetupComponent implements OnInit, OnDestroy { billingEmail: ["", [Validators.required, Validators.email]], }); - protected enableConsolidatedBilling$ = this.configService.getFeatureFlag$( - FeatureFlag.EnableConsolidatedBilling, - ); - private destroy$ = new Subject(); constructor( @@ -112,13 +107,9 @@ export class SetupComponent implements OnInit, OnDestroy { submit = async () => { try { - const consolidatedBillingEnabled = await firstValueFrom(this.enableConsolidatedBilling$); - this.formGroup.markAllAsTouched(); - const formIsValid = consolidatedBillingEnabled - ? this.formGroup.valid && this.manageTaxInformationComponent.touch() - : this.formGroup.valid; + const formIsValid = this.formGroup.valid && this.manageTaxInformationComponent.touch(); if (!formIsValid) { return; @@ -133,19 +124,18 @@ export class SetupComponent implements OnInit, OnDestroy { request.token = this.token; request.key = key; - if (consolidatedBillingEnabled) { - request.taxInfo = new ExpandedTaxInfoUpdateRequest(); - const taxInformation = this.manageTaxInformationComponent.getTaxInformation(); + request.taxInfo = new ExpandedTaxInfoUpdateRequest(); + const taxInformation = this.manageTaxInformationComponent.getTaxInformation(); - request.taxInfo.country = taxInformation.country; - request.taxInfo.postalCode = taxInformation.postalCode; - if (taxInformation.includeTaxId) { - request.taxInfo.taxId = taxInformation.taxId; - request.taxInfo.line1 = taxInformation.line1; - request.taxInfo.line2 = taxInformation.line2; - request.taxInfo.city = taxInformation.city; - request.taxInfo.state = taxInformation.state; - } + request.taxInfo.country = taxInformation.country; + request.taxInfo.postalCode = taxInformation.postalCode; + + if (taxInformation.includeTaxId) { + request.taxInfo.taxId = taxInformation.taxId; + request.taxInfo.line1 = taxInformation.line1; + request.taxInfo.line2 = taxInformation.line2; + request.taxInfo.city = taxInformation.city; + request.taxInfo.state = taxInformation.state; } const provider = await this.providerApiService.postProviderSetup(this.providerId, request); diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts index a870b053dba..b41702e47a3 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts @@ -6,13 +6,11 @@ import { switchMap } from "rxjs/operators"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; +import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; -import { hasConsolidatedBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { DialogService, ToastService } from "@bitwarden/components"; @@ -47,7 +45,6 @@ export class ManageClientsComponent extends BaseClientsComponent { constructor( private billingApiService: BillingApiService, - private configService: ConfigService, private providerService: ProviderService, private router: Router, activatedRoute: ActivatedRoute, @@ -73,9 +70,9 @@ export class ManageClientsComponent extends BaseClientsComponent { switchMap((params) => { this.providerId = params.providerId; return this.providerService.get$(this.providerId).pipe( - hasConsolidatedBilling(this.configService), - map((hasConsolidatedBilling) => { - if (!hasConsolidatedBilling) { + map((provider) => provider?.providerStatus === ProviderStatusType.Billable), + map((isBillable) => { + if (!isBillable) { return from( this.router.navigate(["../clients"], { relativeTo: this.activatedRoute, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/guards/has-consolidated-billing.guard.ts b/bitwarden_license/bit-web/src/app/billing/providers/guards/has-consolidated-billing.guard.ts index 213b9a53681..60dbf4b3b82 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/guards/has-consolidated-billing.guard.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/guards/has-consolidated-billing.guard.ts @@ -4,24 +4,13 @@ import { firstValueFrom } from "rxjs"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderStatusType } from "@bitwarden/common/admin-console/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; export const hasConsolidatedBilling: CanActivateFn = async (route: ActivatedRouteSnapshot) => { - const configService = inject(ConfigService); const providerService = inject(ProviderService); const provider = await firstValueFrom(providerService.get$(route.params.providerId)); - const consolidatedBillingEnabled = await configService.getFeatureFlag( - FeatureFlag.EnableConsolidatedBilling, - ); - - if ( - !consolidatedBillingEnabled || - !provider || - provider.providerStatus !== ProviderStatusType.Billable - ) { + if (!provider || provider.providerStatus !== ProviderStatusType.Billable) { return createUrlTreeFromSnapshot(route, ["/providers", route.params.providerId]); } diff --git a/libs/common/src/billing/abstractions/index.ts b/libs/common/src/billing/abstractions/index.ts index 7ed0f1251af..3f72cd9d2c0 100644 --- a/libs/common/src/billing/abstractions/index.ts +++ b/libs/common/src/billing/abstractions/index.ts @@ -1,4 +1,3 @@ export * from "./account/billing-account-profile-state.service"; export * from "./billing-api.service.abstraction"; export * from "./organization-billing.service"; -export * from "./provider-billing.service.abstraction"; diff --git a/libs/common/src/billing/abstractions/provider-billing.service.abstraction.ts b/libs/common/src/billing/abstractions/provider-billing.service.abstraction.ts deleted file mode 100644 index aa8568d8e9c..00000000000 --- a/libs/common/src/billing/abstractions/provider-billing.service.abstraction.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { map, Observable, OperatorFunction, switchMap } from "rxjs"; - -import { ProviderStatusType } from "@bitwarden/common/admin-console/enums"; -import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -type MaybeProvider = Provider | undefined; - -export const hasConsolidatedBilling = ( - configService: ConfigService, -): OperatorFunction => - switchMap>((provider) => - configService - .getFeatureFlag$(FeatureFlag.EnableConsolidatedBilling) - .pipe( - map((consolidatedBillingEnabled) => - provider - ? provider.providerStatus === ProviderStatusType.Billable && consolidatedBillingEnabled - : false, - ), - ), - ); diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index bb96e9b3ee1..a12d05e99bc 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -7,7 +7,6 @@ export enum FeatureFlag { BrowserFilelessImport = "browser-fileless-import", ItemShare = "item-share", GeneratorToolsModernization = "generator-tools-modernization", - EnableConsolidatedBilling = "enable-consolidated-billing", AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section", ExtensionRefresh = "extension-refresh", PersistPopupView = "persist-popup-view", @@ -59,7 +58,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.BrowserFilelessImport]: FALSE, [FeatureFlag.ItemShare]: FALSE, [FeatureFlag.GeneratorToolsModernization]: FALSE, - [FeatureFlag.EnableConsolidatedBilling]: FALSE, [FeatureFlag.AC1795_UpdatedSubscriptionStatusSection]: FALSE, [FeatureFlag.ExtensionRefresh]: FALSE, [FeatureFlag.PersistPopupView]: FALSE, From 913f109ffae61c840d576f550a85713c99a075be Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:11:53 +0100 Subject: [PATCH 15/74] Fix totp import on Dashlane csv importer (#11747) Co-authored-by: Daniel James Smith --- libs/importer/spec/dashlane-csv-importer.spec.ts | 9 +++++++++ .../test-data/dashlane-csv/credentials-otpurl.csv.ts | 2 ++ .../src/importers/dashlane/dashlane-csv-importer.ts | 2 +- .../src/importers/dashlane/types/dashlane-csv-types.ts | 3 ++- 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 libs/importer/spec/test-data/dashlane-csv/credentials-otpurl.csv.ts diff --git a/libs/importer/spec/dashlane-csv-importer.spec.ts b/libs/importer/spec/dashlane-csv-importer.spec.ts index e76a4d4cb3b..1d76396022c 100644 --- a/libs/importer/spec/dashlane-csv-importer.spec.ts +++ b/libs/importer/spec/dashlane-csv-importer.spec.ts @@ -2,6 +2,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { DashlaneCsvImporter } from "../src/importers"; +import { credentialsData_otpUrl } from "./test-data/dashlane-csv/credentials-otpurl.csv"; import { credentialsData } from "./test-data/dashlane-csv/credentials.csv"; import { identityData } from "./test-data/dashlane-csv/id.csv"; import { multiplePersonalInfoData } from "./test-data/dashlane-csv/multiple-personal-info.csv"; @@ -30,6 +31,14 @@ describe("Dashlane CSV Importer", () => { expect(cipher.notes).toEqual("some note for example.com"); }); + it("should parse login with totp when given otpUrl instead of otpSecret", async () => { + const result = await importer.parse(credentialsData_otpUrl); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.login.totp).toEqual("anotherTOTPSeed"); + }); + it("should parse an item and create a folder", async () => { const result = await importer.parse(credentialsData); diff --git a/libs/importer/spec/test-data/dashlane-csv/credentials-otpurl.csv.ts b/libs/importer/spec/test-data/dashlane-csv/credentials-otpurl.csv.ts new file mode 100644 index 00000000000..9506a98bad0 --- /dev/null +++ b/libs/importer/spec/test-data/dashlane-csv/credentials-otpurl.csv.ts @@ -0,0 +1,2 @@ +export const credentialsData_otpUrl = `username,username2,username3,title,password,note,url,category,otpUrl +jdoe,,,example.com,somePassword,some note for example.com,https://www.example.com,Entertainment,anotherTOTPSeed`; diff --git a/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts b/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts index 888aeb823e8..94bc23c5fa8 100644 --- a/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts +++ b/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts @@ -119,7 +119,7 @@ export class DashlaneCsvImporter extends BaseImporter implements Importer { cipher.notes = row.note; cipher.login.username = row.username; cipher.login.password = row.password; - cipher.login.totp = row.otpSecret; + cipher.login.totp = Object.keys(row).includes("otpUrl") ? row.otpUrl : row.otpSecret; cipher.login.uris = this.makeUriArray(row.url); this.importUnmappedFields(cipher, row, _mappedCredentialsColumns); diff --git a/libs/importer/src/importers/dashlane/types/dashlane-csv-types.ts b/libs/importer/src/importers/dashlane/types/dashlane-csv-types.ts index cb321c56da8..c17aa8bf0e1 100644 --- a/libs/importer/src/importers/dashlane/types/dashlane-csv-types.ts +++ b/libs/importer/src/importers/dashlane/types/dashlane-csv-types.ts @@ -8,7 +8,8 @@ export class CredentialsRecord { note: string; url: string; category: string; - otpSecret: string; + otpSecret?: string; + otpUrl?: string; // Likely introduced by Dashlane as a replacement for `otpSecret` } export class PaymentsRecord { From e341a66a2ed3347106b39249ec6e718f31da63cd Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:15:48 +0100 Subject: [PATCH 16/74] Add autofocus to search field on Send page (#11979) Co-authored-by: Daniel James Smith --- apps/browser/src/tools/popup/send-v2/send-v2.component.html | 2 +- .../send/send-ui/src/send-search/send-search.component.html | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.html b/apps/browser/src/tools/popup/send-v2/send-v2.component.html index 23cc692a598..c0178143329 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.html +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.html @@ -6,7 +6,7 @@ -
+
{{ "sendDisabledWarning" | i18n }} diff --git a/libs/tools/send/send-ui/src/send-search/send-search.component.html b/libs/tools/send/send-ui/src/send-search/send-search.component.html index 55674aa83e5..898d93da32c 100644 --- a/libs/tools/send/send-ui/src/send-search/send-search.component.html +++ b/libs/tools/send/send-ui/src/send-search/send-search.component.html @@ -3,6 +3,7 @@ [placeholder]="'search' | i18n" [(ngModel)]="searchText" (ngModelChange)="onSearchTextChanged()" + appAutofocus >
From 62112b99a9e59bd5b7de81e2987bb1568430f38d Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 13 Nov 2024 15:54:35 +0100 Subject: [PATCH 17/74] [PM-9022] scaffold the extension and build pipeline (#9948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add macos xcode project * feat: add extension to mas build * feat: use `after-sign` to avoid issues Electron builder modifies the .plist in the extension which causes issues with the signing process. Copying and re-signing manually avoids this because it bypasses the electron builder for the extension * feat: always clean build and add better error handling * chore: add some logging to after-sign * feat: automatically cleanup xcode build to avoid duplicate extensions * docs: add information about managing extensions * feat: add missing safari extension logging * lint: allow macos filenames * chore: add macos to platform ownership * lint: add some additional allowed files * feat: don't build autofill extension for MAS * chore: ignore capital letters linting for all macos files * chore: replace gulpfile with regular node script * chore: add lint rules to script * lint: fix remaining lint issues in script * chore: tweak lint rule * feat: remove desktop target * fix: use new provisioning profile for dev extension * Update to unblock CI builds * chore: remove extension from masdev pack This way we don't include the extension in any build and can avoid the signing issues it brings * chore: add autofill as codeowner * chore: remove xcuserdata * chore: ignore xcuserdata --------- Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> Co-authored-by: Michał Chęciński Co-authored-by: Matt Bishop --- .github/CODEOWNERS | 2 + .github/workflows/build-desktop.yml | 20 +- .github/workflows/lint.yml | 1 + apps/desktop/.gitignore | 1 + apps/desktop/macos/README.md | 23 ++ .../CredentialProviderViewController.xib | 69 ++++ .../CredentialProviderViewController.swift | 93 +++++ .../macos/autofill-extension/Info.plist | 23 ++ .../autofill_extension.entitlements | 10 + .../macos/desktop.xcodeproj/project.pbxproj | 367 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + apps/desktop/macos/production.xcconfig | 11 + apps/desktop/package.json | 2 + apps/desktop/resources/entitlements.mac.plist | 2 + .../resources/entitlements.mas.inherit.plist | 2 + apps/desktop/resources/entitlements.mas.plist | 2 + apps/desktop/scripts/after-sign.js | 68 +++- apps/desktop/scripts/build-macos-extension.js | 62 +++ 18 files changed, 740 insertions(+), 26 deletions(-) create mode 100644 apps/desktop/macos/README.md create mode 100644 apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib create mode 100644 apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift create mode 100644 apps/desktop/macos/autofill-extension/Info.plist create mode 100644 apps/desktop/macos/autofill-extension/autofill_extension.entitlements create mode 100644 apps/desktop/macos/desktop.xcodeproj/project.pbxproj create mode 100644 apps/desktop/macos/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 apps/desktop/macos/production.xcconfig create mode 100644 apps/desktop/scripts/build-macos-extension.js diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 93693f183c3..c050ee1f6c0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -71,6 +71,7 @@ bitwarden_license/bit-web/src/app/billing @bitwarden/team-billing-dev ## Platform team files ## apps/browser/src/platform @bitwarden/team-platform-dev apps/cli/src/platform @bitwarden/team-platform-dev +apps/desktop/macos @bitwarden/team-platform-dev apps/desktop/src/platform @bitwarden/team-platform-dev apps/web/src/app/platform @bitwarden/team-platform-dev libs/angular/src/platform @bitwarden/team-platform-dev @@ -91,6 +92,7 @@ apps/web/src/translation-constants.ts @bitwarden/team-platform-dev apps/browser/src/autofill @bitwarden/team-autofill-dev apps/desktop/src/autofill @bitwarden/team-autofill-dev libs/common/src/autofill @bitwarden/team-autofill-dev +apps/desktop/macos/autofill-extension @bitwarden/team-autofill-dev # DuckDuckGo integration apps/desktop/native-messaging-test-runner @bitwarden/team-autofill-dev apps/desktop/src/services/native-message-handler.service.ts @bitwarden/team-autofill-dev diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 221c998247f..bb2889983b4 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -1164,6 +1164,21 @@ jobs: --file $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ --output none + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + --file $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + --output none + + - name: Set up provisioning profiles + run: | + AUTOFILL_PROFILE_PATH=$HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile + PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles + + mkdir -p "$PROFILES_DIR_PATH" + + AUTOFILL_UUID=$(grep UUID -A1 -a $AUTOFILL_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}") + cp $AUTOFILL_PROFILE_PATH "$PROFILES_DIR_PATH/$AUTOFILL_UUID.provisionprofile" + - name: Get certificates run: | mkdir -p $HOME/certificates @@ -1215,11 +1230,6 @@ jobs: security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain - - name: Set up provisioning profiles - run: | - cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ - $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile - - name: Increment version shell: pwsh env: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 561cd9af0c8..9dc72c7fdda 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,6 +36,7 @@ jobs: ! -path "./.github/*" \ ! -path "*/Cargo.toml" \ ! -path "*/Cargo.lock" \ + ! -path "./apps/desktop/macos/*" \ > tmp.txt diff <(sort .github/whitelist-capital-letters.txt) <(sort tmp.txt) diff --git a/apps/desktop/.gitignore b/apps/desktop/.gitignore index 040b2179fab..444c9a85100 100644 --- a/apps/desktop/.gitignore +++ b/apps/desktop/.gitignore @@ -2,3 +2,4 @@ dist-safari/ *.nupkg *.env PlugIns/safari.appex/ +xcuserdata/ diff --git a/apps/desktop/macos/README.md b/apps/desktop/macos/README.md new file mode 100644 index 00000000000..6e016144b4b --- /dev/null +++ b/apps/desktop/macos/README.md @@ -0,0 +1,23 @@ +# MacOS Extensions for Desktop Apps + +This folder contains an Xcode project that builds macOS extensions for our desktop app. The extensions are used to provide additional functionality to the desktop app, such as autofill (password and passkeys). + +## Manage loaded extensions + +macOS automatically loads extensions from apps, even if they have never been used (especially if built with Xcode). This can be confusing when you have multiple copies of the same application. To see where an extension is loaded from, use the following command: + +```bash +# To list all extensions +pluginkit -m -v + +# To list a specific extension +pluginkit -m -v -i com.bitwarden.desktop.autofill-extension +``` + +To unregister an extension, you can either remove it from your filesystem, or use the following command: + +```bash +pluginkit -r +``` + +where the path to the .appex file can be found in the output of the first command. diff --git a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib new file mode 100644 index 00000000000..ace3497a58b --- /dev/null +++ b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift new file mode 100644 index 00000000000..d5c5cabeee4 --- /dev/null +++ b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift @@ -0,0 +1,93 @@ +// +// CredentialProviderViewController.swift +// autofill-extension +// +// Created by Andreas Coroiu on 2023-12-21. +// + +import AuthenticationServices +import os + +class CredentialProviderViewController: ASCredentialProviderViewController { + let logger = Logger() + + /* + Implement this method if your extension supports showing credentials in the QuickType bar. + When the user selects a credential from your app, this method will be called with the + ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore. + Provide the password by completing the extension request with the associated ASPasswordCredential. + If using the credential would require showing custom UI for authenticating the user, cancel + the request with error code ASExtensionError.userInteractionRequired. + + override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { + let databaseIsUnlocked = true + if (databaseIsUnlocked) { + let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } else { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue)) + } + } + */ + + /* + Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with + ASExtensionError.userInteractionRequired. In this case, the system may present your extension's + UI and call this method. Show appropriate UI for authenticating the user then provide the password + by completing the extension request with the associated ASPasswordCredential. + + override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { + } + */ + + @IBAction func cancel(_ sender: AnyObject?) { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue)) + } + + @IBAction func passwordSelected(_ sender: AnyObject?) { + let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } + + override func prepareInterfaceForExtensionConfiguration() { + logger.log("[autofill-extension] prepareInterfaceForExtensionConfiguration called") + } + + override func prepareInterface(forPasskeyRegistration registrationRequest: ASCredentialRequest) { + logger.log("[autofill-extension] prepare interface for registration request \(registrationRequest.description)") + +// self.extensionContext.cancelRequest(withError: ExampleError.nope) + } + + override func prepareInterfaceToProvideCredential(for credentialRequest: ASCredentialRequest) { + logger.log("[autofill-extension] prepare interface for credential request \(credentialRequest.description)") + } + + /* + Prepare your UI to list available credentials for the user to choose from. The items in + 'serviceIdentifiers' describe the service the user is logging in to, so your extension can + prioritize the most relevant credentials in the list. + */ + override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) { + logger.log("[autofill-extension] prepareCredentialList for serviceIdentifiers: \(serviceIdentifiers.count)") + + for serviceIdentifier in serviceIdentifiers { + logger.log(" service: \(serviceIdentifier.identifier)") + } + } + + override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { + logger.log("[autofill-extension] prepareInterfaceToProvideCredential for credentialIdentity: \(credentialIdentity.user)") + } + + override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier], requestParameters: ASPasskeyCredentialRequestParameters) { + logger.log("[autofill-extension] prepareCredentialList(passkey) for serviceIdentifiers: \(serviceIdentifiers.count)") + + for serviceIdentifier in serviceIdentifiers { + logger.log(" service: \(serviceIdentifier.identifier)") + } + + logger.log("request parameters: \(requestParameters.relyingPartyIdentifier)") + } + +} diff --git a/apps/desktop/macos/autofill-extension/Info.plist b/apps/desktop/macos/autofill-extension/Info.plist new file mode 100644 index 00000000000..539cfa35b9d --- /dev/null +++ b/apps/desktop/macos/autofill-extension/Info.plist @@ -0,0 +1,23 @@ + + + + + NSExtension + + NSExtensionAttributes + + ASCredentialProviderExtensionCapabilities + + ProvidesPasskeys + + + ASCredentialProviderExtensionShowsConfigurationUI + + + NSExtensionPointIdentifier + com.apple.authentication-services-credential-provider-ui + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).CredentialProviderViewController + + + diff --git a/apps/desktop/macos/autofill-extension/autofill_extension.entitlements b/apps/desktop/macos/autofill-extension/autofill_extension.entitlements new file mode 100644 index 00000000000..2e600a8d529 --- /dev/null +++ b/apps/desktop/macos/autofill-extension/autofill_extension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.authentication-services.autofill-credential-provider + + com.apple.security.app-sandbox + + + diff --git a/apps/desktop/macos/desktop.xcodeproj/project.pbxproj b/apps/desktop/macos/desktop.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..313b158895c --- /dev/null +++ b/apps/desktop/macos/desktop.xcodeproj/project.pbxproj @@ -0,0 +1,367 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + E1DF713F2B342F6900F29026 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */; }; + E1DF71422B342F6900F29026 /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */; }; + E1DF71452B342F6900F29026 /* CredentialProviderViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 968ED08A2C52A47200FFFEE6 /* Production.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Production.xcconfig; sourceTree = ""; }; + E1DF713C2B342F6900F29026 /* autofill-extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "autofill-extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; + E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderViewController.swift; sourceTree = ""; }; + E1DF71442B342F6900F29026 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/CredentialProviderViewController.xib; sourceTree = ""; }; + E1DF71462B342F6900F29026 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E1DF71472B342F6900F29026 /* autofill_extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = autofill_extension.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E1DF71392B342F6900F29026 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E1DF713F2B342F6900F29026 /* AuthenticationServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + E1DF711D2B342E2800F29026 = { + isa = PBXGroup; + children = ( + 968ED08A2C52A47200FFFEE6 /* Production.xcconfig */, + E1DF71402B342F6900F29026 /* autofill-extension */, + E1DF713D2B342F6900F29026 /* Frameworks */, + E1DF71272B342E2800F29026 /* Products */, + ); + sourceTree = ""; + }; + E1DF71272B342E2800F29026 /* Products */ = { + isa = PBXGroup; + children = ( + E1DF713C2B342F6900F29026 /* autofill-extension.appex */, + ); + name = Products; + sourceTree = ""; + }; + E1DF713D2B342F6900F29026 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E1DF71402B342F6900F29026 /* autofill-extension */ = { + isa = PBXGroup; + children = ( + E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */, + E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */, + E1DF71462B342F6900F29026 /* Info.plist */, + E1DF71472B342F6900F29026 /* autofill_extension.entitlements */, + ); + path = "autofill-extension"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E1DF713B2B342F6900F29026 /* autofill-extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = E1DF714E2B342F6900F29026 /* Build configuration list for PBXNativeTarget "autofill-extension" */; + buildPhases = ( + E1DF71382B342F6900F29026 /* Sources */, + E1DF71392B342F6900F29026 /* Frameworks */, + E1DF713A2B342F6900F29026 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "autofill-extension"; + productName = "autofill-extension"; + productReference = E1DF713C2B342F6900F29026 /* autofill-extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E1DF711E2B342E2800F29026 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1510; + LastUpgradeCheck = 1510; + TargetAttributes = { + E1DF713B2B342F6900F29026 = { + CreatedOnToolsVersion = 15.1; + }; + }; + }; + buildConfigurationList = E1DF71212B342E2800F29026 /* Build configuration list for PBXProject "desktop" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = E1DF711D2B342E2800F29026; + productRefGroup = E1DF71272B342E2800F29026 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E1DF713B2B342F6900F29026 /* autofill-extension */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + E1DF713A2B342F6900F29026 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E1DF71452B342F6900F29026 /* CredentialProviderViewController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E1DF71382B342F6900F29026 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E1DF71422B342F6900F29026 /* CredentialProviderViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */ = { + isa = PBXVariantGroup; + children = ( + E1DF71442B342F6900F29026 /* Base */, + ); + name = CredentialProviderViewController.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + E1DF71332B342E2900F29026 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + E1DF71342B342E2900F29026 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + E1DF714C2B342F6900F29026 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = LTZ2PFU5D6; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "autofill-extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Bitwarden; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.bitwarden.desktop.autofill-extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Bitwarden Desktop Autofill Development 2024"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + E1DF714D2B342F6900F29026 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = LTZ2PFU5D6; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "autofill-extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Bitwarden; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.bitwarden.desktop.autofill-extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Bitwarden Desktop Autofill Development 2024"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E1DF71212B342E2800F29026 /* Build configuration list for PBXProject "desktop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E1DF71332B342E2900F29026 /* Debug */, + E1DF71342B342E2900F29026 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E1DF714E2B342F6900F29026 /* Build configuration list for PBXNativeTarget "autofill-extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E1DF714C2B342F6900F29026 /* Debug */, + E1DF714D2B342F6900F29026 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = E1DF711E2B342E2800F29026 /* Project object */; +} diff --git a/apps/desktop/macos/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/desktop/macos/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/apps/desktop/macos/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/desktop/macos/production.xcconfig b/apps/desktop/macos/production.xcconfig new file mode 100644 index 00000000000..f06f2bf736e --- /dev/null +++ b/apps/desktop/macos/production.xcconfig @@ -0,0 +1,11 @@ +// +// Production.xcconfig +// desktop +// +// Created by Vince Grassia on 7/25/24. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 +CODE_SIGN_IDENTITY[sdk=macosx*] = 3rd Party Mac Developer Application +PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] = Bitwarden Desktop Autofill App Store 2024 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index c9e33b7110a..423650cdcec 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -23,6 +23,7 @@ "build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"", "build:preload": "cross-env NODE_ENV=production webpack --config webpack.preload.js", "build:preload:watch": "cross-env NODE_ENV=production webpack --config webpack.preload.js --watch", + "build:macos-extension": "node scripts/build-macos-extension.js", "build:main": "cross-env NODE_ENV=production webpack --config webpack.main.js", "build:main:dev": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js", "build:main:watch": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js --watch", @@ -38,6 +39,7 @@ "pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never", "pack:mac:mas": "npm run clean:dist && electron-builder --mac mas --universal -p never", "pack:mac:masdev": "npm run clean:dist && electron-builder --mac mas-dev --universal -p never", + "pack:mac:masdev:with-extension": "npm run clean:dist && npm run build:macos-extension && electron-builder --mac mas-dev --universal -p never", "pack:win": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"", "pack:win:ci": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never", "dist:dir": "npm run build && npm run pack:dir", diff --git a/apps/desktop/resources/entitlements.mac.plist b/apps/desktop/resources/entitlements.mac.plist index 48f7bf5cece..915232b83f6 100644 --- a/apps/desktop/resources/entitlements.mac.plist +++ b/apps/desktop/resources/entitlements.mac.plist @@ -8,5 +8,7 @@ com.apple.security.cs.disable-library-validation + com.apple.developer.authentication-services.autofill-credential-provider + diff --git a/apps/desktop/resources/entitlements.mas.inherit.plist b/apps/desktop/resources/entitlements.mas.inherit.plist index 3ee76423e4c..3634c84f81a 100644 --- a/apps/desktop/resources/entitlements.mas.inherit.plist +++ b/apps/desktop/resources/entitlements.mas.inherit.plist @@ -10,5 +10,7 @@ com.apple.security.cs.disable-library-validation + com.apple.developer.authentication-services.autofill-credential-provider + diff --git a/apps/desktop/resources/entitlements.mas.plist b/apps/desktop/resources/entitlements.mas.plist index d42ade962c3..9ab2d3824a8 100644 --- a/apps/desktop/resources/entitlements.mas.plist +++ b/apps/desktop/resources/entitlements.mas.plist @@ -16,6 +16,8 @@ com.apple.security.files.user-selected.read-write + com.apple.developer.authentication-services.autofill-credential-provider + com.apple.security.temporary-exception.files.home-relative-path.read-write /Library/Application Support/Mozilla/NativeMessagingHosts/ diff --git a/apps/desktop/scripts/after-sign.js b/apps/desktop/scripts/after-sign.js index 69c078a13b5..dc60e9d1838 100644 --- a/apps/desktop/scripts/after-sign.js +++ b/apps/desktop/scripts/after-sign.js @@ -15,36 +15,62 @@ async function run(context) { const appName = context.packager.appInfo.productFilename; const appPath = `${context.appOutDir}/${appName}.app`; const macBuild = context.electronPlatformName === "darwin"; - const copyPlugIn = ["darwin", "mas"].includes(context.electronPlatformName); + const copySafariExtension = ["darwin", "mas"].includes(context.electronPlatformName); + const copyAutofillExtension = ["mas"].includes(context.electronPlatformName); - if (copyPlugIn) { + let shouldResign = false; + + // cannot use extraFiles because it modifies the extensions .plist and makes it invalid + if (copyAutofillExtension) { + console.log("### Copying autofill extension"); + const extensionPath = path.join(__dirname, "../macos/dist/autofill-extension.appex"); + if (!fse.existsSync(extensionPath)) { + console.log("### Autofill extension not found - skipping"); + } else { + if (!fse.existsSync(path.join(appPath, "Contents/PlugIns"))) { + fse.mkdirSync(path.join(appPath, "Contents/PlugIns")); + } + fse.copySync(extensionPath, path.join(appPath, "Contents/PlugIns/autofill-extension.appex")); + shouldResign = true; + } + } + + if (copySafariExtension) { + console.log("### Copying safari extension"); // Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552 const plugIn = path.join(__dirname, "../PlugIns"); - if (fse.existsSync(plugIn)) { - fse.mkdirSync(path.join(appPath, "Contents/PlugIns")); + if (!fse.existsSync(plugIn)) { + console.log("### Safari extension not found - skipping"); + } else { + if (!fse.existsSync(path.join(appPath, "Contents/PlugIns"))) { + fse.mkdirSync(path.join(appPath, "Contents/PlugIns")); + } fse.copySync( path.join(plugIn, "safari.appex"), path.join(appPath, "Contents/PlugIns/safari.appex"), ); + shouldResign = true; + } + } - // Resign to sign safari extension - if (context.electronPlatformName === "mas") { - const masBuildOptions = deepAssign( - {}, - context.packager.platformSpecificBuildOptions, - context.packager.config.mas, - ); - if (context.targets.some((e) => e.name === "mas-dev")) { - deepAssign(masBuildOptions, { - type: "development", - }); - } - if (context.packager.packagerOptions.prepackaged == null) { - await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch); - } - } else { - await context.packager.signApp(context, true); + if (shouldResign) { + // Resign to sign safari extension + if (context.electronPlatformName === "mas") { + const masBuildOptions = deepAssign( + {}, + context.packager.platformSpecificBuildOptions, + context.packager.config.mas, + ); + if (context.targets.some((e) => e.name === "mas-dev")) { + deepAssign(masBuildOptions, { + type: "development", + }); } + if (context.packager.packagerOptions.prepackaged == null) { + await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch); + } + } else { + await context.packager.signApp(context, true); } } diff --git a/apps/desktop/scripts/build-macos-extension.js b/apps/desktop/scripts/build-macos-extension.js new file mode 100644 index 00000000000..3aa43fb6785 --- /dev/null +++ b/apps/desktop/scripts/build-macos-extension.js @@ -0,0 +1,62 @@ +/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +const child = require("child_process"); +const { exit } = require("process"); + +const fse = require("fs-extra"); + +const paths = { + macosBuild: "./macos/build", + extensionBuild: "./macos/build/Release/autofill-extension.appex", + extensionDistDir: "./macos/dist", + extensionDist: "./macos/dist/autofill-extension.appex", + macOsProject: "./macos/desktop.xcodeproj", + macOsConfig: "./macos/production.xcconfig", +}; + +async function buildMacOs() { + if (fse.existsSync(paths.macosBuild)) { + fse.removeSync(paths.macosBuild); + } + + if (fse.existsSync(paths.extensionDistDir)) { + fse.removeSync(paths.extensionDistDir); + } + + const proc = child.spawn("xcodebuild", [ + "-project", + paths.macOsProject, + "-alltargets", + "-configuration", + "Release", + "-xcconfig", + paths.macOsConfig, + ]); + stdOutProc(proc); + await new Promise((resolve, reject) => + proc.on("close", (code) => { + if (code > 0) { + console.error("xcodebuild failed with code", code); + return reject(new Error(`xcodebuild failed with code ${code}`)); + } + console.log("xcodebuild success"); + resolve(); + }), + ); + + fse.mkdirSync(paths.extensionDistDir); + fse.copySync(paths.extensionBuild, paths.extensionDist); + // Delete the build dir, otherwise MacOS will load the extension from there instead of the Bitwarden.app bundle + fse.removeSync(paths.macosBuild); +} + +function stdOutProc(proc) { + proc.stdout.on("data", (data) => console.log(data.toString())); + proc.stderr.on("data", (data) => console.error(data.toString())); +} + +buildMacOs() + .then(() => console.log("macOS build complete")) + .catch((err) => { + console.error("macOS build failed", err); + exit(-1); + }); From 334b82764c0f6cdee51dae76f423126504a0e154 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:19:38 -0500 Subject: [PATCH 18/74] Change Docker image tag logic to support pull_request_target trigger (#11984) --- .github/workflows/build-web.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index ba4f2599f37..7e89a5c7cd7 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -216,7 +216,7 @@ jobs: - name: Generate Docker image tag id: tag run: | - if [[ $(grep "pull" <<< "${GITHUB_REF}") ]]; then + if [[ "${GITHUB_EVENT_NAME}" == "pull_request_target" ]]; then IMAGE_TAG=$(echo "${GITHUB_HEAD_REF}" | sed "s#/#-#g") else IMAGE_TAG=$(echo "${GITHUB_REF_NAME}" | sed "s#/#-#g") From 63a71981fb7764493918327b30669f4d1fa0891f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Wed, 13 Nov 2024 16:58:43 +0100 Subject: [PATCH 19/74] Fix github token generating in repository-management.yml workflow (#11983) * Skip token revoke in repository-management.yml workflow * GEt gh token in every job --- .github/workflows/repository-management.yml | 35 ++++++++++++++------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index 21de47f13ba..9935ef7674e 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -44,7 +44,6 @@ jobs: runs-on: ubuntu-24.04 outputs: branch: ${{ steps.set-branch.outputs.branch }} - token: ${{ steps.app-token.outputs.token }} steps: - name: Set branch id: set-branch @@ -59,13 +58,6 @@ jobs: echo "branch=$BRANCH" >> $GITHUB_OUTPUT - - name: Generate GH App token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 - id: app-token - with: - app-id: ${{ secrets.BW_GHAPP_ID }} - private-key: ${{ secrets.BW_GHAPP_KEY }} - cut_branch: name: Cut branch @@ -73,11 +65,18 @@ jobs: needs: setup runs-on: ubuntu-24.04 steps: + - name: Generate GH App token + uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + id: app-token + with: + app-id: ${{ secrets.BW_GHAPP_ID }} + private-key: ${{ secrets.BW_GHAPP_KEY }} + - name: Check out target ref uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.target_ref }} - token: ${{ needs.setup.outputs.token }} + token: ${{ steps.app-token.outputs.token }} - name: Check if ${{ needs.setup.outputs.branch }} branch exists env: @@ -115,11 +114,18 @@ jobs: with: version: ${{ inputs.version_number_override }} + - name: Generate GH App token + uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + id: app-token + with: + app-id: ${{ secrets.BW_GHAPP_ID }} + private-key: ${{ secrets.BW_GHAPP_KEY }} + - name: Check out branch uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: main - token: ${{ needs.setup.outputs.token }} + token: ${{ steps.app-token.outputs.token }} - name: Configure Git run: | @@ -445,11 +451,18 @@ jobs: - bump_version - setup steps: + - name: Generate GH App token + uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + id: app-token + with: + app-id: ${{ secrets.BW_GHAPP_ID }} + private-key: ${{ secrets.BW_GHAPP_KEY }} + - name: Check out main branch uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: main - token: ${{ needs.setup.outputs.token }} + token: ${{ steps.app-token.outputs.token }} - name: Configure Git run: | From 379efb1326ea3e67a0e42c5fd59cade81e77caad Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 13 Nov 2024 11:05:19 -0500 Subject: [PATCH 20/74] Update the publisher name for Bitwarden (#11846) --- apps/browser/store/windows/AppxManifest.xml | 2 +- apps/desktop/electron-builder.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/browser/store/windows/AppxManifest.xml b/apps/browser/store/windows/AppxManifest.xml index df02ea085cf..9765506a558 100644 --- a/apps/browser/store/windows/AppxManifest.xml +++ b/apps/browser/store/windows/AppxManifest.xml @@ -12,7 +12,7 @@ Bitwarden Password Manager - 8bit Solutions LLC + Bitwarden Inc Assets/icon_50.png diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 53c20b7faf0..9a8bc45ae20 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -162,7 +162,7 @@ "applicationId": "bitwardendesktop", "identityName": "8bitSolutionsLLC.bitwardendesktop", "publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418", - "publisherDisplayName": "8bit Solutions LLC", + "publisherDisplayName": "Bitwarden Inc", "languages": [ "en-US", "af", From 0b11596f089f1d3d3dd71ae50fe17d3bbbd1cc75 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Wed, 13 Nov 2024 08:37:29 -0800 Subject: [PATCH 21/74] [PM-14813] - Implement server API to user member cipher details instead of mock data (#11978) * use cipher details data * fix failing tests * fix failing tests --- ...sword-health-members-uri.component.spec.ts | 14 +- .../password-health-members-uri.component.ts | 12 +- .../password-health-members.component.ts | 22 ++- .../password-health.component.spec.ts | 8 +- .../password-health.component.ts | 9 +- .../member-cipher-details-api.service.spec.ts | 134 +++++++++--------- .../member-cipher-details-api.service.ts | 7 +- .../services/password-health.service.spec.ts | 25 +++- .../services/password-health.service.ts | 18 +-- 9 files changed, 140 insertions(+), 109 deletions(-) diff --git a/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.spec.ts b/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.spec.ts index b34730bd328..e3011604a45 100644 --- a/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.spec.ts +++ b/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.spec.ts @@ -4,7 +4,11 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; // eslint-disable-next-line no-restricted-imports -import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + MemberCipherDetailsApiService, + PasswordHealthService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -46,6 +50,14 @@ describe("PasswordHealthMembersUriComponent", () => { url: of([]), }, }, + { + provide: MemberCipherDetailsApiService, + useValue: mock(), + }, + { + provide: ApiService, + useValue: mock(), + }, ], }).compileComponents(); }); diff --git a/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.ts b/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.ts index c977c829537..c8aea97ef77 100644 --- a/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.ts +++ b/apps/web/src/app/tools/risk-insights/password-health-members-uri.component.ts @@ -6,7 +6,10 @@ import { map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; // eslint-disable-next-line no-restricted-imports -import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + MemberCipherDetailsApiService, + PasswordHealthService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -25,8 +28,6 @@ import { // eslint-disable-next-line no-restricted-imports import { HeaderModule } from "../../layouts/header/header.module"; // eslint-disable-next-line no-restricted-imports -import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module"; -// eslint-disable-next-line no-restricted-imports import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; @Component({ @@ -35,7 +36,6 @@ import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; templateUrl: "password-health-members-uri.component.html", imports: [ BadgeModule, - OrganizationBadgeModule, CommonModule, ContainerComponent, PipesModule, @@ -43,7 +43,7 @@ import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; HeaderModule, TableModule, ], - providers: [PasswordHealthService], + providers: [PasswordHealthService, MemberCipherDetailsApiService], }) export class PasswordHealthMembersURIComponent implements OnInit { passwordStrengthMap = new Map(); @@ -74,6 +74,7 @@ export class PasswordHealthMembersURIComponent implements OnInit { protected auditService: AuditService, protected i18nService: I18nService, protected activatedRoute: ActivatedRoute, + protected memberCipherDetailsApiService: MemberCipherDetailsApiService, ) {} ngOnInit() { @@ -93,6 +94,7 @@ export class PasswordHealthMembersURIComponent implements OnInit { this.passwordStrengthService, this.auditService, this.cipherService, + this.memberCipherDetailsApiService, organizationId, ); diff --git a/apps/web/src/app/tools/risk-insights/password-health-members.component.ts b/apps/web/src/app/tools/risk-insights/password-health-members.component.ts index 2581de78ed5..66ff348e9f9 100644 --- a/apps/web/src/app/tools/risk-insights/password-health-members.component.ts +++ b/apps/web/src/app/tools/risk-insights/password-health-members.component.ts @@ -5,7 +5,10 @@ import { ActivatedRoute } from "@angular/router"; import { debounceTime, map } from "rxjs"; // eslint-disable-next-line no-restricted-imports -import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + MemberCipherDetailsApiService, + PasswordHealthService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -18,12 +21,10 @@ import { TableModule, ToastService, } from "@bitwarden/components"; -import { CardComponent } from "@bitwarden/tools-card"; import { HeaderModule } from "../../layouts/header/header.module"; // eslint-disable-next-line no-restricted-imports import { SharedModule } from "../../shared"; -import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module"; // eslint-disable-next-line no-restricted-imports import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; @@ -31,17 +32,8 @@ import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; standalone: true, selector: "tools-password-health-members", templateUrl: "password-health-members.component.html", - imports: [ - CardComponent, - OrganizationBadgeModule, - PipesModule, - HeaderModule, - SearchModule, - FormsModule, - SharedModule, - TableModule, - ], - providers: [PasswordHealthService], + imports: [PipesModule, HeaderModule, SearchModule, FormsModule, SharedModule, TableModule], + providers: [PasswordHealthService, MemberCipherDetailsApiService], }) export class PasswordHealthMembersComponent implements OnInit { passwordStrengthMap = new Map(); @@ -69,6 +61,7 @@ export class PasswordHealthMembersComponent implements OnInit { protected i18nService: I18nService, protected activatedRoute: ActivatedRoute, protected toastService: ToastService, + protected memberCipherDetailsApiService: MemberCipherDetailsApiService, ) { this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) @@ -92,6 +85,7 @@ export class PasswordHealthMembersComponent implements OnInit { this.passwordStrengthService, this.auditService, this.cipherService, + this.memberCipherDetailsApiService, organizationId, ); diff --git a/apps/web/src/app/tools/risk-insights/password-health.component.spec.ts b/apps/web/src/app/tools/risk-insights/password-health.component.spec.ts index 50295b435b2..5e934e3edfc 100644 --- a/apps/web/src/app/tools/risk-insights/password-health.component.spec.ts +++ b/apps/web/src/app/tools/risk-insights/password-health.component.spec.ts @@ -4,7 +4,11 @@ import { mock } from "jest-mock-extended"; import { of } from "rxjs"; // eslint-disable-next-line no-restricted-imports -import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + MemberCipherDetailsApiService, + PasswordHealthService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -30,6 +34,8 @@ describe("PasswordHealthComponent", () => { { provide: CipherService, useValue: mock() }, { provide: I18nService, useValue: mock() }, { provide: AuditService, useValue: mock() }, + { provide: ApiService, useValue: mock() }, + { provide: MemberCipherDetailsApiService, useValue: mock() }, { provide: PasswordStrengthServiceAbstraction, useValue: mock(), diff --git a/apps/web/src/app/tools/risk-insights/password-health.component.ts b/apps/web/src/app/tools/risk-insights/password-health.component.ts index c3c1732854d..058cfb86dae 100644 --- a/apps/web/src/app/tools/risk-insights/password-health.component.ts +++ b/apps/web/src/app/tools/risk-insights/password-health.component.ts @@ -6,7 +6,10 @@ import { map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; // eslint-disable-next-line no-restricted-imports -import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + MemberCipherDetailsApiService, + PasswordHealthService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -41,7 +44,7 @@ import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; HeaderModule, TableModule, ], - providers: [PasswordHealthService], + providers: [PasswordHealthService, MemberCipherDetailsApiService], }) export class PasswordHealthComponent implements OnInit { passwordStrengthMap = new Map(); @@ -62,6 +65,7 @@ export class PasswordHealthComponent implements OnInit { protected auditService: AuditService, protected i18nService: I18nService, protected activatedRoute: ActivatedRoute, + protected memberCipherDetailsApiService: MemberCipherDetailsApiService, ) {} ngOnInit() { @@ -81,6 +85,7 @@ export class PasswordHealthComponent implements OnInit { this.passwordStrengthService, this.auditService, this.cipherService, + this.memberCipherDetailsApiService, organizationId, ); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts index b71abe075e6..872a4cdff55 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.spec.ts @@ -4,74 +4,72 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service"; -const mockMemberCipherDetails: any = { - data: [ - { - userName: "David Brent", - email: "david.brent@wernhamhogg.uk", - usesKeyConnector: true, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228ab1", - "cbea34a8-bde4-46ad-9d19-b05001228ab2", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - "cbea34a8-bde4-46ad-9d19-b05001227nm5", - ], - }, - { - userName: "Tim Canterbury", - email: "tim.canterbury@wernhamhogg.uk", - usesKeyConnector: false, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228ab2", - "cbea34a8-bde4-46ad-9d19-b05001228cd3", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - "cbea34a8-bde4-46ad-9d19-b05001227nm5", - ], - }, - { - userName: "Gareth Keenan", - email: "gareth.keenan@wernhamhogg.uk", - usesKeyConnector: true, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228cd3", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - "cbea34a8-bde4-46ad-9d19-b05001227nm5", - "cbea34a8-bde4-46ad-9d19-b05001227nm7", - ], - }, - { - userName: "Dawn Tinsley", - email: "dawn.tinsley@wernhamhogg.uk", - usesKeyConnector: true, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228ab2", - "cbea34a8-bde4-46ad-9d19-b05001228cd3", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - ], - }, - { - userName: "Keith Bishop", - email: "keith.bishop@wernhamhogg.uk", - usesKeyConnector: false, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228ab1", - "cbea34a8-bde4-46ad-9d19-b05001228cd3", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - "cbea34a8-bde4-46ad-9d19-b05001227nm5", - ], - }, - { - userName: "Chris Finch", - email: "chris.finch@wernhamhogg.uk", - usesKeyConnector: true, - cipherIds: [ - "cbea34a8-bde4-46ad-9d19-b05001228ab2", - "cbea34a8-bde4-46ad-9d19-b05001228cd3", - "cbea34a8-bde4-46ad-9d19-b05001228xy4", - ], - }, - ], -}; +export const mockMemberCipherDetails: any = [ + { + userName: "David Brent", + email: "david.brent@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab1", + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + ], + }, + { + userName: "Tim Canterbury", + email: "tim.canterbury@wernhamhogg.uk", + usesKeyConnector: false, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + ], + }, + { + userName: "Gareth Keenan", + email: "gareth.keenan@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + "cbea34a8-bde4-46ad-9d19-b05001227nm7", + ], + }, + { + userName: "Dawn Tinsley", + email: "dawn.tinsley@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + ], + }, + { + userName: "Keith Bishop", + email: "keith.bishop@wernhamhogg.uk", + usesKeyConnector: false, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab1", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + ], + }, + { + userName: "Chris Finch", + email: "chris.finch@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + ], + }, +]; describe("Member Cipher Details API Service", () => { let memberCipherDetailsApiService: MemberCipherDetailsApiService; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.ts index 9351ac87776..b38f8712add 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/member-cipher-details-api.service.ts @@ -1,8 +1,10 @@ +import { Injectable } from "@angular/core"; + import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { MemberCipherDetailsResponse } from "../response/member-cipher-details.response"; +@Injectable() export class MemberCipherDetailsApiService { constructor(private apiService: ApiService) {} @@ -21,7 +23,6 @@ export class MemberCipherDetailsApiService { true, ); - const listResponse = new ListResponse(response, MemberCipherDetailsResponse); - return listResponse.data.map((r) => new MemberCipherDetailsResponse(r)); + return response; } } diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.spec.ts index 692eb5afba7..c0f77abeb79 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.spec.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.spec.ts @@ -3,15 +3,17 @@ import { TestBed } from "@angular/core/testing"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { mockCiphers } from "./ciphers.mock"; +import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service"; +import { mockMemberCipherDetails } from "./member-cipher-details-api.service.spec"; import { PasswordHealthService } from "./password-health.service"; describe("PasswordHealthService", () => { let service: PasswordHealthService; let cipherService: CipherService; + let memberCipherDetailsApiService: MemberCipherDetailsApiService; beforeEach(() => { TestBed.configureTestingModule({ @@ -35,7 +37,13 @@ describe("PasswordHealthService", () => { { provide: CipherService, useValue: { - getAllFromApiForOrganization: jest.fn().mockResolvedValue(CipherData), + getAllFromApiForOrganization: jest.fn().mockResolvedValue(mockCiphers), + }, + }, + { + provide: MemberCipherDetailsApiService, + useValue: { + getMemberCipherDetails: jest.fn().mockResolvedValue(mockMemberCipherDetails), }, }, { provide: "organizationId", useValue: "org1" }, @@ -44,6 +52,7 @@ describe("PasswordHealthService", () => { service = TestBed.inject(PasswordHealthService); cipherService = TestBed.inject(CipherService); + memberCipherDetailsApiService = TestBed.inject(MemberCipherDetailsApiService); }); it("should be created", () => { @@ -68,6 +77,10 @@ describe("PasswordHealthService", () => { expect(cipherService.getAllFromApiForOrganization).toHaveBeenCalledWith("org1"); }); + it("should fetch member cipher details", () => { + expect(memberCipherDetailsApiService.getMemberCipherDetails).toHaveBeenCalledWith("org1"); + }); + it("should populate reportCiphers with ciphers that have issues", () => { expect(service.reportCiphers.length).toBeGreaterThan(0); }); @@ -99,12 +112,12 @@ describe("PasswordHealthService", () => { it("should calculate total members per cipher", () => { expect(service.totalMembersMap.size).toBeGreaterThan(0); - expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab1")).toBe(3); - expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toBe(5); - expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toBe(6); + expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab1")).toBe(2); + expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toBe(4); + expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toBe(5); expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm5")).toBe(4); expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm7")).toBe(1); - expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228xy4")).toBe(7); + expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228xy4")).toBe(6); }); }); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.ts index 1709261922b..4070b23d29e 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/password-health.service.ts @@ -1,9 +1,5 @@ import { Inject, Injectable } from "@angular/core"; -// eslint-disable-next-line no-restricted-imports -import { mockCiphers } from "@bitwarden/bit-common/tools/reports/risk-insights/services/ciphers.mock"; -// eslint-disable-next-line no-restricted-imports -import { mockMemberCipherDetailsResponse } from "@bitwarden/bit-common/tools/reports/risk-insights/services/member-cipher-details-response.mock"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -12,6 +8,8 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { BadgeVariant } from "@bitwarden/components"; +import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service"; + @Injectable() export class PasswordHealthService { reportCiphers: CipherView[] = []; @@ -30,21 +28,23 @@ export class PasswordHealthService { private passwordStrengthService: PasswordStrengthServiceAbstraction, private auditService: AuditService, private cipherService: CipherService, + private memberCipherDetailsApiService: MemberCipherDetailsApiService, @Inject("organizationId") private organizationId: string, ) {} async generateReport() { - let allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organizationId); - // TODO remove when actual user member data is available - allCiphers = mockCiphers; + const allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organizationId); allCiphers.forEach(async (cipher) => { this.findWeakPassword(cipher); this.findReusedPassword(cipher); await this.findExposedPassword(cipher); }); - // TODO - fetch actual user member when data is available - mockMemberCipherDetailsResponse.data.forEach((user) => { + const memberCipherDetails = await this.memberCipherDetailsApiService.getMemberCipherDetails( + this.organizationId, + ); + + memberCipherDetails.forEach((user) => { user.cipherIds.forEach((cipherId: string) => { if (this.totalMembersMap.has(cipherId)) { this.totalMembersMap.set(cipherId, (this.totalMembersMap.get(cipherId) || 0) + 1); From a75c2118ec9361f0038aff7631baeb38e6eb0cb9 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 13 Nov 2024 17:41:47 +0100 Subject: [PATCH 22/74] [PM-14850] Flatpak development & qa artifacts (#11925) * Add flatpak development manifest * Undo removal of libsecret * Update .github/workflows/build-desktop.yml Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> --------- Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> --- .github/workflows/build-desktop.yml | 15 ++++++- .gitignore | 3 ++ apps/desktop/package.json | 1 + .../com.bitwarden.desktop.devel.yaml | 43 +++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 apps/desktop/resources/com.bitwarden.desktop.devel.yaml diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index bb2889983b4..15ef7946101 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -166,7 +166,7 @@ jobs: - name: Set up environment run: | sudo apt-get update - sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm musl-dev musl-tools + sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm musl-dev musl-tools flatpak flatpak-builder - name: Set up Snap run: sudo snap install snapcraft --classic @@ -248,6 +248,19 @@ jobs: name: ${{ needs.setup.outputs.release_channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml if-no-files-found: error + + - name: Build flatpak + working-directory: apps/desktop + run: | + sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + sudo npm run pack:lin:flatpak + + - name: Upload flatpak artifact + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: com.bitwarden.desktop.flatpak + path: apps/desktop/dist/com.bitwarden.desktop.flatpak + if-no-files-found: error windows: diff --git a/.gitignore b/.gitignore index 6dea4b43f16..d0d8edd596c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,9 @@ npm-debug.log dist build .angular/cache +.flatpak +.flatpak-repo +.flatpak-builder # Testing coverage diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 423650cdcec..ae19e5f93c9 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -34,6 +34,7 @@ "electron:ignore": "node ./scripts/start.js --ignore-certificate-errors", "clean:dist": "rimraf ./dist", "pack:dir": "npm run clean:dist && electron-builder --dir -p never", + "pack:lin:flatpak": "npm run clean:dist && electron-builder --dir -p never && flatpak-builder --repo=build/.repo build/.flatpak ./resources/com.bitwarden.desktop.devel.yaml --install-deps-from=flathub --force-clean && flatpak build-bundle ./build/.repo/ ./dist/com.bitwarden.desktop.flatpak com.bitwarden.desktop", "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never", "pack:mac": "npm run clean:dist && electron-builder --mac --universal -p never", "pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never", diff --git a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml new file mode 100644 index 00000000000..234d37905cc --- /dev/null +++ b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml @@ -0,0 +1,43 @@ +app-id: com.bitwarden.desktop +runtime: org.freedesktop.Platform +runtime-version: "24.08" +sdk: org.freedesktop.Sdk +base: org.electronjs.Electron2.BaseApp +base-version: "24.08" +command: bitwarden.sh +finish-args: + - --share=ipc + - --share=network + - --socket=wayland + - --socket=x11 + - --device=dri + - --env=XDG_CURRENT_DESKTOP=Unity + - --env=XCURSOR_PATH=/run/host/user-share/icons:/run/host/share/icons + - --talk-name=org.kde.StatusNotifierWatcher + - --talk-name=org.freedesktop.Notifications + - --talk-name=org.freedesktop.secrets + - --talk-name=com.canonical.AppMenu.Registrar + - --system-talk-name=org.freedesktop.PolicyKit1 + # Lock on lockscreen + - --talk-name=org.gnome.ScreenSaver + - --talk-name=org.freedesktop.ScreenSaver + - --system-talk-name=org.freedesktop.login1 + - --filesystem=xdg-download +modules: + - name: bitwarden-desktop + buildsystem: simple + build-commands: + - mkdir -p /app/bin + - mkdir -p /app/bin/Bitwarden/ + - cp -r ./* /app/bin/ + - install bitwarden.sh /app/bin/bitwarden.sh + sources: + - type: dir + path: ../dist/linux-unpacked + - type: script + dest-filename: bitwarden.sh + commands: + - ulimit -c 0 + - export TMPDIR="$XDG_RUNTIME_DIR/app/$FLATPAK_ID" + - exec zypak-wrapper /app/bin/bitwarden-app --ozone-platform-hint=auto + --enable-features=WaylandWindowDecorations "$@" From 251213b69c4b7d0e9322f33e55456f17e6d6cc24 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:58:34 -0600 Subject: [PATCH 23/74] remove check for `SHOW_AUTOFILL_BUTTON` (#11971) --- .../vault-v2/view-v2/view-v2.component.spec.ts | 13 ++++++++++++- .../vault-v2/view-v2/view-v2.component.ts | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index b173c36d255..36343d3a661 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -5,6 +5,7 @@ import { Subject } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AUTOFILL_ID } from "@bitwarden/common/autofill/constants"; import { EventType } from "@bitwarden/common/enums"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -33,6 +34,7 @@ describe("ViewV2Component", () => { const params$ = new Subject(); const mockNavigate = jest.fn(); const collect = jest.fn().mockResolvedValue(null); + const doAutofill = jest.fn(); const mockCipher = { id: "122-333-444", @@ -41,7 +43,7 @@ describe("ViewV2Component", () => { }; const mockVaultPopupAutofillService = { - doAutofill: jest.fn(), + doAutofill, }; const mockUserId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); @@ -54,6 +56,7 @@ describe("ViewV2Component", () => { beforeEach(async () => { mockNavigate.mockClear(); collect.mockClear(); + doAutofill.mockClear(); await TestBed.configureTestingModule({ imports: [ViewV2Component], @@ -148,5 +151,13 @@ describe("ViewV2Component", () => { undefined, ); })); + + it('invokes `doAutofill` when action="AUTOFILL_ID"', fakeAsync(() => { + params$.next({ action: AUTOFILL_ID }); + + flush(); // Resolve all promises + + expect(doAutofill).toHaveBeenCalledOnce(); + })); }); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index b1cbe8bc3e4..8242fd8747e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -100,7 +100,7 @@ export class ViewV2Component { switchMap(async (cipher) => { this.cipher = cipher; this.headerText = this.setHeader(cipher.type); - if (this.loadAction === AUTOFILL_ID || this.loadAction === SHOW_AUTOFILL_BUTTON) { + if (this.loadAction === AUTOFILL_ID) { await this.vaultPopupAutofillService.doAutofill(this.cipher); } From 7ef1d01401275c36b1f309ac3975739ac524bd4e Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Wed, 13 Nov 2024 09:46:58 -0800 Subject: [PATCH 24/74] [PM-13822] Password Hint UI tweaks (#11867) * update env selector label * update icon colors * re-upload SVG with viewBox and correct classes --- .../environment-selector.component.html | 2 +- libs/auth/src/angular/icons/user-lock.icon.ts | 114 +++++++++++++++--- 2 files changed, 98 insertions(+), 18 deletions(-) diff --git a/apps/web/src/app/components/environment-selector/environment-selector.component.html b/apps/web/src/app/components/environment-selector/environment-selector.component.html index 4be0db2ba45..a1fb1a8a0f3 100644 --- a/apps/web/src/app/components/environment-selector/environment-selector.component.html +++ b/apps/web/src/app/components/environment-selector/environment-selector.component.html @@ -17,7 +17,7 @@
- {{ "server" | i18n }}: + {{ "accessing" | i18n }}: {{ currentRegion?.domain }} diff --git a/libs/auth/src/angular/icons/user-lock.icon.ts b/libs/auth/src/angular/icons/user-lock.icon.ts index fef00a09a92..e85eac6fc2d 100644 --- a/libs/auth/src/angular/icons/user-lock.icon.ts +++ b/libs/auth/src/angular/icons/user-lock.icon.ts @@ -1,22 +1,102 @@ import { svgIcon } from "@bitwarden/components"; export const UserLockIcon = svgIcon` - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + `; From 204eb3105b9b4c8ed13ce3fece798e725bc3e35b Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:17:23 -0800 Subject: [PATCH 25/74] update anon-layout and extension-anon-layout spacing (#11869) --- ...extension-anon-layout-wrapper.component.html | 3 +-- .../anon-layout/anon-layout.component.html | 17 +++++++++-------- .../anon-layout/anon-layout.component.ts | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html index 4a206b36fa8..2589a08da19 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html @@ -1,4 +1,4 @@ - + diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.html b/libs/auth/src/angular/anon-layout/anon-layout.component.html index 8a0ac4b7186..3323b6eca08 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.html +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.html @@ -1,18 +1,19 @@ -
- + -
+
@@ -36,14 +37,14 @@ [ngClass]="{ 'tw-max-w-md': maxWidth === 'md', 'tw-max-w-3xl': maxWidth === '3xl' }" >
-
diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 7b2d2ba86b8..2d0ccd1d1c0 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -104,7 +104,7 @@ import "../platform/popup/locales"; maxOpened: 2, autoDismiss: true, closeButton: true, - positionClass: "toast-bottom-full-width", + positionClass: "toast-top-full-width", }), BrowserAnimationsModule, BrowserModule, diff --git a/apps/browser/src/popup/images/loading.svg b/apps/browser/src/popup/images/loading.svg index 70763105168..3f2033303db 100644 --- a/apps/browser/src/popup/images/loading.svg +++ b/apps/browser/src/popup/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 80c75360872..89b8816567d 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -6,6 +6,10 @@ margin: 0; } +html { + overflow: hidden; +} + html, body { font-family: $font-family-sans-serif; @@ -15,7 +19,7 @@ body { } body { - width: 375px !important; + width: 380px !important; height: 600px !important; position: relative; min-height: 100vh; @@ -83,9 +87,9 @@ a:not(popup-page a, popup-tab-navigation a) { } } -input, -select, -textarea { +input:not(bit-form-field input, bit-search input), +select:not(bit-form-field select), +textarea:not(bit-form-field textarea) { @include themify($themes) { color: themed("textColor"); background-color: themed("inputBackgroundColor"); @@ -95,7 +99,7 @@ textarea { input, select, textarea, -button { +button:not(bit-chip-select button) { font-size: $font-size-base; font-family: $font-family-sans-serif; } @@ -286,7 +290,7 @@ header:not(bit-callout header, bit-dialog header, popup-page header) { } } - input { + input:not(bit-form-field input) { width: 100%; margin: 0; border: none; diff --git a/apps/browser/src/popup/scss/misc.scss b/apps/browser/src/popup/scss/misc.scss index 57bd3e010c8..f61d593c43f 100644 --- a/apps/browser/src/popup/scss/misc.scss +++ b/apps/browser/src/popup/scss/misc.scss @@ -306,7 +306,7 @@ input[type="password"]::-ms-reveal { // contrast against the background, so its inversion is also still readable) // and suppress user selection for most elements (to make it more app-like) -::selection { +:not(bit-form-field input)::selection { @include themify($themes) { color: themed("backgroundColor"); background-color: themed("primaryAccentColor"); diff --git a/apps/browser/src/popup/scss/pages.scss b/apps/browser/src/popup/scss/pages.scss index bf8f03e7d03..2fd903fedc7 100644 --- a/apps/browser/src/popup/scss/pages.scss +++ b/apps/browser/src/popup/scss/pages.scss @@ -301,7 +301,7 @@ app-fido2-v1 { margin-top: 32px; .subtitle { - font-family: Open Sans; + font-family: "DM Sans"; font-size: 24px; font-style: normal; font-weight: 600; diff --git a/apps/browser/src/popup/scss/tailwind.css b/apps/browser/src/popup/scss/tailwind.css index 7e12c1d6770..0d2a88a9667 100644 --- a/apps/browser/src/popup/scss/tailwind.css +++ b/apps/browser/src/popup/scss/tailwind.css @@ -3,3 +3,24 @@ @tailwind utilities; @import "../../../../../libs/components/src/tw-theme.css"; + +@layer components { + /** Safari Support */ + html.browser_safari .tw-styled-scrollbar::-webkit-scrollbar { + @apply tw-overflow-auto; + } + html.browser_safari .tw-styled-scrollbar::-webkit-scrollbar-thumb { + @apply tw-bg-secondary-500 tw-rounded-lg tw-border-4 tw-border-solid tw-border-transparent tw-bg-clip-content; + } + html.browser_safari .tw-styled-scrollbar::-webkit-scrollbar-track { + @apply tw-bg-background-alt; + } + html.browser_safari .tw-styled-scrollbar::-webkit-scrollbar-thumb:hover { + @apply tw-bg-secondary-600; + } + + /* FireFox & Chrome support */ + html:not(.browser_safari) .tw-styled-scrollbar { + scrollbar-color: rgb(var(--color-secondary-500)) rgb(var(--color-background-alt)); + } +} diff --git a/apps/browser/src/popup/scss/variables.scss b/apps/browser/src/popup/scss/variables.scss index a4366a7415e..cfd61cd6a2b 100644 --- a/apps/browser/src/popup/scss/variables.scss +++ b/apps/browser/src/popup/scss/variables.scss @@ -2,9 +2,9 @@ $dark-icon-themes: "theme_dark", "theme_solarizedDark", "theme_nord"; -$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; -$font-size-base: 14px; +$font-size-base: 16px; $font-size-large: 18px; $font-size-xlarge: 22px; $font-size-xxlarge: 28px; diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault/vault-v2.component.html index 04b6bed469b..8c651e882c5 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault/vault-v2.component.html @@ -58,7 +58,11 @@
-
+
- Loading... diff --git a/apps/desktop/src/scss/variables.scss b/apps/desktop/src/scss/variables.scss index 6f8112b1ca0..23a4644d3da 100644 --- a/apps/desktop/src/scss/variables.scss +++ b/apps/desktop/src/scss/variables.scss @@ -2,7 +2,7 @@ $dark-icon-themes: "theme_dark", "theme_nord"; -$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; $font-size-base: 14px; $font-size-large: 18px; diff --git a/apps/web/src/404.html b/apps/web/src/404.html index 817bfe30985..1a01aee40c7 100644 --- a/apps/web/src/404.html +++ b/apps/web/src/404.html @@ -30,7 +30,7 @@ Go to your web vault - diff --git a/libs/components/src/dialog/dialog/dialog.mdx b/libs/components/src/dialog/dialog/dialog.mdx index d7ccd423da9..c05bdd45445 100644 --- a/libs/components/src/dialog/dialog/dialog.mdx +++ b/libs/components/src/dialog/dialog/dialog.mdx @@ -75,3 +75,10 @@ loading state. Use tabs to separate related content within a dialog. + +## Background Color + +The `background` input can be set to `alt` to change the background color. This is useful for +dialogs that contain multiple card sections. + + diff --git a/libs/components/src/dialog/dialogs.mdx b/libs/components/src/dialog/dialogs.mdx index 9a63e1301b0..b5861aefe5c 100644 --- a/libs/components/src/dialog/dialogs.mdx +++ b/libs/components/src/dialog/dialogs.mdx @@ -4,6 +4,10 @@ import * as stories from "./dialog.service.stories"; +```ts +import { DialogModule } from "@bitwarden/components"; +``` + # Dialog Dialogs are used throughout the app to help the user focus on a specific action. diff --git a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts index 6c2a25c67ca..7b7d46ee96f 100644 --- a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts @@ -1,4 +1,5 @@ import { Component } from "@angular/core"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -144,7 +145,7 @@ export default { component: StoryDialogComponent, decorators: [ moduleMetadata({ - imports: [ButtonModule, DialogModule, CalloutModule], + imports: [ButtonModule, BrowserAnimationsModule, DialogModule, CalloutModule], }), applicationConfig({ providers: [ diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.component.html b/libs/components/src/dialog/simple-dialog/simple-dialog.component.html index efbc4343cd7..0b56c6287dc 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.component.html +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.component.html @@ -1,5 +1,5 @@
@@ -11,13 +11,15 @@

diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts index 08ca6f7dee8..665d658f8ee 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts @@ -1,5 +1,6 @@ -import { DialogModule, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -8,11 +9,8 @@ import { ButtonModule } from "../../button"; import { IconButtonModule } from "../../icon-button"; import { SharedModule } from "../../shared/shared.module"; import { I18nMockService } from "../../utils/i18n-mock.service"; +import { DialogModule } from "../dialog.module"; import { DialogService } from "../dialog.service"; -import { DialogCloseDirective } from "../directives/dialog-close.directive"; -import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive"; - -import { SimpleDialogComponent } from "./simple-dialog.component"; interface Animal { animal: string; @@ -65,13 +63,14 @@ export default { component: StoryDialogComponent, decorators: [ moduleMetadata({ - declarations: [ - StoryDialogContentComponent, - DialogCloseDirective, - DialogTitleContainerDirective, - SimpleDialogComponent, + declarations: [StoryDialogContentComponent], + imports: [ + SharedModule, + IconButtonModule, + ButtonModule, + BrowserAnimationsModule, + DialogModule, ], - imports: [SharedModule, IconButtonModule, ButtonModule, DialogModule], providers: [ DialogService, { diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts index 34dedaaec0d..d86b56101b0 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts @@ -1,17 +1,17 @@ +import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { ButtonModule } from "../../button"; -import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive"; +import { DialogModule } from "../dialog.module"; -import { IconDirective, SimpleDialogComponent } from "./simple-dialog.component"; +import { SimpleDialogComponent } from "./simple-dialog.component"; export default { title: "Component Library/Dialogs/Simple Dialog", component: SimpleDialogComponent, decorators: [ moduleMetadata({ - imports: [ButtonModule], - declarations: [IconDirective, DialogTitleContainerDirective], + imports: [ButtonModule, NoopAnimationsModule, DialogModule], }), ], parameters: { diff --git a/libs/components/src/form-control/form-control.component.html b/libs/components/src/form-control/form-control.component.html index 91bba1719df..c2270ab79a6 100644 --- a/libs/components/src/form-control/form-control.component.html +++ b/libs/components/src/form-control/form-control.component.html @@ -1,13 +1,19 @@ -