bitwarden-estensione-browser/apps/web/src/app/common/base.people.component.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

423 lines
13 KiB
TypeScript
Raw Normal View History

import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
2022-06-14 17:10:53 +02:00
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ValidationService } from "@bitwarden/angular/services/validation.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType";
import { ProviderUserStatusType } from "@bitwarden/common/enums/providerUserStatusType";
import { ProviderUserType } from "@bitwarden/common/enums/providerUserType";
import { Utils } from "@bitwarden/common/misc/utils";
import { ListResponse } from "@bitwarden/common/models/response/listResponse";
import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/models/response/organizationUserResponse";
import { ProviderUserUserDetailsResponse } from "@bitwarden/common/models/response/provider/providerUserResponse";
import { UserConfirmComponent } from "../organizations/manage/user-confirm.component";
type StatusType = OrganizationUserStatusType | ProviderUserStatusType;
const MaxCheckedCount = 500;
@Directive()
export abstract class BasePeopleComponent<
UserType extends ProviderUserUserDetailsResponse | OrganizationUserUserDetailsResponse
> {
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
confirmModalRef: ViewContainerRef;
2021-12-17 15:57:11 +01:00
get allCount() {
return this.activeUsers != null ? this.activeUsers.length : 0;
2021-12-17 15:57:11 +01:00
}
get invitedCount() {
return this.statusMap.has(this.userStatusType.Invited)
? this.statusMap.get(this.userStatusType.Invited).length
2021-12-17 15:57:11 +01:00
: 0;
}
get acceptedCount() {
return this.statusMap.has(this.userStatusType.Accepted)
? this.statusMap.get(this.userStatusType.Accepted).length
2021-12-17 15:57:11 +01:00
: 0;
}
get confirmedCount() {
return this.statusMap.has(this.userStatusType.Confirmed)
? this.statusMap.get(this.userStatusType.Confirmed).length
2021-12-17 15:57:11 +01:00
: 0;
}
get deactivatedCount() {
return this.statusMap.has(this.userStatusType.Deactivated)
? this.statusMap.get(this.userStatusType.Deactivated).length
: 0;
}
get showConfirmUsers(): boolean {
2021-12-17 15:57:11 +01:00
return (
this.activeUsers != null &&
this.statusMap != null &&
this.activeUsers.length > 1 &&
this.confirmedCount > 0 &&
this.confirmedCount < 3 &&
this.acceptedCount > 0
2021-12-17 15:57:11 +01:00
);
}
get showBulkConfirmUsers(): boolean {
return this.acceptedCount > 0;
2021-12-17 15:57:11 +01:00
}
abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
2021-12-17 15:57:11 +01:00
loading = true;
statusMap = new Map<StatusType, UserType[]>();
status: StatusType;
users: UserType[] = [];
pagedUsers: UserType[] = [];
searchText: string;
actionPromise: Promise<any>;
2021-12-17 15:57:11 +01:00
protected allUsers: UserType[] = [];
protected activeUsers: UserType[] = [];
2021-12-17 15:57:11 +01:00
protected didScroll = false;
protected pageSize = 100;
2021-12-17 15:57:11 +01:00
private pagedUsersCount = 0;
2021-12-17 15:57:11 +01:00
[Account Switching] [Refactor] Implement new account centric services (#1220) * [chore] updated services.module to use account services * [refactor] sorted services provided by services.module * [chore] removed references to deleted jslib services * [chore] used activeAccount over storageService for account level storage items * [chore] resolved linter warnings * Refactor activeAccountService to stateService * [bug] Remove uneeded calls to state service on logout This was causing console erros on logout. Clearing of data is handled fully in dedicated services, clearing them in state afterwards is essentially a redundant call. * [bug] Add back null locked callback to VaultTimeoutService * Move call to get showUpdateKey * [bug] Ensure HtmlStorageService does not override StateService options and locations * [bug] Adjust theme logic to pull from the new storage locations * [bug] Correct theme not sticking on refresh * [bug] Add enableFullWidth to the account model * [bug] fix theme option empty when light is selected * [bug] init state on application start * [bug] Reinit state when coming back from a lock * [style] Fix lint complaints * [bug] Clean state on logout * [chore] Resolved merge issues * [bug] Correct default for enableGravitars * Bump angular to 12. * Remove angular.json * Bump rxjs * Fix build errors, remove file-loader with asset/resource * Use contenthash * Bump jslib * Bump ngx-toastr * [chore] resolve issues from merge * [chore] resolve issues from merge * [bug] Add missing bracket * Use newer import syntax * [bug] Correct service orge * [style] Fix lint complaints * [chore] update jslib * [review] Address code review * [review] Address code review * [review] Rename providerService to webProviderService Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com> Co-authored-by: Hinton <oscar@oscarhinton.com>
2021-12-14 17:10:26 +01:00
constructor(
protected apiService: ApiService,
private searchService: SearchService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
2021-12-07 20:41:45 +01:00
protected cryptoService: CryptoService,
[Account Switching] [Refactor] Implement new account centric services (#1220) * [chore] updated services.module to use account services * [refactor] sorted services provided by services.module * [chore] removed references to deleted jslib services * [chore] used activeAccount over storageService for account level storage items * [chore] resolved linter warnings * Refactor activeAccountService to stateService * [bug] Remove uneeded calls to state service on logout This was causing console erros on logout. Clearing of data is handled fully in dedicated services, clearing them in state afterwards is essentially a redundant call. * [bug] Add back null locked callback to VaultTimeoutService * Move call to get showUpdateKey * [bug] Ensure HtmlStorageService does not override StateService options and locations * [bug] Adjust theme logic to pull from the new storage locations * [bug] Correct theme not sticking on refresh * [bug] Add enableFullWidth to the account model * [bug] fix theme option empty when light is selected * [bug] init state on application start * [bug] Reinit state when coming back from a lock * [style] Fix lint complaints * [bug] Clean state on logout * [chore] Resolved merge issues * [bug] Correct default for enableGravitars * Bump angular to 12. * Remove angular.json * Bump rxjs * Fix build errors, remove file-loader with asset/resource * Use contenthash * Bump jslib * Bump ngx-toastr * [chore] resolve issues from merge * [chore] resolve issues from merge * [bug] Add missing bracket * Use newer import syntax * [bug] Correct service orge * [style] Fix lint complaints * [chore] update jslib * [review] Address code review * [review] Address code review * [review] Rename providerService to webProviderService Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com> Co-authored-by: Hinton <oscar@oscarhinton.com>
2021-12-14 17:10:26 +01:00
protected validationService: ValidationService,
protected modalService: ModalService,
private logService: LogService,
private searchPipe: SearchPipe,
protected userNamePipe: UserNamePipe,
protected stateService: StateService
2021-12-17 15:57:11 +01:00
) {}
abstract edit(user: UserType): void;
abstract getUsers(): Promise<ListResponse<UserType>>;
abstract deleteUser(id: string): Promise<any>;
abstract deactivateUser(id: string): Promise<any>;
abstract activateUser(id: string): Promise<any>;
abstract reinviteUser(id: string): Promise<any>;
abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise<any>;
2021-12-17 15:57:11 +01:00
async load() {
const response = await this.getUsers();
this.statusMap.clear();
this.activeUsers = [];
for (const status of Utils.iterateEnum(this.userStatusType)) {
this.statusMap.set(status, []);
}
this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
this.allUsers.sort(Utils.getSortFunction(this.i18nService, "email"));
this.allUsers.forEach((u) => {
if (!this.statusMap.has(u.status)) {
this.statusMap.set(u.status, [u]);
2021-12-17 15:57:11 +01:00
} else {
this.statusMap.get(u.status).push(u);
2021-12-17 15:57:11 +01:00
}
if (u.status !== this.userStatusType.Deactivated) {
this.activeUsers.push(u);
}
2021-12-17 15:57:11 +01:00
});
this.filter(this.status);
this.loading = false;
2021-12-17 15:57:11 +01:00
}
filter(status: StatusType) {
this.status = status;
if (this.status != null) {
this.users = this.statusMap.get(this.status);
2021-12-17 15:57:11 +01:00
} else {
this.users = this.activeUsers;
}
// Reset checkbox selecton
this.selectAll(false);
this.resetPaging();
2021-12-17 15:57:11 +01:00
}
loadMore() {
if (!this.users || this.users.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedUsers.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedUsersCount > this.pageSize) {
pagedSize = this.pagedUsersCount;
}
if (this.users.length > pagedLength) {
this.pagedUsers = this.pagedUsers.concat(
this.users.slice(pagedLength, pagedLength + pagedSize)
);
}
this.pagedUsersCount = this.pagedUsers.length;
this.didScroll = this.pagedUsers.length > this.pageSize;
2021-12-17 15:57:11 +01:00
}
checkUser(user: OrganizationUserUserDetailsResponse, select?: boolean) {
(user as any).checked = select == null ? !(user as any).checked : select;
}
selectAll(select: boolean) {
if (select) {
this.selectAll(false);
}
const filteredUsers = this.searchPipe.transform(
this.users,
this.searchText,
2021-12-17 15:57:11 +01:00
"name",
"email",
"id"
);
2021-12-17 15:57:11 +01:00
const selectCount =
select && filteredUsers.length > MaxCheckedCount ? MaxCheckedCount : filteredUsers.length;
for (let i = 0; i < selectCount; i++) {
this.checkUser(filteredUsers[i], select);
}
2021-12-17 15:57:11 +01:00
}
async resetPaging() {
this.pagedUsers = [];
this.loadMore();
2021-12-17 15:57:11 +01:00
}
invite() {
this.edit(null);
2021-12-17 15:57:11 +01:00
}
async remove(user: UserType) {
const confirmed = await this.platformUtilsService.showDialog(
this.deleteWarningMessage(user),
this.userNamePipe.transform(user),
this.i18nService.t("yes"),
this.i18nService.t("no"),
2021-12-17 15:57:11 +01:00
"warning"
);
if (!confirmed) {
return false;
}
this.actionPromise = this.deleteUser(user.id);
try {
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("removedUserId", this.userNamePipe.transform(user))
2021-12-17 15:57:11 +01:00
);
this.removeUser(user);
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
2021-12-17 15:57:11 +01:00
}
async deactivate(user: UserType) {
const confirmed = await this.platformUtilsService.showDialog(
this.deactivateWarningMessage(),
this.i18nService.t("deactivateUserId", this.userNamePipe.transform(user)),
this.i18nService.t("deactivate"),
this.i18nService.t("cancel"),
"warning"
);
if (!confirmed) {
return false;
}
this.actionPromise = this.deactivateUser(user.id);
try {
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("deactivatedUserId", this.userNamePipe.transform(user))
);
await this.load();
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
async activate(user: UserType) {
const confirmed = await this.platformUtilsService.showDialog(
this.activateWarningMessage(),
this.i18nService.t("activateUserId", this.userNamePipe.transform(user)),
this.i18nService.t("activate"),
this.i18nService.t("cancel"),
"warning"
);
if (!confirmed) {
return false;
}
this.actionPromise = this.activateUser(user.id);
try {
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("activatedUserId", this.userNamePipe.transform(user))
);
await this.load();
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
async reinvite(user: UserType) {
if (this.actionPromise != null) {
return;
}
this.actionPromise = this.reinviteUser(user.id);
2021-12-17 15:57:11 +01:00
try {
await this.actionPromise;
this.platformUtilsService.showToast(
2021-12-17 15:57:11 +01:00
"success",
null,
this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user))
2021-12-17 15:57:11 +01:00
);
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
2021-12-17 15:57:11 +01:00
}
async confirm(user: UserType) {
function updateUser(self: BasePeopleComponent<UserType>) {
user.status = self.userStatusType.Confirmed;
const mapIndex = self.statusMap.get(self.userStatusType.Accepted).indexOf(user);
if (mapIndex > -1) {
self.statusMap.get(self.userStatusType.Accepted).splice(mapIndex, 1);
self.statusMap.get(self.userStatusType.Confirmed).push(user);
2021-12-17 15:57:11 +01:00
}
}
const confirmUser = async (publicKey: Uint8Array) => {
try {
this.actionPromise = this.confirmUser(user, publicKey);
await this.actionPromise;
2021-12-07 20:41:45 +01:00
updateUser(this);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user))
);
} catch (e) {
this.validationService.showError(e);
2021-12-17 15:57:11 +01:00
throw e;
} finally {
this.actionPromise = null;
}
2021-12-17 15:57:11 +01:00
};
if (this.actionPromise != null) {
return;
}
try {
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
2021-12-07 20:41:45 +01:00
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
2021-12-17 15:57:11 +01:00
2021-12-07 20:41:45 +01:00
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
if (autoConfirm == null || !autoConfirm) {
const [modal] = await this.modalService.openViewRef(
UserConfirmComponent,
this.confirmModalRef,
(comp) => {
comp.name = this.userNamePipe.transform(user);
comp.userId = user != null ? user.userId : null;
comp.publicKey = publicKey;
comp.onConfirmedUser.subscribe(async () => {
try {
comp.formPromise = confirmUser(publicKey);
await comp.formPromise;
modal.close();
} catch (e) {
this.logService.error(e);
}
2021-12-17 15:57:11 +01:00
});
}
);
return;
}
try {
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer);
this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`);
} catch (e) {
this.logService.error(e);
2021-12-17 15:57:11 +01:00
}
await confirmUser(publicKey);
} catch (e) {
this.logService.error(`Handled exception: ${e}`);
}
2021-12-17 15:57:11 +01:00
}
isSearching() {
return this.searchService.isSearchable(this.searchText);
}
isPaging() {
const searching = this.isSearching();
if (searching && this.didScroll) {
this.resetPaging();
}
return !searching && this.users && this.users.length > this.pageSize;
2021-12-17 15:57:11 +01:00
}
protected deleteWarningMessage(user: UserType): string {
return this.i18nService.t("removeUserConfirmation");
2021-12-17 15:57:11 +01:00
}
protected deactivateWarningMessage(): string {
return this.i18nService.t("deactivateUserConfirmation");
}
protected activateWarningMessage(): string {
return this.i18nService.t("activateUserConfirmation");
}
protected getCheckedUsers() {
return this.users.filter((u) => (u as any).checked);
2021-12-17 15:57:11 +01:00
}
protected removeUser(user: UserType) {
let index = this.users.indexOf(user);
if (index > -1) {
this.users.splice(index, 1);
this.resetPaging();
}
if (this.statusMap.has(user.status)) {
index = this.statusMap.get(user.status).indexOf(user);
if (index > -1) {
this.statusMap.get(user.status).splice(index, 1);
}
}
2021-12-17 15:57:11 +01:00
}
}