1
0
mirror of https://github.com/bitwarden/web synced 2025-12-15 15:53:18 +00:00

[Provider] Add support for managing providers (#1014)

This commit is contained in:
Oscar Hinton
2021-07-21 11:32:27 +02:00
committed by GitHub
parent ebe08535e0
commit a94faf06a9
64 changed files with 2910 additions and 491 deletions

View File

@@ -14,11 +14,11 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ValidationService } from 'jslib-angular/services/validation.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
@@ -37,12 +37,12 @@ import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/respons
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType';
import { PolicyType } from 'jslib-common/enums/policyType';
import { ProviderUserType } from 'jslib-common/enums/providerUserType';
import { SearchPipe } from 'jslib-angular/pipes/search.pipe';
import { UserNamePipe } from 'jslib-angular/pipes/user-name.pipe';
import { Utils } from 'jslib-common/misc/utils';
import { BasePeopleComponent } from '../../common/base.people.component';
import { ModalComponent } from '../../modal.component';
import { BulkConfirmComponent } from './bulk/bulk-confirm.component';
import { BulkRemoveComponent } from './bulk/bulk-remove.component';
@@ -50,16 +50,13 @@ import { BulkStatusComponent } from './bulk/bulk-status.component';
import { EntityEventsComponent } from './entity-events.component';
import { ResetPasswordComponent } from './reset-password.component';
import { UserAddEditComponent } from './user-add-edit.component';
import { UserConfirmComponent } from './user-confirm.component';
import { UserGroupsComponent } from './user-groups.component';
const MaxCheckedCount = 500;
@Component({
selector: 'app-org-people',
templateUrl: 'people.component.html',
})
export class PeopleComponent implements OnInit {
export class PeopleComponent extends BasePeopleComponent<OrganizationUserUserDetailsResponse> implements OnInit {
@ViewChild('addEdit', { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild('groupsTemplate', { read: ViewContainerRef, static: true }) groupsModalRef: ViewContainerRef;
@ViewChild('eventsTemplate', { read: ViewContainerRef, static: true }) eventsModalRef: ViewContainerRef;
@@ -69,16 +66,11 @@ export class PeopleComponent implements OnInit {
@ViewChild('bulkConfirmTemplate', { read: ViewContainerRef, static: true }) bulkConfirmModalRef: ViewContainerRef;
@ViewChild('bulkRemoveTemplate', { read: ViewContainerRef, static: true }) bulkRemoveModalRef: ViewContainerRef;
loading = true;
userType = ProviderUserType;
userStatusType = OrganizationUserStatusType;
organizationId: string;
users: OrganizationUserUserDetailsResponse[];
pagedUsers: OrganizationUserUserDetailsResponse[];
searchText: string;
status: OrganizationUserStatusType = null;
statusMap = new Map<OrganizationUserStatusType, OrganizationUserUserDetailsResponse[]>();
organizationUserType = OrganizationUserType;
organizationUserStatusType = OrganizationUserStatusType;
actionPromise: Promise<any>;
accessEvents = false;
accessGroups = false;
canResetPassword = false; // User permission (admin/custom)
@@ -87,20 +79,16 @@ export class PeopleComponent implements OnInit {
orgResetPasswordPolicyEnabled = false;
callingUserType: OrganizationUserType = null;
protected didScroll = false;
protected pageSize = 100;
private pagedUsersCount = 0;
private modal: ModalComponent = null;
private allUsers: OrganizationUserUserDetailsResponse[];
constructor(private apiService: ApiService, private route: ActivatedRoute,
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
private platformUtilsService: PlatformUtilsService, private toasterService: ToasterService,
private cryptoService: CryptoService, private userService: UserService, private router: Router,
private storageService: StorageService, private searchService: SearchService,
private validationService: ValidationService, private policyService: PolicyService,
private searchPipe: SearchPipe, private userNamePipe: UserNamePipe, private syncService: SyncService) { }
constructor(apiService: ApiService, private route: ActivatedRoute,
i18nService: I18nService, componentFactoryResolver: ComponentFactoryResolver,
platformUtilsService: PlatformUtilsService, toasterService: ToasterService,
cryptoService: CryptoService, private userService: UserService, private router: Router,
storageService: StorageService, searchService: SearchService,
validationService: ValidationService, private policyService: PolicyService,
logService: LogService, searchPipe: SearchPipe, userNamePipe: UserNamePipe, private syncService: SyncService) {
super(apiService, searchService, i18nService, platformUtilsService, toasterService, cryptoService,
storageService, validationService, componentFactoryResolver, logService, searchPipe, userNamePipe);
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
@@ -149,21 +137,29 @@ export class PeopleComponent implements OnInit {
}
async load() {
const response = await this.apiService.getOrganizationUsers(this.organizationId);
this.statusMap.clear();
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]);
} else {
this.statusMap.get(u.status).push(u);
}
});
this.filter(this.status);
const policies = await this.policyService.getAll(PolicyType.ResetPassword);
this.orgResetPasswordPolicyEnabled = policies.some(p => p.organizationId === this.organizationId && p.enabled);
this.loading = false;
super.load();
}
getUsers(): Promise<ListResponse<OrganizationUserUserDetailsResponse>> {
return this.apiService.getOrganizationUsers(this.organizationId);
}
deleteUser(id: string): Promise<any> {
return this.apiService.deleteOrganizationUser(this.organizationId, id);
}
reinviteUser(id: string): Promise<any> {
return this.apiService.postOrganizationUserReinvite(this.organizationId, id);
}
async confirmUser(user: OrganizationUserUserDetailsResponse, publicKey: Uint8Array): Promise<any> {
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString;
await this.apiService.postOrganizationUserConfirm(this.organizationId, user.id, request);
}
allowResetPassword(orgUser: OrganizationUserUserDetailsResponse): boolean {
@@ -193,62 +189,6 @@ export class PeopleComponent implements OnInit {
return this.orgUseResetPassword && orgUser.resetPasswordEnrolled && this.orgResetPasswordPolicyEnabled;
}
filter(status: OrganizationUserStatusType) {
this.status = status;
if (this.status != null) {
this.users = this.statusMap.get(this.status);
} else {
this.users = this.allUsers;
}
// Reset checkbox selecton
this.selectAll(false);
this.resetPaging();
}
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;
}
get allCount() {
return this.allUsers != null ? this.allUsers.length : 0;
}
get invitedCount() {
return this.statusMap.has(OrganizationUserStatusType.Invited) ?
this.statusMap.get(OrganizationUserStatusType.Invited).length : 0;
}
get acceptedCount() {
return this.statusMap.has(OrganizationUserStatusType.Accepted) ?
this.statusMap.get(OrganizationUserStatusType.Accepted).length : 0;
}
get confirmedCount() {
return this.statusMap.has(OrganizationUserStatusType.Confirmed) ?
this.statusMap.get(OrganizationUserStatusType.Confirmed).length : 0;
}
get showConfirmUsers(): boolean {
return this.allUsers != null && this.statusMap != null && this.allUsers.length > 1 &&
this.confirmedCount > 0 && this.confirmedCount < 3 && this.acceptedCount > 0;
}
get showBulkConfirmUsers(): boolean {
return this.acceptedCount > 0;
}
edit(user: OrganizationUserUserDetailsResponse) {
if (this.modal != null) {
this.modal.close();
@@ -276,10 +216,6 @@ export class PeopleComponent implements OnInit {
});
}
invite() {
this.edit(null);
}
groups(user: OrganizationUserUserDetailsResponse) {
if (this.modal != null) {
this.modal.close();
@@ -302,40 +238,6 @@ export class PeopleComponent implements OnInit {
});
}
async remove(user: OrganizationUserUserDetailsResponse) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('removeUserConfirmation'), this.userNamePipe.transform(user),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
this.actionPromise = this.apiService.deleteOrganizationUser(this.organizationId, user.id);
try {
await this.actionPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', this.userNamePipe.transform(user)));
this.removeUser(user);
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
async reinvite(user: OrganizationUserUserDetailsResponse) {
if (this.actionPromise != null) {
return;
}
this.actionPromise = this.apiService.postOrganizationUserReinvite(this.organizationId, user.id);
try {
await this.actionPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenReinvited', this.userNamePipe.transform(user)));
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
async bulkRemove() {
if (this.actionPromise != null) {
return;
@@ -405,80 +307,6 @@ export class PeopleComponent implements OnInit {
});
}
async confirm(user: OrganizationUserUserDetailsResponse) {
function updateUser(self: PeopleComponent) {
user.status = OrganizationUserStatusType.Confirmed;
const mapIndex = self.statusMap.get(OrganizationUserStatusType.Accepted).indexOf(user);
if (mapIndex > -1) {
self.statusMap.get(OrganizationUserStatusType.Accepted).splice(mapIndex, 1);
self.statusMap.get(OrganizationUserStatusType.Confirmed).push(user);
}
}
const confirmUser = async (publicKey: Uint8Array) => {
try {
this.actionPromise = this.doConfirmation(user, publicKey);
await this.actionPromise;
updateUser(this);
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', this.userNamePipe.transform(user)));
} catch (e) {
this.validationService.showError(e);
throw e;
} finally {
this.actionPromise = null;
}
};
if (this.actionPromise != null) {
return;
}
const autoConfirm = await this.storageService.get<boolean>(ConstantsService.autoConfirmFingerprints);
if (autoConfirm == null || !autoConfirm) {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.confirmModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<UserConfirmComponent>(
UserConfirmComponent, this.confirmModalRef);
childComponent.name = this.userNamePipe.transform(user);
childComponent.organizationId = this.organizationId;
childComponent.organizationUserId = user != null ? user.id : null;
childComponent.userId = user != null ? user.userId : null;
childComponent.onConfirmedUser.subscribe(async (publicKey: Uint8Array) => {
try {
await confirmUser(publicKey);
this.modal.close();
} catch (e) {
// tslint:disable-next-line
console.error('Handled exception:', e);
}
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
return;
}
try {
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
try {
// tslint:disable-next-line
console.log('User\'s fingerprint: ' +
(await this.cryptoService.getFingerprint(user.userId, publicKey.buffer)).join('-'));
} catch { }
await confirmUser(publicKey);
} catch (e) {
// tslint:disable-next-line
console.error('Handled exception:', e);
}
}
async events(user: OrganizationUserUserDetailsResponse) {
if (this.modal != null) {
this.modal.close();
@@ -500,23 +328,6 @@ export class PeopleComponent implements OnInit {
});
}
async resetPaging() {
this.pagedUsers = [];
this.loadMore();
}
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;
}
async resetPassword(user: OrganizationUserUserDetailsResponse) {
if (this.modal != null) {
this.modal.close();
@@ -542,25 +353,6 @@ export class PeopleComponent implements OnInit {
});
}
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, 'name', 'email', 'id');
const selectCount = select && filteredUsers.length > MaxCheckedCount
? MaxCheckedCount
: filteredUsers.length;
for (let i = 0; i < selectCount; i++) {
this.checkUser(filteredUsers[i], select);
}
}
private async showBulkStatus(users: OrganizationUserUserDetailsResponse[], filteredUsers: OrganizationUserUserDetailsResponse[],
request: Promise<ListResponse<OrganizationUserBulkResponse>>, successfullMessage: string) {
@@ -611,42 +403,4 @@ export class PeopleComponent implements OnInit {
}
}
}
private async doConfirmation(user: OrganizationUserUserDetailsResponse, publicKey: Uint8Array) {
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString;
await this.apiService.postOrganizationUserConfirm(this.organizationId, user.id, request);
}
private removeUser(user: OrganizationUserUserDetailsResponse) {
let index = this.users.indexOf(user);
if (index > -1) {
this.users.splice(index, 1);
this.resetPaging();
}
if (this.statusMap.has(OrganizationUserStatusType.Accepted)) {
index = this.statusMap.get(OrganizationUserStatusType.Accepted).indexOf(user);
if (index > -1) {
this.statusMap.get(OrganizationUserStatusType.Accepted).splice(index, 1);
}
}
if (this.statusMap.has(OrganizationUserStatusType.Invited)) {
index = this.statusMap.get(OrganizationUserStatusType.Invited).indexOf(user);
if (index > -1) {
this.statusMap.get(OrganizationUserStatusType.Invited).splice(index, 1);
}
}
if (this.statusMap.has(OrganizationUserStatusType.Confirmed)) {
index = this.statusMap.get(OrganizationUserStatusType.Confirmed).indexOf(user);
if (index > -1) {
this.statusMap.get(OrganizationUserStatusType.Confirmed).splice(index, 1);
}
}
}
private getCheckedUsers() {
return this.users.filter(u => (u as any).checked);
}
}