diff --git a/apps/browser/src/autofill/spec/fido2-testing-utils.ts b/apps/browser/src/autofill/spec/fido2-testing-utils.ts index c9b39c16cc..5c739235dc 100644 --- a/apps/browser/src/autofill/spec/fido2-testing-utils.ts +++ b/apps/browser/src/autofill/spec/fido2-testing-utils.ts @@ -3,7 +3,7 @@ import { mock } from "jest-mock-extended"; import { AssertCredentialResult, CreateCredentialResult, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; export function createCredentialCreationOptionsMock( customFields: Partial = {}, diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index b5ad51515b..e7cc597097 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -79,6 +79,9 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction"; +import { Fido2ClientService as Fido2ClientServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; +import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction"; import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/platform/abstractions/file-upload/file-upload.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; @@ -106,6 +109,8 @@ import { DefaultConfigService } from "@bitwarden/common/platform/services/config import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; +import { Fido2AuthenticatorService } from "@bitwarden/common/platform/services/fido2/fido2-authenticator.service"; +import { Fido2ClientService } from "@bitwarden/common/platform/services/fido2/fido2-client.service"; import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service"; import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; @@ -158,9 +163,6 @@ import { UserId } from "@bitwarden/common/types/guid"; import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/common/vault/abstractions/collection.service"; -import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/vault/abstractions/fido2/fido2-authenticator.service.abstraction"; -import { Fido2ClientService as Fido2ClientServiceAbstraction } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; -import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/vault/abstractions/fido2/fido2-user-interface.service.abstraction"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService as InternalFolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -171,8 +173,6 @@ import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwar import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/services/collection.service"; -import { Fido2AuthenticatorService } from "@bitwarden/common/vault/services/fido2/fido2-authenticator.service"; -import { Fido2ClientService } from "@bitwarden/common/vault/services/fido2/fido2-client.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; diff --git a/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts b/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts index 49f248c7b8..d77a60d3c7 100644 --- a/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts +++ b/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts @@ -3,7 +3,7 @@ import { AssertCredentialResult, CreateCredentialParams, CreateCredentialResult, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; type SharedFido2ScriptInjectionDetails = { runAt: browser.contentScripts.RegisteredContentScriptOptions["runAt"]; diff --git a/apps/browser/src/vault/fido2/background/fido2.background.spec.ts b/apps/browser/src/vault/fido2/background/fido2.background.spec.ts index 534d8a99c5..5da51618ac 100644 --- a/apps/browser/src/vault/fido2/background/fido2.background.spec.ts +++ b/apps/browser/src/vault/fido2/background/fido2.background.spec.ts @@ -1,13 +1,13 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AssertCredentialParams, CreateCredentialParams, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Fido2ClientService } from "@bitwarden/common/platform/services/fido2/fido2-client.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { Fido2ClientService } from "@bitwarden/common/vault/services/fido2/fido2-client.service"; import { createPortSpyMock } from "../../../autofill/spec/autofill-mocks"; import { diff --git a/apps/browser/src/vault/fido2/background/fido2.background.ts b/apps/browser/src/vault/fido2/background/fido2.background.ts index 5e51e05d77..0666f804f2 100644 --- a/apps/browser/src/vault/fido2/background/fido2.background.ts +++ b/apps/browser/src/vault/fido2/background/fido2.background.ts @@ -1,14 +1,14 @@ import { firstValueFrom, startWith } from "rxjs"; import { pairwise } from "rxjs/operators"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AssertCredentialParams, AssertCredentialResult, CreateCredentialParams, CreateCredentialResult, Fido2ClientService, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; diff --git a/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts b/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts index 55bf2468d6..d4ad7209b7 100644 --- a/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts @@ -16,14 +16,14 @@ import { import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserRequestedFallbackAbortReason } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +import { UserRequestedFallbackAbortReason } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction, Fido2UserInterfaceSession, NewCredentialParams, PickCredentialParams, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-user-interface.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { BrowserApi } from "../../platform/browser/browser-api"; import { closeFido2Popout, openFido2Popout } from "../popup/utils/vault-popout-window"; diff --git a/apps/browser/src/vault/fido2/content/content-script.spec.ts b/apps/browser/src/vault/fido2/content/content-script.spec.ts index 0c2a52ed10..c9f970a30c 100644 --- a/apps/browser/src/vault/fido2/content/content-script.spec.ts +++ b/apps/browser/src/vault/fido2/content/content-script.spec.ts @@ -1,6 +1,6 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { CreateCredentialResult } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +import { CreateCredentialResult } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { createPortSpyMock } from "../../../autofill/spec/autofill-mocks"; import { triggerPortOnDisconnectEvent } from "../../../autofill/spec/testing-utils"; diff --git a/apps/browser/src/vault/fido2/content/content-script.ts b/apps/browser/src/vault/fido2/content/content-script.ts index fe3aafe9fb..ad9f526f6c 100644 --- a/apps/browser/src/vault/fido2/content/content-script.ts +++ b/apps/browser/src/vault/fido2/content/content-script.ts @@ -1,7 +1,7 @@ import { AssertCredentialParams, CreateCredentialParams, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { sendExtensionMessage } from "../../../autofill/utils"; import { Fido2PortName } from "../enums/fido2-port-name.enum"; diff --git a/apps/browser/src/vault/fido2/content/messaging/message.ts b/apps/browser/src/vault/fido2/content/messaging/message.ts index b803b97f92..d42c10a5d8 100644 --- a/apps/browser/src/vault/fido2/content/messaging/message.ts +++ b/apps/browser/src/vault/fido2/content/messaging/message.ts @@ -3,7 +3,7 @@ import { CreateCredentialResult, AssertCredentialParams, AssertCredentialResult, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; export enum MessageType { CredentialCreationRequest, diff --git a/apps/browser/src/vault/fido2/content/messaging/messenger.ts b/apps/browser/src/vault/fido2/content/messaging/messenger.ts index f05c138eab..ea4049ac64 100644 --- a/apps/browser/src/vault/fido2/content/messaging/messenger.ts +++ b/apps/browser/src/vault/fido2/content/messaging/messenger.ts @@ -1,4 +1,4 @@ -import { FallbackRequestedError } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +import { FallbackRequestedError } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { Message, MessageType } from "./message"; diff --git a/apps/browser/src/vault/fido2/content/page-script.ts b/apps/browser/src/vault/fido2/content/page-script.ts index 5b04f7c1dd..5898dbd04d 100644 --- a/apps/browser/src/vault/fido2/content/page-script.ts +++ b/apps/browser/src/vault/fido2/content/page-script.ts @@ -1,4 +1,4 @@ -import { FallbackRequestedError } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +import { FallbackRequestedError } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { WebauthnUtils } from "../webauthn-utils"; diff --git a/apps/browser/src/vault/fido2/webauthn-utils.ts b/apps/browser/src/vault/fido2/webauthn-utils.ts index 6ac7af4ce3..618e692aad 100644 --- a/apps/browser/src/vault/fido2/webauthn-utils.ts +++ b/apps/browser/src/vault/fido2/webauthn-utils.ts @@ -1,8 +1,8 @@ import { CreateCredentialResult, AssertCredentialResult, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; -import { Fido2Utils } from "@bitwarden/common/vault/services/fido2/fido2-utils"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; +import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils"; import { InsecureAssertCredentialParams, diff --git a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts index a0d7617ce8..f386665186 100644 --- a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts @@ -3,7 +3,6 @@ import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from "@angula import { AbstractControl, FormBuilder, Validators } from "@angular/forms"; import { combineLatest, - from, map, Observable, of, @@ -23,7 +22,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { CollectionResponse } from "@bitwarden/common/vault/models/response/collection.response"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { BitValidators, DialogService } from "@bitwarden/components"; @@ -56,7 +54,10 @@ export interface CollectionDialogParams { initialTab?: CollectionDialogTabType; parentCollectionId?: string; showOrgSelector?: boolean; - collectionIds?: string[]; + /** + * Flag to limit the nested collections to only those the user has explicit CanManage access too. + */ + limitNestedCollections?: boolean; readonly?: boolean; } @@ -85,7 +86,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { protected tabIndex: CollectionDialogTabType; protected loading = true; protected organization?: Organization; - protected collection?: CollectionView; + protected collection?: CollectionAdminView; protected nestOptions: CollectionView[] = []; protected accessItems: AccessItemView[] = []; protected deletedParentName: string | undefined; @@ -107,7 +108,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private organizationService: OrganizationService, private groupService: GroupService, private collectionAdminService: CollectionAdminService, - private collectionService: CollectionService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private organizationUserService: OrganizationUserService, @@ -124,7 +124,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { this.showOrgSelector = true; this.formGroup.controls.selectedOrg.valueChanges .pipe(takeUntil(this.destroy$)) - .subscribe((id) => this.loadOrg(id, this.params.collectionIds)); + .subscribe((id) => this.loadOrg(id)); this.organizations$ = this.organizationService.organizations$.pipe( first(), map((orgs) => @@ -138,11 +138,11 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { } else { // Opened from the org vault this.formGroup.patchValue({ selectedOrg: this.params.organizationId }); - await this.loadOrg(this.params.organizationId, this.params.collectionIds); + await this.loadOrg(this.params.organizationId); } } - async loadOrg(orgId: string, collectionIds: string[]) { + async loadOrg(orgId: string) { const organization$ = this.organizationService .get$(orgId) .pipe(shareReplay({ refCount: true, bufferSize: 1 })); @@ -158,28 +158,14 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { combineLatest({ organization: organization$, collections: this.collectionAdminService.getAll(orgId), - collectionDetails: this.params.collectionId - ? from(this.collectionAdminService.get(orgId, this.params.collectionId)) - : of(null), groups: groups$, // Collection(s) needed to map readonlypermission for (potential) access selector disabled state users: this.organizationUserService.getAllUsers(orgId, { includeCollections: true }), - collection: this.params.collectionId - ? this.collectionService.get(this.params.collectionId) - : of(null), flexibleCollectionsV1: this.flexibleCollectionsV1Enabled$, }) .pipe(takeUntil(this.formGroup.controls.selectedOrg.valueChanges), takeUntil(this.destroy$)) .subscribe( - ({ - organization, - collections, - collectionDetails, - groups, - users, - collection, - flexibleCollectionsV1, - }) => { + ({ organization, collections: allCollections, groups, users, flexibleCollectionsV1 }) => { this.organization = organization; this.accessItems = [].concat( groups.map((group) => mapGroupToAccessItemView(group, this.collectionId)), @@ -189,37 +175,48 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { // Force change detection to update the access selector's items this.changeDetectorRef.detectChanges(); - if (collectionIds) { - collections = collections.filter((c) => collectionIds.includes(c.id)); - } + this.nestOptions = this.params.limitNestedCollections + ? allCollections.filter((c) => c.manage) + : allCollections; if (this.params.collectionId) { - this.collection = collections.find((c) => c.id === this.collectionId); - this.nestOptions = collections.filter((c) => c.id !== this.collectionId); + this.collection = allCollections.find((c) => c.id === this.collectionId); + // Ensure we don't allow nesting the current collection within itself + this.nestOptions = this.nestOptions.filter((c) => c.id !== this.collectionId); if (!this.collection) { throw new Error("Could not find collection to edit."); } - const { name, parent } = parseName(this.collection); - if (parent !== undefined && !this.nestOptions.find((c) => c.name === parent)) { - this.deletedParentName = parent; + // Parse the name to find its parent name + const { name, parent: parentName } = parseName(this.collection); + + // Determine if the user can see/select the parent collection + if (parentName !== undefined) { + if ( + this.organization.canViewAllCollections && + !allCollections.find((c) => c.name === parentName) + ) { + // The user can view all collections, but the parent was not found -> assume it has been deleted + this.deletedParentName = parentName; + } else if (!this.nestOptions.find((c) => c.name === parentName)) { + // We cannot find the current parent collection in our list of options, so add a placeholder + this.nestOptions.unshift({ name: parentName } as CollectionView); + } } - const accessSelections = mapToAccessSelections(collectionDetails); + const accessSelections = mapToAccessSelections(this.collection); this.formGroup.patchValue({ name, externalId: this.collection.externalId, - parent, + parent: parentName, access: accessSelections, }); - this.collection.manage = collection?.manage ?? false; // Get manage flag from sync data collection this.showDeleteButton = !this.dialogReadonly && this.collection.canDelete(organization, flexibleCollectionsV1); } else { - this.nestOptions = collections; - const parent = collections.find((c) => c.id === this.params.parentCollectionId); + const parent = this.nestOptions.find((c) => c.id === this.params.parentCollectionId); const currentOrgUserId = users.data.find( (u) => u.userId === this.organization?.userId, )?.id; diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 49565bdcee..f203fee063 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -650,7 +650,7 @@ export class VaultComponent implements OnInit, OnDestroy { .sort(Utils.getSortFunction(this.i18nService, "name"))[0].id, parentCollectionId: this.filter.collectionId, showOrgSelector: true, - collectionIds: this.allCollections.map((c) => c.id), + limitNestedCollections: true, }, }); const result = await lastValueFrom(dialog.closed); @@ -666,7 +666,12 @@ export class VaultComponent implements OnInit, OnDestroy { async editCollection(c: CollectionView, tab: CollectionDialogTabType): Promise { const dialog = openCollectionDialog(this.dialogService, { - data: { collectionId: c?.id, organizationId: c.organizationId, initialTab: tab }, + data: { + collectionId: c?.id, + organizationId: c.organizationId, + initialTab: tab, + limitNestedCollections: true, + }, }); const result = await lastValueFrom(dialog.closed); diff --git a/apps/web/src/app/vault/org-vault/collection-access-restricted.component.ts b/apps/web/src/app/vault/org-vault/collection-access-restricted.component.ts index 7a51f01577..eb81fa196d 100644 --- a/apps/web/src/app/vault/org-vault/collection-access-restricted.component.ts +++ b/apps/web/src/app/vault/org-vault/collection-access-restricted.component.ts @@ -3,6 +3,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; import { ButtonModule, NoItemsModule, svgIcon } from "@bitwarden/components"; import { SharedModule } from "../../shared"; +import { CollectionDialogTabType } from "../components/collection-dialog"; const icon = svgIcon` @@ -16,24 +17,36 @@ const icon = svgIcon` {{ "collectionAccessRestricted" | i18n }} + `, }) export class CollectionAccessRestrictedComponent { protected icon = icon; + protected collectionDialogTabType = CollectionDialogTabType; @Input() canEditCollection = false; + @Input() canViewCollectionInfo = false; - @Output() viewCollectionClicked = new EventEmitter(); - - get buttonText() { - return this.canEditCollection ? "editCollection" : "viewCollection"; - } + @Output() viewCollectionClicked = new EventEmitter<{ + readonly: boolean; + tab: CollectionDialogTabType; + }>(); } diff --git a/apps/web/src/app/vault/org-vault/vault.component.html b/apps/web/src/app/vault/org-vault/vault.component.html index a437ac2092..a6d1cd3074 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.html +++ b/apps/web/src/app/vault/org-vault/vault.component.html @@ -116,14 +116,18 @@ diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 64823c9754..0b16c1c78f 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -1175,6 +1175,9 @@ export class VaultComponent implements OnInit, OnDestroy { data: { organizationId: this.organization?.id, parentCollectionId: this.selectedCollection?.node.id, + limitNestedCollections: !this.organization.canEditAnyCollection( + this.flexibleCollectionsV1Enabled, + ), }, }); @@ -1198,6 +1201,9 @@ export class VaultComponent implements OnInit, OnDestroy { organizationId: this.organization?.id, initialTab: tab, readonly: readonly, + limitNestedCollections: !this.organization.canEditAnyCollection( + this.flexibleCollectionsV1Enabled, + ), }, }); diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 757a6c9ae3..fcc9466c45 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7749,9 +7749,6 @@ "success": { "message": "Success" }, - "viewCollection": { - "message": "View collection" - }, "restrictedGroupAccess": { "message": "You cannot add yourself to groups." }, @@ -8198,5 +8195,26 @@ }, "viewAccess": { "message": "View access" + }, + "updateName": { + "message": "Update name" + }, + "updatedOrganizationName": { + "message": "Updated organization name" + }, + "providerPlan": { + "message": "Managed Service Provider" + }, + "orgSeats": { + "message": "Organization Seats" + }, + "providerDiscount": { + "message": "$AMOUNT$% Discount", + "placeholders": { + "amount": { + "content": "$1", + "example": "2" + } + } } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 15d7fdf453..6399952149 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -12,8 +12,9 @@ import { OssModule } from "@bitwarden/web-vault/app/oss.module"; import { ProviderSubscriptionComponent } from "../../billing/providers"; import { CreateClientOrganizationComponent, - ManageClientOrganizationSubscriptionComponent, ManageClientOrganizationsComponent, + ManageClientOrganizationNameComponent, + ManageClientOrganizationSubscriptionComponent, } from "../../billing/providers/clients"; import { AddOrganizationComponent } from "./clients/add-organization.component"; @@ -62,6 +63,7 @@ import { SetupComponent } from "./setup/setup.component"; UserAddEditComponent, CreateClientOrganizationComponent, ManageClientOrganizationsComponent, + ManageClientOrganizationNameComponent, ManageClientOrganizationSubscriptionComponent, ProviderSubscriptionComponent, ], diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts index fd9ef8296c..1968302766 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts @@ -1,3 +1,4 @@ export * from "./create-client-organization.component"; export * from "./manage-client-organizations.component"; +export * from "./manage-client-organization-name.component"; export * from "./manage-client-organization-subscription.component"; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.html new file mode 100644 index 0000000000..6d7d4b2f18 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.html @@ -0,0 +1,24 @@ +
+ + + {{ "updateName" | i18n }} + {{ dialogParams.organization.name }} + +
+ + + {{ "organizationName" | i18n }} + + + +
+ + + + +
+
diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.ts new file mode 100644 index 0000000000..81e01a66cb --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.ts @@ -0,0 +1,77 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; + +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; +import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DialogService, ToastService } from "@bitwarden/components"; + +type ManageClientOrganizationNameParams = { + providerId: string; + organization: { + id: string; + name: string; + seats: number; + }; +}; + +export enum ManageClientOrganizationNameResultType { + Closed = "closed", + Submitted = "submitted", +} + +export const openManageClientOrganizationNameDialog = ( + dialogService: DialogService, + dialogConfig: DialogConfig, +) => + dialogService.open( + ManageClientOrganizationNameComponent, + dialogConfig, + ); + +@Component({ + selector: "app-manage-client-organization-name", + templateUrl: "manage-client-organization-name.component.html", +}) +export class ManageClientOrganizationNameComponent { + protected ResultType = ManageClientOrganizationNameResultType; + protected formGroup = this.formBuilder.group({ + name: [this.dialogParams.organization.name, Validators.required], + }); + + constructor( + @Inject(DIALOG_DATA) protected dialogParams: ManageClientOrganizationNameParams, + private billingApiService: BillingApiServiceAbstraction, + private dialogRef: DialogRef, + private formBuilder: FormBuilder, + private i18nService: I18nService, + private toastService: ToastService, + ) {} + + submit = async () => { + this.formGroup.markAllAsTouched(); + + if (this.formGroup.invalid) { + return; + } + + const request = new UpdateClientOrganizationRequest(); + request.assignedSeats = this.dialogParams.organization.seats; + request.name = this.formGroup.value.name; + + await this.billingApiService.updateClientOrganization( + this.dialogParams.providerId, + this.dialogParams.organization.id, + request, + ); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("updatedOrganizationName"), + }); + + this.dialogRef.close(this.ResultType.Submitted); + }; +} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts index 2182ac43ab..3b05476777 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts @@ -71,6 +71,7 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit { const request = new UpdateClientOrganizationRequest(); request.assignedSeats = assignedSeats; + request.name = this.clientName; await this.billingApiService.updateClientOrganization( this.providerId, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html index ec5df609c4..d2f8ab7a85 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html @@ -78,8 +78,12 @@ appA11yTitle="{{ 'options' | i18n }}" > +