From adb1ee3d38066e81ec7d79cf7c94dfc884c2b9d6 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Thu, 18 Apr 2024 11:21:11 -0400 Subject: [PATCH] [AC-2420] Hide SM checkbox on member invite when org is on SM Standalone (#8644) * Refactoring * Hide SM toggle on member invite and default to true for SM standalone org * changed from hide sm checkbox to default and disable * Removed errant addition from conflict resolution --- .../member-dialog/member-dialog.component.html | 7 ++++++- .../member-dialog/member-dialog.component.ts | 9 +++++++++ .../organizations/members/people.component.ts | 9 +++++++++ .../angular/src/services/jslib-services.module.ts | 1 + .../billilng-api.service.abstraction.ts | 5 +++++ .../abstractions/organization-billing.service.ts | 2 ++ .../src/billing/services/billing-api.service.ts | 15 ++++++++++++++- .../services/organization-billing.service.ts | 15 +++++++++++++++ 8 files changed, 61 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html index 95febbd3c5..4d81d070fb 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html @@ -385,7 +385,12 @@

{{ "secretsManagerAccessDescription" | i18n }}

- + {{ "userAccessSecretsManagerGA" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 771b8cc505..f1af950650 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -63,6 +63,7 @@ export interface MemberDialogParams { organizationUserId: string; allOrganizationUserEmails: string[]; usesKeyConnector: boolean; + isOnSecretsManagerStandalone: boolean; initialTab?: MemberDialogTab; numConfirmedMembers: number; } @@ -88,6 +89,7 @@ export class MemberDialogComponent implements OnDestroy { organizationUserType = OrganizationUserType; PermissionMode = PermissionMode; showNoMasterPasswordWarning = false; + isOnSecretsManagerStandalone: boolean; protected organization$: Observable; protected collectionAccessItems: AccessItemView[] = []; @@ -160,6 +162,13 @@ export class MemberDialogComponent implements OnDestroy { this.editMode = this.params.organizationUserId != null; this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role; this.title = this.i18nService.t(this.editMode ? "editMember" : "inviteMember"); + this.isOnSecretsManagerStandalone = this.params.isOnSecretsManagerStandalone; + + if (this.isOnSecretsManagerStandalone) { + this.formGroup.patchValue({ + accessSecretsManager: true, + }); + } const groups$ = this.organization$.pipe( switchMap((organization) => diff --git a/apps/web/src/app/admin-console/organizations/members/people.component.ts b/apps/web/src/app/admin-console/organizations/members/people.component.ts index 6b632dce38..0df247d7b0 100644 --- a/apps/web/src/app/admin-console/organizations/members/people.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/people.component.ts @@ -37,6 +37,7 @@ import { import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; +import { OrganizationBillingServiceAbstraction as OrganizationBillingService } from "@bitwarden/common/billing/abstractions/organization-billing.service"; import { ProductType } from "@bitwarden/common/enums"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -93,6 +94,7 @@ export class PeopleComponent extends BasePeopleComponent { organization: Organization; status: OrganizationUserStatusType = null; orgResetPasswordPolicyEnabled = false; + orgIsOnSecretsManagerStandalone = false; protected canUseSecretsManager$: Observable; @@ -119,6 +121,7 @@ export class PeopleComponent extends BasePeopleComponent { private groupService: GroupService, private collectionService: CollectionService, organizationManagementPreferencesService: OrganizationManagementPreferencesService, + private organizationBillingService: OrganizationBillingService, ) { super( apiService, @@ -187,6 +190,11 @@ export class PeopleComponent extends BasePeopleComponent { .find((p) => p.organizationId === this.organization.id); this.orgResetPasswordPolicyEnabled = resetPasswordPolicy?.enabled; + this.orgIsOnSecretsManagerStandalone = + await this.organizationBillingService.isOnSecretsManagerStandalone( + this.organization.id, + ); + await this.load(); this.searchText = qParams.search; @@ -446,6 +454,7 @@ export class PeopleComponent extends BasePeopleComponent { organizationUserId: user != null ? user.id : null, allOrganizationUserEmails: this.allUsers?.map((user) => user.email) ?? [], usesKeyConnector: user?.usesKeyConnector, + isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: initialTab, numConfirmedMembers: this.confirmedCount, }, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index cc6d947b33..859103474d 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1052,6 +1052,7 @@ const safeProviders: SafeProvider[] = [ useClass: OrganizationBillingService, deps: [ ApiServiceAbstraction, + BillingApiServiceAbstraction, CryptoServiceAbstraction, EncryptService, I18nServiceAbstraction, diff --git a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts index 16b86c5844..15f0d4b551 100644 --- a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts @@ -1,5 +1,6 @@ import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; +import { OrganizationSubscriptionResponse } from "../../billing/models/response/organization-subscription.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; @@ -11,12 +12,16 @@ export abstract class BillingApiServiceAbstraction { organizationId: string, request: SubscriptionCancellationRequest, ) => Promise; + cancelPremiumUserSubscription: (request: SubscriptionCancellationRequest) => Promise; createClientOrganization: ( providerId: string, request: CreateClientOrganizationRequest, ) => Promise; getBillingStatus: (id: string) => Promise; + getOrganizationSubscription: ( + organizationId: string, + ) => Promise; getPlans: () => Promise>; getProviderSubscription: (providerId: string) => Promise; updateClientOrganization: ( diff --git a/libs/common/src/billing/abstractions/organization-billing.service.ts b/libs/common/src/billing/abstractions/organization-billing.service.ts index d19724b600..0917025eec 100644 --- a/libs/common/src/billing/abstractions/organization-billing.service.ts +++ b/libs/common/src/billing/abstractions/organization-billing.service.ts @@ -41,6 +41,8 @@ export type SubscriptionInformation = { }; export abstract class OrganizationBillingServiceAbstraction { + isOnSecretsManagerStandalone: (organizationId: string) => Promise; + purchaseSubscription: (subscription: SubscriptionInformation) => Promise; startFree: (subscription: SubscriptionInformation) => Promise; diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 70d1d89252..1c119b971d 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -2,6 +2,7 @@ import { ApiService } from "../../abstractions/api.service"; import { BillingApiServiceAbstraction } from "../../billing/abstractions/billilng-api.service.abstraction"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; +import { OrganizationSubscriptionResponse } from "../../billing/models/response/organization-subscription.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; @@ -49,10 +50,22 @@ export class BillingApiService implements BillingApiServiceAbstraction { true, true, ); - return new OrganizationBillingStatusResponse(r); } + async getOrganizationSubscription( + organizationId: string, + ): Promise { + const r = await this.apiService.send( + "GET", + "/organizations/" + organizationId + "/subscription", + null, + true, + true, + ); + return new OrganizationSubscriptionResponse(r); + } + async getPlans(): Promise> { const r = await this.apiService.send("GET", "/plans", null, false, true); return new ListResponse(r, PlanResponse); diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index 6b326472c9..fb2084bb6a 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -9,6 +9,7 @@ import { I18nService } from "../../platform/abstractions/i18n.service"; import { EncString } from "../../platform/models/domain/enc-string"; import { OrgKey } from "../../types/key"; import { SyncService } from "../../vault/abstractions/sync/sync.service.abstraction"; +import { BillingApiServiceAbstraction as BillingApiService } from "../abstractions/billilng-api.service.abstraction"; import { OrganizationBillingServiceAbstraction, OrganizationInformation, @@ -28,6 +29,7 @@ interface OrganizationKeys { export class OrganizationBillingService implements OrganizationBillingServiceAbstraction { constructor( private apiService: ApiService, + private billingApiService: BillingApiService, private cryptoService: CryptoService, private encryptService: EncryptService, private i18nService: I18nService, @@ -35,6 +37,19 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs private syncService: SyncService, ) {} + async isOnSecretsManagerStandalone(organizationId: string): Promise { + const response = await this.billingApiService.getOrganizationSubscription(organizationId); + if (response.customerDiscount?.id === "sm-standalone") { + const productIds = response.subscription.items.map((item) => item.productId); + return ( + response.customerDiscount?.appliesTo.filter((appliesToProductId) => + productIds.includes(appliesToProductId), + ).length > 0 + ); + } + return false; + } + async purchaseSubscription(subscription: SubscriptionInformation): Promise { const request = new OrganizationCreateRequest();