1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 07:13:32 +00:00

Feature/split manage collections permission (#1211)

* Update guard services and routing

* Add depenent checkbox to handle sub permissions

* Present new collections premissions

* Use new split permissions

* Rename to nested-checkbox.component

* Clarify css class name

* update jslib
This commit is contained in:
Matt Gibson
2021-10-05 11:12:44 -05:00
committed by GitHub
parent 7a43510cf5
commit 998d36a5d1
20 changed files with 181 additions and 72 deletions

2
jslib

Submodule jslib updated: ce71c0c0bd...91c5393ae7

View File

@@ -0,0 +1,18 @@
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" [name]="pascalize(parentId)" [id]="parentId"
[(ngModel)]="parentChecked" [indeterminate]="parentIndeterminate">
<label class="form-check-label font-weight-normal" [for]="parentId">
{{parentId | i18n}}
</label>
</div>
<div class="form-group form-group-child-check mb-0">
<div class="form-check mt-1" *ngFor="let c of checkboxes">
<input class="form-check-input" type="checkbox" [name]="pascalize(c.id)" [id]="c.id" [ngModel]="c.get()"
(ngModelChange)="c.set($event)">
<label class="form-check-label font-weight-normal" [for]="c.id">
{{c.id | i18n}}
</label>
</div>
</div>
</div>

View File

@@ -0,0 +1,37 @@
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Utils } from 'jslib-common/misc/utils';
@Component({
selector: 'app-nested-checkbox',
templateUrl: 'nested-checkbox.component.html',
})
export class NestedCheckboxComponent {
@Input() parentId: string;
@Input() checkboxes: { id: string, get: () => boolean, set: (v: boolean) => void; }[];
@Output() onSavedUser = new EventEmitter();
@Output() onDeletedUser = new EventEmitter();
get parentIndeterminate() {
return !this.parentChecked &&
this.checkboxes.some(c => c.get());
}
get parentChecked() {
return this.checkboxes.every(c => c.get());
}
set parentChecked(value: boolean) {
this.checkboxes.forEach(c => {
c.set(value);
});
}
pascalize(s: string) {
return Utils.camelToPascalCase(s);
}
}

View File

@@ -82,8 +82,8 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
get showManageTab(): boolean { get showManageTab(): boolean {
return this.organization.canManageUsers || return this.organization.canManageUsers ||
this.organization.canManageAssignedCollections || this.organization.canViewAllCollections ||
this.organization.canManageAllCollections || this.organization.canViewAssignedCollections ||
this.organization.canManageGroups || this.organization.canManageGroups ||
this.organization.canManagePolicies || this.organization.canManagePolicies ||
this.organization.canAccessEventLogs; this.organization.canAccessEventLogs;
@@ -109,7 +109,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
case this.organization.canManageUsers: case this.organization.canManageUsers:
route = 'manage/people'; route = 'manage/people';
break; break;
case this.organization.canManageAssignedCollections || this.organization.canManageAllCollections: case this.organization.canViewAssignedCollections || this.organization.canViewAllCollections:
route = 'manage/collections'; route = 'manage/collections';
break; break;
case this.organization.canManageGroups: case this.organization.canManageGroups:

View File

@@ -69,7 +69,7 @@ export class CollectionsComponent implements OnInit {
async load() { async load() {
const organization = await this.userService.getOrganization(this.organizationId); const organization = await this.userService.getOrganization(this.organizationId);
let response: ListResponse<CollectionResponse>; let response: ListResponse<CollectionResponse>;
if (organization.canManageAllCollections) { if (organization.canViewAllCollections) {
response = await this.apiService.getCollections(this.organizationId); response = await this.apiService.getCollections(this.organizationId);
} else { } else {
response = await this.apiService.getUserCollections(); response = await this.apiService.getUserCollections();

View File

@@ -9,7 +9,7 @@
{{'people' | i18n}} {{'people' | i18n}}
</a> </a>
<a routerLink="collections" class="list-group-item" routerLinkActive="active" <a routerLink="collections" class="list-group-item" routerLinkActive="active"
*ngIf="organization.canManageAssignedCollections || organization.canManageAllCollections"> *ngIf="organization.canViewAssignedCollections || organization.canViewAllCollections">
{{'collections' | i18n}} {{'collections' | i18n}}
</a> </a>
<a routerLink="groups" class="list-group-item" routerLinkActive="active" <a routerLink="groups" class="list-group-item" routerLinkActive="active"

View File

@@ -80,7 +80,8 @@
<div class="mb-3"> <div class="mb-3">
<label class="font-weight-bold mb-0">Manager Permissions</label> <label class="font-weight-bold mb-0">Manager Permissions</label>
<hr class="my-0 mr-2" /> <hr class="my-0 mr-2" />
<div class="form-group mb-0"> <!-- Deprecated Sep 29 2021 -->
<div class="form-group mb-0" *ngIf="fallbackToManageAssignedCollections">
<div class="form-check mt-1 form-check-block"> <div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageAssignedCollections" <input class="form-check-input" type="checkbox" name="manageAssignedCollections"
id="manageAssignedCollections" id="manageAssignedCollections"
@@ -91,6 +92,10 @@
</label> </label>
</div> </div>
</div> </div>
<!-- -->
<app-nested-checkbox *ngIf="!fallbackToManageAssignedCollections" parentId="manageAssignedCollections"
[checkboxes]="manageAssignedCollectionsCheckboxes">
</app-nested-checkbox>
</div> </div>
</div> </div>
<div class="col-6"> <div class="col-6">
@@ -133,7 +138,8 @@
</label> </label>
</div> </div>
</div> </div>
<div class="form-group mb-0"> <!-- Deprecated 29 Sep 2021 -->
<div class="form-group mb-0" *ngIf="fallbackToManageAllCollections">
<div class="form-check mt-1 form-check-block"> <div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageAllCollections" <input class="form-check-input" type="checkbox" name="manageAllCollections"
id="manageAllCollections" [(ngModel)]="permissions.manageAllCollections"> id="manageAllCollections" [(ngModel)]="permissions.manageAllCollections">
@@ -142,6 +148,10 @@
</label> </label>
</div> </div>
</div> </div>
<!-- -->
<app-nested-checkbox *ngIf="!fallbackToManageAllCollections" parentId="manageAllCollections"
[checkboxes]="manageAllCollectionsCheckboxes">
</app-nested-checkbox>
<div class="form-group mb-0"> <div class="form-group mb-0">
<div class="form-check mt-1 form-check-block"> <div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageGroups" <input class="form-check-input" type="checkbox" name="manageGroups"

View File

@@ -48,6 +48,48 @@ export class UserAddEditComponent implements OnInit {
deletePromise: Promise<any>; deletePromise: Promise<any>;
organizationUserType = OrganizationUserType; organizationUserType = OrganizationUserType;
manageAllCollectionsCheckboxes = [
{
id: 'createNewCollections',
get: () => this.permissions.createNewCollections,
set: (v: boolean) => this.permissions.createNewCollections = v,
},
{
id: 'editAnyCollection',
get: () => this.permissions.editAnyCollection,
set: (v: boolean) => this.permissions.editAnyCollection = v,
},
{
id: 'deleteAnyCollection',
get: () => this.permissions.deleteAnyCollection,
set: (v: boolean) => this.permissions.deleteAnyCollection = v,
},
];
manageAssignedCollectionsCheckboxes = [
{
id: 'editAssignedCollections',
get: () => this.permissions.editAssignedCollections,
set: (v: boolean) => this.permissions.editAssignedCollections = v,
},
{
id: 'deleteAssignedCollections',
get: () => this.permissions.deleteAssignedCollections,
set: (v: boolean) => this.permissions.deleteAssignedCollections = v,
},
];
get fallbackToManageAllCollections() {
return this.permissions.createNewCollections == null &&
this.permissions.editAnyCollection == null &&
this.permissions.deleteAnyCollection == null;
}
get fallbackToManageAssignedCollections() {
return this.permissions.editAssignedCollections == null &&
this.permissions.deleteAssignedCollections == null;
}
get customUserTypeSelected(): boolean { get customUserTypeSelected(): boolean {
return this.type === OrganizationUserType.Custom; return this.type === OrganizationUserType.Custom;
} }
@@ -107,39 +149,7 @@ export class UserAddEditComponent implements OnInit {
} }
setRequestPermissions(p: PermissionsApi, clearPermissions: boolean) { setRequestPermissions(p: PermissionsApi, clearPermissions: boolean) {
p.accessBusinessPortal = clearPermissions ? Object.assign(p, clearPermissions ? new PermissionsApi() : this.permissions);
false :
this.permissions.accessBusinessPortal;
p.accessEventLogs = this.permissions.accessEventLogs = clearPermissions ?
false :
this.permissions.accessEventLogs;
p.accessImportExport = clearPermissions ?
false :
this.permissions.accessImportExport;
p.accessReports = clearPermissions ?
false :
this.permissions.accessReports;
p.manageAllCollections = clearPermissions ?
false :
this.permissions.manageAllCollections;
p.manageAssignedCollections = clearPermissions ?
false :
this.permissions.manageAssignedCollections;
p.manageGroups = clearPermissions ?
false :
this.permissions.manageGroups;
p.manageSso = clearPermissions ?
false :
this.permissions.manageSso;
p.managePolicies = clearPermissions ?
false :
this.permissions.managePolicies;
p.manageUsers = clearPermissions ?
false :
this.permissions.manageUsers;
p.manageResetPassword = clearPermissions ?
false :
this.permissions.manageResetPassword;
return p; return p;
} }
@@ -203,5 +213,4 @@ export class UserAddEditComponent implements OnInit {
this.onDeletedUser.emit(); this.onDeletedUser.emit();
} catch { } } catch { }
} }
} }

View File

@@ -46,7 +46,7 @@ export class AddEditComponent extends BaseAddEditComponent {
protected allowOwnershipAssignment() { protected allowOwnershipAssignment() {
if (this.ownershipOptions != null && (this.ownershipOptions.length > 1 || !this.allowPersonal)) { if (this.ownershipOptions != null && (this.ownershipOptions.length > 1 || !this.allowPersonal)) {
if (this.organization != null) { if (this.organization != null) {
return this.cloneMode && this.organization.canManageAllCollections; return this.cloneMode && this.organization.canEditAnyCollection;
} else { } else {
return !this.editMode || this.cloneMode; return !this.editMode || this.cloneMode;
} }
@@ -55,14 +55,14 @@ export class AddEditComponent extends BaseAddEditComponent {
} }
protected loadCollections() { protected loadCollections() {
if (!this.organization.canManageAllCollections) { if (!this.organization.canEditAnyCollection) {
return super.loadCollections(); return super.loadCollections();
} }
return Promise.resolve(this.collections); return Promise.resolve(this.collections);
} }
protected async loadCipher() { protected async loadCipher() {
if (!this.organization.canManageAllCollections) { if (!this.organization.canEditAnyCollection) {
return await super.loadCipher(); return await super.loadCipher();
} }
const response = await this.apiService.getCipherAdmin(this.cipherId); const response = await this.apiService.getCipherAdmin(this.cipherId);
@@ -72,14 +72,14 @@ export class AddEditComponent extends BaseAddEditComponent {
} }
protected encryptCipher() { protected encryptCipher() {
if (!this.organization.canManageAllCollections) { if (!this.organization.canEditAnyCollection) {
return super.encryptCipher(); return super.encryptCipher();
} }
return this.cipherService.encrypt(this.cipher, null, this.originalCipher); return this.cipherService.encrypt(this.cipher, null, this.originalCipher);
} }
protected async saveCipher(cipher: Cipher) { protected async saveCipher(cipher: Cipher) {
if (!this.organization.canManageAllCollections || cipher.organizationId == null) { if (!this.organization.canEditAnyCollection || cipher.organizationId == null) {
return super.saveCipher(cipher); return super.saveCipher(cipher);
} }
if (this.editMode && !this.cloneMode) { if (this.editMode && !this.cloneMode) {
@@ -92,7 +92,7 @@ export class AddEditComponent extends BaseAddEditComponent {
} }
protected async deleteCipher() { protected async deleteCipher() {
if (!this.organization.canManageAllCollections) { if (!this.organization.canEditAnyCollection) {
return super.deleteCipher(); return super.deleteCipher();
} }
return this.cipher.isDeleted ? this.apiService.deleteCipherAdmin(this.cipherId) return this.cipher.isDeleted ? this.apiService.deleteCipherAdmin(this.cipherId)

View File

@@ -30,13 +30,13 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
} }
protected async reupload(attachment: AttachmentView) { protected async reupload(attachment: AttachmentView) {
if (this.organization.canManageAllCollections && this.showFixOldAttachments(attachment)) { if (this.organization.canEditAnyCollection && this.showFixOldAttachments(attachment)) {
await super.reuploadCipherAttachment(attachment, true); await super.reuploadCipherAttachment(attachment, true);
} }
} }
protected async loadCipher() { protected async loadCipher() {
if (!this.organization.canManageAllCollections) { if (!this.organization.canEditAnyCollection) {
return await super.loadCipher(); return await super.loadCipher();
} }
const response = await this.apiService.getCipherAdmin(this.cipherId); const response = await this.apiService.getCipherAdmin(this.cipherId);
@@ -44,17 +44,17 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
} }
protected saveCipherAttachment(file: File) { protected saveCipherAttachment(file: File) {
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, this.organization.canManageAllCollections); return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, this.organization.canEditAnyCollection);
} }
protected deleteCipherAttachment(attachmentId: string) { protected deleteCipherAttachment(attachmentId: string) {
if (!this.organization.canManageAllCollections) { if (!this.organization.canEditAnyCollection) {
return super.deleteCipherAttachment(attachmentId); return super.deleteCipherAttachment(attachmentId);
} }
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId); return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
} }
protected showFixOldAttachments(attachment: AttachmentView) { protected showFixOldAttachments(attachment: AttachmentView) {
return attachment.key == null && this.organization.canManageAllCollections; return attachment.key == null && this.organization.canEditAnyCollection;
} }
} }

View File

@@ -42,7 +42,7 @@ export class CiphersComponent extends BaseCiphersComponent {
} }
async load(filter: (cipher: CipherView) => boolean = null) { async load(filter: (cipher: CipherView) => boolean = null) {
if (this.organization.canManageAllCollections) { if (this.organization.canViewAllCollections) {
this.accessEvents = this.organization.useEvents; this.accessEvents = this.organization.useEvents;
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id); this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);
} else { } else {
@@ -54,7 +54,7 @@ export class CiphersComponent extends BaseCiphersComponent {
} }
async applyFilter(filter: (cipher: CipherView) => boolean = null) { async applyFilter(filter: (cipher: CipherView) => boolean = null) {
if (this.organization.canManageAllCollections) { if (this.organization.canViewAllCollections) {
await super.applyFilter(filter); await super.applyFilter(filter);
} else { } else {
const f = (c: CipherView) => c.organizationId === this.organization.id && (filter == null || filter(c)); const f = (c: CipherView) => c.organizationId === this.organization.id && (filter == null || filter(c));
@@ -70,13 +70,13 @@ export class CiphersComponent extends BaseCiphersComponent {
} }
protected deleteCipher(id: string) { protected deleteCipher(id: string) {
if (!this.organization.canManageAllCollections) { if (!this.organization.canEditAnyCollection) {
return super.deleteCipher(id, this.deleted); return super.deleteCipher(id, this.deleted);
} }
return this.deleted ? this.apiService.deleteCipherAdmin(id) : this.apiService.putDeleteCipherAdmin(id); return this.deleted ? this.apiService.deleteCipherAdmin(id) : this.apiService.putDeleteCipherAdmin(id);
} }
protected showFixOldAttachments(c: CipherView) { protected showFixOldAttachments(c: CipherView) {
return this.organization.canManageAllCollections && c.hasOldAttachments; return this.organization.canEditAnyCollection && c.hasOldAttachments;
} }
} }

View File

@@ -28,7 +28,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
} }
protected async loadCipher() { protected async loadCipher() {
if (!this.organization.canManageAllCollections) { if (!this.organization.canViewAllCollections) {
return await super.loadCipher(); return await super.loadCipher();
} }
const response = await this.apiService.getCipherAdmin(this.cipherId); const response = await this.apiService.getCipherAdmin(this.cipherId);
@@ -36,21 +36,21 @@ export class CollectionsComponent extends BaseCollectionsComponent {
} }
protected loadCipherCollections() { protected loadCipherCollections() {
if (!this.organization.canManageAllCollections) { if (!this.organization.canViewAllCollections) {
return super.loadCipherCollections(); return super.loadCipherCollections();
} }
return this.collectionIds; return this.collectionIds;
} }
protected loadCollections() { protected loadCollections() {
if (!this.organization.canManageAllCollections) { if (!this.organization.canViewAllCollections) {
return super.loadCollections(); return super.loadCollections();
} }
return Promise.resolve(this.collections); return Promise.resolve(this.collections);
} }
protected saveCollections() { protected saveCollections() {
if (this.organization.canManageAllCollections) { if (this.organization.canEditAnyCollection) {
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds); const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
return this.apiService.putCipherCollectionsAdmin(this.cipherId, request); return this.apiService.putCipherCollectionsAdmin(this.cipherId, request);
} else { } else {

View File

@@ -29,7 +29,7 @@ export class GroupingsComponent extends BaseGroupingsComponent {
} }
async loadCollections() { async loadCollections() {
if (!this.organization.canManageAllCollections) { if (!this.organization.canViewAllCollections) {
await super.loadCollections(this.organization.id); await super.loadCollections(this.organization.id);
return; return;
} }

View File

@@ -72,7 +72,7 @@ export class VaultComponent implements OnInit, OnDestroy {
const queryParamsSub = this.route.queryParams.subscribe(async qParams => { const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search; this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
if (!this.organization.canManageAllCollections) { if (!this.organization.canViewAllCollections) {
await this.syncService.fullSync(false); await this.syncService.fullSync(false);
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => { this.ngZone.run(async () => {
@@ -223,7 +223,7 @@ export class VaultComponent implements OnInit, OnDestroy {
async editCipherCollections(cipher: CipherView) { async editCipherCollections(cipher: CipherView) {
const [modal] = await this.modalService.openViewRef(CollectionsComponent, this.collectionsModalRef, comp => { const [modal] = await this.modalService.openViewRef(CollectionsComponent, this.collectionsModalRef, comp => {
if (this.organization.canManageAllCollections) { if (this.organization.canEditAnyCollection) {
comp.collectionIds = cipher.collectionIds; comp.collectionIds = cipher.collectionIds;
comp.collections = this.groupingsComponent.collections.filter(c => !c.readOnly); comp.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
} }
@@ -240,7 +240,7 @@ export class VaultComponent implements OnInit, OnDestroy {
const component = await this.editCipher(null); const component = await this.editCipher(null);
component.organizationId = this.organization.id; component.organizationId = this.organization.id;
component.type = this.type; component.type = this.type;
if (this.organization.canManageAllCollections) { if (this.organization.canEditAnyCollection) {
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly); component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
} }
if (this.collectionId != null) { if (this.collectionId != null) {
@@ -273,7 +273,7 @@ export class VaultComponent implements OnInit, OnDestroy {
const component = await this.editCipher(cipher); const component = await this.editCipher(cipher);
component.cloneMode = true; component.cloneMode = true;
component.organizationId = this.organization.id; component.organizationId = this.organization.id;
if (this.organization.canManageAllCollections) { if (this.organization.canEditAnyCollection) {
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly); component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
} }
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value // Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value

View File

@@ -350,8 +350,11 @@ const routes: Routes = [
canActivate: [OrganizationTypeGuardService], canActivate: [OrganizationTypeGuardService],
data: { data: {
permissions: [ permissions: [
Permissions.ManageAssignedCollections, Permissions.CreateNewCollections,
Permissions.ManageAllCollections, Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
Permissions.AccessEventLogs, Permissions.AccessEventLogs,
Permissions.ManageGroups, Permissions.ManageGroups,
Permissions.ManageUsers, Permissions.ManageUsers,
@@ -370,7 +373,13 @@ const routes: Routes = [
canActivate: [OrganizationTypeGuardService], canActivate: [OrganizationTypeGuardService],
data: { data: {
titleId: 'collections', titleId: 'collections',
permissions: [Permissions.ManageAssignedCollections, Permissions.ManageAllCollections], permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
],
}, },
}, },
{ {

View File

@@ -12,6 +12,7 @@ import { ToasterModule } from 'angular2-toaster';
import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { AvatarComponent } from './components/avatar.component'; import { AvatarComponent } from './components/avatar.component';
import { NestedCheckboxComponent } from './components/nested-checkbox.component';
import { PasswordRepromptComponent } from './components/password-reprompt.component'; import { PasswordRepromptComponent } from './components/password-reprompt.component';
import { PasswordStrengthComponent } from './components/password-strength.component'; import { PasswordStrengthComponent } from './components/password-strength.component';
@@ -356,6 +357,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
LockComponent, LockComponent,
LoginComponent, LoginComponent,
NavbarComponent, NavbarComponent,
NestedCheckboxComponent,
OptionsComponent, OptionsComponent,
OrgAccountComponent, OrgAccountComponent,
OrgAddEditComponent, OrgAddEditComponent,

View File

@@ -22,8 +22,11 @@ export class OrganizationTypeGuardService implements CanActivate {
(permissions.indexOf(Permissions.AccessEventLogs) !== -1 && org.canAccessEventLogs) || (permissions.indexOf(Permissions.AccessEventLogs) !== -1 && org.canAccessEventLogs) ||
(permissions.indexOf(Permissions.AccessImportExport) !== -1 && org.canAccessImportExport) || (permissions.indexOf(Permissions.AccessImportExport) !== -1 && org.canAccessImportExport) ||
(permissions.indexOf(Permissions.AccessReports) !== -1 && org.canAccessReports) || (permissions.indexOf(Permissions.AccessReports) !== -1 && org.canAccessReports) ||
(permissions.indexOf(Permissions.ManageAllCollections) !== -1 && org.canManageAllCollections) || (permissions.indexOf(Permissions.CreateNewCollections) !== -1 && org.canCreateNewCollections) ||
(permissions.indexOf(Permissions.ManageAssignedCollections) !== -1 && org.canManageAssignedCollections) || (permissions.indexOf(Permissions.EditAnyCollection) !== -1 && org.canEditAnyCollection) ||
(permissions.indexOf(Permissions.DeleteAnyCollection) !== -1 && org.canDeleteAnyCollection) ||
(permissions.indexOf(Permissions.EditAssignedCollections) !== -1 && org.canEditAssignedCollections) ||
(permissions.indexOf(Permissions.DeleteAssignedCollections) !== -1 && org.canDeleteAssignedCollections) ||
(permissions.indexOf(Permissions.ManageGroups) !== -1 && org.canManageGroups) || (permissions.indexOf(Permissions.ManageGroups) !== -1 && org.canManageGroups) ||
(permissions.indexOf(Permissions.ManageOrganization) !== -1 && org.isOwner) || (permissions.indexOf(Permissions.ManageOrganization) !== -1 && org.isOwner) ||
(permissions.indexOf(Permissions.ManagePolicies) !== -1 && org.canManagePolicies) || (permissions.indexOf(Permissions.ManagePolicies) !== -1 && org.canManagePolicies) ||

View File

@@ -29,7 +29,7 @@ export class BulkDeleteComponent {
private i18nService: I18nService, private apiService: ApiService) { } private i18nService: I18nService, private apiService: ApiService) { }
async submit() { async submit() {
if (!this.organization || !this.organization.canManageAllCollections) { if (!this.organization || !this.organization.canEditAnyCollection) {
await this.deleteCiphers(); await this.deleteCiphers();
} else { } else {
await this.deleteCiphersAdmin(); await this.deleteCiphersAdmin();

View File

@@ -3824,9 +3824,24 @@
"manageAllCollections": { "manageAllCollections": {
"message": "Manage All Collections" "message": "Manage All Collections"
}, },
"createNewCollections": {
"message": "Create New Collections"
},
"editAnyCollection": {
"message": "Edit Any Collection"
},
"deleteAnyCollection": {
"message": "Delete Any Collection"
},
"manageAssignedCollections": { "manageAssignedCollections": {
"message": "Manage Assigned Collections" "message": "Manage Assigned Collections"
}, },
"editAssignedCollections": {
"message": "Edit Assigned Collections"
},
"deleteAssignedCollections": {
"message": "Delete Assigned Collections"
},
"manageGroups": { "manageGroups": {
"message": "Manage Groups" "message": "Manage Groups"
}, },

View File

@@ -58,6 +58,12 @@ label.form-check-label, .form-control-file {
} }
} }
.form-group {
.form-group-child-check {
@extend .ml-4
}
}
.form-inline { .form-inline {
input[type='datetime-local'] { input[type='datetime-local'] {
width: 200px; width: 200px;