diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts index d629e4fe9fa..55879f68421 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk.service.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -5,6 +5,7 @@ import { BitwardenClient, Uuid } from "@bitwarden/sdk-internal"; import { UserId } from "../../../types/guid"; import { Rc } from "../../misc/reference-counting/rc"; import { Utils } from "../../misc/utils"; +import { UserKeyDefinition } from "../../state"; export class UserNotLoggedInError extends Error { constructor(userId: UserId) { @@ -77,3 +78,9 @@ export abstract class SdkService { */ abstract setClient(userId: UserId, client: BitwardenClient | undefined): void; } + +export interface SdkRecordMapper { + userKeyDefinition(): UserKeyDefinition>; + toSdk(value: ClientType): SdkType; + fromSdk(value: SdkType): ClientType; +} diff --git a/libs/common/src/platform/services/sdk/client-managed-state.ts b/libs/common/src/platform/services/sdk/client-managed-state.ts new file mode 100644 index 00000000000..c5514857dae --- /dev/null +++ b/libs/common/src/platform/services/sdk/client-managed-state.ts @@ -0,0 +1,59 @@ +import { firstValueFrom, map } from "rxjs"; + +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherRecordMapper } from "@bitwarden/common/vault/models/view/cipher.view"; +import { StateClient, Repository } from "@bitwarden/sdk-internal"; + +import { SdkRecordMapper } from "../../abstractions/sdk/sdk.service"; +import { StateProvider } from "../../state"; + +export async function initializeState( + userId: UserId, + stateClient: StateClient, + stateProvider: StateProvider, +): Promise { + await stateClient.initialize_state( + new RepositoryRecord(userId, stateProvider, new CipherRecordMapper()), + ); +} + +class RepositoryRecord implements Repository { + constructor( + private userId: UserId, + private stateProvider: StateProvider, + private mapper: SdkRecordMapper, + ) {} + + async get(id: string): Promise { + const prov = this.stateProvider.getUser(this.userId, this.mapper.userKeyDefinition()); + const data = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); + const element = data[id]; + if (!element) { + return null; + } + return this.mapper.toSdk(element); + } + + async list(): Promise { + const prov = this.stateProvider.getUser(this.userId, this.mapper.userKeyDefinition()); + const elements = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); + return Object.values(elements).map((element) => this.mapper.toSdk(element)); + } + + async set(id: string, value: SdkType): Promise { + const prov = this.stateProvider.getUser(this.userId, this.mapper.userKeyDefinition()); + const elements = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); + elements[id] = this.mapper.fromSdk(value); + await prov.update(() => elements); + } + + async remove(id: string): Promise { + const prov = this.stateProvider.getUser(this.userId, this.mapper.userKeyDefinition()); + const elements = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); + if (!elements[id]) { + return; + } + delete elements[id]; + await prov.update(() => elements); + } +} diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 3ced4667668..b28d4cffad7 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -12,21 +12,15 @@ import { of, takeWhile, throwIfEmpty, - firstValueFrom, } from "rxjs"; -import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; -import { ENCRYPTED_CIPHERS } from "@bitwarden/common/vault/services/key-state/ciphers.state"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key-management"; import { BitwardenClient, ClientSettings, - Cipher as SdkCipher, DeviceType as SdkDeviceType, - Repository as SdkRepository, } from "@bitwarden/sdk-internal"; import { EncryptedOrganizationKeyData } from "../../../admin-console/models/data/encrypted-organization-key.data"; @@ -42,7 +36,9 @@ import { SdkService, UserNotLoggedInError } from "../../abstractions/sdk/sdk.ser import { compareValues } from "../../misc/compare-values"; import { Rc } from "../../misc/reference-counting/rc"; import { EncryptedString } from "../../models/domain/enc-string"; -import { StateProvider, UserKeyDefinition } from "../../state"; +import { StateProvider } from "../../state"; + +import { initializeState } from "./client-managed-state"; // A symbol that represents an overriden client that is explicitly set to undefined, // blocking the creation of an internal client for that user. @@ -234,17 +230,8 @@ export class DefaultSdkService implements SdkService { // This is optional to avoid having to mock it on the server if (this.stateProvider) { - client - .platform() - .state() - .register_cipher_repository( - new RepositoryRecordImpl( - userId, - this.stateProvider, - ENCRYPTED_CIPHERS, - new CipherMapper(), - ), - ); + // Initialize the SDK managed database and the client managed repositories. + await initializeState(userId, client.platform().state(), this.stateProvider); } } @@ -314,60 +301,3 @@ export class DefaultSdkService implements SdkService { } } } - -interface SdkMapper { - toSdk(value: ClientType): SdkType; - fromSdk(value: SdkType): ClientType; -} - -class RepositoryRecordImpl implements SdkRepository { - constructor( - private userId: UserId, - private stateProvider: StateProvider, - private userKeyDefinition: UserKeyDefinition>, - private mapper: SdkMapper, - ) {} - - async get(id: string): Promise { - const prov = this.stateProvider.getUser(this.userId, this.userKeyDefinition); - const data = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); - - const cipher = data[id]; - if (!cipher) { - return null; - } - - return this.mapper.toSdk(cipher); - } - async list(): Promise { - const prov = this.stateProvider.getUser(this.userId, this.userKeyDefinition); - const ciphers = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); - - return Object.values(ciphers).map((cipher) => this.mapper.toSdk(cipher)); - } - async set(id: string, value: SdkType): Promise { - const prov = this.stateProvider.getUser(this.userId, this.userKeyDefinition); - const ciphers = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); - ciphers[id] = this.mapper.fromSdk(value); - await prov.update(() => ciphers); - } - async remove(id: string): Promise { - const prov = this.stateProvider.getUser(this.userId, this.userKeyDefinition); - const ciphers = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); - if (!ciphers[id]) { - return; - } - delete ciphers[id]; - await prov.update(() => ciphers); - } -} - -class CipherMapper implements SdkMapper { - toSdk(value: CipherData): SdkCipher { - return new Cipher(value).toSdkCipher(); - } - - fromSdk(value: SdkCipher): CipherData { - throw new Error("Cipher.fromSdk is not implemented yet"); - } -} diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index e182025a332..d455416405a 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -1,6 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { CipherView as SdkCipherView } from "@bitwarden/sdk-internal"; +import { + SdkRecordMapper, + uuidToString, +} from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; +import { UserKeyDefinition } from "@bitwarden/common/platform/state"; +import { CipherView as SdkCipherView, Cipher as SdkCipher } from "@bitwarden/sdk-internal"; import { View } from "../../../models/view/view"; import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface"; @@ -8,7 +13,9 @@ import { InitializerKey } from "../../../platform/services/cryptography/initiali import { DeepJsonify } from "../../../types/deep-jsonify"; import { CipherType, LinkedIdType } from "../../enums"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; +import { ENCRYPTED_CIPHERS } from "../../services/key-state/ciphers.state"; import { CipherPermissionsApi } from "../api/cipher-permissions.api"; +import { CipherData } from "../data/cipher.data"; import { LocalData } from "../data/local.data"; import { Cipher } from "../domain/cipher"; @@ -21,6 +28,21 @@ import { PasswordHistoryView } from "./password-history.view"; import { SecureNoteView } from "./secure-note.view"; import { SshKeyView } from "./ssh-key.view"; +// TODO: This should probably be moved to a better place +export class CipherRecordMapper implements SdkRecordMapper { + userKeyDefinition(): UserKeyDefinition> { + return ENCRYPTED_CIPHERS; + } + + toSdk(value: CipherData): SdkCipher { + return new Cipher(value).toSdkCipher(); + } + + fromSdk(value: SdkCipher): CipherData { + throw new Error("Cipher.fromSdk is not implemented yet"); + } +} + export class CipherView implements View, InitializerMetadata { readonly initializerKey = InitializerKey.CipherView; @@ -234,9 +256,9 @@ export class CipherView implements View, InitializerMetadata { } const cipherView = new CipherView(); - cipherView.id = obj.id ?? null; - cipherView.organizationId = obj.organizationId ?? null; - cipherView.folderId = obj.folderId ?? null; + cipherView.id = obj.id ? uuidToString(obj.id) : null; + cipherView.organizationId = obj.organizationId ? uuidToString(obj.organizationId) : null; + cipherView.folderId = obj.folderId ? uuidToString(obj.folderId) : null; cipherView.name = obj.name; cipherView.notes = obj.notes ?? null; cipherView.type = obj.type; @@ -260,7 +282,7 @@ export class CipherView implements View, InitializerMetadata { cipherView.fields = obj.fields?.map((f) => FieldView.fromSdkFieldView(f)) ?? null; cipherView.passwordHistory = obj.passwordHistory?.map((ph) => PasswordHistoryView.fromSdkPasswordHistoryView(ph)) ?? null; - cipherView.collectionIds = obj.collectionIds ?? null; + cipherView.collectionIds = obj.collectionIds.map((id) => uuidToString(id)) ?? null; cipherView.revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); cipherView.creationDate = obj.creationDate == null ? null : new Date(obj.creationDate); cipherView.deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate);