2018-07-03 18:34:20 +02:00
import {
2018-08-21 04:21:13 +02:00
ChangeDetectorRef ,
2018-07-03 21:11:58 +02:00
Component ,
2018-08-21 04:21:13 +02:00
NgZone ,
OnDestroy ,
2018-07-03 21:11:58 +02:00
OnInit ,
2018-07-04 05:33:12 +02:00
ViewChild ,
2018-07-05 15:42:50 +02:00
ViewContainerRef ,
2018-07-03 18:34:20 +02:00
} from "@angular/core" ;
2022-06-13 16:14:09 +02:00
import { ActivatedRoute , Params , Router } from "@angular/router" ;
2021-10-15 00:59:43 +02:00
import { first } from "rxjs/operators" ;
2022-06-14 17:10:53 +02:00
import { ModalService } from "@bitwarden/angular/services/modal.service" ;
2022-08-08 21:08:35 +02:00
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model" ;
2022-06-14 17:10:53 +02:00
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service" ;
import { CipherService } from "@bitwarden/common/abstractions/cipher.service" ;
import { I18nService } from "@bitwarden/common/abstractions/i18n.service" ;
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service" ;
2022-09-27 22:25:19 +02:00
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction" ;
2022-06-14 17:10:53 +02:00
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service" ;
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service" ;
2022-08-29 20:14:59 +02:00
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction" ;
2022-06-14 17:10:53 +02:00
import { CipherType } from "@bitwarden/common/enums/cipherType" ;
import { Organization } from "@bitwarden/common/models/domain/organization" ;
2022-10-14 18:25:50 +02:00
import { CipherView } from "@bitwarden/common/models/view/cipher.view" ;
2018-07-04 05:33:12 +02:00
2022-08-08 21:08:35 +02:00
import { VaultService } from "../../vault/shared/vault.service" ;
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" ;
2022-11-22 14:30:33 +01:00
import { VaultItemsComponent } from "./vault-items.component" ;
2018-07-03 18:34:20 +02:00
2018-08-21 04:21:13 +02:00
const BroadcasterSubscriptionId = "OrgVaultComponent" ;
2018-07-03 18:34:20 +02:00
@Component ( {
selector : "app-org-vault" ,
2022-08-08 21:08:35 +02:00
templateUrl : "vault.component.html" ,
2018-07-03 18:34:20 +02:00
} )
2022-08-08 21:08:35 +02:00
export class VaultComponent implements OnInit , OnDestroy {
2022-06-10 02:10:58 +02:00
@ViewChild ( "vaultFilter" , { static : true } )
2022-08-08 21:08:35 +02:00
vaultFilterComponent : VaultFilterComponent ;
2022-11-22 14:30:33 +01:00
@ViewChild ( VaultItemsComponent , { static : true } ) vaultItemsComponent : VaultItemsComponent ;
2020-08-17 16:04:38 +02:00
@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 ;
2021-12-17 15:57:11 +01:00
2018-07-04 05:33:12 +02:00
organization : Organization ;
2020-04-08 22:48:30 +02:00
collectionId : string = null ;
type : CipherType = null ;
2021-04-27 18:49:02 +02:00
trashCleanupWarning : string = null ;
2022-05-09 14:21:52 +02:00
activeFilter : VaultFilter = new VaultFilter ( ) ;
2021-12-17 15:57:11 +01:00
2022-06-10 02:10:58 +02:00
// This is a hack to avoid redundant api calls that fetch OrganizationVaultFilterComponent collections
// When it makes sense to do so we should leverage some other communication method for change events that isn't directly tied to the query param for organizationId
// i.e. exposing the VaultFiltersService to the OrganizationSwitcherComponent to make relevant updates from a change event instead of just depending on the router
firstLoaded = true ;
2021-12-14 17:10:26 +01:00
constructor (
2018-11-30 16:28:46 +01:00
private route : ActivatedRoute ,
2021-12-14 17:10:26 +01:00
private organizationService : OrganizationService ,
2021-10-15 00:59:43 +02:00
private router : Router ,
2021-10-05 18:12:44 +02:00
private changeDetectorRef : ChangeDetectorRef ,
2018-07-04 05:33:12 +02:00
private syncService : SyncService ,
2018-07-05 15:42:50 +02:00
private i18nService : I18nService ,
2018-07-04 05:33:12 +02:00
private modalService : ModalService ,
2018-08-21 04:21:13 +02:00
private messagingService : MessagingService ,
private broadcasterService : BroadcasterService ,
private ngZone : NgZone ,
2022-04-25 15:41:44 +02:00
private platformUtilsService : PlatformUtilsService ,
2022-05-09 14:21:52 +02:00
private vaultService : VaultService ,
2022-05-06 11:29:45 +02:00
private cipherService : CipherService ,
private passwordRepromptService : PasswordRepromptService
2021-12-17 15:57:11 +01:00
) { }
2018-08-21 04:21:13 +02:00
ngOnInit() {
this . trashCleanupWarning = this . i18nService . t (
this . platformUtilsService . isSelfHost ( )
? "trashCleanupWarningSelfHosted"
: "trashCleanupWarning"
2021-12-17 15:57:11 +01:00
) ;
2022-08-26 18:09:28 +02:00
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
2022-05-09 14:21:52 +02:00
this . route . parent . params . subscribe ( async ( params : any ) = > {
2018-07-04 05:33:12 +02:00
this . organization = await this . organizationService . get ( params . organizationId ) ;
2022-05-09 14:21:52 +02:00
this . vaultFilterComponent . organization = this . organization ;
2022-11-22 14:30:33 +01:00
this . vaultItemsComponent . organization = this . organization ;
2021-12-17 15:57:11 +01:00
2022-08-26 18:09:28 +02:00
/* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */
2018-07-11 21:44:40 +02:00
this . route . queryParams . pipe ( first ( ) ) . subscribe ( async ( qParams ) = > {
2022-11-22 14:30:33 +01:00
this . vaultItemsComponent . searchText = this . vaultFilterComponent . searchText = qParams . search ;
2018-07-11 21:44:40 +02:00
if ( ! this . organization . canViewAllCollections ) {
await this . syncService . fullSync ( false ) ;
this . broadcasterService . subscribe ( BroadcasterSubscriptionId , ( message : any ) = > {
this . ngZone . run ( async ( ) = > {
2018-08-21 04:21:13 +02:00
switch ( message . command ) {
2018-07-11 21:44:40 +02:00
case "syncCompleted" :
if ( message . successfully ) {
await Promise . all ( [
2022-06-10 02:10:58 +02:00
this . vaultFilterComponent . reloadCollectionsAndFolders ( ) ,
2022-11-22 14:30:33 +01:00
this . vaultItemsComponent . refresh ( ) ,
2019-03-19 17:44:22 +01:00
] ) ;
2021-02-03 18:41:33 +01:00
this . changeDetectorRef . detectChanges ( ) ;
2018-07-11 21:44:40 +02:00
}
2021-12-17 15:57:11 +01:00
break ;
2018-07-04 05:33:12 +02:00
}
} ) ;
} ) ;
}
2022-06-10 02:10:58 +02:00
if ( this . firstLoaded ) {
await this . vaultFilterComponent . reloadCollectionsAndFolders ( ) ;
}
this . firstLoaded = true ;
2022-11-22 14:30:33 +01:00
await this . vaultItemsComponent . reload ( ) ;
2018-07-04 05:33:12 +02:00
2020-04-08 22:48:30 +02:00
if ( qParams . viewEvents != null ) {
2022-11-22 14:30:33 +01:00
const cipher = this . vaultItemsComponent . ciphers . filter (
( c ) = > c . id === qParams . viewEvents
) ;
2018-07-11 21:44:40 +02:00
if ( cipher . length > 0 ) {
this . viewEvents ( cipher [ 0 ] ) ;
2021-12-17 15:57:11 +01:00
}
2020-04-08 22:48:30 +02:00
}
2022-04-25 15:41:44 +02:00
2022-08-26 18:09:28 +02:00
/* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */
2022-04-25 15:41:44 +02:00
this . route . queryParams . subscribe ( async ( params ) = > {
2022-06-13 16:14:09 +02:00
const cipherId = getCipherIdFromParams ( params ) ;
if ( cipherId ) {
2022-05-13 15:32:15 +02:00
if (
// Handle users with implicit collection access since they use the admin endpoint
this . organization . canEditAnyCollection ||
2022-06-13 16:14:09 +02:00
( await this . cipherService . get ( cipherId ) ) != null
2022-05-13 15:32:15 +02:00
) {
2022-06-13 16:14:09 +02:00
this . editCipherId ( cipherId ) ;
2022-04-25 15:41:44 +02:00
} else {
this . platformUtilsService . showToast (
"error" ,
this . i18nService . t ( "errorOccurred" ) ,
this . i18nService . t ( "unknownCipher" )
) ;
this . router . navigate ( [ ] , {
2022-06-13 16:14:09 +02:00
queryParams : { cipherId : null , itemId : null } ,
2022-04-25 15:41:44 +02:00
queryParamsHandling : "merge" ,
} ) ;
}
}
} ) ;
2021-12-17 15:57:11 +01:00
} ) ;
} ) ;
}
2022-06-29 22:12:08 +02:00
get deleted ( ) : boolean {
return this . activeFilter . status === "trash" ;
}
2018-08-21 04:21:13 +02:00
ngOnDestroy() {
this . broadcasterService . unsubscribe ( BroadcasterSubscriptionId ) ;
2021-12-17 15:57:11 +01:00
}
2022-05-09 14:21:52 +02:00
async applyVaultFilter ( vaultFilter : VaultFilter ) {
2022-11-22 14:30:33 +01:00
this . vaultItemsComponent . showAddNew = vaultFilter . status !== "trash" ;
2022-05-09 14:21:52 +02:00
this . activeFilter = vaultFilter ;
2022-10-27 23:41:10 +02:00
// Hack to avoid calling cipherService.getAllFromApiForOrganization every time the vault filter changes.
2022-11-22 14:30:33 +01:00
// Call VaultItemsComponent.applyFilter directly instead of going through VaultItemsComponent.reload, which
2022-10-27 23:41:10 +02:00
// reloads all the ciphers unnecessarily. Will be fixed properly by EC-14.
2022-11-22 14:30:33 +01:00
this . vaultItemsComponent . loaded = false ;
this . vaultItemsComponent . deleted = vaultFilter . status === "trash" || false ;
await this . vaultItemsComponent . applyFilter ( this . activeFilter . buildFilter ( ) ) ;
this . vaultItemsComponent . loaded = true ;
2022-10-27 23:41:10 +02:00
// End hack
2022-05-09 14:21:52 +02:00
this . vaultFilterComponent . searchPlaceholder =
this . vaultService . calculateSearchBarLocalizationString ( this . activeFilter ) ;
2018-07-04 05:33:12 +02:00
this . go ( ) ;
2021-12-17 15:57:11 +01:00
}
2018-08-16 04:26:39 +02:00
filterSearchText ( searchText : string ) {
2022-11-22 14:30:33 +01:00
this . vaultItemsComponent . searchText = searchText ;
this . vaultItemsComponent . search ( 200 ) ;
2021-12-17 15:57:11 +01:00
}
2021-08-27 14:50:58 +02:00
async editCipherAttachments ( cipher : CipherView ) {
2021-10-05 18:12:44 +02:00
if ( this . organization . maxStorageGb == null || this . organization . maxStorageGb === 0 ) {
2021-08-27 14:50:58 +02:00
this . messagingService . send ( "upgradeOrganization" , { organizationId : cipher.organizationId } ) ;
return ;
2018-07-05 18:56:58 +02:00
}
2021-08-27 14:50:58 +02:00
let madeAttachmentChanges = false ;
2021-12-17 15:57:11 +01:00
2021-08-27 14:50:58 +02:00
const [ modal ] = await this . modalService . openViewRef (
AttachmentsComponent ,
2018-10-22 22:46:48 +02:00
this . attachmentsModalRef ,
2021-08-27 14:50:58 +02:00
( comp ) = > {
2018-10-22 22:46:48 +02:00
comp . organization = this . organization ;
comp . cipherId = cipher . id ;
2022-08-26 18:09:28 +02:00
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
2018-07-05 15:42:50 +02:00
comp . onUploadedAttachment . subscribe ( ( ) = > ( madeAttachmentChanges = true ) ) ;
2022-08-26 18:09:28 +02:00
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
2021-08-27 14:50:58 +02:00
comp . onDeletedAttachment . subscribe ( ( ) = > ( madeAttachmentChanges = true ) ) ;
2021-12-17 15:57:11 +01:00
}
) ;
2022-08-26 18:09:28 +02:00
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
2021-08-27 14:50:58 +02:00
modal . onClosed . subscribe ( async ( ) = > {
2018-07-05 16:48:51 +02:00
if ( madeAttachmentChanges ) {
2022-11-22 14:30:33 +01:00
await this . vaultItemsComponent . refresh ( ) ;
2021-12-17 15:57:11 +01:00
}
2018-07-05 15:42:50 +02:00
madeAttachmentChanges = false ;
2021-12-17 15:57:11 +01:00
} ) ;
}
2021-08-27 14:50:58 +02:00
async editCipherCollections ( cipher : CipherView ) {
2018-07-05 15:42:50 +02:00
const [ modal ] = await this . modalService . openViewRef (
2021-08-27 14:50:58 +02:00
CollectionsComponent ,
this . collectionsModalRef ,
( comp ) = > {
2021-10-05 18:12:44 +02:00
if ( this . organization . canEditAnyCollection ) {
2021-02-03 18:41:33 +01:00
comp . collectionIds = cipher . collectionIds ;
2022-05-09 14:21:52 +02:00
comp . collections = this . vaultFilterComponent . collections . fullList . filter (
2022-07-19 08:29:58 +02:00
( c ) = > ! c . readOnly && c . id != null
2022-05-09 14:21:52 +02:00
) ;
2018-10-22 22:46:48 +02:00
}
2021-08-27 14:50:58 +02:00
comp . organization = this . organization ;
comp . cipherId = cipher . id ;
2022-08-26 18:09:28 +02:00
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
2021-08-27 14:50:58 +02:00
comp . onSavedCollections . subscribe ( async ( ) = > {
modal . close ( ) ;
2022-11-22 14:30:33 +01:00
await this . vaultItemsComponent . refresh ( ) ;
2018-07-05 15:42:50 +02:00
} ) ;
2021-12-17 15:57:11 +01:00
}
) ;
}
2018-07-05 15:42:50 +02:00
async addCipher() {
2021-08-27 14:50:58 +02:00
const component = await this . editCipher ( null ) ;
2020-02-10 20:03:36 +01:00
component . organizationId = this . organization . id ;
2018-07-04 05:33:12 +02:00
component . type = this . type ;
2021-10-05 18:12:44 +02:00
if ( this . organization . canEditAnyCollection ) {
2022-05-09 14:21:52 +02:00
component . collections = this . vaultFilterComponent . collections . fullList . filter (
2022-07-19 08:29:58 +02:00
( c ) = > ! c . readOnly && c . id != null
2022-05-09 14:21:52 +02:00
) ;
2018-07-05 15:42:50 +02:00
}
2021-10-05 18:12:44 +02:00
if ( this . collectionId != null ) {
2020-02-10 20:03:36 +01:00
component . collectionIds = [ this . collectionId ] ;
}
2021-12-17 15:57:11 +01:00
}
2018-07-11 21:22:55 +02:00
async editCipher ( cipher : CipherView ) {
2022-04-25 15:41:44 +02:00
return this . editCipherId ( cipher ? . id ) ;
}
async editCipherId ( cipherId : string ) {
2022-05-06 11:29:45 +02:00
const cipher = await this . cipherService . get ( cipherId ) ;
2022-05-09 20:53:31 +02:00
if ( cipher != null && cipher . reprompt != 0 ) {
2022-05-06 11:29:45 +02:00
if ( ! ( await this . passwordRepromptService . showPasswordPrompt ( ) ) ) {
2022-06-13 16:14:09 +02:00
this . go ( { cipherId : null , itemId : null } ) ;
2022-05-06 11:29:45 +02:00
return ;
}
}
2021-08-27 14:50:58 +02:00
const [ modal , childComponent ] = await this . modalService . openViewRef (
AddEditComponent ,
this . cipherAddEditModalRef ,
( comp ) = > {
comp . organization = this . organization ;
2022-04-25 15:41:44 +02:00
comp . cipherId = cipherId ;
2022-08-26 18:09:28 +02:00
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
2022-02-24 12:10:07 +01:00
comp . onSavedCipher . subscribe ( async ( ) = > {
2021-08-27 14:50:58 +02:00
modal . close ( ) ;
2022-11-22 14:30:33 +01:00
await this . vaultItemsComponent . refresh ( ) ;
2021-12-17 15:57:11 +01:00
} ) ;
2022-08-26 18:09:28 +02:00
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
2022-02-24 12:10:07 +01:00
comp . onDeletedCipher . subscribe ( async ( ) = > {
2021-08-27 14:50:58 +02:00
modal . close ( ) ;
2022-11-22 14:30:33 +01:00
await this . vaultItemsComponent . refresh ( ) ;
2021-12-17 15:57:11 +01:00
} ) ;
2022-08-26 18:09:28 +02:00
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
2022-02-24 12:10:07 +01:00
comp . onRestoredCipher . subscribe ( async ( ) = > {
2021-08-27 14:50:58 +02:00
modal . close ( ) ;
2022-11-22 14:30:33 +01:00
await this . vaultItemsComponent . refresh ( ) ;
2018-07-11 21:22:55 +02:00
} ) ;
2021-12-17 15:57:11 +01:00
}
) ;
2022-04-25 15:41:44 +02:00
modal . onClosedPromise ( ) . then ( ( ) = > {
2022-06-13 16:14:09 +02:00
this . go ( { cipherId : null , itemId : null } ) ;
2022-04-25 15:41:44 +02:00
} ) ;
2021-08-27 14:50:58 +02:00
return childComponent ;
2021-12-17 15:57:11 +01:00
}
2021-08-27 14:50:58 +02:00
async cloneCipher ( cipher : CipherView ) {
const component = await this . editCipher ( cipher ) ;
2020-02-10 20:03:36 +01:00
component . cloneMode = true ;
component . organizationId = this . organization . id ;
2021-10-05 18:12:44 +02:00
if ( this . organization . canEditAnyCollection ) {
2022-05-09 14:21:52 +02:00
component . collections = this . vaultFilterComponent . collections . fullList . filter (
2022-07-19 08:29:58 +02:00
( c ) = > ! c . readOnly && c . id != null
2022-05-09 14:21:52 +02:00
) ;
2018-07-11 21:22:55 +02:00
}
2020-02-10 20:03:36 +01:00
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
// in the add-edit componenet
2021-08-27 14:50:58 +02:00
component . collectionIds = cipher . collectionIds ;
2021-12-17 15:57:11 +01:00
}
2021-08-27 14:50:58 +02:00
async viewEvents ( cipher : CipherView ) {
await this . modalService . openViewRef ( EntityEventsComponent , this . eventsModalRef , ( comp ) = > {
comp . name = cipher . name ;
2020-02-10 20:03:36 +01:00
comp . organizationId = this . organization . id ;
2021-08-27 14:50:58 +02:00
comp . entityId = cipher . id ;
2018-10-22 22:46:48 +02:00
comp . showUser = true ;
2021-08-27 14:50:58 +02:00
comp . entity = "cipher" ;
2021-12-17 15:57:11 +01:00
} ) ;
}
2018-07-04 05:33:12 +02:00
private go ( queryParams : any = null ) {
if ( queryParams == null ) {
queryParams = {
2022-06-29 22:12:08 +02:00
type : this . activeFilter . cipherType ,
collectionId : this.activeFilter.selectedCollectionId ,
2020-04-08 22:48:30 +02:00
deleted : this.deleted ? true : null ,
2021-12-17 15:57:11 +01:00
} ;
2018-07-04 05:33:12 +02:00
}
2018-11-30 16:28:46 +01:00
this . router . navigate ( [ ] , {
relativeTo : this.route ,
queryParams : queryParams ,
2022-04-25 15:41:44 +02:00
queryParamsHandling : "merge" ,
2018-11-30 16:28:46 +01:00
replaceUrl : true ,
} ) ;
2018-07-04 05:33:12 +02:00
}
2018-07-03 18:34:20 +02:00
}
2022-06-13 16:14:09 +02:00
/ * *
* Allows backwards compatibility with
* old links that used the original ` cipherId ` param
* /
const getCipherIdFromParams = ( params : Params ) : string = > {
return params [ "itemId" ] || params [ "cipherId" ] ;
} ;