1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 05:13:29 +00:00

[EC-267] Unassigned collection has disappeared in web vault (#3000)

* feat: add unassigned collection to decrypted collections

* feat: add support for unassigned in individual vault

* fix: dont activate collection when not selected

* fix: remove collection selection completely when pruning

* feat: prune collection selection if selecting my vault

* fix: filter and only show organization ciphers when unassigned collection is selected

* fix: only show unassigned for admins

* feat: add unassigned logic to organizational vault buildFilter

* refactor: move buildFilter to VaultFilterModel

* chore: add buildFilter tests

* fix: bugs in filtering logic

* refactor: use VaultFilter.buildFilter on desktop

* chore: group and reword tests for better readability

* feat: add additional test

* fix: connect unassigned collection to organization

* fix: test by adding missing

* chore: tweak test group naming

* fix: change undefined to null to better reflect real values
This commit is contained in:
Andreas Coroiu
2022-07-08 10:18:07 +02:00
committed by GitHub
parent 30200c2c3c
commit ce2606b406
11 changed files with 309 additions and 111 deletions

View File

@@ -128,7 +128,7 @@ export class VaultComponent implements OnInit, OnDestroy {
await this.openGenerator(false);
break;
case "syncCompleted":
await this.ciphersComponent.reload(this.buildFilter());
await this.ciphersComponent.reload(this.activeFilter.buildFilter());
await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter);
await this.vaultFilterComponent.reloadOrganizations();
break;
@@ -241,7 +241,7 @@ export class VaultComponent implements OnInit, OnDestroy {
selectedOrganizationId: params.selectedOrganizationId,
myVaultOnly: params.myVaultOnly ?? false,
});
await this.ciphersComponent.reload(this.buildFilter());
await this.ciphersComponent.reload(this.activeFilter.buildFilter());
});
}
@@ -540,7 +540,10 @@ export class VaultComponent implements OnInit, OnDestroy {
this.i18nService.t(this.calculateSearchBarLocalizationString(vaultFilter))
);
this.activeFilter = vaultFilter;
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
await this.ciphersComponent.reload(
this.activeFilter.buildFilter(),
vaultFilter.status === "trash"
);
this.go();
}
@@ -570,40 +573,6 @@ export class VaultComponent implements OnInit, OnDestroy {
return "searchVault";
}
private buildFilter(): (cipher: CipherView) => boolean {
return (cipher) => {
let cipherPassesFilter = true;
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
cipherPassesFilter = cipher.favorite;
}
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
cipherPassesFilter = cipher.isDeleted;
}
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
}
if (
this.activeFilter.selectedFolder &&
this.activeFilter.selectedFolderId != "none" &&
cipherPassesFilter
) {
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
}
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
cipherPassesFilter =
cipher.collectionIds != null &&
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
}
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
}
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === null;
}
return cipherPassesFilter;
};
}
async openGenerator(comingFromAddEdit: boolean, passwordType = true) {
if (this.modal != null) {
this.modal.close();

View File

@@ -23,7 +23,7 @@
<li
*ngFor="let c of collections"
[ngClass]="{
active: c.node.id === activeFilter.selectedCollectionId
active: c.node.id === activeFilter.selectedCollectionId && activeFilter.selectedCollection
}"
class="filter-option"
>

View File

@@ -7,6 +7,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/abstractions/folder.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
@@ -27,6 +28,7 @@ export class VaultFilterService extends BaseVaultFilterService {
cipherService: CipherService,
collectionService: CollectionService,
policyService: PolicyService,
private i18nService: I18nService,
protected apiService: ApiService
) {
super(
@@ -69,6 +71,11 @@ export class VaultFilterService extends BaseVaultFilterService {
result = await this.collectionService.decryptMany(collectionDomains);
}
const noneCollection = new CollectionView();
noneCollection.name = this.i18nService.t("unassigned");
noneCollection.organizationId = organizationId;
result.push(noneCollection);
const nestedCollections = await this.collectionService.getAllNested(result);
return new DynamicTreeNode<CollectionView>({
fullList: result,

View File

@@ -173,7 +173,10 @@ export class IndividualVaultComponent implements OnInit, OnDestroy {
async applyVaultFilter(vaultFilter: VaultFilter) {
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
this.activeFilter = vaultFilter;
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
await this.ciphersComponent.reload(
this.activeFilter.buildFilter(),
vaultFilter.status === "trash"
);
this.filterComponent.searchPlaceholder = this.vaultService.calculateSearchBarLocalizationString(
this.activeFilter
);
@@ -196,40 +199,6 @@ export class IndividualVaultComponent implements OnInit, OnDestroy {
this.ciphersComponent.search(200);
}
private buildFilter(): (cipher: CipherView) => boolean {
return (cipher) => {
let cipherPassesFilter = true;
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
cipherPassesFilter = cipher.favorite;
}
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
cipherPassesFilter = cipher.isDeleted;
}
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
}
if (
this.activeFilter.selectedFolder &&
this.activeFilter.selectedFolderId != "none" &&
cipherPassesFilter
) {
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
}
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
cipherPassesFilter =
cipher.collectionIds != null &&
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
}
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
}
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === null;
}
return cipherPassesFilter;
};
}
async editCipherAttachments(cipher: CipherView) {
const canAccessPremium = await this.stateService.getCanAccessPremium();
if (cipher.organizationId == null && !canAccessPremium) {

View File

@@ -162,46 +162,15 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
async applyVaultFilter(vaultFilter: VaultFilter) {
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
this.activeFilter = vaultFilter;
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
await this.ciphersComponent.reload(
this.activeFilter.buildFilter(),
vaultFilter.status === "trash"
);
this.vaultFilterComponent.searchPlaceholder =
this.vaultService.calculateSearchBarLocalizationString(this.activeFilter);
this.go();
}
private buildFilter(): (cipher: CipherView) => boolean {
return (cipher) => {
let cipherPassesFilter = true;
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
cipherPassesFilter = cipher.favorite;
}
if (this.deleted && cipherPassesFilter) {
cipherPassesFilter = cipher.isDeleted;
}
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
}
if (
this.activeFilter.selectedFolder != null &&
this.activeFilter.selectedFolderId != "none" &&
cipherPassesFilter
) {
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
}
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
cipherPassesFilter =
cipher.collectionIds != null &&
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
}
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
}
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === null;
}
return cipherPassesFilter;
};
}
filterSearchText(searchText: string) {
this.ciphersComponent.searchText = searchText;
this.ciphersComponent.search(200);

View File

@@ -14,7 +14,7 @@ export class VaultService {
if (vaultFilter.selectedFolderId != null && vaultFilter.selectedFolderId != "none") {
return "searchFolder";
}
if (vaultFilter.selectedCollectionId != null) {
if (vaultFilter.selectedCollection) {
return "searchCollection";
}
if (vaultFilter.selectedOrganizationId != null) {