1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-05 18:13:26 +00:00

[PM-25982] Assign to Collections - My Items (#16591)

* update cipher form to exclude my items collections

* handle default collections for assign to collections and bulk

* account for every returning true for empty arrays
This commit is contained in:
Nick Krantz
2025-09-29 13:06:36 -05:00
committed by GitHub
parent f988d3fd70
commit c4ee2fdae2
4 changed files with 199 additions and 8 deletions

View File

@@ -7,7 +7,7 @@ import { BehaviorSubject, of } from "rxjs";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { CollectionTypes, CollectionView } from "@bitwarden/admin-console/common";
import { CollectionType, CollectionTypes, CollectionView } from "@bitwarden/admin-console/common";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
@@ -33,6 +33,7 @@ const createMockCollection = (
organizationId: string,
readOnly = false,
canEdit = true,
type: CollectionType = CollectionTypes.DefaultUserCollection,
): CollectionView => {
const cv = new CollectionView({
name,
@@ -41,7 +42,7 @@ const createMockCollection = (
});
cv.readOnly = readOnly;
cv.manage = true;
cv.type = CollectionTypes.DefaultUserCollection;
cv.type = type;
cv.externalId = "";
cv.hidePasswords = false;
cv.assigned = true;
@@ -519,6 +520,42 @@ describe("ItemDetailsSectionComponent", () => {
expect(component["collectionOptions"].map((c) => c.id)).toEqual(["col1", "col2", "col3"]);
});
it("should exclude default collections when the cipher is only assigned to shared collections", async () => {
component.config.admin = false;
component.config.organizationDataOwnershipDisabled = true;
component.config.organizations = [{ id: "org1" } as Organization];
component.config.collections = new Array(4)
.fill(null)
.map((_, i) => i + 1)
.map(
(i) =>
createMockCollection(
`col${i}`,
`Collection ${i}`,
"org1",
false,
false,
i < 4 ? CollectionTypes.SharedCollection : CollectionTypes.DefaultUserCollection,
) as CollectionView,
);
component.originalCipherView = {
name: "cipher1",
organizationId: "org1",
folderId: "folder1",
collectionIds: ["col2", "col3"],
favorite: true,
} as CipherView;
fixture.detectChanges();
await fixture.whenStable();
component.itemDetailsForm.controls.organizationId.setValue("org1");
fixture.detectChanges();
await fixture.whenStable();
expect(component["collectionOptions"].map((c) => c.id)).toEqual(["col1", "col2", "col3"]);
});
});
describe("readonlyCollections", () => {

View File

@@ -406,6 +406,17 @@ export class ItemDetailsSectionComponent implements OnInit {
this.showCollectionsControl = true;
}
/**
* Determine if the the cipher is only assigned to shared collections.
* i.e. The cipher is not assigned to a default collections.
* Note: `.every` will return true for an empty array
*/
const cipherIsOnlyInOrgCollections =
(this.originalCipherView?.collectionIds ?? []).length > 0 &&
this.originalCipherView.collectionIds.every(
(cId) =>
this.collections.find((c) => c.id === cId)?.type === CollectionTypes.SharedCollection,
);
this.collectionOptions = this.collections
.filter((c) => {
// The collection belongs to the organization
@@ -423,10 +434,17 @@ export class ItemDetailsSectionComponent implements OnInit {
return true;
}
// When the cipher is only assigned to shared collections, do not allow a user to
// move it back to a default collection. Exclude the default collection from the list.
if (cipherIsOnlyInOrgCollections && c.type === CollectionTypes.DefaultUserCollection) {
return false;
}
// Non-admins can only select assigned collections that are not read only. (Non-AC)
return c.assigned && !c.readOnly;
})
.sort((a, b) => {
// Show default collection first
const aIsDefaultCollection = a.type === CollectionTypes.DefaultUserCollection ? -1 : 0;
const bIsDefaultCollection = b.type === CollectionTypes.DefaultUserCollection ? -1 : 0;
return aIsDefaultCollection - bIsDefaultCollection;