-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathevent-collection.service.ts
176 lines (149 loc) · 6.21 KB
/
event-collection.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { firstValueFrom, map, from, zip } 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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EventCollectionService as EventCollectionServiceAbstraction } from "../../abstractions/event/event-collection.service";
import { EventUploadService } from "../../abstractions/event/event-upload.service";
import { AuthService } from "../../auth/abstractions/auth.service";
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
import { EventType } from "../../enums";
import { EventData } from "../../models/data/event.data";
import { StateProvider } from "../../platform/state";
import { CipherService } from "../../vault/abstractions/cipher.service";
import { CipherView } from "../../vault/models/view/cipher.view";
import { EVENT_COLLECTION } from "./key-definitions";
export class EventCollectionService implements EventCollectionServiceAbstraction {
constructor(
private cipherService: CipherService,
private stateProvider: StateProvider,
private organizationService: OrganizationService,
private eventUploadService: EventUploadService,
private authService: AuthService,
private accountService: AccountService,
) {}
private getOrgIds = (orgs: Organization[]): string[] => {
return 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(getUserId(this.accountService.activeAccount$));
const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION);
if (!(await this.shouldUpdate(null, eventType, ciphers))) {
return;
}
const events$ = this.organizationService.organizations$(userId).pipe(
map((orgs) => this.getOrgIds(orgs)),
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
* @param eventType the event type to be added
* @param cipherId if provided the id of the cipher involved in the event
* @param uploadImmediately in some cases the recorded events should be uploaded right after being added
* @param organizationId the organizationId involved in the event. If the cipherId is not provided an organizationId is required
*/
async collect(
eventType: EventType,
cipherId: string = null,
uploadImmediately = false,
organizationId: string = null,
): Promise<any> {
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION);
if (!(await this.shouldUpdate(organizationId, eventType, undefined, cipherId))) {
return;
}
const event = new EventData();
event.type = eventType;
event.cipherId = cipherId;
event.date = new Date().toISOString();
event.organizationId = organizationId;
await eventStore.update((events) => {
events = events ?? [];
events.push(event);
return events;
});
if (uploadImmediately) {
await this.eventUploadService.uploadEvents();
}
}
/** Verifies if the event collection should be updated for the provided information
* @param cipherId the cipher for the event
* @param organizationId the organization for the event
*/
private async shouldUpdate(
organizationId: string = null,
eventType: EventType = null,
ciphers: CipherView[] = [],
cipherId?: string,
): Promise<boolean> {
const cipher$ = from(this.cipherService.get(cipherId));
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const orgIds$ = this.organizationService
.organizations$(userId)
.pipe(map((orgs) => this.getOrgIds(orgs)));
const [authStatus, orgIds, cipher] = await firstValueFrom(
zip(this.authService.activeAccountStatus$, orgIds$, cipher$),
);
// The user must be authorized
if (authStatus != AuthenticationStatus.Unlocked) {
return false;
}
// User must have organizations assigned to them
if (orgIds == null || orgIds.length == 0) {
return false;
}
// Individual vault export doesn't need cipher id or organization id.
if (eventType == EventType.User_ClientExportedVault) {
return true;
}
// If the cipherId was provided and a cipher exists, add it to the collection
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;
}
// If the input list of ciphers is provided. Check the ciphers to see if any
// are in the user's org list
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 (organizationId != null && !orgIds.includes(organizationId)) {
return false;
}
return true;
}
}