diff --git a/apps/browser/src/vault/popup/settings/admin-settings.component.spec.ts b/apps/browser/src/vault/popup/settings/admin-settings.component.spec.ts index f7b4e7b473a..2a9ebdcddf6 100644 --- a/apps/browser/src/vault/popup/settings/admin-settings.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/admin-settings.component.spec.ts @@ -7,6 +7,8 @@ import { of } from "rxjs"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { AutoConfirmState, AutomaticUserConfirmationService } from "@bitwarden/auto-confirm"; import { PopOutComponent } from "@bitwarden/browser/platform/popup/components/pop-out.component"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { mockAccountServiceWith } from "@bitwarden/common/spec"; @@ -52,6 +54,8 @@ describe("AdminSettingsComponent", () => { let autoConfirmService: MockProxy; let nudgesService: MockProxy; let mockDialogService: MockProxy; + let eventCollectionService: MockProxy; + let organizationService: MockProxy; const userId = "test-user-id" as UserId; const mockAutoConfirmState: AutoConfirmState = { @@ -64,10 +68,14 @@ describe("AdminSettingsComponent", () => { autoConfirmService = mock(); nudgesService = mock(); mockDialogService = mock(); + eventCollectionService = mock(); + organizationService = mock(); autoConfirmService.configuration$.mockReturnValue(of(mockAutoConfirmState)); autoConfirmService.upsert.mockResolvedValue(undefined); nudgesService.showNudgeSpotlight$.mockReturnValue(of(false)); + eventCollectionService.collect.mockResolvedValue(undefined); + organizationService.organizations$.mockReturnValue(of([])); await TestBed.configureTestingModule({ imports: [AdminSettingsComponent], @@ -77,6 +85,11 @@ describe("AdminSettingsComponent", () => { { provide: AutomaticUserConfirmationService, useValue: autoConfirmService }, { provide: DialogService, useValue: mockDialogService }, { provide: NudgesService, useValue: nudgesService }, + { provide: EventCollectionService, useValue: eventCollectionService }, + { + provide: InternalOrganizationServiceAbstraction, + useValue: organizationService, + }, { provide: I18nService, useValue: { t: (key: string) => key } }, ], }) diff --git a/apps/browser/src/vault/popup/settings/admin-settings.component.ts b/apps/browser/src/vault/popup/settings/admin-settings.component.ts index 52da4318047..99cb5a814c1 100644 --- a/apps/browser/src/vault/popup/settings/admin-settings.component.ts +++ b/apps/browser/src/vault/popup/settings/admin-settings.component.ts @@ -20,8 +20,11 @@ import { import { PopOutComponent } from "@bitwarden/browser/platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "@bitwarden/browser/platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "@bitwarden/browser/platform/popup/layout/popup-page.component"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { EventType } from "@bitwarden/common/enums"; import { BitIconButtonComponent, CardComponent, @@ -69,6 +72,8 @@ export class AdminSettingsComponent implements OnInit { private destroyRef: DestroyRef, private dialogService: DialogService, private nudgesService: NudgesService, + private eventCollectionService: EventCollectionService, + private organizationService: InternalOrganizationServiceAbstraction, ) {} async ngOnInit() { @@ -88,14 +93,26 @@ export class AdminSettingsComponent implements OnInit { } return of(false); }), - withLatestFrom(this.autoConfirmService.configuration$(userId)), - switchMap(([newValue, existingState]) => - this.autoConfirmService.upsert(userId, { + withLatestFrom( + this.autoConfirmService.configuration$(userId), + this.organizationService.organizations$(userId), + ), + switchMap(async ([newValue, existingState, organizations]) => { + await this.autoConfirmService.upsert(userId, { ...existingState, enabled: newValue, showBrowserNotification: false, - }), - ), + }); + + // Auto-confirm users can only belong to one organization + const organization = organizations[0]; + if (organization?.id) { + const eventType = newValue + ? EventType.Organization_AutoConfirmEnabled_Admin + : EventType.Organization_AutoConfirmDisabled_Admin; + await this.eventCollectionService.collect(eventType, undefined, true, organization.id); + } + }), takeUntilDestroyed(this.destroyRef), ) .subscribe(); diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.ts b/apps/web/src/app/admin-console/organizations/manage/events.component.ts index 62f6539cc16..fffe1c06ab8 100644 --- a/apps/web/src/app/admin-console/organizations/manage/events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/events.component.ts @@ -44,6 +44,7 @@ const EVENT_SYSTEM_USER_TO_TRANSLATION: Record = { [EventSystemUser.SCIM]: null, // SCIM acronym not able to be translated so just display SCIM [EventSystemUser.DomainVerification]: "domainVerification", [EventSystemUser.PublicApi]: "publicApi", + [EventSystemUser.BitwardenPortal]: "system", }; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush diff --git a/apps/web/src/app/core/event.service.ts b/apps/web/src/app/core/event.service.ts index 47f4344ec36..006014b9fed 100644 --- a/apps/web/src/app/core/event.service.ts +++ b/apps/web/src/app/core/event.service.ts @@ -355,6 +355,13 @@ export class EventService { this.getShortId(ev.organizationUserId), ); break; + case EventType.OrganizationUser_AutomaticallyConfirmed: + msg = this.i18nService.t("automaticallyConfirmedUserId", this.formatOrgUserId(ev)); + humanReadableMsg = this.i18nService.t( + "automaticallyConfirmedUserId", + this.getShortId(ev.organizationUserId), + ); + break; // Org case EventType.Organization_Updated: msg = humanReadableMsg = this.i18nService.t("editedOrgSettings"); @@ -458,6 +465,18 @@ export class EventService { case EventType.Organization_ItemOrganization_Declined: msg = humanReadableMsg = this.i18nService.t("userDeclinedTransfer"); break; + case EventType.Organization_AutoConfirmEnabled_Admin: + msg = humanReadableMsg = this.i18nService.t("autoConfirmEnabledByAdmin"); + break; + case EventType.Organization_AutoConfirmDisabled_Admin: + msg = humanReadableMsg = this.i18nService.t("autoConfirmDisabledByAdmin"); + break; + case EventType.Organization_AutoConfirmEnabled_Portal: + msg = humanReadableMsg = this.i18nService.t("autoConfirmEnabledByPortal"); + break; + case EventType.Organization_AutoConfirmDisabled_Portal: + msg = humanReadableMsg = this.i18nService.t("autoConfirmDisabledByPortal"); + 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 b257a68052d..c45d7e5d630 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -6142,6 +6151,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, diff --git a/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts b/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts index 0ea3ca9c23a..2b098d3c231 100644 --- a/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts +++ b/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts @@ -439,6 +439,21 @@ describe("DefaultAutomaticUserConfirmationService", () => { expect(organizationUserApiService.postOrganizationUserAutoConfirm).not.toHaveBeenCalled(); }); + it("should return early when auto-confirm is disabled in configuration", async () => { + const disabledConfig = new AutoConfirmState(); + disabledConfig.enabled = false; + await stateProvider.setUserState( + AUTO_CONFIRM_STATE, + { [mockUserId]: disabledConfig }, + mockUserId, + ); + + await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganizationId); + + expect(apiService.getUserPublicKey).not.toHaveBeenCalled(); + expect(organizationUserApiService.postOrganizationUserAutoConfirm).not.toHaveBeenCalled(); + }); + it("should build confirm request with organization and public key", async () => { await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganizationId); diff --git a/libs/common/src/enums/event-system-user.enum.ts b/libs/common/src/enums/event-system-user.enum.ts index f4abbb1e3e9..e5e92ee7ef1 100644 --- a/libs/common/src/enums/event-system-user.enum.ts +++ b/libs/common/src/enums/event-system-user.enum.ts @@ -5,4 +5,5 @@ export enum EventSystemUser { SCIM = 1, DomainVerification = 2, PublicApi = 3, + BitwardenPortal = 5, } diff --git a/libs/common/src/enums/event-type.enum.ts b/libs/common/src/enums/event-type.enum.ts index 4750c881f06..e1bda61b111 100644 --- a/libs/common/src/enums/event-type.enum.ts +++ b/libs/common/src/enums/event-type.enum.ts @@ -60,6 +60,7 @@ export enum EventType { OrganizationUser_RejectedAuthRequest = 1514, OrganizationUser_Deleted = 1515, OrganizationUser_Left = 1516, + OrganizationUser_AutomaticallyConfirmed = 1517, Organization_Updated = 1600, Organization_PurgedVault = 1601, @@ -81,6 +82,10 @@ export enum EventType { Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsDisabled = 1617, Organization_ItemOrganization_Accepted = 1618, Organization_ItemOrganization_Declined = 1619, + Organization_AutoConfirmEnabled_Admin = 1620, + Organization_AutoConfirmDisabled_Admin = 1621, + Organization_AutoConfirmEnabled_Portal = 1622, + Organization_AutoConfirmDisabled_Portal = 1623, Policy_Updated = 1700,