mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[SM-1274] Add Project Events to the Log List in Admin Console (#15442)
* 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 * Bug fix, make sure event logs related to service accounts are still links that take you to the object * removing unused import
This commit is contained in:
@@ -1,8 +1,13 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Directive } from "@angular/core";
|
import { Directive, OnDestroy } from "@angular/core";
|
||||||
import { FormControl, FormGroup } from "@angular/forms";
|
import { FormControl, FormGroup } from "@angular/forms";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { combineLatest, filter, map, Observable, Subject, switchMap, takeUntil } from "rxjs";
|
||||||
|
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { EventResponse } from "@bitwarden/common/models/response/event.response";
|
import { EventResponse } from "@bitwarden/common/models/response/event.response";
|
||||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
import { EventView } from "@bitwarden/common/models/view/event.view";
|
import { EventView } from "@bitwarden/common/models/view/event.view";
|
||||||
@@ -12,16 +17,17 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { ToastService } from "@bitwarden/components";
|
import { ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { EventService } from "../../core";
|
import { EventOptions, EventService } from "../../core";
|
||||||
import { EventExportService } from "../../tools/event-export";
|
import { EventExportService } from "../../tools/event-export";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export abstract class BaseEventsComponent {
|
export abstract class BaseEventsComponent implements OnDestroy {
|
||||||
loading = true;
|
loading = true;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
events: EventView[];
|
events: EventView[];
|
||||||
dirtyDates = true;
|
dirtyDates = true;
|
||||||
continuationToken: string;
|
continuationToken: string;
|
||||||
|
canUseSM = false;
|
||||||
|
|
||||||
abstract readonly exportFileName: string;
|
abstract readonly exportFileName: string;
|
||||||
|
|
||||||
@@ -30,6 +36,15 @@ export abstract class BaseEventsComponent {
|
|||||||
end: new FormControl(null),
|
end: new FormControl(null),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
protected canUseSM$: Observable<boolean>;
|
||||||
|
protected activeOrganization$: Observable<Organization | undefined>;
|
||||||
|
protected organizations$: Observable<Organization[]>;
|
||||||
|
private destroySubject$ = new Subject<void>();
|
||||||
|
|
||||||
|
protected get destroy$(): Observable<void> {
|
||||||
|
return this.destroySubject$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected eventService: EventService,
|
protected eventService: EventService,
|
||||||
protected i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
@@ -38,12 +53,39 @@ export abstract class BaseEventsComponent {
|
|||||||
protected logService: LogService,
|
protected logService: LogService,
|
||||||
protected fileDownloadService: FileDownloadService,
|
protected fileDownloadService: FileDownloadService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
|
protected activeRoute: ActivatedRoute,
|
||||||
|
protected accountService: AccountService,
|
||||||
|
protected organizationService: OrganizationService,
|
||||||
) {
|
) {
|
||||||
const defaultDates = this.eventService.getDefaultDateFilters();
|
const defaultDates = this.eventService.getDefaultDateFilters();
|
||||||
this.start = defaultDates[0];
|
this.start = defaultDates[0];
|
||||||
this.end = defaultDates[1];
|
this.end = defaultDates[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected initBase(): void {
|
||||||
|
this.organizations$ = this.accountService.activeAccount$.pipe(
|
||||||
|
filter((account): account is Account => !!account?.id),
|
||||||
|
switchMap((account) => this.organizationService.organizations$(account.id)),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.activeOrganization$ = combineLatest([this.activeRoute.paramMap, this.organizations$]).pipe(
|
||||||
|
map(([params, orgs]) => orgs.find((org) => org.id === params.get("organizationId"))),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.canUseSM$ = this.activeOrganization$.pipe(
|
||||||
|
map((org) => org?.canAccessSecretsManager ?? false),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.canUseSM$.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||||
|
this.canUseSM = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroySubject$.next();
|
||||||
|
this.destroySubject$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
get start(): string {
|
get start(): string {
|
||||||
return this.eventsForm.value.start;
|
return this.eventsForm.value.start;
|
||||||
}
|
}
|
||||||
@@ -139,7 +181,10 @@ export abstract class BaseEventsComponent {
|
|||||||
const events = await Promise.all(
|
const events = await Promise.all(
|
||||||
response.data.map(async (r) => {
|
response.data.map(async (r) => {
|
||||||
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
|
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
|
||||||
const eventInfo = await this.eventService.getEventInfo(r);
|
const options = new EventOptions();
|
||||||
|
options.disableLink = !this.canUseSM;
|
||||||
|
|
||||||
|
const eventInfo = await this.eventService.getEventInfo(r, options);
|
||||||
const user = this.getUserName(r, userId);
|
const user = this.getUserName(r, userId);
|
||||||
const userName = user != null ? user.name : this.i18nService.t("unknown");
|
const userName = user != null ? user.name : this.i18nService.t("unknown");
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { firstValueFrom, switchMap } from "rxjs";
|
|||||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { EventResponse } from "@bitwarden/common/models/response/event.response";
|
import { EventResponse } from "@bitwarden/common/models/response/event.response";
|
||||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
import { EventView } from "@bitwarden/common/models/view/event.view";
|
import { EventView } from "@bitwarden/common/models/view/event.view";
|
||||||
@@ -26,7 +28,7 @@ import { EventService } from "../../../core";
|
|||||||
import { SharedModule } from "../../../shared";
|
import { SharedModule } from "../../../shared";
|
||||||
|
|
||||||
export interface EntityEventsDialogParams {
|
export interface EntityEventsDialogParams {
|
||||||
entity: "user" | "cipher";
|
entity: "user" | "cipher" | "secret" | "project";
|
||||||
entityId: string;
|
entityId: string;
|
||||||
|
|
||||||
organizationId?: string;
|
organizationId?: string;
|
||||||
@@ -72,6 +74,8 @@ export class EntityEventsComponent implements OnInit, OnDestroy {
|
|||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private activeRoute: ActivatedRoute,
|
private activeRoute: ActivatedRoute,
|
||||||
|
private accountService: AccountService,
|
||||||
|
protected organizationService: OrganizationService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -162,6 +166,22 @@ export class EntityEventsComponent implements OnInit, OnDestroy {
|
|||||||
dates[1],
|
dates[1],
|
||||||
clearExisting ? null : this.continuationToken,
|
clearExisting ? null : this.continuationToken,
|
||||||
);
|
);
|
||||||
|
} else if (this.params.entity === "secret") {
|
||||||
|
response = await this.apiService.getEventsSecret(
|
||||||
|
this.params.organizationId,
|
||||||
|
this.params.entityId,
|
||||||
|
dates[0],
|
||||||
|
dates[1],
|
||||||
|
clearExisting ? null : this.continuationToken,
|
||||||
|
);
|
||||||
|
} else if (this.params.entity === "project") {
|
||||||
|
response = await this.apiService.getEventsProject(
|
||||||
|
this.params.organizationId,
|
||||||
|
this.params.entityId,
|
||||||
|
dates[0],
|
||||||
|
dates[1],
|
||||||
|
clearExisting ? null : this.continuationToken,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
response = await this.apiService.getEventsCipher(
|
response = await this.apiService.getEventsCipher(
|
||||||
this.params.entityId,
|
this.params.entityId,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { concatMap, firstValueFrom, lastValueFrom, Subject, takeUntil } from "rxjs";
|
import { concatMap, firstValueFrom, lastValueFrom, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||||
@@ -60,8 +60,6 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
|
|||||||
placeholderEvents = placeholderEvents as EventView[];
|
placeholderEvents = placeholderEvents as EventView[];
|
||||||
|
|
||||||
private orgUsersUserIdMap = new Map<string, any>();
|
private orgUsersUserIdMap = new Map<string, any>();
|
||||||
private destroy$ = new Subject<void>();
|
|
||||||
|
|
||||||
readonly ProductTierType = ProductTierType;
|
readonly ProductTierType = ProductTierType;
|
||||||
|
|
||||||
protected isBreadcrumbEventLogsEnabled$ = this.configService.getFeatureFlag$(
|
protected isBreadcrumbEventLogsEnabled$ = this.configService.getFeatureFlag$(
|
||||||
@@ -75,18 +73,18 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
|
|||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
exportService: EventExportService,
|
exportService: EventExportService,
|
||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
private router: Router,
|
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
private userNamePipe: UserNamePipe,
|
private userNamePipe: UserNamePipe,
|
||||||
private organizationService: OrganizationService,
|
protected organizationService: OrganizationService,
|
||||||
private organizationUserApiService: OrganizationUserApiService,
|
private organizationUserApiService: OrganizationUserApiService,
|
||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
fileDownloadService: FileDownloadService,
|
fileDownloadService: FileDownloadService,
|
||||||
toastService: ToastService,
|
toastService: ToastService,
|
||||||
private accountService: AccountService,
|
protected accountService: AccountService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
|
protected activeRoute: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
eventService,
|
eventService,
|
||||||
@@ -96,10 +94,15 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
|
|||||||
logService,
|
logService,
|
||||||
fileDownloadService,
|
fileDownloadService,
|
||||||
toastService,
|
toastService,
|
||||||
|
activeRoute,
|
||||||
|
accountService,
|
||||||
|
organizationService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
this.initBase();
|
||||||
|
|
||||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
this.route.params
|
this.route.params
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -233,9 +236,4 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
|
|||||||
}
|
}
|
||||||
await this.load();
|
await this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
this.destroy$.next();
|
|
||||||
this.destroy$.complete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -467,21 +467,60 @@ export class EventService {
|
|||||||
break;
|
break;
|
||||||
// Secrets Manager
|
// Secrets Manager
|
||||||
case EventType.Secret_Retrieved:
|
case EventType.Secret_Retrieved:
|
||||||
msg = this.i18nService.t("accessedSecretWithId", this.formatSecretId(ev));
|
msg = this.i18nService.t("accessedSecretWithId", this.formatSecretId(ev, options));
|
||||||
humanReadableMsg = this.i18nService.t("accessedSecretWithId", this.getShortId(ev.secretId));
|
humanReadableMsg = this.i18nService.t("accessedSecretWithId", this.getShortId(ev.secretId));
|
||||||
break;
|
break;
|
||||||
case EventType.Secret_Created:
|
case EventType.Secret_Created:
|
||||||
msg = this.i18nService.t("createdSecretWithId", this.formatSecretId(ev));
|
msg = this.i18nService.t("createdSecretWithId", this.formatSecretId(ev, options));
|
||||||
humanReadableMsg = this.i18nService.t("createdSecretWithId", this.getShortId(ev.secretId));
|
humanReadableMsg = this.i18nService.t("createdSecretWithId", this.getShortId(ev.secretId));
|
||||||
break;
|
break;
|
||||||
case EventType.Secret_Deleted:
|
case EventType.Secret_Deleted:
|
||||||
msg = this.i18nService.t("deletedSecretWithId", this.formatSecretId(ev));
|
msg = this.i18nService.t("deletedSecretWithId", this.formatSecretId(ev, options));
|
||||||
humanReadableMsg = this.i18nService.t("deletedSecretWithId", this.getShortId(ev.secretId));
|
humanReadableMsg = this.i18nService.t("deletedSecretWithId", this.getShortId(ev.secretId));
|
||||||
break;
|
break;
|
||||||
|
case EventType.Secret_Permanently_Deleted:
|
||||||
|
msg = this.i18nService.t(
|
||||||
|
"permanentlyDeletedSecretWithId",
|
||||||
|
this.formatSecretId(ev, options),
|
||||||
|
);
|
||||||
|
humanReadableMsg = this.i18nService.t(
|
||||||
|
"permanentlyDeletedSecretWithId",
|
||||||
|
this.getShortId(ev.secretId),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case EventType.Secret_Restored:
|
||||||
|
msg = this.i18nService.t("restoredSecretWithId", this.formatSecretId(ev, options));
|
||||||
|
humanReadableMsg = this.i18nService.t("restoredSecretWithId", this.getShortId(ev.secretId));
|
||||||
|
break;
|
||||||
case EventType.Secret_Edited:
|
case EventType.Secret_Edited:
|
||||||
msg = this.i18nService.t("editedSecretWithId", this.formatSecretId(ev));
|
msg = this.i18nService.t("editedSecretWithId", this.formatSecretId(ev, options));
|
||||||
humanReadableMsg = this.i18nService.t("editedSecretWithId", this.getShortId(ev.secretId));
|
humanReadableMsg = this.i18nService.t("editedSecretWithId", this.getShortId(ev.secretId));
|
||||||
break;
|
break;
|
||||||
|
case EventType.Project_Retrieved:
|
||||||
|
msg = this.i18nService.t("accessedProjectWithId", this.formatProjectId(ev, options));
|
||||||
|
humanReadableMsg = this.i18nService.t(
|
||||||
|
"accessedProjectWithId",
|
||||||
|
this.getShortId(ev.projectId),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case EventType.Project_Created:
|
||||||
|
msg = this.i18nService.t("createdProjectWithId", this.formatProjectId(ev, options));
|
||||||
|
humanReadableMsg = this.i18nService.t(
|
||||||
|
"createdProjectWithId",
|
||||||
|
this.getShortId(ev.projectId),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case EventType.Project_Deleted:
|
||||||
|
msg = this.i18nService.t("deletedProjectWithId", this.formatProjectId(ev, options));
|
||||||
|
humanReadableMsg = this.i18nService.t(
|
||||||
|
"deletedProjectWithId",
|
||||||
|
this.getShortId(ev.projectId),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case EventType.Project_Edited:
|
||||||
|
msg = this.i18nService.t("editedProjectWithId", this.formatProjectId(ev, options));
|
||||||
|
humanReadableMsg = this.i18nService.t("editedProjectWithId", this.getShortId(ev.projectId));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -637,10 +676,41 @@ export class EventService {
|
|||||||
return a.outerHTML;
|
return a.outerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
formatSecretId(ev: EventResponse): string {
|
formatSecretId(ev: EventResponse, options: EventOptions): string {
|
||||||
const shortId = this.getShortId(ev.secretId);
|
const shortId = this.getShortId(ev.secretId);
|
||||||
|
if (options.disableLink) {
|
||||||
|
return shortId;
|
||||||
|
}
|
||||||
const a = this.makeAnchor(shortId);
|
const a = this.makeAnchor(shortId);
|
||||||
a.setAttribute("href", "#/sm/" + ev.organizationId + "/secrets?search=" + shortId);
|
a.setAttribute(
|
||||||
|
"href",
|
||||||
|
"#/sm/" +
|
||||||
|
ev.organizationId +
|
||||||
|
"/secrets?search=" +
|
||||||
|
shortId +
|
||||||
|
"&viewEvents=" +
|
||||||
|
ev.secretId +
|
||||||
|
"&type=all",
|
||||||
|
);
|
||||||
|
return a.outerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatProjectId(ev: EventResponse, options: EventOptions): string {
|
||||||
|
const shortId = this.getShortId(ev.projectId);
|
||||||
|
if (options.disableLink) {
|
||||||
|
return shortId;
|
||||||
|
}
|
||||||
|
const a = this.makeAnchor(shortId);
|
||||||
|
a.setAttribute(
|
||||||
|
"href",
|
||||||
|
"#/sm/" +
|
||||||
|
ev.organizationId +
|
||||||
|
"/projects?search=" +
|
||||||
|
shortId +
|
||||||
|
"&viewEvents=" +
|
||||||
|
ev.projectId +
|
||||||
|
"&type=all",
|
||||||
|
);
|
||||||
return a.outerHTML;
|
return a.outerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,4 +754,5 @@ export class EventInfo {
|
|||||||
|
|
||||||
export class EventOptions {
|
export class EventOptions {
|
||||||
cipherInfo = true;
|
cipherInfo = true;
|
||||||
|
disableLink = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7039,6 +7039,12 @@
|
|||||||
"unknownCipher": {
|
"unknownCipher": {
|
||||||
"message": "Unknown item, you may need to request permission to access this item."
|
"message": "Unknown item, you may need to request permission to access this item."
|
||||||
},
|
},
|
||||||
|
"unknownSecret": {
|
||||||
|
"message": "Unknown secret, you may need to request permission to access this secret."
|
||||||
|
},
|
||||||
|
"unknownProject": {
|
||||||
|
"message": "Unknown project, you may need to request permission to access this project."
|
||||||
|
},
|
||||||
"cannotSponsorSelf": {
|
"cannotSponsorSelf": {
|
||||||
"message": "You cannot redeem for the active account. Enter a different email."
|
"message": "You cannot redeem for the active account. Enter a different email."
|
||||||
},
|
},
|
||||||
@@ -8361,6 +8367,24 @@
|
|||||||
"example": "4d34e8a8"
|
"example": "4d34e8a8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"permanentlyDeletedSecretWithId": {
|
||||||
|
"message": "Permanently deleted a secret with identifier: $SECRET_ID$",
|
||||||
|
"placeholders": {
|
||||||
|
"secret_id": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "4d34e8a8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restoredSecretWithId": {
|
||||||
|
"message": "Restored a secret with identifier: $SECRET_ID$",
|
||||||
|
"placeholders": {
|
||||||
|
"secret_id": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "4d34e8a8"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"createdSecretWithId": {
|
"createdSecretWithId": {
|
||||||
"message": "Created a new secret with identifier: $SECRET_ID$",
|
"message": "Created a new secret with identifier: $SECRET_ID$",
|
||||||
@@ -8371,6 +8395,60 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"accessedProjectWithId": {
|
||||||
|
"message": "Accessed a project with Id: $PROJECT_ID$.",
|
||||||
|
"placeholders": {
|
||||||
|
"project_id": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "4d34e8a8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nameUnavailableProjectDeleted": {
|
||||||
|
"message": "Deleted project Id: $PROJECT_ID$",
|
||||||
|
"placeholders": {
|
||||||
|
"project_id": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "4d34e8a8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nameUnavailableSecretDeleted": {
|
||||||
|
"message": "Deleted secret Id: $SECRET_ID$",
|
||||||
|
"placeholders": {
|
||||||
|
"secret_id": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "4d34e8a8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"editedProjectWithId": {
|
||||||
|
"message": "Edited a project with identifier: $PROJECT_ID$",
|
||||||
|
"placeholders": {
|
||||||
|
"project_id": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "4d34e8a8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deletedProjectWithId": {
|
||||||
|
"message": "Deleted a project with identifier: $PROJECT_ID$",
|
||||||
|
"placeholders": {
|
||||||
|
"project_id": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "4d34e8a8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"createdProjectWithId": {
|
||||||
|
"message": "Created a new project with identifier: $PROJECT_ID$",
|
||||||
|
"placeholders": {
|
||||||
|
"project_id": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "4d34e8a8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"message": "SDK",
|
"message": "SDK",
|
||||||
"description": "Software Development Kit"
|
"description": "Software Development Kit"
|
||||||
@@ -10755,6 +10833,9 @@
|
|||||||
"upgradeEventLogMessage":{
|
"upgradeEventLogMessage":{
|
||||||
"message" : "These events are examples only and do not reflect real events within your Bitwarden organization."
|
"message" : "These events are examples only and do not reflect real events within your Bitwarden organization."
|
||||||
},
|
},
|
||||||
|
"viewEvents":{
|
||||||
|
"message" : "View Events"
|
||||||
|
},
|
||||||
"cannotCreateCollection": {
|
"cannotCreateCollection": {
|
||||||
"message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections."
|
"message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import { ActivatedRoute, Router } from "@angular/router";
|
|||||||
|
|
||||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { EventResponse } from "@bitwarden/common/models/response/event.response";
|
import { EventResponse } from "@bitwarden/common/models/response/event.response";
|
||||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@@ -41,6 +43,8 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
|
|||||||
private userNamePipe: UserNamePipe,
|
private userNamePipe: UserNamePipe,
|
||||||
fileDownloadService: FileDownloadService,
|
fileDownloadService: FileDownloadService,
|
||||||
toastService: ToastService,
|
toastService: ToastService,
|
||||||
|
accountService: AccountService,
|
||||||
|
organizationService: OrganizationService,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
eventService,
|
eventService,
|
||||||
@@ -50,6 +54,9 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
|
|||||||
logService,
|
logService,
|
||||||
fileDownloadService,
|
fileDownloadService,
|
||||||
toastService,
|
toastService,
|
||||||
|
route,
|
||||||
|
accountService,
|
||||||
|
organizationService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +76,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
|
this.initBase();
|
||||||
const response = await this.apiService.getProviderUsers(this.providerId);
|
const response = await this.apiService.getProviderUsers(this.providerId);
|
||||||
response.data.forEach((u) => {
|
response.data.forEach((u) => {
|
||||||
const name = this.userNamePipe.transform(u);
|
const name = this.userNamePipe.transform(u);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import {
|
import {
|
||||||
combineLatest,
|
combineLatest,
|
||||||
firstValueFrom,
|
firstValueFrom,
|
||||||
@@ -17,9 +17,12 @@ import {
|
|||||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/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 { ProjectListView } from "../../models/view/project-list.view";
|
import { ProjectListView } from "../../models/view/project-list.view";
|
||||||
|
import { ProjectView } from "../../models/view/project.view";
|
||||||
import {
|
import {
|
||||||
BulkConfirmationDetails,
|
BulkConfirmationDetails,
|
||||||
BulkConfirmationDialogComponent,
|
BulkConfirmationDialogComponent,
|
||||||
@@ -55,6 +58,9 @@ export class ProjectsComponent implements OnInit {
|
|||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private router: Router,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -73,9 +79,53 @@ export class ProjectsComponent implements OnInit {
|
|||||||
)
|
)
|
||||||
)?.enabled;
|
)?.enabled;
|
||||||
|
|
||||||
return await this.getProjects();
|
const projects = await this.getProjects();
|
||||||
|
const viewEvents = this.route.snapshot.queryParams.viewEvents;
|
||||||
|
|
||||||
|
if (viewEvents) {
|
||||||
|
const targetProject = projects.find((project) => project.id === viewEvents);
|
||||||
|
|
||||||
|
const userIsAdmin = (
|
||||||
|
await firstValueFrom(
|
||||||
|
this.organizationService
|
||||||
|
.organizations$(userId)
|
||||||
|
.pipe(getOrganizationById(params.organizationId)),
|
||||||
|
)
|
||||||
|
)?.isAdmin;
|
||||||
|
|
||||||
|
// They would fall into here if they don't have access to a project, or if it has been permanently deleted.
|
||||||
|
if (!targetProject) {
|
||||||
|
//If they are an admin it was permanently deleted and we can show the events with project name redacted
|
||||||
|
if (userIsAdmin) {
|
||||||
|
this.openEventsDialogFromEntityId(
|
||||||
|
this.i18nService.t("nameUnavailableProjectDeleted", viewEvents),
|
||||||
|
params.organizationId,
|
||||||
|
viewEvents,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
//They aren't an admin so we don't know if they have access to it, lets show the unknown cipher toast.
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: null,
|
||||||
|
message: this.i18nService.t("unknownProject"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.openEventsDialog(targetProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.router.navigate([], {
|
||||||
|
queryParams: { search: this.search },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.route.snapshot.queryParams.search) {
|
||||||
|
this.search = this.route.snapshot.queryParams.search;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getProjects(): Promise<ProjectListView[]> {
|
private async getProjects(): Promise<ProjectListView[]> {
|
||||||
@@ -103,6 +153,30 @@ export class ProjectsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openEventsDialog = (project: ProjectView): DialogRef<void> =>
|
||||||
|
openEntityEventsDialog(this.dialogService, {
|
||||||
|
data: {
|
||||||
|
name: project.name,
|
||||||
|
organizationId: project.organizationId,
|
||||||
|
entityId: project.id,
|
||||||
|
entity: "project",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
openEventsDialogFromEntityId = (
|
||||||
|
headerName: string,
|
||||||
|
organizationId: string,
|
||||||
|
entityId: string,
|
||||||
|
): DialogRef<void> =>
|
||||||
|
openEntityEventsDialog(this.dialogService, {
|
||||||
|
data: {
|
||||||
|
name: headerName,
|
||||||
|
organizationId: organizationId,
|
||||||
|
entityId: entityId,
|
||||||
|
entity: "project",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
async openDeleteProjectDialog(projects: ProjectListView[]) {
|
async openDeleteProjectDialog(projects: ProjectListView[]) {
|
||||||
let projectsToDelete = projects;
|
let projectsToDelete = projects;
|
||||||
const readOnlyProjects = projects.filter((project) => project.write == false);
|
const readOnlyProjects = projects.filter((project) => project.write == false);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { combineLatestWith, firstValueFrom, Observable, startWith, switchMap } from "rxjs";
|
import { combineLatestWith, firstValueFrom, Observable, startWith, switchMap } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -13,7 +13,8 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogRef, DialogService, ToastService } from "@bitwarden/components";
|
||||||
|
import { openEntityEventsDialog } from "@bitwarden/web-vault/app/admin-console/organizations/manage/entity-events.component";
|
||||||
|
|
||||||
import { SecretListView } from "../models/view/secret-list.view";
|
import { SecretListView } from "../models/view/secret-list.view";
|
||||||
import { SecretsListComponent } from "../shared/secrets-list.component";
|
import { SecretsListComponent } from "../shared/secrets-list.component";
|
||||||
@@ -54,6 +55,8 @@ export class SecretsComponent implements OnInit {
|
|||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private router: Router,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -71,7 +74,53 @@ export class SecretsComponent implements OnInit {
|
|||||||
)
|
)
|
||||||
)?.enabled;
|
)?.enabled;
|
||||||
|
|
||||||
return await this.getSecrets();
|
const secrets = await this.getSecrets();
|
||||||
|
const viewEvents = this.route.snapshot.queryParams.viewEvents;
|
||||||
|
|
||||||
|
if (viewEvents) {
|
||||||
|
let targetSecret = secrets.find((secret) => secret.id === viewEvents);
|
||||||
|
|
||||||
|
const userIsAdmin = (
|
||||||
|
await firstValueFrom(
|
||||||
|
this.organizationService
|
||||||
|
.organizations$(userId)
|
||||||
|
.pipe(getOrganizationById(params.organizationId)),
|
||||||
|
)
|
||||||
|
)?.isAdmin;
|
||||||
|
|
||||||
|
// Secret might be deleted, make sure they are an admin before checking the trashed secrets
|
||||||
|
if (!targetSecret && userIsAdmin) {
|
||||||
|
targetSecret = (await this.secretService.getTrashedSecrets(this.organizationId)).find(
|
||||||
|
(e) => e.id == viewEvents,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// They would fall into here if they don't have access to a secret, or if it has been permanently deleted.
|
||||||
|
if (!targetSecret) {
|
||||||
|
//If they are an admin it was permanently deleted and we can show the events even though we don't have the secret name
|
||||||
|
if (userIsAdmin) {
|
||||||
|
this.openEventsDialogByEntityId(
|
||||||
|
this.i18nService.t("nameUnavailableSecretDeleted", viewEvents),
|
||||||
|
viewEvents,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
//They aren't an admin so we don't know if they have access to it, lets show the unknown secret toast.
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: null,
|
||||||
|
message: this.i18nService.t("unknownSecret"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.openEventsDialog(targetSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.router.navigate([], {
|
||||||
|
queryParams: { search: this.search },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return secrets;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -80,6 +129,26 @@ export class SecretsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openEventsDialogByEntityId = (secretName: string, secretId: string): DialogRef<void> =>
|
||||||
|
openEntityEventsDialog(this.dialogService, {
|
||||||
|
data: {
|
||||||
|
name: secretName,
|
||||||
|
organizationId: this.organizationId,
|
||||||
|
entityId: secretId,
|
||||||
|
entity: "secret",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
openEventsDialog = (secret: SecretListView): DialogRef<void> =>
|
||||||
|
openEntityEventsDialog(this.dialogService, {
|
||||||
|
data: {
|
||||||
|
name: secret.name,
|
||||||
|
organizationId: this.organizationId,
|
||||||
|
entityId: secret.id,
|
||||||
|
entity: "secret",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
private async getSecrets(): Promise<SecretListView[]> {
|
private async getSecrets(): Promise<SecretListView[]> {
|
||||||
return await this.secretService.getSecrets(this.organizationId);
|
return await this.secretService.getSecrets(this.organizationId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { Subject, takeUntil } from "rxjs";
|
import { takeUntil } from "rxjs";
|
||||||
|
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
@@ -25,7 +27,6 @@ export class ServiceAccountEventsComponent
|
|||||||
implements OnInit, OnDestroy
|
implements OnInit, OnDestroy
|
||||||
{
|
{
|
||||||
exportFileName = "machine-account-events";
|
exportFileName = "machine-account-events";
|
||||||
private destroy$ = new Subject<void>();
|
|
||||||
private serviceAccountId: string;
|
private serviceAccountId: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -38,6 +39,8 @@ export class ServiceAccountEventsComponent
|
|||||||
logService: LogService,
|
logService: LogService,
|
||||||
fileDownloadService: FileDownloadService,
|
fileDownloadService: FileDownloadService,
|
||||||
toastService: ToastService,
|
toastService: ToastService,
|
||||||
|
protected organizationService: OrganizationService,
|
||||||
|
protected accountService: AccountService,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
eventService,
|
eventService,
|
||||||
@@ -47,10 +50,14 @@ export class ServiceAccountEventsComponent
|
|||||||
logService,
|
logService,
|
||||||
fileDownloadService,
|
fileDownloadService,
|
||||||
toastService,
|
toastService,
|
||||||
|
route,
|
||||||
|
accountService,
|
||||||
|
organizationService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
this.initBase();
|
||||||
// eslint-disable-next-line rxjs/no-async-subscribe
|
// eslint-disable-next-line rxjs/no-async-subscribe
|
||||||
this.route.params.pipe(takeUntil(this.destroy$)).subscribe(async (params) => {
|
this.route.params.pipe(takeUntil(this.destroy$)).subscribe(async (params) => {
|
||||||
this.serviceAccountId = params.serviceAccountId;
|
this.serviceAccountId = params.serviceAccountId;
|
||||||
@@ -78,9 +85,4 @@ export class ServiceAccountEventsComponent
|
|||||||
email: "",
|
email: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
this.destroy$.next();
|
|
||||||
this.destroy$.complete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core
|
|||||||
import { Subject, takeUntil } from "rxjs";
|
import { Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
|
||||||
import { TableDataSource, ToastService } from "@bitwarden/components";
|
import { TableDataSource, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -49,7 +48,6 @@ export class ServiceAccountsListComponent implements OnDestroy {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
) {
|
) {
|
||||||
this.selection.changed
|
this.selection.changed
|
||||||
|
|||||||
@@ -114,6 +114,15 @@
|
|||||||
<i class="bwi bwi-fw bwi-trash tw-text-danger" aria-hidden="true"></i>
|
<i class="bwi bwi-fw bwi-trash tw-text-danger" aria-hidden="true"></i>
|
||||||
<span class="tw-text-danger">{{ "deleteProject" | i18n }}</span>
|
<span class="tw-text-danger">{{ "deleteProject" | i18n }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitMenuItem
|
||||||
|
*ngIf="viewEventsAllowed$ | async as allowed"
|
||||||
|
(click)="openEventsDialog(project)"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-fw bwi-billing" aria-hidden="true"></i>
|
||||||
|
<span> {{ "viewEvents" | i18n }} </span>
|
||||||
|
</button>
|
||||||
</bit-menu>
|
</bit-menu>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -1,21 +1,31 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { SelectionModel } from "@angular/cdk/collections";
|
import { SelectionModel } from "@angular/cdk/collections";
|
||||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
import { Component, EventEmitter, Input, Output, OnInit } from "@angular/core";
|
||||||
import { map } from "rxjs";
|
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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 { ProjectListView } from "../models/view/project-list.view";
|
import { ProjectListView } from "../models/view/project-list.view";
|
||||||
|
import { ProjectView } from "../models/view/project.view";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "sm-projects-list",
|
selector: "sm-projects-list",
|
||||||
templateUrl: "./projects-list.component.html",
|
templateUrl: "./projects-list.component.html",
|
||||||
standalone: false,
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class ProjectsListComponent {
|
export class ProjectsListComponent implements OnInit {
|
||||||
@Input()
|
@Input()
|
||||||
get projects(): ProjectListView[] {
|
get projects(): ProjectListView[] {
|
||||||
return this._projects;
|
return this._projects;
|
||||||
@@ -26,6 +36,9 @@ export class ProjectsListComponent {
|
|||||||
this.dataSource.data = projects;
|
this.dataSource.data = projects;
|
||||||
}
|
}
|
||||||
private _projects: ProjectListView[];
|
private _projects: ProjectListView[];
|
||||||
|
protected viewEventsAllowed$: Observable<boolean>;
|
||||||
|
protected isAdmin$: Observable<boolean>;
|
||||||
|
private destroy$: Subject<void> = new Subject<void>();
|
||||||
|
|
||||||
@Input() showMenus?: boolean = true;
|
@Input() showMenus?: boolean = true;
|
||||||
|
|
||||||
@@ -50,8 +63,41 @@ export class ProjectsListComponent {
|
|||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private organizationService: OrganizationService,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private accountService: AccountService,
|
||||||
|
private logService: LogService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
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$),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
isAllSelected() {
|
isAllSelected() {
|
||||||
if (this.selection.selected?.length > 0) {
|
if (this.selection.selected?.length > 0) {
|
||||||
const numSelected = this.selection.selected.length;
|
const numSelected = this.selection.selected.length;
|
||||||
@@ -87,6 +133,16 @@ export class ProjectsListComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openEventsDialog = (project: ProjectView): DialogRef<void> =>
|
||||||
|
openEntityEventsDialog(this.dialogService, {
|
||||||
|
data: {
|
||||||
|
name: project.name,
|
||||||
|
organizationId: project.organizationId,
|
||||||
|
entityId: project.id,
|
||||||
|
entity: "project",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
private selectedHasWriteAccess() {
|
private selectedHasWriteAccess() {
|
||||||
const selectedProjects = this.projects.filter((project) =>
|
const selectedProjects = this.projects.filter((project) =>
|
||||||
this.selection.isSelected(project.id),
|
this.selection.isSelected(project.id),
|
||||||
|
|||||||
@@ -148,6 +148,15 @@
|
|||||||
<i class="bwi bwi-fw bwi-refresh" aria-hidden="true"></i>
|
<i class="bwi bwi-fw bwi-refresh" aria-hidden="true"></i>
|
||||||
{{ "restoreSecret" | i18n }}
|
{{ "restoreSecret" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitMenuItem
|
||||||
|
*ngIf="viewEventsAllowed$ | async as allowed"
|
||||||
|
(click)="openEventsDialog(secret)"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-fw bwi-billing" aria-hidden="true"></i>
|
||||||
|
<span> {{ "viewEvents" | i18n }} </span>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
bitMenuItem
|
bitMenuItem
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { SelectionModel } from "@angular/cdk/collections";
|
import { SelectionModel } from "@angular/cdk/collections";
|
||||||
import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core";
|
import { Component, EventEmitter, Input, OnDestroy, Output, OnInit } from "@angular/core";
|
||||||
import { Subject, takeUntil } from "rxjs";
|
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { TableDataSource, ToastService } from "@bitwarden/components";
|
import { DialogRef, DialogService, TableDataSource, ToastService } from "@bitwarden/components";
|
||||||
|
import { openEntityEventsDialog } from "@bitwarden/web-vault/app/admin-console/organizations/manage/entity-events.component";
|
||||||
|
|
||||||
import { SecretListView } from "../models/view/secret-list.view";
|
import { SecretListView } from "../models/view/secret-list.view";
|
||||||
|
import { SecretView } from "../models/view/secret.view";
|
||||||
import { SecretService } from "../secrets/secret.service";
|
import { SecretService } from "../secrets/secret.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -17,7 +26,7 @@ import { SecretService } from "../secrets/secret.service";
|
|||||||
templateUrl: "./secrets-list.component.html",
|
templateUrl: "./secrets-list.component.html",
|
||||||
standalone: false,
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class SecretsListComponent implements OnDestroy {
|
export class SecretsListComponent implements OnDestroy, OnInit {
|
||||||
protected dataSource = new TableDataSource<SecretListView>();
|
protected dataSource = new TableDataSource<SecretListView>();
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
@@ -52,17 +61,51 @@ export class SecretsListComponent implements OnDestroy {
|
|||||||
private destroy$: Subject<void> = new Subject<void>();
|
private destroy$: Subject<void> = new Subject<void>();
|
||||||
|
|
||||||
selection = new SelectionModel<string>(true, []);
|
selection = new SelectionModel<string>(true, []);
|
||||||
|
protected viewEventsAllowed$: Observable<boolean>;
|
||||||
|
protected isAdmin$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private organizationService: OrganizationService,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private accountService: AccountService,
|
||||||
|
private logService: LogService,
|
||||||
) {
|
) {
|
||||||
this.selection.changed
|
this.selection.changed
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe((_) => this.onSecretCheckedEvent.emit(this.selection.selected));
|
.subscribe((_) => this.onSecretCheckedEvent.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 {
|
ngOnDestroy(): void {
|
||||||
this.destroy$.next();
|
this.destroy$.next();
|
||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
@@ -76,6 +119,15 @@ export class SecretsListComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
openEventsDialog = (secret: SecretView): DialogRef<void> =>
|
||||||
|
openEntityEventsDialog(this.dialogService, {
|
||||||
|
data: {
|
||||||
|
name: secret.name,
|
||||||
|
organizationId: secret.organizationId,
|
||||||
|
entityId: secret.id,
|
||||||
|
entity: "secret",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
toggleAll() {
|
toggleAll() {
|
||||||
if (this.isAllSelected()) {
|
if (this.isAllSelected()) {
|
||||||
|
|||||||
@@ -388,19 +388,23 @@ export abstract class ApiService {
|
|||||||
id: string,
|
id: string,
|
||||||
request: ProviderUserAcceptRequest,
|
request: ProviderUserAcceptRequest,
|
||||||
): Promise<any>;
|
): Promise<any>;
|
||||||
|
|
||||||
abstract postProviderUserConfirm(
|
abstract postProviderUserConfirm(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
id: string,
|
id: string,
|
||||||
request: ProviderUserConfirmRequest,
|
request: ProviderUserConfirmRequest,
|
||||||
): Promise<any>;
|
): Promise<any>;
|
||||||
|
|
||||||
abstract postProviderUsersPublicKey(
|
abstract postProviderUsersPublicKey(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
request: ProviderUserBulkRequest,
|
request: ProviderUserBulkRequest,
|
||||||
): Promise<ListResponse<ProviderUserBulkPublicKeyResponse>>;
|
): Promise<ListResponse<ProviderUserBulkPublicKeyResponse>>;
|
||||||
|
|
||||||
abstract postProviderUserBulkConfirm(
|
abstract postProviderUserBulkConfirm(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
request: ProviderUserBulkConfirmRequest,
|
request: ProviderUserBulkConfirmRequest,
|
||||||
): Promise<ListResponse<ProviderUserBulkResponse>>;
|
): Promise<ListResponse<ProviderUserBulkResponse>>;
|
||||||
|
|
||||||
abstract putProviderUser(
|
abstract putProviderUser(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
id: string,
|
id: string,
|
||||||
@@ -435,6 +439,21 @@ export abstract class ApiService {
|
|||||||
end: string,
|
end: string,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<ListResponse<EventResponse>>;
|
): Promise<ListResponse<EventResponse>>;
|
||||||
|
|
||||||
|
abstract getEventsSecret(
|
||||||
|
orgId: string,
|
||||||
|
id: string,
|
||||||
|
start: string,
|
||||||
|
end: string,
|
||||||
|
token: string,
|
||||||
|
): Promise<ListResponse<EventResponse>>;
|
||||||
|
abstract getEventsProject(
|
||||||
|
orgId: string,
|
||||||
|
id: string,
|
||||||
|
start: string,
|
||||||
|
end: string,
|
||||||
|
token: string,
|
||||||
|
): Promise<ListResponse<EventResponse>>;
|
||||||
abstract getEventsOrganization(
|
abstract getEventsOrganization(
|
||||||
id: string,
|
id: string,
|
||||||
start: string,
|
start: string,
|
||||||
|
|||||||
@@ -93,4 +93,11 @@ export enum EventType {
|
|||||||
Secret_Created = 2101,
|
Secret_Created = 2101,
|
||||||
Secret_Edited = 2102,
|
Secret_Edited = 2102,
|
||||||
Secret_Deleted = 2103,
|
Secret_Deleted = 2103,
|
||||||
|
Secret_Permanently_Deleted = 2104,
|
||||||
|
Secret_Restored = 2105,
|
||||||
|
|
||||||
|
Project_Retrieved = 2200,
|
||||||
|
Project_Created = 2201,
|
||||||
|
Project_Edited = 2202,
|
||||||
|
Project_Deleted = 2203,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export class EventResponse extends BaseResponse {
|
|||||||
systemUser: EventSystemUser;
|
systemUser: EventSystemUser;
|
||||||
domainName: string;
|
domainName: string;
|
||||||
secretId: string;
|
secretId: string;
|
||||||
|
projectId: string;
|
||||||
serviceAccountId: string;
|
serviceAccountId: string;
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
@@ -45,6 +46,7 @@ export class EventResponse extends BaseResponse {
|
|||||||
this.systemUser = this.getResponseProperty("SystemUser");
|
this.systemUser = this.getResponseProperty("SystemUser");
|
||||||
this.domainName = this.getResponseProperty("DomainName");
|
this.domainName = this.getResponseProperty("DomainName");
|
||||||
this.secretId = this.getResponseProperty("SecretId");
|
this.secretId = this.getResponseProperty("SecretId");
|
||||||
|
this.projectId = this.getResponseProperty("ProjectId");
|
||||||
this.serviceAccountId = this.getResponseProperty("ServiceAccountId");
|
this.serviceAccountId = this.getResponseProperty("ServiceAccountId");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1267,6 +1267,50 @@ export class ApiService implements ApiServiceAbstraction {
|
|||||||
return new ListResponse(r, EventResponse);
|
return new ListResponse(r, EventResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getEventsSecret(
|
||||||
|
orgId: string,
|
||||||
|
id: string,
|
||||||
|
start: string,
|
||||||
|
end: string,
|
||||||
|
token: string,
|
||||||
|
): Promise<ListResponse<EventResponse>> {
|
||||||
|
const r = await this.send(
|
||||||
|
"GET",
|
||||||
|
this.addEventParameters(
|
||||||
|
"/organization/" + orgId + "/secrets/" + id + "/events",
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return new ListResponse(r, EventResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEventsProject(
|
||||||
|
orgId: string,
|
||||||
|
id: string,
|
||||||
|
start: string,
|
||||||
|
end: string,
|
||||||
|
token: string,
|
||||||
|
): Promise<ListResponse<EventResponse>> {
|
||||||
|
const r = await this.send(
|
||||||
|
"GET",
|
||||||
|
this.addEventParameters(
|
||||||
|
"/organization/" + orgId + "/projects/" + id + "/events",
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
token,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return new ListResponse(r, EventResponse);
|
||||||
|
}
|
||||||
|
|
||||||
async getEventsOrganization(
|
async getEventsOrganization(
|
||||||
id: string,
|
id: string,
|
||||||
start: string,
|
start: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user