1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 14:53:33 +00:00

[PM-5957] CLI - List items long runtime (#9589)

* Initial checking of collect many

* should update to better handle parameters

* cleaning up event collection params

* Adding documentation

* Removing commented out code saved for testing

* Adding pr changes and using the account service for event collection user id

* browser main.background event collection service needed the account service
This commit is contained in:
Tom
2024-06-27 08:44:43 -04:00
committed by GitHub
parent 0fc489cfa0
commit 98c6cc4a7e
6 changed files with 80 additions and 26 deletions

View File

@@ -869,6 +869,7 @@ export default class MainBackground {
this.organizationService, this.organizationService,
this.eventUploadService, this.eventUploadService,
this.authService, this.authService,
this.accountService,
); );
this.totpService = new TotpService(this.cryptoFunctionService, this.logService); this.totpService = new TotpService(this.cryptoFunctionService, this.logService);

View File

@@ -128,17 +128,7 @@ export class ListCommand {
ciphers = this.searchService.searchCiphersBasic(ciphers, options.search, options.trash); ciphers = this.searchService.searchCiphersBasic(ciphers, options.search, options.trash);
} }
for (let i = 0; i < ciphers.length; i++) { await this.eventCollectionService.collectMany(EventType.Cipher_ClientViewed, ciphers, true);
const c = ciphers[i];
// Set upload immediately on the last item in the ciphers collection to avoid the event collection
// service from uploading each time.
await this.eventCollectionService.collect(
EventType.Cipher_ClientViewed,
c.id,
i === ciphers.length - 1,
c.organizationId,
);
}
const res = new ListResponse(ciphers.map((o) => new CipherResponse(o))); const res = new ListResponse(ciphers.map((o) => new CipherResponse(o)));
return Response.success(res); return Response.success(res);

View File

@@ -729,6 +729,7 @@ export class ServiceContainer {
this.organizationService, this.organizationService,
this.eventUploadService, this.eventUploadService,
this.authService, this.authService,
this.accountService,
); );
this.providerApiService = new ProviderApiService(this.apiService); this.providerApiService = new ProviderApiService(this.apiService);

View File

@@ -830,6 +830,7 @@ const safeProviders: SafeProvider[] = [
OrganizationServiceAbstraction, OrganizationServiceAbstraction,
EventUploadServiceAbstraction, EventUploadServiceAbstraction,
AuthServiceAbstraction, AuthServiceAbstraction,
AccountServiceAbstraction,
], ],
}), }),
safeProvider({ safeProvider({

View File

@@ -1,6 +1,12 @@
import { EventType } from "../../enums"; import { EventType } from "../../enums";
import { CipherView } from "../../vault/models/view/cipher.view";
export abstract class EventCollectionService { export abstract class EventCollectionService {
collectMany: (
eventType: EventType,
ciphers: CipherView[],
uploadImmediately?: boolean,
) => Promise<any>;
collect: ( collect: (
eventType: EventType, eventType: EventType,
cipherId?: string, cipherId?: string,

View File

@@ -1,25 +1,76 @@
import { firstValueFrom, map, from, zip } from "rxjs"; import { firstValueFrom, map, from, zip, Observable } from "rxjs";
import { EventCollectionService as EventCollectionServiceAbstraction } from "../../abstractions/event/event-collection.service"; import { EventCollectionService as EventCollectionServiceAbstraction } from "../../abstractions/event/event-collection.service";
import { EventUploadService } from "../../abstractions/event/event-upload.service"; import { EventUploadService } from "../../abstractions/event/event-upload.service";
import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "../../auth/abstractions/account.service";
import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthService } from "../../auth/abstractions/auth.service";
import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { AuthenticationStatus } from "../../auth/enums/authentication-status";
import { EventType } from "../../enums"; import { EventType } from "../../enums";
import { EventData } from "../../models/data/event.data"; import { EventData } from "../../models/data/event.data";
import { StateProvider } from "../../platform/state"; import { StateProvider } from "../../platform/state";
import { CipherService } from "../../vault/abstractions/cipher.service"; import { CipherService } from "../../vault/abstractions/cipher.service";
import { CipherView } from "../../vault/models/view/cipher.view";
import { EVENT_COLLECTION } from "./key-definitions"; import { EVENT_COLLECTION } from "./key-definitions";
export class EventCollectionService implements EventCollectionServiceAbstraction { export class EventCollectionService implements EventCollectionServiceAbstraction {
private orgIds$: Observable<string[]>;
constructor( constructor(
private cipherService: CipherService, private cipherService: CipherService,
private stateProvider: StateProvider, private stateProvider: StateProvider,
private organizationService: OrganizationService, private organizationService: OrganizationService,
private eventUploadService: EventUploadService, private eventUploadService: EventUploadService,
private authService: AuthService, private authService: AuthService,
) {} private accountService: AccountService,
) {
this.orgIds$ = this.organizationService.organizations$.pipe(
map((orgs) => orgs?.filter((o) => o.useEvents)?.map((x) => x.id) ?? []),
);
}
/** Adds an event to the active user's event collection
* @param eventType the event type to be added
* @param ciphers The collection of ciphers to log events for
* @param uploadImmediately in some cases the recorded events should be uploaded right after being added
*/
async collectMany(
eventType: EventType,
ciphers: CipherView[],
uploadImmediately = false,
): Promise<any> {
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION);
if (!(await this.shouldUpdate(null, eventType, ciphers))) {
return;
}
const events$ = this.orgIds$.pipe(
map((orgs) =>
ciphers
.filter((c) => orgs.includes(c.organizationId))
.map((c) => ({
type: eventType,
cipherId: c.id,
date: new Date().toISOString(),
organizationId: c.organizationId,
})),
),
);
await eventStore.update(
(currentEvents, newEvents) => [...(currentEvents ?? []), ...newEvents],
{
combineLatestWith: events$,
},
);
if (uploadImmediately) {
await this.eventUploadService.uploadEvents();
}
}
/** Adds an event to the active user's event collection /** Adds an event to the active user's event collection
* @param eventType the event type to be added * @param eventType the event type to be added
@@ -33,10 +84,10 @@ export class EventCollectionService implements EventCollectionServiceAbstraction
uploadImmediately = false, uploadImmediately = false,
organizationId: string = null, organizationId: string = null,
): Promise<any> { ): Promise<any> {
const userId = await firstValueFrom(this.stateProvider.activeUserId$); const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION); const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION);
if (!(await this.shouldUpdate(cipherId, organizationId, eventType))) { if (!(await this.shouldUpdate(organizationId, eventType, undefined, cipherId))) {
return; return;
} }
@@ -62,18 +113,15 @@ export class EventCollectionService implements EventCollectionServiceAbstraction
* @param organizationId the organization for the event * @param organizationId the organization for the event
*/ */
private async shouldUpdate( private async shouldUpdate(
cipherId: string = null,
organizationId: string = null, organizationId: string = null,
eventType: EventType = null, eventType: EventType = null,
ciphers: CipherView[] = [],
cipherId?: string,
): Promise<boolean> { ): Promise<boolean> {
const orgIds$ = this.organizationService.organizations$.pipe(
map((orgs) => orgs?.filter((o) => o.useEvents)?.map((x) => x.id) ?? []),
);
const cipher$ = from(this.cipherService.get(cipherId)); const cipher$ = from(this.cipherService.get(cipherId));
const [authStatus, orgIds, cipher] = await firstValueFrom( const [authStatus, orgIds, cipher] = await firstValueFrom(
zip(this.authService.activeAccountStatus$, orgIds$, cipher$), zip(this.authService.activeAccountStatus$, this.orgIds$, cipher$),
); );
// The user must be authorized // The user must be authorized
@@ -91,14 +139,21 @@ export class EventCollectionService implements EventCollectionServiceAbstraction
return true; return true;
} }
// If the cipher is null there must be an organization id provided // If the cipherId was provided and a cipher exists, add it to the collection
if (cipher == null && organizationId == null) { if (cipher != null) {
ciphers.push(new CipherView(cipher));
}
// If no ciphers there must be an organization id provided
if ((ciphers == null || ciphers.length == 0) && organizationId == null) {
return false; return false;
} }
// If the cipher is present it must be in the user's org list // If the input list of ciphers is provided. Check the ciphers to see if any
if (cipher != null && !orgIds.includes(cipher?.organizationId)) { // are in the user's org list
return false; if (ciphers != null && ciphers.length > 0) {
const filtered = ciphers.filter((c) => orgIds.includes(c.organizationId));
return filtered.length > 0;
} }
// If the organization id is provided it must be in the user's org list // If the organization id is provided it must be in the user's org list