diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts index dc7b079fefe..154a683b0e1 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts @@ -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; 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, + ); + } + }), + ), ); } } diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 1fa4c8bf8f7..6848f76286f 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -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({ diff --git a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts index 423977e73c4..5924c2f7814 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts @@ -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; let organizationUserService: MockProxy; + let configService: MockProxy; let organizationMetadataService: MockProxy; const organizationId = newGuid() as OrganizationId; @@ -42,6 +44,7 @@ describe("MemberActionsService", () => { beforeEach(() => { organizationUserApiService = mock(); organizationUserService = mock(); + configService = mock(); organizationMetadataService = mock(); 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 }); + }); }); }); diff --git a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts index e8c4a21d675..3b0db124a6b 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts @@ -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 { 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) { diff --git a/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts b/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts index cbaece1b442..1dc0e0b3bef 100644 --- a/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts +++ b/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts @@ -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; + /** + * 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; + /** * 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>; + /** + * 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>; + /** * Remove an organization user's access to the organization and delete their account data * @param organizationId - Identifier for the organization the user belongs to diff --git a/libs/admin-console/src/common/organization-user/abstractions/organization-user.service.ts b/libs/admin-console/src/common/organization-user/abstractions/organization-user.service.ts index 844a0f412be..03e6840d786 100644 --- a/libs/admin-console/src/common/organization-user/abstractions/organization-user.service.ts +++ b/libs/admin-console/src/common/organization-user/abstractions/organization-user.service.ts @@ -42,4 +42,11 @@ export abstract class OrganizationUserService { organization: Organization, userIdsWithKeys: { id: string; key: string }[], ): Observable>; + + abstract restoreUser(organization: Organization, userId: string): Observable; + + abstract bulkRestoreUsers( + organization: Organization, + userIds: string[], + ): Observable>; } diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-bulk-restore.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-bulk-restore.request.ts new file mode 100644 index 00000000000..74a91897a58 --- /dev/null +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-bulk-restore.request.ts @@ -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; + } +} diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-restore.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-restore.request.ts new file mode 100644 index 00000000000..c4607065845 --- /dev/null +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-restore.request.ts @@ -0,0 +1,9 @@ +import { EncString } from "@bitwarden/sdk-internal"; + +export class OrganizationUserRestoreRequest { + defaultUserCollectionName: EncString | undefined; + + constructor(defaultUserCollectionName?: EncString) { + this.defaultUserCollectionName = defaultUserCollectionName; + } +} diff --git a/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts b/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts index 536afd2b3f6..e5609f75251 100644 --- a/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts +++ b/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts @@ -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 { + 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> { + 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 { return this.apiService.send( "DELETE", diff --git a/libs/admin-console/src/common/organization-user/services/default-organization-user.service.spec.ts b/libs/admin-console/src/common/organization-user/services/default-organization-user.service.spec.ts index 982fb3ca5e0..0448b23e4d2 100644 --- a/libs/admin-console/src/common/organization-user/services/default-organization-user.service.spec.ts +++ b/libs/admin-console/src/common/organization-user/services/default-organization-user.service.spec.ts @@ -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; + + 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, + }); + }); + }); }); diff --git a/libs/admin-console/src/common/organization-user/services/default-organization-user.service.ts b/libs/admin-console/src/common/organization-user/services/default-organization-user.service.ts index 4f503a92675..d54743e2f7b 100644 --- a/libs/admin-console/src/common/organization-user/services/default-organization-user.service.ts +++ b/libs/admin-console/src/common/organization-user/services/default-organization-user.service.ts @@ -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 { + return this.getEncryptedDefaultCollectionName$(organization).pipe( + map((collectionName) => new OrganizationUserRestoreRequest(collectionName.encryptedString)), + ); + } + + restoreUser(organization: Organization, userId: string): Observable { + return this.buildRestoreUserRequest(organization).pipe( + switchMap((request) => + this.organizationUserApiService.restoreOrganizationUser_vNext( + organization.id, + userId, + request, + ), + ), + ); + } + + bulkRestoreUsers( + organization: Organization, + userIds: string[], + ): Observable> { + 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) => diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index ac5f3c10260..9d9de56c608 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -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 */