diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts index 21df1cbbd69..568470725a9 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts @@ -1,9 +1,8 @@ import { mock } from "jest-mock-extended"; -import { firstValueFrom, of } from "rxjs"; +import { firstValueFrom, of, timeout, TimeoutError } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { KeyService } from "@bitwarden/key-management"; import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; @@ -22,11 +21,7 @@ import { MasterKey } from "../../../types/key"; import { FakeMasterPasswordService } from "../../master-password/services/fake-master-password.service"; import { KeyConnectorUserKeyRequest } from "../models/key-connector-user-key.request"; -import { - USES_KEY_CONNECTOR, - CONVERT_ACCOUNT_TO_KEY_CONNECTOR, - KeyConnectorService, -} from "./key-connector.service"; +import { USES_KEY_CONNECTOR, KeyConnectorService } from "./key-connector.service"; describe("KeyConnectorService", () => { let keyConnectorService: KeyConnectorService; @@ -37,7 +32,6 @@ describe("KeyConnectorService", () => { const logService = mock(); const organizationService = mock(); const keyGenerationService = mock(); - const messagingService = mock(); let stateProvider: FakeStateProvider; @@ -69,7 +63,6 @@ describe("KeyConnectorService", () => { keyGenerationService, async () => {}, stateProvider, - messagingService, ); }); @@ -233,108 +226,6 @@ describe("KeyConnectorService", () => { }); }); - describe("setConvertAccountRequired", () => { - it("should update the convertAccountToKeyConnectorState with the provided value", async () => { - const state = stateProvider.activeUser.getFake(CONVERT_ACCOUNT_TO_KEY_CONNECTOR); - state.nextState(false); - - const newValue = true; - - await keyConnectorService.setConvertAccountRequired(newValue, mockUserId); - - expect(await firstValueFrom(state.state$)).toBe(newValue); - }); - - it("should remove the convertAccountToKeyConnectorState", async () => { - const state = stateProvider.activeUser.getFake(CONVERT_ACCOUNT_TO_KEY_CONNECTOR); - state.nextState(false); - - const newValue: boolean | null = null; - - await keyConnectorService.setConvertAccountRequired(newValue, mockUserId); - - expect(await firstValueFrom(state.state$)).toBe(newValue); - }); - }); - - describe("userNeedsMigration", () => { - it("should return true if the user needs migration", async () => { - // token - tokenService.getIsExternal.mockResolvedValue(true); - - // create organization object - const data = organizationData( - true, - true, - "https://key-connector-url.com", - OrganizationUserType.User, - false, - ); - - // uses KeyConnector - const state = stateProvider.singleUser.getFake(mockUserId, USES_KEY_CONNECTOR); - state.nextState(false); - - const result = await keyConnectorService.userNeedsMigration(mockUserId, [data]); - - expect(result).toBe(true); - }); - - it("should return false when not logged in with sso", async () => { - tokenService.getIsExternal.mockResolvedValue(false); - const data = organizationData( - true, - true, - "https://key-connector-url.com", - OrganizationUserType.User, - false, - ); - - const state = stateProvider.singleUser.getFake(mockUserId, USES_KEY_CONNECTOR); - state.nextState(false); - - const result = await keyConnectorService.userNeedsMigration(mockUserId, [data]); - - expect(result).toBe(false); - }); - - it("should return false when key connector not enabled in organization", async () => { - tokenService.getIsExternal.mockResolvedValue(true); - const data = organizationData( - true, - false, - "https://key-connector-url.com", - OrganizationUserType.User, - false, - ); - - const state = stateProvider.singleUser.getFake(mockUserId, USES_KEY_CONNECTOR); - state.nextState(false); - - const result = await keyConnectorService.userNeedsMigration(mockUserId, [data]); - - expect(result).toBe(false); - }); - - it("should return false when user does not need key connector", async () => { - tokenService.getIsExternal.mockResolvedValue(true); - const data = organizationData( - true, - true, - "https://key-connector-url.com", - OrganizationUserType.User, - false, - ); - - const state = stateProvider.singleUser.getFake(mockUserId, USES_KEY_CONNECTOR); - state.nextState(true); - - const result = await keyConnectorService.userNeedsMigration(mockUserId, [data]); - - expect(result).toBe(false); - }); - }); - describe("setMasterKeyFromUrl", () => { it("should set the master key from the provided URL", async () => { // Arrange @@ -436,6 +327,136 @@ describe("KeyConnectorService", () => { }); }); + describe("convertAccountRequired$", () => { + beforeEach(async () => { + const organization = organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.User, + false, + ); + organizationService.organizations$.mockReturnValue(of([organization])); + await stateProvider.getUser(mockUserId, USES_KEY_CONNECTOR).update(() => false); + tokenService.getIsExternal.mockResolvedValue(true); + tokenService.hasAccessToken$.mockReturnValue(of(true)); + }); + + it("should return true when user logged in with sso, belong to organization using key connector and user does not use key connector", async () => { + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(true); + }); + + it("should return false when user logged in with password", async () => { + tokenService.getIsExternal.mockResolvedValue(false); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should return false when organization's key connector disabled", async () => { + const organization = organizationData( + true, + false, + "https://key-connector-url.com", + OrganizationUserType.User, + false, + ); + organizationService.organizations$.mockReturnValue(of([organization])); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should return false when user is admin of the organization", async () => { + const organization = organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.Admin, + false, + ); + organizationService.organizations$.mockReturnValue(of([organization])); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should return false when user is owner of the organization", async () => { + const organization = organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.Owner, + false, + ); + organizationService.organizations$.mockReturnValue(of([organization])); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should return false when user is provider user of the organization", async () => { + const organization = organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.User, + true, + ); + organizationService.organizations$.mockReturnValue(of([organization])); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should return false when user already uses key connector", async () => { + await stateProvider.getUser(mockUserId, USES_KEY_CONNECTOR).update(() => true); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should not return any value when user not logged in", async () => { + await accountService.switchAccount(null as unknown as UserId); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).rejects.toBeInstanceOf(TimeoutError); + }); + + it("should not return any value when organization state is empty", async () => { + organizationService.organizations$.mockReturnValue(of(null as unknown as Organization[])); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).rejects.toBeInstanceOf(TimeoutError); + }); + + it("should not return any value when user is not using key connector", async () => { + await stateProvider.getUser(mockUserId, USES_KEY_CONNECTOR).update(() => null); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).rejects.toBeInstanceOf(TimeoutError); + }); + + it("should not return any value when user does not have access token", async () => { + tokenService.hasAccessToken$.mockReturnValue(of(false)); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).rejects.toBeInstanceOf(TimeoutError); + }); + }); + function organizationData( usesKeyConnector: boolean, keyConnectorEnabled: boolean,