mirror of
https://github.com/bitwarden/browser
synced 2026-01-07 11:03:30 +00:00
* Move DeprecatedVaultFilterService to vault folder * [libs] move VaultItemsComponent * [libs] move AddEditComponent * [libs] move AddEditCustomFields * [libs] move attachmentsComponent * [libs] folderAddEditComponent * [libs] IconComponent * [libs] PasswordRepormptComponent * [libs] PremiumComponent * [libs] ViewCustomFieldsComponent * [libs] ViewComponent * [libs] PasswordRepromptService * [libs] Move FolderService and FolderApiService abstractions * [libs] FolderService imports * [libs] PasswordHistoryComponent * [libs] move Sync and SyncNotifier abstractions * [libs] SyncService imports * [libs] fix file casing for passwordReprompt abstraction * [libs] SyncNotifier import fix * [libs] CipherServiceAbstraction * [libs] PasswordRepromptService abstraction * [libs] Fix file casing for angular passwordReprompt service * [libs] fix file casing for SyncNotifierService * [libs] CipherRepromptType * [libs] rename CipherRepromptType * [libs] CipherType * [libs] Rename CipherType * [libs] CipherData * [libs] FolderData * [libs] PasswordHistoryData * [libs] AttachmentData * [libs] CardData * [libs] FieldData * [libs] IdentityData * [libs] LocalData * [libs] LoginData * [libs] SecureNoteData * [libs] LoginUriData * [libs] Domain classes * [libs] SecureNote * [libs] Request models * [libs] Response models * [libs] View part 1 * [libs] Views part 2 * [libs] Move folder services * [libs] Views fixes * [libs] Move sync services * [libs] cipher service * [libs] Types * [libs] Sync file casing * [libs] Fix folder service import * [libs] Move spec files * [libs] casing fixes on spec files * [browser] Autofill background, clipboard, commands * [browser] Fix ContextMenusBackground casing * [browser] Rename fix * [browser] Autofill content * [browser] autofill.js * [libs] enpass importer spec fix * [browser] autofill models * [browser] autofill manifest path updates * [browser] Autofill notification files * [browser] autofill services * [browser] Fix file casing * [browser] Vault popup loose components * [browser] Vault components * [browser] Manifest fixes * [browser] Vault services * [cli] vault commands and models * [browser] File capitilization fixes * [desktop] Vault components and services * [web] vault loose components * [web] Vault components * [browser] Fix misc-utils import * [libs] Fix psono spec imports * [fix] Add comments to address lint rules
322 lines
11 KiB
TypeScript
322 lines
11 KiB
TypeScript
import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core";
|
|
import { lastValueFrom } from "rxjs";
|
|
|
|
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
|
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
|
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
|
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
|
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
|
import { TokenService } from "@bitwarden/common/abstractions/token.service";
|
|
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
|
|
import { Organization } from "@bitwarden/common/models/domain/organization";
|
|
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
|
|
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
|
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
|
import { DialogService } from "@bitwarden/components";
|
|
|
|
import {
|
|
BulkDeleteDialogResult,
|
|
openBulkDeleteDialog,
|
|
} from "../../../vault/app/vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component";
|
|
import { VaultFilterService } from "../../../vault/app/vault/vault-filter/services/abstractions/vault-filter.service";
|
|
import { CollectionFilter } from "../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
|
|
import {
|
|
VaultItemRow,
|
|
VaultItemsComponent as BaseVaultItemsComponent,
|
|
} from "../../../vault/app/vault/vault-items.component";
|
|
import { CollectionAdminView } from "../core";
|
|
import { GroupService } from "../core/services/group/group.service";
|
|
import {
|
|
CollectionDialogResult,
|
|
CollectionDialogTabType,
|
|
openCollectionDialog,
|
|
} from "../shared/components/collection-dialog/collection-dialog.component";
|
|
|
|
const MaxCheckedCount = 500;
|
|
|
|
@Component({
|
|
selector: "app-org-vault-items",
|
|
templateUrl: "../../../vault/app/vault/vault-items.component.html",
|
|
})
|
|
export class VaultItemsComponent extends BaseVaultItemsComponent implements OnDestroy {
|
|
@Input() set initOrganization(value: Organization) {
|
|
this.organization = value;
|
|
this.changeOrganization();
|
|
}
|
|
@Output() onEventsClicked = new EventEmitter<CipherView>();
|
|
|
|
protected allCiphers: CipherView[] = [];
|
|
|
|
constructor(
|
|
searchService: SearchService,
|
|
i18nService: I18nService,
|
|
platformUtilsService: PlatformUtilsService,
|
|
cipherService: CipherService,
|
|
vaultFilterService: VaultFilterService,
|
|
eventCollectionService: EventCollectionService,
|
|
totpService: TotpService,
|
|
passwordRepromptService: PasswordRepromptService,
|
|
dialogService: DialogService,
|
|
logService: LogService,
|
|
stateService: StateService,
|
|
organizationService: OrganizationService,
|
|
tokenService: TokenService,
|
|
searchPipe: SearchPipe,
|
|
protected groupService: GroupService,
|
|
private apiService: ApiService
|
|
) {
|
|
super(
|
|
searchService,
|
|
i18nService,
|
|
platformUtilsService,
|
|
vaultFilterService,
|
|
cipherService,
|
|
eventCollectionService,
|
|
totpService,
|
|
stateService,
|
|
passwordRepromptService,
|
|
dialogService,
|
|
logService,
|
|
searchPipe,
|
|
organizationService,
|
|
tokenService
|
|
);
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
super.ngOnDestroy();
|
|
}
|
|
|
|
async changeOrganization() {
|
|
this.groups = await this.groupService.getAll(this.organization?.id);
|
|
await this.loadCiphers();
|
|
await this.reload(this.activeFilter.buildFilter());
|
|
}
|
|
|
|
async loadCiphers() {
|
|
if (this.organization?.canEditAnyCollection) {
|
|
this.accessEvents = this.organization?.useEvents;
|
|
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(
|
|
this.organization?.id
|
|
);
|
|
} else {
|
|
this.allCiphers = (await this.cipherService.getAllDecrypted()).filter(
|
|
(c) => c.organizationId === this.organization?.id
|
|
);
|
|
}
|
|
await this.searchService.indexCiphers(this.organization?.id, this.allCiphers);
|
|
}
|
|
|
|
async refreshCollections(): Promise<void> {
|
|
await this.vaultFilterService.reloadCollections();
|
|
if (this.activeFilter.selectedCollectionNode) {
|
|
this.activeFilter.selectedCollectionNode =
|
|
await this.vaultFilterService.getCollectionNodeFromTree(
|
|
this.activeFilter.selectedCollectionNode.node.id
|
|
);
|
|
}
|
|
}
|
|
|
|
async load(filter: (cipher: CipherView) => boolean = null, deleted = false) {
|
|
this.deleted = deleted ?? false;
|
|
await this.applyFilter(filter);
|
|
this.loaded = true;
|
|
}
|
|
|
|
async refresh() {
|
|
await this.loadCiphers();
|
|
await this.refreshCollections();
|
|
super.refresh();
|
|
}
|
|
|
|
async search(timeout: number = null) {
|
|
await super.search(timeout, this.allCiphers);
|
|
}
|
|
|
|
events(c: CipherView) {
|
|
this.onEventsClicked.emit(c);
|
|
}
|
|
|
|
protected showFixOldAttachments(c: CipherView) {
|
|
return this.organization?.canEditAnyCollection && c.hasOldAttachments;
|
|
}
|
|
|
|
checkAll(select: boolean) {
|
|
if (select) {
|
|
this.checkAll(false);
|
|
}
|
|
|
|
const items: VaultItemRow[] = [...this.collections, ...this.ciphers];
|
|
if (!items.length) {
|
|
return;
|
|
}
|
|
|
|
const selectCount = select && items.length > MaxCheckedCount ? MaxCheckedCount : items.length;
|
|
for (let i = 0; i < selectCount; i++) {
|
|
this.checkRow(items[i], select);
|
|
}
|
|
}
|
|
|
|
checkRow(item: VaultItemRow, select?: boolean) {
|
|
if (item instanceof TreeNode && item.node.id == null) {
|
|
return;
|
|
}
|
|
|
|
// Do not allow checking a collection we cannot delete
|
|
if (item instanceof TreeNode && !this.canDeleteCollection(item.node)) {
|
|
return;
|
|
}
|
|
|
|
item.checked = select ?? !item.checked;
|
|
}
|
|
|
|
get selectedCollections(): TreeNode<CollectionFilter>[] {
|
|
if (!this.collections) {
|
|
return [];
|
|
}
|
|
return this.collections.filter((c) => !!(c as VaultItemRow).checked);
|
|
}
|
|
|
|
get selectedCollectionIds(): string[] {
|
|
return this.selectedCollections.map((c) => c.node.id);
|
|
}
|
|
|
|
canEditCollection(c: CollectionAdminView): boolean {
|
|
// Only edit collections if we're in the org vault and not editing "Unassigned"
|
|
if (this.organization === undefined || c.id === null) {
|
|
return false;
|
|
}
|
|
|
|
// Otherwise, check if we can edit the specified collection
|
|
return (
|
|
this.organization.canEditAnyCollection ||
|
|
(this.organization.canEditAssignedCollections && c.assigned)
|
|
);
|
|
}
|
|
|
|
async editCollection(c: CollectionView, tab: "info" | "access"): Promise<void> {
|
|
const tabType = tab == "info" ? CollectionDialogTabType.Info : CollectionDialogTabType.Access;
|
|
|
|
const dialog = openCollectionDialog(this.dialogService, {
|
|
data: { collectionId: c?.id, organizationId: this.organization?.id, initialTab: tabType },
|
|
});
|
|
|
|
const result = await lastValueFrom(dialog.closed);
|
|
if (result === CollectionDialogResult.Saved || result === CollectionDialogResult.Deleted) {
|
|
this.actionPromise = this.refresh();
|
|
await this.actionPromise;
|
|
this.actionPromise = null;
|
|
}
|
|
}
|
|
|
|
get showMissingCollectionPermissionMessage(): boolean {
|
|
// Not filtering by collections, so no need to show message
|
|
if (this.activeFilter.selectedCollectionNode == null) {
|
|
return false;
|
|
}
|
|
|
|
// Filtering by all collections, so no need to show message
|
|
if (this.activeFilter.selectedCollectionNode.node.id == "AllCollections") {
|
|
return false;
|
|
}
|
|
|
|
// Filtering by a collection, so show message if user is not assigned
|
|
return !this.activeFilter.selectedCollectionNode.node.assigned && !this.organization.isAdmin;
|
|
}
|
|
|
|
canDeleteCollection(c: CollectionAdminView): boolean {
|
|
// Only delete collections if we're in the org vault and not deleting "Unassigned"
|
|
if (this.organization === undefined || c.id === null) {
|
|
return false;
|
|
}
|
|
|
|
// Otherwise, check if we can delete the specified collection
|
|
return (
|
|
this.organization?.canDeleteAnyCollection ||
|
|
(this.organization?.canDeleteAssignedCollections && c.assigned)
|
|
);
|
|
}
|
|
|
|
async deleteCollection(collection: CollectionView): Promise<void> {
|
|
if (!this.organization.canDeleteAssignedCollections) {
|
|
this.platformUtilsService.showToast(
|
|
"error",
|
|
this.i18nService.t("errorOccurred"),
|
|
this.i18nService.t("missingPermissions")
|
|
);
|
|
return;
|
|
}
|
|
const confirmed = await this.platformUtilsService.showDialog(
|
|
this.i18nService.t("deleteCollectionConfirmation"),
|
|
collection.name,
|
|
this.i18nService.t("yes"),
|
|
this.i18nService.t("no"),
|
|
"warning"
|
|
);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
try {
|
|
this.actionPromise = this.apiService.deleteCollection(this.organization?.id, collection.id);
|
|
await this.actionPromise;
|
|
this.platformUtilsService.showToast(
|
|
"success",
|
|
null,
|
|
this.i18nService.t("deletedCollectionId", collection.name)
|
|
);
|
|
await this.refresh();
|
|
} catch (e) {
|
|
this.logService.error(e);
|
|
}
|
|
}
|
|
|
|
async bulkDelete() {
|
|
if (!(await this.repromptCipher())) {
|
|
return;
|
|
}
|
|
|
|
const selectedCipherIds = this.selectedCipherIds;
|
|
const selectedCollectionIds = this.deleted ? null : this.selectedCollectionIds;
|
|
|
|
if (!selectedCipherIds?.length && !selectedCollectionIds?.length) {
|
|
this.platformUtilsService.showToast(
|
|
"error",
|
|
this.i18nService.t("errorOccurred"),
|
|
this.i18nService.t("nothingSelected")
|
|
);
|
|
return;
|
|
}
|
|
|
|
const dialog = openBulkDeleteDialog(this.dialogService, {
|
|
data: {
|
|
permanent: this.deleted,
|
|
cipherIds: selectedCipherIds,
|
|
collectionIds: selectedCollectionIds,
|
|
organization: this.organization,
|
|
},
|
|
});
|
|
|
|
const result = await lastValueFrom(dialog.closed);
|
|
if (result === BulkDeleteDialogResult.Deleted) {
|
|
this.actionPromise = this.refresh();
|
|
await this.actionPromise;
|
|
this.actionPromise = null;
|
|
}
|
|
}
|
|
|
|
protected deleteCipherWithServer(id: string, permanent: boolean) {
|
|
if (!this.organization?.canEditAnyCollection) {
|
|
return super.deleteCipherWithServer(id, this.deleted);
|
|
}
|
|
return permanent
|
|
? this.apiService.deleteCipherAdmin(id)
|
|
: this.apiService.putDeleteCipherAdmin(id);
|
|
}
|
|
}
|