diff --git a/apps/web/src/app/core/event.service.ts b/apps/web/src/app/core/event.service.ts index 55d5524c2fa..c8c6a54f2a6 100644 --- a/apps/web/src/app/core/event.service.ts +++ b/apps/web/src/app/core/event.service.ts @@ -452,6 +452,12 @@ export class EventService { this.getShortId(ev.organizationId), ); break; + case EventType.Organization_ItemOrganization_Accepted: + msg = humanReadableMsg = this.i18nService.t("userAcceptedTransfer"); + break; + case EventType.Organization_ItemOrganization_Declined: + msg = humanReadableMsg = this.i18nService.t("userDeclinedTransfer"); + break; // Policies case EventType.Policy_Updated: { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 16c274b5b83..ac40f78e43f 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -4214,6 +4214,12 @@ } } }, + "userAcceptedTransfer": { + "message": "Accepted transfer to organization ownership." + }, + "userDeclinedTransfer": { + "message": "Revoked for declining transfer to organization ownership." + }, "invitedUserId": { "message": "Invited user $ID$.", "placeholders": { @@ -12411,7 +12417,7 @@ "placeholders": { "organization": { "content": "$1", - "example": "My Org Name" + "example": "My Org Name" } } }, @@ -12420,7 +12426,7 @@ "placeholders": { "organization": { "content": "$1", - "example": "My Org Name" + "example": "My Org Name" } } }, diff --git a/libs/common/src/enums/event-type.enum.ts b/libs/common/src/enums/event-type.enum.ts index b3b12118ede..4750c881f06 100644 --- a/libs/common/src/enums/event-type.enum.ts +++ b/libs/common/src/enums/event-type.enum.ts @@ -79,6 +79,8 @@ export enum EventType { Organization_CollectionManagement_LimitItemDeletionDisabled = 1615, Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled = 1616, Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsDisabled = 1617, + Organization_ItemOrganization_Accepted = 1618, + Organization_ItemOrganization_Declined = 1619, Policy_Updated = 1700, diff --git a/libs/vault/src/services/default-vault-items-transfer.service.spec.ts b/libs/vault/src/services/default-vault-items-transfer.service.spec.ts index d78cf95ebf2..c0afa950c41 100644 --- a/libs/vault/src/services/default-vault-items-transfer.service.spec.ts +++ b/libs/vault/src/services/default-vault-items-transfer.service.spec.ts @@ -3,11 +3,13 @@ import { firstValueFrom, of, Subject } from "rxjs"; // eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { EventType } from "@bitwarden/common/enums"; 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"; @@ -37,6 +39,7 @@ describe("DefaultVaultItemsTransferService", () => { let mockI18nService: MockProxy; let mockDialogService: MockProxy; let mockToastService: MockProxy; + let mockEventCollectionService: MockProxy; let mockConfigService: MockProxy; const userId = "user-id" as UserId; @@ -71,6 +74,7 @@ describe("DefaultVaultItemsTransferService", () => { mockI18nService = mock(); mockDialogService = mock(); mockToastService = mock(); + mockEventCollectionService = mock(); mockConfigService = mock(); mockI18nService.t.mockImplementation((key) => key); @@ -85,6 +89,7 @@ describe("DefaultVaultItemsTransferService", () => { mockI18nService, mockDialogService, mockToastService, + mockEventCollectionService, mockConfigService, ); }); @@ -774,6 +779,63 @@ describe("DefaultVaultItemsTransferService", () => { expect(mockDialogService.open).toHaveBeenCalledTimes(4); expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled(); }); + + describe("event logs", () => { + it("logs accepted event when user accepts transfer", async () => { + const personalCiphers = [{ id: "cipher-1" } as CipherView]; + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: personalCiphers, + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + }); + + mockDialogService.open.mockReturnValueOnce( + createMockDialogRef(TransferItemsDialogResult.Accepted), + ); + mockCipherService.shareManyWithServer.mockResolvedValue(undefined); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockEventCollectionService.collect).toHaveBeenCalledWith( + EventType.Organization_ItemOrganization_Accepted, + undefined, + undefined, + organizationId, + ); + }); + + it("logs declined event when user rejects transfer", async () => { + const personalCiphers = [{ id: "cipher-1" } as CipherView]; + setupMocksForEnforcementScenario({ + policies: [policy], + organizations: [organization], + ciphers: personalCiphers, + defaultCollection: { + id: collectionId, + organizationId: organizationId, + isDefaultCollection: true, + } as CollectionView, + }); + + mockDialogService.open + .mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Declined)) + .mockReturnValueOnce(createMockDialogRef(LeaveConfirmationDialogResult.Confirmed)); + + await service.enforceOrganizationDataOwnership(userId); + + expect(mockEventCollectionService.collect).toHaveBeenCalledWith( + EventType.Organization_ItemOrganization_Declined, + undefined, + undefined, + organizationId, + ); + }); + }); }); describe("transferInProgress$", () => { diff --git a/libs/vault/src/services/default-vault-items-transfer.service.ts b/libs/vault/src/services/default-vault-items-transfer.service.ts index d7088873071..c184b2c902e 100644 --- a/libs/vault/src/services/default-vault-items-transfer.service.ts +++ b/libs/vault/src/services/default-vault-items-transfer.service.ts @@ -11,10 +11,12 @@ import { // eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { EventType } from "@bitwarden/common/enums"; 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"; @@ -49,6 +51,7 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi private i18nService: I18nService, private dialogService: DialogService, private toastService: ToastService, + private eventCollectionService: EventCollectionService, private configService: ConfigService, ) {} @@ -160,6 +163,13 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi if (!userAcceptedTransfer) { // TODO: Revoke user from organization if they decline migration and show toast PM-29465 + + await this.eventCollectionService.collect( + EventType.Organization_ItemOrganization_Declined, + undefined, + undefined, + migrationInfo.enforcingOrganization.id, + ); return; } @@ -175,6 +185,13 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi variant: "success", message: this.i18nService.t("itemsTransferred"), }); + + await this.eventCollectionService.collect( + EventType.Organization_ItemOrganization_Accepted, + undefined, + undefined, + migrationInfo.enforcingOrganization.id, + ); } catch (error) { this._transferInProgressSubject.next(false); this.logService.error("Error transferring personal items to organization", error);