From 389c7a57f5f2e8bb2c68c394f7a43b12cca27d8f Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Tue, 23 Sep 2025 08:14:25 -0700 Subject: [PATCH] add support for export-scope-callout.component to conditionally render organizational export message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • use config service to capture feature flag status • use platform service and routing to determine admin console context • use the above two variables to conditionally render export descriptions added to locale files --- apps/browser/src/_locales/en/messages.json | 18 +++++ apps/desktop/src/locales/en/messages.json | 18 +++++ apps/web/src/locales/en/messages.json | 18 +++++ libs/common/src/enums/feature-flag.enum.ts | 2 +- .../export-scope-callout.component.ts | 80 +++++++++++++------ 5 files changed, 111 insertions(+), 25 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 72c3892af62..1ac7fcacaed 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3193,6 +3193,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collection will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index aa597a6bf97..cc0df3b21c6 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2654,6 +2654,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collection will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 11d2d8e3dd8..597f312eccd 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -6972,6 +6972,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collection will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 18134fee2c3..9c4773eb2dc 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -74,7 +74,7 @@ const FALSE = false as boolean; */ export const DefaultFeatureFlagValue = { /* Admin Console Team */ - [FeatureFlag.CreateDefaultLocation]: FALSE, + [FeatureFlag.CreateDefaultLocation]: true, [FeatureFlag.CollectionVaultRefactor]: FALSE, /* Autofill */ diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts index 2b03234c5e2..d5df590fa5c 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts @@ -1,7 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { CommonModule } from "@angular/common"; -import { Component, effect, input } from "@angular/core"; +import { Component, Optional, effect, input } from "@angular/core"; +import { Router } from "@angular/router"; import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -11,6 +12,10 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { ClientType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CalloutModule } from "@bitwarden/components"; @Component({ @@ -34,6 +39,9 @@ export class ExportScopeCalloutComponent { constructor( protected organizationService: OrganizationService, protected accountService: AccountService, + private configService: ConfigService, + private platformUtilsService: PlatformUtilsService, + @Optional() private router?: Router, ) { effect(async () => { this.show = false; @@ -42,30 +50,54 @@ export class ExportScopeCalloutComponent { }); } + private get isAdminConsoleContext(): boolean { + const isWeb = this.platformUtilsService.getClientType?.() === ClientType.Web; + if (!isWeb || !this.router) { + return false; + } + try { + const url = this.router.url ?? ""; + return url.includes("/organizations/"); + } catch { + return false; + } + } + private async getScopeMessage(organizationId: string, exportFormat: string): Promise { const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - this.scopeConfig = - organizationId != null - ? { - title: "exportingOrganizationVaultTitle", - description: "exportingOrganizationVaultDesc", - scopeIdentifier: ( - await firstValueFrom( - this.organizationService - .organizations$(userId) - .pipe(getOrganizationById(organizationId)), - ) - ).name, - } - : { - title: "exportingPersonalVaultTitle", - description: - exportFormat == "zip" - ? "exportingIndividualVaultWithAttachmentsDescription" - : "exportingIndividualVaultDescription", - scopeIdentifier: await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), - ), - }; + const enforceOrgDataOwnershipPolicyEnabled = await this.configService.getFeatureFlag( + FeatureFlag.CreateDefaultLocation, + ); + + const orgExportDescription = enforceOrgDataOwnershipPolicyEnabled + ? this.isAdminConsoleContext + ? "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc" + : "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc" + : "exportingOrganizationVaultDesc"; + + if (organizationId != null) { + // exporting from organizational vault + const org = await firstValueFrom( + this.organizationService.organizations$(userId).pipe(getOrganizationById(organizationId)), + ); + + this.scopeConfig = { + title: "exportingOrganizationVaultTitle", + description: orgExportDescription, + scopeIdentifier: org?.name ?? "", + }; + } else { + this.scopeConfig = { + // exporting from individual vault + title: "exportingPersonalVaultTitle", + description: + exportFormat === "zip" + ? "exportingIndividualVaultWithAttachmentsDescription" + : "exportingIndividualVaultDescription", + scopeIdentifier: + (await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.email)))) ?? + "", + }; + } } }