mirror of
https://github.com/bitwarden/browser
synced 2026-02-03 18:23:57 +00:00
Add calls to use SDK entirely for cipher sharing operation.
This commit is contained in:
@@ -2,7 +2,7 @@ import { UserKey } from "@bitwarden/common/types/key";
|
||||
import { EncryptionContext } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherListView } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { UserId, OrganizationId } from "../../types/guid";
|
||||
import { UserId, OrganizationId, CollectionId } from "../../types/guid";
|
||||
import { Cipher } from "../models/domain/cipher";
|
||||
import { AttachmentView } from "../models/view/attachment.view";
|
||||
import { CipherView } from "../models/view/cipher.view";
|
||||
@@ -33,6 +33,20 @@ export abstract class CipherEncryptionService {
|
||||
userId: UserId,
|
||||
): Promise<EncryptionContext | undefined>;
|
||||
|
||||
abstract share(
|
||||
model: CipherView,
|
||||
organizationId: OrganizationId,
|
||||
userId: UserId,
|
||||
collectionIds: CollectionId[],
|
||||
): Promise<Cipher | undefined>;
|
||||
|
||||
abstract shareMany(
|
||||
models: CipherView[],
|
||||
organizationId: OrganizationId,
|
||||
userId: UserId,
|
||||
collectionIds: CollectionId[],
|
||||
): Promise<Cipher[] | undefined>;
|
||||
|
||||
/**
|
||||
* Encrypts a cipher for a given userId with a new key for key rotation.
|
||||
* @param model The cipher view to encrypt
|
||||
|
||||
@@ -912,40 +912,33 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
|
||||
await this.adjustCipherHistory(cipher, userId, originalCipher);
|
||||
|
||||
let encCipher: EncryptionContext;
|
||||
if (sdkCipherEncryptionEnabled) {
|
||||
// The SDK does not expect the cipher to already have an organizationId. It will result in the wrong
|
||||
// cipher encryption key being used during the move to organization operation.
|
||||
if (cipher.organizationId != null) {
|
||||
throw new Error("Cipher is already associated with an organization.");
|
||||
}
|
||||
|
||||
encCipher = await this.cipherEncryptionService.moveToOrganization(
|
||||
return await this.cipherEncryptionService.share(
|
||||
cipher,
|
||||
organizationId as OrganizationId,
|
||||
userId,
|
||||
collectionIds as CollectionId[],
|
||||
);
|
||||
encCipher.cipher.collectionIds = collectionIds;
|
||||
} else {
|
||||
// This old attachment logic is safe to remove after it is replaced in PM-22750; which will require fixing
|
||||
// the attachment before sharing.
|
||||
const attachmentPromises: Promise<any>[] = [];
|
||||
if (cipher.attachments != null) {
|
||||
cipher.attachments.forEach((attachment) => {
|
||||
if (attachment.key == null) {
|
||||
attachmentPromises.push(
|
||||
this.shareAttachmentWithServer(attachment, cipher.id, organizationId),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
await Promise.all(attachmentPromises);
|
||||
|
||||
cipher.organizationId = organizationId;
|
||||
cipher.collectionIds = collectionIds;
|
||||
encCipher = await this.encryptSharedCipher(cipher, userId);
|
||||
}
|
||||
|
||||
// This old attachment logic is safe to remove after it is replaced in PM-22750; which will require fixing
|
||||
// the attachment before sharing.
|
||||
const attachmentPromises: Promise<any>[] = [];
|
||||
if (cipher.attachments != null) {
|
||||
cipher.attachments.forEach((attachment) => {
|
||||
if (attachment.key == null) {
|
||||
attachmentPromises.push(
|
||||
this.shareAttachmentWithServer(attachment, cipher.id, organizationId),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
await Promise.all(attachmentPromises);
|
||||
|
||||
cipher.organizationId = organizationId;
|
||||
cipher.collectionIds = collectionIds;
|
||||
const encCipher = await this.encryptSharedCipher(cipher, userId);
|
||||
|
||||
const request = new CipherShareRequest(encCipher);
|
||||
const response = await this.apiService.putShareCipher(cipher.id, request);
|
||||
const data = new CipherData(response, collectionIds);
|
||||
@@ -962,25 +955,17 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
const sdkCipherEncryptionEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM22136_SdkCipherEncryption,
|
||||
);
|
||||
const promises: Promise<any>[] = [];
|
||||
const encCiphers: Cipher[] = [];
|
||||
for (const cipher of ciphers) {
|
||||
if (sdkCipherEncryptionEnabled) {
|
||||
// The SDK does not expect the cipher to already have an organizationId. It will result in the wrong
|
||||
// cipher encryption key being used during the move to organization operation.
|
||||
if (cipher.organizationId != null) {
|
||||
throw new Error("Cipher is already associated with an organization.");
|
||||
}
|
||||
|
||||
promises.push(
|
||||
this.cipherEncryptionService
|
||||
.moveToOrganization(cipher, organizationId as OrganizationId, userId)
|
||||
.then((encCipher) => {
|
||||
encCipher.cipher.collectionIds = collectionIds;
|
||||
encCiphers.push(encCipher.cipher);
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
if (sdkCipherEncryptionEnabled) {
|
||||
return await this.cipherEncryptionService.shareMany(
|
||||
ciphers,
|
||||
organizationId as OrganizationId,
|
||||
userId,
|
||||
collectionIds as CollectionId[],
|
||||
);
|
||||
} else {
|
||||
const promises: Promise<any>[] = [];
|
||||
const encCiphers: Cipher[] = [];
|
||||
for (const cipher of ciphers) {
|
||||
cipher.organizationId = organizationId;
|
||||
cipher.collectionIds = collectionIds;
|
||||
promises.push(
|
||||
@@ -989,26 +974,26 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
const request = new CipherBulkShareRequest(encCiphers, collectionIds, userId);
|
||||
try {
|
||||
const response = await this.apiService.putShareCiphers(request);
|
||||
const responseMap = new Map(response.data.map((r) => [r.id, r]));
|
||||
await Promise.all(promises);
|
||||
const request = new CipherBulkShareRequest(encCiphers, collectionIds, userId);
|
||||
try {
|
||||
const response = await this.apiService.putShareCiphers(request);
|
||||
const responseMap = new Map(response.data.map((r) => [r.id, r]));
|
||||
|
||||
encCiphers.forEach((cipher) => {
|
||||
const matchingCipher = responseMap.get(cipher.id);
|
||||
if (matchingCipher) {
|
||||
cipher.revisionDate = new Date(matchingCipher.revisionDate);
|
||||
encCiphers.forEach((cipher) => {
|
||||
const matchingCipher = responseMap.get(cipher.id);
|
||||
if (matchingCipher) {
|
||||
cipher.revisionDate = new Date(matchingCipher.revisionDate);
|
||||
}
|
||||
});
|
||||
await this.upsert(encCiphers.map((c) => c.toCipherData()));
|
||||
} catch (e) {
|
||||
for (const cipher of ciphers) {
|
||||
cipher.organizationId = null;
|
||||
cipher.collectionIds = null;
|
||||
}
|
||||
});
|
||||
await this.upsert(encCiphers.map((c) => c.toCipherData()));
|
||||
} catch (e) {
|
||||
for (const cipher of ciphers) {
|
||||
cipher.organizationId = null;
|
||||
cipher.collectionIds = null;
|
||||
throw e;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
|
||||
import { LogService } from "../../platform/abstractions/log.service";
|
||||
import { SdkService, asUuid, uuidAsString } from "../../platform/abstractions/sdk/sdk.service";
|
||||
import { UserId, OrganizationId } from "../../types/guid";
|
||||
import { UserId, OrganizationId, CollectionId } from "../../types/guid";
|
||||
import { CipherEncryptionService } from "../abstractions/cipher-encryption.service";
|
||||
import { CipherType } from "../enums";
|
||||
import { Cipher } from "../models/domain/cipher";
|
||||
@@ -86,6 +86,76 @@ export class DefaultCipherEncryptionService implements CipherEncryptionService {
|
||||
);
|
||||
}
|
||||
|
||||
async shareMany(
|
||||
models: CipherView[],
|
||||
organizationId: OrganizationId,
|
||||
userId: UserId,
|
||||
collectionIds: CollectionId[],
|
||||
): Promise<Cipher[] | undefined> {
|
||||
return firstValueFrom(
|
||||
this.sdkService.userClient$(userId).pipe(
|
||||
map(async (sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
|
||||
using ref = sdk.take();
|
||||
const sdkCipherViews = models.map((model) => this.toSdkCipherView(model, ref.value));
|
||||
|
||||
const ciphers = await ref.value
|
||||
.vault()
|
||||
.ciphers()
|
||||
.share_ciphers_bulk(
|
||||
sdkCipherViews,
|
||||
asUuid(organizationId),
|
||||
collectionIds.map((id) => asUuid(id)),
|
||||
);
|
||||
|
||||
return ciphers.map((c) => Cipher.fromSdkCipher(c));
|
||||
}),
|
||||
catchError((error: unknown) => {
|
||||
this.logService.error(`Failed to move cipher to organization: ${error}`);
|
||||
return EMPTY;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async share(
|
||||
model: CipherView,
|
||||
organizationId: OrganizationId,
|
||||
userId: UserId,
|
||||
collectionIds: CollectionId[],
|
||||
): Promise<Cipher | undefined> {
|
||||
return firstValueFrom(
|
||||
this.sdkService.userClient$(userId).pipe(
|
||||
map(async (sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
|
||||
using ref = sdk.take();
|
||||
const sdkCipherView = this.toSdkCipherView(model, ref.value);
|
||||
|
||||
const movedCipher = await ref.value
|
||||
.vault()
|
||||
.ciphers()
|
||||
.share_cipher(
|
||||
sdkCipherView,
|
||||
asUuid(organizationId),
|
||||
collectionIds.map((id) => asUuid(id)),
|
||||
);
|
||||
|
||||
return Cipher.fromSdkCipher(movedCipher);
|
||||
}),
|
||||
catchError((error: unknown) => {
|
||||
this.logService.error(`Failed to move cipher to organization: ${error}`);
|
||||
return EMPTY;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async encryptCipherForRotation(
|
||||
model: CipherView,
|
||||
userId: UserId,
|
||||
|
||||
Reference in New Issue
Block a user