mirror of
https://github.com/bitwarden/browser
synced 2026-01-05 18:13:26 +00:00
[PM-24227] Enable TS-strict for Collection Domain models (#15765)
* wip ts-strict * wip ts-strict * wip * cleanup * cleanup * fix story * fix story * fix story * wip * clean up CollectionAdminView construction * fix deprecated function call * fix cli * clean up * fix story * wip * fix cli * requested changes * clean up, fixing minor bugs, more type saftey * assign props in static ctor, clean up
This commit is contained in:
@@ -9,13 +9,14 @@ import { CollectionId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { CollectionData, CollectionView } from "../models";
|
||||
|
||||
export const ENCRYPTED_COLLECTION_DATA_KEY = UserKeyDefinition.record<
|
||||
CollectionData | null,
|
||||
CollectionId
|
||||
>(COLLECTION_DISK, "collections", {
|
||||
deserializer: (jsonData: Jsonify<CollectionData | null>) => CollectionData.fromJSON(jsonData),
|
||||
clearOn: ["logout"],
|
||||
});
|
||||
export const ENCRYPTED_COLLECTION_DATA_KEY = UserKeyDefinition.record<CollectionData, CollectionId>(
|
||||
COLLECTION_DISK,
|
||||
"collections",
|
||||
{
|
||||
deserializer: (jsonData: Jsonify<CollectionData>) => CollectionData.fromJSON(jsonData),
|
||||
clearOn: ["logout"],
|
||||
},
|
||||
);
|
||||
|
||||
export const DECRYPTED_COLLECTION_DATA_KEY = new UserKeyDefinition<CollectionView[] | null>(
|
||||
COLLECTION_MEMORY,
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
import { combineLatest, firstValueFrom, from, map, Observable, of, switchMap } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { OrgKey } from "@bitwarden/common/types/key";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
@@ -36,12 +32,15 @@ export class DefaultCollectionAdminService implements CollectionAdminService {
|
||||
this.keyService.orgKeys$(userId),
|
||||
from(this.apiService.getManyCollectionsWithAccessDetails(organizationId)),
|
||||
]).pipe(
|
||||
switchMap(([orgKey, res]) => {
|
||||
switchMap(([orgKeys, res]) => {
|
||||
if (res?.data == null || res.data.length === 0) {
|
||||
return of([]);
|
||||
}
|
||||
if (orgKeys == null) {
|
||||
throw new Error("No org keys found.");
|
||||
}
|
||||
|
||||
return this.decryptMany(organizationId, res.data, orgKey);
|
||||
return this.decryptMany(organizationId, res.data, orgKeys);
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -104,55 +103,65 @@ export class DefaultCollectionAdminService implements CollectionAdminService {
|
||||
orgKeys: Record<OrganizationId, OrgKey>,
|
||||
): Promise<CollectionAdminView[]> {
|
||||
const promises = collections.map(async (c) => {
|
||||
const view = new CollectionAdminView();
|
||||
view.id = c.id;
|
||||
view.name = await this.encryptService.decryptString(
|
||||
new EncString(c.name),
|
||||
orgKeys[organizationId as OrganizationId],
|
||||
);
|
||||
view.externalId = c.externalId;
|
||||
view.organizationId = c.organizationId;
|
||||
|
||||
if (isCollectionAccessDetailsResponse(c)) {
|
||||
view.groups = c.groups;
|
||||
view.users = c.users;
|
||||
view.assigned = c.assigned;
|
||||
view.readOnly = c.readOnly;
|
||||
view.hidePasswords = c.hidePasswords;
|
||||
view.manage = c.manage;
|
||||
view.unmanaged = c.unmanaged;
|
||||
return CollectionAdminView.fromCollectionAccessDetails(
|
||||
c,
|
||||
this.encryptService,
|
||||
orgKeys[organizationId as OrganizationId],
|
||||
);
|
||||
}
|
||||
|
||||
return view;
|
||||
return await CollectionAdminView.fromCollectionResponse(
|
||||
c,
|
||||
this.encryptService,
|
||||
orgKeys[organizationId as OrganizationId],
|
||||
);
|
||||
});
|
||||
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
|
||||
private async encrypt(model: CollectionAdminView, userId: UserId): Promise<CollectionRequest> {
|
||||
if (model.organizationId == null) {
|
||||
if (!model.organizationId) {
|
||||
throw new Error("Collection has no organization id.");
|
||||
}
|
||||
|
||||
const key = await firstValueFrom(
|
||||
this.keyService
|
||||
.orgKeys$(userId)
|
||||
.pipe(map((orgKeys) => orgKeys[model.organizationId] ?? null)),
|
||||
this.keyService.orgKeys$(userId).pipe(
|
||||
map((orgKeys) => {
|
||||
if (!orgKeys) {
|
||||
throw new Error("No keys for the provided userId.");
|
||||
}
|
||||
|
||||
const key = orgKeys[model.organizationId];
|
||||
|
||||
if (key == null) {
|
||||
throw new Error("No key for this collection's organization.");
|
||||
}
|
||||
|
||||
return key;
|
||||
}),
|
||||
),
|
||||
);
|
||||
if (key == null) {
|
||||
throw new Error("No key for this collection's organization.");
|
||||
}
|
||||
const collection = new CollectionRequest();
|
||||
collection.externalId = model.externalId;
|
||||
collection.name = (await this.encryptService.encryptString(model.name, key)).encryptedString;
|
||||
collection.groups = model.groups.map(
|
||||
|
||||
const groups = model.groups.map(
|
||||
(group) =>
|
||||
new SelectionReadOnlyRequest(group.id, group.readOnly, group.hidePasswords, group.manage),
|
||||
);
|
||||
collection.users = model.users.map(
|
||||
|
||||
const users = model.users.map(
|
||||
(user) =>
|
||||
new SelectionReadOnlyRequest(user.id, user.readOnly, user.hidePasswords, user.manage),
|
||||
);
|
||||
return collection;
|
||||
|
||||
const collectionRequest = new CollectionRequest({
|
||||
name: await this.encryptService.encryptString(model.name, key),
|
||||
externalId: model.externalId,
|
||||
users,
|
||||
groups,
|
||||
});
|
||||
|
||||
return collectionRequest;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -390,9 +390,11 @@ const collectionDataFactory = (orgId?: OrganizationId) => {
|
||||
};
|
||||
|
||||
function collectionViewDataFactory(orgId?: OrganizationId): CollectionView {
|
||||
const collectionView = new CollectionView();
|
||||
collectionView.id = Utils.newGuid() as CollectionId;
|
||||
collectionView.organizationId = orgId ?? (Utils.newGuid() as OrganizationId);
|
||||
collectionView.name = "DEC_NAME_" + collectionView.id;
|
||||
const id = Utils.newGuid() as CollectionId;
|
||||
const collectionView = new CollectionView({
|
||||
id,
|
||||
organizationId: orgId ?? (Utils.newGuid() as OrganizationId),
|
||||
name: "DEC_NAME_" + id,
|
||||
});
|
||||
return collectionView;
|
||||
}
|
||||
|
||||
@@ -42,9 +42,7 @@ export class DefaultCollectionService implements CollectionService {
|
||||
/**
|
||||
* @returns a SingleUserState for encrypted collection data.
|
||||
*/
|
||||
private encryptedState(
|
||||
userId: UserId,
|
||||
): SingleUserState<Record<CollectionId, CollectionData | null>> {
|
||||
private encryptedState(userId: UserId): SingleUserState<Record<CollectionId, CollectionData>> {
|
||||
return this.stateProvider.getUser(userId, ENCRYPTED_COLLECTION_DATA_KEY);
|
||||
}
|
||||
|
||||
@@ -62,7 +60,7 @@ export class DefaultCollectionService implements CollectionService {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.values(collections).map((c) => new Collection(c));
|
||||
return Object.values(collections).map((c) => Collection.fromCollectionData(c));
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -110,8 +108,8 @@ export class DefaultCollectionService implements CollectionService {
|
||||
if (collections == null) {
|
||||
collections = {};
|
||||
}
|
||||
collections[toUpdate.id] = toUpdate;
|
||||
|
||||
collections[toUpdate.id] = toUpdate;
|
||||
return collections;
|
||||
});
|
||||
|
||||
@@ -121,7 +119,7 @@ export class DefaultCollectionService implements CollectionService {
|
||||
if (!orgKeys) {
|
||||
throw new Error("No key for this collection's organization.");
|
||||
}
|
||||
return this.decryptMany$([new Collection(toUpdate)], orgKeys);
|
||||
return this.decryptMany$([Collection.fromCollectionData(toUpdate)], orgKeys);
|
||||
}),
|
||||
),
|
||||
);
|
||||
@@ -177,10 +175,6 @@ export class DefaultCollectionService implements CollectionService {
|
||||
}
|
||||
|
||||
async encrypt(model: CollectionView, userId: UserId): Promise<Collection> {
|
||||
if (model.organizationId == null) {
|
||||
throw new Error("Collection has no organization id.");
|
||||
}
|
||||
|
||||
const key = await firstValueFrom(
|
||||
this.keyService.orgKeys$(userId).pipe(
|
||||
filter((orgKeys) => !!orgKeys),
|
||||
@@ -188,13 +182,7 @@ export class DefaultCollectionService implements CollectionService {
|
||||
),
|
||||
);
|
||||
|
||||
const collection = new Collection();
|
||||
collection.id = model.id;
|
||||
collection.organizationId = model.organizationId;
|
||||
collection.readOnly = model.readOnly;
|
||||
collection.externalId = model.externalId;
|
||||
collection.name = await this.encryptService.encryptString(model.name, key);
|
||||
return collection;
|
||||
return await model.encrypt(key, this.encryptService);
|
||||
}
|
||||
|
||||
// TODO: this should be private.
|
||||
@@ -211,7 +199,12 @@ export class DefaultCollectionService implements CollectionService {
|
||||
|
||||
collections.forEach((collection) => {
|
||||
decCollections.push(
|
||||
from(collection.decrypt(orgKeys[collection.organizationId as OrganizationId])),
|
||||
from(
|
||||
collection.decrypt(
|
||||
orgKeys[collection.organizationId as OrganizationId],
|
||||
this.encryptService,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -223,9 +216,8 @@ export class DefaultCollectionService implements CollectionService {
|
||||
getAllNested(collections: CollectionView[]): TreeNode<CollectionView>[] {
|
||||
const nodes: TreeNode<CollectionView>[] = [];
|
||||
collections.forEach((c) => {
|
||||
const collectionCopy = new CollectionView();
|
||||
collectionCopy.id = c.id;
|
||||
collectionCopy.organizationId = c.organizationId;
|
||||
const collectionCopy = Object.assign(new CollectionView({ ...c }), c);
|
||||
|
||||
const parts = c.name != null ? c.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
|
||||
ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, undefined, NestingDelimiter);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user