From 404d514e5355a508e9a318e0eddecb610094f791 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Thu, 22 Aug 2024 16:38:21 -0400 Subject: [PATCH] [PM-10653] MP Reprompt Check for Inline Autofill (#10546) * add logic for MP reprompt on autofill of web input opening popout browser --- .../item-more-options.component.ts | 2 +- .../view-v2/view-v2.component.spec.ts | 5 +++ .../vault-v2/view-v2/view-v2.component.ts | 17 +++++-- .../vault-popup-autofill.service.spec.ts | 18 +++++++- .../services/vault-popup-autofill.service.ts | 45 +++++++++++++++---- 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index 161a68043f..2bc3fcea2f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -79,7 +79,7 @@ export class ItemMoreOptionsComponent { } async doAutofillAndSave() { - await this.vaultPopupAutofillService.doAutofillAndSave(this.cipher); + await this.vaultPopupAutofillService.doAutofillAndSave(this.cipher, false); } /** 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 9943046b1c..9851b16aa4 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 @@ -16,6 +16,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; +import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service"; import { ViewV2Component } from "./view-v2.component"; // 'qrcode-parser' is used by `BrowserTotpCaptureService` but is an es6 module that jest can't compile. @@ -34,6 +35,9 @@ describe("ViewV2Component", () => { type: CipherType.Login, }; + const mockVaultPopupAutofillService = { + doAutofill: jest.fn(), + }; const mockUserId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); @@ -66,6 +70,7 @@ describe("ViewV2Component", () => { }, }, }, + { provide: VaultPopupAutofillService, useValue: mockVaultPopupAutofillService }, { provide: AccountService, useValue: accountService, 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 9b8403cd31..dbdf7287dd 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 @@ -8,6 +8,7 @@ import { firstValueFrom, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AUTOFILL_ID, SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants"; 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"; @@ -32,6 +33,7 @@ import { BrowserTotpCaptureService } from "../../../services/browser-totp-captur 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", @@ -59,6 +61,7 @@ export class ViewV2Component { organization$: Observable; folder$: Observable; collections$: Observable; + loadAction: typeof AUTOFILL_ID | typeof SHOW_AUTOFILL_BUTTON; constructor( private route: ActivatedRoute, @@ -68,6 +71,7 @@ export class ViewV2Component { private dialogService: DialogService, private logService: LogService, private toastService: ToastService, + private vaultPopupAutofillService: VaultPopupAutofillService, private accountService: AccountService, ) { this.subscribeToParams(); @@ -77,14 +81,19 @@ export class ViewV2Component { this.route.queryParams .pipe( switchMap(async (params): Promise => { + this.loadAction = params.action; return await this.getCipherData(params.cipherId); }), + switchMap(async (cipher) => { + this.cipher = cipher; + this.headerText = this.setHeader(cipher.type); + if (this.loadAction === AUTOFILL_ID || this.loadAction === SHOW_AUTOFILL_BUTTON) { + await this.vaultPopupAutofillService.doAutofill(this.cipher); + } + }), takeUntilDestroyed(), ) - .subscribe((cipher) => { - this.cipher = cipher; - this.headerText = this.setHeader(cipher.type); - }); + .subscribe(); } setHeader(type: CipherType) { diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts index 2df52e6b55..effadad07f 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts @@ -1,6 +1,7 @@ import { TestBed } from "@angular/core/testing"; +import { ActivatedRoute } from "@angular/router"; import { mock } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -33,6 +34,9 @@ describe("VaultPopupAutofillService", () => { let service: VaultPopupAutofillService; const mockCurrentTab = { url: "https://example.com" } as chrome.tabs.Tab; + const mockActivatedRoute = { + queryParams: of({}), + } as any; // Create mocks for VaultPopupAutofillService const mockAutofillService = mock(); @@ -61,6 +65,7 @@ describe("VaultPopupAutofillService", () => { { provide: PasswordRepromptService, useValue: mockPasswordRepromptService }, { provide: CipherService, useValue: mockCipherService }, { provide: MessagingService, useValue: mockMessagingService }, + { provide: ActivatedRoute, useValue: mockActivatedRoute }, { provide: AccountService, useValue: accountService, @@ -258,6 +263,17 @@ describe("VaultPopupAutofillService", () => { expect(setTimeout).toHaveBeenCalledTimes(1); expect(BrowserApi.closePopup).toHaveBeenCalled(); }); + + it("should show a successful toast message if login form is populated", async () => { + jest.spyOn(BrowserPopupUtils, "inSingleActionPopout").mockReturnValue(true); + (service as any).currentAutofillTab$ = of({ id: 1234 }); + await service.doAutofill(mockCipher); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + title: null, + message: mockI18nService.t("autoFillSuccess"), + }); + }); }); }); diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts index 66a432efe6..a2e032a54f 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts @@ -1,5 +1,7 @@ import { Injectable } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; import { + combineLatest, firstValueFrom, map, Observable, @@ -27,20 +29,30 @@ import { } from "../../../autofill/services/abstractions/autofill.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; +import { closeViewVaultItemPopout, VaultPopoutType } from "../utils/vault-popout-window"; @Injectable({ providedIn: "root", }) export class VaultPopupAutofillService { private _refreshCurrentTab$ = new Subject(); - + private senderTabId$: Observable = this.route.queryParams.pipe( + map((params) => (params?.senderTabId ? parseInt(params.senderTabId, 10) : undefined)), + ); /** - * Observable that contains the current tab to be considered for autofill. If there is no current tab - * or the popup is in a popout window, this will be null. + * Observable that contains the current tab to be considered for autofill. + * This can be the tab from the current window if opened in a Popup OR + * the sending tab when opened the single action Popout (specified by the senderTabId route query parameter) */ - currentAutofillTab$: Observable = this._refreshCurrentTab$.pipe( - startWith(null), - switchMap(async () => { + currentAutofillTab$: Observable = combineLatest([ + this.senderTabId$, + this._refreshCurrentTab$.pipe(startWith(null)), + ]).pipe( + switchMap(async ([senderTabId]) => { + if (senderTabId) { + return await BrowserApi.getTab(senderTabId); + } + if (BrowserPopupUtils.inPopout(window)) { return null; } @@ -73,6 +85,7 @@ export class VaultPopupAutofillService { private passwordRepromptService: PasswordRepromptService, private cipherService: CipherService, private messagingService: MessagingService, + private route: ActivatedRoute, private accountService: AccountService, ) { this._currentPageDetails$.subscribe(); @@ -124,7 +137,21 @@ export class VaultPopupAutofillService { return true; } - private _closePopup() { + private async _closePopup(cipher: CipherView, tab: chrome.tabs.Tab | null) { + if (BrowserPopupUtils.inSingleActionPopout(window, VaultPopoutType.viewVaultItem) && tab.id) { + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("autoFillSuccess"), + }); + setTimeout(async () => { + await BrowserApi.focusTab(tab.id); + await closeViewVaultItemPopout(`${VaultPopoutType.viewVaultItem}_${cipher.id}`); + }, 1000); + + return; + } + if (!BrowserPopupUtils.inPopup(window)) { return; } @@ -158,7 +185,7 @@ export class VaultPopupAutofillService { const didAutofill = await this._internalDoAutofill(cipher, tab, pageDetails); if (didAutofill && closePopup) { - this._closePopup(); + await this._closePopup(cipher, tab); } return didAutofill; @@ -193,7 +220,7 @@ export class VaultPopupAutofillService { } if (closePopup) { - this._closePopup(); + await this._closePopup(cipher, tab); } else { this.toastService.showToast({ variant: "success",