diff --git a/apps/web/src/app/admin-console/common/base.events.component.ts b/apps/web/src/app/admin-console/common/base.events.component.ts index 9d06be92eb8..3d8aaecb32f 100644 --- a/apps/web/src/app/admin-console/common/base.events.component.ts +++ b/apps/web/src/app/admin-console/common/base.events.component.ts @@ -10,6 +10,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; import { EventService } from "../../core"; @@ -24,6 +25,8 @@ export abstract class BaseEventsComponent { continuationToken: string; abstract readonly exportFileName: string; + abstract organizationId: OrganizationId; + abstract userId: UserId; protected eventsForm = new FormGroup({ start: new FormControl(null), @@ -134,6 +137,7 @@ export abstract class BaseEventsComponent { endDate: string, continuationToken: string, ) { + await this.eventService.loadAllOrganizationCiphers(this.organizationId, this.userId); const response = await this.requestEvents(startDate, endDate, continuationToken); const events = await Promise.all( @@ -157,6 +161,8 @@ export abstract class BaseEventsComponent { installationId: r.installationId, systemUser: r.systemUser, serviceAccountId: r.serviceAccountId, + eventName: eventInfo.eventName, + eventLink: eventInfo.eventLink, }); }), ); diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts index 10f68695e88..b1453754e0d 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts @@ -195,6 +195,8 @@ export class EntityEventsComponent implements OnInit, OnDestroy { installationId: r.installationId, systemUser: r.systemUser, serviceAccountId: r.serviceAccountId, + eventName: eventInfo.eventName, + eventLink: eventInfo.eventLink, }); }), ); diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.html b/apps/web/src/app/admin-console/organizations/manage/events.component.html index 02be3476ad5..c57d0185a0e 100644 --- a/apps/web/src/app/admin-console/organizations/manage/events.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/events.component.html @@ -100,7 +100,11 @@ {{ e.userName }} - + + {{ e.eventName }}
+ + 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 3daa6c17d07..179910ec331 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 @@ -27,6 +27,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { DialogService, ToastService } from "@bitwarden/components"; import { @@ -52,9 +53,10 @@ const EVENT_SYSTEM_USER_TO_TRANSLATION: Record = { }) export class EventsComponent extends BaseEventsComponent implements OnInit, OnDestroy { exportFileName = "org-events"; - organizationId: string; + organizationId: OrganizationId; organization: Organization; organizationSubscription: OrganizationSubscriptionResponse; + userId: UserId; placeholderEvents = placeholderEvents as EventView[]; @@ -99,14 +101,14 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe } async ngOnInit() { - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.route.params .pipe( concatMap(async (params) => { this.organizationId = params.organizationId; this.organization = await firstValueFrom( this.organizationService - .organizations$(userId) + .organizations$(this.userId) .pipe(getOrganizationById(this.organizationId)), ); diff --git a/apps/web/src/app/core/event.service.ts b/apps/web/src/app/core/event.service.ts index 14c87181f62..f6200e68313 100644 --- a/apps/web/src/app/core/event.service.ts +++ b/apps/web/src/app/core/event.service.ts @@ -11,15 +11,21 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { DeviceType, EventType } from "@bitwarden/common/enums"; import { EventResponse } from "@bitwarden/common/models/response/event.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @Injectable() export class EventService { private policies: Policy[]; + private organizationCiphers: CipherView[] = []; + private ciphers: CipherView[] = []; constructor( private i18nService: I18nService, policyService: PolicyService, accountService: AccountService, + private cipherService: CipherService, ) { accountService.activeAccount$ .pipe( @@ -48,20 +54,39 @@ export class EventService { return [start.toISOString(), end.toISOString()]; } + async loadAllOrganizationCiphers(organizationId: OrganizationId, userId: UserId) { + // this.organizationCiphers = + // await this.cipherService.getAllFromApiForOrganization(organizationId); + this.ciphers = await this.cipherService.getAllDecrypted(userId); + } + async getEventInfo(ev: EventResponse, options = new EventOptions()): Promise { const appInfo = this.getAppInfo(ev); - const { message, humanReadableMessage } = await this.getEventMessage(ev, options); + // const cv = this.organizationCiphers.find((c) => c.id === ev.cipherId); + const cv = this.ciphers.find((c) => c.id === ev.cipherId); + options.cipher = cv; + options.useCipherName = !!cv; + + const { message, humanReadableMessage, eventName, eventLink } = await this.getEventMessage( + ev, + options, + ); return { message: message, humanReadableMessage: humanReadableMessage, appIcon: appInfo[0], appName: appInfo[1], + eventName: eventName.replace(".", ""), + eventLink, }; } private async getEventMessage(ev: EventResponse, options: EventOptions) { let msg = ""; let humanReadableMsg = ""; + let eventName = ""; + const eventLink = ev.cipherId ? this.formatCipherId(ev, options) : ""; + switch (ev.type) { // User case EventType.User_LoggedIn: @@ -104,10 +129,12 @@ export class EventService { case EventType.Cipher_Created: msg = this.i18nService.t("createdItemId", this.formatCipherId(ev, options)); humanReadableMsg = this.i18nService.t("createdItemId", this.getShortId(ev.cipherId)); + eventName = this.i18nService.t("createdItemId", ""); break; case EventType.Cipher_Updated: msg = this.i18nService.t("editedItemId", this.formatCipherId(ev, options)); humanReadableMsg = this.i18nService.t("editedItemId", this.getShortId(ev.cipherId)); + eventName = this.i18nService.t("editedItemId", ""); break; case EventType.Cipher_Deleted: msg = this.i18nService.t("permanentlyDeletedItemId", this.formatCipherId(ev, options)); @@ -115,6 +142,7 @@ export class EventService { "permanentlyDeletedItemId", this.getShortId(ev.cipherId), ); + eventName = this.i18nService.t("permanentlyDeletedItemId", ""); break; case EventType.Cipher_SoftDeleted: msg = this.i18nService.t("deletedItemId", this.formatCipherId(ev, options)); @@ -123,6 +151,7 @@ export class EventService { case EventType.Cipher_Restored: msg = this.i18nService.t("restoredItemId", this.formatCipherId(ev, options)); humanReadableMsg = this.i18nService.t("restoredItemId", this.formatCipherId(ev, options)); + eventName = this.i18nService.t("restoredItemId", ""); break; case EventType.Cipher_AttachmentCreated: msg = this.i18nService.t("createdAttachmentForItem", this.formatCipherId(ev, options)); @@ -130,6 +159,7 @@ export class EventService { "createdAttachmentForItem", this.getShortId(ev.cipherId), ); + eventName = this.i18nService.t("createdAttachmentForItem", ""); break; case EventType.Cipher_AttachmentDeleted: msg = this.i18nService.t("deletedAttachmentForItem", this.formatCipherId(ev, options)); @@ -137,18 +167,22 @@ export class EventService { "deletedAttachmentForItem", this.getShortId(ev.cipherId), ); + eventName = this.i18nService.t("deletedAttachmentForItem", ""); break; case EventType.Cipher_Shared: msg = this.i18nService.t("movedItemIdToOrg", this.formatCipherId(ev, options)); humanReadableMsg = this.i18nService.t("movedItemIdToOrg", this.getShortId(ev.cipherId)); + eventName = this.i18nService.t("movedItemIdToOrg", ""); break; case EventType.Cipher_ClientViewed: msg = this.i18nService.t("viewedItemId", this.formatCipherId(ev, options)); humanReadableMsg = this.i18nService.t("viewedItemId", this.getShortId(ev.cipherId)); + eventName = this.i18nService.t("viewedItemId", ""); break; case EventType.Cipher_ClientToggledPasswordVisible: msg = this.i18nService.t("viewedPasswordItemId", this.formatCipherId(ev, options)); humanReadableMsg = this.i18nService.t("viewedPasswordItemId", this.getShortId(ev.cipherId)); + eventName = this.i18nService.t("viewedPasswordItemId", ""); break; case EventType.Cipher_ClientToggledHiddenFieldVisible: msg = this.i18nService.t("viewedHiddenFieldItemId", this.formatCipherId(ev, options)); @@ -156,6 +190,7 @@ export class EventService { "viewedHiddenFieldItemId", this.getShortId(ev.cipherId), ); + eventName = this.i18nService.t("viewedHiddenFieldItemId", ""); break; case EventType.Cipher_ClientToggledCardNumberVisible: msg = this.i18nService.t("viewedCardNumberItemId", this.formatCipherId(ev, options)); @@ -163,6 +198,7 @@ export class EventService { "viewedCardNumberItemId", this.getShortId(ev.cipherId), ); + eventName = this.i18nService.t("viewedCardNumberItemId", ""); break; case EventType.Cipher_ClientToggledCardCodeVisible: msg = this.i18nService.t("viewedSecurityCodeItemId", this.formatCipherId(ev, options)); @@ -170,6 +206,7 @@ export class EventService { "viewedSecurityCodeItemId", this.getShortId(ev.cipherId), ); + eventName = this.i18nService.t("viewedSecurityCodeItemId", ""); break; case EventType.Cipher_ClientCopiedHiddenField: msg = this.i18nService.t("copiedHiddenFieldItemId", this.formatCipherId(ev, options)); @@ -177,10 +214,12 @@ export class EventService { "copiedHiddenFieldItemId", this.getShortId(ev.cipherId), ); + eventName = this.i18nService.t("copiedHiddenFieldItemId", ""); break; case EventType.Cipher_ClientCopiedPassword: msg = this.i18nService.t("copiedPasswordItemId", this.formatCipherId(ev, options)); humanReadableMsg = this.i18nService.t("copiedPasswordItemId", this.getShortId(ev.cipherId)); + eventName = this.i18nService.t("copiedPasswordItemId", ""); break; case EventType.Cipher_ClientCopiedCardCode: msg = this.i18nService.t("copiedSecurityCodeItemId", this.formatCipherId(ev, options)); @@ -188,10 +227,12 @@ export class EventService { "copiedSecurityCodeItemId", this.getShortId(ev.cipherId), ); + eventName = this.i18nService.t("copiedSecurityCodeItemId", ""); break; case EventType.Cipher_ClientAutofilled: msg = this.i18nService.t("autofilledItemId", this.formatCipherId(ev, options)); humanReadableMsg = this.i18nService.t("autofilledItemId", this.getShortId(ev.cipherId)); + eventName = this.i18nService.t("autofilledItemId", ""); break; case EventType.Cipher_UpdatedCollections: msg = this.i18nService.t("editedCollectionsForItem", this.formatCipherId(ev, options)); @@ -199,6 +240,7 @@ export class EventService { "editedCollectionsForItem", this.getShortId(ev.cipherId), ); + eventName = this.i18nService.t("editedCollectionsForItem", ""); break; // Collection case EventType.Collection_Created: @@ -488,6 +530,8 @@ export class EventService { return { message: msg === "" ? null : msg, humanReadableMessage: humanReadableMsg === "" ? null : humanReadableMsg, + eventName: eventName, + eventLink: eventLink, }; } @@ -558,7 +602,12 @@ export class EventService { if (ev.organizationId == null || !options.cipherInfo) { return "" + shortId + ""; } - const a = this.makeAnchor(shortId); + const anchorName = + options.useCipherName && options.cipher && options.cipher.name + ? options.cipher.name + : shortId; + + const a = this.makeAnchor(anchorName); a.setAttribute( "href", `#/organizations/${ev.organizationId}/vault?search=${shortId}&viewEvents=${ev.cipherId}&type=all`, @@ -680,8 +729,12 @@ export class EventInfo { humanReadableMessage: string; appIcon: string; appName: string; + eventName: string; + eventLink: string; } export class EventOptions { cipherInfo = true; + useCipherName = false; + cipher: CipherView | null = null; } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts index 2ad2ecdccbd..5d4b78d6a3b 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts @@ -11,6 +11,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; import { BaseEventsComponent } from "@bitwarden/web-vault/app/admin-console/common/base.events.component"; import { EventService } from "@bitwarden/web-vault/app/core"; @@ -22,6 +23,8 @@ import { EventExportService } from "@bitwarden/web-vault/app/tools/event-export" standalone: false, }) export class EventsComponent extends BaseEventsComponent implements OnInit { + organizationId: OrganizationId; + userId: UserId; exportFileName = "provider-events"; providerId: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts index ddaa0937e6f..f3319589d7c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts @@ -8,6 +8,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; import { BaseEventsComponent } from "@bitwarden/web-vault/app/admin-console/common/base.events.component"; import { EventService } from "@bitwarden/web-vault/app/core"; @@ -24,6 +25,8 @@ export class ServiceAccountEventsComponent extends BaseEventsComponent implements OnInit, OnDestroy { + organizationId: OrganizationId; + userId: UserId; exportFileName = "machine-account-events"; private destroy$ = new Subject(); private serviceAccountId: string; diff --git a/libs/common/src/models/view/event.view.ts b/libs/common/src/models/view/event.view.ts index 5eb5ddd9edb..67b6f4dadbd 100644 --- a/libs/common/src/models/view/event.view.ts +++ b/libs/common/src/models/view/event.view.ts @@ -14,6 +14,8 @@ export class EventView { installationId: string; systemUser: EventSystemUser; serviceAccountId: string; + eventName: string; + eventLink: string; constructor(data: Required) { this.message = data.message; @@ -29,5 +31,7 @@ export class EventView { this.installationId = data.installationId; this.systemUser = data.systemUser; this.serviceAccountId = data.serviceAccountId; + this.eventName = data.eventName; + this.eventLink = data.eventLink; } }