1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

[AC-2828] Add provider portal members page behind FF (#9949)

* Add provider portal members page behind a FF

* Fix reinvite issue

* Import scrolling module

* Add deprecations to old classes

* Move members.component init to constructor

* Rename new-base.people.component to base.members.component

* Hide bulk reinvite when no users can be re-invited on AC members page

* Rename events() to openEventsDialog()

* Fix return type for members component getUsers()

* Make table headers sortable

* Extract row height class to ts file

* Convert open methods to static methods for bulk dialogs

* Rename and refactor member-dialog.component

* Prevent event emission for searchControl and set filter in members component constructor

* use featureFlaggedRoute rather than using FF in components

* Add BaseBulkConfirmComponent for use in both web and bit-web

* Add BaseBulkRemoveComponent for use in both web and bit-web

* Thomas' feedback on base confirm/remove

* Remaining feedback
This commit is contained in:
Alex Morask
2024-07-15 11:56:11 -04:00
committed by GitHub
parent 4edbd65faf
commit e7b50e790a
21 changed files with 982 additions and 27 deletions

View File

@@ -4,7 +4,6 @@ import { FormControl } from "@angular/forms";
import { firstValueFrom, lastValueFrom, debounceTime, combineLatest, BehaviorSubject } from "rxjs";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import {
@@ -35,7 +34,7 @@ export type UserViewTypes = ProviderUserUserDetailsResponse | OrganizationUserVi
* This will replace BasePeopleComponent once all subclasses have been changed over to use this class.
*/
@Directive()
export abstract class NewBasePeopleComponent<UserView extends UserViewTypes> {
export abstract class BaseMembersComponent<UserView extends UserViewTypes> {
/**
* Shows a banner alerting the admin that users need to be confirmed.
*/
@@ -52,6 +51,10 @@ export abstract class NewBasePeopleComponent<UserView extends UserViewTypes> {
return this.dataSource.acceptedUserCount > 0;
}
get showBulkReinviteUsers(): boolean {
return this.dataSource.invitedUserCount > 0;
}
abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
@@ -77,7 +80,6 @@ export abstract class NewBasePeopleComponent<UserView extends UserViewTypes> {
protected i18nService: I18nService,
protected cryptoService: CryptoService,
protected validationService: ValidationService,
protected modalService: ModalService,
private logService: LogService,
protected userNamePipe: UserNamePipe,
protected dialogService: DialogService,

View File

@@ -4,7 +4,7 @@ import {
} from "@bitwarden/common/admin-console/enums";
import { TableDataSource } from "@bitwarden/components";
import { StatusType, UserViewTypes } from "./new-base.people.component";
import { StatusType, UserViewTypes } from "./base-members.component";
const MaxCheckedCount = 500;

View File

@@ -0,0 +1,99 @@
import { Directive, OnInit } from "@angular/core";
import {
OrganizationUserBulkPublicKeyResponse,
OrganizationUserBulkResponse,
} from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
import { ProviderUserBulkPublicKeyResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk-public-key.response";
import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { BulkUserDetails } from "./bulk-status.component";
@Directive()
export abstract class BaseBulkConfirmComponent implements OnInit {
protected users: BulkUserDetails[];
protected excludedUsers: BulkUserDetails[];
protected filteredUsers: BulkUserDetails[];
protected publicKeys: Map<string, Uint8Array> = new Map();
protected fingerprints: Map<string, string> = new Map();
protected statuses: Map<string, string> = new Map();
protected done = false;
protected loading = true;
protected error: string;
protected constructor(
protected cryptoService: CryptoService,
protected i18nService: I18nService,
) {}
async ngOnInit() {
this.excludedUsers = this.users.filter((user) => !this.isAccepted(user));
this.filteredUsers = this.users.filter((user) => this.isAccepted(user));
if (this.filteredUsers.length <= 0) {
this.done = true;
}
const publicKeysResponse = await this.getPublicKeys();
for (const entry of publicKeysResponse.data) {
const publicKey = Utils.fromB64ToArray(entry.key);
const fingerprint = await this.cryptoService.getFingerprint(entry.userId, publicKey);
if (fingerprint != null) {
this.publicKeys.set(entry.id, publicKey);
this.fingerprints.set(entry.id, fingerprint.join("-"));
}
}
this.loading = false;
}
submit = async () => {
this.loading = true;
try {
const key = await this.getCryptoKey();
const userIdsWithKeys: { id: string; key: string }[] = [];
for (const user of this.filteredUsers) {
const publicKey = this.publicKeys.get(user.id);
if (publicKey == null) {
continue;
}
const encryptedKey = await this.cryptoService.rsaEncrypt(key.key, publicKey);
userIdsWithKeys.push({
id: user.id,
key: encryptedKey.encryptedString,
});
}
const userBulkResponse = await this.postConfirmRequest(userIdsWithKeys);
userBulkResponse.data.forEach((entry) => {
const error = entry.error !== "" ? entry.error : this.i18nService.t("bulkConfirmMessage");
this.statuses.set(entry.id, error);
});
this.done = true;
} catch (e) {
this.error = e.message;
}
this.loading = false;
};
protected abstract getCryptoKey(): Promise<SymmetricCryptoKey>;
protected abstract getPublicKeys(): Promise<
ListResponse<OrganizationUserBulkPublicKeyResponse | ProviderUserBulkPublicKeyResponse>
>;
protected abstract isAccepted(user: BulkUserDetails): boolean;
protected abstract postConfirmRequest(
userIdsWithKeys: { id: string; key: string }[],
): Promise<ListResponse<OrganizationUserBulkResponse | ProviderUserBulkResponse>>;
}

View File

@@ -0,0 +1,40 @@
import { Directive } from "@angular/core";
import { OrganizationUserBulkResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@Directive()
export abstract class BaseBulkRemoveComponent {
protected showNoMasterPasswordWarning: boolean;
protected statuses: Map<string, string> = new Map();
protected done = false;
protected loading = false;
protected error: string;
protected constructor(protected i18nService: I18nService) {}
submit = async () => {
this.loading = true;
try {
const deleteUsersResponse = await this.deleteUsers();
deleteUsersResponse.data.forEach((entry) => {
const error = entry.error !== "" ? entry.error : this.i18nService.t("bulkRemovedMessage");
this.statuses.set(entry.id, error);
});
this.done = true;
} catch (e) {
this.error = e.message;
}
this.loading = false;
};
protected abstract deleteUsers(): Promise<
ListResponse<OrganizationUserBulkResponse | ProviderUserBulkResponse>
>;
protected abstract get removeUsersWarning(): string;
}

View File

@@ -33,7 +33,7 @@ type BulkStatusDialogData = {
users: Array<OrganizationUserView | ProviderUserUserDetailsResponse>;
filteredUsers: Array<OrganizationUserView | ProviderUserUserDetailsResponse>;
request: Promise<ListResponse<OrganizationUserBulkResponse | ProviderUserBulkResponse>>;
successfullMessage: string;
successfulMessage: string;
};
@Component({
@@ -67,7 +67,7 @@ export class BulkStatusComponent implements OnInit {
);
this.users = data.users.map((user) => {
let message = keyedErrors[user.id] ?? data.successfullMessage;
let message = keyedErrors[user.id] ?? data.successfulMessage;
// eslint-disable-next-line
if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
message = this.i18nService.t("bulkFilteredMessage");

View File

@@ -103,7 +103,12 @@
</button>
<bit-menu-divider></bit-menu-divider>
</ng-container>
<button type="button" bitMenuItem (click)="bulkReinvite()">
<button
type="button"
bitMenuItem
(click)="bulkReinvite()"
*ngIf="showBulkReinviteUsers"
>
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
{{ "reinviteSelected" | i18n }}
</button>

View File

@@ -45,7 +45,7 @@ import { Collection } from "@bitwarden/common/vault/models/domain/collection";
import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/response/collection.response";
import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components";
import { NewBasePeopleComponent } from "../../common/new-base.people.component";
import { BaseMembersComponent } from "../../common/base-members.component";
import { PeopleTableDataSource } from "../../common/people-table-data-source";
import { GroupService } from "../core";
import { OrganizationUserView } from "../core/views/organization-user.view";
@@ -70,7 +70,7 @@ class MembersTableDataSource extends PeopleTableDataSource<OrganizationUserView>
@Component({
templateUrl: "members.component.html",
})
export class MembersComponent extends NewBasePeopleComponent<OrganizationUserView> {
export class MembersComponent extends BaseMembersComponent<OrganizationUserView> {
@ViewChild("resetPasswordTemplate", { read: ViewContainerRef, static: true })
resetPasswordModalRef: ViewContainerRef;
@@ -94,7 +94,6 @@ export class MembersComponent extends NewBasePeopleComponent<OrganizationUserVie
apiService: ApiService,
i18nService: I18nService,
organizationManagementPreferencesService: OrganizationManagementPreferencesService,
modalService: ModalService,
cryptoService: CryptoService,
validationService: ValidationService,
logService: LogService,
@@ -112,13 +111,13 @@ export class MembersComponent extends NewBasePeopleComponent<OrganizationUserVie
private groupService: GroupService,
private collectionService: CollectionService,
private billingApiService: BillingApiServiceAbstraction,
private modalService: ModalService,
) {
super(
apiService,
i18nService,
cryptoService,
validationService,
modalService,
logService,
userNamePipe,
dialogService,
@@ -564,7 +563,7 @@ export class MembersComponent extends NewBasePeopleComponent<OrganizationUserVie
users: users,
filteredUsers: filteredUsers,
request: response,
successfullMessage: this.i18nService.t("bulkReinviteMessage"),
successfulMessage: this.i18nService.t("bulkReinviteMessage"),
},
});
await lastValueFrom(dialogRef.closed);