mirror of
https://github.com/bitwarden/browser
synced 2026-01-28 15:23:53 +00:00
Move getCipher functions to cipher-sdk.service.ts when using SDK flag
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
/**
|
||||
* Result of decrypting all ciphers, containing both successes and failures.
|
||||
*/
|
||||
export interface DecryptAllCiphersResult {
|
||||
/** Successfully decrypted cipher views */
|
||||
successes: CipherView[];
|
||||
/** Cipher views that failed to decrypt (with decryptionFailure flag set) */
|
||||
failures: CipherView[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Service responsible for cipher operations using the SDK.
|
||||
*/
|
||||
@@ -106,4 +116,26 @@ export abstract class CipherSdkService {
|
||||
* @returns A promise that resolves when the ciphers are restored
|
||||
*/
|
||||
abstract restoreManyWithServer(ids: string[], userId: UserId, orgId?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Lists and decrypts all ciphers from state using the SDK.
|
||||
*
|
||||
* @param userId The user ID to use for SDK client
|
||||
* @returns A promise that resolves to the decrypt result containing successes and failures
|
||||
*/
|
||||
abstract getAllDecrypted(userId: UserId): Promise<DecryptAllCiphersResult>;
|
||||
|
||||
/**
|
||||
* Fetches and decrypts all ciphers for an organization from the API using the SDK.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
abstract getAllFromApiForOrganization(
|
||||
organizationId: string,
|
||||
userId: UserId,
|
||||
includeMemberItems: boolean,
|
||||
): Promise<CipherView[]>;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,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: [] }),
|
||||
};
|
||||
mockCiphersSdk = {
|
||||
create: jest.fn(),
|
||||
@@ -44,6 +45,7 @@ describe("DefaultCipherSdkService", () => {
|
||||
soft_delete_many: jest.fn().mockResolvedValue(undefined),
|
||||
restore: jest.fn().mockResolvedValue(undefined),
|
||||
restore_many: jest.fn().mockResolvedValue(undefined),
|
||||
list: jest.fn().mockResolvedValue({ successes: [], failures: [] }),
|
||||
admin: jest.fn().mockReturnValue(mockAdminSdk),
|
||||
};
|
||||
mockVaultSdk = {
|
||||
@@ -531,4 +533,135 @@ describe("DefaultCipherSdkService", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAllDecrypted()", () => {
|
||||
it("should list and decrypt ciphers using SDK", async () => {
|
||||
const mockSdkCipherView = new CipherView().toSdkCipherView();
|
||||
mockSdkCipherView.name = "Test Cipher";
|
||||
mockCiphersSdk.list.mockResolvedValue({
|
||||
successes: [mockSdkCipherView],
|
||||
failures: [],
|
||||
});
|
||||
|
||||
const result = await cipherSdkService.getAllDecrypted(userId);
|
||||
|
||||
expect(sdkService.userClient$).toHaveBeenCalledWith(userId);
|
||||
expect(mockVaultSdk.ciphers).toHaveBeenCalled();
|
||||
expect(mockCiphersSdk.list).toHaveBeenCalled();
|
||||
expect(result.successes).toHaveLength(1);
|
||||
expect(result.successes[0]).toBeInstanceOf(CipherView);
|
||||
expect(result.failures).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should return failures with decryptionFailure flag set", async () => {
|
||||
// Create a minimal mock that matches what fromSdkCipher expects
|
||||
const mockFailedCipher: any = {
|
||||
id: cipherId,
|
||||
name: "2.encryptedName|iv|data",
|
||||
type: CipherType.Login,
|
||||
organizationId: null,
|
||||
folderId: null,
|
||||
favorite: false,
|
||||
edit: true,
|
||||
viewPassword: true,
|
||||
organizationUseTotp: false,
|
||||
revisionDate: new Date().toISOString(),
|
||||
collectionIds: [],
|
||||
deletedDate: null,
|
||||
reprompt: 0,
|
||||
key: null,
|
||||
localData: null,
|
||||
attachments: null,
|
||||
fields: null,
|
||||
passwordHistory: null,
|
||||
creationDate: new Date().toISOString(),
|
||||
login: null,
|
||||
secureNote: null,
|
||||
card: null,
|
||||
identity: null,
|
||||
sshKey: null,
|
||||
};
|
||||
mockCiphersSdk.list.mockResolvedValue({
|
||||
successes: [],
|
||||
failures: [mockFailedCipher],
|
||||
});
|
||||
|
||||
const result = await cipherSdkService.getAllDecrypted(userId);
|
||||
|
||||
expect(result.successes).toHaveLength(0);
|
||||
expect(result.failures).toHaveLength(1);
|
||||
expect(result.failures[0].decryptionFailure).toBe(true);
|
||||
});
|
||||
|
||||
it("should throw error and log when SDK client is not available", async () => {
|
||||
sdkService.userClient$.mockReturnValue(of(null));
|
||||
|
||||
await expect(cipherSdkService.getAllDecrypted(userId)).rejects.toThrow("SDK not available");
|
||||
expect(logService.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Failed to list and decrypt ciphers"),
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error and log when SDK throws an error", async () => {
|
||||
mockCiphersSdk.list.mockRejectedValue(new Error("SDK error"));
|
||||
|
||||
await expect(cipherSdkService.getAllDecrypted(userId)).rejects.toThrow();
|
||||
expect(logService.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Failed to list and decrypt ciphers"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAllFromApiForOrganization()", () => {
|
||||
it("should list organization ciphers using SDK admin API", async () => {
|
||||
const mockSdkCipherView = new CipherView().toSdkCipherView();
|
||||
mockSdkCipherView.name = "Org Cipher";
|
||||
mockAdminSdk.list_org_ciphers.mockResolvedValue({
|
||||
successes: [mockSdkCipherView],
|
||||
failures: [],
|
||||
});
|
||||
|
||||
const result = await cipherSdkService.getAllFromApiForOrganization(orgId, userId, false);
|
||||
|
||||
expect(sdkService.userClient$).toHaveBeenCalledWith(userId);
|
||||
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);
|
||||
});
|
||||
|
||||
it("should pass includeMemberItems parameter to SDK", async () => {
|
||||
mockAdminSdk.list_org_ciphers.mockResolvedValue({
|
||||
successes: [],
|
||||
failures: [],
|
||||
});
|
||||
|
||||
await cipherSdkService.getAllFromApiForOrganization(orgId, userId, true);
|
||||
|
||||
expect(mockAdminSdk.list_org_ciphers).toHaveBeenCalledWith(orgId, true);
|
||||
});
|
||||
|
||||
it("should throw error and log when SDK client is not available", async () => {
|
||||
sdkService.userClient$.mockReturnValue(of(null));
|
||||
|
||||
await expect(
|
||||
cipherSdkService.getAllFromApiForOrganization(orgId, userId, false),
|
||||
).rejects.toThrow("SDK not available");
|
||||
expect(logService.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Failed to list organization ciphers"),
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error and log when SDK throws an error", async () => {
|
||||
mockAdminSdk.list_org_ciphers.mockRejectedValue(new Error("SDK error"));
|
||||
|
||||
await expect(
|
||||
cipherSdkService.getAllFromApiForOrganization(orgId, userId, false),
|
||||
).rejects.toThrow();
|
||||
expect(logService.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Failed to list organization ciphers"),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { firstValueFrom, switchMap, catchError } from "rxjs";
|
||||
|
||||
import { DECRYPT_ERROR } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
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 { CipherSdkService } from "../abstractions/cipher-sdk.service";
|
||||
import { CipherSdkService, DecryptAllCiphersResult } from "../abstractions/cipher-sdk.service";
|
||||
import { Cipher } from "../models/domain/cipher";
|
||||
|
||||
export class DefaultCipherSdkService implements CipherSdkService {
|
||||
constructor(
|
||||
@@ -260,4 +262,78 @@ export class DefaultCipherSdkService implements CipherSdkService {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async getAllDecrypted(userId: UserId): Promise<DecryptAllCiphersResult> {
|
||||
return await firstValueFrom(
|
||||
this.sdkService.userClient$(userId).pipe(
|
||||
switchMap(async (sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
using ref = sdk.take();
|
||||
|
||||
const decryptResult = await ref.value.vault().ciphers().list();
|
||||
|
||||
// Convert successes - SDK returns array of SdkCipherView
|
||||
const successArray = Array.isArray(decryptResult.successes)
|
||||
? decryptResult.successes
|
||||
: Array.from(decryptResult.successes ?? []);
|
||||
|
||||
const successes = successArray
|
||||
.map((sdkCipherView: any) => CipherView.fromSdkCipherView(sdkCipherView))
|
||||
.filter((v): v is CipherView => v !== undefined);
|
||||
|
||||
// Convert failures to CipherView with error markers
|
||||
const failureArray = Array.isArray(decryptResult.failures)
|
||||
? decryptResult.failures
|
||||
: Array.from(decryptResult.failures ?? []);
|
||||
|
||||
const failures: CipherView[] = failureArray.map((failure: any) => {
|
||||
const cipher = Cipher.fromSdkCipher(failure);
|
||||
const cipherView = new CipherView(cipher);
|
||||
cipherView.name = DECRYPT_ERROR;
|
||||
cipherView.decryptionFailure = true;
|
||||
return cipherView;
|
||||
});
|
||||
|
||||
return { successes, failures };
|
||||
}),
|
||||
catchError((error: unknown) => {
|
||||
this.logService.error(`Failed to list and decrypt ciphers: ${error}`);
|
||||
throw error;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async getAllFromApiForOrganization(
|
||||
organizationId: string,
|
||||
userId: UserId,
|
||||
includeMemberItems: boolean,
|
||||
): Promise<CipherView[]> {
|
||||
return await firstValueFrom(
|
||||
this.sdkService.userClient$(userId).pipe(
|
||||
switchMap(async (sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
using ref = sdk.take();
|
||||
|
||||
const decryptResult = 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);
|
||||
}),
|
||||
catchError((error: unknown) => {
|
||||
this.logService.error(`Failed to list organization ciphers: ${error}`);
|
||||
throw error;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1258,47 +1258,7 @@ describe("Cipher Service", () => {
|
||||
});
|
||||
|
||||
describe("getAllFromApiForOrganization()", () => {
|
||||
let mockSdkClient: any;
|
||||
let mockCiphersSdk: any;
|
||||
let mockAdminSdk: any;
|
||||
let mockVaultSdk: any;
|
||||
const testOrgId = "4ff8c0b2-1d3e-4f8c-9b2d-1d3e4f8c0b21" as OrganizationId;
|
||||
const mockSdkCipherView1 = {
|
||||
id: "5ff8c0b2-1d3e-4f8c-9b2d-1d3e4f8c0b22",
|
||||
name: "Test Cipher 1",
|
||||
};
|
||||
const mockSdkCipherView2 = {
|
||||
id: "6ff8c0b2-1d3e-4f8c-9b2d-1d3e4f8c0b23",
|
||||
name: "Test Cipher 2",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock the SDK client chain for list_org_ciphers
|
||||
mockAdminSdk = {
|
||||
list_org_ciphers: jest.fn().mockResolvedValue({
|
||||
successes: [mockSdkCipherView1, mockSdkCipherView2],
|
||||
failures: [],
|
||||
}),
|
||||
};
|
||||
mockCiphersSdk = {
|
||||
admin: jest.fn().mockReturnValue(mockAdminSdk),
|
||||
};
|
||||
mockVaultSdk = {
|
||||
ciphers: jest.fn().mockReturnValue(mockCiphersSdk),
|
||||
};
|
||||
const mockSdkValue = {
|
||||
vault: jest.fn().mockReturnValue(mockVaultSdk),
|
||||
};
|
||||
mockSdkClient = {
|
||||
take: jest.fn().mockReturnValue({
|
||||
value: mockSdkValue,
|
||||
[Symbol.dispose]: jest.fn(),
|
||||
}),
|
||||
};
|
||||
|
||||
// Mock sdkService to return the mock client
|
||||
sdkService.userClient$.mockReturnValue(of(mockSdkClient));
|
||||
});
|
||||
|
||||
it("should call apiService.getCiphersOrganization when feature flag is disabled", async () => {
|
||||
configService.getFeatureFlag
|
||||
@@ -1316,7 +1276,6 @@ describe("Cipher Service", () => {
|
||||
await cipherService.getAllFromApiForOrganization(testOrgId, true);
|
||||
|
||||
expect(apiSpy).toHaveBeenCalledWith(testOrgId, true);
|
||||
expect(mockSdkClient.take).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call apiService.getCiphersOrganization without includeMemberItems when not provided", async () => {
|
||||
@@ -1332,7 +1291,6 @@ describe("Cipher Service", () => {
|
||||
await cipherService.getAllFromApiForOrganization(testOrgId);
|
||||
|
||||
expect(apiSpy).toHaveBeenCalledWith(testOrgId, undefined);
|
||||
expect(mockSdkClient.take).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should use SDK to list organization ciphers when feature flag is enabled", async () => {
|
||||
@@ -1340,12 +1298,20 @@ describe("Cipher Service", () => {
|
||||
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
|
||||
.mockResolvedValue(true);
|
||||
|
||||
const mockCipherView1 = new CipherView();
|
||||
mockCipherView1.name = "Test Cipher 1";
|
||||
const mockCipherView2 = new CipherView();
|
||||
mockCipherView2.name = "Test Cipher 2";
|
||||
|
||||
const sdkServiceSpy = jest
|
||||
.spyOn(cipherSdkService, "getAllFromApiForOrganization")
|
||||
.mockResolvedValue([mockCipherView1, mockCipherView2]);
|
||||
|
||||
const apiSpy = jest.spyOn(apiService, "getCiphersOrganization");
|
||||
|
||||
const result = await cipherService.getAllFromApiForOrganization(testOrgId, true);
|
||||
|
||||
expect(mockSdkClient.take).toHaveBeenCalled();
|
||||
expect(mockAdminSdk.list_org_ciphers).toHaveBeenCalledWith(testOrgId, true);
|
||||
expect(sdkServiceSpy).toHaveBeenCalledWith(testOrgId, mockUserId, true);
|
||||
expect(apiSpy).not.toHaveBeenCalled();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toBeInstanceOf(CipherView);
|
||||
@@ -1357,53 +1323,21 @@ describe("Cipher Service", () => {
|
||||
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
|
||||
.mockResolvedValue(true);
|
||||
|
||||
const sdkServiceSpy = jest
|
||||
.spyOn(cipherSdkService, "getAllFromApiForOrganization")
|
||||
.mockResolvedValue([]);
|
||||
|
||||
const apiSpy = jest.spyOn(apiService, "getCiphersOrganization");
|
||||
|
||||
await cipherService.getAllFromApiForOrganization(testOrgId);
|
||||
|
||||
expect(mockSdkClient.take).toHaveBeenCalled();
|
||||
expect(mockAdminSdk.list_org_ciphers).toHaveBeenCalledWith(testOrgId, false);
|
||||
expect(sdkServiceSpy).toHaveBeenCalledWith(testOrgId, mockUserId, false);
|
||||
expect(apiSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAllDecrypted()", () => {
|
||||
let mockSdkClient: any;
|
||||
let mockCiphersSdk: any;
|
||||
let mockVaultSdk: any;
|
||||
const mockSdkCipherView1 = {
|
||||
id: "5ff8c0b2-1d3e-4f8c-9b2d-1d3e4f8c0b22",
|
||||
name: "Test Cipher 1",
|
||||
};
|
||||
const mockSdkCipherView2 = {
|
||||
id: "6ff8c0b2-1d3e-4f8c-9b2d-1d3e4f8c0b23",
|
||||
name: "Test Cipher 2",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock the SDK client chain for list
|
||||
mockCiphersSdk = {
|
||||
list: jest.fn().mockResolvedValue({
|
||||
successes: [mockSdkCipherView1, mockSdkCipherView2],
|
||||
failures: [],
|
||||
}),
|
||||
};
|
||||
mockVaultSdk = {
|
||||
ciphers: jest.fn().mockReturnValue(mockCiphersSdk),
|
||||
};
|
||||
const mockSdkValue = {
|
||||
vault: jest.fn().mockReturnValue(mockVaultSdk),
|
||||
};
|
||||
mockSdkClient = {
|
||||
take: jest.fn().mockReturnValue({
|
||||
value: mockSdkValue,
|
||||
[Symbol.dispose]: jest.fn(),
|
||||
}),
|
||||
};
|
||||
|
||||
// Mock sdkService to return the mock client
|
||||
sdkService.userClient$.mockReturnValue(of(mockSdkClient));
|
||||
|
||||
// Clear the decrypted cache to ensure we test the decrypt path
|
||||
stateProvider.singleUser.getFake(mockUserId, DECRYPTED_CIPHERS).nextState({});
|
||||
});
|
||||
@@ -1413,21 +1347,32 @@ describe("Cipher Service", () => {
|
||||
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
|
||||
.mockResolvedValue(true);
|
||||
|
||||
const mockCipherView1 = new CipherView();
|
||||
mockCipherView1.name = "Test Cipher 1";
|
||||
const mockCipherView2 = new CipherView();
|
||||
mockCipherView2.name = "Test Cipher 2";
|
||||
|
||||
const sdkServiceSpy = jest.spyOn(cipherSdkService, "getAllDecrypted").mockResolvedValue({
|
||||
successes: [mockCipherView1, mockCipherView2],
|
||||
failures: [],
|
||||
});
|
||||
|
||||
const result = await cipherService.getAllDecrypted(mockUserId);
|
||||
|
||||
expect(mockSdkClient.take).toHaveBeenCalled();
|
||||
expect(mockCiphersSdk.list).toHaveBeenCalled();
|
||||
expect(sdkServiceSpy).toHaveBeenCalledWith(mockUserId);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toBeInstanceOf(CipherView);
|
||||
expect(result[1]).toBeInstanceOf(CipherView);
|
||||
});
|
||||
|
||||
it("should not call SDK when feature flag is disabled", async () => {
|
||||
it("should not call cipherSdkService when feature flag is disabled", async () => {
|
||||
configService.getFeatureFlag
|
||||
.calledWith(FeatureFlag.PM27632_SdkCipherCrudOperations)
|
||||
.mockResolvedValue(false);
|
||||
|
||||
// Just verify SDK is not called - don't test the full legacy path
|
||||
const sdkServiceSpy = jest.spyOn(cipherSdkService, "getAllDecrypted");
|
||||
|
||||
// Just verify SDK service is not called - don't test the full legacy path
|
||||
// as it would require complex mocking of keyService observables
|
||||
stateProvider.singleUser.getFake(mockUserId, ENCRYPTED_CIPHERS).nextState({});
|
||||
|
||||
@@ -1435,10 +1380,10 @@ describe("Cipher Service", () => {
|
||||
await cipherService.getAllDecrypted(mockUserId);
|
||||
} catch {
|
||||
// Expected to fail due to missing keyService mocks, but that's okay
|
||||
// We just want to verify SDK wasn't called
|
||||
// We just want to verify SDK service wasn't called
|
||||
}
|
||||
|
||||
expect(mockSdkClient.take).not.toHaveBeenCalled();
|
||||
expect(sdkServiceSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
firstValueFrom,
|
||||
map,
|
||||
Observable,
|
||||
of,
|
||||
Subject,
|
||||
switchMap,
|
||||
tap,
|
||||
@@ -26,7 +25,7 @@ import { AutofillSettingsServiceAbstraction } from "../../autofill/services/auto
|
||||
import { DomainSettingsService } from "../../autofill/services/domain-settings.service";
|
||||
import { FeatureFlag } from "../../enums/feature-flag.enum";
|
||||
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
|
||||
import { DECRYPT_ERROR, EncString } from "../../key-management/crypto/models/enc-string";
|
||||
import { EncString } from "../../key-management/crypto/models/enc-string";
|
||||
import { UriMatchStrategySetting } from "../../models/domain/domain-service";
|
||||
import { ErrorResponse } from "../../models/response/error.response";
|
||||
import { ListResponse } from "../../models/response/list.response";
|
||||
@@ -490,7 +489,7 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
FeatureFlag.PM27632_SdkCipherCrudOperations,
|
||||
);
|
||||
if (useSdk) {
|
||||
return this.getAllDecrypted_sdk(userId);
|
||||
return this.getAllDecryptedUsingSdk(userId);
|
||||
}
|
||||
|
||||
const decCiphers = await this.getDecryptedCiphers(userId);
|
||||
@@ -514,53 +513,18 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
return newDecCiphers;
|
||||
}
|
||||
|
||||
private async getAllDecrypted_sdk(userId: UserId): Promise<CipherView[]> {
|
||||
// Use SDK to list and decrypt all ciphers from state
|
||||
const result = await firstValueFrom(
|
||||
this.sdkService.userClient$(userId).pipe(
|
||||
switchMap(async (sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
using ref = sdk.take();
|
||||
private async getAllDecryptedUsingSdk(userId: UserId): Promise<CipherView[]> {
|
||||
try {
|
||||
const result = await this.cipherSdkService.getAllDecrypted(userId);
|
||||
|
||||
const decryptResult = await ref.value.vault().ciphers().list();
|
||||
await this.setDecryptedCipherCache(result.successes, userId);
|
||||
await this.setFailedDecryptedCiphers(result.failures, userId);
|
||||
|
||||
// Convert successes - SDK returns array of SdkCipherView
|
||||
const successArray = Array.isArray(decryptResult.successes)
|
||||
? decryptResult.successes
|
||||
: Array.from(decryptResult.successes ?? []);
|
||||
|
||||
const successViews = successArray.map((sdkCipherView: any) =>
|
||||
CipherView.fromSdkCipherView(sdkCipherView),
|
||||
);
|
||||
|
||||
// Convert failures to CipherView with error markers
|
||||
const failureArray = Array.isArray(decryptResult.failures)
|
||||
? decryptResult.failures
|
||||
: Array.from(decryptResult.failures ?? []);
|
||||
|
||||
const failureViews: CipherView[] = failureArray.map((failure: any) => {
|
||||
const cipher = Cipher.fromSdkCipher(failure);
|
||||
const cipherView = new CipherView(cipher);
|
||||
cipherView.name = DECRYPT_ERROR;
|
||||
cipherView.decryptionFailure = true;
|
||||
return cipherView;
|
||||
});
|
||||
|
||||
await this.setDecryptedCipherCache(successViews, userId);
|
||||
await this.setFailedDecryptedCiphers(failureViews, userId);
|
||||
|
||||
return successViews;
|
||||
}),
|
||||
catchError((error: unknown) => {
|
||||
this.logService.error(`Failed to list and decrypt ciphers: ${error}`);
|
||||
return of([]);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return result;
|
||||
return result.successes;
|
||||
} catch {
|
||||
// Return empty array on error to maintain existing behavior
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private async getDecryptedCiphers(userId: UserId) {
|
||||
@@ -801,7 +765,7 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
FeatureFlag.PM27632_SdkCipherCrudOperations,
|
||||
);
|
||||
if (useSdk) {
|
||||
return this.getAllFromApiForOrganization_sdk(organizationId, includeMemberItems ?? false);
|
||||
return this.getAllFromApiForOrganizationUsingSdk(organizationId, includeMemberItems ?? false);
|
||||
}
|
||||
|
||||
const response = await this.apiService.getCiphersOrganization(
|
||||
@@ -811,7 +775,7 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
return await this.decryptOrganizationCiphersResponse(response, organizationId);
|
||||
}
|
||||
|
||||
private async getAllFromApiForOrganization_sdk(
|
||||
private async getAllFromApiForOrganizationUsingSdk(
|
||||
organizationId: string,
|
||||
includeMemberItems: boolean,
|
||||
): Promise<CipherView[]> {
|
||||
@@ -820,36 +784,21 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
throw new Error("User ID is required");
|
||||
}
|
||||
|
||||
const result = await firstValueFrom(
|
||||
this.sdkService.userClient$(userId).pipe(
|
||||
switchMap(async (sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
using ref = sdk.take();
|
||||
const decryptResult = await ref.value
|
||||
.vault()
|
||||
.ciphers()
|
||||
.admin()
|
||||
.list_org_ciphers(asUuid(organizationId), includeMemberItems);
|
||||
try {
|
||||
const cipherViews = await this.cipherSdkService.getAllFromApiForOrganization(
|
||||
organizationId,
|
||||
userId,
|
||||
includeMemberItems,
|
||||
);
|
||||
|
||||
const cipherViews = decryptResult.successes.map((sdkCipherView: any) =>
|
||||
CipherView.fromSdkCipherView(sdkCipherView),
|
||||
);
|
||||
// Sort by locale (matching existing behavior)
|
||||
cipherViews.sort(this.getLocaleSortingFunction());
|
||||
|
||||
// Sort by locale (matching existing behavior)
|
||||
cipherViews.sort(this.getLocaleSortingFunction());
|
||||
|
||||
return cipherViews;
|
||||
}),
|
||||
catchError((error: unknown) => {
|
||||
this.logService.error(`Failed to list organization ciphers: ${error}`);
|
||||
return of([]);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return result;
|
||||
return cipherViews;
|
||||
} catch {
|
||||
// Return empty array on error to maintain existing behavior
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getManyFromApiForOrganization(organizationId: string): Promise<CipherView[]> {
|
||||
|
||||
Reference in New Issue
Block a user