mirror of
https://github.com/bitwarden/browser
synced 2026-01-31 00:33:33 +00:00
[PM-30891] - Create My Items On Restore (#18454)
* Added encrypted default collection name to new feature flagged restore user methods/endpoint. * corrected filter to use null check with imperative code
This commit is contained in:
@@ -1,10 +1,21 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { combineLatest, firstValueFrom, map, Observable, switchMap } from "rxjs";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserService,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { getById } from "@bitwarden/common/platform/misc";
|
||||
import { DIALOG_DATA, DialogService } from "@bitwarden/components";
|
||||
|
||||
import { BulkUserDetails } from "./bulk-status.component";
|
||||
@@ -34,10 +45,15 @@ export class BulkRestoreRevokeComponent {
|
||||
error: string;
|
||||
showNoMasterPasswordWarning = false;
|
||||
nonCompliantMembers: boolean = false;
|
||||
organization$: Observable<Organization>;
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private accountService: AccountService,
|
||||
private organizationService: OrganizationService,
|
||||
private configService: ConfigService,
|
||||
@Inject(DIALOG_DATA) protected data: BulkRestoreDialogParams,
|
||||
) {
|
||||
this.isRevoking = data.isRevoking;
|
||||
@@ -46,6 +62,18 @@ export class BulkRestoreRevokeComponent {
|
||||
this.showNoMasterPasswordWarning = this.users.some(
|
||||
(u) => u.status > OrganizationUserStatusType.Invited && u.hasMasterPassword === false,
|
||||
);
|
||||
|
||||
this.organization$ = accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => organizationService.organizations$(userId)),
|
||||
getById(this.organizationId),
|
||||
map((organization) => {
|
||||
if (organization == null) {
|
||||
throw new Error("Organization not found");
|
||||
}
|
||||
return organization;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
get bulkTitle() {
|
||||
@@ -83,9 +111,22 @@ export class BulkRestoreRevokeComponent {
|
||||
userIds,
|
||||
);
|
||||
} else {
|
||||
return await this.organizationUserApiService.restoreManyOrganizationUsers(
|
||||
this.organizationId,
|
||||
userIds,
|
||||
return await firstValueFrom(
|
||||
combineLatest([
|
||||
this.configService.getFeatureFlag$(FeatureFlag.DefaultUserCollectionRestore),
|
||||
this.organization$,
|
||||
]).pipe(
|
||||
switchMap(([enabled, organization]) => {
|
||||
if (enabled) {
|
||||
return this.organizationUserService.bulkRestoreUsers(organization, userIds);
|
||||
} else {
|
||||
return this.organizationUserApiService.restoreManyOrganizationUsers(
|
||||
this.organizationId,
|
||||
userIds,
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,9 @@ import {
|
||||
import {
|
||||
CollectionAdminService,
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserService,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
getOrganizationById,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import {
|
||||
OrganizationUserStatusType,
|
||||
OrganizationUserType,
|
||||
@@ -36,8 +34,10 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { getById } from "@bitwarden/common/platform/misc";
|
||||
import {
|
||||
DIALOG_DATA,
|
||||
DialogConfig,
|
||||
@@ -197,14 +197,19 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
private toastService: ToastService,
|
||||
private configService: ConfigService,
|
||||
private deleteManagedMemberWarningService: DeleteManagedMemberWarningService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
) {
|
||||
this.organization$ = accountService.activeAccount$.pipe(
|
||||
switchMap((account) =>
|
||||
organizationService
|
||||
.organizations$(account?.id)
|
||||
.pipe(getOrganizationById(this.params.organizationId))
|
||||
.pipe(shareReplay({ refCount: true, bufferSize: 1 })),
|
||||
),
|
||||
getUserId,
|
||||
switchMap((userId) => organizationService.organizations$(userId)),
|
||||
getById(this.params.organizationId),
|
||||
map((organization) => {
|
||||
if (organization == null) {
|
||||
throw new Error("Organization not found");
|
||||
}
|
||||
return organization;
|
||||
}),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
let userDetails$;
|
||||
@@ -633,9 +638,26 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.organizationUserApiService.restoreOrganizationUser(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId,
|
||||
await firstValueFrom(
|
||||
combineLatest([
|
||||
this.configService.getFeatureFlag$(FeatureFlag.DefaultUserCollectionRestore),
|
||||
this.organization$,
|
||||
this.editParams$,
|
||||
]).pipe(
|
||||
switchMap(([enabled, organization, params]) => {
|
||||
if (enabled) {
|
||||
return this.organizationUserService.restoreUser(
|
||||
organization,
|
||||
params.organizationUserId,
|
||||
);
|
||||
} else {
|
||||
return this.organizationUserApiService.restoreOrganizationUser(
|
||||
params.organizationId,
|
||||
params.organizationUserId,
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
this.toastService.showToast({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
import { of, throwError } from "rxjs";
|
||||
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { OrganizationMetadataServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-metadata.service.abstraction";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
@@ -31,6 +32,7 @@ describe("MemberActionsService", () => {
|
||||
let service: MemberActionsService;
|
||||
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
|
||||
let organizationUserService: MockProxy<OrganizationUserService>;
|
||||
let configService: MockProxy<ConfigService>;
|
||||
let organizationMetadataService: MockProxy<OrganizationMetadataServiceAbstraction>;
|
||||
|
||||
const organizationId = newGuid() as OrganizationId;
|
||||
@@ -42,6 +44,7 @@ describe("MemberActionsService", () => {
|
||||
beforeEach(() => {
|
||||
organizationUserApiService = mock<OrganizationUserApiService>();
|
||||
organizationUserService = mock<OrganizationUserService>();
|
||||
configService = mock<ConfigService>();
|
||||
organizationMetadataService = mock<OrganizationMetadataServiceAbstraction>();
|
||||
|
||||
mockOrganization = {
|
||||
@@ -65,6 +68,7 @@ describe("MemberActionsService", () => {
|
||||
MemberActionsService,
|
||||
{ provide: OrganizationUserApiService, useValue: organizationUserApiService },
|
||||
{ provide: OrganizationUserService, useValue: organizationUserService },
|
||||
{ provide: ConfigService, useValue: configService },
|
||||
{
|
||||
provide: OrganizationMetadataServiceAbstraction,
|
||||
useValue: organizationMetadataService,
|
||||
@@ -174,25 +178,64 @@ describe("MemberActionsService", () => {
|
||||
});
|
||||
|
||||
describe("restoreUser", () => {
|
||||
it("should successfully restore a user", async () => {
|
||||
organizationUserApiService.restoreOrganizationUser.mockResolvedValue(undefined);
|
||||
describe("when feature flag is enabled", () => {
|
||||
beforeEach(() => {
|
||||
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
});
|
||||
|
||||
const result = await service.restoreUser(mockOrganization, userIdToManage);
|
||||
it("should call organizationUserService.restoreUser", async () => {
|
||||
organizationUserService.restoreUser.mockReturnValue(of(undefined));
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(organizationUserApiService.restoreOrganizationUser).toHaveBeenCalledWith(
|
||||
organizationId,
|
||||
userIdToManage,
|
||||
);
|
||||
const result = await service.restoreUser(mockOrganization, userIdToManage);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(organizationUserService.restoreUser).toHaveBeenCalledWith(
|
||||
mockOrganization,
|
||||
userIdToManage,
|
||||
);
|
||||
expect(organizationUserApiService.restoreOrganizationUser).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle errors from organizationUserService.restoreUser", async () => {
|
||||
const errorMessage = "Restore failed";
|
||||
organizationUserService.restoreUser.mockReturnValue(
|
||||
throwError(() => new Error(errorMessage)),
|
||||
);
|
||||
|
||||
const result = await service.restoreUser(mockOrganization, userIdToManage);
|
||||
|
||||
expect(result).toEqual({ success: false, error: errorMessage });
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle restore errors", async () => {
|
||||
const errorMessage = "Restore failed";
|
||||
organizationUserApiService.restoreOrganizationUser.mockRejectedValue(new Error(errorMessage));
|
||||
describe("when feature flag is disabled", () => {
|
||||
beforeEach(() => {
|
||||
configService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
});
|
||||
|
||||
const result = await service.restoreUser(mockOrganization, userIdToManage);
|
||||
it("should call organizationUserApiService.restoreOrganizationUser", async () => {
|
||||
organizationUserApiService.restoreOrganizationUser.mockResolvedValue(undefined);
|
||||
|
||||
expect(result).toEqual({ success: false, error: errorMessage });
|
||||
const result = await service.restoreUser(mockOrganization, userIdToManage);
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(organizationUserApiService.restoreOrganizationUser).toHaveBeenCalledWith(
|
||||
organizationId,
|
||||
userIdToManage,
|
||||
);
|
||||
expect(organizationUserService.restoreUser).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle errors", async () => {
|
||||
const errorMessage = "Restore failed";
|
||||
organizationUserApiService.restoreOrganizationUser.mockRejectedValue(
|
||||
new Error(errorMessage),
|
||||
);
|
||||
|
||||
const result = await service.restoreUser(mockOrganization, userIdToManage);
|
||||
|
||||
expect(result).toEqual({ success: false, error: errorMessage });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { inject, Injectable, signal } from "@angular/core";
|
||||
import { lastValueFrom, firstValueFrom } from "rxjs";
|
||||
import { lastValueFrom, firstValueFrom, switchMap } from "rxjs";
|
||||
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
@@ -10,13 +10,15 @@ import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
|
||||
import {
|
||||
OrganizationUserType,
|
||||
OrganizationUserStatusType,
|
||||
OrganizationUserType,
|
||||
} from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { assertNonNullish } from "@bitwarden/common/auth/utils";
|
||||
import { OrganizationMetadataServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-metadata.service.abstraction";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
@@ -43,6 +45,7 @@ export interface BulkActionResult {
|
||||
export class MemberActionsService {
|
||||
private organizationUserApiService = inject(OrganizationUserApiService);
|
||||
private organizationUserService = inject(OrganizationUserService);
|
||||
private configService = inject(ConfigService);
|
||||
private organizationMetadataService = inject(OrganizationMetadataServiceAbstraction);
|
||||
private apiService = inject(ApiService);
|
||||
private dialogService = inject(DialogService);
|
||||
@@ -116,7 +119,21 @@ export class MemberActionsService {
|
||||
async restoreUser(organization: Organization, userId: string): Promise<MemberActionResult> {
|
||||
this.startProcessing();
|
||||
try {
|
||||
await this.organizationUserApiService.restoreOrganizationUser(organization.id, userId);
|
||||
await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.DefaultUserCollectionRestore).pipe(
|
||||
switchMap((enabled) => {
|
||||
if (enabled) {
|
||||
return this.organizationUserService.restoreUser(organization, userId);
|
||||
} else {
|
||||
return this.organizationUserApiService.restoreOrganizationUser(
|
||||
organization.id,
|
||||
userId,
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
this.organizationMetadataService.refreshMetadataCache();
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
OrganizationUserResetPasswordRequest,
|
||||
OrganizationUserUpdateRequest,
|
||||
} from "../models/requests";
|
||||
import { OrganizationUserBulkRestoreRequest } from "../models/requests/organization-user-bulk-restore.request";
|
||||
import { OrganizationUserRestoreRequest } from "../models/requests/organization-user-restore.request";
|
||||
import {
|
||||
OrganizationUserBulkPublicKeyResponse,
|
||||
OrganizationUserBulkResponse,
|
||||
@@ -278,6 +280,18 @@ export abstract class OrganizationUserApiService {
|
||||
*/
|
||||
abstract restoreOrganizationUser(organizationId: string, id: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Restore an organization user's access to the organization
|
||||
* @param organizationId - Identifier for the organization the user belongs to
|
||||
* @param id - Organization user identifier
|
||||
* @param request - Restore request containing default user collection name
|
||||
*/
|
||||
abstract restoreOrganizationUser_vNext(
|
||||
organizationId: string,
|
||||
id: string,
|
||||
request: OrganizationUserRestoreRequest,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Restore many organization users' access to the organization
|
||||
* @param organizationId - Identifier for the organization the users belongs to
|
||||
@@ -289,6 +303,17 @@ export abstract class OrganizationUserApiService {
|
||||
ids: string[],
|
||||
): Promise<ListResponse<OrganizationUserBulkResponse>>;
|
||||
|
||||
/**
|
||||
* Restore many organization users' access to the organization
|
||||
* @param organizationId - Identifier for the organization the users belongs to
|
||||
* @param request - Restore request containing default user collection name
|
||||
* @return List of user ids, including both those that were successfully restored and those that had an error
|
||||
*/
|
||||
abstract restoreManyOrganizationUsers_vNext(
|
||||
organizationId: string,
|
||||
request: OrganizationUserBulkRestoreRequest,
|
||||
): Promise<ListResponse<OrganizationUserBulkResponse>>;
|
||||
|
||||
/**
|
||||
* Remove an organization user's access to the organization and delete their account data
|
||||
* @param organizationId - Identifier for the organization the user belongs to
|
||||
|
||||
@@ -42,4 +42,11 @@ export abstract class OrganizationUserService {
|
||||
organization: Organization,
|
||||
userIdsWithKeys: { id: string; key: string }[],
|
||||
): Observable<ListResponse<OrganizationUserBulkResponse>>;
|
||||
|
||||
abstract restoreUser(organization: Organization, userId: string): Observable<void>;
|
||||
|
||||
abstract bulkRestoreUsers(
|
||||
organization: Organization,
|
||||
userIds: string[],
|
||||
): Observable<ListResponse<OrganizationUserBulkResponse>>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { EncString } from "@bitwarden/sdk-internal";
|
||||
|
||||
export class OrganizationUserBulkRestoreRequest {
|
||||
userIds: string[];
|
||||
defaultUserCollectionName: EncString | undefined;
|
||||
|
||||
constructor(userIds: string[], defaultUserCollectionName?: EncString) {
|
||||
this.userIds = userIds;
|
||||
this.defaultUserCollectionName = defaultUserCollectionName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { EncString } from "@bitwarden/sdk-internal";
|
||||
|
||||
export class OrganizationUserRestoreRequest {
|
||||
defaultUserCollectionName: EncString | undefined;
|
||||
|
||||
constructor(defaultUserCollectionName?: EncString) {
|
||||
this.defaultUserCollectionName = defaultUserCollectionName;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
OrganizationUserUpdateRequest,
|
||||
OrganizationUserBulkRequest,
|
||||
} from "../models/requests";
|
||||
import { OrganizationUserBulkRestoreRequest } from "../models/requests/organization-user-bulk-restore.request";
|
||||
import { OrganizationUserRestoreRequest } from "../models/requests/organization-user-restore.request";
|
||||
import {
|
||||
OrganizationUserBulkPublicKeyResponse,
|
||||
OrganizationUserBulkResponse,
|
||||
@@ -359,6 +361,20 @@ export class DefaultOrganizationUserApiService implements OrganizationUserApiSer
|
||||
);
|
||||
}
|
||||
|
||||
restoreOrganizationUser_vNext(
|
||||
organizationId: string,
|
||||
id: string,
|
||||
request: OrganizationUserRestoreRequest,
|
||||
): Promise<void> {
|
||||
return this.apiService.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/users/" + id + "/restore/vnext",
|
||||
request,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
async restoreManyOrganizationUsers(
|
||||
organizationId: string,
|
||||
ids: string[],
|
||||
@@ -373,6 +389,20 @@ export class DefaultOrganizationUserApiService implements OrganizationUserApiSer
|
||||
return new ListResponse(r, OrganizationUserBulkResponse);
|
||||
}
|
||||
|
||||
async restoreManyOrganizationUsers_vNext(
|
||||
organizationId: string,
|
||||
request: OrganizationUserBulkRestoreRequest,
|
||||
): Promise<ListResponse<OrganizationUserBulkResponse>> {
|
||||
const r = await this.apiService.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/users/restore",
|
||||
request,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
return new ListResponse(r, OrganizationUserBulkResponse);
|
||||
}
|
||||
|
||||
deleteOrganizationUser(organizationId: string, id: string): Promise<void> {
|
||||
return this.apiService.send(
|
||||
"DELETE",
|
||||
|
||||
@@ -61,6 +61,8 @@ describe("DefaultOrganizationUserService", () => {
|
||||
organizationUserApiService = {
|
||||
postOrganizationUserConfirm: jest.fn(),
|
||||
postOrganizationUserBulkConfirm: jest.fn(),
|
||||
restoreOrganizationUser_vNext: jest.fn(),
|
||||
restoreManyOrganizationUsers_vNext: jest.fn(),
|
||||
} as any;
|
||||
|
||||
accountService = {
|
||||
@@ -174,4 +176,97 @@ describe("DefaultOrganizationUserService", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildRestoreUserRequest", () => {
|
||||
beforeEach(() => {
|
||||
setupCommonMocks();
|
||||
});
|
||||
|
||||
it("should build a restore request with encrypted collection name", (done) => {
|
||||
service.buildRestoreUserRequest(mockOrganization).subscribe({
|
||||
next: (request) => {
|
||||
expect(i18nService.t).toHaveBeenCalledWith("myItems");
|
||||
expect(encryptService.encryptString).toHaveBeenCalledWith(
|
||||
mockDefaultCollectionName,
|
||||
mockOrgKey,
|
||||
);
|
||||
expect(request).toEqual({
|
||||
defaultUserCollectionName: mockEncryptedCollectionName.encryptedString,
|
||||
});
|
||||
done();
|
||||
},
|
||||
error: done,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("restoreUser", () => {
|
||||
beforeEach(() => {
|
||||
setupCommonMocks();
|
||||
organizationUserApiService.restoreOrganizationUser_vNext.mockReturnValue(Promise.resolve());
|
||||
});
|
||||
|
||||
it("should restore a user successfully", (done) => {
|
||||
service.restoreUser(mockOrganization, mockUserId).subscribe({
|
||||
next: () => {
|
||||
expect(i18nService.t).toHaveBeenCalledWith("myItems");
|
||||
expect(encryptService.encryptString).toHaveBeenCalledWith(
|
||||
mockDefaultCollectionName,
|
||||
mockOrgKey,
|
||||
);
|
||||
expect(organizationUserApiService.restoreOrganizationUser_vNext).toHaveBeenCalledWith(
|
||||
mockOrganization.id,
|
||||
mockUserId,
|
||||
{
|
||||
defaultUserCollectionName: mockEncryptedCollectionName.encryptedString,
|
||||
},
|
||||
);
|
||||
done();
|
||||
},
|
||||
error: done,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("bulkRestoreUsers", () => {
|
||||
const mockUserIds = ["user-1", "user-2"];
|
||||
|
||||
const mockBulkResponse = {
|
||||
data: [
|
||||
{ id: "user-1", error: null } as OrganizationUserBulkResponse,
|
||||
{ id: "user-2", error: null } as OrganizationUserBulkResponse,
|
||||
],
|
||||
} as ListResponse<OrganizationUserBulkResponse>;
|
||||
|
||||
beforeEach(() => {
|
||||
setupCommonMocks();
|
||||
organizationUserApiService.restoreManyOrganizationUsers_vNext.mockReturnValue(
|
||||
Promise.resolve(mockBulkResponse),
|
||||
);
|
||||
});
|
||||
|
||||
it("should bulk restore users successfully", (done) => {
|
||||
service.bulkRestoreUsers(mockOrganization, mockUserIds).subscribe({
|
||||
next: (response) => {
|
||||
expect(i18nService.t).toHaveBeenCalledWith("myItems");
|
||||
expect(encryptService.encryptString).toHaveBeenCalledWith(
|
||||
mockDefaultCollectionName,
|
||||
mockOrgKey,
|
||||
);
|
||||
expect(
|
||||
organizationUserApiService.restoreManyOrganizationUsers_vNext,
|
||||
).toHaveBeenCalledWith(
|
||||
mockOrganization.id,
|
||||
expect.objectContaining({
|
||||
userIds: mockUserIds,
|
||||
defaultUserCollectionName: mockEncryptedCollectionName.encryptedString,
|
||||
}),
|
||||
);
|
||||
expect(response).toEqual(mockBulkResponse);
|
||||
done();
|
||||
},
|
||||
error: done,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { combineLatest, filter, map, Observable, switchMap } from "rxjs";
|
||||
|
||||
import {
|
||||
OrganizationUserConfirmRequest,
|
||||
OrganizationUserBulkConfirmRequest,
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserBulkConfirmRequest,
|
||||
OrganizationUserBulkResponse,
|
||||
OrganizationUserConfirmRequest,
|
||||
OrganizationUserService,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
@@ -16,6 +16,9 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { OrganizationUserBulkRestoreRequest } from "../models/requests/organization-user-bulk-restore.request";
|
||||
import { OrganizationUserRestoreRequest } from "../models/requests/organization-user-restore.request";
|
||||
|
||||
export class DefaultOrganizationUserService implements OrganizationUserService {
|
||||
constructor(
|
||||
protected keyService: KeyService,
|
||||
@@ -83,6 +86,43 @@ export class DefaultOrganizationUserService implements OrganizationUserService {
|
||||
);
|
||||
}
|
||||
|
||||
buildRestoreUserRequest(organization: Organization): Observable<OrganizationUserRestoreRequest> {
|
||||
return this.getEncryptedDefaultCollectionName$(organization).pipe(
|
||||
map((collectionName) => new OrganizationUserRestoreRequest(collectionName.encryptedString)),
|
||||
);
|
||||
}
|
||||
|
||||
restoreUser(organization: Organization, userId: string): Observable<void> {
|
||||
return this.buildRestoreUserRequest(organization).pipe(
|
||||
switchMap((request) =>
|
||||
this.organizationUserApiService.restoreOrganizationUser_vNext(
|
||||
organization.id,
|
||||
userId,
|
||||
request,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bulkRestoreUsers(
|
||||
organization: Organization,
|
||||
userIds: string[],
|
||||
): Observable<ListResponse<OrganizationUserBulkResponse>> {
|
||||
return this.getEncryptedDefaultCollectionName$(organization).pipe(
|
||||
switchMap((collectionName) => {
|
||||
const request = new OrganizationUserBulkRestoreRequest(
|
||||
userIds,
|
||||
collectionName.encryptedString,
|
||||
);
|
||||
|
||||
return this.organizationUserApiService.restoreManyOrganizationUsers_vNext(
|
||||
organization.id,
|
||||
request,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private getEncryptedDefaultCollectionName$(organization: Organization) {
|
||||
return this.orgKey$(organization).pipe(
|
||||
switchMap((orgKey) =>
|
||||
|
||||
@@ -13,6 +13,7 @@ export enum FeatureFlag {
|
||||
/* Admin Console Team */
|
||||
AutoConfirm = "pm-19934-auto-confirm-organization-users",
|
||||
BlockClaimedDomainAccountCreation = "pm-28297-block-uninvited-claimed-domain-registration",
|
||||
DefaultUserCollectionRestore = "pm-30883-my-items-restored-users",
|
||||
MembersComponentRefactor = "pm-29503-refactor-members-inheritance",
|
||||
|
||||
/* Auth */
|
||||
@@ -104,6 +105,7 @@ export const DefaultFeatureFlagValue = {
|
||||
/* Admin Console Team */
|
||||
[FeatureFlag.AutoConfirm]: FALSE,
|
||||
[FeatureFlag.BlockClaimedDomainAccountCreation]: FALSE,
|
||||
[FeatureFlag.DefaultUserCollectionRestore]: FALSE,
|
||||
[FeatureFlag.MembersComponentRefactor]: FALSE,
|
||||
|
||||
/* Autofill */
|
||||
|
||||
Reference in New Issue
Block a user