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:
@@ -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,
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user