1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-23 19:53:43 +00:00

[SG-998] Move org vault to vault team (#4681)

* Rename vault folder to org-vault

* Move org-vault folder to vault

* Rename nested vault folder to individual-vault

* Fix vault module imports

* Undo desktop imports

* Remove extra app folder

* Add @bitwarden/team-vault-dev code owners

* Update .github/CODEOWNERS

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>

* Remove eslint ignore comments

---------

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
This commit is contained in:
Robyn MacCallum
2023-02-08 11:39:40 -05:00
committed by GitHub
parent 67aad0c5b7
commit 18e143b9bb
89 changed files with 97 additions and 88 deletions

View File

@@ -24,9 +24,7 @@ import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
// TODO refine elsint rule for **/app/core/*
// eslint-disable-next-line no-restricted-imports
import { PasswordRepromptService } from "../../vault/app/core/password-reprompt.service";
import { PasswordRepromptService } from "../../vault/core/password-reprompt.service";
import { BroadcasterMessagingService } from "./broadcaster-messaging.service";
import { EventService } from "./event.service";

View File

@@ -12,13 +12,14 @@ import {
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { VaultModule } from "../../vault/org-vault/vault.module";
import { OrganizationPermissionsGuard } from "./guards/org-permissions.guard";
import { OrganizationRedirectGuard } from "./guards/org-redirect.guard";
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
import { CollectionsComponent } from "./manage/collections.component";
import { GroupsComponent } from "./manage/groups.component";
import { ManageComponent } from "./manage/manage.component";
import { VaultModule } from "./vault/vault.module";
const routes: Routes = [
{

View File

@@ -1,133 +0,0 @@
import { Component } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.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 { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherCreateRequest } from "@bitwarden/common/vault/models/request/cipher-create.request";
import { CipherRequest } from "@bitwarden/common/vault/models/request/cipher.request";
import { AddEditComponent as BaseAddEditComponent } from "../../../vault/app/vault/add-edit.component";
@Component({
selector: "app-org-vault-add-edit",
templateUrl: "../../../vault/app/vault/add-edit.component.html",
})
export class AddEditComponent extends BaseAddEditComponent {
originalCipher: Cipher = null;
protected override componentName = "app-org-vault-add-edit";
constructor(
cipherService: CipherService,
folderService: FolderService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
auditService: AuditService,
stateService: StateService,
collectionService: CollectionService,
totpService: TotpService,
passwordGenerationService: PasswordGenerationService,
private apiService: ApiService,
messagingService: MessagingService,
eventCollectionService: EventCollectionService,
policyService: PolicyService,
logService: LogService,
passwordRepromptService: PasswordRepromptService,
organizationService: OrganizationService
) {
super(
cipherService,
folderService,
i18nService,
platformUtilsService,
auditService,
stateService,
collectionService,
totpService,
passwordGenerationService,
messagingService,
eventCollectionService,
policyService,
organizationService,
logService,
passwordRepromptService
);
}
protected allowOwnershipAssignment() {
if (
this.ownershipOptions != null &&
(this.ownershipOptions.length > 1 || !this.allowPersonal)
) {
if (this.organization != null) {
return this.cloneMode && this.organization.canEditAnyCollection;
} else {
return !this.editMode || this.cloneMode;
}
}
return false;
}
protected loadCollections() {
if (!this.organization.canEditAnyCollection) {
return super.loadCollections();
}
return Promise.resolve(this.collections);
}
protected async loadCipher() {
if (!this.organization.canEditAnyCollection) {
return await super.loadCipher();
}
const response = await this.apiService.getCipherAdmin(this.cipherId);
const data = new CipherData(response);
data.edit = true;
const cipher = new Cipher(data);
this.originalCipher = cipher;
return cipher;
}
protected encryptCipher() {
if (!this.organization.canEditAnyCollection) {
return super.encryptCipher();
}
return this.cipherService.encrypt(this.cipher, null, this.originalCipher);
}
protected async saveCipher(cipher: Cipher) {
if (!this.organization.canEditAnyCollection || cipher.organizationId == null) {
return super.saveCipher(cipher);
}
if (this.editMode && !this.cloneMode) {
const request = new CipherRequest(cipher);
return this.apiService.putCipherAdmin(this.cipherId, request);
} else {
const request = new CipherCreateRequest(cipher);
return this.apiService.postCipherAdmin(request);
}
}
protected async deleteCipher() {
if (!this.organization.canEditAnyCollection) {
return super.deleteCipher();
}
return this.cipher.isDeleted
? this.apiService.deleteCipherAdmin(this.cipherId)
: this.apiService.putDeleteCipherAdmin(this.cipherId);
}
}

View File

@@ -1,80 +0,0 @@
import { Component } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
import { AttachmentsComponent as BaseAttachmentsComponent } from "../../../vault/app/vault/attachments.component";
@Component({
selector: "app-org-vault-attachments",
templateUrl: "../../../vault/app/vault/attachments.component.html",
})
export class AttachmentsComponent extends BaseAttachmentsComponent {
viewOnly = false;
organization: Organization;
constructor(
cipherService: CipherService,
i18nService: I18nService,
cryptoService: CryptoService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService,
fileDownloadService: FileDownloadService
) {
super(
cipherService,
i18nService,
cryptoService,
stateService,
platformUtilsService,
apiService,
logService,
fileDownloadService
);
}
protected async reupload(attachment: AttachmentView) {
if (this.organization.canEditAnyCollection && this.showFixOldAttachments(attachment)) {
await super.reuploadCipherAttachment(attachment, true);
}
}
protected async loadCipher() {
if (!this.organization.canEditAnyCollection) {
return await super.loadCipher();
}
const response = await this.apiService.getCipherAdmin(this.cipherId);
return new Cipher(new CipherData(response));
}
protected saveCipherAttachment(file: File) {
return this.cipherService.saveAttachmentWithServer(
this.cipherDomain,
file,
this.organization.canEditAnyCollection
);
}
protected deleteCipherAttachment(attachmentId: string) {
if (!this.organization.canEditAnyCollection) {
return super.deleteCipherAttachment(attachmentId);
}
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
}
protected showFixOldAttachments(attachment: AttachmentView) {
return attachment.key == null && this.organization.canEditAnyCollection;
}
}

View File

@@ -1,13 +0,0 @@
import { NgModule } from "@angular/core";
import { PipesModule } from "../../../../vault/app/vault/pipes/pipes.module";
import { SharedModule } from "../../../shared";
import { CollectionNameBadgeComponent } from "./collection-name.badge.component";
@NgModule({
imports: [SharedModule, PipesModule],
declarations: [CollectionNameBadgeComponent],
exports: [CollectionNameBadgeComponent],
})
export class CollectionBadgeModule {}

View File

@@ -1,6 +0,0 @@
<ng-container *ngFor="let c of shownCollections">
<span bitBadge badgeType="secondary">{{ c | collectionNameFromId: collections }}</span>
</ng-container>
<ng-container *ngIf="showXMore">
<span bitBadge badgeType="secondary">+ {{ xMoreCount }} more</span>
</ng-container>

View File

@@ -1,24 +0,0 @@
import { Component, Input } from "@angular/core";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
@Component({
selector: "app-collection-badge",
templateUrl: "collection-name-badge.component.html",
})
export class CollectionNameBadgeComponent {
@Input() collectionIds: string[];
@Input() collections: CollectionView[];
get shownCollections(): string[] {
return this.showXMore ? this.collectionIds.slice(0, 2) : this.collectionIds;
}
get showXMore(): boolean {
return this.collectionIds.length > 3;
}
get xMoreCount(): number {
return this.collectionIds.length - 2;
}
}

View File

@@ -11,11 +11,11 @@ import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherCollectionsRequest } from "@bitwarden/common/vault/models/request/cipher-collections.request";
import { CollectionsComponent as BaseCollectionsComponent } from "../../../vault/app/vault/collections.component";
import { CollectionsComponent as BaseCollectionsComponent } from "../../../vault/individual-vault/collections.component";
@Component({
selector: "app-org-vault-collections",
templateUrl: "../../../vault/app/vault/collections.component.html",
templateUrl: "../../../vault/individual-vault/collections.component.html",
})
export class CollectionsComponent extends BaseCollectionsComponent {
organization: Organization;

View File

@@ -1,13 +0,0 @@
import { NgModule } from "@angular/core";
import { PipesModule } from "../../../../vault/app/vault/pipes/pipes.module";
import { SharedModule } from "../../../shared";
import { GroupNameBadgeComponent } from "./group-name-badge.component";
@NgModule({
imports: [SharedModule, PipesModule],
declarations: [GroupNameBadgeComponent],
exports: [GroupNameBadgeComponent],
})
export class GroupBadgeModule {}

View File

@@ -1 +0,0 @@
<bit-badge-list [items]="groupNames" [maxItems]="3" badgeType="secondary"></bit-badge-list>

View File

@@ -1,27 +0,0 @@
import { Component, Input, OnChanges } from "@angular/core";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request";
import { GroupView } from "../../core";
@Component({
selector: "app-group-badge",
templateUrl: "group-name-badge.component.html",
})
export class GroupNameBadgeComponent implements OnChanges {
@Input() selectedGroups: SelectionReadOnlyRequest[];
@Input() allGroups: GroupView[];
protected groupNames: string[] = [];
constructor(private i18nService: I18nService) {}
ngOnChanges() {
this.groupNames = this.selectedGroups
.map((g) => {
return this.allGroups.find((o) => o.id === g.id)?.name;
})
.sort(this.i18nService.collator.compare);
}
}

View File

@@ -1,77 +0,0 @@
import { Component, Input, OnDestroy, OnInit } from "@angular/core";
import { firstValueFrom, Subject, switchMap, takeUntil } from "rxjs";
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 { VaultFilterComponent as BaseVaultFilterComponent } from "../../../../vault/app/vault/vault-filter/components/vault-filter.component";
import {
VaultFilterList,
VaultFilterType,
} from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter-section.type";
import { CollectionFilter } from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
@Component({
selector: "app-organization-vault-filter",
templateUrl: "../../../../vault/app/vault/vault-filter/components/vault-filter.component.html",
})
export class VaultFilterComponent extends BaseVaultFilterComponent implements OnInit, OnDestroy {
@Input() set organization(value: Organization) {
if (value && value !== this._organization) {
this._organization = value;
this.vaultFilterService.setOrganizationFilter(this._organization);
}
}
_organization: Organization;
protected destroy$: Subject<void>;
async ngOnInit() {
this.filters = await this.buildAllFilters();
if (!this.activeFilter.selectedCipherTypeNode) {
this.activeFilter.resetFilter();
this.activeFilter.selectedCollectionNode =
(await this.getDefaultFilter()) as TreeNode<CollectionFilter>;
}
this.isLoaded = true;
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
protected loadSubscriptions() {
this.vaultFilterService.filteredCollections$
.pipe(
switchMap(async (collections) => {
this.removeInvalidCollectionSelection(collections);
}),
takeUntil(this.destroy$)
)
.subscribe();
}
protected async removeInvalidCollectionSelection(collections: CollectionView[]) {
if (this.activeFilter.selectedCollectionNode) {
if (!collections.some((f) => f.id === this.activeFilter.collectionId)) {
this.activeFilter.resetFilter();
this.activeFilter.selectedCollectionNode =
(await this.getDefaultFilter()) as TreeNode<CollectionFilter>;
this.applyVaultFilter(this.activeFilter);
}
}
}
async buildAllFilters(): Promise<VaultFilterList> {
const builderFilter = {} as VaultFilterList;
builderFilter.typeFilter = await this.addTypeFilter(["favorites"]);
builderFilter.collectionFilter = await this.addCollectionFilter();
builderFilter.trashFilter = await this.addTrashFilter();
return builderFilter;
}
async getDefaultFilter(): Promise<TreeNode<VaultFilterType>> {
return await firstValueFrom(this.filters?.collectionFilter.data$);
}
}

View File

@@ -1,20 +0,0 @@
import { NgModule } from "@angular/core";
import { VaultFilterService as VaultFilterServiceAbstraction } from "../../../../vault/app/vault/vault-filter/services/abstractions/vault-filter.service";
import { VaultFilterSharedModule } from "../../../../vault/app/vault/vault-filter/shared/vault-filter-shared.module";
import { VaultFilterComponent } from "./vault-filter.component";
import { VaultFilterService } from "./vault-filter.service";
@NgModule({
imports: [VaultFilterSharedModule],
declarations: [VaultFilterComponent],
exports: [VaultFilterComponent],
providers: [
{
provide: VaultFilterServiceAbstraction,
useClass: VaultFilterService,
},
],
})
export class VaultFilterModule {}

View File

@@ -1,90 +0,0 @@
import { Injectable, OnDestroy } from "@angular/core";
import { filter, map, Observable, ReplaySubject, Subject, switchMap, takeUntil } from "rxjs";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import {
canAccessVaultTab,
OrganizationService,
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { VaultFilterService as BaseVaultFilterService } from "../../../../vault/app/vault/vault-filter/services/vault-filter.service";
import { CollectionFilter } from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
import { CollectionAdminView } from "../../core";
import { CollectionAdminService } from "../../core/services/collection-admin.service";
@Injectable()
export class VaultFilterService extends BaseVaultFilterService implements OnDestroy {
private destroy$ = new Subject<void>();
private _collections = new ReplaySubject<CollectionAdminView[]>(1);
filteredCollections$: Observable<CollectionAdminView[]> = this._collections.asObservable();
collectionTree$: Observable<TreeNode<CollectionFilter>> = this.filteredCollections$.pipe(
map((collections) => this.buildCollectionTree(collections))
);
constructor(
stateService: StateService,
organizationService: OrganizationService,
folderService: FolderService,
cipherService: CipherService,
collectionService: CollectionService,
policyService: PolicyService,
i18nService: I18nService,
protected collectionAdminService: CollectionAdminService
) {
super(
stateService,
organizationService,
folderService,
cipherService,
collectionService,
policyService,
i18nService
);
this.loadSubscriptions();
}
protected loadSubscriptions() {
this._organizationFilter
.pipe(
filter((org) => org != null),
switchMap((org) => {
return this.loadCollections(org);
}),
takeUntil(this.destroy$)
)
.subscribe((collections) => {
this._collections.next(collections);
});
}
async reloadCollections() {
this._collections.next(await this.loadCollections(this._organizationFilter.getValue()));
}
protected async loadCollections(org: Organization): Promise<CollectionAdminView[]> {
let collections: CollectionAdminView[] = [];
if (canAccessVaultTab(org)) {
collections = await this.collectionAdminService.getAll(org.id);
const noneCollection = new CollectionAdminView();
noneCollection.name = this.i18nService.t("unassigned");
noneCollection.organizationId = org.id;
collections.push(noneCollection);
}
return collections;
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@@ -1,113 +0,0 @@
<div class="tw-mb-4 tw-flex tw-items-start tw-justify-between">
<div>
<bit-breadcrumbs *ngIf="activeFilter.collectionBreadcrumbs.length > 0">
<bit-breadcrumb
*ngFor="let collection of activeFilter.collectionBreadcrumbs; let first = first"
[icon]="first ? undefined : 'bwi-collection'"
(click)="applyCollectionFilter(collection)"
>
<!-- First node in the tree is the "Org Name Vault" item. The rest come from user input. -->
<ng-container *ngIf="first">
{{ activeOrganizationId | orgNameFromId: (organizations$ | async) }}
{{ "vault" | i18n | lowercase }}
</ng-container>
<ng-container *ngIf="!first">{{ collection.node.name }}</ng-container>
</bit-breadcrumb>
</bit-breadcrumbs>
<h1 class="tw-mb-0 tw-mt-1 tw-flex tw-items-center tw-space-x-2">
<i
*ngIf="activeFilter.isCollectionSelected"
class="bwi bwi-collection"
aria-hidden="true"
></i>
<span>{{ title }}</span>
<ng-container
*ngIf="activeFilter.isCollectionSelected && !activeFilter.isUnassignedCollectionSelected"
>
<button
bitIconButton="bwi-angle-down"
[bitMenuTriggerFor]="editCollectionMenu"
size="small"
type="button"
aria-haspopup
></button>
<bit-menu #editCollectionMenu>
<button
type="button"
*ngIf="canEditCollection(activeFilter.selectedCollectionNode.node)"
bitMenuItem
(click)="editCollection(activeFilter.selectedCollectionNode.node, 'info')"
>
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
{{ "editInfo" | i18n }}
</button>
<button
type="button"
*ngIf="canEditCollection(activeFilter.selectedCollectionNode.node)"
bitMenuItem
(click)="editCollection(activeFilter.selectedCollectionNode.node, 'access')"
>
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
{{ "access" | i18n }}
</button>
<button
type="button"
*ngIf="canDeleteCollection(activeFilter.selectedCollectionNode.node)"
bitMenuItem
(click)="deleteCollection(activeFilter.selectedCollectionNode.node)"
>
<span class="tw-text-danger">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ "delete" | i18n }}
</span>
</button>
</bit-menu>
</ng-container>
<small #actionSpinner [appApiAction]="actionPromise">
<ng-container *ngIf="$any(actionSpinner).loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
</small>
</h1>
</div>
<div *ngIf="!activeFilter.isDeleted" class="tw-shrink-0">
<div *ngIf="organization.canCreateNewCollections" appListDropdown>
<button
bitButton
buttonType="primary"
type="button"
[bitMenuTriggerFor]="addOptions"
id="newItemDropdown"
appA11yTitle="{{ 'new' | i18n }}"
>
{{ "new" | i18n }}<i class="bwi bwi-angle-down tw-ml-2" aria-hidden="true"></i>
</button>
<bit-menu #addOptions aria-labelledby="newItemDropdown">
<button type="button" bitMenuItem (click)="addCipher()">
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>
{{ "item" | i18n }}
</button>
<button type="button" bitMenuItem (click)="addCollection()">
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
{{ "collection" | i18n }}
</button>
</bit-menu>
</div>
<button
*ngIf="!organization.canCreateNewCollections"
type="button"
bitButton
buttonType="primary"
(click)="addCipher()"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newItem" | i18n }}
</button>
</div>
</div>

View File

@@ -1,243 +0,0 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom, lastValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.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 { ProductType } from "@bitwarden/common/enums/productType";
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 {
DialogService,
SimpleDialogCloseType,
SimpleDialogOptions,
SimpleDialogType,
} from "@bitwarden/components";
import { VaultFilterService } from "../../../../vault/app/vault/vault-filter/services/abstractions/vault-filter.service";
import { VaultFilter } from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter.model";
import { CollectionFilter } from "../../../../vault/app/vault/vault-filter/shared/models/vault-filter.type";
import { CollectionAdminService, CollectionAdminView } from "../../core";
import {
CollectionDialogResult,
CollectionDialogTabType,
openCollectionDialog,
} from "../../shared";
@Component({
selector: "app-org-vault-header",
templateUrl: "./vault-header.component.html",
})
export class VaultHeaderComponent {
/**
* The organization currently being viewed
*/
@Input() organization: Organization;
/**
* Promise that is used to determine the loading state of the header via the ApiAction directive.
* When the promise exists and is not resolved, the loading spinner will be shown.
*/
@Input() actionPromise: Promise<any>;
/**
* The filter being actively applied to the vault view
*/
@Input() activeFilter: VaultFilter;
/**
* Emits when the active filter has been modified by the header
*/
@Output() activeFilterChanged = new EventEmitter<VaultFilter>();
/**
* Emits an event when a collection is modified or deleted via the header collection dropdown menu
*/
@Output() onCollectionChanged = new EventEmitter<CollectionView | null>();
/**
* Emits an event when the new item button is clicked in the header
*/
@Output() onAddCipher = new EventEmitter<void>();
protected organizations$ = this.organizationService.organizations$;
constructor(
private organizationService: OrganizationService,
private i18nService: I18nService,
private dialogService: DialogService,
private vaultFilterService: VaultFilterService,
private platformUtilsService: PlatformUtilsService,
private apiService: ApiService,
private logService: LogService,
private collectionAdminService: CollectionAdminService,
private router: Router
) {}
/**
* The id of the organization that is currently being filtered on.
* This can come from a collection filter, organization filter, or the current organization when viewed
* in the organization admin console and no other filters are applied.
*/
get activeOrganizationId() {
if (this.activeFilter.selectedCollectionNode != null) {
return this.activeFilter.selectedCollectionNode.node.organizationId;
}
if (this.activeFilter.selectedOrganizationNode != null) {
return this.activeFilter.selectedOrganizationNode.node.id;
}
return this.organization.id;
}
get title() {
if (this.activeFilter.isCollectionSelected) {
return this.activeFilter.selectedCollectionNode.node.name;
}
if (this.activeFilter.isUnassignedCollectionSelected) {
return this.i18nService.t("unassigned");
}
return `${this.organization.name} ${this.i18nService.t("vault").toLowerCase()}`;
}
private showFreeOrgUpgradeDialog(): void {
const orgUpgradeSimpleDialogOpts: SimpleDialogOptions = {
title: this.i18nService.t("upgradeOrganization"),
content: this.i18nService.t(
this.organization.canManageBilling
? "freeOrgMaxCollectionReachedManageBilling"
: "freeOrgMaxCollectionReachedNoManageBilling",
this.organization.maxCollections
),
type: SimpleDialogType.PRIMARY,
};
if (this.organization.canManageBilling) {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("upgrade");
} else {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("ok");
orgUpgradeSimpleDialogOpts.cancelButtonText = null; // hide secondary btn
}
const simpleDialog = this.dialogService.openSimpleDialog(orgUpgradeSimpleDialogOpts);
firstValueFrom(simpleDialog.closed).then((result: SimpleDialogCloseType | undefined) => {
if (!result) {
return;
}
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canManageBilling) {
this.router.navigate(["/organizations", this.organization.id, "billing", "subscription"], {
queryParams: { upgrade: true },
});
}
});
}
applyCollectionFilter(collection: TreeNode<CollectionFilter>) {
const filter = this.activeFilter;
filter.resetFilter();
filter.selectedCollectionNode = collection;
this.activeFilterChanged.emit(filter);
}
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)
);
}
addCipher() {
this.onAddCipher.emit();
}
async addCollection() {
if (this.organization.planProductType === ProductType.Free) {
const collections = await this.collectionAdminService.getAll(this.organization.id);
if (collections.length === this.organization.maxCollections) {
this.showFreeOrgUpgradeDialog();
return;
}
}
const dialog = openCollectionDialog(this.dialogService, {
data: {
organizationId: this.organization?.id,
parentCollectionId: this.activeFilter.collectionId,
},
});
const result = await lastValueFrom(dialog.closed);
if (result === CollectionDialogResult.Saved || result === CollectionDialogResult.Deleted) {
this.onCollectionChanged.emit(null);
}
}
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.onCollectionChanged.emit(c);
}
}
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)
);
this.onCollectionChanged.emit(collection);
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,321 +0,0 @@
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 { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.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);
}
}

View File

@@ -1,21 +0,0 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { canAccessVaultTab } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { OrganizationPermissionsGuard } from "../guards/org-permissions.guard";
import { VaultComponent } from "./vault.component";
const routes: Routes = [
{
path: "",
component: VaultComponent,
canActivate: [OrganizationPermissionsGuard],
data: { titleId: "vaults", organizationPermissions: canAccessVaultTab },
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class VaultRoutingModule {}

View File

@@ -1,54 +0,0 @@
<!-- Please remove this disable statement when editing this file! -->
<!-- eslint-disable @angular-eslint/template/button-has-type -->
<div class="container page-content">
<div class="row">
<div class="col-3">
<div class="groupings">
<div class="content">
<div class="inner-content">
<app-organization-vault-filter
#vaultFilter
[organization]="organization"
[activeFilter]="activeFilter"
(activeFilterChanged)="applyVaultFilter($event)"
(onSearchTextChanged)="filterSearchText($event)"
></app-organization-vault-filter>
</div>
</div>
</div>
</div>
<div class="col-9">
<app-org-vault-header
[activeFilter]="activeFilter"
(activeFilterChanged)="applyVaultFilter($event)"
(onCollectionChanged)="refreshItems()"
[actionPromise]="vaultItemsComponent.actionPromise"
(onAddCipher)="addCipher()"
[organization]="organization"
></app-org-vault-header>
<app-callout
type="warning"
*ngIf="activeFilter.isDeleted"
icon="bwi bwi-exclamation-triangle"
>
{{ trashCleanupWarning }}
</app-callout>
<app-org-vault-items
[activeFilter]="activeFilter"
[initOrganization]="organization"
(activeFilterChanged)="applyVaultFilter($event)"
(onCipherClicked)="navigateToCipher($event)"
(onAttachmentsClicked)="editCipherAttachments($event)"
(onAddCipher)="addCipher()"
(onEditCipherCollectionsClicked)="editCipherCollections($event)"
(onEventsClicked)="viewEvents($event)"
(onCloneClicked)="cloneCipher($event)"
>
</app-org-vault-items>
</div>
</div>
</div>
<ng-template #attachments></ng-template>
<ng-template #cipherAddEdit></ng-template>
<ng-template #collections></ng-template>
<ng-template #eventsTemplate></ng-template>

View File

@@ -1,324 +0,0 @@
import {
ChangeDetectorRef,
Component,
NgZone,
OnDestroy,
OnInit,
ViewChild,
ViewContainerRef,
} from "@angular/core";
import { ActivatedRoute, Params, Router } from "@angular/router";
import { combineLatest, firstValueFrom, Subject } from "rxjs";
import { first, switchMap, takeUntil } from "rxjs/operators";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { DialogService } from "@bitwarden/components";
import { VaultFilterService } from "../../../vault/app/vault/vault-filter/services/abstractions/vault-filter.service";
import { VaultFilter } from "../../../vault/app/vault/vault-filter/shared/models/vault-filter.model";
import { EntityEventsComponent } from "../manage/entity-events.component";
import { AddEditComponent } from "./add-edit.component";
import { AttachmentsComponent } from "./attachments.component";
import { CollectionsComponent } from "./collections.component";
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
import { VaultItemsComponent } from "./vault-items.component";
const BroadcasterSubscriptionId = "OrgVaultComponent";
@Component({
selector: "app-org-vault",
templateUrl: "vault.component.html",
})
export class VaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultFilter", { static: true })
vaultFilterComponent: VaultFilterComponent;
@ViewChild(VaultItemsComponent, { static: true }) vaultItemsComponent: VaultItemsComponent;
@ViewChild("attachments", { read: ViewContainerRef, static: true })
attachmentsModalRef: ViewContainerRef;
@ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true })
cipherAddEditModalRef: ViewContainerRef;
@ViewChild("collections", { read: ViewContainerRef, static: true })
collectionsModalRef: ViewContainerRef;
@ViewChild("eventsTemplate", { read: ViewContainerRef, static: true })
eventsModalRef: ViewContainerRef;
organization: Organization;
trashCleanupWarning: string = null;
activeFilter: VaultFilter = new VaultFilter();
private destroy$ = new Subject<void>();
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
protected vaultFilterService: VaultFilterService,
private router: Router,
private changeDetectorRef: ChangeDetectorRef,
private syncService: SyncService,
private i18nService: I18nService,
private modalService: ModalService,
private dialogService: DialogService,
private messagingService: MessagingService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService,
private cipherService: CipherService,
private passwordRepromptService: PasswordRepromptService
) {}
async ngOnInit() {
this.trashCleanupWarning = this.i18nService.t(
this.platformUtilsService.isSelfHost()
? "trashCleanupWarningSelfHosted"
: "trashCleanupWarning"
);
this.route.parent.params.pipe(takeUntil(this.destroy$)).subscribe((params) => {
this.organization = this.organizationService.get(params.organizationId);
});
this.route.queryParams.pipe(first(), takeUntil(this.destroy$)).subscribe((qParams) => {
this.vaultItemsComponent.searchText = this.vaultFilterComponent.searchText = qParams.search;
});
// verifies that the organization has been set
combineLatest([this.route.queryParams, this.route.parent.params])
.pipe(
switchMap(async ([qParams]) => {
const cipherId = getCipherIdFromParams(qParams);
if (!cipherId) {
return;
}
if (
// Handle users with implicit collection access since they use the admin endpoint
this.organization.canUseAdminCollections ||
(await this.cipherService.get(cipherId)) != null
) {
this.editCipherId(cipherId);
} else {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("unknownCipher")
);
this.router.navigate([], {
queryParams: { cipherId: null, itemId: null },
queryParamsHandling: "merge",
});
}
}),
takeUntil(this.destroy$)
)
.subscribe();
if (!this.organization.canUseAdminCollections) {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "syncCompleted":
if (message.successfully) {
await Promise.all([
this.vaultFilterService.reloadCollections(),
this.vaultItemsComponent.refresh(),
]);
this.changeDetectorRef.detectChanges();
}
break;
}
});
});
await this.syncService.fullSync(false);
}
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.destroy$.next();
this.destroy$.complete();
}
async applyVaultFilter(filter: VaultFilter) {
this.activeFilter = filter;
this.vaultItemsComponent.showAddNew = !this.activeFilter.isDeleted;
await this.vaultItemsComponent.reload(
this.activeFilter.buildFilter(),
this.activeFilter.isDeleted
);
this.go();
}
async refreshItems() {
this.vaultItemsComponent.actionPromise = this.vaultItemsComponent.refresh();
await this.vaultItemsComponent.actionPromise;
this.vaultItemsComponent.actionPromise = null;
}
filterSearchText(searchText: string) {
this.vaultItemsComponent.searchText = searchText;
this.vaultItemsComponent.search(200);
}
async editCipherAttachments(cipher: CipherView) {
if (this.organization.maxStorageGb == null || this.organization.maxStorageGb === 0) {
this.messagingService.send("upgradeOrganization", { organizationId: cipher.organizationId });
return;
}
let madeAttachmentChanges = false;
const [modal] = await this.modalService.openViewRef(
AttachmentsComponent,
this.attachmentsModalRef,
(comp) => {
comp.organization = this.organization;
comp.cipherId = cipher.id;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
comp.onUploadedAttachment.subscribe(() => (madeAttachmentChanges = true));
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
comp.onDeletedAttachment.subscribe(() => (madeAttachmentChanges = true));
}
);
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
modal.onClosed.subscribe(async () => {
if (madeAttachmentChanges) {
await this.vaultItemsComponent.refresh();
}
madeAttachmentChanges = false;
});
}
async editCipherCollections(cipher: CipherView) {
const currCollections = await firstValueFrom(this.vaultFilterService.filteredCollections$);
const [modal] = await this.modalService.openViewRef(
CollectionsComponent,
this.collectionsModalRef,
(comp) => {
comp.collectionIds = cipher.collectionIds;
comp.collections = currCollections.filter((c) => !c.readOnly && c.id != null);
comp.organization = this.organization;
comp.cipherId = cipher.id;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onSavedCollections.subscribe(async () => {
modal.close();
await this.vaultItemsComponent.refresh();
});
}
);
}
async addCipher() {
const component = await this.editCipher(null);
component.organizationId = this.organization.id;
component.type = this.activeFilter.cipherType;
component.collections = (
await firstValueFrom(this.vaultFilterService.filteredCollections$)
).filter((c) => !c.readOnly && c.id != null);
if (this.activeFilter.collectionId) {
component.collectionIds = [this.activeFilter.collectionId];
}
}
async navigateToCipher(cipher: CipherView) {
this.go({ itemId: cipher?.id });
}
async editCipher(cipher: CipherView) {
return this.editCipherId(cipher?.id);
}
async editCipherId(cipherId: string) {
const cipher = await this.cipherService.get(cipherId);
if (cipher != null && cipher.reprompt != 0) {
if (!(await this.passwordRepromptService.showPasswordPrompt())) {
this.go({ cipherId: null, itemId: null });
return;
}
}
const [modal, childComponent] = await this.modalService.openViewRef(
AddEditComponent,
this.cipherAddEditModalRef,
(comp) => {
comp.organization = this.organization;
comp.cipherId = cipherId;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onSavedCipher.subscribe(async () => {
modal.close();
await this.vaultItemsComponent.refresh();
});
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onDeletedCipher.subscribe(async () => {
modal.close();
await this.vaultItemsComponent.refresh();
});
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
comp.onRestoredCipher.subscribe(async () => {
modal.close();
await this.vaultItemsComponent.refresh();
});
}
);
modal.onClosedPromise().then(() => {
this.go({ cipherId: null, itemId: null });
});
return childComponent;
}
async cloneCipher(cipher: CipherView) {
const component = await this.editCipher(cipher);
component.cloneMode = true;
component.organizationId = this.organization.id;
component.collections = (
await firstValueFrom(this.vaultFilterService.filteredCollections$)
).filter((c) => !c.readOnly && c.id != null);
component.collectionIds = cipher.collectionIds;
}
async viewEvents(cipher: CipherView) {
await this.modalService.openViewRef(EntityEventsComponent, this.eventsModalRef, (comp) => {
comp.name = cipher.name;
comp.organizationId = this.organization.id;
comp.entityId = cipher.id;
comp.showUser = true;
comp.entity = "cipher";
});
}
private go(queryParams: any = null) {
if (queryParams == null) {
queryParams = {
type: this.activeFilter.cipherType,
collectionId: this.activeFilter.collectionId,
deleted: this.activeFilter.isDeleted || null,
};
}
this.router.navigate([], {
relativeTo: this.route,
queryParams: queryParams,
queryParamsHandling: "merge",
replaceUrl: true,
});
}
}
/**
* Allows backwards compatibility with
* old links that used the original `cipherId` param
*/
const getCipherIdFromParams = (params: Params): string => {
return params["itemId"] || params["cipherId"];
};

View File

@@ -1,33 +0,0 @@
import { NgModule } from "@angular/core";
import { BreadcrumbsModule } from "@bitwarden/components";
import { OrganizationBadgeModule } from "../../../vault/app/vault/organization-badge/organization-badge.module";
import { PipesModule } from "../../../vault/app/vault/pipes/pipes.module";
import { LooseComponentsModule } from "../../shared/loose-components.module";
import { SharedModule } from "../../shared/shared.module";
import { CollectionBadgeModule } from "./collection-badge/collection-badge.module";
import { GroupBadgeModule } from "./group-badge/group-badge.module";
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
import { VaultHeaderComponent } from "./vault-header/vault-header.component";
import { VaultItemsComponent } from "./vault-items.component";
import { VaultRoutingModule } from "./vault-routing.module";
import { VaultComponent } from "./vault.component";
@NgModule({
imports: [
VaultRoutingModule,
VaultFilterModule,
SharedModule,
LooseComponentsModule,
GroupBadgeModule,
CollectionBadgeModule,
OrganizationBadgeModule,
PipesModule,
BreadcrumbsModule,
],
declarations: [VaultComponent, VaultItemsComponent, VaultHeaderComponent],
exports: [VaultComponent],
})
export class VaultModule {}

View File

@@ -24,7 +24,7 @@ import { UpdateTempPasswordComponent } from "../auth/update-temp-password.compon
import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component";
import { flagEnabled, Flags } from "../utils/flags";
import { VaultModule } from "../vault/app/vault/vault.module";
import { VaultModule } from "../vault/individual-vault/vault.module";
import { TrialInitiationComponent } from "./accounts/trial-initiation/trial-initiation.component";
import { HomeGuard } from "./guards/home.guard";

View File

@@ -1,8 +1,8 @@
import { NgModule } from "@angular/core";
import { LoginModule } from "../auth/login/login.module";
import { OrganizationBadgeModule } from "../vault/app/vault/organization-badge/organization-badge.module";
import { VaultFilterModule } from "../vault/app/vault/vault-filter/vault-filter.module";
import { OrganizationBadgeModule } from "../vault/individual-vault/organization-badge/organization-badge.module";
import { VaultFilterModule } from "../vault/individual-vault/vault-filter/vault-filter.module";
import { TrialInitiationModule } from "./accounts/trial-initiation/trial-initiation.module";
import { OrganizationCreateModule } from "./organizations/create/organization-create.module";

View File

@@ -7,8 +7,8 @@ import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/pa
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { AddEditComponent } from "../../../vault/app/vault/add-edit.component";
import { AddEditComponent as OrgAddEditComponent } from "../../organizations/vault/add-edit.component";
import { AddEditComponent } from "../../../vault/individual-vault/add-edit.component";
import { AddEditComponent as OrgAddEditComponent } from "../../../vault/org-vault/add-edit.component";
@Directive()
export class CipherReportComponent {

View File

@@ -4,7 +4,7 @@ import { Meta, Story, moduleMetadata } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeModule, IconModule } from "@bitwarden/components";
import { PremiumBadgeComponent } from "../../../../vault/app/components/premium-badge.component";
import { PremiumBadgeComponent } from "../../../../vault/components/premium-badge.component";
import { PreloadedEnglishI18nModule } from "../../../tests/preloaded-english-i18n.module";
import { ReportVariant } from "../models/report-variant";

View File

@@ -4,7 +4,7 @@ import { Meta, Story, moduleMetadata } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeModule, IconModule } from "@bitwarden/components";
import { PremiumBadgeComponent } from "../../../../vault/app/components/premium-badge.component";
import { PremiumBadgeComponent } from "../../../../vault/components/premium-badge.component";
import { PreloadedEnglishI18nModule } from "../../../tests/preloaded-english-i18n.module";
import { reports } from "../../reports";
import { ReportVariant } from "../models/report-variant";

View File

@@ -1,6 +1,6 @@
import { NgModule } from "@angular/core";
import { PasswordRepromptComponent } from "../../../src/vault/app/components/password-reprompt.component";
import { PasswordRepromptComponent } from "../../../src/vault/components/password-reprompt.component";
import { AcceptEmergencyComponent } from "../../auth/accept-emergency.component";
import { AcceptOrganizationComponent } from "../../auth/accept-organization.component";
import { HintComponent } from "../../auth/hint.component";
@@ -34,13 +34,15 @@ import { UpdatePasswordComponent } from "../../auth/update-password.component";
import { UpdateTempPasswordComponent } from "../../auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "../../auth/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "../../auth/verify-recover-delete.component";
import { PremiumBadgeComponent } from "../../vault/app/components/premium-badge.component";
import { AddEditCustomFieldsComponent } from "../../vault/app/vault/add-edit-custom-fields.component";
import { AddEditComponent } from "../../vault/app/vault/add-edit.component";
import { AttachmentsComponent } from "../../vault/app/vault/attachments.component";
import { CollectionsComponent } from "../../vault/app/vault/collections.component";
import { FolderAddEditComponent } from "../../vault/app/vault/folder-add-edit.component";
import { ShareComponent } from "../../vault/app/vault/share.component";
import { PremiumBadgeComponent } from "../../vault/components/premium-badge.component";
import { AddEditCustomFieldsComponent } from "../../vault/individual-vault/add-edit-custom-fields.component";
import { AddEditComponent } from "../../vault/individual-vault/add-edit.component";
import { AttachmentsComponent } from "../../vault/individual-vault/attachments.component";
import { CollectionsComponent } from "../../vault/individual-vault/collections.component";
import { FolderAddEditComponent } from "../../vault/individual-vault/folder-add-edit.component";
import { ShareComponent } from "../../vault/individual-vault/share.component";
import { AddEditComponent as OrgAddEditComponent } from "../../vault/org-vault/add-edit.component";
import { AttachmentsComponent as OrgAttachmentsComponent } from "../../vault/org-vault/attachments.component";
import { DynamicAvatarComponent } from "../components/dynamic-avatar.component";
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
import { SelectableAvatarComponent } from "../components/selectable-avatar.component";
@@ -66,8 +68,6 @@ import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } f
import { ToolsComponent as OrgToolsComponent } from "../organizations/tools/tools.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../organizations/tools/weak-passwords-report.component";
import { AddEditComponent as OrgAddEditComponent } from "../organizations/vault/add-edit.component";
import { AttachmentsComponent as OrgAttachmentsComponent } from "../organizations/vault/attachments.component";
import { CollectionsComponent as OrgCollectionsComponent } from "../organizations/vault/collections.component";
import { ProvidersComponent } from "../providers/providers.component";
import { AccessComponent } from "../send/access.component";