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.groups = u.groups;
|
||||||
view.accessSecretsManager = u.accessSecretsManager;
|
view.accessSecretsManager = u.accessSecretsManager;
|
||||||
|
view.hasMasterPassword = u.hasMasterPassword;
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
});
|
});
|
||||||
|
@ -16,6 +16,7 @@ export class OrganizationUserAdminView {
|
|||||||
accessAll: boolean;
|
accessAll: boolean;
|
||||||
permissions: PermissionsApi;
|
permissions: PermissionsApi;
|
||||||
resetPasswordEnrolled: boolean;
|
resetPasswordEnrolled: boolean;
|
||||||
|
hasMasterPassword: boolean;
|
||||||
|
|
||||||
collections: CollectionAccessSelectionView[] = [];
|
collections: CollectionAccessSelectionView[] = [];
|
||||||
groups: string[] = [];
|
groups: string[] = [];
|
||||||
|
@ -20,6 +20,7 @@ export class OrganizationUserView {
|
|||||||
avatarColor: string;
|
avatarColor: string;
|
||||||
twoFactorEnabled: boolean;
|
twoFactorEnabled: boolean;
|
||||||
usesKeyConnector: boolean;
|
usesKeyConnector: boolean;
|
||||||
|
hasMasterPassword: boolean;
|
||||||
|
|
||||||
collections: CollectionAccessSelectionView[] = [];
|
collections: CollectionAccessSelectionView[] = [];
|
||||||
groups: string[] = [];
|
groups: string[] = [];
|
||||||
|
@ -23,12 +23,16 @@
|
|||||||
</app-callout>
|
</app-callout>
|
||||||
<ng-container *ngIf="!done">
|
<ng-container *ngIf="!done">
|
||||||
<app-callout type="warning" *ngIf="users.length > 0 && !error">
|
<app-callout type="warning" *ngIf="users.length > 0 && !error">
|
||||||
{{ removeUsersWarning }}
|
<p>{{ removeUsersWarning }}</p>
|
||||||
|
<p *ngIf="this.showNoMasterPasswordWarning">
|
||||||
|
{{ "removeMembersWithoutMasterPasswordWarning" | i18n }}
|
||||||
|
</p>
|
||||||
</app-callout>
|
</app-callout>
|
||||||
<table class="table table-hover table-list">
|
<table class="table table-hover table-list">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2">{{ "user" | i18n }}</th>
|
<th colspan="2">{{ "user" | i18n }}</th>
|
||||||
|
<th *ngIf="this.showNoMasterPasswordWarning">{{ "details" | i18n }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tr *ngFor="let user of users">
|
<tr *ngFor="let user of users">
|
||||||
@ -39,6 +43,15 @@
|
|||||||
{{ user.email }}
|
{{ user.email }}
|
||||||
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
|
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -12,13 +12,23 @@ import { BulkUserDetails } from "./bulk-status.component";
|
|||||||
})
|
})
|
||||||
export class BulkRemoveComponent {
|
export class BulkRemoveComponent {
|
||||||
@Input() organizationId: string;
|
@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();
|
statuses: Map<string, string> = new Map();
|
||||||
|
|
||||||
loading = false;
|
loading = false;
|
||||||
done = false;
|
done = false;
|
||||||
error: string;
|
error: string;
|
||||||
|
showNoMasterPasswordWarning = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected apiService: ApiService,
|
protected apiService: ApiService,
|
||||||
|
@ -23,12 +23,16 @@
|
|||||||
</app-callout>
|
</app-callout>
|
||||||
<ng-container *ngIf="!done">
|
<ng-container *ngIf="!done">
|
||||||
<app-callout type="warning" *ngIf="users.length > 0 && !error && isRevoking">
|
<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>
|
</app-callout>
|
||||||
<table class="table table-hover table-list">
|
<table class="table table-hover table-list">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2">{{ "user" | i18n }}</th>
|
<th colspan="2">{{ "user" | i18n }}</th>
|
||||||
|
<th *ngIf="this.showNoMasterPasswordWarning">{{ "details" | i18n }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tr *ngFor="let user of users">
|
<tr *ngFor="let user of users">
|
||||||
@ -39,6 +43,15 @@
|
|||||||
{{ user.email }}
|
{{ user.email }}
|
||||||
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
|
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -20,6 +20,7 @@ export class BulkRestoreRevokeComponent {
|
|||||||
loading = false;
|
loading = false;
|
||||||
done = false;
|
done = false;
|
||||||
error: string;
|
error: string;
|
||||||
|
showNoMasterPasswordWarning = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
@ -29,6 +30,7 @@ export class BulkRestoreRevokeComponent {
|
|||||||
this.isRevoking = config.data.isRevoking;
|
this.isRevoking = config.data.isRevoking;
|
||||||
this.organizationId = config.data.organizationId;
|
this.organizationId = config.data.organizationId;
|
||||||
this.users = config.data.users;
|
this.users = config.data.users;
|
||||||
|
this.showNoMasterPasswordWarning = this.users.some((u) => u.hasMasterPassword === false);
|
||||||
}
|
}
|
||||||
|
|
||||||
get bulkTitle() {
|
get bulkTitle() {
|
||||||
|
@ -10,6 +10,7 @@ export interface BulkUserDetails {
|
|||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
status: OrganizationUserStatusType | ProviderUserStatusType;
|
status: OrganizationUserStatusType | ProviderUserStatusType;
|
||||||
|
hasMasterPassword?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type BulkStatusEntry = {
|
type BulkStatusEntry = {
|
||||||
|
@ -72,6 +72,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
canUseCustomPermissions: boolean;
|
canUseCustomPermissions: boolean;
|
||||||
PermissionMode = PermissionMode;
|
PermissionMode = PermissionMode;
|
||||||
canUseSecretsManager: boolean;
|
canUseSecretsManager: boolean;
|
||||||
|
showNoMasterPasswordWarning = false;
|
||||||
|
|
||||||
protected organization: Organization;
|
protected organization: Organization;
|
||||||
protected collectionAccessItems: AccessItemView[] = [];
|
protected collectionAccessItems: AccessItemView[] = [];
|
||||||
@ -179,6 +180,9 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
throw new Error("Could not find user to edit.");
|
throw new Error("Could not find user to edit.");
|
||||||
}
|
}
|
||||||
this.isRevoked = userDetails.status === OrganizationUserStatusType.Revoked;
|
this.isRevoked = userDetails.status === OrganizationUserStatusType.Revoked;
|
||||||
|
this.showNoMasterPasswordWarning =
|
||||||
|
userDetails.status > OrganizationUserStatusType.Invited &&
|
||||||
|
userDetails.hasMasterPassword === false;
|
||||||
const assignedCollectionsPermissions = {
|
const assignedCollectionsPermissions = {
|
||||||
editAssignedCollections: userDetails.permissions.editAssignedCollections,
|
editAssignedCollections: userDetails.permissions.editAssignedCollections,
|
||||||
deleteAssignedCollections: userDetails.permissions.deleteAssignedCollections,
|
deleteAssignedCollections: userDetails.permissions.deleteAssignedCollections,
|
||||||
@ -366,7 +370,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
? "removeUserConfirmationKeyConnector"
|
? "removeUserConfirmationKeyConnector"
|
||||||
: "removeOrgUserConfirmation";
|
: "removeOrgUserConfirmation";
|
||||||
|
|
||||||
const confirmed = await this.dialogService.openSimpleDialog({
|
let confirmed = await this.dialogService.openSimpleDialog({
|
||||||
title: { key: "removeUserIdAccess", placeholders: [this.params.name] },
|
title: { key: "removeUserIdAccess", placeholders: [this.params.name] },
|
||||||
content: { key: message },
|
content: { key: message },
|
||||||
type: SimpleDialogType.WARNING,
|
type: SimpleDialogType.WARNING,
|
||||||
@ -376,6 +380,14 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.showNoMasterPasswordWarning) {
|
||||||
|
confirmed = await this.noMasterPasswordConfirmationDialog();
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await this.organizationUserService.deleteOrganizationUser(
|
await this.organizationUserService.deleteOrganizationUser(
|
||||||
this.params.organizationId,
|
this.params.organizationId,
|
||||||
this.params.organizationUserId
|
this.params.organizationUserId
|
||||||
@ -394,7 +406,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmed = await this.dialogService.openSimpleDialog({
|
let confirmed = await this.dialogService.openSimpleDialog({
|
||||||
title: { key: "revokeUserId", placeholders: [this.params.name] },
|
title: { key: "revokeUserId", placeholders: [this.params.name] },
|
||||||
content: { key: "revokeUserConfirmation" },
|
content: { key: "revokeUserConfirmation" },
|
||||||
acceptButtonText: { key: "revokeAccess" },
|
acceptButtonText: { key: "revokeAccess" },
|
||||||
@ -405,6 +417,14 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.showNoMasterPasswordWarning) {
|
||||||
|
confirmed = await this.noMasterPasswordConfirmationDialog();
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await this.organizationUserService.revokeOrganizationUser(
|
await this.organizationUserService.revokeOrganizationUser(
|
||||||
this.params.organizationId,
|
this.params.organizationId,
|
||||||
this.params.organizationUserId
|
this.params.organizationUserId
|
||||||
@ -450,6 +470,19 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
private close(result: MemberDialogResult) {
|
private close(result: MemberDialogResult) {
|
||||||
this.dialogRef.close(result);
|
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(
|
function mapCollectionToAccessItemView(
|
||||||
|
@ -546,7 +546,7 @@ export class PeopleComponent
|
|||||||
? "removeUserConfirmationKeyConnector"
|
? "removeUserConfirmationKeyConnector"
|
||||||
: "removeOrgUserConfirmation";
|
: "removeOrgUserConfirmation";
|
||||||
|
|
||||||
return await this.dialogService.openSimpleDialog({
|
const confirmed = await this.dialogService.openSimpleDialog({
|
||||||
title: {
|
title: {
|
||||||
key: "removeUserIdAccess",
|
key: "removeUserIdAccess",
|
||||||
placeholders: [this.userNamePipe.transform(user)],
|
placeholders: [this.userNamePipe.transform(user)],
|
||||||
@ -554,6 +554,35 @@ export class PeopleComponent
|
|||||||
content: { key: content },
|
content: { key: content },
|
||||||
type: SimpleDialogType.WARNING,
|
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(
|
private async showBulkStatus(
|
||||||
@ -608,4 +637,17 @@ export class PeopleComponent
|
|||||||
modal.close();
|
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;
|
this.actionPromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async revoke(user: UserType) {
|
protected async revokeUserConfirmationDialog(user: UserType) {
|
||||||
const confirmed = await this.dialogService.openSimpleDialog({
|
return this.dialogService.openSimpleDialog({
|
||||||
title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] },
|
title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] },
|
||||||
content: this.revokeWarningMessage(),
|
content: this.revokeWarningMessage(),
|
||||||
acceptButtonText: { key: "revokeAccess" },
|
acceptButtonText: { key: "revokeAccess" },
|
||||||
type: SimpleDialogType.WARNING,
|
type: SimpleDialogType.WARNING,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async revoke(user: UserType) {
|
||||||
|
const confirmed = await this.revokeUserConfirmationDialog(user);
|
||||||
|
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -6884,5 +6884,23 @@
|
|||||||
},
|
},
|
||||||
"loginRequestApproved": {
|
"loginRequestApproved": {
|
||||||
"message": "Login request approved"
|
"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;
|
accessSecretsManager: boolean;
|
||||||
permissions: PermissionsApi;
|
permissions: PermissionsApi;
|
||||||
resetPasswordEnrolled: boolean;
|
resetPasswordEnrolled: boolean;
|
||||||
|
hasMasterPassword: boolean;
|
||||||
collections: SelectionReadOnlyResponse[] = [];
|
collections: SelectionReadOnlyResponse[] = [];
|
||||||
groups: string[] = [];
|
groups: string[] = [];
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ export class OrganizationUserResponse extends BaseResponse {
|
|||||||
this.accessAll = this.getResponseProperty("AccessAll");
|
this.accessAll = this.getResponseProperty("AccessAll");
|
||||||
this.accessSecretsManager = this.getResponseProperty("AccessSecretsManager");
|
this.accessSecretsManager = this.getResponseProperty("AccessSecretsManager");
|
||||||
this.resetPasswordEnrolled = this.getResponseProperty("ResetPasswordEnrolled");
|
this.resetPasswordEnrolled = this.getResponseProperty("ResetPasswordEnrolled");
|
||||||
|
this.hasMasterPassword = this.getResponseProperty("HasMasterPassword");
|
||||||
|
|
||||||
const collections = this.getResponseProperty("Collections");
|
const collections = this.getResponseProperty("Collections");
|
||||||
if (collections != null) {
|
if (collections != null) {
|
||||||
|
Loading…
Reference in New Issue
Block a user