Skip to content

Commit

Permalink
Merge branch 'main' into AC-2009/migrate-organization-state
Browse files Browse the repository at this point in the history
  • Loading branch information
addisonbeck committed Mar 15, 2024
2 parents d1ce9f8 + ca86288 commit e6fb4f9
Show file tree
Hide file tree
Showing 90 changed files with 985 additions and 261 deletions.
Expand Up @@ -9,6 +9,7 @@ import {
ApiServiceInitOptions,
} from "../../../platform/background/service-factories/api-service.factory";
import { appIdServiceFactory } from "../../../platform/background/service-factories/app-id-service.factory";
import { billingAccountProfileStateServiceFactory } from "../../../platform/background/service-factories/billing-account-profile-state-service.factory";
import {
CryptoServiceInitOptions,
cryptoServiceFactory,
Expand Down Expand Up @@ -119,6 +120,7 @@ export function loginStrategyServiceFactory(
await deviceTrustCryptoServiceFactory(cache, opts),
await authRequestServiceFactory(cache, opts),
await globalStateProviderFactory(cache, opts),
await billingAccountProfileStateServiceFactory(cache, opts),
),
);
}
Expand Up @@ -6,6 +6,7 @@ import {
EventCollectionServiceInitOptions,
eventCollectionServiceFactory,
} from "../../../background/service-factories/event-collection-service.factory";
import { billingAccountProfileStateServiceFactory } from "../../../platform/background/service-factories/billing-account-profile-state-service.factory";
import {
CachedServices,
factory,
Expand Down Expand Up @@ -69,6 +70,7 @@ export function autofillServiceFactory(
await logServiceFactory(cache, opts),
await domainSettingsServiceFactory(cache, opts),
await userVerificationServiceFactory(cache, opts),
await billingAccountProfileStateServiceFactory(cache, opts),
),
);
}
Expand Up @@ -3,6 +3,7 @@ import { of } from "rxjs";

import { NOOP_COMMAND_SUFFIX } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { CipherType } from "@bitwarden/common/vault/enums";
Expand All @@ -18,6 +19,7 @@ describe("context-menu", () => {
let autofillSettingsService: MockProxy<AutofillSettingsServiceAbstraction>;
let i18nService: MockProxy<I18nService>;
let logService: MockProxy<LogService>;
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;

let removeAllSpy: jest.SpyInstance<void, [callback?: () => void]>;
let createSpy: jest.SpyInstance<
Expand All @@ -32,6 +34,7 @@ describe("context-menu", () => {
autofillSettingsService = mock();
i18nService = mock();
logService = mock();
billingAccountProfileStateService = mock();

removeAllSpy = jest
.spyOn(chrome.contextMenus, "removeAll")
Expand All @@ -50,6 +53,7 @@ describe("context-menu", () => {
autofillSettingsService,
i18nService,
logService,
billingAccountProfileStateService,
);
autofillSettingsService.enableContextMenu$ = of(true);
});
Expand All @@ -66,15 +70,15 @@ describe("context-menu", () => {
});

it("has menu enabled, but does not have premium", async () => {
stateService.getCanAccessPremium.mockResolvedValue(false);
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false);

const createdMenu = await sut.init();
expect(createdMenu).toBeTruthy();
expect(createSpy).toHaveBeenCalledTimes(10);
});

it("has menu enabled and has premium", async () => {
stateService.getCanAccessPremium.mockResolvedValue(true);
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);

const createdMenu = await sut.init();
expect(createdMenu).toBeTruthy();
Expand Down Expand Up @@ -128,7 +132,7 @@ describe("context-menu", () => {
});

it("create entry for each cipher piece", async () => {
stateService.getCanAccessPremium.mockResolvedValue(true);
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);

await sut.loadOptions("TEST_TITLE", "1", createCipher());

Expand All @@ -137,7 +141,7 @@ describe("context-menu", () => {
});

it("creates a login/unlock item for each context menu action option when user is not authenticated", async () => {
stateService.getCanAccessPremium.mockResolvedValue(true);
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);

await sut.loadOptions("TEST_TITLE", "NOOP");

Expand Down
13 changes: 11 additions & 2 deletions apps/browser/src/autofill/browser/main-context-menu-handler.ts
Expand Up @@ -17,6 +17,7 @@ import {
SEPARATOR_ID,
} from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
Expand All @@ -27,6 +28,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";

import { autofillSettingsServiceFactory } from "../../autofill/background/service_factories/autofill-settings-service.factory";
import { Account } from "../../models/account";
import { billingAccountProfileStateServiceFactory } from "../../platform/background/service-factories/billing-account-profile-state-service.factory";
import { CachedServices } from "../../platform/background/service-factories/factory-options";
import {
i18nServiceFactory,
Expand Down Expand Up @@ -163,6 +165,7 @@ export class MainContextMenuHandler {
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private i18nService: I18nService,
private logService: LogService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
) {}

static async mv3Create(cachedServices: CachedServices) {
Expand Down Expand Up @@ -196,6 +199,7 @@ export class MainContextMenuHandler {
await autofillSettingsServiceFactory(cachedServices, serviceOptions),
await i18nServiceFactory(cachedServices, serviceOptions),
await logServiceFactory(cachedServices, serviceOptions),
await billingAccountProfileStateServiceFactory(cachedServices, serviceOptions),
);
}

Expand All @@ -217,7 +221,10 @@ export class MainContextMenuHandler {

try {
for (const options of this.initContextMenuItems) {
if (options.checkPremiumAccess && !(await this.stateService.getCanAccessPremium())) {
if (
options.checkPremiumAccess &&
!(await firstValueFrom(this.billingAccountProfileStateService.hasPremiumFromAnySource$))
) {
continue;
}

Expand Down Expand Up @@ -312,7 +319,9 @@ export class MainContextMenuHandler {
await createChildItem(COPY_USERNAME_ID);
}

const canAccessPremium = await this.stateService.getCanAccessPremium();
const canAccessPremium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$,
);
if (canAccessPremium && (!cipher || !Utils.isNullOrEmpty(cipher.login?.totp))) {
await createChildItem(COPY_VERIFICATION_CODE_ID);
}
Expand Down
16 changes: 8 additions & 8 deletions apps/browser/src/autofill/services/autofill.service.spec.ts
Expand Up @@ -8,6 +8,7 @@ import {
DefaultDomainSettingsService,
DomainSettingsService,
} from "@bitwarden/common/autofill/services/domain-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums";
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
Expand Down Expand Up @@ -72,6 +73,7 @@ describe("AutofillService", () => {
const eventCollectionService = mock<EventCollectionService>();
const logService = mock<LogService>();
const userVerificationService = mock<UserVerificationService>();
const billingAccountProfileStateService = mock<BillingAccountProfileStateService>();

beforeEach(() => {
autofillService = new AutofillService(
Expand All @@ -83,6 +85,7 @@ describe("AutofillService", () => {
logService,
domainSettingsService,
userVerificationService,
billingAccountProfileStateService,
);

domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
Expand Down Expand Up @@ -476,6 +479,7 @@ describe("AutofillService", () => {

it("throws an error if an autofill did not occur for any of the passed pages", async () => {
autofillOptions.tab.url = "https://a-different-url.com";
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);

try {
await autofillService.doAutoFill(autofillOptions);
Expand All @@ -487,7 +491,6 @@ describe("AutofillService", () => {
});

it("will autofill login data for a page", async () => {
jest.spyOn(stateService, "getCanAccessPremium");
jest.spyOn(autofillService as any, "generateFillScript");
jest.spyOn(autofillService as any, "generateLoginFillScript");
jest.spyOn(logService, "info");
Expand All @@ -497,8 +500,6 @@ describe("AutofillService", () => {
const autofillResult = await autofillService.doAutoFill(autofillOptions);

const currentAutofillPageDetails = autofillOptions.pageDetails[0];
expect(stateService.getCanAccessPremium).toHaveBeenCalled();
expect(autofillService["getDefaultUriMatchStrategy"]).toHaveBeenCalled();
expect(autofillService["generateFillScript"]).toHaveBeenCalledWith(
currentAutofillPageDetails.details,
{
Expand Down Expand Up @@ -660,7 +661,7 @@ describe("AutofillService", () => {
it("returns a TOTP value", async () => {
const totpCode = "123456";
autofillOptions.cipher.login.totp = "totp";
jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(true);
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);
jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true);
jest.spyOn(totpService, "getCode").mockResolvedValue(totpCode);

Expand All @@ -673,7 +674,7 @@ describe("AutofillService", () => {

it("does not return a TOTP value if the user does not have premium features", async () => {
autofillOptions.cipher.login.totp = "totp";
jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(false);
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false);
jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true);

const autofillResult = await autofillService.doAutoFill(autofillOptions);
Expand Down Expand Up @@ -707,7 +708,7 @@ describe("AutofillService", () => {
it("returns a null value if the user cannot access premium and the organization does not use TOTP", async () => {
autofillOptions.cipher.login.totp = "totp";
autofillOptions.cipher.organizationUseTotp = false;
jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValueOnce(false);
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false);

const autofillResult = await autofillService.doAutoFill(autofillOptions);

Expand All @@ -717,13 +718,12 @@ describe("AutofillService", () => {
it("returns a null value if the user has disabled `auto TOTP copy`", async () => {
autofillOptions.cipher.login.totp = "totp";
autofillOptions.cipher.organizationUseTotp = true;
jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(true);
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);
jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(false);
jest.spyOn(totpService, "getCode");

const autofillResult = await autofillService.doAutoFill(autofillOptions);

expect(stateService.getCanAccessPremium).toHaveBeenCalled();
expect(autofillService.getShouldAutoCopyTotp).toHaveBeenCalled();
expect(totpService.getCode).not.toHaveBeenCalled();
expect(autofillResult).toBeNull();
Expand Down
6 changes: 5 additions & 1 deletion apps/browser/src/autofill/services/autofill.service.ts
Expand Up @@ -5,6 +5,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums";
import {
UriMatchStrategySetting,
Expand Down Expand Up @@ -55,6 +56,7 @@ export default class AutofillService implements AutofillServiceInterface {
private logService: LogService,
private domainSettingsService: DomainSettingsService,
private userVerificationService: UserVerificationService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
) {}

/**
Expand Down Expand Up @@ -240,7 +242,9 @@ export default class AutofillService implements AutofillServiceInterface {

let totp: string | null = null;

const canAccessPremium = await this.stateService.getCanAccessPremium();
const canAccessPremium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$,
);
const defaultUriMatch = await this.getDefaultUriMatchStrategy();

if (!canAccessPremium) {
Expand Down
11 changes: 11 additions & 0 deletions apps/browser/src/background/main.background.ts
Expand Up @@ -65,6 +65,8 @@ import {
UserNotificationSettingsService,
UserNotificationSettingsServiceAbstraction,
} from "@bitwarden/common/autofill/services/user-notification-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
Expand Down Expand Up @@ -311,6 +313,7 @@ export default class MainBackground {
biometricStateService: BiometricStateService;
stateEventRunnerService: StateEventRunnerService;
ssoLoginService: SsoLoginServiceAbstraction;
billingAccountProfileStateService: BillingAccountProfileStateService;

onUpdatedRan: boolean;
onReplacedRan: boolean;
Expand Down Expand Up @@ -569,6 +572,10 @@ export default class MainBackground {
this.stateService,
);

this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService(
this.activeUserStateProvider,
);

this.loginStrategyService = new LoginStrategyService(
this.cryptoService,
this.apiService,
Expand All @@ -588,6 +595,7 @@ export default class MainBackground {
this.deviceTrustCryptoService,
this.authRequestService,
this.globalStateProvider,
this.billingAccountProfileStateService,
);

this.ssoLoginService = new SsoLoginService(this.stateProvider);
Expand Down Expand Up @@ -715,6 +723,7 @@ export default class MainBackground {
this.sendApiService,
this.avatarService,
logoutCallback,
this.billingAccountProfileStateService,
);
this.eventUploadService = new EventUploadService(
this.apiService,
Expand All @@ -738,6 +747,7 @@ export default class MainBackground {
this.logService,
this.domainSettingsService,
this.userVerificationService,
this.billingAccountProfileStateService,
);
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);

Expand Down Expand Up @@ -958,6 +968,7 @@ export default class MainBackground {
this.autofillSettingsService,
this.i18nService,
this.logService,
this.billingAccountProfileStateService,
);

this.cipherContextMenuHandler = new CipherContextMenuHandler(
Expand Down
@@ -0,0 +1,28 @@
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";

import { activeUserStateProviderFactory } from "./active-user-state-provider.factory";
import { FactoryOptions, CachedServices, factory } from "./factory-options";
import { StateProviderInitOptions } from "./state-provider.factory";

type BillingAccountProfileStateServiceFactoryOptions = FactoryOptions;

export type BillingAccountProfileStateServiceInitOptions =
BillingAccountProfileStateServiceFactoryOptions & StateProviderInitOptions;

export function billingAccountProfileStateServiceFactory(
cache: {
billingAccountProfileStateService?: BillingAccountProfileStateService;
} & CachedServices,
opts: BillingAccountProfileStateServiceInitOptions,
): Promise<BillingAccountProfileStateService> {
return factory(
cache,
"billingAccountProfileStateService",
opts,
async () =>
new DefaultBillingAccountProfileStateService(
await activeUserStateProviderFactory(cache, opts),
),
);
}
4 changes: 2 additions & 2 deletions apps/browser/src/popup/settings/premium.component.html
Expand Up @@ -12,7 +12,7 @@ <h1 class="center">
</header>
<main tabindex="-1">
<div class="content">
<ng-container *ngIf="!isPremium">
<ng-container *ngIf="!(isPremium$ | async)">
<p class="text-center lead">{{ "premiumNotCurrentMember" | i18n }}</p>
<p>{{ "premiumSignUpAndGet" | i18n }}</p>
<ul class="bwi-ul">
Expand Down Expand Up @@ -61,7 +61,7 @@ <h1 class="center">
></i>
</button>
</ng-container>
<ng-container *ngIf="isPremium">
<ng-container *ngIf="isPremium$ | async">
<p class="text-center lead">{{ "premiumCurrentMember" | i18n }}</p>
<p class="text-center">{{ "premiumCurrentMemberThanks" | i18n }}</p>
<button type="button" class="btn block primary" (click)="manage()">
Expand Down

0 comments on commit e6fb4f9

Please sign in to comment.