1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

Import: Migrate input to handle and validate organization (#16920)

* Verify route and input to be a valid organizationId

* Optimize canImport check
Instead of loading the user and organization again, used the previously loaded organization and check the permission directly

* Reset organization in case organizationId is set to undefined

---------

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
Daniel James Smith
2025-10-17 16:45:59 +02:00
committed by GitHub
parent a860f218bd
commit b8d55c4db1
2 changed files with 38 additions and 32 deletions

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom, map } from "rxjs"; import { firstValueFrom, map } from "rxjs";
@@ -11,6 +9,7 @@ import {
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { isId, OrganizationId } from "@bitwarden/common/types/guid";
import { ImportCollectionServiceAbstraction } from "@bitwarden/importer-core"; import { ImportCollectionServiceAbstraction } from "@bitwarden/importer-core";
import { ImportComponent } from "@bitwarden/importer-ui"; import { ImportComponent } from "@bitwarden/importer-ui";
@@ -31,7 +30,7 @@ import { ImportCollectionAdminService } from "./import-collection-admin.service"
], ],
}) })
export class OrgImportComponent implements OnInit { export class OrgImportComponent implements OnInit {
protected routeOrgId: string = null; protected routeOrgId: OrganizationId | undefined = undefined;
protected loading = false; protected loading = false;
protected disabled = false; protected disabled = false;
@@ -43,7 +42,16 @@ export class OrgImportComponent implements OnInit {
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
this.routeOrgId = this.route.snapshot.paramMap.get("organizationId"); const orgIdParam = this.route.snapshot.paramMap.get("organizationId");
if (orgIdParam === undefined) {
throw new Error("`organizationId` is a required route parameter");
}
if (!isId<OrganizationId>(orgIdParam)) {
throw new Error("Invalid OrganizationId provided in route parameter `organizationId`");
}
this.routeOrgId = orgIdParam;
} }
/** /**

View File

@@ -35,10 +35,7 @@ import {
CollectionView, CollectionView,
} from "@bitwarden/admin-console/common"; } from "@bitwarden/admin-console/common";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
getOrganizationById,
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 { 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 { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
@@ -48,7 +45,9 @@ import { ClientType } from "@bitwarden/common/enums";
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 { getById } from "@bitwarden/common/platform/misc";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { isId, OrganizationId } from "@bitwarden/common/types/guid";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
@@ -122,21 +121,34 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit {
collections$: Observable<CollectionView[]>; collections$: Observable<CollectionView[]>;
organizations$: Observable<Organization[]>; organizations$: Observable<Organization[]>;
private _organizationId: string; private _organizationId: OrganizationId | undefined;
get organizationId(): string { get organizationId(): OrganizationId | undefined {
return this._organizationId; return this._organizationId;
} }
@Input() set organizationId(value: string) { /**
* Enables the hosting control to pass in an organizationId
* If a organizationId is provided, the organization selection is disabled.
*/
@Input() set organizationId(value: OrganizationId | string | undefined) {
if (Utils.isNullOrEmpty(value)) {
this._organizationId = undefined;
this.organization = undefined;
return;
}
if (!isId<OrganizationId>(value)) {
this._organizationId = undefined;
this.organization = undefined;
return;
}
this._organizationId = value; this._organizationId = value;
getUserId(this.accountService.activeAccount$) getUserId(this.accountService.activeAccount$)
.pipe( .pipe(
switchMap((userId) => switchMap((userId) => this.organizationService.organizations$(userId).pipe(getById(value))),
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this._organizationId)),
),
) )
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe((organization) => { .subscribe((organization) => {
@@ -151,7 +163,7 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit {
@Input() @Input()
onImportFromBrowser: (browser: string, profile: string) => Promise<any[]>; onImportFromBrowser: (browser: string, profile: string) => Promise<any[]>;
protected organization: Organization; protected organization: Organization | undefined = undefined;
protected destroy$ = new Subject<void>(); protected destroy$ = new Subject<void>();
protected readonly isCardTypeRestricted$: Observable<boolean> = protected readonly isCardTypeRestricted$: Observable<boolean> =
@@ -451,7 +463,7 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit {
importContents, importContents,
this.organizationId, this.organizationId,
this.formGroup.controls.targetSelector.value, this.formGroup.controls.targetSelector.value,
(await this.canAccessImport(this.organizationId)) && this.isFromAC, this.organization?.canAccessImport && this.isFromAC,
); );
//No errors, display success message //No errors, display success message
@@ -471,20 +483,6 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit {
} }
} }
private async canAccessImport(organizationId?: string): Promise<boolean> {
if (!organizationId) {
return false;
}
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
return (
await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
)
)?.canAccessImport;
}
getFormatInstructionTitle() { getFormatInstructionTitle() {
if (this.format == null) { if (this.format == null) {
return null; return null;