From 57c5c46cf7653396962a80d639719e6bba8e75c1 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 25 Sep 2024 09:44:26 -0400
Subject: [PATCH 01/11] [deps] Platform: Update @types/chrome to v0.0.272
(#11057)
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 57b3aa028fb..97dee9ef983 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -95,7 +95,7 @@
"@storybook/manager-api": "8.2.9",
"@storybook/theming": "8.2.9",
"@types/argon2-browser": "1.18.4",
- "@types/chrome": "0.0.270",
+ "@types/chrome": "0.0.272",
"@types/firefox-webext-browser": "111.0.5",
"@types/inquirer": "8.2.10",
"@types/jest": "29.5.12",
@@ -8901,9 +8901,9 @@
}
},
"node_modules/@types/chrome": {
- "version": "0.0.270",
- "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.270.tgz",
- "integrity": "sha512-ADvkowV7YnJfycZZxL2brluZ6STGW+9oKG37B422UePf2PCXuFA/XdERI0T18wtuWPx0tmFeZqq6MOXVk1IC+Q==",
+ "version": "0.0.272",
+ "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.272.tgz",
+ "integrity": "sha512-9cxDmmgyhXV8gsZvlRjqaDizNjIjbV0spsR0fIEaQUoHtbl9D8VkTOLyONgiBKK+guR38x5eMO3E3avUYOXwcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index b073cd27d87..71fb63c37d6 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
"@storybook/manager-api": "8.2.9",
"@storybook/theming": "8.2.9",
"@types/argon2-browser": "1.18.4",
- "@types/chrome": "0.0.270",
+ "@types/chrome": "0.0.272",
"@types/firefox-webext-browser": "111.0.5",
"@types/inquirer": "8.2.10",
"@types/jest": "29.5.12",
From 742900a663ec7cbc3d2ef02ae122ad08553e2c46 Mon Sep 17 00:00:00 2001
From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com>
Date: Wed, 25 Sep 2024 09:45:13 -0500
Subject: [PATCH 02/11] PM-11390: [Defect] View Login - Clicking Password
History opens Edit Item window behind View Login window (#11119)
* Add password dialog component.
* Properly direct to browser password history screen.
* Add padding to history items.
* Update test to correct password history route.
* Remove unneeded provider.
* Use relative path for SharedModule.
---
.../vault-v2/view-v2/view-v2.component.ts | 18 ++-
...wser-view-password-history.service.spec.ts | 28 ++++
.../browser-view-password-history.service.ts | 18 +++
.../password-history.component.html | 40 ++++++
.../password-history.component.ts | 131 ++++++++++++++++++
.../vault/individual-vault/view.component.ts | 3 +
.../web-view-password-history.service.spec.ts | 45 ++++++
.../web-view-password-history.service.ts | 23 +++
.../view-password-history.service.ts | 8 ++
.../item-history-v2.component.html | 6 +-
.../item-history/item-history-v2.component.ts | 11 ++
11 files changed, 320 insertions(+), 11 deletions(-)
create mode 100644 apps/browser/src/vault/popup/services/browser-view-password-history.service.spec.ts
create mode 100644 apps/browser/src/vault/popup/services/browser-view-password-history.service.ts
create mode 100644 apps/web/src/app/vault/individual-vault/password-history.component.html
create mode 100644 apps/web/src/app/vault/individual-vault/password-history.component.ts
create mode 100644 apps/web/src/app/vault/services/web-view-password-history.service.spec.ts
create mode 100644 apps/web/src/app/vault/services/web-view-password-history.service.ts
create mode 100644 libs/common/src/vault/abstractions/view-password-history.service.ts
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 a02713c53f0..107447c50ac 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
@@ -14,6 +14,7 @@ import { EventType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
+import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
@@ -30,20 +31,19 @@ import {
import { PremiumUpgradePromptService } from "../../../../../../../../libs/common/src/vault/abstractions/premium-upgrade-prompt.service";
import { CipherViewComponent } from "../../../../../../../../libs/vault/src/cipher-view";
import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component";
-import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component";
-import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component";
-import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component";
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
import { BrowserPremiumUpgradePromptService } from "../../../services/browser-premium-upgrade-prompt.service";
-import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
+import { BrowserViewPasswordHistoryService } from "../../../services/browser-view-password-history.service";
+
+import { PopupFooterComponent } from "./../../../../../platform/popup/layout/popup-footer.component";
+import { PopupHeaderComponent } from "./../../../../../platform/popup/layout/popup-header.component";
+import { PopupPageComponent } from "./../../../../../platform/popup/layout/popup-page.component";
+import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service";
@Component({
selector: "app-view-v2",
templateUrl: "view-v2.component.html",
standalone: true,
- providers: [
- { provide: PremiumUpgradePromptService, useClass: BrowserPremiumUpgradePromptService },
- ],
imports: [
CommonModule,
SearchModule,
@@ -58,6 +58,10 @@ import { VaultPopupAutofillService } from "../../../services/vault-popup-autofil
AsyncActionsModule,
PopOutComponent,
],
+ providers: [
+ { provide: ViewPasswordHistoryService, useClass: BrowserViewPasswordHistoryService },
+ { provide: PremiumUpgradePromptService, useClass: BrowserPremiumUpgradePromptService },
+ ],
})
export class ViewV2Component {
headerText: string;
diff --git a/apps/browser/src/vault/popup/services/browser-view-password-history.service.spec.ts b/apps/browser/src/vault/popup/services/browser-view-password-history.service.spec.ts
new file mode 100644
index 00000000000..ded4686477e
--- /dev/null
+++ b/apps/browser/src/vault/popup/services/browser-view-password-history.service.spec.ts
@@ -0,0 +1,28 @@
+import { TestBed } from "@angular/core/testing";
+import { Router } from "@angular/router";
+import { mock, MockProxy } from "jest-mock-extended";
+
+import { BrowserViewPasswordHistoryService } from "./browser-view-password-history.service";
+
+describe("BrowserViewPasswordHistoryService", () => {
+ let service: BrowserViewPasswordHistoryService;
+ let router: MockProxy;
+
+ beforeEach(async () => {
+ router = mock();
+ await TestBed.configureTestingModule({
+ providers: [BrowserViewPasswordHistoryService, { provide: Router, useValue: router }],
+ }).compileComponents();
+
+ service = TestBed.inject(BrowserViewPasswordHistoryService);
+ });
+
+ describe("viewPasswordHistory", () => {
+ it("navigates to the password history screen", async () => {
+ await service.viewPasswordHistory("test");
+ expect(router.navigate).toHaveBeenCalledWith(["/cipher-password-history"], {
+ queryParams: { cipherId: "test" },
+ });
+ });
+ });
+});
diff --git a/apps/browser/src/vault/popup/services/browser-view-password-history.service.ts b/apps/browser/src/vault/popup/services/browser-view-password-history.service.ts
new file mode 100644
index 00000000000..6b57b0b625e
--- /dev/null
+++ b/apps/browser/src/vault/popup/services/browser-view-password-history.service.ts
@@ -0,0 +1,18 @@
+import { inject } from "@angular/core";
+import { Router } from "@angular/router";
+
+import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
+
+/**
+ * This class handles the premium upgrade process for the browser extension.
+ */
+export class BrowserViewPasswordHistoryService implements ViewPasswordHistoryService {
+ private router = inject(Router);
+
+ /**
+ * Navigates to the password history screen.
+ */
+ async viewPasswordHistory(cipherId: string) {
+ await this.router.navigate(["/cipher-password-history"], { queryParams: { cipherId } });
+ }
+}
diff --git a/apps/web/src/app/vault/individual-vault/password-history.component.html b/apps/web/src/app/vault/individual-vault/password-history.component.html
new file mode 100644
index 00000000000..bae10d85aa7
--- /dev/null
+++ b/apps/web/src/app/vault/individual-vault/password-history.component.html
@@ -0,0 +1,40 @@
+
+
+ {{ "passwordHistory" | i18n }}
+
+
+
+
+
+
+
{{ h.lastUsedDate | date: "medium" }}
+
+
+
+
+
+
+
+
+
+
{{ "noPasswordsInList" | i18n }}
+
+
+
+
+
+
diff --git a/apps/web/src/app/vault/individual-vault/password-history.component.ts b/apps/web/src/app/vault/individual-vault/password-history.component.ts
new file mode 100644
index 00000000000..21a1b01e583
--- /dev/null
+++ b/apps/web/src/app/vault/individual-vault/password-history.component.ts
@@ -0,0 +1,131 @@
+import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
+import { CommonModule } from "@angular/common";
+import { OnInit, Inject, Component } from "@angular/core";
+import { firstValueFrom, map } from "rxjs";
+
+import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
+import { CipherId, UserId } from "@bitwarden/common/types/guid";
+import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
+import { PasswordHistoryView } from "@bitwarden/common/vault/models/view/password-history.view";
+import {
+ AsyncActionsModule,
+ DialogModule,
+ DialogService,
+ ToastService,
+ ItemModule,
+} from "@bitwarden/components";
+
+import { SharedModule } from "../../shared/shared.module";
+
+/**
+ * The parameters for the password history dialog.
+ */
+export interface ViewPasswordHistoryDialogParams {
+ cipherId: CipherId;
+}
+
+/**
+ * A dialog component that displays the password history for a cipher.
+ */
+@Component({
+ selector: "app-vault-password-history",
+ templateUrl: "password-history.component.html",
+ standalone: true,
+ imports: [CommonModule, AsyncActionsModule, DialogModule, ItemModule, SharedModule],
+})
+export class PasswordHistoryComponent implements OnInit {
+ /**
+ * The ID of the cipher to display the password history for.
+ */
+ cipherId: CipherId;
+
+ /**
+ * The password history for the cipher.
+ */
+ history: PasswordHistoryView[] = [];
+
+ /**
+ * The constructor for the password history dialog component.
+ * @param params The parameters passed to the password history dialog.
+ * @param cipherService The cipher service - used to get the cipher to display the password history for.
+ * @param platformUtilsService The platform utils service - used to copy passwords to the clipboard.
+ * @param i18nService The i18n service - used to translate strings.
+ * @param accountService The account service - used to get the active account to decrypt the cipher.
+ * @param win The window object - used to copy passwords to the clipboard.
+ * @param toastService The toast service - used to display feedback to the user when a password is copied.
+ * @param dialogRef The dialog reference - used to close the dialog.
+ **/
+ constructor(
+ @Inject(DIALOG_DATA) public params: ViewPasswordHistoryDialogParams,
+ protected cipherService: CipherService,
+ protected platformUtilsService: PlatformUtilsService,
+ protected i18nService: I18nService,
+ protected accountService: AccountService,
+ @Inject(WINDOW) private win: Window,
+ protected toastService: ToastService,
+ private dialogRef: DialogRef,
+ ) {
+ /**
+ * Set the cipher ID from the parameters.
+ */
+ this.cipherId = params.cipherId;
+ }
+
+ async ngOnInit() {
+ await this.init();
+ }
+
+ /**
+ * Copies a password to the clipboard.
+ * @param password The password to copy.
+ */
+ copy(password: string) {
+ const copyOptions = this.win != null ? { window: this.win } : undefined;
+ this.platformUtilsService.copyToClipboard(password, copyOptions);
+ this.toastService.showToast({
+ variant: "info",
+ title: "",
+ message: this.i18nService.t("valueCopied", this.i18nService.t("password")),
+ });
+ }
+
+ /**
+ * Initializes the password history dialog component.
+ */
+ protected async init() {
+ const cipher = await this.cipherService.get(this.cipherId);
+ const activeAccount = await firstValueFrom(
+ this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)),
+ );
+
+ if (!activeAccount || !activeAccount.id) {
+ throw new Error("Active account is not available.");
+ }
+
+ const activeUserId = activeAccount.id as UserId;
+ const decCipher = await cipher.decrypt(
+ await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
+ );
+ this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory;
+ }
+
+ /**
+ * Closes the password history dialog.
+ */
+ close() {
+ this.dialogRef.close();
+ }
+}
+
+/**
+ * Strongly typed wrapper around the dialog service to open the password history dialog.
+ */
+export function openPasswordHistoryDialog(
+ dialogService: DialogService,
+ config: DialogConfig,
+) {
+ return dialogService.open(PasswordHistoryComponent, config);
+}
diff --git a/apps/web/src/app/vault/individual-vault/view.component.ts b/apps/web/src/app/vault/individual-vault/view.component.ts
index 67a0223c738..ead52d805a8 100644
--- a/apps/web/src/app/vault/individual-vault/view.component.ts
+++ b/apps/web/src/app/vault/individual-vault/view.component.ts
@@ -8,6 +8,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
+import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
@@ -22,6 +23,7 @@ import { PremiumUpgradePromptService } from "../../../../../../libs/common/src/v
import { CipherViewComponent } from "../../../../../../libs/vault/src/cipher-view/cipher-view.component";
import { SharedModule } from "../../shared/shared.module";
import { WebVaultPremiumUpgradePromptService } from "../services/web-premium-upgrade-prompt.service";
+import { WebViewPasswordHistoryService } from "../services/web-view-password-history.service";
export interface ViewCipherDialogParams {
cipher: CipherView;
@@ -57,6 +59,7 @@ export interface ViewCipherDialogCloseResult {
standalone: true,
imports: [CipherViewComponent, CommonModule, AsyncActionsModule, DialogModule, SharedModule],
providers: [
+ { provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService },
{ provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService },
],
})
diff --git a/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts b/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts
new file mode 100644
index 00000000000..2c5fb82c53c
--- /dev/null
+++ b/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts
@@ -0,0 +1,45 @@
+import { Overlay } from "@angular/cdk/overlay";
+import { TestBed } from "@angular/core/testing";
+
+import { CipherId } from "@bitwarden/common/types/guid";
+import { DialogService } from "@bitwarden/components";
+
+import { openPasswordHistoryDialog } from "../individual-vault/password-history.component";
+
+import { WebViewPasswordHistoryService } from "./web-view-password-history.service";
+
+jest.mock("../individual-vault/password-history.component", () => ({
+ openPasswordHistoryDialog: jest.fn(),
+}));
+
+describe("WebViewPasswordHistoryService", () => {
+ let service: WebViewPasswordHistoryService;
+ let dialogService: DialogService;
+
+ beforeEach(async () => {
+ const mockDialogService = {
+ open: jest.fn(),
+ };
+
+ await TestBed.configureTestingModule({
+ providers: [
+ WebViewPasswordHistoryService,
+ { provide: DialogService, useValue: mockDialogService },
+ Overlay,
+ ],
+ }).compileComponents();
+
+ service = TestBed.inject(WebViewPasswordHistoryService);
+ dialogService = TestBed.inject(DialogService);
+ });
+
+ describe("viewPasswordHistory", () => {
+ it("calls openPasswordHistoryDialog with the correct parameters", async () => {
+ const mockCipherId = "cipher-id" as CipherId;
+ await service.viewPasswordHistory(mockCipherId);
+ expect(openPasswordHistoryDialog).toHaveBeenCalledWith(dialogService, {
+ data: { cipherId: mockCipherId },
+ });
+ });
+ });
+});
diff --git a/apps/web/src/app/vault/services/web-view-password-history.service.ts b/apps/web/src/app/vault/services/web-view-password-history.service.ts
new file mode 100644
index 00000000000..cbdc3928e60
--- /dev/null
+++ b/apps/web/src/app/vault/services/web-view-password-history.service.ts
@@ -0,0 +1,23 @@
+import { Injectable } from "@angular/core";
+
+import { CipherId } from "@bitwarden/common/types/guid";
+import { DialogService } from "@bitwarden/components";
+
+import { ViewPasswordHistoryService } from "../../../../../../libs/common/src/vault/abstractions/view-password-history.service";
+import { openPasswordHistoryDialog } from "../individual-vault/password-history.component";
+
+/**
+ * This service is used to display the password history dialog in the web vault.
+ */
+@Injectable()
+export class WebViewPasswordHistoryService implements ViewPasswordHistoryService {
+ constructor(private dialogService: DialogService) {}
+
+ /**
+ * Opens the password history dialog for the given cipher ID.
+ * @param cipherId The ID of the cipher to view the password history for.
+ */
+ async viewPasswordHistory(cipherId: CipherId) {
+ openPasswordHistoryDialog(this.dialogService, { data: { cipherId } });
+ }
+}
diff --git a/libs/common/src/vault/abstractions/view-password-history.service.ts b/libs/common/src/vault/abstractions/view-password-history.service.ts
new file mode 100644
index 00000000000..d9b1306eacb
--- /dev/null
+++ b/libs/common/src/vault/abstractions/view-password-history.service.ts
@@ -0,0 +1,8 @@
+import { CipherId } from "../../types/guid";
+
+/**
+ * The ViewPasswordHistoryService is responsible for displaying the password history for a cipher.
+ */
+export abstract class ViewPasswordHistoryService {
+ abstract viewPasswordHistory(cipherId?: CipherId): Promise;
+}
diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html
index a03639dee61..d48666ad83a 100644
--- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html
+++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html
@@ -27,10 +27,8 @@
{{ "passwordHistory" | i18n }}
diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts b/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts
index 55c8b90da15..4a37c9a491b 100644
--- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts
+++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts
@@ -3,6 +3,8 @@ import { Component, Input } from "@angular/core";
import { RouterModule } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { CipherId } from "@bitwarden/common/types/guid";
+import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
@@ -31,7 +33,16 @@ import {
export class ItemHistoryV2Component {
@Input() cipher: CipherView;
+ constructor(private viewPasswordHistoryService: ViewPasswordHistoryService) {}
+
get isLogin() {
return this.cipher.type === CipherType.Login;
}
+
+ /**
+ * View the password history for the cipher.
+ */
+ async viewPasswordHistory() {
+ await this.viewPasswordHistoryService.viewPasswordHistory(this.cipher?.id as CipherId);
+ }
}
From d0b09202c6684fd3b5482baf3abfa49a788908ce Mon Sep 17 00:00:00 2001
From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com>
Date: Wed, 25 Sep 2024 10:40:23 -0700
Subject: [PATCH 03/11] [PM-12504] - hide create send button and send tab when
sends are disabled (#11186)
* hide create send button and send tab when sends are disabled
* reverse logic
* tidy up filter.
* fix popup tab navigation filter
* fix popup tab nav state
* fix popup-layout stories
---
.../popup/layout/popup-layout.stories.ts | 26 +++++++
.../layout/popup-tab-navigation.component.ts | 77 ++++++++++++-------
.../popup/send-v2/send-v2.component.html | 19 +++--
.../popup/send-v2/send-v2.component.spec.ts | 6 ++
.../tools/popup/send-v2/send-v2.component.ts | 15 +++-
5 files changed, 108 insertions(+), 35 deletions(-)
diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts
index affa804cc79..4851541576f 100644
--- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts
+++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts
@@ -3,7 +3,9 @@ import { Component, importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
+import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import {
AvatarModule,
BadgeModule,
@@ -318,6 +320,30 @@ export default {
});
},
},
+ {
+ provide: PolicyService,
+ useFactory: () => {
+ return {
+ policyAppliesToActiveUser$: () => {
+ return {
+ pipe: () => ({
+ subscribe: () => ({}),
+ }),
+ };
+ },
+ };
+ },
+ },
+ {
+ provide: SendService,
+ useFactory: () => {
+ return {
+ sends$: () => {
+ return { pipe: () => ({}) };
+ },
+ };
+ },
+ },
],
}),
applicationConfig({
diff --git a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts
index ced3f6462e9..8463bbe6e9f 100644
--- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts
+++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts
@@ -1,9 +1,41 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
+import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { RouterModule } from "@angular/router";
+import { filter, map, switchMap } from "rxjs";
+import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
+import { PolicyType } from "@bitwarden/common/admin-console/enums";
+import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { LinkModule } from "@bitwarden/components";
+const allNavButtons = [
+ {
+ label: "Vault",
+ page: "/tabs/vault",
+ iconKey: "lock",
+ iconKeyActive: "lock-f",
+ },
+ {
+ label: "Generator",
+ page: "/tabs/generator",
+ iconKey: "generate",
+ iconKeyActive: "generate-f",
+ },
+ {
+ label: "Send",
+ page: "/tabs/send",
+ iconKey: "send",
+ iconKeyActive: "send-f",
+ },
+ {
+ label: "Settings",
+ page: "/tabs/settings",
+ iconKey: "cog",
+ iconKeyActive: "cog-f",
+ },
+];
+
@Component({
selector: "popup-tab-navigation",
templateUrl: "popup-tab-navigation.component.html",
@@ -14,30 +46,23 @@ import { LinkModule } from "@bitwarden/components";
},
})
export class PopupTabNavigationComponent {
- navButtons = [
- {
- label: "Vault",
- page: "/tabs/vault",
- iconKey: "lock",
- iconKeyActive: "lock-f",
- },
- {
- label: "Generator",
- page: "/tabs/generator",
- iconKey: "generate",
- iconKeyActive: "generate-f",
- },
- {
- label: "Send",
- page: "/tabs/send",
- iconKey: "send",
- iconKeyActive: "send-f",
- },
- {
- label: "Settings",
- page: "/tabs/settings",
- iconKey: "cog",
- iconKeyActive: "cog-f",
- },
- ];
+ navButtons = allNavButtons;
+ constructor(
+ private policyService: PolicyService,
+ private sendService: SendService,
+ ) {
+ this.policyService
+ .policyAppliesToActiveUser$(PolicyType.DisableSend)
+ .pipe(
+ filter((policyAppliesToActiveUser) => policyAppliesToActiveUser),
+ switchMap(() => this.sendService.sends$),
+ map((sends) => sends.length > 1),
+ takeUntilDestroyed(),
+ )
+ .subscribe((hasSends) => {
+ this.navButtons = hasSends
+ ? allNavButtons
+ : allNavButtons.filter((b) => b.page !== "/tabs/send");
+ });
+ }
}
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 a8dd3e24f29..698901d8460 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
@@ -1,12 +1,20 @@
-
-
+
+
+
+ {{ "sendDisabledWarning" | i18n }}
+
+
+
+
+
+
{{ "sendsNoItemsTitle" | i18n }}
{{ "sendsNoItemsMessage" | i18n }}
-
+
@@ -31,9 +39,4 @@
-
-
diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts
index 50e5531743a..63e3c2d2fc6 100644
--- a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts
+++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts
@@ -7,6 +7,7 @@ import { of, BehaviorSubject } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
+import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
@@ -46,6 +47,7 @@ describe("SendV2Component", () => {
let sendListFiltersServiceFilters$: BehaviorSubject<{ sendType: SendType | null }>;
let sendItemsServiceEmptyList$: BehaviorSubject;
let sendItemsServiceNoFilteredResults$: BehaviorSubject;
+ let policyService: MockProxy;
beforeEach(async () => {
sendListFiltersServiceFilters$ = new BehaviorSubject({ sendType: null });
@@ -60,6 +62,9 @@ describe("SendV2Component", () => {
latestSearchText$: of(""),
});
+ policyService = mock();
+ policyService.policyAppliesToActiveUser$.mockReturnValue(of(true)); // Return `true` by default
+
sendListFiltersService = new SendListFiltersService(mock(), new FormBuilder());
sendListFiltersService.filters$ = sendListFiltersServiceFilters$;
@@ -104,6 +109,7 @@ describe("SendV2Component", () => {
{ provide: I18nService, useValue: { t: (key: string) => key } },
{ provide: SendListFiltersService, useValue: sendListFiltersService },
{ provide: PopupRouterCacheService, useValue: mock() },
+ { provide: PolicyService, useValue: policyService },
],
}).compileComponents();
diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts
index 5c1ec89fde9..19fff402e2e 100644
--- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts
+++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts
@@ -5,8 +5,10 @@ import { RouterLink } from "@angular/router";
import { combineLatest } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
+import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
-import { ButtonModule, Icons, NoItemsModule } from "@bitwarden/components";
+import { ButtonModule, CalloutModule, Icons, NoItemsModule } from "@bitwarden/components";
import {
NoSendsIcon,
NewSendDropdownComponent,
@@ -31,6 +33,7 @@ export enum SendState {
templateUrl: "send-v2.component.html",
standalone: true,
imports: [
+ CalloutModule,
PopupPageComponent,
PopupHeaderComponent,
PopOutComponent,
@@ -61,9 +64,12 @@ export class SendV2Component implements OnInit, OnDestroy {
protected noResultsIcon = Icons.NoResults;
+ protected sendsDisabled = false;
+
constructor(
protected sendItemsService: SendItemsService,
protected sendListFiltersService: SendListFiltersService,
+ private policyService: PolicyService,
) {
combineLatest([
this.sendItemsService.emptyList$,
@@ -90,6 +96,13 @@ export class SendV2Component implements OnInit, OnDestroy {
this.listState = null;
});
+
+ this.policyService
+ .policyAppliesToActiveUser$(PolicyType.DisableSend)
+ .pipe(takeUntilDestroyed())
+ .subscribe((sendsDisabled) => {
+ this.sendsDisabled = sendsDisabled;
+ });
}
ngOnInit(): void {}
From 7f339543165b351d2c5e4de8857a9d3a39131309 Mon Sep 17 00:00:00 2001
From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com>
Date: Wed, 25 Sep 2024 10:56:04 -0700
Subject: [PATCH 04/11] don't display free bitwarden families button (#11184)
---
.../about-page/more-from-bitwarden-page-v2.component.ts | 2 +-
.../organization/organization.service.abstraction.ts | 4 ++++
.../services/organization/organization.service.ts | 4 ++++
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts
index 0f05480ea12..7cdb691d56c 100644
--- a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts
+++ b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts
@@ -38,7 +38,7 @@ export class MoreFromBitwardenPageV2Component {
private organizationService: OrganizationService,
) {
this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$;
- this.familySponsorshipAvailable$ = this.organizationService.canManageSponsorships$;
+ this.familySponsorshipAvailable$ = this.organizationService.familySponsorshipAvailable$;
}
async openFreeBitwardenFamiliesPage() {
diff --git a/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts
index 0cea2aee539..a2ea6aa8861 100644
--- a/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts
+++ b/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts
@@ -117,6 +117,10 @@ export abstract class OrganizationService {
* Emits true if the user can create or manage a Free Bitwarden Families sponsorship.
*/
canManageSponsorships$: Observable;
+ /**
+ * Emits true if any of the user's organizations have a Free Bitwarden Families sponsorship available.
+ */
+ familySponsorshipAvailable$: Observable;
hasOrganizations: () => Promise;
get$: (id: string) => Observable;
get: (id: string) => Promise;
diff --git a/libs/common/src/admin-console/services/organization/organization.service.ts b/libs/common/src/admin-console/services/organization/organization.service.ts
index d8fe18dc5cb..91bfcbd0d5d 100644
--- a/libs/common/src/admin-console/services/organization/organization.service.ts
+++ b/libs/common/src/admin-console/services/organization/organization.service.ts
@@ -88,6 +88,10 @@ export class OrganizationService implements InternalOrganizationServiceAbstracti
mapToBooleanHasAnyOrganizations(),
);
+ familySponsorshipAvailable$ = this.organizations$.pipe(
+ map((orgs) => orgs.some((o) => o.familySponsorshipAvailable)),
+ );
+
async hasOrganizations(): Promise {
return await firstValueFrom(this.organizations$.pipe(mapToBooleanHasAnyOrganizations()));
}
From caece397c68055666b5323aaf02dd7d35a1260b0 Mon Sep 17 00:00:00 2001
From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com>
Date: Wed, 25 Sep 2024 12:35:12 -0700
Subject: [PATCH 05/11] [PM-11927] - File Send popout dialog (#11138)
* file popout component
* finish file popout dialog
* finalize send popout dialog component
* fix tests
* conditionally provide file popout dialog
* simplify send file popout dialog
* add file popout dialog container
* remove unnecessary modules
---
apps/browser/src/_locales/en/messages.json | 11 +++++++
.../add-edit/send-add-edit.component.html | 2 ++
.../add-edit/send-add-edit.component.ts | 2 ++
...ile-popout-dialog-container.component.html | 0
...-file-popout-dialog-container.component.ts | 31 +++++++++++++++++++
.../send-file-popout-dialog.component.html | 20 ++++++++++++
.../send-file-popout-dialog.component.ts | 25 +++++++++++++++
.../tools/popup/send-v2/send-v2.component.ts | 5 ---
8 files changed, 91 insertions(+), 5 deletions(-)
create mode 100644 apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.html
create mode 100644 apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts
create mode 100644 apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog.component.html
create mode 100644 apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog.component.ts
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 8c08caec353..49b5eb82bbe 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -2504,6 +2504,14 @@
"message": "Send saved",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
+ "sendFilePopoutDialogText": {
+ "message": "Pop out extension?",
+ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
+ },
+ "sendFilePopoutDialogDesc": {
+ "message": "To create a file Send, you need to pop out te extension to a new window.",
+ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
+ },
"sendLinuxChromiumFileWarning": {
"message": "In order to choose a file, open the extension in the sidebar (if possible) or pop out to a new window by clicking this banner."
},
@@ -2513,6 +2521,9 @@
"sendSafariFileWarning": {
"message": "In order to choose a file using Safari, pop out to a new window by clicking this banner."
},
+ "popOut": {
+ "message": "Pop out"
+ },
"sendFileCalloutHeader": {
"message": "Before you start"
},
diff --git a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html
index 7f723cc7364..e96a0742a03 100644
--- a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html
+++ b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html
@@ -9,6 +9,8 @@
>
+
+