1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +00:00

[AC-1139] Fix canDelete logic in

collection-dialog.component.ts and
bulk-delete-dialog.component.ts
This commit is contained in:
Rui Tome
2023-12-04 20:21:47 +00:00
parent 9cfe891bef
commit 59d1fe647d
4 changed files with 112 additions and 128 deletions

View File

@@ -72,7 +72,7 @@ export enum CollectionDialogAction {
export class CollectionDialogComponent implements OnInit, OnDestroy { export class CollectionDialogComponent implements OnInit, OnDestroy {
protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollections, FeatureFlag.FlexibleCollections,
false false,
); );
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
@@ -107,7 +107,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
private organizationUserService: OrganizationUserService, private organizationUserService: OrganizationUserService,
private dialogService: DialogService, private dialogService: DialogService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private configService: ConfigServiceAbstraction private configService: ConfigServiceAbstraction,
) { ) {
this.tabIndex = params.initialTab ?? CollectionDialogTabType.Info; this.tabIndex = params.initialTab ?? CollectionDialogTabType.Info;
} }
@@ -123,8 +123,8 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
map((orgs) => map((orgs) =>
orgs orgs
.filter((o) => o.canCreateNewCollections && !o.isProviderUser) .filter((o) => o.canCreateNewCollections && !o.isProviderUser)
.sort(Utils.getSortFunction(this.i18nService, "name")) .sort(Utils.getSortFunction(this.i18nService, "name")),
) ),
); );
// patchValue will trigger a call to loadOrg() in this case, so no need to call it again here // patchValue will trigger a call to loadOrg() in this case, so no need to call it again here
this.formGroup.patchValue({ selectedOrg: this.params.organizationId }); this.formGroup.patchValue({ selectedOrg: this.params.organizationId });
@@ -141,7 +141,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
async loadOrg(orgId: string, collectionIds: string[]) { async loadOrg(orgId: string, collectionIds: string[]) {
const organization$ = of(this.organizationService.get(orgId)).pipe( const organization$ = of(this.organizationService.get(orgId)).pipe(
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
const groups$ = organization$.pipe( const groups$ = organization$.pipe(
switchMap((organization) => { switchMap((organization) => {
@@ -150,7 +150,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
} }
return this.groupService.getAll(orgId); return this.groupService.getAll(orgId);
}) }),
); );
combineLatest({ combineLatest({
organization: organization$, organization: organization$,
@@ -168,7 +168,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
this.organization = organization; this.organization = organization;
this.accessItems = [].concat( this.accessItems = [].concat(
groups.map(mapGroupToAccessItemView), groups.map(mapGroupToAccessItemView),
users.data.map(mapUserToAccessItemView) users.data.map(mapUserToAccessItemView),
); );
// Force change detection to update the access selector's items // Force change detection to update the access selector's items
@@ -201,9 +201,8 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
} else { } else {
this.nestOptions = collections; this.nestOptions = collections;
const parent = collections.find((c) => c.id === this.params.parentCollectionId); const parent = collections.find((c) => c.id === this.params.parentCollectionId);
const currentOrgUserId = users.data.find( const currentOrgUserId = users.data.find((u) => u.userId === this.organization?.userId)
(u) => u.userId === this.organization?.userId ?.id;
)?.id;
const initialSelection: AccessItemValue[] = const initialSelection: AccessItemValue[] =
currentOrgUserId !== undefined currentOrgUserId !== undefined
? [ ? [
@@ -224,7 +223,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
} }
this.loading = false; this.loading = false;
} },
); );
} }
@@ -250,13 +249,13 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
null, null,
this.i18nService.t("fieldOnTabRequiresAttention", this.i18nService.t("collectionInfo")) this.i18nService.t("fieldOnTabRequiresAttention", this.i18nService.t("collectionInfo")),
); );
} else if (this.tabIndex === CollectionDialogTabType.Info && accessTabError) { } else if (this.tabIndex === CollectionDialogTabType.Info && accessTabError) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
null, null,
this.i18nService.t("fieldOnTabRequiresAttention", this.i18nService.t("access")) this.i18nService.t("fieldOnTabRequiresAttention", this.i18nService.t("access")),
); );
} }
return; return;
@@ -287,8 +286,8 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
null, null,
this.i18nService.t( this.i18nService.t(
this.editMode ? "editedCollectionId" : "createdCollectionId", this.editMode ? "editedCollectionId" : "createdCollectionId",
collectionView.name collectionView.name,
) ),
); );
this.close(CollectionDialogAction.Saved, savedCollection); this.close(CollectionDialogAction.Saved, savedCollection);
@@ -310,18 +309,17 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"success", "success",
null, null,
this.i18nService.t("deletedCollectionId", this.collection?.name) this.i18nService.t("deletedCollectionId", this.collection?.name),
); );
this.close(CollectionDialogAction.Deleted, this.collection); this.close(CollectionDialogAction.Deleted, this.collection);
}; };
protected canDelete$ = this.flexibleCollectionsEnabled$.pipe( protected canDelete$ = this.flexibleCollectionsEnabled$.pipe(
switchMap(async (flexibleCollectionsEnabled) => { map(
return ( (flexibleCollectionsEnabled) =>
this.editMode && this.collection.canDelete(this.organization, flexibleCollectionsEnabled) this.editMode && this.collection.canDelete(this.organization, flexibleCollectionsEnabled),
); ),
})
); );
ngOnDestroy(): void { ngOnDestroy(): void {
@@ -356,7 +354,7 @@ function mapToAccessSelections(collectionDetails: CollectionAdminView): AccessIt
id: selection.id, id: selection.id,
type: AccessItemType.Member, type: AccessItemType.Member,
permission: convertToPermission(selection), permission: convertToPermission(selection),
})) })),
); );
} }
@@ -377,10 +375,10 @@ function validateCanManagePermission(control: AbstractControl) {
*/ */
export function openCollectionDialog( export function openCollectionDialog(
dialogService: DialogService, dialogService: DialogService,
config: DialogConfig<CollectionDialogParams> config: DialogConfig<CollectionDialogParams>,
) { ) {
return dialogService.open<CollectionDialogResult, CollectionDialogParams>( return dialogService.open<CollectionDialogResult, CollectionDialogParams>(
CollectionDialogComponent, CollectionDialogComponent,
config config,
); );
} }

View File

@@ -34,11 +34,11 @@ export enum BulkDeleteDialogResult {
*/ */
export const openBulkDeleteDialog = ( export const openBulkDeleteDialog = (
dialogService: DialogService, dialogService: DialogService,
config: DialogConfig<BulkDeleteDialogParams> config: DialogConfig<BulkDeleteDialogParams>,
) => { ) => {
return dialogService.open<BulkDeleteDialogResult, BulkDeleteDialogParams>( return dialogService.open<BulkDeleteDialogResult, BulkDeleteDialogParams>(
BulkDeleteDialogComponent, BulkDeleteDialogComponent,
config config,
); );
}; };
@@ -61,7 +61,7 @@ export class BulkDeleteDialogComponent {
private i18nService: I18nService, private i18nService: I18nService,
private apiService: ApiService, private apiService: ApiService,
private collectionService: CollectionService, private collectionService: CollectionService,
private configService: ConfigServiceAbstraction private configService: ConfigServiceAbstraction,
) { ) {
this.cipherIds = params.cipherIds ?? []; this.cipherIds = params.cipherIds ?? [];
this.collectionIds = params.collectionIds ?? []; this.collectionIds = params.collectionIds ?? [];
@@ -95,7 +95,7 @@ export class BulkDeleteDialogComponent {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"success", "success",
null, null,
this.i18nService.t(this.permanent ? "permanentlyDeletedItems" : "deletedItems") this.i18nService.t(this.permanent ? "permanentlyDeletedItems" : "deletedItems"),
); );
} }
if (this.collectionIds.length) { if (this.collectionIds.length) {
@@ -103,7 +103,7 @@ export class BulkDeleteDialogComponent {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"success", "success",
null, null,
this.i18nService.t("deletedCollections") this.i18nService.t("deletedCollections"),
); );
} }
this.close(BulkDeleteDialogResult.Deleted); this.close(BulkDeleteDialogResult.Deleted);
@@ -130,20 +130,17 @@ export class BulkDeleteDialogComponent {
private async deleteCollections(): Promise<any> { private async deleteCollections(): Promise<any> {
const flexibleCollectionsEnabled = await this.configService.getFeatureFlag( const flexibleCollectionsEnabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollections, FeatureFlag.FlexibleCollections,
false false,
); );
// From org vault // From org vault
if (this.organization) { if (this.organization) {
if ( if (
(flexibleCollectionsEnabled this.collections.some((c) => !c.canDelete(this.organization, flexibleCollectionsEnabled))
? this.collections.some((c) => !c.canDelete)
: !this.organization.canDeleteAssignedCollections) &&
!this.organization.canDeleteAnyCollection
) { ) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("missingPermissions") this.i18nService.t("missingPermissions"),
); );
return; return;
} }
@@ -152,16 +149,11 @@ export class BulkDeleteDialogComponent {
} else if (this.organizations && this.collections) { } else if (this.organizations && this.collections) {
const deletePromises: Promise<any>[] = []; const deletePromises: Promise<any>[] = [];
for (const organization of this.organizations) { for (const organization of this.organizations) {
if ( if (this.collections.some((c) => !c.canDelete(organization, flexibleCollectionsEnabled))) {
(flexibleCollectionsEnabled
? this.collections.some((c) => !c.canDelete)
: !this.organization.canDeleteAssignedCollections) &&
!organization.canDeleteAnyCollection
) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("missingPermissions") this.i18nService.t("missingPermissions"),
); );
return; return;
} }
@@ -169,7 +161,7 @@ export class BulkDeleteDialogComponent {
.filter((o) => o.organizationId === organization.id) .filter((o) => o.organizationId === organization.id)
.map((c) => c.id); .map((c) => c.id);
deletePromises.push( deletePromises.push(
this.apiService.deleteManyCollections(this.organization.id, orgCollections) this.apiService.deleteManyCollections(this.organization.id, orgCollections),
); );
} }
return await Promise.all(deletePromises); return await Promise.all(deletePromises);

View File

@@ -147,7 +147,7 @@ export class VaultComponent implements OnInit, OnDestroy {
protected currentSearchText$: Observable<string>; protected currentSearchText$: Observable<string>;
protected showBulkCollectionAccess$ = this.configService.getFeatureFlag$( protected showBulkCollectionAccess$ = this.configService.getFeatureFlag$(
FeatureFlag.BulkCollectionAccess, FeatureFlag.BulkCollectionAccess,
false false,
); );
private searchText$ = new Subject<string>(); private searchText$ = new Subject<string>();
@@ -183,7 +183,7 @@ export class VaultComponent implements OnInit, OnDestroy {
private searchPipe: SearchPipe, private searchPipe: SearchPipe,
private configService: ConfigServiceAbstraction, private configService: ConfigServiceAbstraction,
private apiService: ApiService, private apiService: ApiService,
private userVerificationService: UserVerificationService private userVerificationService: UserVerificationService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@@ -191,7 +191,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.trashCleanupWarning = this.i18nService.t( this.trashCleanupWarning = this.i18nService.t(
this.platformUtilsService.isSelfHost() this.platformUtilsService.isSelfHost()
? "trashCleanupWarningSelfHosted" ? "trashCleanupWarningSelfHosted"
: "trashCleanupWarning" : "trashCleanupWarning",
); );
const firstSetup$ = this.route.queryParams.pipe( const firstSetup$ = this.route.queryParams.pipe(
@@ -219,7 +219,7 @@ export class VaultComponent implements OnInit, OnDestroy {
await this.editCipher(cipherView); await this.editCipher(cipherView);
} }
}), }),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
@@ -243,11 +243,11 @@ export class VaultComponent implements OnInit, OnDestroy {
const filter$ = this.routedVaultFilterService.filter$; const filter$ = this.routedVaultFilterService.filter$;
const canAccessPremium$ = Utils.asyncToObservable(() => const canAccessPremium$ = Utils.asyncToObservable(() =>
this.stateService.getCanAccessPremium() this.stateService.getCanAccessPremium(),
).pipe(shareReplay({ refCount: true, bufferSize: 1 })); ).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
const allCollections$ = Utils.asyncToObservable(() => this.collectionService.getAllDecrypted()); const allCollections$ = Utils.asyncToObservable(() => this.collectionService.getAllDecrypted());
const nestedCollections$ = allCollections$.pipe( const nestedCollections$ = allCollections$.pipe(
map((collections) => getNestedCollectionTree(collections)) map((collections) => getNestedCollectionTree(collections)),
); );
this.searchText$ this.searchText$
@@ -257,7 +257,7 @@ export class VaultComponent implements OnInit, OnDestroy {
queryParams: { search: Utils.isNullOrEmpty(searchText) ? null : searchText }, queryParams: { search: Utils.isNullOrEmpty(searchText) ? null : searchText },
queryParamsHandling: "merge", queryParamsHandling: "merge",
replaceUrl: true, replaceUrl: true,
}) }),
); );
this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search)); this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
@@ -277,7 +277,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return ciphers.filter(filterFunction); return ciphers.filter(filterFunction);
}), }),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
const collections$ = combineLatest([nestedCollections$, filter$, this.currentSearchText$]).pipe( const collections$ = combineLatest([nestedCollections$, filter$, this.currentSearchText$]).pipe(
@@ -297,7 +297,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} else { } else {
const selectedCollection = ServiceUtils.getTreeNodeObjectFromList( const selectedCollection = ServiceUtils.getTreeNodeObjectFromList(
collections, collections,
filter.collectionId filter.collectionId,
); );
collectionsToReturn = selectedCollection?.children.map((c) => c.node) ?? []; collectionsToReturn = selectedCollection?.children.map((c) => c.node) ?? [];
} }
@@ -307,13 +307,13 @@ export class VaultComponent implements OnInit, OnDestroy {
collectionsToReturn, collectionsToReturn,
searchText, searchText,
(collection) => collection.name, (collection) => collection.name,
(collection) => collection.id (collection) => collection.id,
); );
} }
return collectionsToReturn; return collectionsToReturn;
}), }),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
const selectedCollection$ = combineLatest([nestedCollections$, filter$]).pipe( const selectedCollection$ = combineLatest([nestedCollections$, filter$]).pipe(
@@ -329,7 +329,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return ServiceUtils.getTreeNodeObjectFromList(collections, filter.collectionId); return ServiceUtils.getTreeNodeObjectFromList(collections, filter.collectionId);
}), }),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
firstSetup$ firstSetup$
@@ -344,7 +344,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("unknownCipher") this.i18nService.t("unknownCipher"),
); );
this.router.navigate([], { this.router.navigate([], {
queryParams: { itemId: null, cipherId: null }, queryParams: { itemId: null, cipherId: null },
@@ -353,7 +353,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
} }
}), }),
takeUntil(this.destroy$) takeUntil(this.destroy$),
) )
.subscribe(); .subscribe();
@@ -370,9 +370,9 @@ export class VaultComponent implements OnInit, OnDestroy {
ciphers$, ciphers$,
collections$, collections$,
selectedCollection$, selectedCollection$,
]) ]),
), ),
takeUntil(this.destroy$) takeUntil(this.destroy$),
) )
.subscribe( .subscribe(
([ ([
@@ -393,7 +393,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.selectedCollection = selectedCollection; this.selectedCollection = selectedCollection;
this.canCreateCollections = allOrganizations?.some( this.canCreateCollections = allOrganizations?.some(
(o) => o.canCreateNewCollections && !o.isProviderUser (o) => o.canCreateNewCollections && !o.isProviderUser,
); );
this.showBulkMove = this.showBulkMove =
@@ -407,7 +407,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.performingInitialLoad = false; this.performingInitialLoad = false;
this.refreshing = false; this.refreshing = false;
} },
); );
} }
@@ -534,7 +534,7 @@ export class VaultComponent implements OnInit, OnDestroy {
comp.onReuploadedAttachment comp.onReuploadedAttachment
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe(() => (madeAttachmentChanges = true)); .subscribe(() => (madeAttachmentChanges = true));
} },
); );
modal.onClosed.pipe(takeUntil(this.destroy$)).subscribe(() => { modal.onClosed.pipe(takeUntil(this.destroy$)).subscribe(() => {
@@ -559,7 +559,7 @@ export class VaultComponent implements OnInit, OnDestroy {
modal.close(); modal.close();
this.refresh(); this.refresh();
}); });
} },
); );
} }
@@ -573,7 +573,7 @@ export class VaultComponent implements OnInit, OnDestroy {
modal.close(); modal.close();
this.refresh(); this.refresh();
}); });
} },
); );
} }
@@ -589,7 +589,7 @@ export class VaultComponent implements OnInit, OnDestroy {
const selectedColId = this.activeFilter.collectionId; const selectedColId = this.activeFilter.collectionId;
if (selectedColId !== "AllCollections") { if (selectedColId !== "AllCollections") {
component.organizationId = component.collections.find( component.organizationId = component.collections.find(
(collection) => collection.id === selectedColId (collection) => collection.id === selectedColId,
)?.organizationId; )?.organizationId;
component.collectionIds = [selectedColId]; component.collectionIds = [selectedColId];
} }
@@ -635,7 +635,7 @@ export class VaultComponent implements OnInit, OnDestroy {
modal.close(); modal.close();
this.refresh(); this.refresh();
}); });
} },
); );
modal.onClosedPromise().then(() => { modal.onClosedPromise().then(() => {
@@ -690,16 +690,13 @@ export class VaultComponent implements OnInit, OnDestroy {
const organization = this.organizationService.get(collection.organizationId); const organization = this.organizationService.get(collection.organizationId);
const flexibleCollectionsEnabled = await this.configService.getFeatureFlag( const flexibleCollectionsEnabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollections, FeatureFlag.FlexibleCollections,
false false,
); );
if ( if (collection.canDelete(organization, flexibleCollectionsEnabled)) {
(flexibleCollectionsEnabled || !organization.canDeleteAssignedCollections) &&
!organization.canDeleteAnyCollection
) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("missingPermissions") this.i18nService.t("missingPermissions"),
); );
return; return;
} }
@@ -717,7 +714,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"success", "success",
null, null,
this.i18nService.t("deletedCollectionId", collection.name) this.i18nService.t("deletedCollectionId", collection.name),
); );
// Navigate away if we deleted the collection we were viewing // Navigate away if we deleted the collection we were viewing
if (this.selectedCollection?.node.id === collection.id) { if (this.selectedCollection?.node.id === collection.id) {
@@ -778,7 +775,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("nothingSelected") this.i18nService.t("nothingSelected"),
); );
return; return;
} }
@@ -801,8 +798,8 @@ export class VaultComponent implements OnInit, OnDestroy {
.map((i) => i.collection.organizationId); .map((i) => i.collection.organizationId);
const orgs = await firstValueFrom( const orgs = await firstValueFrom(
this.organizationService.organizations$.pipe( this.organizationService.organizations$.pipe(
map((orgs) => orgs.filter((o) => orgIds.includes(o.id))) map((orgs) => orgs.filter((o) => orgIds.includes(o.id))),
) ),
); );
await this.bulkDelete(ciphers, collections, orgs); await this.bulkDelete(ciphers, collections, orgs);
} }
@@ -830,7 +827,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"success", "success",
null, null,
this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem") this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"),
); );
this.refresh(); this.refresh();
} catch (e) { } catch (e) {
@@ -841,7 +838,7 @@ export class VaultComponent implements OnInit, OnDestroy {
async bulkDelete( async bulkDelete(
ciphers: CipherView[], ciphers: CipherView[],
collections: CollectionView[], collections: CollectionView[],
organizations: Organization[] organizations: Organization[],
) { ) {
if (!(await this.repromptCipher(ciphers))) { if (!(await this.repromptCipher(ciphers))) {
return; return;
@@ -851,7 +848,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("nothingSelected") this.i18nService.t("nothingSelected"),
); );
return; return;
} }
@@ -881,7 +878,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("nothingSelected") this.i18nService.t("nothingSelected"),
); );
return; return;
} }
@@ -933,7 +930,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"info", "info",
null, null,
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)) this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)),
); );
if (field === "password") { if (field === "password") {
@@ -952,7 +949,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("nothingSelected") this.i18nService.t("nothingSelected"),
); );
return; return;
} }

View File

@@ -130,7 +130,7 @@ export class VaultComponent implements OnInit, OnDestroy {
protected currentSearchText$: Observable<string>; protected currentSearchText$: Observable<string>;
protected showBulkEditCollectionAccess$ = this.configService.getFeatureFlag$( protected showBulkEditCollectionAccess$ = this.configService.getFeatureFlag$(
FeatureFlag.BulkCollectionAccess, FeatureFlag.BulkCollectionAccess,
false false,
); );
protected flexibleCollectionsEnabled: boolean; protected flexibleCollectionsEnabled: boolean;
@@ -164,27 +164,27 @@ export class VaultComponent implements OnInit, OnDestroy {
private eventCollectionService: EventCollectionService, private eventCollectionService: EventCollectionService,
private totpService: TotpService, private totpService: TotpService,
private apiService: ApiService, private apiService: ApiService,
protected configService: ConfigServiceAbstraction protected configService: ConfigServiceAbstraction,
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.trashCleanupWarning = this.i18nService.t( this.trashCleanupWarning = this.i18nService.t(
this.platformUtilsService.isSelfHost() this.platformUtilsService.isSelfHost()
? "trashCleanupWarningSelfHosted" ? "trashCleanupWarningSelfHosted"
: "trashCleanupWarning" : "trashCleanupWarning",
); );
const filter$ = this.routedVaultFilterService.filter$; const filter$ = this.routedVaultFilterService.filter$;
const organizationId$ = filter$.pipe( const organizationId$ = filter$.pipe(
map((filter) => filter.organizationId), map((filter) => filter.organizationId),
filter((filter) => filter !== undefined), filter((filter) => filter !== undefined),
distinctUntilChanged() distinctUntilChanged(),
); );
const organization$ = organizationId$.pipe( const organization$ = organizationId$.pipe(
switchMap((organizationId) => this.organizationService.get$(organizationId)), switchMap((organizationId) => this.organizationService.get$(organizationId)),
takeUntil(this.destroy$), takeUntil(this.destroy$),
shareReplay({ refCount: false, bufferSize: 1 }) shareReplay({ refCount: false, bufferSize: 1 }),
); );
const firstSetup$ = combineLatest([organization$, this.route.queryParams]).pipe( const firstSetup$ = combineLatest([organization$, this.route.queryParams]).pipe(
@@ -198,7 +198,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return undefined; return undefined;
}), }),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
@@ -227,14 +227,14 @@ export class VaultComponent implements OnInit, OnDestroy {
queryParams: { search: Utils.isNullOrEmpty(searchText) ? null : searchText }, queryParams: { search: Utils.isNullOrEmpty(searchText) ? null : searchText },
queryParamsHandling: "merge", queryParamsHandling: "merge",
replaceUrl: true, replaceUrl: true,
}) }),
); );
this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search)); this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
const allCollectionsWithoutUnassigned$ = organizationId$.pipe( const allCollectionsWithoutUnassigned$ = organizationId$.pipe(
switchMap((orgId) => this.collectionAdminService.getAll(orgId)), switchMap((orgId) => this.collectionAdminService.getAll(orgId)),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
const allCollections$ = combineLatest([organizationId$, allCollectionsWithoutUnassigned$]).pipe( const allCollections$ = combineLatest([organizationId$, allCollectionsWithoutUnassigned$]).pipe(
@@ -244,12 +244,12 @@ export class VaultComponent implements OnInit, OnDestroy {
noneCollection.id = Unassigned; noneCollection.id = Unassigned;
noneCollection.organizationId = organizationId; noneCollection.organizationId = organizationId;
return allCollections.concat(noneCollection); return allCollections.concat(noneCollection);
}) }),
); );
const allGroups$ = organizationId$.pipe( const allGroups$ = organizationId$.pipe(
switchMap((organizationId) => this.groupService.getAll(organizationId)), switchMap((organizationId) => this.groupService.getAll(organizationId)),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
const allCiphers$ = organization$.pipe( const allCiphers$ = organization$.pipe(
@@ -259,12 +259,12 @@ export class VaultComponent implements OnInit, OnDestroy {
ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id); ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id);
} else { } else {
ciphers = (await this.cipherService.getAllDecrypted()).filter( ciphers = (await this.cipherService.getAllDecrypted()).filter(
(c) => c.organizationId === organization.id (c) => c.organizationId === organization.id,
); );
} }
await this.searchService.indexCiphers(ciphers, organization.id); await this.searchService.indexCiphers(ciphers, organization.id);
return ciphers; return ciphers;
}) }),
); );
const ciphers$ = combineLatest([allCiphers$, filter$, this.currentSearchText$]).pipe( const ciphers$ = combineLatest([allCiphers$, filter$, this.currentSearchText$]).pipe(
@@ -282,12 +282,12 @@ export class VaultComponent implements OnInit, OnDestroy {
return ciphers.filter(filterFunction); return ciphers.filter(filterFunction);
}), }),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
const nestedCollections$ = allCollections$.pipe( const nestedCollections$ = allCollections$.pipe(
map((collections) => getNestedCollectionTree(collections)), map((collections) => getNestedCollectionTree(collections)),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
const collections$ = combineLatest([nestedCollections$, filter$, this.currentSearchText$]).pipe( const collections$ = combineLatest([nestedCollections$, filter$, this.currentSearchText$]).pipe(
@@ -306,7 +306,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} else { } else {
const selectedCollection = ServiceUtils.getTreeNodeObjectFromList( const selectedCollection = ServiceUtils.getTreeNodeObjectFromList(
collections, collections,
filter.collectionId filter.collectionId,
); );
collectionsToReturn = selectedCollection?.children.map((c) => c.node) ?? []; collectionsToReturn = selectedCollection?.children.map((c) => c.node) ?? [];
} }
@@ -316,14 +316,14 @@ export class VaultComponent implements OnInit, OnDestroy {
collectionsToReturn, collectionsToReturn,
searchText, searchText,
(collection) => collection.name, (collection) => collection.name,
(collection) => collection.id (collection) => collection.id,
); );
} }
return collectionsToReturn; return collectionsToReturn;
}), }),
takeUntil(this.destroy$), takeUntil(this.destroy$),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
const selectedCollection$ = combineLatest([nestedCollections$, filter$]).pipe( const selectedCollection$ = combineLatest([nestedCollections$, filter$]).pipe(
@@ -339,7 +339,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return ServiceUtils.getTreeNodeObjectFromList(collections, filter.collectionId); return ServiceUtils.getTreeNodeObjectFromList(collections, filter.collectionId);
}), }),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
const showMissingCollectionPermissionMessage$ = combineLatest([ const showMissingCollectionPermissionMessage$ = combineLatest([
@@ -357,7 +357,7 @@ export class VaultComponent implements OnInit, OnDestroy {
!organization.canUseAdminCollections) !organization.canUseAdminCollections)
); );
}), }),
shareReplay({ refCount: true, bufferSize: 1 }) shareReplay({ refCount: true, bufferSize: 1 }),
); );
firstSetup$ firstSetup$
@@ -378,7 +378,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("unknownCipher") this.i18nService.t("unknownCipher"),
); );
this.router.navigate([], { this.router.navigate([], {
queryParams: { cipherId: null, itemId: null }, queryParams: { cipherId: null, itemId: null },
@@ -386,7 +386,7 @@ export class VaultComponent implements OnInit, OnDestroy {
}); });
} }
}), }),
takeUntil(this.destroy$) takeUntil(this.destroy$),
) )
.subscribe(); .subscribe();
@@ -405,7 +405,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("unknownCipher") this.i18nService.t("unknownCipher"),
); );
this.router.navigate([], { this.router.navigate([], {
queryParams: { viewEvents: null }, queryParams: { viewEvents: null },
@@ -413,7 +413,7 @@ export class VaultComponent implements OnInit, OnDestroy {
}); });
} }
}), }),
takeUntil(this.destroy$) takeUntil(this.destroy$),
) )
.subscribe(); .subscribe();
@@ -431,9 +431,9 @@ export class VaultComponent implements OnInit, OnDestroy {
collections$, collections$,
selectedCollection$, selectedCollection$,
showMissingCollectionPermissionMessage$, showMissingCollectionPermissionMessage$,
]) ]),
), ),
takeUntil(this.destroy$) takeUntil(this.destroy$),
) )
.subscribe( .subscribe(
([ ([
@@ -463,7 +463,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.refreshing = false; this.refreshing = false;
this.performingInitialLoad = false; this.performingInitialLoad = false;
} },
); );
} }
@@ -550,7 +550,7 @@ export class VaultComponent implements OnInit, OnDestroy {
comp.onDeletedAttachment comp.onDeletedAttachment
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe(() => (madeAttachmentChanges = true)); .subscribe(() => (madeAttachmentChanges = true));
} },
); );
modal.onClosed.pipe(takeUntil(this.destroy$)).subscribe(() => { modal.onClosed.pipe(takeUntil(this.destroy$)).subscribe(() => {
@@ -575,13 +575,13 @@ export class VaultComponent implements OnInit, OnDestroy {
modal.close(); modal.close();
this.refresh(); this.refresh();
}); });
} },
); );
} }
async addCipher() { async addCipher() {
const collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter( const collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter(
(c) => !c.readOnly && c.id != Unassigned (c) => !c.readOnly && c.id != Unassigned,
); );
await this.editCipher(null, (comp) => { await this.editCipher(null, (comp) => {
@@ -599,14 +599,14 @@ export class VaultComponent implements OnInit, OnDestroy {
async editCipher( async editCipher(
cipher: CipherView, cipher: CipherView,
additionalComponentParameters?: (comp: AddEditComponent) => void additionalComponentParameters?: (comp: AddEditComponent) => void,
) { ) {
return this.editCipherId(cipher?.id, additionalComponentParameters); return this.editCipherId(cipher?.id, additionalComponentParameters);
} }
async editCipherId( async editCipherId(
cipherId: string, cipherId: string,
additionalComponentParameters?: (comp: AddEditComponent) => void additionalComponentParameters?: (comp: AddEditComponent) => void,
) { ) {
const cipher = await this.cipherService.get(cipherId); const cipher = await this.cipherService.get(cipherId);
// if cipher exists (cipher is null when new) and MP reprompt // if cipher exists (cipher is null when new) and MP reprompt
@@ -647,7 +647,7 @@ export class VaultComponent implements OnInit, OnDestroy {
: (comp) => { : (comp) => {
defaultComponentParameters(comp); defaultComponentParameters(comp);
additionalComponentParameters(comp); additionalComponentParameters(comp);
} },
); );
modal.onClosedPromise().then(() => { modal.onClosedPromise().then(() => {
@@ -671,7 +671,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
const collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter( const collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter(
(c) => !c.readOnly && c.id != Unassigned (c) => !c.readOnly && c.id != Unassigned,
); );
await this.editCipher(cipher, (comp) => { await this.editCipher(cipher, (comp) => {
@@ -710,7 +710,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("nothingSelected") this.i18nService.t("nothingSelected"),
); );
return; return;
} }
@@ -742,7 +742,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"success", "success",
null, null,
this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem") this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"),
); );
this.refresh(); this.refresh();
} catch (e) { } catch (e) {
@@ -753,16 +753,13 @@ export class VaultComponent implements OnInit, OnDestroy {
async deleteCollection(collection: CollectionView): Promise<void> { async deleteCollection(collection: CollectionView): Promise<void> {
const flexibleCollectionsEnabled = await this.configService.getFeatureFlag( const flexibleCollectionsEnabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollections, FeatureFlag.FlexibleCollections,
false false,
); );
if ( if (collection.canDelete(this.organization, flexibleCollectionsEnabled)) {
(flexibleCollectionsEnabled || !this.organization.canDeleteAssignedCollections) &&
!this.organization.canDeleteAnyCollection
) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("missingPermissions") this.i18nService.t("missingPermissions"),
); );
return; return;
} }
@@ -780,7 +777,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"success", "success",
null, null,
this.i18nService.t("deletedCollectionId", collection.name) this.i18nService.t("deletedCollectionId", collection.name),
); );
// Navigate away if we deleted the colletion we were viewing // Navigate away if we deleted the colletion we were viewing
@@ -801,7 +798,7 @@ export class VaultComponent implements OnInit, OnDestroy {
async bulkDelete( async bulkDelete(
ciphers: CipherView[], ciphers: CipherView[],
collections: CollectionView[], collections: CollectionView[],
organization: Organization organization: Organization,
) { ) {
if (!(await this.repromptCipher(ciphers))) { if (!(await this.repromptCipher(ciphers))) {
return; return;
@@ -811,7 +808,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("nothingSelected") this.i18nService.t("nothingSelected"),
); );
return; return;
} }
@@ -867,7 +864,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"info", "info",
null, null,
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)) this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)),
); );
if (field === "password") { if (field === "password") {
@@ -913,7 +910,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
this.i18nService.t("nothingSelected") this.i18nService.t("nothingSelected"),
); );
return; return;
} }