diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-account-event-log-api.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-account-event-log-api.service.ts new file mode 100644 index 0000000000..669c063e98 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-account-event-log-api.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from "@angular/core"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { EventResponse } from "@bitwarden/common/models/response/event.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; + +@Injectable({ + providedIn: "root", +}) +export class ServiceAccountEventLogApiService { + constructor(private apiService: ApiService) {} + + async getEvents( + serviceAccountId: string, + start: string, + end: string, + token: string + ): Promise> { + const r = await this.apiService.send( + "GET", + this.addEventParameters("/sm/events/service-accounts/" + serviceAccountId, start, end, token), + null, + true, + true + ); + return new ListResponse(r, EventResponse); + } + + private addEventParameters(base: string, start: string, end: string, token: string) { + if (start != null) { + base += "?start=" + start; + } + if (end != null) { + base += base.indexOf("?") > -1 ? "&" : "?"; + base += "end=" + end; + } + if (token != null) { + base += base.indexOf("?") > -1 ? "&" : "?"; + base += "continuationToken=" + token; + } + return base; + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.html new file mode 100644 index 0000000000..e5a7ce64da --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.html @@ -0,0 +1,105 @@ +
+

{{ "eventLogs" | i18n }}

+
+ + {{ "from" | i18n }} + + + - + + {{ "to" | i18n }} + + +
+ +
+
+ +
+
+
+ + + {{ "loading" | i18n }} + + +

{{ "noEventsInList" | i18n }}

+ + + + {{ "timestamp" | i18n }} + {{ "client" | i18n }} + {{ "event" | i18n }} + + + + + {{ e.date | date : "medium" }} + + {{ e.appName }} + + + + + + +
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 new file mode 100644 index 0000000000..652272ecd1 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts @@ -0,0 +1,77 @@ +import { Component, OnDestroy } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { Subject, takeUntil } from "rxjs"; + +import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; +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 { BaseEventsComponent } from "@bitwarden/web-vault/app/common/base.events.component"; +import { EventService } from "@bitwarden/web-vault/app/core"; +import { EventExportService } from "@bitwarden/web-vault/app/tools/event-export"; + +import { ServiceAccountEventLogApiService } from "./service-account-event-log-api.service"; + +@Component({ + selector: "sm-service-accounts-events", + templateUrl: "./service-accounts-events.component.html", +}) +export class ServiceAccountEventsComponent extends BaseEventsComponent implements OnDestroy { + exportFileName = "service-account-events"; + private destroy$ = new Subject(); + private serviceAccountId: string; + + constructor( + eventService: EventService, + private serviceAccountEventsApiService: ServiceAccountEventLogApiService, + private route: ActivatedRoute, + i18nService: I18nService, + exportService: EventExportService, + platformUtilsService: PlatformUtilsService, + logService: LogService, + fileDownloadService: FileDownloadService + ) { + super( + eventService, + i18nService, + exportService, + platformUtilsService, + logService, + fileDownloadService + ); + } + + async ngOnInit() { + // eslint-disable-next-line rxjs/no-async-subscribe + this.route.params.pipe(takeUntil(this.destroy$)).subscribe(async (params) => { + this.serviceAccountId = params.serviceAccountId; + await this.load(); + }); + } + + async load() { + await this.loadEvents(true); + this.loaded = true; + } + + protected requestEvents(startDate: string, endDate: string, continuationToken: string) { + return this.serviceAccountEventsApiService.getEvents( + this.serviceAccountId, + startDate, + endDate, + continuationToken + ); + } + + protected getUserName() { + return { + name: this.i18nService.t("serviceAccount") + " " + this.serviceAccountId, + email: "", + }; + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts new file mode 100644 index 0000000000..a1c54a6bfa --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.ts @@ -0,0 +1,28 @@ +import { inject } from "@angular/core"; +import { ActivatedRouteSnapshot, CanActivateFn, createUrlTreeFromSnapshot } from "@angular/router"; + +import { ServiceAccountService } from "../service-account.service"; + +/** + * Redirects to service accounts page if the user doesn't have access to service account. + */ +export const serviceAccountAccessGuard: CanActivateFn = async (route: ActivatedRouteSnapshot) => { + const serviceAccountService = inject(ServiceAccountService); + + try { + const serviceAccount = await serviceAccountService.getByServiceAccountId( + route.params.serviceAccountId, + route.params.organizationId + ); + if (serviceAccount) { + return true; + } + } catch { + return createUrlTreeFromSnapshot(route, [ + "/sm", + route.params.organizationId, + "service-accounts", + ]); + } + return createUrlTreeFromSnapshot(route, ["/sm", route.params.organizationId, "service-accounts"]); +}; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html index 7d6304b5a0..8b7991b121 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.html @@ -13,6 +13,7 @@ {{ "projects" | i18n }} {{ "people" | i18n }} {{ "accessTokens" | i18n }} + {{ "eventLogs" | i18n }}