From c17f582768f4679e98881bccf98c41e205800ec5 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:36:52 +0100 Subject: [PATCH] [PM-13345]Add the new policy (#11894) * Add the new policy * Add the free family policy behind flag * Patch build process * Revert "Patch build process" This reverts commit 4024e974b1ed120b49f2b7514b97a9b28d545e80. * [PM-13346] Email notification impacts (#11967) * Changes error notification for disabled offer * Add the feature to the change * Add the missing dot * Remove the authenicated endpoint * Add the changes for error toast * Resolve the lint issue * rename file a correctly * Remove the floating promise comments * Delete unwanted comments --------- Co-authored-by: Matt Bishop --- .../accept-family-sponsorship.component.ts | 20 ++++++++++++------- ...families-for-enterprise-setup.component.ts | 17 +++++++++++++++- apps/web/src/locales/en/messages.json | 9 +++++++++ .../bit-web/src/app/app.component.ts | 11 +++++++++- .../bit-web/src/app/app.module.ts | 2 ++ .../free-families-sponsorship.component.html | 4 ++++ .../free-families-sponsorship.component.ts | 20 +++++++++++++++++++ libs/common/src/abstractions/api.service.ts | 5 ++++- .../admin-console/enums/policy-type.enum.ts | 1 + .../organization-sponsorship.response.ts | 10 ++++++++++ .../pre-validate-sponsorship.response.ts | 12 +++++++++++ libs/common/src/enums/feature-flag.enum.ts | 2 ++ libs/common/src/services/api.service.ts | 10 +++++++--- 13 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.html create mode 100644 bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts create mode 100644 libs/common/src/admin-console/models/response/organization-sponsorship.response.ts create mode 100644 libs/common/src/admin-console/models/response/pre-validate-sponsorship.response.ts diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts index 239ac835f5..b00723d96c 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts @@ -1,7 +1,12 @@ -import { Component } from "@angular/core"; +import { Component, inject } from "@angular/core"; import { Params } from "@angular/router"; import { firstValueFrom } from "rxjs"; +import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; +import { OrganizationSponsorshipResponse } from "@bitwarden/common/admin-console/models/response/organization-sponsorship.response"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { ToastService } from "@bitwarden/components"; + import { BaseAcceptComponent } from "../../../common/base.accept.component"; /* @@ -19,17 +24,18 @@ export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent { requiredParameters = ["email", "token"]; + policyResponse!: OrganizationSponsorshipResponse; + policyApiService = inject(PolicyApiServiceAbstraction); + configService = inject(ConfigService); + toastService = inject(ToastService); + async authedHandler(qParams: Params) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/setup/families-for-enterprise"], { queryParams: qParams }); + await this.router.navigate(["/setup/families-for-enterprise"], { queryParams: qParams }); } async unauthedHandler(qParams: Params) { if (!qParams.register) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/login"], { queryParams: { email: qParams.email } }); + await this.router.navigate(["/login"], { queryParams: { email: qParams.email } }); } else { // TODO: update logic when email verification flag is removed let queryParams: Params; diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts index f857eb6376..09ad27e6dc 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts @@ -9,6 +9,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationSponsorshipRedeemRequest } from "@bitwarden/common/admin-console/models/request/organization/organization-sponsorship-redeem.request"; +import { PreValidateSponsorshipResponse } from "@bitwarden/common/admin-console/models/response/pre-validate-sponsorship.response"; import { PlanSponsorshipType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -51,6 +52,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { showNewOrganization = false; _organizationPlansComponent: OrganizationPlansComponent; + preValidateSponsorshipResponse: PreValidateSponsorshipResponse; _selectedFamilyOrganizationId = ""; private _destroy = new Subject(); @@ -92,7 +94,20 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { this.token = qParams.token; await this.syncService.fullSync(true); - this.badToken = !(await this.apiService.postPreValidateSponsorshipToken(this.token)); + + this.preValidateSponsorshipResponse = await this.apiService.postPreValidateSponsorshipToken( + this.token, + ); + if (this.preValidateSponsorshipResponse.isFreeFamilyPolicyEnabled) { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccured"), + message: this.i18nService.t("offerNoLongerValid"), + }); + } else { + this.badToken = !this.preValidateSponsorshipResponse.isTokenValid; + } + this.loading = false; }); diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index a24a9acd90..dee1b6baee 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -6376,6 +6376,9 @@ "idpSingleSignOnServiceUrlRequired": { "message": "Required if Entity ID is not a URL." }, + "offerNoLongerValid": { + "message": "This offer is no longer valid. Contact your organization administrators for more information." + }, "openIdOptionalCustomizations": { "message": "Optional customizations" }, @@ -9727,5 +9730,11 @@ }, "deletedSuccessfully": { "message": "Deleted successfully" + }, + "freeFamiliesSponsorship": { + "message": "Remove Free Bitwarden Families sponsorship" + }, + "freeFamiliesSponsorshipPolicyDesc": { + "message": "Do not allow members to redeem a Families plan through this organization." } } diff --git a/bitwarden_license/bit-web/src/app/app.component.ts b/bitwarden_license/bit-web/src/app/app.component.ts index 9431e77e25..1e0f60e2cd 100644 --- a/bitwarden_license/bit-web/src/app/app.component.ts +++ b/bitwarden_license/bit-web/src/app/app.component.ts @@ -7,6 +7,7 @@ import { ActivateAutofillPolicy } from "./admin-console/policies/activate-autofi import { AutomaticAppLoginPolicy } from "./admin-console/policies/automatic-app-login.component"; import { DisablePersonalVaultExportPolicy } from "./admin-console/policies/disable-personal-vault-export.component"; import { MaximumVaultTimeoutPolicy } from "./admin-console/policies/maximum-vault-timeout.component"; +import { FreeFamiliesSponsorshipPolicy } from "./billing/policies/free-families-sponsorship.component"; @Component({ selector: "app-root", @@ -19,9 +20,17 @@ export class AppComponent extends BaseAppComponent implements OnInit { this.policyListService.addPolicies([ new MaximumVaultTimeoutPolicy(), new DisablePersonalVaultExportPolicy(), - new ActivateAutofillPolicy(), ]); + this.configService + .getFeatureFlag(FeatureFlag.DisableFreeFamiliesSponsorship) + .then((isFreeFamilyEnabled) => { + if (isFreeFamilyEnabled) { + this.policyListService.addPolicies([new FreeFamiliesSponsorshipPolicy()]); + } + this.policyListService.addPolicies([new ActivateAutofillPolicy()]); + }); + this.configService.getFeatureFlag(FeatureFlag.IdpAutoSubmitLogin).then((enabled) => { if ( enabled && diff --git a/bitwarden_license/bit-web/src/app/app.module.ts b/bitwarden_license/bit-web/src/app/app.module.ts index 8f2074262d..4db1e2f5e2 100644 --- a/bitwarden_license/bit-web/src/app/app.module.ts +++ b/bitwarden_license/bit-web/src/app/app.module.ts @@ -19,6 +19,7 @@ import { DisablePersonalVaultExportPolicyComponent } from "./admin-console/polic import { MaximumVaultTimeoutPolicyComponent } from "./admin-console/policies/maximum-vault-timeout.component"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; +import { FreeFamiliesSponsorshipPolicyComponent } from "./billing/policies/free-families-sponsorship.component"; /** * This is the AppModule for the commercial version of Bitwarden. @@ -49,6 +50,7 @@ import { AppComponent } from "./app.component"; MaximumVaultTimeoutPolicyComponent, ActivateAutofillPolicyComponent, AutomaticAppLoginPolicyComponent, + FreeFamiliesSponsorshipPolicyComponent, ], bootstrap: [AppComponent], }) diff --git a/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.html b/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.html new file mode 100644 index 0000000000..b0dd1b688b --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.html @@ -0,0 +1,4 @@ + + + {{ "turnOn" | i18n }} + diff --git a/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts b/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts new file mode 100644 index 0000000000..521f865889 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts @@ -0,0 +1,20 @@ +import { Component } from "@angular/core"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { + BasePolicy, + BasePolicyComponent, +} from "@bitwarden/web-vault/app/admin-console/organizations/policies/base-policy.component"; + +export class FreeFamiliesSponsorshipPolicy extends BasePolicy { + name = "freeFamiliesSponsorship"; + description = "freeFamiliesSponsorshipPolicyDesc"; + type = PolicyType.FreeFamiliesSponsorshipPolicy; + component = FreeFamiliesSponsorshipPolicyComponent; +} + +@Component({ + selector: "policy-personal-ownership", + templateUrl: "free-families-sponsorship.component.html", +}) +export class FreeFamiliesSponsorshipPolicyComponent extends BasePolicyComponent {} diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 9834116c9f..236599ed69 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -24,6 +24,7 @@ import { } from "../admin-console/models/response/organization-connection.response"; import { OrganizationExportResponse } from "../admin-console/models/response/organization-export.response"; import { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response"; +import { PreValidateSponsorshipResponse } from "../admin-console/models/response/pre-validate-sponsorship.response"; import { ProviderOrganizationOrganizationDetailsResponse, ProviderOrganizationResponse, @@ -490,7 +491,9 @@ export abstract class ApiService { ) => Promise; deleteRevokeSponsorship: (sponsoringOrganizationId: string) => Promise; deleteRemoveSponsorship: (sponsoringOrgId: string) => Promise; - postPreValidateSponsorshipToken: (sponsorshipToken: string) => Promise; + postPreValidateSponsorshipToken: ( + sponsorshipToken: string, + ) => Promise; postRedeemSponsorship: ( sponsorshipToken: string, request: OrganizationSponsorshipRedeemRequest, diff --git a/libs/common/src/admin-console/enums/policy-type.enum.ts b/libs/common/src/admin-console/enums/policy-type.enum.ts index b05a03bb92..2ca9fbef8d 100644 --- a/libs/common/src/admin-console/enums/policy-type.enum.ts +++ b/libs/common/src/admin-console/enums/policy-type.enum.ts @@ -12,4 +12,5 @@ export enum PolicyType { DisablePersonalVaultExport = 10, // Disable personal vault export ActivateAutofill = 11, // Activates autofill with page load on the browser extension AutomaticAppLogIn = 12, // Enables automatic log in of apps from configured identity provider + FreeFamiliesSponsorshipPolicy = 13, // Disables free families plan for organization } diff --git a/libs/common/src/admin-console/models/response/organization-sponsorship.response.ts b/libs/common/src/admin-console/models/response/organization-sponsorship.response.ts new file mode 100644 index 0000000000..31f7f5907e --- /dev/null +++ b/libs/common/src/admin-console/models/response/organization-sponsorship.response.ts @@ -0,0 +1,10 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +export class OrganizationSponsorshipResponse extends BaseResponse { + isPolicyEnabled: string; + + constructor(response: any) { + super(response); + this.isPolicyEnabled = this.getResponseProperty("IsPolicyEnabled"); + } +} diff --git a/libs/common/src/admin-console/models/response/pre-validate-sponsorship.response.ts b/libs/common/src/admin-console/models/response/pre-validate-sponsorship.response.ts new file mode 100644 index 0000000000..53855c8409 --- /dev/null +++ b/libs/common/src/admin-console/models/response/pre-validate-sponsorship.response.ts @@ -0,0 +1,12 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +export class PreValidateSponsorshipResponse extends BaseResponse { + isTokenValid: boolean; + isFreeFamilyPolicyEnabled: boolean; + + constructor(response: any) { + super(response); + this.isTokenValid = this.getResponseProperty("IsTokenValid"); + this.isFreeFamilyPolicyEnabled = this.getResponseProperty("IsFreeFamilyPolicyEnabled"); + } +} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 69298db654..ec5a8e47d7 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -39,6 +39,7 @@ export enum FeatureFlag { SecurityTasks = "security-tasks", NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss", NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss", + DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship", } export type AllowedFeatureFlagTypes = boolean | number | string; @@ -88,6 +89,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.SecurityTasks]: FALSE, [FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE, [FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE, + [FeatureFlag.DisableFreeFamiliesSponsorship]: FALSE, } satisfies Record; export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue; diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 0c508bfeb8..293aa8aa90 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -29,6 +29,7 @@ import { } from "../admin-console/models/response/organization-connection.response"; import { OrganizationExportResponse } from "../admin-console/models/response/organization-export.response"; import { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response"; +import { PreValidateSponsorshipResponse } from "../admin-console/models/response/pre-validate-sponsorship.response"; import { ProviderOrganizationOrganizationDetailsResponse, ProviderOrganizationResponse, @@ -1680,8 +1681,10 @@ export class ApiService implements ApiServiceAbstraction { ); } - async postPreValidateSponsorshipToken(sponsorshipToken: string): Promise { - const r = await this.send( + async postPreValidateSponsorshipToken( + sponsorshipToken: string, + ): Promise { + const response = await this.send( "POST", "/organization/sponsorship/validate-token?sponsorshipToken=" + encodeURIComponent(sponsorshipToken), @@ -1689,7 +1692,8 @@ export class ApiService implements ApiServiceAbstraction { true, true, ); - return r as boolean; + + return new PreValidateSponsorshipResponse(response); } async postRedeemSponsorship(