1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-28 15:23:53 +00:00

Added encrypted default collection name to new feature flagged restore user methods/endpoint.

This commit is contained in:
Jared McCannon
2026-01-16 16:21:48 -06:00
parent a005921c40
commit 4253f05de7
10 changed files with 206 additions and 12 deletions

View File

@@ -1,10 +1,20 @@
// 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, from, 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 { 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 +44,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 +61,12 @@ export class BulkRestoreRevokeComponent {
this.showNoMasterPasswordWarning = this.users.some(
(u) => u.status > OrganizationUserStatusType.Invited && u.hasMasterPassword === false,
);
this.organization$ = accountService.activeAccount$.pipe(
switchMap((account) =>
organizationService.organizations$(account?.id).pipe(getById(this.organizationId)),
),
);
}
get bulkTitle() {
@@ -83,9 +104,24 @@ 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 from(
this.organizationUserApiService.restoreManyOrganizationUsers(
this.organizationId,
userIds,
),
);
}
}),
),
);
}
}

View File

@@ -5,6 +5,7 @@ import { FormBuilder, Validators } from "@angular/forms";
import {
combineLatest,
firstValueFrom,
from,
map,
Observable,
of,
@@ -17,6 +18,7 @@ import {
import {
CollectionAdminService,
OrganizationUserApiService,
OrganizationUserService,
} from "@bitwarden/admin-console/common";
import {
getOrganizationById,
@@ -36,6 +38,7 @@ 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 {
@@ -197,6 +200,7 @@ export class MemberDialogComponent implements OnDestroy {
private toastService: ToastService,
private configService: ConfigService,
private deleteManagedMemberWarningService: DeleteManagedMemberWarningService,
private organizationUserService: OrganizationUserService,
) {
this.organization$ = accountService.activeAccount$.pipe(
switchMap((account) =>
@@ -633,9 +637,28 @@ 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 from(
this.organizationUserApiService.restoreOrganizationUser(
params.organizationId,
params.organizationUserId,
),
);
}
}),
),
);
this.toastService.showToast({

View File

@@ -1,5 +1,5 @@
import { inject, Injectable, signal } from "@angular/core";
import { lastValueFrom, firstValueFrom } from "rxjs";
import { lastValueFrom, firstValueFrom, from, switchMap } from "rxjs";
import {
OrganizationUserApiService,
@@ -10,8 +10,8 @@ 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";
@@ -119,7 +119,20 @@ 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 from(
this.organizationUserApiService.restoreOrganizationUser(organization.id, userId),
);
}
}),
),
);
this.organizationMetadataService.refreshMetadataCache();
return { success: true };
} catch (error) {

View File

@@ -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

View File

@@ -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>>;
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,5 @@
import { EncString } from "@bitwarden/sdk-internal";
export class OrganizationUserRestoreRequest {
defaultUserCollectionName: EncString | undefined;
}

View File

@@ -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",
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",

View File

@@ -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,45 @@ export class DefaultOrganizationUserService implements OrganizationUserService {
);
}
buildRestoreUserRequest(organization: Organization): Observable<OrganizationUserRestoreRequest> {
return this.getEncryptedDefaultCollectionName$(organization).pipe(
map((collectionName) => ({
defaultUserCollectionName: 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) =>

View File

@@ -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",
IncreaseBulkReinviteLimitForCloud = "pm-28251-increase-bulk-reinvite-limit-for-cloud",
MembersComponentRefactor = "pm-29503-refactor-members-inheritance",
@@ -100,6 +101,7 @@ export const DefaultFeatureFlagValue = {
/* Admin Console Team */
[FeatureFlag.AutoConfirm]: FALSE,
[FeatureFlag.BlockClaimedDomainAccountCreation]: FALSE,
[FeatureFlag.DefaultUserCollectionRestore]: FALSE,
[FeatureFlag.IncreaseBulkReinviteLimitForCloud]: FALSE,
[FeatureFlag.MembersComponentRefactor]: FALSE,