mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-11 10:10:25 +01:00
[PM-10653] MP Reprompt Check for Inline Autofill (#10546)
* add logic for MP reprompt on autofill of web input opening popout browser
This commit is contained in:
parent
83ed0442de
commit
404d514e53
@ -79,7 +79,7 @@ export class ItemMoreOptionsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async doAutofillAndSave() {
|
async doAutofillAndSave() {
|
||||||
await this.vaultPopupAutofillService.doAutofillAndSave(this.cipher);
|
await this.vaultPopupAutofillService.doAutofillAndSave(this.cipher, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +16,7 @@ import { CipherType } from "@bitwarden/common/vault/enums";
|
|||||||
|
|
||||||
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
|
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";
|
import { ViewV2Component } from "./view-v2.component";
|
||||||
|
|
||||||
// 'qrcode-parser' is used by `BrowserTotpCaptureService` but is an es6 module that jest can't compile.
|
// '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,
|
type: CipherType.Login,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockVaultPopupAutofillService = {
|
||||||
|
doAutofill: jest.fn(),
|
||||||
|
};
|
||||||
const mockUserId = Utils.newGuid() as UserId;
|
const mockUserId = Utils.newGuid() as UserId;
|
||||||
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
||||||
|
|
||||||
@ -66,6 +70,7 @@ describe("ViewV2Component", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{ provide: VaultPopupAutofillService, useValue: mockVaultPopupAutofillService },
|
||||||
{
|
{
|
||||||
provide: AccountService,
|
provide: AccountService,
|
||||||
useValue: accountService,
|
useValue: accountService,
|
||||||
|
@ -8,6 +8,7 @@ import { firstValueFrom, map, Observable, switchMap } from "rxjs";
|
|||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.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 { PopupFooterComponent } from "./../../../../../platform/popup/layout/popup-footer.component";
|
||||||
import { PopupHeaderComponent } from "./../../../../../platform/popup/layout/popup-header.component";
|
import { PopupHeaderComponent } from "./../../../../../platform/popup/layout/popup-header.component";
|
||||||
import { PopupPageComponent } from "./../../../../../platform/popup/layout/popup-page.component";
|
import { PopupPageComponent } from "./../../../../../platform/popup/layout/popup-page.component";
|
||||||
|
import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-view-v2",
|
selector: "app-view-v2",
|
||||||
@ -59,6 +61,7 @@ export class ViewV2Component {
|
|||||||
organization$: Observable<Organization>;
|
organization$: Observable<Organization>;
|
||||||
folder$: Observable<FolderView>;
|
folder$: Observable<FolderView>;
|
||||||
collections$: Observable<CollectionView[]>;
|
collections$: Observable<CollectionView[]>;
|
||||||
|
loadAction: typeof AUTOFILL_ID | typeof SHOW_AUTOFILL_BUTTON;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@ -68,6 +71,7 @@ export class ViewV2Component {
|
|||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
|
private vaultPopupAutofillService: VaultPopupAutofillService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
) {
|
) {
|
||||||
this.subscribeToParams();
|
this.subscribeToParams();
|
||||||
@ -77,14 +81,19 @@ export class ViewV2Component {
|
|||||||
this.route.queryParams
|
this.route.queryParams
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(async (params): Promise<CipherView> => {
|
switchMap(async (params): Promise<CipherView> => {
|
||||||
|
this.loadAction = params.action;
|
||||||
return await this.getCipherData(params.cipherId);
|
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(),
|
takeUntilDestroyed(),
|
||||||
)
|
)
|
||||||
.subscribe((cipher) => {
|
.subscribe();
|
||||||
this.cipher = cipher;
|
|
||||||
this.headerText = this.setHeader(cipher.type);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setHeader(type: CipherType) {
|
setHeader(type: CipherType) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { TestBed } from "@angular/core/testing";
|
import { TestBed } from "@angular/core/testing";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { mock } from "jest-mock-extended";
|
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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@ -33,6 +34,9 @@ describe("VaultPopupAutofillService", () => {
|
|||||||
let service: VaultPopupAutofillService;
|
let service: VaultPopupAutofillService;
|
||||||
|
|
||||||
const mockCurrentTab = { url: "https://example.com" } as chrome.tabs.Tab;
|
const mockCurrentTab = { url: "https://example.com" } as chrome.tabs.Tab;
|
||||||
|
const mockActivatedRoute = {
|
||||||
|
queryParams: of({}),
|
||||||
|
} as any;
|
||||||
|
|
||||||
// Create mocks for VaultPopupAutofillService
|
// Create mocks for VaultPopupAutofillService
|
||||||
const mockAutofillService = mock<AutofillService>();
|
const mockAutofillService = mock<AutofillService>();
|
||||||
@ -61,6 +65,7 @@ describe("VaultPopupAutofillService", () => {
|
|||||||
{ provide: PasswordRepromptService, useValue: mockPasswordRepromptService },
|
{ provide: PasswordRepromptService, useValue: mockPasswordRepromptService },
|
||||||
{ provide: CipherService, useValue: mockCipherService },
|
{ provide: CipherService, useValue: mockCipherService },
|
||||||
{ provide: MessagingService, useValue: mockMessagingService },
|
{ provide: MessagingService, useValue: mockMessagingService },
|
||||||
|
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
||||||
{
|
{
|
||||||
provide: AccountService,
|
provide: AccountService,
|
||||||
useValue: accountService,
|
useValue: accountService,
|
||||||
@ -258,6 +263,17 @@ describe("VaultPopupAutofillService", () => {
|
|||||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||||
expect(BrowserApi.closePopup).toHaveBeenCalled();
|
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"),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import {
|
import {
|
||||||
|
combineLatest,
|
||||||
firstValueFrom,
|
firstValueFrom,
|
||||||
map,
|
map,
|
||||||
Observable,
|
Observable,
|
||||||
@ -27,20 +29,30 @@ import {
|
|||||||
} from "../../../autofill/services/abstractions/autofill.service";
|
} from "../../../autofill/services/abstractions/autofill.service";
|
||||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||||
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
|
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
|
||||||
|
import { closeViewVaultItemPopout, VaultPopoutType } from "../utils/vault-popout-window";
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: "root",
|
providedIn: "root",
|
||||||
})
|
})
|
||||||
export class VaultPopupAutofillService {
|
export class VaultPopupAutofillService {
|
||||||
private _refreshCurrentTab$ = new Subject<void>();
|
private _refreshCurrentTab$ = new Subject<void>();
|
||||||
|
private senderTabId$: Observable<number | undefined> = 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
|
* Observable that contains the current tab to be considered for autofill.
|
||||||
* or the popup is in a popout window, this will be null.
|
* 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<chrome.tabs.Tab | null> = this._refreshCurrentTab$.pipe(
|
currentAutofillTab$: Observable<chrome.tabs.Tab | null> = combineLatest([
|
||||||
startWith(null),
|
this.senderTabId$,
|
||||||
switchMap(async () => {
|
this._refreshCurrentTab$.pipe(startWith(null)),
|
||||||
|
]).pipe(
|
||||||
|
switchMap(async ([senderTabId]) => {
|
||||||
|
if (senderTabId) {
|
||||||
|
return await BrowserApi.getTab(senderTabId);
|
||||||
|
}
|
||||||
|
|
||||||
if (BrowserPopupUtils.inPopout(window)) {
|
if (BrowserPopupUtils.inPopout(window)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -73,6 +85,7 @@ export class VaultPopupAutofillService {
|
|||||||
private passwordRepromptService: PasswordRepromptService,
|
private passwordRepromptService: PasswordRepromptService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
) {
|
) {
|
||||||
this._currentPageDetails$.subscribe();
|
this._currentPageDetails$.subscribe();
|
||||||
@ -124,7 +137,21 @@ export class VaultPopupAutofillService {
|
|||||||
return true;
|
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)) {
|
if (!BrowserPopupUtils.inPopup(window)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -158,7 +185,7 @@ export class VaultPopupAutofillService {
|
|||||||
const didAutofill = await this._internalDoAutofill(cipher, tab, pageDetails);
|
const didAutofill = await this._internalDoAutofill(cipher, tab, pageDetails);
|
||||||
|
|
||||||
if (didAutofill && closePopup) {
|
if (didAutofill && closePopup) {
|
||||||
this._closePopup();
|
await this._closePopup(cipher, tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
return didAutofill;
|
return didAutofill;
|
||||||
@ -193,7 +220,7 @@ export class VaultPopupAutofillService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (closePopup) {
|
if (closePopup) {
|
||||||
this._closePopup();
|
await this._closePopup(cipher, tab);
|
||||||
} else {
|
} else {
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
|
Loading…
Reference in New Issue
Block a user