1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[SM-1489] machine account event logs (#15997)

* Adding enums for additional event logs for secrets

* updating messages

* Updating messages to be consistent for logs

* Displaying project logs, and fixing search query param searching in projects list, having deleted log for secrets and projects not show as a link

* Viewing secret and project event logs in event modal, adding to the context menu for secrets and projects the ability to view the logs if user has permission. Restricting logs to SM projs and Secs if the logged in user has event log access but not SM access.

* lint

* Lint Fixes

* fix to messages file

* fixing lint

* Adding machine account event logs

* lint fix

* Update event.service.ts

* removing duplicate function issue from merge

* Update service-accounts-list.component.ts

* fixing message

* Fixes to QA bugs

* lint fix

* linter for messages is annoying

* lint
This commit is contained in:
cd-bitwarden
2025-10-03 03:52:00 -04:00
committed by GitHub
parent 2ddf1c34b2
commit cb20889a94
10 changed files with 375 additions and 19 deletions

View File

@@ -84,6 +84,15 @@
<i class="bwi bwi-fw bwi-pencil" aria-hidden="true"></i>
{{ "editMachineAccount" | i18n }}
</button>
<button
type="button"
bitMenuItem
*ngIf="viewEventsAllowed$ | async as allowed"
(click)="openEventsDialog(serviceAccount)"
>
<i class="bwi bwi-fw bwi-billing" aria-hidden="true"></i>
<span> {{ "viewEvents" | i18n }} </span>
</button>
<button type="button" bitMenuItem (click)="delete(serviceAccount)">
<i class="bwi bwi-fw bwi-trash tw-text-danger" aria-hidden="true"></i>
<span class="tw-text-danger">

View File

@@ -1,11 +1,20 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { SelectionModel } from "@angular/cdk/collections";
import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { Component, EventEmitter, Input, OnDestroy, Output, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { catchError, concatMap, map, Observable, of, Subject, switchMap, takeUntil } from "rxjs";
import {
getOrganizationById,
OrganizationService,
} 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { TableDataSource, ToastService } from "@bitwarden/components";
import { DialogRef, DialogService, TableDataSource, ToastService } from "@bitwarden/components";
import { LogService } from "@bitwarden/logging";
import { openEntityEventsDialog } from "@bitwarden/web-vault/app/admin-console/organizations/manage/entity-events.component";
import {
ServiceAccountSecretsDetailsView,
@@ -17,7 +26,7 @@ import {
templateUrl: "./service-accounts-list.component.html",
standalone: false,
})
export class ServiceAccountsListComponent implements OnDestroy {
export class ServiceAccountsListComponent implements OnDestroy, OnInit {
protected dataSource = new TableDataSource<ServiceAccountSecretsDetailsView>();
@Input()
@@ -43,18 +52,52 @@ export class ServiceAccountsListComponent implements OnDestroy {
@Output() editServiceAccountEvent = new EventEmitter<string>();
private destroy$: Subject<void> = new Subject<void>();
protected viewEventsAllowed$: Observable<boolean>;
protected isAdmin$: Observable<boolean>;
selection = new SelectionModel<string>(true, []);
constructor(
private i18nService: I18nService,
private toastService: ToastService,
private dialogService: DialogService,
private organizationService: OrganizationService,
private activatedRoute: ActivatedRoute,
private accountService: AccountService,
private logService: LogService,
) {
this.selection.changed
.pipe(takeUntil(this.destroy$))
.subscribe((_) => this.onServiceAccountCheckedEvent.emit(this.selection.selected));
}
ngOnInit(): void {
this.viewEventsAllowed$ = this.activatedRoute.params.pipe(
concatMap((params) =>
getUserId(this.accountService.activeAccount$).pipe(
switchMap((userId) =>
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(params.organizationId)),
),
),
),
map((org) => org.canAccessEventLogs),
catchError((error: unknown) => {
if (typeof error === "string") {
this.toastService.showToast({
message: error,
variant: "error",
title: "",
});
} else {
this.logService.error(error);
}
return of(false);
}),
takeUntil(this.destroy$),
);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
@@ -94,4 +137,13 @@ export class ServiceAccountsListComponent implements OnDestroy {
});
}
}
openEventsDialog = (serviceAccount: ServiceAccountView): DialogRef<void> =>
openEntityEventsDialog(this.dialogService, {
data: {
name: serviceAccount.name,
organizationId: serviceAccount.organizationId,
entityId: serviceAccount.id,
entity: "service-account",
},
});
}

View File

@@ -1,8 +1,8 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { combineLatest, firstValueFrom, Observable, startWith, switchMap } from "rxjs";
import { ActivatedRoute, Router } from "@angular/router";
import { combineLatestWith, firstValueFrom, Observable, startWith, switchMap } from "rxjs";
import {
getOrganizationById,
@@ -10,7 +10,9 @@ import {
} 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 { DialogService } from "@bitwarden/components";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DialogRef, DialogService, ToastService } from "@bitwarden/components";
import { openEntityEventsDialog } from "@bitwarden/web-vault/app/admin-console/organizations/manage/entity-events.component";
import {
ServiceAccountSecretsDetailsView,
@@ -46,14 +48,16 @@ export class ServiceAccountsComponent implements OnInit {
private serviceAccountService: ServiceAccountService,
private organizationService: OrganizationService,
private accountService: AccountService,
private toastService: ToastService,
private router: Router,
private i18nService: I18nService,
) {}
ngOnInit() {
this.serviceAccounts$ = combineLatest([
this.route.params,
this.serviceAccountService.serviceAccount$.pipe(startWith(null)),
]).pipe(
switchMap(async ([params]) => {
this.serviceAccounts$ = this.serviceAccountService.serviceAccount$.pipe(
startWith(null),
combineLatestWith(this.route.params),
switchMap(async ([_, params]) => {
this.organizationId = params.organizationId;
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.organizationEnabled = (
@@ -64,11 +68,74 @@ export class ServiceAccountsComponent implements OnInit {
)
)?.enabled;
return await this.getServiceAccounts();
const serviceAccounts = await this.getServiceAccounts();
const viewEvents = this.route.snapshot.queryParams.viewEvents;
if (viewEvents) {
const targetAccount = serviceAccounts.find((sa) => sa.id === viewEvents);
const userIsAdmin = (
await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(params.organizationId)),
)
)?.isAdmin;
if (!targetAccount) {
if (userIsAdmin) {
this.openEventsDialogByEntityId(
this.i18nService.t("nameUnavailableServiceAccountDeleted", viewEvents),
viewEvents,
);
} else {
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("unknownServiceAccount"),
});
}
} else {
this.openEventsDialog(targetAccount);
}
await this.router.navigate([], {
queryParams: { search: this.search },
});
}
return serviceAccounts;
}),
);
if (this.route.snapshot.queryParams.search) {
this.search = this.route.snapshot.queryParams.search;
}
}
openEventsDialogByEntityId = (
serviceAccountName: string,
serviceAccountId: string,
): DialogRef<void> =>
openEntityEventsDialog(this.dialogService, {
data: {
name: serviceAccountName,
organizationId: this.organizationId,
entityId: serviceAccountId,
entity: "service-account",
},
});
openEventsDialog = (serviceAccount: ServiceAccountView): DialogRef<void> =>
openEntityEventsDialog(this.dialogService, {
data: {
name: serviceAccount.name,
organizationId: this.organizationId,
entityId: serviceAccount.id,
entity: "service-account",
},
});
openNewServiceAccountDialog() {
this.dialogService.open<unknown, ServiceAccountOperation>(ServiceAccountDialogComponent, {
data: {