1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-26 17:43:22 +00:00

updated changes to match SDK changes

This commit is contained in:
gbubemismith
2026-02-25 15:06:15 -05:00
parent 0043dba455
commit c3f3b6a4d9
5 changed files with 78 additions and 28 deletions

View File

@@ -1,5 +1,7 @@
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherListView } from "@bitwarden/sdk-internal";
/**
* Result of decrypting all ciphers, containing both successes and failures.
@@ -126,16 +128,17 @@ export abstract class CipherSdkService {
abstract getAllDecrypted(userId: UserId): Promise<DecryptAllCiphersResult>;
/**
* Fetches and decrypts all ciphers for an organization from the API using the SDK.
* Fetches all ciphers for an organization from the API using the SDK.
* Returns encrypted ciphers for on-demand decryption and lightweight list views for display.
*
* @param organizationId The organization ID to fetch ciphers for
* @param userId The user ID to use for SDK client
* @param includeMemberItems Whether to include member items
* @returns A promise that resolves to the decrypted cipher views
* @returns A promise that resolves to the encrypted ciphers and decrypted list views
*/
abstract getAllFromApiForOrganization(
organizationId: string,
userId: UserId,
includeMemberItems: boolean,
): Promise<CipherView[]>;
): Promise<[Cipher[], CipherListView[]]>;
}

View File

@@ -7,6 +7,7 @@ import { UserId, CipherId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherType } from "../enums/cipher-type";
import { Cipher } from "../models/domain/cipher";
import { DefaultCipherSdkService } from "./cipher-sdk.service";
@@ -34,7 +35,7 @@ describe("DefaultCipherSdkService", () => {
soft_delete_many: jest.fn().mockResolvedValue(undefined),
restore: jest.fn().mockResolvedValue(undefined),
restore_many: jest.fn().mockResolvedValue(undefined),
list_org_ciphers: jest.fn().mockResolvedValue({ successes: [], failures: [] }),
list_org_ciphers: jest.fn().mockResolvedValue({ ciphers: [], listViews: [] }),
};
mockCiphersSdk = {
create: jest.fn(),
@@ -613,12 +614,40 @@ describe("DefaultCipherSdkService", () => {
});
describe("getAllFromApiForOrganization()", () => {
const mockSdkCipher: any = {
id: cipherId,
name: "2.encryptedName|iv|data",
type: CipherType.Login,
organizationId: orgId,
folderId: null,
favorite: false,
edit: true,
viewPassword: true,
organizationUseTotp: false,
revisionDate: new Date().toISOString(),
creationDate: new Date().toISOString(),
collectionIds: [],
deletedDate: null,
reprompt: 0,
key: null,
localData: null,
attachments: null,
fields: null,
passwordHistory: null,
notes: null,
login: null,
secureNote: null,
card: null,
identity: null,
sshKey: null,
permissions: null,
};
it("should list organization ciphers using SDK admin API", async () => {
const mockSdkCipherView = new CipherView().toSdkCipherView();
mockSdkCipherView.name = "Org Cipher";
const mockListView: any = { id: cipherId, name: "Org Cipher" };
mockAdminSdk.list_org_ciphers.mockResolvedValue({
successes: [mockSdkCipherView],
failures: [],
ciphers: [mockSdkCipher],
listViews: [mockListView],
});
const result = await cipherSdkService.getAllFromApiForOrganization(orgId, userId, false);
@@ -627,14 +656,16 @@ describe("DefaultCipherSdkService", () => {
expect(mockVaultSdk.ciphers).toHaveBeenCalled();
expect(mockCiphersSdk.admin).toHaveBeenCalled();
expect(mockAdminSdk.list_org_ciphers).toHaveBeenCalledWith(orgId, false);
expect(result).toHaveLength(1);
expect(result[0]).toBeInstanceOf(CipherView);
const [ciphers, listViews] = result;
expect(ciphers).toHaveLength(1);
expect(ciphers[0]).toBeInstanceOf(Cipher);
expect(listViews).toHaveLength(1);
});
it("should pass includeMemberItems parameter to SDK", async () => {
mockAdminSdk.list_org_ciphers.mockResolvedValue({
successes: [],
failures: [],
ciphers: [],
listViews: [],
});
await cipherSdkService.getAllFromApiForOrganization(orgId, userId, true);

View File

@@ -5,7 +5,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { SdkService, asUuid } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherView as SdkCipherView } from "@bitwarden/sdk-internal";
import { CipherListView, CipherView as SdkCipherView } from "@bitwarden/sdk-internal";
import { CipherSdkService, DecryptAllCiphersResult } from "../abstractions/cipher-sdk.service";
import { Cipher } from "../models/domain/cipher";
@@ -299,7 +299,7 @@ export class DefaultCipherSdkService implements CipherSdkService {
organizationId: string,
userId: UserId,
includeMemberItems: boolean,
): Promise<CipherView[]> {
): Promise<[Cipher[], CipherListView[]]> {
return await firstValueFrom(
this.sdkService.userClient$(userId).pipe(
switchMap(async (sdk) => {
@@ -308,15 +308,17 @@ export class DefaultCipherSdkService implements CipherSdkService {
}
using ref = sdk.take();
const decryptResult = await ref.value
const result = await ref.value
.vault()
.ciphers()
.admin()
.list_org_ciphers(asUuid(organizationId), includeMemberItems);
return decryptResult.successes
.map((sdkCipherView: any) => CipherView.fromSdkCipherView(sdkCipherView))
.filter((v): v is CipherView => v !== undefined);
const ciphers = result.ciphers
.map((c) => Cipher.fromSdkCipher(c))
.filter((c): c is Cipher => c !== undefined);
return [ciphers, result.listViews] as [Cipher[], CipherListView[]];
}),
catchError((error: unknown) => {
this.logService.error(`Failed to list organization ciphers: ${error}`);

View File

@@ -1280,6 +1280,9 @@ describe("Cipher Service", () => {
it("should use SDK to list organization ciphers when feature flag is enabled", async () => {
sdkCrudFeatureFlag$.next(true);
const mockCipher1 = new Cipher(cipherData);
const mockCipher2 = new Cipher(cipherData);
const mockCipherView1 = new CipherView();
mockCipherView1.name = "Test Cipher 1";
const mockCipherView2 = new CipherView();
@@ -1287,7 +1290,12 @@ describe("Cipher Service", () => {
const sdkServiceSpy = jest
.spyOn(cipherSdkService, "getAllFromApiForOrganization")
.mockResolvedValue([mockCipherView1, mockCipherView2]);
.mockResolvedValue([[mockCipher1, mockCipher2], []]);
cipherEncryptionService.decryptManyLegacy.mockResolvedValue([
[mockCipherView1, mockCipherView2],
[],
]);
const apiSpy = jest.spyOn(apiService, "getCiphersOrganization");
@@ -1295,6 +1303,10 @@ describe("Cipher Service", () => {
expect(sdkServiceSpy).toHaveBeenCalledWith(testOrgId, mockUserId, true);
expect(apiSpy).not.toHaveBeenCalled();
expect(cipherEncryptionService.decryptManyLegacy).toHaveBeenCalledWith(
[mockCipher1, mockCipher2],
mockUserId,
);
expect(result).toHaveLength(2);
expect(result[0]).toBeInstanceOf(CipherView);
expect(result[1]).toBeInstanceOf(CipherView);
@@ -1305,7 +1317,9 @@ describe("Cipher Service", () => {
const sdkServiceSpy = jest
.spyOn(cipherSdkService, "getAllFromApiForOrganization")
.mockResolvedValue([]);
.mockResolvedValue([[], []]);
cipherEncryptionService.decryptManyLegacy.mockResolvedValue([[], []]);
const apiSpy = jest.spyOn(apiService, "getCiphersOrganization");
@@ -1323,9 +1337,7 @@ describe("Cipher Service", () => {
});
it("should use SDK to list and decrypt ciphers when feature flag is enabled", async () => {
configService.getFeatureFlag
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
.mockResolvedValue(true);
sdkCrudFeatureFlag$.next(true);
const mockCipherView1 = new CipherView();
mockCipherView1.name = "Test Cipher 1";

View File

@@ -788,12 +788,14 @@ export class CipherService implements CipherServiceAbstraction {
}
try {
const cipherViews = await this.cipherSdkService.getAllFromApiForOrganization(
const [ciphers] = await this.cipherSdkService.getAllFromApiForOrganization(
organizationId,
userId,
includeMemberItems,
);
const [cipherViews] = await this.cipherEncryptionService.decryptManyLegacy(ciphers, userId);
// Sort by locale (matching existing behavior)
cipherViews.sort(this.getLocaleSortingFunction());
@@ -975,7 +977,7 @@ export class CipherService implements CipherServiceAbstraction {
}
const encrypted = await this.encrypt(cipherView, userId);
const result = await this.createWithServer_legacy(encrypted, orgAdmin);
const result = await this.createWithServerLegacy(encrypted, orgAdmin);
return await this.decrypt(result, userId);
}
@@ -993,7 +995,7 @@ export class CipherService implements CipherServiceAbstraction {
return resultCipherView;
}
private async createWithServer_legacy(
private async createWithServerLegacy(
{ cipher, encryptedFor }: EncryptionContext,
orgAdmin?: boolean,
): Promise<Cipher> {
@@ -1032,7 +1034,7 @@ export class CipherService implements CipherServiceAbstraction {
}
const encrypted = await this.encrypt(cipherView, userId);
const updatedCipher = await this.updateWithServer_legacy(encrypted, orgAdmin);
const updatedCipher = await this.updateWithServerLegacy(encrypted, orgAdmin);
const updatedCipherView = await this.decrypt(updatedCipher, userId);
return updatedCipherView;
}
@@ -1053,7 +1055,7 @@ export class CipherService implements CipherServiceAbstraction {
return resultCipherView;
}
async updateWithServer_legacy(
async updateWithServerLegacy(
{ cipher, encryptedFor }: EncryptionContext,
orgAdmin?: boolean,
): Promise<Cipher> {