From 8c339ead1992c2641375a6c167ddd3249e5b146c Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:50:29 +0100 Subject: [PATCH] Enable import into managed collections within Password Manager (#13288) Members of an org that lacked the canAccessImport permission were not able to import into an organization. With the introduction of flexible collections, Bitwarden would like to enable members of an organization to import into collections they manage Co-authored-by: Daniel James Smith --- .../src/components/import.component.ts | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index 5b59055bc4e..6ea58545352 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -15,8 +15,8 @@ import { } from "@angular/core"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import * as JSZip from "jszip"; -import { concat, Observable, Subject, lastValueFrom, combineLatest, firstValueFrom } from "rxjs"; -import { filter, map, switchMap, takeUntil } from "rxjs/operators"; +import { Observable, Subject, lastValueFrom, combineLatest, firstValueFrom } from "rxjs"; +import { combineLatestWith, filter, map, switchMap, takeUntil } from "rxjs/operators"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -239,11 +239,10 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit { async ngOnInit() { this.setImportOptions(); - await this.initializeOrganizations(); - if (this.organizationId && (await this.canAccessImport(this.organizationId))) { - this.handleOrganizationImportInit(); + if (this.organizationId) { + await this.handleOrganizationImportInit(); } else { - this.handleImportInit(); + await this.handleImportInit(); } this.formGroup.controls.format.valueChanges @@ -255,7 +254,19 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit { await this.handlePolicies(); } - private handleOrganizationImportInit() { + private async handleOrganizationImportInit() { + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.organizations$ = this.organizationService + .memberOrganizations$(userId) + .pipe( + map((orgs) => + orgs.filter( + (org) => + org.id == this.organizationId && (org.canAccessImport || org.canCreateNewCollections), + ), + ), + ); + this.formGroup.controls.vaultSelector.patchValue(this.organizationId); this.formGroup.controls.vaultSelector.disable(); @@ -268,7 +279,7 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit { this.isFromAC = true; } - private handleImportInit() { + private async handleImportInit() { // Filter out the no folder-item from folderViews$ this.folders$ = this.activeUserId$.pipe( switchMap((userId) => { @@ -279,6 +290,17 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit { this.formGroup.controls.targetSelector.disable(); + // Retrieve all organizations a user is a member of and has collections they can manage + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.organizations$ = this.organizationService.memberOrganizations$(userId).pipe( + combineLatestWith(this.collectionService.decryptedCollections$), + map(([organizations, collections]) => + organizations + .filter((org) => collections.some((c) => c.organizationId === org.id && c.manage)) + .sort(Utils.getSortFunction(this.i18nService, "name")), + ), + ); + combineLatest([this.formGroup.controls.vaultSelector.valueChanges, this.organizations$]) .pipe(takeUntil(this.destroy$)) .subscribe(([value, organizations]) => { @@ -303,18 +325,6 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit { this.formGroup.controls.vaultSelector.setValue("myVault"); } - private async initializeOrganizations() { - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - this.organizations$ = concat( - this.organizationService.memberOrganizations$(userId).pipe( - // Import is an alternative way to create collections during onboarding, so import from Password Manager - // is available to any user who can create collections in the organization. - map((orgs) => orgs.filter((org) => org.canAccessImport || org.canCreateNewCollections)), - map((orgs) => orgs.sort(Utils.getSortFunction(this.i18nService, "name"))), - ), - ); - } - private async handlePolicies() { combineLatest([ this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership),