[PM-1925][PM-2741][AC-1334] flexible collections export page (#5759)

* Use bitTypography for page title

* Replaced app-callout with bit-callout

* Replace button with bit-button

* Update radio buttons to use CL

* Use searchable select for fileFormat dropdown

* Remove unneeded divs (old styling)

* pm-1826 remove eslint-disable tailwindcss/no-custom-classname

* Removed for-attribute from bit-labels

* Removed bitInput from bit-selects

* Removed name-attribute from bit-selects

* Make format a required field

* Removed unused dependency on cryptoService

* Remove unused dependency on BroadcasterService

* Removed dependency on window

* Moved organizationId into BaseExportComponent

* Add vaultSelector

Add organizationService as new dependency
Retrieve organizations a user has access to
Add vaultSelector dropdown
Add `export from` label
Add exportFromHint

* Removed hint as discussed by product&design

* Add function to check for import/export permission

* Export callout should listen to changes

Even though the organizationId was changed, the Input did not trigger changing the scope

* Reading FlexibleCollections feature flag to show the vault-selector on export (#7196)

---------

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: aj-rosado <109146700+aj-rosado@users.noreply.github.com>
This commit is contained in:
Daniel James Smith 2023-12-14 13:55:54 +01:00 committed by GitHub
parent 12de4b1386
commit 60d9f3d150
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 180 additions and 158 deletions

View File

@ -4,9 +4,9 @@ import { Router } from "@angular/router";
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component"; import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -20,7 +20,6 @@ import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export"
}) })
export class ExportComponent extends BaseExportComponent { export class ExportComponent extends BaseExportComponent {
constructor( constructor(
cryptoService: CryptoService,
i18nService: I18nService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
exportService: VaultExportServiceAbstraction, exportService: VaultExportServiceAbstraction,
@ -32,20 +31,20 @@ export class ExportComponent extends BaseExportComponent {
formBuilder: UntypedFormBuilder, formBuilder: UntypedFormBuilder,
fileDownloadService: FileDownloadService, fileDownloadService: FileDownloadService,
dialogService: DialogService, dialogService: DialogService,
organizationService: OrganizationService,
) { ) {
super( super(
cryptoService,
i18nService, i18nService,
platformUtilsService, platformUtilsService,
exportService, exportService,
eventCollectionService, eventCollectionService,
policyService, policyService,
window,
logService, logService,
userVerificationService, userVerificationService,
formBuilder, formBuilder,
fileDownloadService, fileDownloadService,
dialogService, dialogService,
organizationService,
); );
} }

View File

@ -3,10 +3,9 @@ import { UntypedFormBuilder } from "@angular/forms";
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component"; import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -14,15 +13,12 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export"; import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export";
const BroadcasterSubscriptionId = "ExportComponent";
@Component({ @Component({
selector: "app-export", selector: "app-export",
templateUrl: "export.component.html", templateUrl: "export.component.html",
}) })
export class ExportComponent extends BaseExportComponent implements OnInit { export class ExportComponent extends BaseExportComponent implements OnInit {
constructor( constructor(
cryptoService: CryptoService,
i18nService: I18nService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
exportService: VaultExportServiceAbstraction, exportService: VaultExportServiceAbstraction,
@ -30,28 +26,23 @@ export class ExportComponent extends BaseExportComponent implements OnInit {
policyService: PolicyService, policyService: PolicyService,
userVerificationService: UserVerificationService, userVerificationService: UserVerificationService,
formBuilder: UntypedFormBuilder, formBuilder: UntypedFormBuilder,
private broadcasterService: BroadcasterService,
logService: LogService, logService: LogService,
fileDownloadService: FileDownloadService, fileDownloadService: FileDownloadService,
dialogService: DialogService, dialogService: DialogService,
organizationService: OrganizationService,
) { ) {
super( super(
cryptoService,
i18nService, i18nService,
platformUtilsService, platformUtilsService,
exportService, exportService,
eventCollectionService, eventCollectionService,
policyService, policyService,
window,
logService, logService,
userVerificationService, userVerificationService,
formBuilder, formBuilder,
fileDownloadService, fileDownloadService,
dialogService, dialogService,
organizationService,
); );
} }
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
} }

View File

@ -3,10 +3,11 @@ import { UntypedFormBuilder } from "@angular/forms";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -23,7 +24,6 @@ import { ExportComponent } from "../../../../tools/vault-export/export.component
// eslint-disable-next-line rxjs-angular/prefer-takeuntil // eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class OrganizationVaultExportComponent extends ExportComponent { export class OrganizationVaultExportComponent extends ExportComponent {
constructor( constructor(
cryptoService: CryptoService,
i18nService: I18nService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
exportService: VaultExportServiceAbstraction, exportService: VaultExportServiceAbstraction,
@ -35,9 +35,10 @@ export class OrganizationVaultExportComponent extends ExportComponent {
formBuilder: UntypedFormBuilder, formBuilder: UntypedFormBuilder,
fileDownloadService: FileDownloadService, fileDownloadService: FileDownloadService,
dialogService: DialogService, dialogService: DialogService,
organizationService: OrganizationService,
configService: ConfigServiceAbstraction,
) { ) {
super( super(
cryptoService,
i18nService, i18nService,
platformUtilsService, platformUtilsService,
exportService, exportService,
@ -48,6 +49,8 @@ export class OrganizationVaultExportComponent extends ExportComponent {
formBuilder, formBuilder,
fileDownloadService, fileDownloadService,
dialogService, dialogService,
organizationService,
configService,
); );
} }

View File

@ -1,5 +1,3 @@
<!-- Please remove this disable statement when editing this file! -->
<!-- eslint-disable tailwindcss/no-custom-classname -->
<form <form
#form #form
(ngSubmit)="submit()" (ngSubmit)="submit()"
@ -7,137 +5,112 @@
[formGroup]="exportForm" [formGroup]="exportForm"
*ngIf="exportForm" *ngIf="exportForm"
> >
<div class="page-header"> <h1 bitTypography="h1">{{ "exportVault" | i18n }}</h1>
<h1>{{ "exportVault" | i18n }}</h1>
</div>
<app-callout type="error" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy"> <bit-callout type="danger" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
{{ "personalVaultExportPolicyInEffect" | i18n }} {{ "personalVaultExportPolicyInEffect" | i18n }}
</app-callout> </bit-callout>
<app-export-scope-callout <app-export-scope-callout
[organizationId]="organizationId" [organizationId]="organizationId"
*ngIf="!disabledByPolicy" *ngIf="!disabledByPolicy"
></app-export-scope-callout> ></app-export-scope-callout>
<div class="row"> <bit-form-field *ngIf="flexibleCollectionsEnabled$ | async">
<div class="col-6"> <bit-label>{{ "exportFrom" | i18n }}</bit-label>
<bit-form-field> <bit-select formControlName="vaultSelector">
<bit-label class="tw-text-lg" for="format">{{ "fileFormat" | i18n }}</bit-label> <bit-option [label]="'myVault' | i18n" value="myVault" icon="bwi-user" />
<select bitInput name="format" formControlName="format"> <bit-option
<option *ngFor="let f of formatOptions" [value]="f.value">{{ f.name }}</option> *ngFor="let o of organizations$ | async"
</select> [value]="o.id"
</bit-form-field> [label]="o.name"
</div> icon="bwi-business"
</div> />
<div class="row"> </bit-select>
<div class="form-group col-6"> </bit-form-field>
<ng-container *ngIf="format === 'encrypted_json'">
<div role="radiogroup" aria-labelledby="exportTypeHeading">
<label id="exportTypeHeading" class="tw-semi-bold tw-text-lg">
{{ "exportTypeHeading" | i18n }}
</label>
<div appBoxRow name="FileTypeOptions" class="tw-flex tw-items-center"> <bit-form-field>
<div> <bit-label>{{ "fileFormat" | i18n }}</bit-label>
<input <bit-select formControlName="format">
type="radio" <bit-option *ngFor="let f of formatOptions" [value]="f.value" [label]="f.name" />
class="radio" </bit-select>
name="fileEncryptionType" </bit-form-field>
id="AccountEncrypted"
[value]="encryptedExportType.AccountEncrypted"
formControlName="fileEncryptionType"
[checked]="fileEncryptionType === encryptedExportType.AccountEncrypted"
/>
</div>
<div>
<label class="tw-semi-bold tw-text-md tw-my-1 tw-ml-1" for="AccountEncrypted">
{{ "accountRestricted" | i18n }}
</label>
</div>
</div>
<div class="tw-regular ml-3 pb-2 tw-text-sm"> <ng-container *ngIf="format === 'encrypted_json'">
{{ "accountRestrictedOptionDescription" | i18n }} <bit-radio-group formControlName="fileEncryptionType" aria-label="exportTypeHeading">
</div> <bit-label>{{ "exportTypeHeading" | i18n }}</bit-label>
<div class="tw-flex tw-items-center"> <bit-radio-button
<div> id="AccountEncrypted"
<input name="fileEncryptionType"
type="radio" class="tw-block"
class="radio" [value]="encryptedExportType.AccountEncrypted"
name="fileEncryptionType" checked="fileEncryptionType === encryptedExportType.AccountEncrypted"
id="FileEncrypted"
[value]="encryptedExportType.FileEncrypted"
formControlName="fileEncryptionType"
[checked]="fileEncryptionType === encryptedExportType.FileEncrypted"
/>
</div>
<div>
<label class="tw-semi-bold tw-text-md tw-my-1 tw-ml-1" for="FileEncrypted">{{
"passwordProtected" | i18n
}}</label>
</div>
</div>
<div class="tw-regular ml-3 tw-text-sm">
{{ "passwordProtectedOptionDescription" | i18n }}
</div>
</div>
<br />
<ng-container *ngIf="fileEncryptionType == encryptedExportType.FileEncrypted">
<div class="tw-mb-3">
<bit-form-field>
<bit-label>{{ "filePassword" | i18n }}</bit-label>
<input
bitInput
type="password"
id="filePassword"
formControlName="filePassword"
name="password"
/>
<button
type="button"
bitSuffix
bitIconButton
bitPasswordInputToggle
[(toggled)]="showFilePassword"
></button>
<bit-hint>{{ "exportPasswordDescription" | i18n }}</bit-hint>
</bit-form-field>
<app-password-strength [password]="filePassword" [showText]="true">
</app-password-strength>
</div>
<bit-form-field>
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
<input
bitInput
type="password"
id="confirmFilePassword"
formControlName="confirmFilePassword"
name="confirmFilePassword"
/>
<button
type="button"
bitSuffix
bitIconButton
bitPasswordInputToggle
[(toggled)]="showFilePassword"
></button>
</bit-form-field>
</ng-container>
</ng-container>
<button
type="submit"
class="btn btn-primary btn-submit"
[disabled]="form.loading || disabledByPolicy"
[ngClass]="{ manual: disabledByPolicy }"
> >
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <bit-label>{{ "accountRestricted" | i18n }}</bit-label>
<span>{{ "confirmFormat" | i18n }}</span> <bit-hint>{{ "accountRestrictedOptionDescription" | i18n }}</bit-hint>
</button> </bit-radio-button>
</div>
</div> <bit-radio-button
id="FileEncrypted"
name="fileEncryptionType"
class="tw-block"
[value]="encryptedExportType.FileEncrypted"
checked="fileEncryptionType === encryptedExportType.FileEncrypted"
>
<bit-label>{{ "passwordProtected" | i18n }}</bit-label>
<bit-hint>{{ "passwordProtectedOptionDescription" | i18n }}</bit-hint>
</bit-radio-button>
</bit-radio-group>
<ng-container *ngIf="fileEncryptionType == encryptedExportType.FileEncrypted">
<div class="tw-mb-3">
<bit-form-field>
<bit-label>{{ "filePassword" | i18n }}</bit-label>
<input
bitInput
type="password"
id="filePassword"
formControlName="filePassword"
name="password"
/>
<button
type="button"
bitSuffix
bitIconButton
bitPasswordInputToggle
[(toggled)]="showFilePassword"
></button>
<bit-hint>{{ "exportPasswordDescription" | i18n }}</bit-hint>
</bit-form-field>
<app-password-strength [password]="filePassword" [showText]="true"> </app-password-strength>
</div>
<bit-form-field>
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
<input
bitInput
type="password"
id="confirmFilePassword"
formControlName="confirmFilePassword"
name="confirmFilePassword"
/>
<button
type="button"
bitSuffix
bitIconButton
bitPasswordInputToggle
[(toggled)]="showFilePassword"
></button>
</bit-form-field>
</ng-container>
</ng-container>
<button
bitButton
type="submit"
buttonType="primary"
[loading]="form.loading"
[disabled]="disabledByPolicy"
>
{{ "confirmFormat" | i18n }}
</button>
</form> </form>

View File

@ -4,9 +4,11 @@ import { firstValueFrom } from "rxjs";
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component"; import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -22,12 +24,15 @@ import { openUserVerificationPrompt } from "../../auth/shared/components/user-ve
templateUrl: "export.component.html", templateUrl: "export.component.html",
}) })
export class ExportComponent extends BaseExportComponent { export class ExportComponent extends BaseExportComponent {
organizationId: string;
encryptedExportType = EncryptedExportType; encryptedExportType = EncryptedExportType;
protected showFilePassword: boolean; protected showFilePassword: boolean;
protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollections,
false,
);
constructor( constructor(
cryptoService: CryptoService,
i18nService: I18nService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
exportService: VaultExportServiceAbstraction, exportService: VaultExportServiceAbstraction,
@ -38,20 +43,21 @@ export class ExportComponent extends BaseExportComponent {
formBuilder: UntypedFormBuilder, formBuilder: UntypedFormBuilder,
fileDownloadService: FileDownloadService, fileDownloadService: FileDownloadService,
dialogService: DialogService, dialogService: DialogService,
organizationService: OrganizationService,
protected configService: ConfigServiceAbstraction,
) { ) {
super( super(
cryptoService,
i18nService, i18nService,
platformUtilsService, platformUtilsService,
exportService, exportService,
eventCollectionService, eventCollectionService,
policyService, policyService,
window,
logService, logService,
userVerificationService, userVerificationService,
formBuilder, formBuilder,
fileDownloadService, fileDownloadService,
dialogService, dialogService,
organizationService,
); );
} }

View File

@ -1074,6 +1074,9 @@
"export": { "export": {
"message": "Export" "message": "Export"
}, },
"exportFrom": {
"message": "Export from"
},
"exportVault": { "exportVault": {
"message": "Export vault" "message": "Export vault"
}, },

View File

@ -8,8 +8,6 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
templateUrl: "export-scope-callout.component.html", templateUrl: "export-scope-callout.component.html",
}) })
export class ExportScopeCalloutComponent implements OnInit { export class ExportScopeCalloutComponent implements OnInit {
@Input() organizationId: string = null;
show = false; show = false;
scopeConfig: { scopeConfig: {
title: string; title: string;
@ -17,6 +15,17 @@ export class ExportScopeCalloutComponent implements OnInit {
scopeIdentifier: string; scopeIdentifier: string;
}; };
private _organizationId: string;
get organizationId(): string {
return this._organizationId;
}
@Input() set organizationId(value: string) {
this._organizationId = value;
this.getScopeMessage(this._organizationId);
}
constructor( constructor(
protected organizationService: OrganizationService, protected organizationService: OrganizationService,
protected stateService: StateService, protected stateService: StateService,
@ -26,18 +35,23 @@ export class ExportScopeCalloutComponent implements OnInit {
if (!this.organizationService.hasOrganizations()) { if (!this.organizationService.hasOrganizations()) {
return; return;
} }
await this.getScopeMessage(this.organizationId);
this.show = true;
}
private async getScopeMessage(organizationId: string) {
this.scopeConfig = this.scopeConfig =
this.organizationId != null organizationId != null
? { ? {
title: "exportingOrganizationVaultTitle", title: "exportingOrganizationVaultTitle",
description: "exportingOrganizationVaultDesc", description: "exportingOrganizationVaultDesc",
scopeIdentifier: this.organizationService.get(this.organizationId).name, scopeIdentifier: this.organizationService.get(organizationId).name,
} }
: { : {
title: "exportingPersonalVaultTitle", title: "exportingPersonalVaultTitle",
description: "exportingIndividualVaultDescription", description: "exportingIndividualVaultDescription",
scopeIdentifier: await this.stateService.getEmail(), scopeIdentifier: await this.stateService.getEmail(),
}; };
this.show = true;
} }
} }

View File

@ -1,17 +1,22 @@
import { Directive, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from "@angular/core"; import { Directive, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from "@angular/core";
import { UntypedFormBuilder, Validators } from "@angular/forms"; import { UntypedFormBuilder, Validators } from "@angular/forms";
import { merge, startWith, Subject, takeUntil } from "rxjs"; import { concat, map, merge, Observable, startWith, Subject, takeUntil } from "rxjs";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import {
OrganizationService,
canAccessImportExport,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedExportType } from "@bitwarden/common/tools/enums/encrypted-export-type.enum"; import { EncryptedExportType } from "@bitwarden/common/tools/enums/encrypted-export-type.enum";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export"; import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export";
@ -26,13 +31,22 @@ export class ExportComponent implements OnInit, OnDestroy {
filePasswordValue: string = null; filePasswordValue: string = null;
formPromise: Promise<string>; formPromise: Promise<string>;
private _disabledByPolicy = false; private _disabledByPolicy = false;
protected organizationId: string = null;
organizations$: Observable<Organization[]>;
protected get disabledByPolicy(): boolean { protected get disabledByPolicy(): boolean {
return this._disabledByPolicy; return this._disabledByPolicy;
} }
exportForm = this.formBuilder.group({ exportForm = this.formBuilder.group({
format: ["json"], vaultSelector: [
"myVault",
{
nonNullable: true,
validators: [Validators.required],
},
],
format: ["json", Validators.required],
secret: [""], secret: [""],
filePassword: ["", Validators.required], filePassword: ["", Validators.required],
confirmFilePassword: ["", Validators.required], confirmFilePassword: ["", Validators.required],
@ -48,21 +62,27 @@ export class ExportComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
constructor( constructor(
protected cryptoService: CryptoService,
protected i18nService: I18nService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected platformUtilsService: PlatformUtilsService,
protected exportService: VaultExportServiceAbstraction, protected exportService: VaultExportServiceAbstraction,
protected eventCollectionService: EventCollectionService, protected eventCollectionService: EventCollectionService,
private policyService: PolicyService, private policyService: PolicyService,
protected win: Window,
private logService: LogService, private logService: LogService,
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
private formBuilder: UntypedFormBuilder, private formBuilder: UntypedFormBuilder,
protected fileDownloadService: FileDownloadService, protected fileDownloadService: FileDownloadService,
protected dialogService: DialogService, protected dialogService: DialogService,
protected organizationService: OrganizationService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.organizations$ = concat(
this.organizationService.memberOrganizations$.pipe(
canAccessImportExport(this.i18nService),
map((orgs) => orgs.sort(Utils.getSortFunction(this.i18nService, "name"))),
),
);
this.policyService this.policyService
.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport) .policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport)
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
@ -73,6 +93,19 @@ export class ExportComponent implements OnInit, OnDestroy {
} }
}); });
if (this.organizationId) {
this.exportForm.controls.vaultSelector.patchValue(this.organizationId);
this.exportForm.controls.vaultSelector.disable();
} else {
this.exportForm.controls.vaultSelector.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe((value) => {
this.organizationId = value != "myVault" ? value : undefined;
});
this.exportForm.controls.vaultSelector.setValue("myVault");
}
merge( merge(
this.exportForm.get("format").valueChanges, this.exportForm.get("format").valueChanges,
this.exportForm.get("fileEncryptionType").valueChanges, this.exportForm.get("fileEncryptionType").valueChanges,