mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[PM-10247] Browser Refresh - Fix save credential banner (#10520)
* [PM-10247] Prioritize initialValues when initiating the CipherForm child forms * [PM-10247] Fetch the addEditCipherInfo when opening the cipher form in Browser and override any initialValues if present * [PM-10247] Fix item details section tests * [PM-10247] Add login details section test * [PM-10247] Add autofill options tests * [PM-10247] Undo webpack config change * [PM-10247] Fix failing tests * [PM-10247] Add additional tests for addEditCipherInfo
This commit is contained in:
@@ -1,14 +1,22 @@
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherFormConfig, CipherFormConfigService, CipherFormMode } from "@bitwarden/vault";
|
||||
import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info";
|
||||
import {
|
||||
CipherFormConfig,
|
||||
CipherFormConfigService,
|
||||
CipherFormMode,
|
||||
OptionalInitialValues,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { BrowserFido2UserInterfaceSession } from "../../../../../autofill/fido2/services/browser-fido2-user-interface.service";
|
||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||
@@ -25,6 +33,8 @@ jest.mock("qrcode-parser", () => {});
|
||||
describe("AddEditV2Component", () => {
|
||||
let component: AddEditV2Component;
|
||||
let fixture: ComponentFixture<AddEditV2Component>;
|
||||
let addEditCipherInfo$: BehaviorSubject<AddEditCipherInfo | null>;
|
||||
let cipherServiceMock: MockProxy<CipherService>;
|
||||
|
||||
const buildConfigResponse = { originalCipher: {} } as CipherFormConfig;
|
||||
const buildConfig = jest.fn((mode: CipherFormMode) =>
|
||||
@@ -41,6 +51,10 @@ describe("AddEditV2Component", () => {
|
||||
navigate.mockClear();
|
||||
back.mockClear();
|
||||
|
||||
addEditCipherInfo$ = new BehaviorSubject(null);
|
||||
cipherServiceMock = mock<CipherService>();
|
||||
cipherServiceMock.addEditCipherInfo$ = addEditCipherInfo$.asObservable();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AddEditV2Component],
|
||||
providers: [
|
||||
@@ -51,6 +65,7 @@ describe("AddEditV2Component", () => {
|
||||
{ provide: Router, useValue: { navigate } },
|
||||
{ provide: ActivatedRoute, useValue: { queryParams: queryParams$ } },
|
||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||
{ provide: CipherService, useValue: cipherServiceMock },
|
||||
],
|
||||
})
|
||||
.overrideProvider(CipherFormConfigService, {
|
||||
@@ -107,6 +122,72 @@ describe("AddEditV2Component", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("addEditCipherInfo initialization", () => {
|
||||
it("populates config.initialValues with `addEditCipherInfo` values", fakeAsync(() => {
|
||||
const addEditCipherInfo = {
|
||||
cipher: {
|
||||
name: "test",
|
||||
folderId: "folder1",
|
||||
organizationId: "org1",
|
||||
type: CipherType.Login,
|
||||
login: {
|
||||
password: "password",
|
||||
username: "username",
|
||||
uris: [{ uri: "https://example.com" }],
|
||||
},
|
||||
},
|
||||
collectionIds: ["col1", "col2"],
|
||||
} as AddEditCipherInfo;
|
||||
addEditCipherInfo$.next(addEditCipherInfo);
|
||||
queryParams$.next({});
|
||||
|
||||
tick();
|
||||
|
||||
expect(component.config.initialValues).toEqual({
|
||||
name: "test",
|
||||
folderId: "folder1",
|
||||
organizationId: "org1",
|
||||
password: "password",
|
||||
username: "username",
|
||||
loginUri: "https://example.com",
|
||||
collectionIds: ["col1", "col2"],
|
||||
} as OptionalInitialValues);
|
||||
}));
|
||||
|
||||
it("populates config.initialValues.username when `addEditCipherInfo` is an Identity", fakeAsync(() => {
|
||||
addEditCipherInfo$.next({
|
||||
cipher: { type: CipherType.Identity, identity: { username: "identity-username" } },
|
||||
} as AddEditCipherInfo);
|
||||
queryParams$.next({});
|
||||
|
||||
tick();
|
||||
|
||||
expect(component.config.initialValues.username).toBe("identity-username");
|
||||
}));
|
||||
|
||||
it("overrides query params with `addEditCipherInfo` values", fakeAsync(() => {
|
||||
addEditCipherInfo$.next({
|
||||
cipher: { name: "AddEditCipherName" },
|
||||
} as AddEditCipherInfo);
|
||||
queryParams$.next({
|
||||
name: "QueryParamName",
|
||||
});
|
||||
|
||||
tick();
|
||||
|
||||
expect(component.config.initialValues.name).toBe("AddEditCipherName");
|
||||
}));
|
||||
|
||||
it("clears `addEditCipherInfo` after initialization", fakeAsync(() => {
|
||||
addEditCipherInfo$.next({ cipher: { name: "test" } } as AddEditCipherInfo);
|
||||
queryParams$.next({});
|
||||
|
||||
tick();
|
||||
|
||||
expect(cipherServiceMock.setAddEditCipherInfo).toHaveBeenCalledTimes(1);
|
||||
}));
|
||||
});
|
||||
|
||||
describe("onCipherSaved", () => {
|
||||
it("disables warning when in popout", async () => {
|
||||
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValueOnce(true);
|
||||
|
||||
@@ -8,8 +8,10 @@ import { firstValueFrom, map, switchMap } from "rxjs";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info";
|
||||
import { AsyncActionsModule, ButtonModule, SearchModule } from "@bitwarden/components";
|
||||
import {
|
||||
CipherFormConfig,
|
||||
@@ -18,6 +20,7 @@ import {
|
||||
CipherFormMode,
|
||||
CipherFormModule,
|
||||
DefaultCipherFormConfigService,
|
||||
OptionalInitialValues,
|
||||
TotpCaptureService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
@@ -156,6 +159,7 @@ export class AddEditV2Component implements OnInit {
|
||||
private popupCloseWarningService: PopupCloseWarningService,
|
||||
private popupRouterCacheService: PopupRouterCacheService,
|
||||
private router: Router,
|
||||
private cipherService: CipherService,
|
||||
) {
|
||||
this.subscribeToParams();
|
||||
}
|
||||
@@ -255,7 +259,21 @@ export class AddEditV2Component implements OnInit {
|
||||
config.mode = "partial-edit";
|
||||
}
|
||||
|
||||
this.setInitialValuesFromParams(params, config);
|
||||
config.initialValues = this.setInitialValuesFromParams(params);
|
||||
|
||||
// The browser notification bar and overlay use addEditCipherInfo$ to pass modified cipher details to the form
|
||||
// Attempt to fetch them here and overwrite the initialValues if present
|
||||
const cachedCipherInfo = await firstValueFrom(this.cipherService.addEditCipherInfo$);
|
||||
|
||||
if (cachedCipherInfo != null) {
|
||||
// Cached cipher info has priority over queryParams
|
||||
config.initialValues = {
|
||||
...config.initialValues,
|
||||
...mapAddEditCipherInfoToInitialValues(cachedCipherInfo),
|
||||
};
|
||||
// Be sure to clear the "cached" cipher info, so it doesn't get used again
|
||||
await this.cipherService.setAddEditCipherInfo(null);
|
||||
}
|
||||
|
||||
return config;
|
||||
}),
|
||||
@@ -266,26 +284,27 @@ export class AddEditV2Component implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
setInitialValuesFromParams(params: QueryParams, config: CipherFormConfig) {
|
||||
config.initialValues = {};
|
||||
setInitialValuesFromParams(params: QueryParams) {
|
||||
const initialValues = {} as OptionalInitialValues;
|
||||
if (params.folderId) {
|
||||
config.initialValues.folderId = params.folderId;
|
||||
initialValues.folderId = params.folderId;
|
||||
}
|
||||
if (params.organizationId) {
|
||||
config.initialValues.organizationId = params.organizationId;
|
||||
initialValues.organizationId = params.organizationId;
|
||||
}
|
||||
if (params.collectionId) {
|
||||
config.initialValues.collectionIds = [params.collectionId];
|
||||
initialValues.collectionIds = [params.collectionId];
|
||||
}
|
||||
if (params.uri) {
|
||||
config.initialValues.loginUri = params.uri;
|
||||
initialValues.loginUri = params.uri;
|
||||
}
|
||||
if (params.username) {
|
||||
config.initialValues.username = params.username;
|
||||
initialValues.username = params.username;
|
||||
}
|
||||
if (params.name) {
|
||||
config.initialValues.name = params.name;
|
||||
initialValues.name = params.name;
|
||||
}
|
||||
return initialValues;
|
||||
}
|
||||
|
||||
setHeader(mode: CipherFormMode, type: CipherType) {
|
||||
@@ -303,3 +322,63 @@ export class AddEditV2Component implements OnInit {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to map the old AddEditCipherInfo to the new OptionalInitialValues type used by the CipherForm
|
||||
* @param cipherInfo
|
||||
*/
|
||||
const mapAddEditCipherInfoToInitialValues = (
|
||||
cipherInfo: AddEditCipherInfo | null,
|
||||
): OptionalInitialValues => {
|
||||
const initialValues: OptionalInitialValues = {};
|
||||
|
||||
if (cipherInfo == null) {
|
||||
return initialValues;
|
||||
}
|
||||
|
||||
if (cipherInfo.collectionIds != null) {
|
||||
initialValues.collectionIds = cipherInfo.collectionIds as CollectionId[];
|
||||
}
|
||||
|
||||
if (cipherInfo.cipher == null) {
|
||||
return initialValues;
|
||||
}
|
||||
|
||||
const cipher = cipherInfo.cipher;
|
||||
|
||||
if (cipher.folderId != null) {
|
||||
initialValues.folderId = cipher.folderId;
|
||||
}
|
||||
|
||||
if (cipher.organizationId != null) {
|
||||
initialValues.organizationId = cipher.organizationId as OrganizationId;
|
||||
}
|
||||
|
||||
if (cipher.name != null) {
|
||||
initialValues.name = cipher.name;
|
||||
}
|
||||
|
||||
if (cipher.type === CipherType.Login) {
|
||||
const login = cipher.login;
|
||||
|
||||
if (login != null) {
|
||||
if (login.uris != null && login.uris.length > 0) {
|
||||
initialValues.loginUri = login.uris[0].uri;
|
||||
}
|
||||
|
||||
if (login.username != null) {
|
||||
initialValues.username = login.username;
|
||||
}
|
||||
|
||||
if (login.password != null) {
|
||||
initialValues.password = login.password;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cipher.type === CipherType.Identity && cipher.identity?.username != null) {
|
||||
initialValues.username = cipher.identity.username;
|
||||
}
|
||||
|
||||
return initialValues;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user