mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-21 11:35:34 +01:00
[AC-1144] Warn admins when removing or revoking users without master password (#5494)
* [AC-1144] Added new messages for warning removing/revoking user without master password * [AC-1144] Added property 'hasMasterPassword' to OrganizationUserUserDetailsResponse and OrganizationUserView * [AC-1144] Added user's name to 'No master password' warning * [AC-1144] Added property 'hasMasterPassword' to ProviderUserResponse * [AC-1144] Added alert to bulk "remove/revoke users" action when a selected user has no master password * [AC-1144] Moved 'noMasterPasswordConfirmationDialog' method to BasePeopleComponent * [AC-1144] Removed await from noMasterPasswordConfirmationDialog * [AC-1144] Changed ApiService.getProviderUser to output ProviderUserUserDetailsResponse * [AC-1144] Added warning on removing a provider user without master password * [AC-1144] Added "No Master password" warning to provider users * [AC-1144] Added "no master password" warning when removing/revoking user in modal view * [AC-1144] Reverted changes made to ProviderUsers * [AC-1144] Converted showNoMasterPasswordWarning() into a property * [AC-1144] Fixed issue when opening invite member modal
This commit is contained in:
parent
1052f00b87
commit
d3d17f1496
@ -83,6 +83,7 @@ export class UserAdminService {
|
||||
}));
|
||||
view.groups = u.groups;
|
||||
view.accessSecretsManager = u.accessSecretsManager;
|
||||
view.hasMasterPassword = u.hasMasterPassword;
|
||||
|
||||
return view;
|
||||
});
|
||||
|
@ -16,6 +16,7 @@ export class OrganizationUserAdminView {
|
||||
accessAll: boolean;
|
||||
permissions: PermissionsApi;
|
||||
resetPasswordEnrolled: boolean;
|
||||
hasMasterPassword: boolean;
|
||||
|
||||
collections: CollectionAccessSelectionView[] = [];
|
||||
groups: string[] = [];
|
||||
|
@ -20,6 +20,7 @@ export class OrganizationUserView {
|
||||
avatarColor: string;
|
||||
twoFactorEnabled: boolean;
|
||||
usesKeyConnector: boolean;
|
||||
hasMasterPassword: boolean;
|
||||
|
||||
collections: CollectionAccessSelectionView[] = [];
|
||||
groups: string[] = [];
|
||||
|
@ -23,12 +23,16 @@
|
||||
</app-callout>
|
||||
<ng-container *ngIf="!done">
|
||||
<app-callout type="warning" *ngIf="users.length > 0 && !error">
|
||||
{{ removeUsersWarning }}
|
||||
<p>{{ removeUsersWarning }}</p>
|
||||
<p *ngIf="this.showNoMasterPasswordWarning">
|
||||
{{ "removeMembersWithoutMasterPasswordWarning" | i18n }}
|
||||
</p>
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ "user" | i18n }}</th>
|
||||
<th *ngIf="this.showNoMasterPasswordWarning">{{ "details" | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr *ngFor="let user of users">
|
||||
@ -39,6 +43,15 @@
|
||||
{{ user.email }}
|
||||
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
|
||||
</td>
|
||||
<td *ngIf="this.showNoMasterPasswordWarning">
|
||||
<span class="text-muted d-block tw-lowercase">
|
||||
<ng-container *ngIf="user.hasMasterPassword === true"> - </ng-container>
|
||||
<ng-container *ngIf="user.hasMasterPassword === false">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "noMasterPassword" | i18n }}
|
||||
</ng-container>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
|
@ -12,13 +12,23 @@ import { BulkUserDetails } from "./bulk-status.component";
|
||||
})
|
||||
export class BulkRemoveComponent {
|
||||
@Input() organizationId: string;
|
||||
@Input() users: BulkUserDetails[];
|
||||
@Input() set users(value: BulkUserDetails[]) {
|
||||
this._users = value;
|
||||
this.showNoMasterPasswordWarning = this._users.some((u) => u.hasMasterPassword === false);
|
||||
}
|
||||
|
||||
get users(): BulkUserDetails[] {
|
||||
return this._users;
|
||||
}
|
||||
|
||||
private _users: BulkUserDetails[];
|
||||
|
||||
statuses: Map<string, string> = new Map();
|
||||
|
||||
loading = false;
|
||||
done = false;
|
||||
error: string;
|
||||
showNoMasterPasswordWarning = false;
|
||||
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
|
@ -23,12 +23,16 @@
|
||||
</app-callout>
|
||||
<ng-container *ngIf="!done">
|
||||
<app-callout type="warning" *ngIf="users.length > 0 && !error && isRevoking">
|
||||
{{ "revokeUsersWarning" | i18n }}
|
||||
<p>{{ "revokeUsersWarning" | i18n }}</p>
|
||||
<p *ngIf="this.showNoMasterPasswordWarning">
|
||||
{{ "removeMembersWithoutMasterPasswordWarning" | i18n }}
|
||||
</p>
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ "user" | i18n }}</th>
|
||||
<th *ngIf="this.showNoMasterPasswordWarning">{{ "details" | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr *ngFor="let user of users">
|
||||
@ -39,6 +43,15 @@
|
||||
{{ user.email }}
|
||||
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
|
||||
</td>
|
||||
<td *ngIf="this.showNoMasterPasswordWarning">
|
||||
<span class="text-muted d-block tw-lowercase">
|
||||
<ng-container *ngIf="user.hasMasterPassword === true"> - </ng-container>
|
||||
<ng-container *ngIf="user.hasMasterPassword === false">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "noMasterPassword" | i18n }}
|
||||
</ng-container>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
|
@ -20,6 +20,7 @@ export class BulkRestoreRevokeComponent {
|
||||
loading = false;
|
||||
done = false;
|
||||
error: string;
|
||||
showNoMasterPasswordWarning = false;
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
@ -29,6 +30,7 @@ export class BulkRestoreRevokeComponent {
|
||||
this.isRevoking = config.data.isRevoking;
|
||||
this.organizationId = config.data.organizationId;
|
||||
this.users = config.data.users;
|
||||
this.showNoMasterPasswordWarning = this.users.some((u) => u.hasMasterPassword === false);
|
||||
}
|
||||
|
||||
get bulkTitle() {
|
||||
|
@ -10,6 +10,7 @@ export interface BulkUserDetails {
|
||||
name: string;
|
||||
email: string;
|
||||
status: OrganizationUserStatusType | ProviderUserStatusType;
|
||||
hasMasterPassword?: boolean;
|
||||
}
|
||||
|
||||
type BulkStatusEntry = {
|
||||
|
@ -72,6 +72,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
canUseCustomPermissions: boolean;
|
||||
PermissionMode = PermissionMode;
|
||||
canUseSecretsManager: boolean;
|
||||
showNoMasterPasswordWarning = false;
|
||||
|
||||
protected organization: Organization;
|
||||
protected collectionAccessItems: AccessItemView[] = [];
|
||||
@ -179,6 +180,9 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
throw new Error("Could not find user to edit.");
|
||||
}
|
||||
this.isRevoked = userDetails.status === OrganizationUserStatusType.Revoked;
|
||||
this.showNoMasterPasswordWarning =
|
||||
userDetails.status > OrganizationUserStatusType.Invited &&
|
||||
userDetails.hasMasterPassword === false;
|
||||
const assignedCollectionsPermissions = {
|
||||
editAssignedCollections: userDetails.permissions.editAssignedCollections,
|
||||
deleteAssignedCollections: userDetails.permissions.deleteAssignedCollections,
|
||||
@ -366,7 +370,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
? "removeUserConfirmationKeyConnector"
|
||||
: "removeOrgUserConfirmation";
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
let confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "removeUserIdAccess", placeholders: [this.params.name] },
|
||||
content: { key: message },
|
||||
type: SimpleDialogType.WARNING,
|
||||
@ -376,6 +380,14 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.showNoMasterPasswordWarning) {
|
||||
confirmed = await this.noMasterPasswordConfirmationDialog();
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
await this.organizationUserService.deleteOrganizationUser(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId
|
||||
@ -394,7 +406,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
let confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "revokeUserId", placeholders: [this.params.name] },
|
||||
content: { key: "revokeUserConfirmation" },
|
||||
acceptButtonText: { key: "revokeAccess" },
|
||||
@ -405,6 +417,14 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.showNoMasterPasswordWarning) {
|
||||
confirmed = await this.noMasterPasswordConfirmationDialog();
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
await this.organizationUserService.revokeOrganizationUser(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId
|
||||
@ -450,6 +470,19 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
private close(result: MemberDialogResult) {
|
||||
this.dialogRef.close(result);
|
||||
}
|
||||
|
||||
private noMasterPasswordConfirmationDialog() {
|
||||
return this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
key: "removeOrgUserNoMasterPasswordTitle",
|
||||
},
|
||||
content: {
|
||||
key: "removeOrgUserNoMasterPasswordDesc",
|
||||
placeholders: [this.params.name],
|
||||
},
|
||||
type: SimpleDialogType.WARNING,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mapCollectionToAccessItemView(
|
||||
|
@ -546,7 +546,7 @@ export class PeopleComponent
|
||||
? "removeUserConfirmationKeyConnector"
|
||||
: "removeOrgUserConfirmation";
|
||||
|
||||
return await this.dialogService.openSimpleDialog({
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
key: "removeUserIdAccess",
|
||||
placeholders: [this.userNamePipe.transform(user)],
|
||||
@ -554,6 +554,35 @@ export class PeopleComponent
|
||||
content: { key: content },
|
||||
type: SimpleDialogType.WARNING,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user.status > OrganizationUserStatusType.Invited && user.hasMasterPassword === false) {
|
||||
return await this.noMasterPasswordConfirmationDialog(user);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async revokeUserConfirmationDialog(user: OrganizationUserView) {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] },
|
||||
content: this.revokeWarningMessage(),
|
||||
acceptButtonText: { key: "revokeAccess" },
|
||||
type: SimpleDialogType.WARNING,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user.status > OrganizationUserStatusType.Invited && user.hasMasterPassword === false) {
|
||||
return await this.noMasterPasswordConfirmationDialog(user);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async showBulkStatus(
|
||||
@ -608,4 +637,17 @@ export class PeopleComponent
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
|
||||
private async noMasterPasswordConfirmationDialog(user: OrganizationUserView) {
|
||||
return this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
key: "removeOrgUserNoMasterPasswordTitle",
|
||||
},
|
||||
content: {
|
||||
key: "removeOrgUserNoMasterPasswordDesc",
|
||||
placeholders: [this.userNamePipe.transform(user)],
|
||||
},
|
||||
type: SimpleDialogType.WARNING,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -247,13 +247,17 @@ export abstract class BasePeopleComponent<
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async revoke(user: UserType) {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
protected async revokeUserConfirmationDialog(user: UserType) {
|
||||
return this.dialogService.openSimpleDialog({
|
||||
title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] },
|
||||
content: this.revokeWarningMessage(),
|
||||
acceptButtonText: { key: "revokeAccess" },
|
||||
type: SimpleDialogType.WARNING,
|
||||
});
|
||||
}
|
||||
|
||||
async revoke(user: UserType) {
|
||||
const confirmed = await this.revokeUserConfirmationDialog(user);
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
|
@ -6884,5 +6884,23 @@
|
||||
},
|
||||
"loginRequestApproved": {
|
||||
"message": "Login request approved"
|
||||
},
|
||||
"removeOrgUserNoMasterPasswordTitle": {
|
||||
"message": "Account does not have master password"
|
||||
},
|
||||
"removeOrgUserNoMasterPasswordDesc": {
|
||||
"message": "Removing $USER$ without setting a master password for them may restrict access to their full account. Are you sure you want to continue?",
|
||||
"placeholders": {
|
||||
"user": {
|
||||
"content": "$1",
|
||||
"example": "John Smith"
|
||||
}
|
||||
}
|
||||
},
|
||||
"noMasterPassword": {
|
||||
"message": "No master password"
|
||||
},
|
||||
"removeMembersWithoutMasterPasswordWarning": {
|
||||
"message": "Removing members who do not have master passwords without setting one for them may restrict access to their full account."
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ export class OrganizationUserResponse extends BaseResponse {
|
||||
accessSecretsManager: boolean;
|
||||
permissions: PermissionsApi;
|
||||
resetPasswordEnrolled: boolean;
|
||||
hasMasterPassword: boolean;
|
||||
collections: SelectionReadOnlyResponse[] = [];
|
||||
groups: string[] = [];
|
||||
|
||||
@ -28,6 +29,7 @@ export class OrganizationUserResponse extends BaseResponse {
|
||||
this.accessAll = this.getResponseProperty("AccessAll");
|
||||
this.accessSecretsManager = this.getResponseProperty("AccessSecretsManager");
|
||||
this.resetPasswordEnrolled = this.getResponseProperty("ResetPasswordEnrolled");
|
||||
this.hasMasterPassword = this.getResponseProperty("HasMasterPassword");
|
||||
|
||||
const collections = this.getResponseProperty("Collections");
|
||||
if (collections != null) {
|
||||
|
Loading…
Reference in New Issue
Block a user