mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
[PM-26372] Add auto confirm service (#17001)
* add state definition for auto confirm * typo * refactor organziation user service * WIP create auto confirm service * add POST method, finish implementation * add missing userId param, jsdoc * fix DI * refactor organziation user service * WIP create auto confirm service * add POST method, finish implementation * add missing userId param, jsdoc * clean up, more DI fixes * remove @Injectable from service, fix tests * remove from libs/common, fix dir structure, add tests
This commit is contained in:
@@ -5,6 +5,7 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
|||||||
import { firstValueFrom, map, Observable, switchMap } from "rxjs";
|
import { firstValueFrom, map, Observable, switchMap } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
DefaultOrganizationUserService,
|
||||||
OrganizationUserApiService,
|
OrganizationUserApiService,
|
||||||
OrganizationUserBulkConfirmRequest,
|
OrganizationUserBulkConfirmRequest,
|
||||||
OrganizationUserBulkPublicKeyResponse,
|
OrganizationUserBulkPublicKeyResponse,
|
||||||
@@ -26,8 +27,6 @@ import { OrgKey } from "@bitwarden/common/types/key";
|
|||||||
import { DIALOG_DATA, DialogConfig, DialogService } from "@bitwarden/components";
|
import { DIALOG_DATA, DialogConfig, DialogService } from "@bitwarden/components";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { OrganizationUserService } from "../../services/organization-user/organization-user.service";
|
|
||||||
|
|
||||||
import { BaseBulkConfirmComponent } from "./base-bulk-confirm.component";
|
import { BaseBulkConfirmComponent } from "./base-bulk-confirm.component";
|
||||||
import { BulkUserDetails } from "./bulk-status.component";
|
import { BulkUserDetails } from "./bulk-status.component";
|
||||||
|
|
||||||
@@ -54,7 +53,7 @@ export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent {
|
|||||||
private organizationUserApiService: OrganizationUserApiService,
|
private organizationUserApiService: OrganizationUserApiService,
|
||||||
protected i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
private stateProvider: StateProvider,
|
private stateProvider: StateProvider,
|
||||||
private organizationUserService: OrganizationUserService,
|
private organizationUserService: DefaultOrganizationUserService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
) {
|
) {
|
||||||
super(keyService, encryptService, i18nService);
|
super(keyService, encryptService, i18nService);
|
||||||
|
|||||||
@@ -2,4 +2,3 @@ export { OrganizationMembersService } from "./organization-members-service/organ
|
|||||||
export { MemberActionsService } from "./member-actions/member-actions.service";
|
export { MemberActionsService } from "./member-actions/member-actions.service";
|
||||||
export { MemberDialogManagerService } from "./member-dialog-manager/member-dialog-manager.service";
|
export { MemberDialogManagerService } from "./member-dialog-manager/member-dialog-manager.service";
|
||||||
export { DeleteManagedMemberWarningService } from "./delete-managed-member/delete-managed-member-warning.service";
|
export { DeleteManagedMemberWarningService } from "./delete-managed-member/delete-managed-member-warning.service";
|
||||||
export { OrganizationUserService } from "./organization-user/organization-user.service";
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
OrganizationUserStatusType,
|
OrganizationUserStatusType,
|
||||||
} from "@bitwarden/common/admin-console/enums";
|
} from "@bitwarden/common/admin-console/enums";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { OrganizationMetadataServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-metadata.service.abstraction";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
@@ -20,7 +21,6 @@ import { OrgKey } from "@bitwarden/common/types/key";
|
|||||||
import { newGuid } from "@bitwarden/guid";
|
import { newGuid } from "@bitwarden/guid";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { BillingConstraintService } from "../../../../../billing/members/billing-constraint/billing-constraint.service";
|
|
||||||
import { OrganizationUserView } from "../../../core/views/organization-user.view";
|
import { OrganizationUserView } from "../../../core/views/organization-user.view";
|
||||||
import { OrganizationUserService } from "../organization-user/organization-user.service";
|
import { OrganizationUserService } from "../organization-user/organization-user.service";
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ describe("MemberActionsService", () => {
|
|||||||
let encryptService: MockProxy<EncryptService>;
|
let encryptService: MockProxy<EncryptService>;
|
||||||
let configService: MockProxy<ConfigService>;
|
let configService: MockProxy<ConfigService>;
|
||||||
let accountService: FakeAccountService;
|
let accountService: FakeAccountService;
|
||||||
let billingConstraintService: MockProxy<BillingConstraintService>;
|
let organizationMetadataService: MockProxy<OrganizationMetadataServiceAbstraction>;
|
||||||
|
|
||||||
const userId = newGuid() as UserId;
|
const userId = newGuid() as UserId;
|
||||||
const organizationId = newGuid() as OrganizationId;
|
const organizationId = newGuid() as OrganizationId;
|
||||||
@@ -50,7 +50,7 @@ describe("MemberActionsService", () => {
|
|||||||
encryptService = mock<EncryptService>();
|
encryptService = mock<EncryptService>();
|
||||||
configService = mock<ConfigService>();
|
configService = mock<ConfigService>();
|
||||||
accountService = mockAccountServiceWith(userId);
|
accountService = mockAccountServiceWith(userId);
|
||||||
billingConstraintService = mock<BillingConstraintService>();
|
organizationMetadataService = mock<OrganizationMetadataServiceAbstraction>();
|
||||||
|
|
||||||
mockOrganization = {
|
mockOrganization = {
|
||||||
id: organizationId,
|
id: organizationId,
|
||||||
@@ -75,7 +75,7 @@ describe("MemberActionsService", () => {
|
|||||||
encryptService,
|
encryptService,
|
||||||
configService,
|
configService,
|
||||||
accountService,
|
accountService,
|
||||||
billingConstraintService,
|
organizationMetadataService,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -251,7 +251,7 @@ describe("MemberActionsService", () => {
|
|||||||
expect(result).toEqual({ success: true });
|
expect(result).toEqual({ success: true });
|
||||||
expect(organizationUserService.confirmUser).toHaveBeenCalledWith(
|
expect(organizationUserService.confirmUser).toHaveBeenCalledWith(
|
||||||
mockOrganization,
|
mockOrganization,
|
||||||
mockOrgUser,
|
mockOrgUser.id,
|
||||||
publicKey,
|
publicKey,
|
||||||
);
|
);
|
||||||
expect(organizationUserApiService.postOrganizationUserConfirm).not.toHaveBeenCalled();
|
expect(organizationUserApiService.postOrganizationUserConfirm).not.toHaveBeenCalled();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Injectable } from "@angular/core";
|
|||||||
import { firstValueFrom, switchMap, map } from "rxjs";
|
import { firstValueFrom, switchMap, map } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
DefaultOrganizationUserService,
|
||||||
OrganizationUserApiService,
|
OrganizationUserApiService,
|
||||||
OrganizationUserBulkResponse,
|
OrganizationUserBulkResponse,
|
||||||
OrganizationUserConfirmRequest,
|
OrganizationUserConfirmRequest,
|
||||||
@@ -21,7 +22,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
|||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { OrganizationUserView } from "../../../core/views/organization-user.view";
|
import { OrganizationUserView } from "../../../core/views/organization-user.view";
|
||||||
import { OrganizationUserService } from "../organization-user/organization-user.service";
|
|
||||||
|
|
||||||
export interface MemberActionResult {
|
export interface MemberActionResult {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@@ -39,7 +39,7 @@ export class MemberActionsService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private organizationUserApiService: OrganizationUserApiService,
|
private organizationUserApiService: OrganizationUserApiService,
|
||||||
private organizationUserService: OrganizationUserService,
|
private organizationUserService: DefaultOrganizationUserService,
|
||||||
private keyService: KeyService,
|
private keyService: KeyService,
|
||||||
private encryptService: EncryptService,
|
private encryptService: EncryptService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
@@ -129,7 +129,7 @@ export class MemberActionsService {
|
|||||||
await firstValueFrom(this.configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation))
|
await firstValueFrom(this.configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation))
|
||||||
) {
|
) {
|
||||||
await firstValueFrom(
|
await firstValueFrom(
|
||||||
this.organizationUserService.confirmUser(organization, user, publicKey),
|
this.organizationUserService.confirmUser(organization, user.id, publicKey),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const request = await firstValueFrom(
|
const request = await firstValueFrom(
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
|
import { UserId } from "@bitwarden/user-core";
|
||||||
|
|
||||||
|
import { AutoConfirmState } from "../models/auto-confirm-state.model";
|
||||||
|
|
||||||
|
export abstract class AutomaticUserConfirmationService {
|
||||||
|
/**
|
||||||
|
* @param userId
|
||||||
|
* @returns Observable<AutoConfirmState> an observable with the Auto Confirm user state for the provided userId.
|
||||||
|
**/
|
||||||
|
abstract configuration$(userId: UserId): Observable<AutoConfirmState>;
|
||||||
|
/**
|
||||||
|
* Upserts the existing user state with a new configuration.
|
||||||
|
* @param userId
|
||||||
|
* @param config The new AutoConfirmState to upsert into the user state for the provided userId.
|
||||||
|
**/
|
||||||
|
abstract upsert(userId: UserId, config: AutoConfirmState): Promise<void>;
|
||||||
|
/**
|
||||||
|
* This will check if the feature is enabled, the organization plan feature UseAutomaticUserConfirmation is enabled
|
||||||
|
* and the the provided user has admin/owner/manage custom permission role.
|
||||||
|
* @param userId
|
||||||
|
* @returns Observable<boolean> an observable with a boolean telling us if the provided user may confgure the auto confirm feature.
|
||||||
|
**/
|
||||||
|
abstract canManageAutoConfirm$(
|
||||||
|
userId: UserId,
|
||||||
|
organizationId: OrganizationId,
|
||||||
|
): Observable<boolean>;
|
||||||
|
/**
|
||||||
|
* Calls the API endpoint to initiate automatic user confirmation.
|
||||||
|
* @param userId The userId of the logged in admin performing auto confirmation. This is neccesary to perform the key exchange and for permissions checks.
|
||||||
|
* @param confirmingUserId The userId of the user being confirmed.
|
||||||
|
* @param organization the organization the user is being auto confirmed to.
|
||||||
|
**/
|
||||||
|
abstract autoConfirmUser(
|
||||||
|
userId: UserId,
|
||||||
|
confirmingUserId: UserId,
|
||||||
|
organization: Organization,
|
||||||
|
): Promise<void>;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./auto-confirm.service.abstraction";
|
||||||
3
libs/admin-console/src/common/auto-confirm/index.ts
Normal file
3
libs/admin-console/src/common/auto-confirm/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./abstractions";
|
||||||
|
export * from "./models";
|
||||||
|
export * from "./services";
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AUTO_CONFIRM, UserKeyDefinition } from "../../../platform/state";
|
import { AUTO_CONFIRM, UserKeyDefinition } from "@bitwarden/state";
|
||||||
|
|
||||||
export class AutoConfirmState {
|
export class AutoConfirmState {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./auto-confirm-state.model";
|
||||||
@@ -0,0 +1,382 @@
|
|||||||
|
import { TestBed } from "@angular/core/testing";
|
||||||
|
import { BehaviorSubject, firstValueFrom, of, throwError } from "rxjs";
|
||||||
|
|
||||||
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
|
||||||
|
import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data";
|
||||||
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { FakeStateProvider, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||||
|
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DefaultOrganizationUserService,
|
||||||
|
OrganizationUserApiService,
|
||||||
|
OrganizationUserConfirmRequest,
|
||||||
|
} from "../../organization-user";
|
||||||
|
import { AUTO_CONFIRM_STATE, AutoConfirmState } from "../models/auto-confirm-state.model";
|
||||||
|
|
||||||
|
import { DefaultAutomaticUserConfirmationService } from "./default-auto-confirm.service";
|
||||||
|
|
||||||
|
describe("DefaultAutomaticUserConfirmationService", () => {
|
||||||
|
let service: DefaultAutomaticUserConfirmationService;
|
||||||
|
let configService: jest.Mocked<ConfigService>;
|
||||||
|
let apiService: jest.Mocked<ApiService>;
|
||||||
|
let organizationUserService: jest.Mocked<DefaultOrganizationUserService>;
|
||||||
|
let stateProvider: FakeStateProvider;
|
||||||
|
let organizationService: jest.Mocked<InternalOrganizationServiceAbstraction>;
|
||||||
|
let organizationUserApiService: jest.Mocked<OrganizationUserApiService>;
|
||||||
|
|
||||||
|
const mockUserId = Utils.newGuid() as UserId;
|
||||||
|
const mockConfirmingUserId = Utils.newGuid() as UserId;
|
||||||
|
const mockOrganizationId = Utils.newGuid() as OrganizationId;
|
||||||
|
let mockOrganization: Organization;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
configService = {
|
||||||
|
getFeatureFlag$: jest.fn(),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
apiService = {
|
||||||
|
getUserPublicKey: jest.fn(),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
organizationUserService = {
|
||||||
|
buildConfirmRequest: jest.fn(),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
stateProvider = new FakeStateProvider(mockAccountServiceWith(mockUserId));
|
||||||
|
|
||||||
|
organizationService = {
|
||||||
|
organizations$: jest.fn(),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
organizationUserApiService = {
|
||||||
|
postOrganizationUserConfirm: jest.fn(),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
DefaultAutomaticUserConfirmationService,
|
||||||
|
{ provide: ConfigService, useValue: configService },
|
||||||
|
{ provide: ApiService, useValue: apiService },
|
||||||
|
{ provide: DefaultOrganizationUserService, useValue: organizationUserService },
|
||||||
|
{ provide: "StateProvider", useValue: stateProvider },
|
||||||
|
{
|
||||||
|
provide: InternalOrganizationServiceAbstraction,
|
||||||
|
useValue: organizationService,
|
||||||
|
},
|
||||||
|
{ provide: OrganizationUserApiService, useValue: organizationUserApiService },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
service = new DefaultAutomaticUserConfirmationService(
|
||||||
|
configService,
|
||||||
|
apiService,
|
||||||
|
organizationUserService,
|
||||||
|
stateProvider,
|
||||||
|
organizationService,
|
||||||
|
organizationUserApiService,
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockOrgData = new OrganizationData({} as any, {} as any);
|
||||||
|
mockOrgData.id = mockOrganizationId;
|
||||||
|
mockOrgData.useAutomaticUserConfirmation = true;
|
||||||
|
|
||||||
|
const permissions = new PermissionsApi();
|
||||||
|
permissions.manageUsers = true;
|
||||||
|
mockOrgData.permissions = permissions;
|
||||||
|
|
||||||
|
mockOrganization = new Organization(mockOrgData);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("configuration$", () => {
|
||||||
|
it("should return default AutoConfirmState when no state exists", async () => {
|
||||||
|
const config$ = service.configuration$(mockUserId);
|
||||||
|
const config = await firstValueFrom(config$);
|
||||||
|
|
||||||
|
expect(config).toBeInstanceOf(AutoConfirmState);
|
||||||
|
expect(config.enabled).toBe(false);
|
||||||
|
expect(config.showSetupDialog).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return stored AutoConfirmState when state exists", async () => {
|
||||||
|
const expectedConfig = new AutoConfirmState();
|
||||||
|
expectedConfig.enabled = true;
|
||||||
|
expectedConfig.showSetupDialog = false;
|
||||||
|
expectedConfig.showBrowserNotification = true;
|
||||||
|
|
||||||
|
await stateProvider.setUserState(
|
||||||
|
AUTO_CONFIRM_STATE,
|
||||||
|
{ [mockUserId]: expectedConfig },
|
||||||
|
mockUserId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const config$ = service.configuration$(mockUserId);
|
||||||
|
const config = await firstValueFrom(config$);
|
||||||
|
|
||||||
|
expect(config.enabled).toBe(true);
|
||||||
|
expect(config.showSetupDialog).toBe(false);
|
||||||
|
expect(config.showBrowserNotification).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should emit updates when state changes", async () => {
|
||||||
|
const config$ = service.configuration$(mockUserId);
|
||||||
|
const configs: AutoConfirmState[] = [];
|
||||||
|
|
||||||
|
const subscription = config$.subscribe((config) => configs.push(config));
|
||||||
|
|
||||||
|
expect(configs[0].enabled).toBe(false);
|
||||||
|
|
||||||
|
const newConfig = new AutoConfirmState();
|
||||||
|
newConfig.enabled = true;
|
||||||
|
await stateProvider.setUserState(AUTO_CONFIRM_STATE, { [mockUserId]: newConfig }, mockUserId);
|
||||||
|
|
||||||
|
expect(configs.length).toBeGreaterThan(1);
|
||||||
|
expect(configs[configs.length - 1].enabled).toBe(true);
|
||||||
|
|
||||||
|
subscription.unsubscribe();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("upsert", () => {
|
||||||
|
it("should store new configuration for user", async () => {
|
||||||
|
const newConfig = new AutoConfirmState();
|
||||||
|
newConfig.enabled = true;
|
||||||
|
newConfig.showSetupDialog = false;
|
||||||
|
|
||||||
|
await service.upsert(mockUserId, newConfig);
|
||||||
|
|
||||||
|
const storedState = await firstValueFrom(
|
||||||
|
stateProvider.getUser(mockUserId, AUTO_CONFIRM_STATE).state$,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(storedState != null);
|
||||||
|
expect(storedState![mockUserId]).toEqual(newConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update existing configuration for user", async () => {
|
||||||
|
const initialConfig = new AutoConfirmState();
|
||||||
|
initialConfig.enabled = false;
|
||||||
|
|
||||||
|
await service.upsert(mockUserId, initialConfig);
|
||||||
|
|
||||||
|
const updatedConfig = new AutoConfirmState();
|
||||||
|
updatedConfig.enabled = true;
|
||||||
|
updatedConfig.showSetupDialog = false;
|
||||||
|
|
||||||
|
await service.upsert(mockUserId, updatedConfig);
|
||||||
|
|
||||||
|
const storedState = await firstValueFrom(
|
||||||
|
stateProvider.getUser(mockUserId, AUTO_CONFIRM_STATE).state$,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(storedState != null);
|
||||||
|
expect(storedState![mockUserId].enabled).toBe(true);
|
||||||
|
expect(storedState![mockUserId].showSetupDialog).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should preserve other user configurations when updating", async () => {
|
||||||
|
const otherUserId = Utils.newGuid() as UserId;
|
||||||
|
const otherConfig = new AutoConfirmState();
|
||||||
|
otherConfig.enabled = true;
|
||||||
|
|
||||||
|
await stateProvider.setUserState(
|
||||||
|
AUTO_CONFIRM_STATE,
|
||||||
|
{ [otherUserId]: otherConfig },
|
||||||
|
mockUserId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const newConfig = new AutoConfirmState();
|
||||||
|
newConfig.enabled = false;
|
||||||
|
|
||||||
|
await service.upsert(mockUserId, newConfig);
|
||||||
|
|
||||||
|
const storedState = await firstValueFrom(
|
||||||
|
stateProvider.getUser(mockUserId, AUTO_CONFIRM_STATE).state$,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(storedState != null);
|
||||||
|
expect(storedState![mockUserId]).toEqual(newConfig);
|
||||||
|
expect(storedState![otherUserId]).toEqual(otherConfig);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("canManageAutoConfirm$", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const organizations$ = new BehaviorSubject<Organization[]>([mockOrganization]);
|
||||||
|
organizationService.organizations$.mockReturnValue(organizations$);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true when feature flag is enabled and organization allows management", async () => {
|
||||||
|
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||||
|
|
||||||
|
const canManage$ = service.canManageAutoConfirm$(mockUserId, mockOrganizationId);
|
||||||
|
const canManage = await firstValueFrom(canManage$);
|
||||||
|
|
||||||
|
expect(canManage).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when feature flag is disabled", async () => {
|
||||||
|
configService.getFeatureFlag$.mockReturnValue(of(false));
|
||||||
|
|
||||||
|
const canManage$ = service.canManageAutoConfirm$(mockUserId, mockOrganizationId);
|
||||||
|
const canManage = await firstValueFrom(canManage$);
|
||||||
|
|
||||||
|
expect(canManage).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when organization canManageUsers is false", async () => {
|
||||||
|
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||||
|
|
||||||
|
// Create organization without manageUsers permission
|
||||||
|
const mockOrgData = new OrganizationData({} as any, {} as any);
|
||||||
|
mockOrgData.id = mockOrganizationId;
|
||||||
|
mockOrgData.useAutomaticUserConfirmation = true;
|
||||||
|
const permissions = new PermissionsApi();
|
||||||
|
permissions.manageUsers = false;
|
||||||
|
mockOrgData.permissions = permissions;
|
||||||
|
const orgWithoutManageUsers = new Organization(mockOrgData);
|
||||||
|
|
||||||
|
const organizations$ = new BehaviorSubject<Organization[]>([orgWithoutManageUsers]);
|
||||||
|
organizationService.organizations$.mockReturnValue(organizations$);
|
||||||
|
|
||||||
|
const canManage$ = service.canManageAutoConfirm$(mockUserId, mockOrganizationId);
|
||||||
|
const canManage = await firstValueFrom(canManage$);
|
||||||
|
|
||||||
|
expect(canManage).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when organization useAutomaticUserConfirmation is false", async () => {
|
||||||
|
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||||
|
|
||||||
|
// Create organization without useAutomaticUserConfirmation
|
||||||
|
const mockOrgData = new OrganizationData({} as any, {} as any);
|
||||||
|
mockOrgData.id = mockOrganizationId;
|
||||||
|
mockOrgData.useAutomaticUserConfirmation = false;
|
||||||
|
const permissions = new PermissionsApi();
|
||||||
|
permissions.manageUsers = true;
|
||||||
|
mockOrgData.permissions = permissions;
|
||||||
|
const orgWithoutAutoConfirm = new Organization(mockOrgData);
|
||||||
|
|
||||||
|
const organizations$ = new BehaviorSubject<Organization[]>([orgWithoutAutoConfirm]);
|
||||||
|
organizationService.organizations$.mockReturnValue(organizations$);
|
||||||
|
|
||||||
|
const canManage$ = service.canManageAutoConfirm$(mockUserId, mockOrganizationId);
|
||||||
|
const canManage = await firstValueFrom(canManage$);
|
||||||
|
|
||||||
|
expect(canManage).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when organization is not found", async () => {
|
||||||
|
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||||
|
|
||||||
|
const organizations$ = new BehaviorSubject<Organization[]>([]);
|
||||||
|
organizationService.organizations$.mockReturnValue(organizations$);
|
||||||
|
|
||||||
|
const canManage$ = service.canManageAutoConfirm$(mockUserId, mockOrganizationId);
|
||||||
|
const canManage = await firstValueFrom(canManage$);
|
||||||
|
|
||||||
|
expect(canManage).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use the correct feature flag", async () => {
|
||||||
|
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||||
|
|
||||||
|
const canManage$ = service.canManageAutoConfirm$(mockUserId, mockOrganizationId);
|
||||||
|
await firstValueFrom(canManage$);
|
||||||
|
|
||||||
|
expect(configService.getFeatureFlag$).toHaveBeenCalledWith(FeatureFlag.AutoConfirm);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("autoConfirmUser", () => {
|
||||||
|
const mockPublicKey = "mock-public-key-base64";
|
||||||
|
const mockPublicKeyArray = new Uint8Array([1, 2, 3, 4]);
|
||||||
|
const mockConfirmRequest = {
|
||||||
|
key: "encrypted-key",
|
||||||
|
defaultUserCollectionName: "encrypted-collection",
|
||||||
|
} as OrganizationUserConfirmRequest;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const organizations$ = new BehaviorSubject<Organization[]>([mockOrganization]);
|
||||||
|
organizationService.organizations$.mockReturnValue(organizations$);
|
||||||
|
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||||
|
|
||||||
|
apiService.getUserPublicKey.mockResolvedValue({ publicKey: mockPublicKey } as any);
|
||||||
|
jest.spyOn(Utils, "fromB64ToArray").mockReturnValue(mockPublicKeyArray);
|
||||||
|
organizationUserService.buildConfirmRequest.mockReturnValue(of(mockConfirmRequest));
|
||||||
|
organizationUserApiService.postOrganizationUserConfirm.mockResolvedValue(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should successfully auto-confirm a user", async () => {
|
||||||
|
await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization);
|
||||||
|
|
||||||
|
expect(apiService.getUserPublicKey).toHaveBeenCalledWith(mockUserId);
|
||||||
|
expect(organizationUserService.buildConfirmRequest).toHaveBeenCalledWith(
|
||||||
|
mockOrganization,
|
||||||
|
mockPublicKeyArray,
|
||||||
|
);
|
||||||
|
expect(organizationUserApiService.postOrganizationUserConfirm).toHaveBeenCalledWith(
|
||||||
|
mockOrganizationId,
|
||||||
|
mockConfirmingUserId,
|
||||||
|
mockConfirmRequest,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not confirm user when canManageAutoConfirm returns false", async () => {
|
||||||
|
configService.getFeatureFlag$.mockReturnValue(of(false));
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization),
|
||||||
|
).rejects.toThrow("Cannot automatically confirm user (insufficient permissions)");
|
||||||
|
|
||||||
|
expect(apiService.getUserPublicKey).not.toHaveBeenCalled();
|
||||||
|
expect(organizationUserApiService.postOrganizationUserConfirm).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should build confirm request with organization and public key", async () => {
|
||||||
|
await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization);
|
||||||
|
|
||||||
|
expect(organizationUserService.buildConfirmRequest).toHaveBeenCalledWith(
|
||||||
|
mockOrganization,
|
||||||
|
mockPublicKeyArray,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call API with correct parameters", async () => {
|
||||||
|
await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization);
|
||||||
|
|
||||||
|
expect(organizationUserApiService.postOrganizationUserConfirm).toHaveBeenCalledWith(
|
||||||
|
mockOrganization.id,
|
||||||
|
mockConfirmingUserId,
|
||||||
|
mockConfirmRequest,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle API errors gracefully", async () => {
|
||||||
|
const apiError = new Error("API Error");
|
||||||
|
apiService.getUserPublicKey.mockRejectedValue(apiError);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization),
|
||||||
|
).rejects.toThrow("API Error");
|
||||||
|
|
||||||
|
expect(organizationUserApiService.postOrganizationUserConfirm).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle buildConfirmRequest errors gracefully", async () => {
|
||||||
|
const buildError = new Error("Build Error");
|
||||||
|
organizationUserService.buildConfirmRequest.mockReturnValue(throwError(() => buildError));
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization),
|
||||||
|
).rejects.toThrow("Build Error");
|
||||||
|
|
||||||
|
expect(organizationUserApiService.postOrganizationUserConfirm).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { combineLatest, firstValueFrom, map, Observable, switchMap } from "rxjs";
|
||||||
|
|
||||||
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
|
import { getById } from "@bitwarden/common/platform/misc";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
|
import { StateProvider } from "@bitwarden/state";
|
||||||
|
import { UserId } from "@bitwarden/user-core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DefaultOrganizationUserService,
|
||||||
|
OrganizationUserApiService,
|
||||||
|
} from "../../organization-user";
|
||||||
|
import { AutomaticUserConfirmationService } from "../abstractions/auto-confirm.service.abstraction";
|
||||||
|
import { AUTO_CONFIRM_STATE, AutoConfirmState } from "../models/auto-confirm-state.model";
|
||||||
|
|
||||||
|
export class DefaultAutomaticUserConfirmationService implements AutomaticUserConfirmationService {
|
||||||
|
constructor(
|
||||||
|
private configService: ConfigService,
|
||||||
|
private apiService: ApiService,
|
||||||
|
private organizationUserService: DefaultOrganizationUserService,
|
||||||
|
private stateProvider: StateProvider,
|
||||||
|
private organizationService: InternalOrganizationServiceAbstraction,
|
||||||
|
private organizationUserApiService: OrganizationUserApiService,
|
||||||
|
) {}
|
||||||
|
private autoConfirmState(userId: UserId) {
|
||||||
|
return this.stateProvider.getUser(userId, AUTO_CONFIRM_STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration$(userId: UserId): Observable<AutoConfirmState> {
|
||||||
|
return this.autoConfirmState(userId).state$.pipe(
|
||||||
|
map((records) => records?.[userId] ?? new AutoConfirmState()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsert(userId: UserId, config: AutoConfirmState): Promise<void> {
|
||||||
|
await this.autoConfirmState(userId).update((records) => {
|
||||||
|
return {
|
||||||
|
...records,
|
||||||
|
[userId]: config,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
canManageAutoConfirm$(userId: UserId, organizationId: OrganizationId): Observable<boolean> {
|
||||||
|
return combineLatest([
|
||||||
|
this.configService.getFeatureFlag$(FeatureFlag.AutoConfirm),
|
||||||
|
this.organizationService.organizations$(userId).pipe(getById(organizationId)),
|
||||||
|
]).pipe(
|
||||||
|
map(
|
||||||
|
([enabled, organization]) =>
|
||||||
|
(enabled && organization?.canManageUsers && organization?.useAutomaticUserConfirmation) ??
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async autoConfirmUser(
|
||||||
|
userId: UserId,
|
||||||
|
confirmingUserId: UserId,
|
||||||
|
organization: Organization,
|
||||||
|
): Promise<void> {
|
||||||
|
await firstValueFrom(
|
||||||
|
this.canManageAutoConfirm$(userId, organization.id).pipe(
|
||||||
|
map((canManage) => {
|
||||||
|
if (!canManage) {
|
||||||
|
throw new Error("Cannot automatically confirm user (insufficient permissions)");
|
||||||
|
}
|
||||||
|
return canManage;
|
||||||
|
}),
|
||||||
|
switchMap(() => this.apiService.getUserPublicKey(userId)),
|
||||||
|
map((publicKeyResponse) => Utils.fromB64ToArray(publicKeyResponse.publicKey)),
|
||||||
|
switchMap((publicKey) =>
|
||||||
|
this.organizationUserService.buildConfirmRequest(organization, publicKey),
|
||||||
|
),
|
||||||
|
switchMap((request) =>
|
||||||
|
this.organizationUserApiService.postOrganizationUserConfirm(
|
||||||
|
organization.id,
|
||||||
|
confirmingUserId,
|
||||||
|
request,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./default-auto-confirm.service";
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./organization-user";
|
export * from "./auto-confirm";
|
||||||
export * from "./collections";
|
export * from "./collections";
|
||||||
|
export * from "./organization-user";
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from "./organization-user-api.service";
|
export * from "./organization-user-api.service";
|
||||||
|
export * from "./organization-user.service";
|
||||||
|
|||||||
@@ -148,6 +148,19 @@ export abstract class OrganizationUserApiService {
|
|||||||
request: OrganizationUserConfirmRequest,
|
request: OrganizationUserConfirmRequest,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin api for automatically confirming an organization user that
|
||||||
|
* has accepted their invitation
|
||||||
|
* @param organizationId - Identifier for the organization to confirm
|
||||||
|
* @param id - Organization user identifier
|
||||||
|
* @param request - Request details for confirming the user
|
||||||
|
*/
|
||||||
|
abstract postOrganizationUserAutoConfirm(
|
||||||
|
organizationId: string,
|
||||||
|
id: string,
|
||||||
|
request: OrganizationUserConfirmRequest,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a list of the specified users' public keys
|
* Retrieve a list of the specified users' public keys
|
||||||
* @param organizationId - Identifier for the organization to accept
|
* @param organizationId - Identifier for the organization to accept
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
OrganizationUserConfirmRequest,
|
||||||
|
OrganizationUserBulkResponse,
|
||||||
|
} from "@bitwarden/admin-console/common";
|
||||||
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
|
|
||||||
|
export abstract class OrganizationUserService {
|
||||||
|
/**
|
||||||
|
* Builds a confirmation request for an organization user.
|
||||||
|
* @param organization - The organization the user belongs to
|
||||||
|
* @param publicKey - The user's public key
|
||||||
|
* @returns An observable that emits the confirmation request
|
||||||
|
*/
|
||||||
|
abstract buildConfirmRequest(
|
||||||
|
organization: Organization,
|
||||||
|
publicKey: Uint8Array,
|
||||||
|
): Observable<OrganizationUserConfirmRequest>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms a user in an organization.
|
||||||
|
* @param organization - The organization the user belongs to
|
||||||
|
* @param userId - The ID of the user to confirm
|
||||||
|
* @param publicKey - The user's public key
|
||||||
|
* @returns An observable that completes when the user is confirmed
|
||||||
|
*/
|
||||||
|
abstract confirmUser(
|
||||||
|
organization: Organization,
|
||||||
|
userId: string,
|
||||||
|
publicKey: Uint8Array,
|
||||||
|
): Observable<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms multiple users in an organization.
|
||||||
|
* @param organization - The organization the users belong to
|
||||||
|
* @param userIdsWithKeys - Array of user IDs with their encrypted keys
|
||||||
|
* @returns An observable that emits the bulk confirmation response
|
||||||
|
*/
|
||||||
|
abstract bulkConfirmUsers(
|
||||||
|
organization: Organization,
|
||||||
|
userIdsWithKeys: { id: string; key: string }[],
|
||||||
|
): Observable<ListResponse<OrganizationUserBulkResponse>>;
|
||||||
|
}
|
||||||
@@ -194,6 +194,20 @@ export class DefaultOrganizationUserApiService implements OrganizationUserApiSer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postOrganizationUserAutoConfirm(
|
||||||
|
organizationId: string,
|
||||||
|
id: string,
|
||||||
|
request: OrganizationUserConfirmRequest,
|
||||||
|
): Promise<void> {
|
||||||
|
return this.apiService.send(
|
||||||
|
"POST",
|
||||||
|
"/organizations/" + organizationId + "/users/" + id + "/auto-confirm",
|
||||||
|
request,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async postOrganizationUsersPublicKey(
|
async postOrganizationUsersPublicKey(
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
ids: string[],
|
ids: string[],
|
||||||
|
|||||||
@@ -19,12 +19,10 @@ import { OrganizationId } from "@bitwarden/common/types/guid";
|
|||||||
import { OrgKey } from "@bitwarden/common/types/key";
|
import { OrgKey } from "@bitwarden/common/types/key";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { OrganizationUserView } from "../../../core/views/organization-user.view";
|
import { DefaultOrganizationUserService } from "./default-organization-user.service";
|
||||||
|
|
||||||
import { OrganizationUserService } from "./organization-user.service";
|
describe("DefaultOrganizationUserService", () => {
|
||||||
|
let service: DefaultOrganizationUserService;
|
||||||
describe("OrganizationUserService", () => {
|
|
||||||
let service: OrganizationUserService;
|
|
||||||
let keyService: jest.Mocked<KeyService>;
|
let keyService: jest.Mocked<KeyService>;
|
||||||
let encryptService: jest.Mocked<EncryptService>;
|
let encryptService: jest.Mocked<EncryptService>;
|
||||||
let organizationUserApiService: jest.Mocked<OrganizationUserApiService>;
|
let organizationUserApiService: jest.Mocked<OrganizationUserApiService>;
|
||||||
@@ -34,9 +32,7 @@ describe("OrganizationUserService", () => {
|
|||||||
const mockOrganization = new Organization();
|
const mockOrganization = new Organization();
|
||||||
mockOrganization.id = "org-123" as OrganizationId;
|
mockOrganization.id = "org-123" as OrganizationId;
|
||||||
|
|
||||||
const mockOrganizationUser = new OrganizationUserView();
|
const mockUserId = "user-123";
|
||||||
mockOrganizationUser.id = "user-123";
|
|
||||||
|
|
||||||
const mockPublicKey = new Uint8Array(64) as CsprngArray;
|
const mockPublicKey = new Uint8Array(64) as CsprngArray;
|
||||||
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||||
const mockOrgKey = new SymmetricCryptoKey(mockRandomBytes) as OrgKey;
|
const mockOrgKey = new SymmetricCryptoKey(mockRandomBytes) as OrgKey;
|
||||||
@@ -77,7 +73,7 @@ describe("OrganizationUserService", () => {
|
|||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
OrganizationUserService,
|
DefaultOrganizationUserService,
|
||||||
{ provide: KeyService, useValue: keyService },
|
{ provide: KeyService, useValue: keyService },
|
||||||
{ provide: EncryptService, useValue: encryptService },
|
{ provide: EncryptService, useValue: encryptService },
|
||||||
{ provide: OrganizationUserApiService, useValue: organizationUserApiService },
|
{ provide: OrganizationUserApiService, useValue: organizationUserApiService },
|
||||||
@@ -86,7 +82,13 @@ describe("OrganizationUserService", () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
service = TestBed.inject(OrganizationUserService);
|
service = new DefaultOrganizationUserService(
|
||||||
|
keyService,
|
||||||
|
encryptService,
|
||||||
|
organizationUserApiService,
|
||||||
|
accountService,
|
||||||
|
i18nService,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("confirmUser", () => {
|
describe("confirmUser", () => {
|
||||||
@@ -97,7 +99,7 @@ describe("OrganizationUserService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should confirm a user successfully", (done) => {
|
it("should confirm a user successfully", (done) => {
|
||||||
service.confirmUser(mockOrganization, mockOrganizationUser, mockPublicKey).subscribe({
|
service.confirmUser(mockOrganization, mockUserId, mockPublicKey).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
expect(i18nService.t).toHaveBeenCalledWith("myItems");
|
expect(i18nService.t).toHaveBeenCalledWith("myItems");
|
||||||
|
|
||||||
@@ -112,7 +114,7 @@ describe("OrganizationUserService", () => {
|
|||||||
|
|
||||||
expect(organizationUserApiService.postOrganizationUserConfirm).toHaveBeenCalledWith(
|
expect(organizationUserApiService.postOrganizationUserConfirm).toHaveBeenCalledWith(
|
||||||
mockOrganization.id,
|
mockOrganization.id,
|
||||||
mockOrganizationUser.id,
|
mockUserId,
|
||||||
{
|
{
|
||||||
key: mockEncryptedKey.encryptedString,
|
key: mockEncryptedKey.encryptedString,
|
||||||
defaultUserCollectionName: mockEncryptedCollectionName.encryptedString,
|
defaultUserCollectionName: mockEncryptedCollectionName.encryptedString,
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Injectable } from "@angular/core";
|
|
||||||
import { combineLatest, filter, map, Observable, switchMap } from "rxjs";
|
import { combineLatest, filter, map, Observable, switchMap } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -6,6 +5,7 @@ import {
|
|||||||
OrganizationUserBulkConfirmRequest,
|
OrganizationUserBulkConfirmRequest,
|
||||||
OrganizationUserApiService,
|
OrganizationUserApiService,
|
||||||
OrganizationUserBulkResponse,
|
OrganizationUserBulkResponse,
|
||||||
|
OrganizationUserService,
|
||||||
} from "@bitwarden/admin-console/common";
|
} from "@bitwarden/admin-console/common";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
@@ -16,12 +16,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
|||||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { OrganizationUserView } from "../../../core/views/organization-user.view";
|
export class DefaultOrganizationUserService implements OrganizationUserService {
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: "root",
|
|
||||||
})
|
|
||||||
export class OrganizationUserService {
|
|
||||||
constructor(
|
constructor(
|
||||||
protected keyService: KeyService,
|
protected keyService: KeyService,
|
||||||
private encryptService: EncryptService,
|
private encryptService: EncryptService,
|
||||||
@@ -39,11 +34,10 @@ export class OrganizationUserService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmUser(
|
buildConfirmRequest(
|
||||||
organization: Organization,
|
organization: Organization,
|
||||||
user: OrganizationUserView,
|
|
||||||
publicKey: Uint8Array,
|
publicKey: Uint8Array,
|
||||||
): Observable<void> {
|
): Observable<OrganizationUserConfirmRequest> {
|
||||||
const encryptedCollectionName$ = this.getEncryptedDefaultCollectionName$(organization);
|
const encryptedCollectionName$ = this.getEncryptedDefaultCollectionName$(organization);
|
||||||
|
|
||||||
const encryptedKey$ = this.orgKey$(organization).pipe(
|
const encryptedKey$ = this.orgKey$(organization).pipe(
|
||||||
@@ -51,18 +45,22 @@ export class OrganizationUserService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return combineLatest([encryptedKey$, encryptedCollectionName$]).pipe(
|
return combineLatest([encryptedKey$, encryptedCollectionName$]).pipe(
|
||||||
switchMap(([key, collectionName]) => {
|
map(([key, collectionName]) => ({
|
||||||
const request: OrganizationUserConfirmRequest = {
|
key: key.encryptedString,
|
||||||
key: key.encryptedString,
|
defaultUserCollectionName: collectionName.encryptedString,
|
||||||
defaultUserCollectionName: collectionName.encryptedString,
|
})),
|
||||||
};
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return this.organizationUserApiService.postOrganizationUserConfirm(
|
confirmUser(organization: Organization, userId: string, publicKey: Uint8Array): Observable<void> {
|
||||||
|
return this.buildConfirmRequest(organization, publicKey).pipe(
|
||||||
|
switchMap((request) =>
|
||||||
|
this.organizationUserApiService.postOrganizationUserConfirm(
|
||||||
organization.id,
|
organization.id,
|
||||||
user.id,
|
userId,
|
||||||
request,
|
request,
|
||||||
);
|
),
|
||||||
}),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * from "./default-organization-user-api.service";
|
export * from "./default-organization-user-api.service";
|
||||||
|
export * from "./default-organization-user.service";
|
||||||
|
|||||||
Reference in New Issue
Block a user