diff --git a/apps/browser/src/autofill/content/components/.lit-storybook/main.ts b/apps/browser/src/autofill/content/components/.lit-storybook/main.ts index a316d8f5baa..53c06264672 100644 --- a/apps/browser/src/autofill/content/components/.lit-storybook/main.ts +++ b/apps/browser/src/autofill/content/components/.lit-storybook/main.ts @@ -1,9 +1,16 @@ -import path, { dirname, join } from "path"; +import { createRequire } from "module"; +import { dirname, join, resolve } from "path"; +import { fileURLToPath } from "url"; import type { StorybookConfig } from "@storybook/web-components-webpack5"; import remarkGfm from "remark-gfm"; import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; +const currentFile = fileURLToPath(import.meta.url); +const currentDirectory = dirname(currentFile); + +const require = createRequire(import.meta.url); + const getAbsolutePath = (value: string): string => dirname(require.resolve(join(value, "package.json"))); @@ -43,7 +50,7 @@ const config: StorybookConfig = { if (config.resolve) { config.resolve.plugins = [ new TsconfigPathsPlugin({ - configFile: path.resolve(__dirname, "../../../../../tsconfig.json"), + configFile: resolve(currentDirectory, "../../../../../tsconfig.json"), }), ] as any; } diff --git a/apps/browser/src/autofill/content/components/buttons/action-button.ts b/apps/browser/src/autofill/content/components/buttons/action-button.ts index 339b628875c..b43bed7f96b 100644 --- a/apps/browser/src/autofill/content/components/buttons/action-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/action-button.ts @@ -8,6 +8,7 @@ import { Spinner } from "../icons"; export type ActionButtonProps = { buttonText: string | TemplateResult; + dataTestId?: string; disabled?: boolean; isLoading?: boolean; theme: Theme; @@ -17,6 +18,7 @@ export type ActionButtonProps = { export function ActionButton({ buttonText, + dataTestId, disabled = false, isLoading = false, theme, @@ -32,6 +34,7 @@ export function ActionButton({ return html` + @if (isBreadcrumbingEnabled$ | async) { + + } - + @if (loading) { {{ "loading" | i18n }} - - - - - - - {{ - "on" | i18n - }} - {{ p.description | i18n }} - - - - + } + @if (!loading) { + + + @for (p of policies; track p.name) { + @if (p.display(organization, configService) | async) { + + + + @if (policiesEnabledMap.get(p.type)) { + {{ "on" | i18n }} + } + {{ p.description | i18n }} + + + } + } + + + } diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts index 8b6894871bd..3dfc4cc0c20 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -15,7 +15,6 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { DialogService } from "@bitwarden/components"; import { @@ -25,7 +24,7 @@ import { import { All } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model"; import { PolicyListService } from "../../core/policy-list.service"; -import { BasePolicy, RestrictedItemTypesPolicy } from "../policies"; +import { BasePolicy } from "../policies"; import { CollectionDialogTabType } from "../shared/components/collection-dialog"; import { PolicyEditComponent, PolicyEditDialogResult } from "./policy-edit.component"; @@ -53,7 +52,7 @@ export class PoliciesComponent implements OnInit { private policyListService: PolicyListService, private organizationBillingService: OrganizationBillingServiceAbstraction, private dialogService: DialogService, - private configService: ConfigService, + protected configService: ConfigService, ) {} async ngOnInit() { @@ -71,35 +70,31 @@ export class PoliciesComponent implements OnInit { await this.load(); // Handle policies component launch from Event message - /* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */ - this.route.queryParams.pipe(first()).subscribe(async (qParams) => { - if (qParams.policyId != null) { - const policyIdFromEvents: string = qParams.policyId; - for (const orgPolicy of this.orgPolicies) { - if (orgPolicy.id === policyIdFromEvents) { - for (let i = 0; i < this.policies.length; i++) { - if (this.policies[i].type === orgPolicy.type) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.edit(this.policies[i]); - break; + this.route.queryParams + .pipe(first()) + /* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */ + .subscribe(async (qParams) => { + if (qParams.policyId != null) { + const policyIdFromEvents: string = qParams.policyId; + for (const orgPolicy of this.orgPolicies) { + if (orgPolicy.id === policyIdFromEvents) { + for (let i = 0; i < this.policies.length; i++) { + if (this.policies[i].type === orgPolicy.type) { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.edit(this.policies[i]); + break; + } } + break; } - break; } } - } - }); + }); }); } async load() { - if ( - (await this.configService.getFeatureFlag(FeatureFlag.RemoveCardItemTypePolicy)) && - this.policyListService.getPolicies().every((p) => !(p instanceof RestrictedItemTypesPolicy)) - ) { - this.policyListService.addPolicies([new RestrictedItemTypesPolicy()]); - } const response = await this.policyApiService.getPolicies(this.organizationId); this.orgPolicies = response.data != null && response.data.length > 0 ? response.data : []; this.orgPolicies.forEach((op) => { diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html index 7f33f08f888..90cfb52e5ad 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html @@ -22,7 +22,9 @@ {{ "loading" | i18n }}
-

{{ policy.description | i18n }}

+ @if (policy.showDescription) { +

{{ policy.description | i18n }}

+ }
diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts index d3d03d2aaae..2984db67d39 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts @@ -128,13 +128,20 @@ export class PolicyEditComponent implements AfterViewInit { } submit = async () => { + if ((await this.policyComponent.confirm()) == false) { + this.dialogRef.close(); + return; + } + let request: PolicyRequest; + try { request = await this.policyComponent.buildRequest(); } catch (e) { this.toastService.showToast({ variant: "error", title: null, message: e.message }); return; } + await this.policyApiService.putPolicy(this.data.organizationId, this.data.policy.type, request); this.toastService.showToast({ variant: "success", diff --git a/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts b/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts index 21de143dea6..3a0d196c593 100644 --- a/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts @@ -1,7 +1,9 @@ import { Component } from "@angular/core"; +import { of } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; @@ -11,8 +13,8 @@ export class RequireSsoPolicy extends BasePolicy { type = PolicyType.RequireSso; component = RequireSsoPolicyComponent; - display(organization: Organization) { - return organization.useSso; + display(organization: Organization, configService: ConfigService) { + return of(organization.useSso); } } diff --git a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts index 62fc42f6a06..93a42285fbc 100644 --- a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, of } from "rxjs"; import { getOrganizationById, @@ -10,6 +10,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; @@ -19,8 +20,8 @@ export class ResetPasswordPolicy extends BasePolicy { type = PolicyType.ResetPassword; component = ResetPasswordPolicyComponent; - display(organization: Organization) { - return organization.useResetPassword; + display(organization: Organization, configService: ConfigService) { + return of(organization.useResetPassword); } } @@ -52,6 +53,10 @@ export class ResetPasswordPolicyComponent extends BasePolicyComponent implements throw new Error("No user found."); } + if (!this.policyResponse) { + throw new Error("Policies not found"); + } + const organization = await firstValueFrom( this.organizationService .organizations$(userId) diff --git a/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.ts b/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.ts index 1bee5583718..6cad0fc0170 100644 --- a/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.ts @@ -1,6 +1,10 @@ import { Component } from "@angular/core"; +import { Observable } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; @@ -9,6 +13,10 @@ export class RestrictedItemTypesPolicy extends BasePolicy { description = "restrictedItemTypePolicyDesc"; type = PolicyType.RestrictedItemTypes; component = RestrictedItemTypesPolicyComponent; + + display(organization: Organization, configService: ConfigService): Observable { + return configService.getFeatureFlag$(FeatureFlag.RemoveCardItemTypePolicy); + } } @Component({ diff --git a/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts b/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts index ad32b4218bc..613253ef8d9 100644 --- a/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts @@ -20,6 +20,9 @@ export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnI async ngOnInit() { super.ngOnInit(); + if (!this.policyResponse) { + throw new Error("Policies not found"); + } if (!this.policyResponse.canToggleState) { this.enabled.disable(); } diff --git a/apps/web/src/app/admin-console/organizations/policies/vnext-organization-data-ownership.component.html b/apps/web/src/app/admin-console/organizations/policies/vnext-organization-data-ownership.component.html new file mode 100644 index 00000000000..0abc40da683 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/vnext-organization-data-ownership.component.html @@ -0,0 +1,57 @@ +

+ {{ "organizationDataOwnershipContent" | i18n }} + + {{ "organizationDataOwnershipContentAnchor" | i18n }}. + +

+ + + + {{ "turnOn" | i18n }} + + + + + {{ "organizationDataOwnershipWarningTitle" | i18n }} + +
+ {{ "organizationDataOwnershipWarningContentTop" | i18n }} +
+
    +
  • + {{ "organizationDataOwnershipWarning1" | i18n }} +
  • +
  • + {{ "organizationDataOwnershipWarning2" | i18n }} +
  • +
  • + {{ "organizationDataOwnershipWarning3" | i18n }} +
  • +
+
+ {{ "organizationDataOwnershipWarningContentBottom" | i18n }} + + {{ "organizationDataOwnershipContentAnchor" | i18n }}. + +
+
+ + + + + + +
+
diff --git a/apps/web/src/app/admin-console/organizations/policies/vnext-organization-data-ownership.component.ts b/apps/web/src/app/admin-console/organizations/policies/vnext-organization-data-ownership.component.ts new file mode 100644 index 00000000000..11b1548d9f9 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/vnext-organization-data-ownership.component.ts @@ -0,0 +1,50 @@ +import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; +import { lastValueFrom, Observable } from "rxjs"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { DialogService } from "@bitwarden/components"; + +import { SharedModule } from "../../../shared"; + +import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; + +export class vNextOrganizationDataOwnershipPolicy extends BasePolicy { + name = "organizationDataOwnership"; + description = "organizationDataOwnershipDesc"; + type = PolicyType.OrganizationDataOwnership; + component = vNextOrganizationDataOwnershipPolicyComponent; + showDescription = false; + + override display(organization: Organization, configService: ConfigService): Observable { + return configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation); + } +} + +@Component({ + selector: "vnext-policy-organization-data-ownership", + templateUrl: "vnext-organization-data-ownership.component.html", + standalone: true, + imports: [SharedModule], +}) +export class vNextOrganizationDataOwnershipPolicyComponent + extends BasePolicyComponent + implements OnInit +{ + constructor(private dialogService: DialogService) { + super(); + } + + @ViewChild("dialog", { static: true }) warningContent!: TemplateRef; + + override async confirm(): Promise { + if (this.policyResponse?.enabled && !this.enabled.value) { + const dialogRef = this.dialogService.open(this.warningContent); + const result = await lastValueFrom(dialogRef.closed); + return Boolean(result); + } + return true; + } +} diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts index fabfb65fc6b..a0964a90fca 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts @@ -39,6 +39,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { getById } from "@bitwarden/common/platform/misc"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { DIALOG_DATA, DialogConfig, @@ -87,8 +88,8 @@ enum ButtonType { } export interface CollectionDialogParams { - collectionId?: string; - organizationId: string; + collectionId?: CollectionId; + organizationId: OrganizationId; initialTab?: CollectionDialogTabType; parentCollectionId?: string; showOrgSelector?: boolean; @@ -136,7 +137,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { externalId: { value: "", disabled: true }, parent: undefined as string | undefined, access: [[] as AccessItemValue[]], - selectedOrg: "", + selectedOrg: "" as OrganizationId, }); protected PermissionMode = PermissionMode; protected showDeleteButton = false; diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index ceb2c788e75..694d0c6eb9a 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -35,12 +35,14 @@ import { MasterPasswordPolicy, PasswordGeneratorPolicy, OrganizationDataOwnershipPolicy, + vNextOrganizationDataOwnershipPolicy, RequireSsoPolicy, ResetPasswordPolicy, SendOptionsPolicy, SingleOrgPolicy, TwoFactorAuthenticationPolicy, RemoveUnlockWithPinPolicy, + RestrictedItemTypesPolicy, } from "./admin-console/organizations/policies"; const BroadcasterSubscriptionId = "AppComponent"; @@ -244,8 +246,10 @@ export class AppComponent implements OnDestroy, OnInit { new SingleOrgPolicy(), new RequireSsoPolicy(), new OrganizationDataOwnershipPolicy(), + new vNextOrganizationDataOwnershipPolicy(), new DisableSendPolicy(), new SendOptionsPolicy(), + new RestrictedItemTypesPolicy(), ]); } diff --git a/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts b/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts index 5edd8bc046e..7292e13a6a5 100644 --- a/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts +++ b/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts @@ -1,8 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Guid } from "@bitwarden/common/types/guid"; +import { OrganizationId } from "@bitwarden/common/types/guid"; export class RequestSMAccessRequest { - OrganizationId: Guid; + OrganizationId: OrganizationId; EmailContent: string; } diff --git a/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts b/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts index 443b3e03e5f..0e32321a0b3 100644 --- a/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts +++ b/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts @@ -10,7 +10,6 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Guid } from "@bitwarden/common/types/guid"; import { NoItemsModule, SearchModule, ToastService } from "@bitwarden/components"; import { HeaderModule } from "../../layouts/header/header.module"; @@ -63,7 +62,7 @@ export class RequestSMAccessComponent implements OnInit { const formValue = this.requestAccessForm.value; const request = new RequestSMAccessRequest(); - request.OrganizationId = formValue.selectedOrganization.id as Guid; + request.OrganizationId = formValue.selectedOrganization.id; request.EmailContent = formValue.requestAccessEmailContents; await this.smLandingApiService.requestSMAccessFromAdmins(request); diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts index 96d274727dd..a8dd0056806 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts @@ -166,6 +166,10 @@ export class VaultItemsComponent { ); } + clearSelection() { + this.selection.clear(); + } + get showExtraColumn() { return this.showCollections || this.showGroups || this.showOwner; } diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts index 78c4d21dede..c114cb6d7c2 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts @@ -29,6 +29,7 @@ import { } from "@bitwarden/common/platform/abstractions/environment.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -262,7 +263,7 @@ export const OrganizationTrash: Story = { }; const unassignedCollection = new CollectionAdminView(); -unassignedCollection.id = Unassigned; +unassignedCollection.id = Unassigned as CollectionId; unassignedCollection.name = "Unassigned"; export const OrganizationTopLevelCollection: Story = { args: { @@ -327,7 +328,7 @@ function createCollectionView(i: number): CollectionAdminView { const organization = organizations[i % (organizations.length + 1)]; const group = groups[i % (groups.length + 1)]; const view = new CollectionAdminView(); - view.id = `collection-${i}`; + view.id = `collection-${i}` as CollectionId; view.name = `Collection ${i}`; view.organizationId = organization?.id; view.manage = true; @@ -357,7 +358,7 @@ function createGroupView(i: number): GroupView { function createOrganization(i: number): Organization { const organization = new Organization(); - organization.id = `organization-${i}`; + organization.id = `organization-${i}` as OrganizationId; organization.name = `Organization ${i}`; organization.type = OrganizationUserType.Owner; organization.permissions = new PermissionsApi(); diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service.ts index a42b5228272..a5a99428b2d 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service.ts @@ -2,6 +2,8 @@ import { Injectable, OnDestroy } from "@angular/core"; import { ActivatedRoute, NavigationExtras } from "@angular/router"; import { combineLatest, map, Observable, Subject, takeUntil } from "rxjs"; +import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; + import { isRoutedVaultFilterItemType, RoutedVaultFilterModel, @@ -31,10 +33,12 @@ export class RoutedVaultFilterService implements OnDestroy { const type = isRoutedVaultFilterItemType(unsafeType) ? unsafeType : undefined; return { - collectionId: queryParams.get("collectionId") ?? undefined, + collectionId: (queryParams.get("collectionId") as CollectionId) ?? undefined, folderId: queryParams.get("folderId") ?? undefined, organizationId: - params.get("organizationId") ?? queryParams.get("organizationId") ?? undefined, + (params.get("organizationId") as OrganizationId) ?? + (queryParams.get("organizationId") as OrganizationId) ?? + undefined, organizationIdParamType: params.get("organizationId") != undefined ? ("path" as const) : ("query" as const), type, diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts index 266676e418b..11e074db985 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts @@ -28,7 +28,7 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SingleUserState, StateProvider } from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -209,7 +209,7 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { protected getOrganizationFilterMyVault(): TreeNode { const myVault = new Organization() as OrganizationFilter; - myVault.id = "MyVault"; + myVault.id = "MyVault" as OrganizationId; myVault.icon = "bwi-user"; myVault.enabled = true; myVault.hideOptions = true; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter-bridge.model.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter-bridge.model.ts index fe236a089e0..02d536eb6ab 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter-bridge.model.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter-bridge.model.ts @@ -1,4 +1,5 @@ import { Unassigned } from "@bitwarden/admin-console/common"; +import { CollectionId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; @@ -65,7 +66,7 @@ export class RoutedVaultFilterBridge implements VaultFilter { let type: RoutedVaultFilterItemType | undefined; if (value?.node.id === "AllItems" && this.routedFilter.organizationIdParamType === "path") { - type = "all"; + type = All; } else if ( value?.node.id === "AllItems" && this.routedFilter.organizationIdParamType === "query" @@ -98,7 +99,7 @@ export class RoutedVaultFilterBridge implements VaultFilter { return this.legacyFilter.selectedCollectionNode; } set selectedCollectionNode(value: TreeNode) { - let collectionId: string | undefined; + let collectionId: CollectionId | All | Unassigned | undefined; if (value != null && value.node.id === null) { collectionId = Unassigned; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model.ts index 866ba1d9848..280ffd15732 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model.ts @@ -1,4 +1,11 @@ +import { Unassigned } from "@bitwarden/admin-console/common"; +import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; + +/** + * A constant used to represent viewing "all" of a particular filter. + */ export const All = "all"; +export type All = typeof All; // TODO: Remove `All` when moving to vertical navigation. const itemTypes = [ @@ -19,9 +26,9 @@ export function isRoutedVaultFilterItemType(value: unknown): value is RoutedVaul } export interface RoutedVaultFilterModel { - collectionId?: string; + collectionId?: CollectionId | All | Unassigned; folderId?: string; - organizationId?: string; + organizationId?: OrganizationId | Unassigned; type?: RoutedVaultFilterItemType; organizationIdParamType?: "path" | "query"; diff --git a/apps/web/src/app/vault/individual-vault/vault.component.html b/apps/web/src/app/vault/individual-vault/vault.component.html index c20209a0192..35b1a1876a1 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.html +++ b/apps/web/src/app/vault/individual-vault/vault.component.html @@ -37,6 +37,7 @@ {{ trashCleanupWarning }} implements OnInit, OnDestroy { @ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent; + @ViewChild("vaultItems", { static: false }) vaultItemsComponent: VaultItemsComponent; trashCleanupWarning: string = null; kdfIterations: number; @@ -1281,6 +1283,7 @@ export class VaultComponent implements OnInit, OnDestr private refresh() { this.refresh$.next(); + this.vaultItemsComponent?.clearSelection(); } private async go(queryParams: any = null) { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 62f73fd4935..587dcd84e0c 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5429,6 +5429,37 @@ "organizationDataOwnership": { "message": "Enforce organization data ownership" }, + "organizationDataOwnershipDesc": { + "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", + "description": "This is the policy description shown in the policy list." + }, + "organizationDataOwnershipContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + }, + "organizationDataOwnershipContentAnchor":{ + "message": "credential lifecycle", + "description": "This will be used as a hyperlink" + }, + "organizationDataOwnershipWarningTitle":{ + "message": "Are you sure you want to proceed?" + }, + "organizationDataOwnershipWarning1":{ + "message": "will remain accessible to members" + }, + "organizationDataOwnershipWarning2":{ + "message": "will not be automatically selected when creating new items" + }, + "organizationDataOwnershipWarning3":{ + "message": "cannot be managed from the Admin Console until the user is offboarded" + }, + "organizationDataOwnershipWarningContentTop":{ + "message": "By turning this policy off, the default collection: " + }, + "organizationDataOwnershipWarningContentBottom":{ + "message": "Learn more about the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about the credential lifecycle.'" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -10965,5 +10996,11 @@ }, "unlimitedSecretsAndProjects": { "message": "Unlimited secrets and projects" + }, + "providersubscriptionCanceled": { + "message": "Subscription canceled" + }, + "providersubCanceledmessage": { + "message" : "To resubscribe, contact Bitwarden Customer Support." } } \ No newline at end of file diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts index 61e2133d059..821509b43e2 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts @@ -1,7 +1,9 @@ import { Component } from "@angular/core"; +import { of } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { BasePolicy, BasePolicyComponent, @@ -13,8 +15,8 @@ export class ActivateAutofillPolicy extends BasePolicy { type = PolicyType.ActivateAutofill; component = ActivateAutofillPolicyComponent; - display(organization: Organization) { - return organization.useActivateAutofillPolicy; + display(organization: Organization, configService: ConfigService) { + return of(organization.useActivateAutofillPolicy); } } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts index 974dc9c460f..f9ff006de24 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts @@ -158,7 +158,7 @@ export class ProviderSubscriptionStatusComponent { } case "incomplete_expired": case "canceled": { - const canceledText = this.i18nService.t("canceled"); + const canceledText = this.i18nService.t("providersubscriptionCanceled"); return { status: { label: defaultStatusLabel, @@ -171,7 +171,7 @@ export class ProviderSubscriptionStatusComponent { callout: { severity: "danger", header: canceledText, - body: this.i18nService.t("subscriptionCanceled"), + body: this.i18nService.t("providersubCanceledmessage"), }, }; } diff --git a/libs/admin-console/src/common/collections/models/collection-admin.view.ts b/libs/admin-console/src/common/collections/models/collection-admin.view.ts index dd7a57013ca..dcc88551551 100644 --- a/libs/admin-console/src/common/collections/models/collection-admin.view.ts +++ b/libs/admin-console/src/common/collections/models/collection-admin.view.ts @@ -4,7 +4,10 @@ import { CollectionAccessSelectionView } from "./collection-access-selection.vie import { CollectionAccessDetailsResponse } from "./collection.response"; import { CollectionView } from "./collection.view"; +// TODO: this is used to represent the pseudo "Unassigned" collection as well as +// the user's personal vault (as a pseudo organization). This should be separated out into different values. export const Unassigned = "unassigned"; +export type Unassigned = typeof Unassigned; export class CollectionAdminView extends CollectionView { groups: CollectionAccessSelectionView[] = []; diff --git a/libs/admin-console/src/common/collections/models/collection.spec.ts b/libs/admin-console/src/common/collections/models/collection.spec.ts index 925490d22b9..fb38f1507f9 100644 --- a/libs/admin-console/src/common/collections/models/collection.spec.ts +++ b/libs/admin-console/src/common/collections/models/collection.spec.ts @@ -54,7 +54,7 @@ describe("Collection", () => { it("Decrypt", async () => { const collection = new Collection(); - collection.id = "id"; + collection.id = "id" as CollectionId; collection.organizationId = "orgId" as OrganizationId; collection.name = mockEnc("encName"); collection.externalId = "extId"; diff --git a/libs/admin-console/src/common/collections/models/collection.ts b/libs/admin-console/src/common/collections/models/collection.ts index 7bbd018fa96..d1709d1751d 100644 --- a/libs/admin-console/src/common/collections/models/collection.ts +++ b/libs/admin-console/src/common/collections/models/collection.ts @@ -1,5 +1,6 @@ import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import Domain, { EncryptableKeys } from "@bitwarden/common/platform/models/domain/domain-base"; +import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { OrgKey } from "@bitwarden/common/types/key"; import { CollectionData } from "./collection.data"; @@ -13,8 +14,8 @@ export const CollectionTypes = { export type CollectionType = (typeof CollectionTypes)[keyof typeof CollectionTypes]; export class Collection extends Domain { - id: string | undefined; - organizationId: string | undefined; + id: CollectionId | undefined; + organizationId: OrganizationId | undefined; name: EncString | undefined; externalId: string | undefined; readOnly: boolean = false; diff --git a/libs/admin-console/src/common/collections/models/collection.view.ts b/libs/admin-console/src/common/collections/models/collection.view.ts index f75ff565100..3a60320856d 100644 --- a/libs/admin-console/src/common/collections/models/collection.view.ts +++ b/libs/admin-console/src/common/collections/models/collection.view.ts @@ -2,6 +2,7 @@ import { Jsonify } from "type-fest"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { View } from "@bitwarden/common/models/view/view"; +import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node"; import { Collection, CollectionType, CollectionTypes } from "./collection"; @@ -10,8 +11,8 @@ import { CollectionAccessDetailsResponse } from "./collection.response"; export const NestingDelimiter = "/"; export class CollectionView implements View, ITreeNodeObject { - id: string | undefined; - organizationId: string | undefined; + id: CollectionId | undefined; + organizationId: OrganizationId | undefined; name: string = ""; externalId: string | undefined; // readOnly applies to the items within a collection diff --git a/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts index 57df2d03398..d0dfd44e41d 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts @@ -5,7 +5,7 @@ import { combineLatest, Observable, of, switchMap } from "rxjs"; // eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { UserId } from "@bitwarden/common/types/guid"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DefaultSingleNudgeService } from "../default-single-nudge.service"; @@ -42,7 +42,7 @@ export class EmptyVaultNudgeService extends DefaultSingleNudgeService { const orgIds = new Set(orgs.map((org) => org.id)); const canCreateCollections = orgs.some((org) => org.canCreateNewCollections); const hasManageCollections = collections.some( - (c) => c.manage && orgIds.has(c.organizationId!), + (c) => c.manage && orgIds.has(c.organizationId! as OrganizationId), ); // When the user has dismissed the nudge or spotlight, return the nudge status directly diff --git a/libs/angular/src/vault/services/custom-nudges-services/vault-settings-import-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/vault-settings-import-nudge.service.ts index 2529fc40b73..a8f29830f89 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/vault-settings-import-nudge.service.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/vault-settings-import-nudge.service.ts @@ -5,7 +5,7 @@ import { combineLatest, Observable, of, switchMap } from "rxjs"; // eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { UserId } from "@bitwarden/common/types/guid"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DefaultSingleNudgeService } from "../default-single-nudge.service"; @@ -46,7 +46,7 @@ export class VaultSettingsImportNudgeService extends DefaultSingleNudgeService { const orgIds = new Set(orgs.map((org) => org.id)); const canCreateCollections = orgs.some((org) => org.canCreateNewCollections); const hasManageCollections = collections.some( - (c) => c.manage && orgIds.has(c.organizationId!), + (c) => c.manage && orgIds.has(c.organizationId! as OrganizationId), ); // When the user has dismissed the nudge or spotlight, return the nudge status directly diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index f8de5293913..130b32e519e 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -3,12 +3,13 @@ import { Jsonify } from "type-fest"; import { ProductTierType } from "../../../billing/enums"; +import { OrganizationId } from "../../../types/guid"; import { OrganizationUserStatusType, OrganizationUserType, ProviderType } from "../../enums"; import { PermissionsApi } from "../api/permissions.api"; import { OrganizationData } from "../data/organization.data"; export class Organization { - id: string; + id: OrganizationId; name: string; status: OrganizationUserStatusType; @@ -99,7 +100,7 @@ export class Organization { return; } - this.id = obj.id; + this.id = obj.id as OrganizationId; this.name = obj.name; this.status = obj.status; this.type = obj.type; diff --git a/libs/common/src/admin-console/models/domain/policy.ts b/libs/common/src/admin-console/models/domain/policy.ts index b45acb9920d..8408f4832df 100644 --- a/libs/common/src/admin-console/models/domain/policy.ts +++ b/libs/common/src/admin-console/models/domain/policy.ts @@ -2,14 +2,14 @@ // @ts-strict-ignore import { ListResponse } from "../../../models/response/list.response"; import Domain from "../../../platform/models/domain/domain-base"; -import { PolicyId } from "../../../types/guid"; +import { OrganizationId, PolicyId } from "../../../types/guid"; import { PolicyType } from "../../enums"; import { PolicyData } from "../data/policy.data"; import { PolicyResponse } from "../response/policy.response"; export class Policy extends Domain { id: PolicyId; - organizationId: string; + organizationId: OrganizationId; type: PolicyType; data: any; @@ -26,7 +26,7 @@ export class Policy extends Domain { } this.id = obj.id; - this.organizationId = obj.organizationId; + this.organizationId = obj.organizationId as OrganizationId; this.type = obj.type; this.data = obj.data; this.enabled = obj.enabled; diff --git a/libs/common/src/admin-console/models/request/policy.request.ts b/libs/common/src/admin-console/models/request/policy.request.ts index 0f3b1be7d88..7b2e4f76063 100644 --- a/libs/common/src/admin-console/models/request/policy.request.ts +++ b/libs/common/src/admin-console/models/request/policy.request.ts @@ -1,9 +1,7 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { PolicyType } from "../../enums"; -export class PolicyRequest { +export type PolicyRequest = { type: PolicyType; enabled: boolean; data: any; -} +}; diff --git a/libs/common/src/models/export/collection-with-id.export.ts b/libs/common/src/models/export/collection-with-id.export.ts index a93f07b54e5..c973472e0bb 100644 --- a/libs/common/src/models/export/collection-with-id.export.ts +++ b/libs/common/src/models/export/collection-with-id.export.ts @@ -4,10 +4,12 @@ // eslint-disable-next-line no-restricted-imports import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common"; +import { CollectionId } from "../../types/guid"; + import { CollectionExport } from "./collection.export"; export class CollectionWithIdExport extends CollectionExport { - id: string; + id: CollectionId; static toView(req: CollectionWithIdExport, view = new CollectionView()) { view.id = req.id; diff --git a/libs/common/src/models/export/collection.export.ts b/libs/common/src/models/export/collection.export.ts index 88b68c08c58..b141346d03f 100644 --- a/libs/common/src/models/export/collection.export.ts +++ b/libs/common/src/models/export/collection.export.ts @@ -5,13 +5,14 @@ import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common"; import { EncString } from "../../key-management/crypto/models/enc-string"; +import { emptyGuid, OrganizationId } from "../../types/guid"; import { safeGetString } from "./utils"; export class CollectionExport { static template(): CollectionExport { const req = new CollectionExport(); - req.organizationId = "00000000-0000-0000-0000-000000000000"; + req.organizationId = emptyGuid as OrganizationId; req.name = "Collection name"; req.externalId = null; return req; @@ -35,7 +36,7 @@ export class CollectionExport { return domain; } - organizationId: string; + organizationId: OrganizationId; name: string; externalId: string; diff --git a/libs/common/src/types/guid.ts b/libs/common/src/types/guid.ts index bd0980cd36c..5a6aaf2ce51 100644 --- a/libs/common/src/types/guid.ts +++ b/libs/common/src/types/guid.ts @@ -20,3 +20,8 @@ export type OrganizationIntegrationConfigurationId = Opaque< string, "OrganizationIntegrationConfigurationId" >; + +/** + * A string representation of an empty guid. + */ +export const emptyGuid = "00000000-0000-0000-0000-000000000000"; diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts index 3a91330f316..3a35eaab333 100644 --- a/libs/components/src/color-password/color-password.component.ts +++ b/libs/components/src/color-password/color-password.component.ts @@ -2,14 +2,8 @@ import { Component, computed, HostBinding, input } from "@angular/core"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -enum CharacterType { - Letter, - Emoji, - Special, - Number, -} +type CharacterType = "letter" | "emoji" | "special" | "number"; + /** * The color password is used primarily in the Generator pages and in the Login type form. It includes * the logic for displaying letters as `text-main`, numbers as `primary`, and special symbols as @@ -36,10 +30,10 @@ export class ColorPasswordComponent { }); characterStyles: Record = { - [CharacterType.Emoji]: [], - [CharacterType.Letter]: ["tw-text-main"], - [CharacterType.Special]: ["tw-text-danger"], - [CharacterType.Number]: ["tw-text-primary-600"], + emoji: [], + letter: ["tw-text-main"], + special: ["tw-text-danger"], + number: ["tw-text-primary-600"], }; @HostBinding("class") @@ -68,18 +62,18 @@ export class ColorPasswordComponent { private getCharacterType(character: string): CharacterType { if (character.match(Utils.regexpEmojiPresentation)) { - return CharacterType.Emoji; + return "emoji"; } if (character.match(/\d/)) { - return CharacterType.Number; + return "number"; } const specials = ["&", "<", ">", " "]; if (specials.includes(character) || character.match(/[^\w ]/)) { - return CharacterType.Special; + return "special"; } - return CharacterType.Letter; + return "letter"; } } diff --git a/libs/importer/src/importers/base-importer.ts b/libs/importer/src/importers/base-importer.ts index 1a97bc5a325..4c25a01f965 100644 --- a/libs/importer/src/importers/base-importer.ts +++ b/libs/importer/src/importers/base-importer.ts @@ -9,6 +9,7 @@ import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; +import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { FieldType, SecureNoteType, CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; @@ -20,7 +21,7 @@ import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note. import { ImportResult } from "../models/import-result"; export abstract class BaseImporter { - organizationId: string = null; + organizationId: OrganizationId = null; // FIXME: This should be replaced by injecting the log service. protected logService: LogService = new ConsoleLogService(false); @@ -279,7 +280,7 @@ export abstract class BaseImporter { result.collections = result.folders.map((f) => { const collection = new CollectionView(); collection.name = f.name; - collection.id = f.id ?? undefined; // folder id may be null, which is not suitable for collections. + collection.id = (f.id as CollectionId) ?? undefined; // folder id may be null, which is not suitable for collections. return collection; }); result.folderRelationships = []; diff --git a/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts b/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts index 026c501cf5a..b326bc5d351 100644 --- a/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts +++ b/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts @@ -1,4 +1,5 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { testData as TestData, @@ -103,7 +104,7 @@ describe("Keeper CSV Importer", () => { }); it("should create collections, with subcollections and relationships", async () => { - importer.organizationId = Utils.newGuid(); + importer.organizationId = Utils.newGuid() as OrganizationId; const result = await importer.parse(TestData); expect(result != null).toBe(true); @@ -126,7 +127,7 @@ describe("Keeper CSV Importer", () => { }); it("should create collections tree, with child collections and relationships", async () => { - importer.organizationId = Utils.newGuid(); + importer.organizationId = Utils.newGuid() as OrganizationId; const result = await importer.parse(testDataMultiCollection); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts b/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts index 22008f3b4c1..1141897a044 100644 --- a/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts +++ b/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts @@ -1,4 +1,5 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { testData as TestData } from "../spec-data/keeper-json/testdata.json"; @@ -93,7 +94,7 @@ describe("Keeper Json Importer", () => { }); it("should create collections if part of an organization", async () => { - importer.organizationId = Utils.newGuid(); + importer.organizationId = Utils.newGuid() as OrganizationId; const result = await importer.parse(testDataJson); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/netwrix/netwrix-passwordsecure-csv-importer.spec.ts b/libs/importer/src/importers/netwrix/netwrix-passwordsecure-csv-importer.spec.ts index 8736b3df0c8..b5479ce83cc 100644 --- a/libs/importer/src/importers/netwrix/netwrix-passwordsecure-csv-importer.spec.ts +++ b/libs/importer/src/importers/netwrix/netwrix-passwordsecure-csv-importer.spec.ts @@ -1,4 +1,5 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { credentialsData, @@ -79,7 +80,7 @@ describe("Netwrix Password Secure CSV Importer", () => { }); it("should parse an item and create a collection when importing into an organization", async () => { - importer.organizationId = Utils.newGuid(); + importer.organizationId = Utils.newGuid() as OrganizationId; const result = await importer.parse(credentialsData); expect(result).not.toBeNull(); @@ -93,7 +94,7 @@ describe("Netwrix Password Secure CSV Importer", () => { }); it("should parse multiple collections", async () => { - importer.organizationId = Utils.newGuid(); + importer.organizationId = Utils.newGuid() as OrganizationId; const result = await importer.parse(credentialsDataWithFolders); expect(result).not.toBeNull(); diff --git a/libs/importer/src/importers/nordpass-csv-importer.spec.ts b/libs/importer/src/importers/nordpass-csv-importer.spec.ts index cadc7bca28c..e633310e6ee 100644 --- a/libs/importer/src/importers/nordpass-csv-importer.spec.ts +++ b/libs/importer/src/importers/nordpass-csv-importer.spec.ts @@ -1,4 +1,5 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { SecureNoteType, CipherType, FieldType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; @@ -224,7 +225,7 @@ describe("NordPass CSV Importer", () => { }); it("should parse an item and create a collection if organizationId is set", async () => { - importer.organizationId = Utils.newGuid(); + importer.organizationId = Utils.newGuid() as OrganizationId; const result = await importer.parse(secureNoteData); expect(result).not.toBeNull(); diff --git a/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts b/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts index d4976f7a198..1ca12a9ce69 100644 --- a/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts +++ b/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts @@ -1,4 +1,5 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { FieldType, SecureNoteType, CipherType } from "@bitwarden/common/vault/enums"; import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; @@ -691,7 +692,7 @@ describe("1Password 1Pux Importer", () => { it("should create collections if part of an organization", async () => { const importer = new OnePassword1PuxImporter(); - importer.organizationId = Utils.newGuid(); + importer.organizationId = Utils.newGuid() as OrganizationId; const result = await importer.parse(SanitizedExportJson); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/passsordxp/passwordxp-csv-importer.spec.ts b/libs/importer/src/importers/passsordxp/passwordxp-csv-importer.spec.ts index 12cfbbe62bb..67d90ef966a 100644 --- a/libs/importer/src/importers/passsordxp/passwordxp-csv-importer.spec.ts +++ b/libs/importer/src/importers/passsordxp/passwordxp-csv-importer.spec.ts @@ -1,3 +1,4 @@ +import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { ImportResult } from "../../models/import-result"; @@ -146,7 +147,7 @@ describe("PasswordXPCsvImporter", () => { }); it("should convert folders to collections when importing into an organization", async () => { - importer.organizationId = "someOrg"; + importer.organizationId = "someOrg" as OrganizationId; const result: ImportResult = await importer.parse(withFolders); expect(result.success).toBe(true); expect(result.ciphers.length).toBe(5); @@ -172,7 +173,7 @@ describe("PasswordXPCsvImporter", () => { }); it("should convert multi-level folders to collections when importing into an organization", async () => { - importer.organizationId = "someOrg"; + importer.organizationId = "someOrg" as OrganizationId; const result: ImportResult = await importer.parse(withMultipleFolders); expect(result.success).toBe(true); expect(result.ciphers.length).toBe(5); diff --git a/libs/importer/src/importers/password-depot/password-depot-17-xml-importer.spec.ts b/libs/importer/src/importers/password-depot/password-depot-17-xml-importer.spec.ts index ea84603aef4..dc438c0edee 100644 --- a/libs/importer/src/importers/password-depot/password-depot-17-xml-importer.spec.ts +++ b/libs/importer/src/importers/password-depot/password-depot-17-xml-importer.spec.ts @@ -1,6 +1,7 @@ // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { FieldType, SecureNoteType } from "@bitwarden/common/vault/enums"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { CipherType } from "@bitwarden/sdk-internal"; @@ -485,7 +486,7 @@ describe("Password Depot 17 Xml Importer", () => { it("should parse groups nodes into collections when importing into an organization", async () => { const importer = new PasswordDepot17XmlImporter(); - importer.organizationId = "someOrgId"; + importer.organizationId = "someOrgId" as OrganizationId; const collection = new CollectionView(); collection.name = "tempDB"; const actual = [collection]; diff --git a/libs/importer/src/importers/protonpass/protonpass-json-importer.spec.ts b/libs/importer/src/importers/protonpass/protonpass-json-importer.spec.ts index b8550bcb191..a97239fbd10 100644 --- a/libs/importer/src/importers/protonpass/protonpass-json-importer.spec.ts +++ b/libs/importer/src/importers/protonpass/protonpass-json-importer.spec.ts @@ -2,6 +2,7 @@ import { MockProxy } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { FieldType, CipherType } from "@bitwarden/common/vault/enums"; import { testData } from "../spec-data/protonpass-json/protonpass.json"; @@ -90,7 +91,7 @@ describe("Protonpass Json Importer", () => { it("should create collections if part of an organization", async () => { const testDataJson = JSON.stringify(testData); - importer.organizationId = Utils.newGuid(); + importer.organizationId = Utils.newGuid() as OrganizationId; const result = await importer.parse(testDataJson); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/psono/psono-json-importer.spec.ts b/libs/importer/src/importers/psono/psono-json-importer.spec.ts index 3b4fcf67a30..29e5a8fb4ab 100644 --- a/libs/importer/src/importers/psono/psono-json-importer.spec.ts +++ b/libs/importer/src/importers/psono/psono-json-importer.spec.ts @@ -1,3 +1,4 @@ +import { OrganizationId } from "@bitwarden/common/types/guid"; import { FieldType, CipherType } from "@bitwarden/common/vault/enums"; import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; @@ -236,7 +237,7 @@ describe("PSONO JSON Importer", () => { it("should create collections if part of an organization", async () => { const importer = new PsonoJsonImporter(); - importer.organizationId = "someOrg"; + importer.organizationId = "someOrg" as OrganizationId; const result = await importer.parse(FoldersTestDataJson); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/zohovault-csv-importer.spec.ts b/libs/importer/src/importers/zohovault-csv-importer.spec.ts index e49b527cbbf..d3904fb521a 100644 --- a/libs/importer/src/importers/zohovault-csv-importer.spec.ts +++ b/libs/importer/src/importers/zohovault-csv-importer.spec.ts @@ -1,3 +1,4 @@ +import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; @@ -73,7 +74,7 @@ describe("Zoho Vault CSV Importer", () => { it("should create collection and assign ciphers when importing into an organization", async () => { const importer = new ZohoVaultCsvImporter(); - importer.organizationId = "someOrgId"; + importer.organizationId = "someOrgId" as OrganizationId; const result = await importer.parse(samplezohovaultcsvdata); expect(result != null).toBe(true); expect(result.success).toBe(true); diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index 4245b770ce4..ac560ed6f7f 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -9,6 +9,7 @@ import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin. import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { MockSdkService } from "@bitwarden/common/platform/spec/mock-sdk.service"; +import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -67,7 +68,7 @@ describe("ImportService", () => { describe("getImporterInstance", () => { describe("Get bitPasswordProtected importer", () => { let importer: Importer; - const organizationId = Utils.newGuid(); + const organizationId = Utils.newGuid() as OrganizationId; const password = Utils.newGuid(); const promptForPassword_callback = async () => { return password; @@ -98,7 +99,7 @@ describe("ImportService", () => { }); describe("setImportTarget", () => { - const organizationId = Utils.newGuid(); + const organizationId = Utils.newGuid() as OrganizationId; let importResult: ImportResult; @@ -145,19 +146,19 @@ describe("ImportService", () => { }); const mockImportTargetCollection = new CollectionView(); - mockImportTargetCollection.id = "myImportTarget"; + mockImportTargetCollection.id = "myImportTarget" as CollectionId; mockImportTargetCollection.name = "myImportTarget"; mockImportTargetCollection.organizationId = organizationId; const mockCollection1 = new CollectionView(); - mockCollection1.id = "collection1"; + mockCollection1.id = "collection1" as CollectionId; mockCollection1.name = "collection1"; mockCollection1.organizationId = organizationId; const mockCollection2 = new CollectionView(); - mockCollection1.id = "collection2"; - mockCollection1.name = "collection2"; - mockCollection1.organizationId = organizationId; + mockCollection2.id = "collection2" as CollectionId; + mockCollection2.name = "collection2"; + mockCollection2.organizationId = organizationId; it("passing importTarget adds it to collections", async () => { await importService["setImportTarget"]( diff --git a/libs/importer/src/services/import.service.ts b/libs/importer/src/services/import.service.ts index 9acd4514b31..d3880f63bd5 100644 --- a/libs/importer/src/services/import.service.ts +++ b/libs/importer/src/services/import.service.ts @@ -19,6 +19,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType, toCipherTypeName } from "@bitwarden/common/vault/enums"; @@ -130,7 +131,7 @@ export class ImportService implements ImportServiceAbstraction { async import( importer: Importer, fileContents: string, - organizationId: string = null, + organizationId: OrganizationId = null, selectedImportTarget: FolderView | CollectionView = null, canAccessImportExport: boolean, ): Promise { @@ -204,7 +205,7 @@ export class ImportService implements ImportServiceAbstraction { getImporter( format: ImportType | "bitwardenpasswordprotected", promptForPassword_callback: () => Promise, - organizationId: string = null, + organizationId: OrganizationId = null, ): Importer { if (promptForPassword_callback == null) { return null; @@ -393,7 +394,10 @@ export class ImportService implements ImportServiceAbstraction { return await this.importApiService.postImportCiphers(request); } - private async handleOrganizationalImport(importResult: ImportResult, organizationId: string) { + private async handleOrganizationalImport( + importResult: ImportResult, + organizationId: OrganizationId, + ) { const request = new ImportOrganizationCiphersRequest(); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts index 3c513a2f067..1f2aaa8904b 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts @@ -14,6 +14,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { SelectComponent } from "@bitwarden/components"; @@ -33,9 +34,9 @@ const createMockCollection = ( canEdit = true, ): CollectionView => { return { - id, + id: id as CollectionId, name, - organizationId, + organizationId: organizationId as OrganizationId, externalId: "", readOnly, hidePasswords: false,