diff --git a/apps/browser/src/vault/popup/components/vault/collections.component.ts b/apps/browser/src/vault/popup/components/vault/collections.component.ts
index acbdab3685..c8f85a8b7a 100644
--- a/apps/browser/src/vault/popup/components/vault/collections.component.ts
+++ b/apps/browser/src/vault/popup/components/vault/collections.component.ts
@@ -4,6 +4,7 @@ import { ActivatedRoute } from "@angular/router";
import { first } from "rxjs/operators";
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
+import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -21,11 +22,19 @@ export class CollectionsComponent extends BaseCollectionsComponent {
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
cipherService: CipherService,
+ organizationService: OrganizationService,
private route: ActivatedRoute,
private location: Location,
logService: LogService,
) {
- super(collectionService, platformUtilsService, i18nService, cipherService, logService);
+ super(
+ collectionService,
+ platformUtilsService,
+ i18nService,
+ cipherService,
+ organizationService,
+ logService,
+ );
}
async ngOnInit() {
diff --git a/apps/desktop/src/vault/app/vault/collections.component.ts b/apps/desktop/src/vault/app/vault/collections.component.ts
index b8465669e7..cd08427016 100644
--- a/apps/desktop/src/vault/app/vault/collections.component.ts
+++ b/apps/desktop/src/vault/app/vault/collections.component.ts
@@ -1,6 +1,7 @@
import { Component } from "@angular/core";
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
+import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -17,8 +18,16 @@ export class CollectionsComponent extends BaseCollectionsComponent {
i18nService: I18nService,
collectionService: CollectionService,
platformUtilsService: PlatformUtilsService,
+ organizationService: OrganizationService,
logService: LogService,
) {
- super(collectionService, platformUtilsService, i18nService, cipherService, logService);
+ super(
+ collectionService,
+ platformUtilsService,
+ i18nService,
+ cipherService,
+ organizationService,
+ logService,
+ );
}
}
diff --git a/apps/web/src/app/vault/individual-vault/collections.component.html b/apps/web/src/app/vault/individual-vault/collections.component.html
index 603e899c11..46bd94b316 100644
--- a/apps/web/src/app/vault/individual-vault/collections.component.html
+++ b/apps/web/src/app/vault/individual-vault/collections.component.html
@@ -40,6 +40,7 @@
[(ngModel)]="$any(c).checked"
name="Collection[{{ i }}].Checked"
appStopProp
+ [disabled]="!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)"
/>
diff --git a/apps/web/src/app/vault/individual-vault/collections.component.ts b/apps/web/src/app/vault/individual-vault/collections.component.ts
index 29e6293c99..6cf0901f33 100644
--- a/apps/web/src/app/vault/individual-vault/collections.component.ts
+++ b/apps/web/src/app/vault/individual-vault/collections.component.ts
@@ -1,6 +1,7 @@
import { Component, OnDestroy } from "@angular/core";
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
+import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -18,9 +19,17 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
cipherService: CipherService,
+ organizationSerivce: OrganizationService,
logService: LogService,
) {
- super(collectionService, platformUtilsService, i18nService, cipherService, logService);
+ super(
+ collectionService,
+ platformUtilsService,
+ i18nService,
+ cipherService,
+ organizationSerivce,
+ logService,
+ );
}
ngOnDestroy() {
@@ -28,6 +37,9 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
}
check(c: CollectionView, select?: boolean) {
+ if (!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)) {
+ return;
+ }
(c as any).checked = select == null ? !(c as any).checked : select;
}
diff --git a/apps/web/src/app/vault/org-vault/collections.component.ts b/apps/web/src/app/vault/org-vault/collections.component.ts
index 4b6bc26e7b..020d3fbe95 100644
--- a/apps/web/src/app/vault/org-vault/collections.component.ts
+++ b/apps/web/src/app/vault/org-vault/collections.component.ts
@@ -1,6 +1,7 @@
import { Component } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -25,15 +26,27 @@ export class CollectionsComponent extends BaseCollectionsComponent {
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
cipherService: CipherService,
+ organizationService: OrganizationService,
private apiService: ApiService,
logService: LogService,
) {
- super(collectionService, platformUtilsService, i18nService, cipherService, logService);
+ super(
+ collectionService,
+ platformUtilsService,
+ i18nService,
+ cipherService,
+ organizationService,
+ logService,
+ );
this.allowSelectNone = true;
}
protected async loadCipher() {
- if (!this.organization.canViewAllCollections) {
+ // if cipher is unassigned use apiService. We can see this by looking at this.collectionIds
+ if (
+ !this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) &&
+ this.collectionIds.length !== 0
+ ) {
return await super.loadCipher();
}
const response = await this.apiService.getCipherAdmin(this.cipherId);
@@ -55,7 +68,10 @@ export class CollectionsComponent extends BaseCollectionsComponent {
}
protected saveCollections() {
- if (this.organization.canEditAnyCollection) {
+ if (
+ this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) ||
+ this.collectionIds.length === 0
+ ) {
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
return this.apiService.putCipherCollectionsAdmin(this.cipherId, request);
} else {
diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts
index fa8c376630..ecec349482 100644
--- a/apps/web/src/app/vault/org-vault/vault.component.ts
+++ b/apps/web/src/app/vault/org-vault/vault.component.ts
@@ -137,6 +137,7 @@ export class VaultComponent implements OnInit, OnDestroy {
protected showCollectionAccessRestricted: boolean;
protected currentSearchText$: Observable;
protected editableCollections$: Observable;
+ protected allCollectionsWithoutUnassigned$: Observable;
protected showBulkEditCollectionAccess$ = this.configService.getFeatureFlag$(
FeatureFlag.BulkCollectionAccess,
false,
@@ -253,7 +254,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
- const allCollectionsWithoutUnassigned$ = combineLatest([
+ this.allCollectionsWithoutUnassigned$ = combineLatest([
organizationId$.pipe(switchMap((orgId) => this.collectionAdminService.getAll(orgId))),
defer(() => this.collectionService.getAllDecrypted()),
]).pipe(
@@ -276,7 +277,7 @@ export class VaultComponent implements OnInit, OnDestroy {
shareReplay({ refCount: true, bufferSize: 1 }),
);
- this.editableCollections$ = allCollectionsWithoutUnassigned$.pipe(
+ this.editableCollections$ = this.allCollectionsWithoutUnassigned$.pipe(
map((collections) => {
// Users that can edit all ciphers can implicitly edit all collections
if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
@@ -287,7 +288,10 @@ export class VaultComponent implements OnInit, OnDestroy {
shareReplay({ refCount: true, bufferSize: 1 }),
);
- const allCollections$ = combineLatest([organizationId$, allCollectionsWithoutUnassigned$]).pipe(
+ const allCollections$ = combineLatest([
+ organizationId$,
+ this.allCollectionsWithoutUnassigned$,
+ ]).pipe(
map(([organizationId, allCollections]) => {
const noneCollection = new CollectionAdminView();
noneCollection.name = this.i18nService.t("unassigned");
@@ -680,16 +684,35 @@ export class VaultComponent implements OnInit, OnDestroy {
if (this.flexibleCollectionsV1Enabled) {
// V1 limits admins to only adding items to collections they have access to.
- collections = await firstValueFrom(this.editableCollections$);
- } else {
- collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter(
- (c) => !c.readOnly && c.id != Unassigned,
+ collections = await firstValueFrom(
+ this.allCollectionsWithoutUnassigned$.pipe(
+ map((c) => {
+ return c.sort((a, b) => {
+ if (
+ a.canEditItems(this.organization, true) &&
+ !b.canEditItems(this.organization, true)
+ ) {
+ return -1;
+ } else if (
+ !a.canEditItems(this.organization, true) &&
+ b.canEditItems(this.organization, true)
+ ) {
+ return 1;
+ } else {
+ return a.name.localeCompare(b.name);
+ }
+ });
+ }),
+ ),
);
+ } else {
+ collections = await firstValueFrom(this.allCollectionsWithoutUnassigned$);
}
const [modal] = await this.modalService.openViewRef(
CollectionsComponent,
this.collectionsModalRef,
(comp) => {
+ comp.flexibleCollectionsV1Enabled = this.flexibleCollectionsV1Enabled;
comp.collectionIds = cipher.collectionIds;
comp.collections = collections;
comp.organization = this.organization;
diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts
index 9c3ce48369..167fe0a97f 100644
--- a/libs/angular/src/admin-console/components/collections.component.ts
+++ b/libs/angular/src/admin-console/components/collections.component.ts
@@ -1,5 +1,7 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
+import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
+import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -19,6 +21,8 @@ export class CollectionsComponent implements OnInit {
cipher: CipherView;
collectionIds: string[];
collections: CollectionView[] = [];
+ organization: Organization;
+ flexibleCollectionsV1Enabled: boolean;
protected cipherDomain: Cipher;
@@ -27,6 +31,7 @@ export class CollectionsComponent implements OnInit {
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
protected cipherService: CipherService,
+ protected organizationService: OrganizationService,
private logService: LogService,
) {}
@@ -48,11 +53,21 @@ export class CollectionsComponent implements OnInit {
(c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1;
});
}
+
+ if (this.organization == null) {
+ this.organization = await this.organizationService.get(this.cipher.organizationId);
+ }
}
async submit() {
const selectedCollectionIds = this.collections
- .filter((c) => !!(c as any).checked)
+ .filter((c) => {
+ if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
+ return !!(c as any).checked;
+ } else {
+ return !!(c as any).checked && c.readOnly == null;
+ }
+ })
.map((c) => c.id);
if (!this.allowSelectNone && selectedCollectionIds.length === 0) {
this.platformUtilsService.showToast(
diff --git a/libs/common/src/vault/models/domain/collection.spec.ts b/libs/common/src/vault/models/domain/collection.spec.ts
index 848633e501..cd1cab8b42 100644
--- a/libs/common/src/vault/models/domain/collection.spec.ts
+++ b/libs/common/src/vault/models/domain/collection.spec.ts
@@ -68,6 +68,7 @@ describe("Collection", () => {
organizationId: "orgId",
readOnly: false,
manage: true,
+ assigned: true,
});
});
});
diff --git a/libs/common/src/vault/models/view/collection.view.ts b/libs/common/src/vault/models/view/collection.view.ts
index 1177d23220..74d369380b 100644
--- a/libs/common/src/vault/models/view/collection.view.ts
+++ b/libs/common/src/vault/models/view/collection.view.ts
@@ -17,6 +17,7 @@ export class CollectionView implements View, ITreeNodeObject {
readOnly: boolean = null;
hidePasswords: boolean = null;
manage: boolean = null;
+ assigned: boolean = null;
constructor(c?: Collection | CollectionAccessDetailsResponse) {
if (!c) {
@@ -30,7 +31,29 @@ export class CollectionView implements View, ITreeNodeObject {
this.readOnly = c.readOnly;
this.hidePasswords = c.hidePasswords;
this.manage = c.manage;
+ this.assigned = true;
}
+ if (c instanceof CollectionAccessDetailsResponse) {
+ this.assigned = c.assigned;
+ }
+ }
+
+ canEditItems(org: Organization, v1FlexibleCollections: boolean): boolean {
+ if (org != null && org.id !== this.organizationId) {
+ throw new Error(
+ "Id of the organization provided does not match the org id of the collection.",
+ );
+ }
+
+ if (org?.flexibleCollections) {
+ return (
+ org?.canEditAllCiphers(v1FlexibleCollections) ||
+ this.manage ||
+ (this.assigned && !this.readOnly)
+ );
+ }
+
+ return org?.canEditAnyCollection || (org?.canEditAssignedCollections && this.assigned);
}
// For editing collection details, not the items within it.
|