From 0516ca00dc9d22dbe3856bd5b82cf261b5e8f14c Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:48:44 -0500 Subject: [PATCH 1/7] update trial initiation text to only represent the product (#11171) Co-authored-by: Alec Rippberger --- .../resolver/free-trial-text.resolver.spec.ts | 55 ++++++------------- .../resolver/free-trial-text.resolver.ts | 31 ++--------- apps/web/src/locales/en/messages.json | 39 ++----------- 3 files changed, 27 insertions(+), 98 deletions(-) diff --git a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts b/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts index f9831c6dc4..2c58d614c5 100644 --- a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts +++ b/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts @@ -1,6 +1,6 @@ import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; -import { ProductTierType, ProductType } from "@bitwarden/common/billing/enums"; +import { ProductType } from "@bitwarden/common/billing/enums"; import { freeTrialTextResolver } from "./free-trial-text.resolver"; @@ -11,48 +11,25 @@ const route = { const routerStateSnapshot = {} as RouterStateSnapshot; describe("freeTrialTextResolver", () => { - [ - { - param: ProductType.PasswordManager, - keyBase: "startYour7DayFreeTrialOfBitwardenPasswordManager", - }, - { - param: ProductType.SecretsManager, - keyBase: "startYour7DayFreeTrialOfBitwardenSecretsManager", - }, - { - param: `${ProductType.PasswordManager},${ProductType.SecretsManager}`, - keyBase: "startYour7DayFreeTrialOfBitwarden", - }, - ].forEach(({ param, keyBase }) => { - describe(`when product is ${param}`, () => { - beforeEach(() => { - route.queryParams.product = `${param}`; - }); + it("shows password manager text", () => { + route.queryParams.product = `${ProductType.PasswordManager}`; - it("returns teams trial text", () => { - route.queryParams.productTier = ProductTierType.Teams; + expect(freeTrialTextResolver(route, routerStateSnapshot)).toBe( + "continueSettingUpFreeTrialPasswordManager", + ); + }); - expect(freeTrialTextResolver(route, routerStateSnapshot)).toBe(`${keyBase}ForTeams`); - }); + it("shows secret manager text", () => { + route.queryParams.product = `${ProductType.SecretsManager}`; - it("returns enterprise trial text", () => { - route.queryParams.productTier = ProductTierType.Enterprise; + expect(freeTrialTextResolver(route, routerStateSnapshot)).toBe( + "continueSettingUpFreeTrialSecretsManager", + ); + }); - expect(freeTrialTextResolver(route, routerStateSnapshot)).toBe(`${keyBase}ForEnterprise`); - }); + it("shows default text", () => { + route.queryParams.product = `${ProductType.PasswordManager},${ProductType.SecretsManager}`; - it("returns families trial text", () => { - route.queryParams.productTier = ProductTierType.Families; - - expect(freeTrialTextResolver(route, routerStateSnapshot)).toBe(`${keyBase}ForFamilies`); - }); - - it("returns default trial text", () => { - route.queryParams.productTier = ""; - - expect(freeTrialTextResolver(route, routerStateSnapshot)).toBe(keyBase); - }); - }); + expect(freeTrialTextResolver(route, routerStateSnapshot)).toBe("continueSettingUpFreeTrial"); }); }); diff --git a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts b/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts index 7dec807fd4..cd23ef5ff7 100644 --- a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts +++ b/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts @@ -1,43 +1,22 @@ import { ActivatedRouteSnapshot, ResolveFn } from "@angular/router"; -import { ProductType, ProductTierType } from "@bitwarden/common/billing/enums"; +import { ProductType } from "@bitwarden/common/billing/enums"; export const freeTrialTextResolver: ResolveFn = ( route: ActivatedRouteSnapshot, ): string | null => { - const { product, productTier } = route.queryParams; + const { product } = route.queryParams; const products: ProductType[] = (product ?? "").split(",").map((p: string) => parseInt(p)); const onlyPasswordManager = products.length === 1 && products[0] === ProductType.PasswordManager; const onlySecretsManager = products.length === 1 && products[0] === ProductType.SecretsManager; - const forTeams = parseInt(productTier) === ProductTierType.Teams; - const forEnterprise = parseInt(productTier) === ProductTierType.Enterprise; - const forFamilies = parseInt(productTier) === ProductTierType.Families; switch (true) { - case onlyPasswordManager && forTeams: - return "startYour7DayFreeTrialOfBitwardenPasswordManagerForTeams"; - case onlyPasswordManager && forEnterprise: - return "startYour7DayFreeTrialOfBitwardenPasswordManagerForEnterprise"; - case onlyPasswordManager && forFamilies: - return "startYour7DayFreeTrialOfBitwardenPasswordManagerForFamilies"; case onlyPasswordManager: - return "startYour7DayFreeTrialOfBitwardenPasswordManager"; - case onlySecretsManager && forTeams: - return "startYour7DayFreeTrialOfBitwardenSecretsManagerForTeams"; - case onlySecretsManager && forEnterprise: - return "startYour7DayFreeTrialOfBitwardenSecretsManagerForEnterprise"; - case onlySecretsManager && forFamilies: - return "startYour7DayFreeTrialOfBitwardenSecretsManagerForFamilies"; + return "continueSettingUpFreeTrialPasswordManager"; case onlySecretsManager: - return "startYour7DayFreeTrialOfBitwardenSecretsManager"; - case forTeams: - return "startYour7DayFreeTrialOfBitwardenForTeams"; - case forEnterprise: - return "startYour7DayFreeTrialOfBitwardenForEnterprise"; - case forFamilies: - return "startYour7DayFreeTrialOfBitwardenForFamilies"; + return "continueSettingUpFreeTrialSecretsManager"; default: - return "startYour7DayFreeTrialOfBitwarden"; + return "continueSettingUpFreeTrial"; } }; diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index cc2754b6d6..52291a20a7 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -8683,41 +8683,14 @@ "manageBillingFromProviderPortalMessage": { "message": "Manage billing from the Provider Portal" }, - "startYour7DayFreeTrialOfBitwarden": { - "message": "Start your 7-Day free trial of Bitwarden" + "continueSettingUpFreeTrial": { + "message": "Continue setting up your free trial of Bitwarden" }, - "startYour7DayFreeTrialOfBitwardenForTeams": { - "message": "Start your 7-Day free trial of Bitwarden for Teams" + "continueSettingUpFreeTrialPasswordManager": { + "message": "Continue setting up your free trial of Bitwarden Password Manager" }, - "startYour7DayFreeTrialOfBitwardenForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden for Families" - }, - "startYour7DayFreeTrialOfBitwardenForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden for Enterprise" - }, - "startYour7DayFreeTrialOfBitwardenSecretsManager": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager" - }, - "startYour7DayFreeTrialOfBitwardenSecretsManagerForTeams": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Teams" - }, - "startYour7DayFreeTrialOfBitwardenSecretsManagerForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Families" - }, - "startYour7DayFreeTrialOfBitwardenSecretsManagerForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Enterprise" - }, - "startYour7DayFreeTrialOfBitwardenPasswordManager": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager" - }, - "startYour7DayFreeTrialOfBitwardenPasswordManagerForTeams": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager for Teams" - }, - "startYour7DayFreeTrialOfBitwardenPasswordManagerForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager for Families" - }, - "startYour7DayFreeTrialOfBitwardenPasswordManagerForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden Password Manager for Enterprise" + "continueSettingUpFreeTrialSecretsManager": { + "message": "Continue setting up your free trial of Bitwarden Secrets Manager" }, "enterTeamsOrgInfo": { "message": "Enter your Teams organization information" From 972339be837e4757bbca92d0202673eba09d671d Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:46:00 -0400 Subject: [PATCH 2/7] Remove Storage Reseed FF (#11156) --- .../browser/src/background/main.background.ts | 17 +- .../src/background/runtime.background.ts | 19 +- .../browser-local-storage.service.spec.ts | 192 ------------------ .../services/browser-local-storage.service.ts | 132 +----------- libs/common/src/enums/feature-flag.enum.ts | 2 - 5 files changed, 6 insertions(+), 356 deletions(-) delete mode 100644 apps/browser/src/platform/services/browser-local-storage.service.spec.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 62d83b1900..18883a5fe5 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1484,14 +1484,7 @@ export default class MainBackground { }); if (needStorageReseed) { - await this.reseedStorage( - await firstValueFrom( - this.configService.userCachedFeatureFlag$( - FeatureFlag.StorageReseedRefactor, - userBeingLoggedOut, - ), - ), - ); + await this.reseedStorage(); } if (BrowserApi.isManifestVersion(3)) { @@ -1546,7 +1539,7 @@ export default class MainBackground { await SafariApp.sendMessageToApp("showPopover", null, true); } - async reseedStorage(doFillBuffer: boolean) { + async reseedStorage() { if ( !this.platformUtilsService.isChrome() && !this.platformUtilsService.isVivaldi() && @@ -1555,11 +1548,7 @@ export default class MainBackground { return; } - if (doFillBuffer) { - await this.storageService.fillBuffer(); - } else { - await this.storageService.reseed(); - } + await this.storageService.fillBuffer(); } async clearClipboard(clipboardValue: string, clearMs: number) { diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 1ec7edcc30..3e0933942b 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -1,4 +1,4 @@ -import { firstValueFrom, map, mergeMap, of, switchMap } from "rxjs"; +import { firstValueFrom, map, mergeMap } from "rxjs"; import { LockService } from "@bitwarden/auth/common"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; @@ -281,22 +281,7 @@ export default class RuntimeBackground { await this.main.refreshMenu(); break; case "bgReseedStorage": { - const doFillBuffer = await firstValueFrom( - this.accountService.activeAccount$.pipe( - switchMap((account) => { - if (account == null) { - return of(false); - } - - return this.configService.userCachedFeatureFlag$( - FeatureFlag.StorageReseedRefactor, - account.id, - ); - }), - ), - ); - - await this.main.reseedStorage(doFillBuffer); + await this.main.reseedStorage(); break; } case "authResult": { diff --git a/apps/browser/src/platform/services/browser-local-storage.service.spec.ts b/apps/browser/src/platform/services/browser-local-storage.service.spec.ts deleted file mode 100644 index 13e26c26dd..0000000000 --- a/apps/browser/src/platform/services/browser-local-storage.service.spec.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { objToStore } from "./abstractions/abstract-chrome-storage-api.service"; -import BrowserLocalStorageService, { - RESEED_IN_PROGRESS_KEY, -} from "./browser-local-storage.service"; - -const apiGetLike = - (store: Record) => (key: string, callback: (items: { [key: string]: any }) => void) => { - if (key == null) { - callback(store); - } else { - callback({ [key]: store[key] }); - } - }; - -describe("BrowserLocalStorageService", () => { - let service: BrowserLocalStorageService; - let store: Record; - let changeListener: (changes: { [key: string]: chrome.storage.StorageChange }) => void; - - let saveMock: jest.Mock; - let getMock: jest.Mock; - let clearMock: jest.Mock; - let removeMock: jest.Mock; - - beforeEach(() => { - store = {}; - - // Record change listener - chrome.storage.local.onChanged.addListener = jest.fn((listener) => { - changeListener = listener; - }); - - service = new BrowserLocalStorageService(); - - // setup mocks - getMock = chrome.storage.local.get as jest.Mock; - getMock.mockImplementation(apiGetLike(store)); - saveMock = chrome.storage.local.set as jest.Mock; - saveMock.mockImplementation((update, callback) => { - Object.entries(update).forEach(([key, value]) => { - store[key] = value; - }); - callback(); - }); - clearMock = chrome.storage.local.clear as jest.Mock; - clearMock.mockImplementation((callback) => { - store = {}; - callback?.(); - }); - removeMock = chrome.storage.local.remove as jest.Mock; - removeMock.mockImplementation((keys, callback) => { - if (Array.isArray(keys)) { - keys.forEach((key) => { - delete store[key]; - }); - } else { - delete store[keys]; - } - - callback(); - }); - }); - - afterEach(() => { - chrome.runtime.lastError = undefined; - jest.resetAllMocks(); - }); - - describe("reseed", () => { - it.each([ - { - key1: objToStore("value1"), - key2: objToStore("value2"), - key3: null, - }, - {}, - ])("saves all data in storage %s", async (testStore) => { - for (const key of Object.keys(testStore) as Array) { - store[key] = testStore[key]; - } - await service.reseed(); - - expect(saveMock).toHaveBeenLastCalledWith( - { ...testStore, [RESEED_IN_PROGRESS_KEY]: objToStore(true) }, - expect.any(Function), - ); - }); - - it.each([ - { - key1: objToStore("value1"), - key2: objToStore("value2"), - key3: null, - }, - {}, - ])("results in the same store %s", async (testStore) => { - for (const key of Object.keys(testStore) as Array) { - store[key] = testStore[key]; - } - await service.reseed(); - - expect(store).toEqual(testStore); - }); - - it("converts non-serialized values to serialized", async () => { - store.key1 = "value1"; - store.key2 = "value2"; - - const expectedStore = { - key1: objToStore("value1"), - key2: objToStore("value2"), - reseedInProgress: objToStore(true), - }; - - await service.reseed(); - - expect(saveMock).toHaveBeenLastCalledWith(expectedStore, expect.any(Function)); - }); - - it("clears data", async () => { - await service.reseed(); - - expect(clearMock).toHaveBeenCalledTimes(1); - }); - - it("throws if get has chrome.runtime.lastError", async () => { - getMock.mockImplementation((key, callback) => { - chrome.runtime.lastError = new Error("Get Test Error"); - callback(); - }); - - await expect(async () => await service.reseed()).rejects.toThrow("Get Test Error"); - }); - - it("throws if save has chrome.runtime.lastError", async () => { - saveMock.mockImplementation((obj, callback) => { - chrome.runtime.lastError = new Error("Save Test Error"); - callback(); - }); - - await expect(async () => await service.reseed()).rejects.toThrow("Save Test Error"); - }); - }); - - describe.each(["get", "has", "save", "remove"] as const)("%s", (method) => { - let interval: string | number | NodeJS.Timeout; - - afterEach(() => { - if (interval) { - clearInterval(interval); - } - }); - - function startReseed() { - store[RESEED_IN_PROGRESS_KEY] = objToStore(true); - } - - function endReseed() { - delete store[RESEED_IN_PROGRESS_KEY]; - changeListener({ reseedInProgress: { oldValue: true } }); - } - - it("waits for reseed prior to operation", async () => { - startReseed(); - - const promise = service[method]("key", "value"); // note "value" is only used in save, but ignored in other methods - - await expect(promise).not.toBeFulfilled(10); - - endReseed(); - - await expect(promise).toBeResolved(); - }); - - it("does not wait if reseed is not in progress", async () => { - const promise = service[method]("key", "value"); - await expect(promise).toBeResolved(1); - }); - - it("awaits prior reseed operations before starting a new one", async () => { - startReseed(); - - const promise = service.reseed(); - - await expect(promise).not.toBeFulfilled(10); - - endReseed(); - - await expect(promise).toBeResolved(); - }); - }); -}); diff --git a/apps/browser/src/platform/services/browser-local-storage.service.ts b/apps/browser/src/platform/services/browser-local-storage.service.ts index 15cf26d1fb..61a2653f13 100644 --- a/apps/browser/src/platform/services/browser-local-storage.service.ts +++ b/apps/browser/src/platform/services/browser-local-storage.service.ts @@ -1,35 +1,8 @@ -import { defer, filter, firstValueFrom, map, merge, throwError, timeout } from "rxjs"; - -import AbstractChromeStorageService, { - SerializedValue, - objToStore, -} from "./abstractions/abstract-chrome-storage-api.service"; - -export const RESEED_IN_PROGRESS_KEY = "reseedInProgress"; +import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service"; export default class BrowserLocalStorageService extends AbstractChromeStorageService { constructor() { super(chrome.storage.local); - this.chromeStorageApi.remove(RESEED_IN_PROGRESS_KEY, () => { - return; - }); - } - - /** - * Reads, clears, and re-saves all data in local storage. This is a hack to remove previously stored sensitive data from - * local storage logs. - * - * @see https://github.com/bitwarden/clients/issues/485 - */ - async reseed(): Promise { - try { - await this.save(RESEED_IN_PROGRESS_KEY, true); - const data = await this.getAll(); - await this.clear(); - await this.saveAll(data); - } finally { - await super.remove(RESEED_IN_PROGRESS_KEY); - } } async fillBuffer() { @@ -71,107 +44,4 @@ export default class BrowserLocalStorageService extends AbstractChromeStorageSer ); }); } - - override async get(key: string): Promise { - await this.awaitReseed(); - return super.get(key); - } - - override async has(key: string): Promise { - await this.awaitReseed(); - return super.has(key); - } - - override async save(key: string, obj: any): Promise { - await this.awaitReseed(); - return super.save(key, obj); - } - - override async remove(key: string): Promise { - await this.awaitReseed(); - return super.remove(key); - } - - private async awaitReseed(): Promise { - const notReseeding = async () => { - return !(await super.get(RESEED_IN_PROGRESS_KEY)); - }; - - const finishedReseeding = this.updates$.pipe( - filter(({ key, updateType }) => key === RESEED_IN_PROGRESS_KEY && updateType === "remove"), - map(() => true), - ); - - await firstValueFrom( - merge(defer(notReseeding), finishedReseeding).pipe( - filter((v) => v), - timeout({ - // We eventually need to give up and throw an error - first: 5_000, - with: () => - throwError( - () => new Error("Reseeding local storage did not complete in a timely manner."), - ), - }), - ), - ); - } - - /** - * Clears local storage - */ - private async clear() { - return new Promise((resolve, reject) => { - this.chromeStorageApi.clear(() => { - if (chrome.runtime.lastError) { - return reject(chrome.runtime.lastError); - } - resolve(); - }); - }); - } - - /** - * Retrieves all objects stored in local storage. - * - * @remarks This method processes values prior to resolving, do not use `chrome.storage.local` directly - * @returns Promise resolving to keyed object of all stored data - */ - private async getAll(): Promise> { - return new Promise((resolve, reject) => { - this.chromeStorageApi.get(null, (allStorage) => { - if (chrome.runtime.lastError) { - return reject(chrome.runtime.lastError); - } - - const resolved = Object.entries(allStorage).reduce( - (agg, [key, value]) => { - agg[key] = this.processGetObject(value); - return agg; - }, - {} as Record, - ); - resolve(resolved); - }); - }); - } - - private async saveAll(data: Record): Promise { - return new Promise((resolve, reject) => { - const keyedData = Object.entries(data).reduce( - (agg, [key, value]) => { - agg[key] = objToStore(value); - return agg; - }, - {} as Record, - ); - this.chromeStorageApi.set(keyedData, () => { - if (chrome.runtime.lastError) { - return reject(chrome.runtime.lastError); - } - - resolve(); - }); - }); - } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index e7ece5b36a..3d426aee3b 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -34,7 +34,6 @@ export enum FeatureFlag { AccountDeprovisioning = "pm-10308-account-deprovisioning", NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api", - StorageReseedRefactor = "storage-reseed-refactor", CipherKeyEncryption = "cipher-key-encryption", } @@ -77,7 +76,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, - [FeatureFlag.StorageReseedRefactor]: FALSE, [FeatureFlag.AccountDeprovisioning]: FALSE, [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, [FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE, From ea025b9026b1b35f8ab662d8369192c0fbabccd1 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 20 Sep 2024 08:49:27 -0700 Subject: [PATCH 3/7] add padding to new organization input (#11090) --- .../app/billing/organizations/organization-plans.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.html b/apps/web/src/app/billing/organizations/organization-plans.component.html index 498374aa14..5992af5f54 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.html +++ b/apps/web/src/app/billing/organizations/organization-plans.component.html @@ -11,7 +11,7 @@
{{ "licenseFile" | i18n }} -
+
From cf1f7cc61da2466a4ff6e750a6cdc9b3b1df68c9 Mon Sep 17 00:00:00 2001 From: Robyn MacCallum Date: Fri, 20 Sep 2024 12:54:03 -0400 Subject: [PATCH 4/7] [SM-1302] Initial config page (#10196) * Initial config page * Remove project actions * Add copy projectId method to the project page * Update bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Update bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Update apps/web/src/locales/en/messages.json Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Fix method and string naming * Ensure config component load logic happens after params observed * Remove projectId emitted event * Update bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Adjust load function * Fix config translation * Remove unnecceary async from copy functions * Add project ID translation key * Update bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Simplify load function * Simplify variable definition * Add all machine account projects to the config page * Update bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * Remove unused variable * Remove revision date in config project list --------- Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> --- apps/web/src/locales/en/messages.json | 18 +++ .../models/view/project-list.view.ts | 1 + .../projects/project.service.ts | 1 + .../config/config.component.html | 47 +++++++ .../config/config.component.ts | 127 ++++++++++++++++++ .../service-account.component.html | 1 + .../service-accounts-routing.module.ts | 5 + .../service-accounts.module.ts | 2 + .../shared/projects-list.component.html | 36 ++++- .../shared/projects-list.component.ts | 12 ++ .../shared/sm-shared.module.ts | 6 + 11 files changed, 249 insertions(+), 7 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 52291a20a7..06d42c4265 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -9002,6 +9002,24 @@ "purchasedSeatsRemoved": { "message": "purchased seats removed" }, + "environmentVariables": { + "message": "Environment variables" + }, + "organizationId": { + "message": "Organization ID" + }, + "projectIds": { + "message": "Project IDs" + }, + "projectId": { + "message": "Project ID" + }, + "projectsAccessedByMachineAccount": { + "message": "The following projects can be accessed by this machine account." + }, + "config": { + "message": "Config" + }, "learnMoreAboutEmergencyAccess": { "message":"Learn more about emergency access" }, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts index 03a0353352..5f0aa9647c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/project-list.view.ts @@ -6,4 +6,5 @@ export class ProjectListView { revisionDate: string; read: boolean; write: boolean; + linkable: boolean; } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts index 3b4add0c36..282980ece7 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts @@ -131,6 +131,7 @@ export class ProjectService { ); projectListView.creationDate = s.creationDate; projectListView.revisionDate = s.revisionDate; + projectListView.linkable = true; return projectListView; }), ); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.html new file mode 100644 index 0000000000..b17e47a39e --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.html @@ -0,0 +1,47 @@ +
+
+

{{ "environmentVariables" | i18n }}

+
+ + {{ "identityUrl" | i18n }} + + + + + {{ "apiUrl" | i18n }} + + + +
+ + {{ "organizationId" | i18n }} + + + +
+
+

{{ "projectIds" | i18n }}

+

{{ "projectsNoItemsTitle" | i18n }}

+

{{ "projectsAccessedByMachineAccount" | i18n }}

+ +
+
+
+ +
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts new file mode 100644 index 0000000000..47deeb2a41 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts @@ -0,0 +1,127 @@ +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute, Params } from "@angular/router"; +import { Subject, concatMap, takeUntil } from "rxjs"; + +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ToastService } from "@bitwarden/components"; + +import { ProjectListView } from "../../models/view/project-list.view"; +import { ProjectService } from "../../projects/project.service"; +import { AccessPolicyService } from "../../shared/access-policies/access-policy.service"; + +class ServiceAccountConfig { + organizationId: string; + serviceAccountId: string; + identityUrl: string; + apiUrl: string; + projects: ProjectListView[]; +} + +@Component({ + selector: "sm-service-account-config", + templateUrl: "./config.component.html", +}) +export class ServiceAccountConfigComponent implements OnInit, OnDestroy { + identityUrl: string; + apiUrl: string; + organizationId: string; + serviceAccountId: string; + projects: ProjectListView[]; + hasProjects = false; + + private destroy$ = new Subject(); + loading = true; + + constructor( + private environmentService: EnvironmentService, + private route: ActivatedRoute, + private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, + private i18nService: I18nService, + private projectService: ProjectService, + private accessPolicyService: AccessPolicyService, + ) {} + + async ngOnInit() { + this.route.params + .pipe( + concatMap(async (params: Params) => { + return await this.load(params.organizationId, params.serviceAccountId); + }), + takeUntil(this.destroy$), + ) + .subscribe((smConfig) => { + this.identityUrl = smConfig.identityUrl; + this.apiUrl = smConfig.apiUrl; + this.organizationId = smConfig.organizationId; + this.serviceAccountId = smConfig.serviceAccountId; + this.projects = smConfig.projects; + + this.hasProjects = smConfig.projects.length > 0; + this.loading = false; + }); + } + + async load(organizationId: string, serviceAccountId: string): Promise { + const environment = await this.environmentService.getEnvironment(); + + const allProjects = await this.projectService.getProjects(organizationId); + const policies = await this.accessPolicyService.getServiceAccountGrantedPolicies( + organizationId, + serviceAccountId, + ); + + const projects = policies.grantedProjectPolicies.map((policy) => { + return { + id: policy.accessPolicy.grantedProjectId, + name: policy.accessPolicy.grantedProjectName, + organizationId: organizationId, + linkable: allProjects.some( + (project) => project.id === policy.accessPolicy.grantedProjectId, + ), + } as ProjectListView; + }); + + return { + organizationId: organizationId, + serviceAccountId: serviceAccountId, + identityUrl: environment.getIdentityUrl(), + apiUrl: environment.getApiUrl(), + projects: projects, + } as ServiceAccountConfig; + } + + copyIdentityUrl = () => { + this.platformUtilsService.copyToClipboard(this.identityUrl); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t("identityUrl")), + }); + }; + + copyApiUrl = () => { + this.platformUtilsService.copyToClipboard(this.apiUrl); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t("apiUrl")), + }); + }; + + copyOrganizationId = () => { + this.platformUtilsService.copyToClipboard(this.organizationId); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t("organizationId")), + }); + }; + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html index c9ce8d8c64..392bfcb806 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html @@ -29,6 +29,7 @@
{{ "eventLogs" | i18n }} + {{ "config" | i18n }} - +
- {{ - project.name - }} +
+ {{ project.name }} + {{ project.name }} +
+ {{ project.id }} + +
+
- {{ project.revisionDate | date: "medium" }} + + {{ project.revisionDate | date: "medium" }} + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts index 8c6a5e97dc..2d01908a36 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts @@ -24,6 +24,8 @@ export class ProjectsListComponent { } private _projects: ProjectListView[]; + @Input() showMenus?: boolean = true; + @Input() set search(search: string) { this.selection.clear(); @@ -33,6 +35,7 @@ export class ProjectsListComponent { @Output() editProjectEvent = new EventEmitter(); @Output() deleteProjectEvent = new EventEmitter(); @Output() newProjectEvent = new EventEmitter(); + @Output() copiedProjectUUIdEvent = new EventEmitter(); selection = new SelectionModel(true, []); protected dataSource = new TableDataSource(); @@ -90,4 +93,13 @@ export class ProjectsListComponent { } return false; } + + copyProjectUuidToClipboard(id: string) { + this.platformUtilsService.copyToClipboard(id); + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("valueCopied", this.i18nService.t("projectId")), + ); + } } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts index cb723af6d7..d778db9814 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts @@ -1,10 +1,12 @@ import { NgModule } from "@angular/core"; import { + CardComponent, MultiSelectModule, SearchModule, SelectModule, NoItemsModule, + FormFieldModule, } from "@bitwarden/components"; import { CoreOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/core"; import { DynamicAvatarComponent } from "@bitwarden/web-vault/app/components/dynamic-avatar.component"; @@ -31,17 +33,21 @@ import { SecretsListComponent } from "./secrets-list.component"; DynamicAvatarComponent, SearchModule, HeaderModule, + CardComponent, + FormFieldModule, ], exports: [ AccessPolicySelectorComponent, BulkConfirmationDialogComponent, BulkStatusDialogComponent, + FormFieldModule, HeaderModule, NewMenuComponent, NoItemsModule, ProjectsListComponent, SearchModule, SecretsListComponent, + CardComponent, SelectModule, SharedModule, ], From 9b352c0cd8eadf2e50452703552e086879ff699a Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Fri, 20 Sep 2024 12:59:48 -0400 Subject: [PATCH 5/7] fix typography of change shortcut section text (#11163) --- .../browser/src/autofill/popup/settings/autofill.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.html b/apps/browser/src/autofill/popup/settings/autofill.component.html index 77f96612c8..81be74f7dd 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.html +++ b/apps/browser/src/autofill/popup/settings/autofill.component.html @@ -107,7 +107,7 @@