From d799529428ab148d5d0c3d0da69e5e68a6843fe6 Mon Sep 17 00:00:00 2001 From: Robyn MacCallum Date: Tue, 28 Mar 2023 12:37:40 -0400 Subject: [PATCH] [SG 623] Send Service Refactor (#4327) * Split out api methods into sendApiService * Move SendService and abstraction * Libs updates * Web updates * CLI updates * Desktop updates * libs send service fixes * browser factory additions * Browser updates * Fix service injection for CLI SendReceiveCommand * Deprecate directly calling send state service methods * SendService observables updates * Update components to use new observables * Modify CLI to use state service instead of observables * Remove unnecessary await on get() * Move delete() to InternalSendService * SendService unit tests * Split fileUploadService by send and cipher * send and cipher service factory updates * Add file upload methods to get around circular dependency issues * Move api methods from sendService to sendApiService * Update cipherService to use fileApi methods * libs service injection and component changes * browser service injection and component changes * Desktop component changes * Web component changes * cipher service test fix * Fix file capitalization * CLI service import and command updates * Remove extra abstract fileUploadService * WIP: Condense callbacks for file upload Co-authored-by: Robyn MacCallum * Send callbacks for file upload * Fix circular service dependencies * Fix response return on upload * Fix function definitions * Service injection fixes and bug fixes * Fix folder casing * Service injection cleanup * Remove deleted file from capital letters whitelist * Create new SendApiService for popup * Move cipherFileUploadService to vault * Move SendFileUploadService methods into SendApiService * Rename methods to remove 'WithServer' * Properly subscribe to sendViews * Fix Send serialization * Implement fromJSON on sendFile and sendText * [PM-1347] Fix send key serialization (#4989) * Properly serialize key on send fromJSON * Remove call that nulled out decrypted sends * Fix null checks in fromJSON methods for models * lint fixes --------- Co-authored-by: Matt Gibson --- .github/whitelist-capital-letters.txt | 2 - .../browser/src/background/main.background.ts | 49 ++-- .../cipher-file-upload-service.factory.ts | 31 +++ .../cipher-file-upload.service.factory.ts | 31 +++ .../file-upload-service.factory.ts | 23 +- .../service_factories/send-service.factory.ts | 34 +++ .../src/popup/send/send-add-edit.component.ts | 9 +- .../popup/send/send-groupings.component.ts | 9 +- .../src/popup/send/send-type.component.ts | 9 +- .../src/popup/services/services.module.ts | 52 +++- .../src/services/browser-send.service.ts | 15 ++ .../cipher-service.factory.ts | 14 +- .../components/vault/add-edit.component.ts | 7 +- apps/cli/src/bw.ts | 42 ++- apps/cli/src/commands/send/create.command.ts | 10 +- apps/cli/src/commands/send/delete.command.ts | 9 +- apps/cli/src/commands/send/edit.command.ts | 10 +- apps/cli/src/commands/send/get.command.ts | 6 +- apps/cli/src/commands/send/list.command.ts | 4 +- apps/cli/src/commands/send/receive.command.ts | 12 +- .../commands/send/remove-password.command.ts | 7 +- apps/cli/src/commands/serve.command.ts | 13 +- apps/cli/src/send.program.ts | 17 +- .../src/app/send/add-edit.component.ts | 9 +- apps/desktop/src/app/send/send.component.ts | 9 +- .../src/vault/app/vault/add-edit.component.ts | 7 +- apps/web/src/app/send/access.component.ts | 10 +- apps/web/src/app/send/add-edit.component.ts | 9 +- apps/web/src/app/send/send.component.ts | 9 +- .../app/settings/change-password.component.ts | 4 +- .../individual-vault/add-edit.component.ts | 7 +- .../app/vault/org-vault/add-edit.component.ts | 7 +- .../emergency-add-edit.component.ts | 7 +- .../src/components/send/add-edit.component.ts | 14 +- .../src/components/send/send.component.ts | 15 +- .../src/services/jslib-services.module.ts | 61 +++-- .../vault/components/add-edit.component.ts | 4 +- .../common/spec/services/send.service.spec.ts | 178 +++++++++++++ libs/common/src/abstractions/api.service.ts | 32 --- .../file-upload/file-upload.service.ts | 18 ++ .../src/abstractions/fileUpload.service.ts | 18 -- libs/common/src/abstractions/send.service.ts | 25 -- .../send/send-api.service.abstraction.ts | 40 +++ .../send/send.service.abstraction.ts | 39 +++ libs/common/src/abstractions/state.service.ts | 12 + libs/common/src/models/domain/send-file.ts | 12 + libs/common/src/models/domain/send-text.ts | 12 + libs/common/src/models/domain/send.ts | 23 ++ libs/common/src/services/api.service.ts | 104 -------- .../file-upload/file-upload.service.ts | 52 ++++ .../common/src/services/fileUpload.service.ts | 108 -------- .../src/services/send/send-api.service.ts | 251 ++++++++++++++++++ .../src/services/{ => send}/send.service.ts | 194 ++++++-------- .../file-upload/cipher-file-upload.service.ts | 15 ++ .../src/vault/services/cipher.service.spec.ts | 14 +- .../src/vault/services/cipher.service.ts | 95 +------ .../file-upload/cipher-file-upload.service.ts | 157 +++++++++++ .../src/vault/services/sync/sync.service.ts | 10 +- 58 files changed, 1333 insertions(+), 663 deletions(-) create mode 100644 apps/browser/src/background/service_factories/cipher-file-upload-service.factory.ts create mode 100644 apps/browser/src/background/service_factories/cipher-file-upload.service.factory.ts create mode 100644 apps/browser/src/background/service_factories/send-service.factory.ts create mode 100644 apps/browser/src/services/browser-send.service.ts create mode 100644 libs/common/spec/services/send.service.spec.ts create mode 100644 libs/common/src/abstractions/file-upload/file-upload.service.ts delete mode 100644 libs/common/src/abstractions/fileUpload.service.ts delete mode 100644 libs/common/src/abstractions/send.service.ts create mode 100644 libs/common/src/abstractions/send/send-api.service.abstraction.ts create mode 100644 libs/common/src/abstractions/send/send.service.abstraction.ts create mode 100644 libs/common/src/services/file-upload/file-upload.service.ts delete mode 100644 libs/common/src/services/fileUpload.service.ts create mode 100644 libs/common/src/services/send/send-api.service.ts rename libs/common/src/services/{ => send}/send.service.ts (53%) create mode 100644 libs/common/src/vault/abstractions/file-upload/cipher-file-upload.service.ts create mode 100644 libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index 5c0c56b289..64d1f6a76e 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -76,7 +76,6 @@ ./libs/common/src/abstractions/formValidationErrors.service.ts ./libs/common/src/abstractions/vaultTimeout/vaultTimeoutSettings.service.ts ./libs/common/src/abstractions/vaultTimeout/vaultTimeout.service.ts -./libs/common/src/abstractions/fileUpload.service.ts ./libs/common/src/abstractions/cryptoFunction.service.ts ./libs/common/src/abstractions/anonymousHub.service.ts ./libs/common/src/abstractions/appId.service.ts @@ -86,7 +85,6 @@ ./libs/common/src/services/formValidationErrors.service.ts ./libs/common/src/services/vaultTimeout/vaultTimeoutSettings.service.ts ./libs/common/src/services/vaultTimeout/vaultTimeout.service.ts -./libs/common/src/services/fileUpload.service.ts ./libs/common/src/services/anonymousHub.service.ts ./libs/common/src/services/appId.service.ts ./libs/common/src/services/noopMessaging.service.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 3687bcf006..97cbe88d6f 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -8,14 +8,15 @@ import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service"; -import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/fileUpload.service"; +import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/file-upload/file-upload.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service"; import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service"; import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service"; -import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService as SendApiServiceAbstraction } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { InternalSendService as InternalSendServiceAbstraction } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service"; import { AbstractMemoryStorageService, @@ -58,11 +59,11 @@ import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/servi import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { ExportService } from "@bitwarden/common/services/export.service"; -import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; +import { FileUploadService } from "@bitwarden/common/services/file-upload/file-upload.service"; import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service"; import { NotificationsService } from "@bitwarden/common/services/notifications.service"; import { SearchService } from "@bitwarden/common/services/search.service"; -import { SendService } from "@bitwarden/common/services/send.service"; +import { SendApiService } from "@bitwarden/common/services/send/send-api.service"; import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service"; import { SystemService } from "@bitwarden/common/services/system.service"; import { TotpService } from "@bitwarden/common/services/totp.service"; @@ -77,12 +78,14 @@ import { UsernameGenerationServiceAbstraction, } from "@bitwarden/common/tools/generator/username"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService as InternalFolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync-notifier.service.abstraction"; import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service"; import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service"; @@ -105,6 +108,7 @@ import { Account } from "../models/account"; import { BrowserStateService as StateServiceAbstraction } from "../services/abstractions/browser-state.service"; import { BrowserEnvironmentService } from "../services/browser-environment.service"; import { BrowserI18nService } from "../services/browser-i18n.service"; +import { BrowserSendService } from "../services/browser-send.service"; import { BrowserSettingsService } from "../services/browser-settings.service"; import { BrowserStateService } from "../services/browser-state.service"; import { BrowserCryptoService } from "../services/browserCrypto.service"; @@ -160,8 +164,9 @@ export default class MainBackground { eventCollectionService: EventCollectionServiceAbstraction; eventUploadService: EventUploadServiceAbstraction; policyService: InternalPolicyServiceAbstraction; - sendService: SendServiceAbstraction; + sendService: InternalSendServiceAbstraction; fileUploadService: FileUploadServiceAbstraction; + cipherFileUploadService: CipherFileUploadServiceAbstraction; organizationService: InternalOrganizationServiceAbstraction; providerService: ProviderServiceAbstraction; keyConnectorService: KeyConnectorServiceAbstraction; @@ -172,6 +177,7 @@ export default class MainBackground { encryptService: EncryptService; folderApiService: FolderApiServiceAbstraction; policyApiService: PolicyApiServiceAbstraction; + sendApiService: SendApiServiceAbstraction; userVerificationApiService: UserVerificationApiServiceAbstraction; syncNotifierService: SyncNotifierServiceAbstraction; avatarUpdateService: AvatarUpdateServiceAbstraction; @@ -292,17 +298,21 @@ export default class MainBackground { (expired: boolean) => this.logout(expired) ); this.settingsService = new BrowserSettingsService(this.stateService); - this.fileUploadService = new FileUploadService(this.logService, this.apiService); + this.fileUploadService = new FileUploadService(this.logService); + this.cipherFileUploadService = new CipherFileUploadService( + this.apiService, + this.fileUploadService + ); this.cipherService = new CipherService( this.cryptoService, this.settingsService, this.apiService, - this.fileUploadService, this.i18nService, () => this.searchService, this.logService, this.stateService, - this.encryptService + this.encryptService, + this.cipherFileUploadService ); this.folderService = new BrowserFolderService( this.cryptoService, @@ -317,14 +327,6 @@ export default class MainBackground { this.stateService ); this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService); - this.sendService = new SendService( - this.cryptoService, - this.apiService, - this.fileUploadService, - this.i18nService, - this.cryptoFunctionService, - this.stateService - ); this.syncNotifierService = new SyncNotifierService(); this.organizationService = new BrowserOrganizationService(this.stateService); this.policyService = new BrowserPolicyService(this.stateService, this.organizationService); @@ -401,7 +403,18 @@ export default class MainBackground { lockedCallback, logoutCallback ); - + this.containerService = new ContainerService(this.cryptoService, this.encryptService); + this.sendService = new BrowserSendService( + this.cryptoService, + this.i18nService, + this.cryptoFunctionService, + this.stateService + ); + this.sendApiService = new SendApiService( + this.apiService, + this.fileUploadService, + this.sendService + ); this.providerService = new ProviderService(this.stateService); this.syncService = new SyncService( this.apiService, @@ -419,6 +432,7 @@ export default class MainBackground { this.providerService, this.folderApiService, this.organizationService, + this.sendApiService, logoutCallback ); this.eventUploadService = new EventUploadService( @@ -446,7 +460,6 @@ export default class MainBackground { this.logService, this.settingsService ); - this.containerService = new ContainerService(this.cryptoService, this.encryptService); this.auditService = new AuditService(this.cryptoFunctionService, this.apiService); this.exportService = new ExportService( this.folderService, diff --git a/apps/browser/src/background/service_factories/cipher-file-upload-service.factory.ts b/apps/browser/src/background/service_factories/cipher-file-upload-service.factory.ts new file mode 100644 index 0000000000..ef2be8fa76 --- /dev/null +++ b/apps/browser/src/background/service_factories/cipher-file-upload-service.factory.ts @@ -0,0 +1,31 @@ +import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; +import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; + +import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory"; +import { FactoryOptions, CachedServices, factory } from "./factory-options"; +import { + fileUploadServiceFactory, + FileUploadServiceInitOptions, +} from "./file-upload-service.factory"; + +type CipherFileUploadServiceFactoyOptions = FactoryOptions; + +export type CipherFileUploadServiceInitOptions = CipherFileUploadServiceFactoyOptions & + ApiServiceInitOptions & + FileUploadServiceInitOptions; + +export function cipherFileUploadServiceFactory( + cache: { cipherFileUploadService?: CipherFileUploadServiceAbstraction } & CachedServices, + opts: CipherFileUploadServiceInitOptions +): Promise { + return factory( + cache, + "cipherFileUploadService", + opts, + async () => + new CipherFileUploadService( + await apiServiceFactory(cache, opts), + await fileUploadServiceFactory(cache, opts) + ) + ); +} diff --git a/apps/browser/src/background/service_factories/cipher-file-upload.service.factory.ts b/apps/browser/src/background/service_factories/cipher-file-upload.service.factory.ts new file mode 100644 index 0000000000..ef2be8fa76 --- /dev/null +++ b/apps/browser/src/background/service_factories/cipher-file-upload.service.factory.ts @@ -0,0 +1,31 @@ +import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; +import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; + +import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory"; +import { FactoryOptions, CachedServices, factory } from "./factory-options"; +import { + fileUploadServiceFactory, + FileUploadServiceInitOptions, +} from "./file-upload-service.factory"; + +type CipherFileUploadServiceFactoyOptions = FactoryOptions; + +export type CipherFileUploadServiceInitOptions = CipherFileUploadServiceFactoyOptions & + ApiServiceInitOptions & + FileUploadServiceInitOptions; + +export function cipherFileUploadServiceFactory( + cache: { cipherFileUploadService?: CipherFileUploadServiceAbstraction } & CachedServices, + opts: CipherFileUploadServiceInitOptions +): Promise { + return factory( + cache, + "cipherFileUploadService", + opts, + async () => + new CipherFileUploadService( + await apiServiceFactory(cache, opts), + await fileUploadServiceFactory(cache, opts) + ) + ); +} diff --git a/apps/browser/src/background/service_factories/file-upload-service.factory.ts b/apps/browser/src/background/service_factories/file-upload-service.factory.ts index 0aa04126d9..5ead09f929 100644 --- a/apps/browser/src/background/service_factories/file-upload-service.factory.ts +++ b/apps/browser/src/background/service_factories/file-upload-service.factory.ts @@ -1,28 +1,21 @@ -import { FileUploadService as AbstractFileUploadService } from "@bitwarden/common/abstractions/fileUpload.service"; -import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; +import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/file-upload/file-upload.service"; +import { FileUploadService } from "@bitwarden/common/services/file-upload/file-upload.service"; -import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory"; -import { FactoryOptions, CachedServices, factory } from "./factory-options"; +import { CachedServices, factory, FactoryOptions } from "./factory-options"; import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory"; -type FileUploadServiceFactoyOptions = FactoryOptions; +type FileUploadServiceFactoryOptions = FactoryOptions; -export type FileUploadServiceInitOptions = FileUploadServiceFactoyOptions & - LogServiceInitOptions & - ApiServiceInitOptions; +export type FileUploadServiceInitOptions = FileUploadServiceFactoryOptions & LogServiceInitOptions; export function fileUploadServiceFactory( - cache: { fileUploadService?: AbstractFileUploadService } & CachedServices, + cache: { fileUploadService?: FileUploadServiceAbstraction } & CachedServices, opts: FileUploadServiceInitOptions -): Promise { +): Promise { return factory( cache, "fileUploadService", opts, - async () => - new FileUploadService( - await logServiceFactory(cache, opts), - await apiServiceFactory(cache, opts) - ) + async () => new FileUploadService(await logServiceFactory(cache, opts)) ); } diff --git a/apps/browser/src/background/service_factories/send-service.factory.ts b/apps/browser/src/background/service_factories/send-service.factory.ts new file mode 100644 index 0000000000..5f9ff13aa6 --- /dev/null +++ b/apps/browser/src/background/service_factories/send-service.factory.ts @@ -0,0 +1,34 @@ +import { InternalSendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; + +import { BrowserSendService } from "../../services/browser-send.service"; + +import { cryptoFunctionServiceFactory } from "./crypto-function-service.factory"; +import { cryptoServiceFactory, CryptoServiceInitOptions } from "./crypto-service.factory"; +import { FactoryOptions, CachedServices, factory } from "./factory-options"; +import { i18nServiceFactory, I18nServiceInitOptions } from "./i18n-service.factory"; +import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; + +type SendServiceFactoryOptions = FactoryOptions; + +export type SendServiceInitOptions = SendServiceFactoryOptions & + CryptoServiceInitOptions & + I18nServiceInitOptions & + StateServiceInitOptions; + +export function sendServiceFactory( + cache: { sendService?: InternalSendService } & CachedServices, + opts: SendServiceInitOptions +): Promise { + return factory( + cache, + "sendService", + opts, + async () => + new BrowserSendService( + await cryptoServiceFactory(cache, opts), + await i18nServiceFactory(cache, opts), + await cryptoFunctionServiceFactory(cache, opts), + await stateServiceFactory(cache, opts) + ) + ); +} diff --git a/apps/browser/src/popup/send/send-add-edit.component.ts b/apps/browser/src/popup/send/send-add-edit.component.ts index c620de643d..03f430e8b5 100644 --- a/apps/browser/src/popup/send/send-add-edit.component.ts +++ b/apps/browser/src/popup/send/send-add-edit.component.ts @@ -9,7 +9,8 @@ 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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { BrowserStateService } from "../../services/abstractions/browser-state.service"; @@ -43,7 +44,8 @@ export class SendAddEditComponent extends BaseAddEditComponent { private router: Router, private location: Location, private popupUtilsService: PopupUtilsService, - logService: LogService + logService: LogService, + sendApiService: SendApiService ) { super( i18nService, @@ -54,7 +56,8 @@ export class SendAddEditComponent extends BaseAddEditComponent { messagingService, policyService, logService, - stateService + stateService, + sendApiService ); } diff --git a/apps/browser/src/popup/send/send-groupings.component.ts b/apps/browser/src/popup/send/send-groupings.component.ts index 7ac3f1cb8e..27e48888de 100644 --- a/apps/browser/src/popup/send/send-groupings.component.ts +++ b/apps/browser/src/popup/send/send-groupings.component.ts @@ -8,7 +8,8 @@ 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 { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { SendType } from "@bitwarden/common/enums/sendType"; import { SendView } from "@bitwarden/common/models/view/send.view"; @@ -47,7 +48,8 @@ export class SendGroupingsComponent extends BaseSendComponent { private syncService: SyncService, private changeDetectorRef: ChangeDetectorRef, private broadcasterService: BroadcasterService, - logService: LogService + logService: LogService, + sendApiService: SendApiService ) { super( sendService, @@ -57,7 +59,8 @@ export class SendGroupingsComponent extends BaseSendComponent { ngZone, searchService, policyService, - logService + logService, + sendApiService ); super.onSuccessfulLoad = async () => { this.calculateTypeCounts(); diff --git a/apps/browser/src/popup/send/send-type.component.ts b/apps/browser/src/popup/send/send-type.component.ts index e4bed31f4d..a8f8e97e6d 100644 --- a/apps/browser/src/popup/send/send-type.component.ts +++ b/apps/browser/src/popup/send/send-type.component.ts @@ -10,7 +10,8 @@ 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 { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { SendType } from "@bitwarden/common/enums/sendType"; import { SendView } from "@bitwarden/common/models/view/send.view"; @@ -47,7 +48,8 @@ export class SendTypeComponent extends BaseSendComponent { private changeDetectorRef: ChangeDetectorRef, private broadcasterService: BroadcasterService, private router: Router, - logService: LogService + logService: LogService, + sendApiService: SendApiService ) { super( sendService, @@ -57,7 +59,8 @@ export class SendTypeComponent extends BaseSendComponent { ngZone, searchService, policyService, - logService + logService, + sendApiService ); super.onSuccessfulLoad = async () => { this.selectType(this.type); diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index ad23d4d2a2..849eea0ec1 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -18,15 +18,19 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; import { ExportService } from "@bitwarden/common/abstractions/export.service"; +import { FileUploadService } from "@bitwarden/common/abstractions/file-upload/file-upload.service"; import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service"; -import { FileUploadService } from "@bitwarden/common/abstractions/fileUpload.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService as SendApiServiceAbstraction } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { + InternalSendService as InternalSendServiceAbstraction, + SendService, +} from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { SettingsService } from "@bitwarden/common/abstractions/settings.service"; import { StateService as BaseStateServiceAbstraction, @@ -62,9 +66,11 @@ import { GlobalState } from "@bitwarden/common/models/domain/global-state"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { ContainerService } from "@bitwarden/common/services/container.service"; import { SearchService } from "@bitwarden/common/services/search.service"; +import { SendApiService } from "@bitwarden/common/services/send/send-api.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherFileUploadService } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderService, @@ -85,6 +91,7 @@ import { BrowserStateService as StateServiceAbstraction } from "../../services/a import { BrowserConfigService } from "../../services/browser-config.service"; import { BrowserEnvironmentService } from "../../services/browser-environment.service"; import { BrowserI18nService } from "../../services/browser-i18n.service"; +import { BrowserSendService } from "../../services/browser-send.service"; import { BrowserSettingsService } from "../../services/browser-settings.service"; import { BrowserStateService } from "../../services/browser-state.service"; import { BrowserFileDownloadService } from "../../services/browserFileDownloadService"; @@ -173,8 +180,8 @@ function getBgService(service: keyof MainBackground) { }, { provide: AuditService, useFactory: getBgService("auditService"), deps: [] }, { - provide: FileUploadService, - useFactory: getBgService("fileUploadService"), + provide: CipherFileUploadService, + useFactory: getBgService("cipherFileUploadService"), deps: [], }, { provide: CipherService, useFactory: getBgService("cipherService"), deps: [] }, @@ -183,6 +190,10 @@ function getBgService(service: keyof MainBackground) { useFactory: getBgService("cryptoFunctionService"), deps: [], }, + { + provide: FileUploadService, + useFactory: getBgService("fileUploadService"), + }, { provide: FolderService, useFactory: ( @@ -285,6 +296,38 @@ function getBgService(service: keyof MainBackground) { deps: [], }, { provide: ApiService, useFactory: getBgService("apiService"), deps: [] }, + { + provide: SendService, + useFactory: ( + cryptoService: CryptoService, + i18nService: I18nServiceAbstraction, + cryptoFunctionService: CryptoFunctionService, + stateServiceAbstraction: StateServiceAbstraction + ) => { + return new BrowserSendService( + cryptoService, + i18nService, + cryptoFunctionService, + stateServiceAbstraction + ); + }, + deps: [CryptoService, I18nServiceAbstraction, CryptoFunctionService, StateServiceAbstraction], + }, + { + provide: InternalSendServiceAbstraction, + useExisting: SendService, + }, + { + provide: SendApiServiceAbstraction, + useFactory: ( + apiService: ApiService, + fileUploadService: FileUploadService, + sendService: InternalSendServiceAbstraction + ) => { + return new SendApiService(apiService, fileUploadService, sendService); + }, + deps: [ApiService, FileUploadService, InternalSendServiceAbstraction], + }, { provide: SyncService, useFactory: getBgService("syncService"), deps: [] }, { provide: SettingsService, @@ -305,7 +348,6 @@ function getBgService(service: keyof MainBackground) { deps: [], }, { provide: ExportService, useFactory: getBgService("exportService"), deps: [] }, - { provide: SendService, useFactory: getBgService("sendService"), deps: [] }, { provide: KeyConnectorService, useFactory: getBgService("keyConnectorService"), diff --git a/apps/browser/src/services/browser-send.service.ts b/apps/browser/src/services/browser-send.service.ts new file mode 100644 index 0000000000..e2b6dadb41 --- /dev/null +++ b/apps/browser/src/services/browser-send.service.ts @@ -0,0 +1,15 @@ +import { BehaviorSubject } from "rxjs"; + +import { Send } from "@bitwarden/common/models/domain/send"; +import { SendView } from "@bitwarden/common/models/view/send.view"; +import { SendService } from "@bitwarden/common/services/send/send.service"; + +import { browserSession, sessionSync } from "../decorators/session-sync-observable"; + +@browserSession +export class BrowserSendService extends SendService { + @sessionSync({ initializer: Send.fromJSON, initializeAs: "array" }) + protected _sends: BehaviorSubject; + @sessionSync({ initializer: SendView.fromJSON, initializeAs: "array" }) + protected _sendViews: BehaviorSubject; +} diff --git a/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts b/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts index ad2259f141..fb677ff115 100644 --- a/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts +++ b/apps/browser/src/vault/background/service_factories/cipher-service.factory.ts @@ -6,6 +6,10 @@ import { apiServiceFactory, ApiServiceInitOptions, } from "../../../background/service_factories/api-service.factory"; +import { + CipherFileUploadServiceInitOptions, + cipherFileUploadServiceFactory, +} from "../../../background/service_factories/cipher-file-upload-service.factory"; import { cryptoServiceFactory, CryptoServiceInitOptions, @@ -19,10 +23,6 @@ import { factory, FactoryOptions, } from "../../../background/service_factories/factory-options"; -import { - FileUploadServiceInitOptions, - fileUploadServiceFactory, -} from "../../../background/service_factories/file-upload-service.factory"; import { i18nServiceFactory, I18nServiceInitOptions, @@ -50,7 +50,7 @@ export type CipherServiceInitOptions = CipherServiceFactoryOptions & CryptoServiceInitOptions & SettingsServiceInitOptions & ApiServiceInitOptions & - FileUploadServiceInitOptions & + CipherFileUploadServiceInitOptions & I18nServiceInitOptions & LogServiceInitOptions & StateServiceInitOptions & @@ -69,14 +69,14 @@ export function cipherServiceFactory( await cryptoServiceFactory(cache, opts), await settingsServiceFactory(cache, opts), await apiServiceFactory(cache, opts), - await fileUploadServiceFactory(cache, opts), await i18nServiceFactory(cache, opts), opts.cipherServiceOptions?.searchServiceFactory === undefined ? () => cache.searchService as SearchService : opts.cipherServiceOptions.searchServiceFactory, await logServiceFactory(cache, opts), await stateServiceFactory(cache, opts), - await encryptServiceFactory(cache, opts) + await encryptServiceFactory(cache, opts), + await cipherFileUploadServiceFactory(cache, opts) ) ); } diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts index 362f6d46ed..df0e8483c7 100644 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts @@ -10,6 +10,7 @@ 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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -51,7 +52,8 @@ export class AddEditComponent extends BaseAddEditComponent { private popupUtilsService: PopupUtilsService, organizationService: OrganizationService, passwordRepromptService: PasswordRepromptService, - logService: LogService + logService: LogService, + sendApiService: SendApiService ) { super( cipherService, @@ -66,7 +68,8 @@ export class AddEditComponent extends BaseAddEditComponent { policyService, logService, passwordRepromptService, - organizationService + organizationService, + sendApiService ); } diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 314711ee94..90d6b50194 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -31,12 +31,13 @@ import { CryptoService } from "@bitwarden/common/services/crypto.service"; import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; import { EnvironmentService } from "@bitwarden/common/services/environment.service"; import { ExportService } from "@bitwarden/common/services/export.service"; -import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; +import { FileUploadService } from "@bitwarden/common/services/file-upload/file-upload.service"; import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service"; import { NoopMessagingService } from "@bitwarden/common/services/noopMessaging.service"; import { OrganizationUserServiceImplementation } from "@bitwarden/common/services/organization-user/organization-user.service.implementation"; import { SearchService } from "@bitwarden/common/services/search.service"; -import { SendService } from "@bitwarden/common/services/send.service"; +import { SendApiService } from "@bitwarden/common/services/send/send-api.service"; +import { SendService } from "@bitwarden/common/services/send/send.service"; import { SettingsService } from "@bitwarden/common/services/settings.service"; import { StateService } from "@bitwarden/common/services/state.service"; import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service"; @@ -49,6 +50,7 @@ import { } from "@bitwarden/common/tools/generator/password"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service"; @@ -115,6 +117,7 @@ export class Main { logService: ConsoleLogService; sendService: SendService; fileUploadService: FileUploadService; + cipherFileUploadService: CipherFileUploadService; keyConnectorService: KeyConnectorService; userVerificationService: UserVerificationService; stateService: StateService; @@ -127,6 +130,7 @@ export class Main { userVerificationApiService: UserVerificationApiService; organizationApiService: OrganizationApiServiceAbstraction; syncNotifierService: SyncNotifierService; + sendApiService: SendApiService; constructor() { let p = null; @@ -217,18 +221,36 @@ export class Main { this.settingsService = new SettingsService(this.stateService); - this.fileUploadService = new FileUploadService(this.logService, this.apiService); + this.fileUploadService = new FileUploadService(this.logService); + + this.sendService = new SendService( + this.cryptoService, + this.i18nService, + this.cryptoFunctionService, + this.stateService + ); + + this.cipherFileUploadService = new CipherFileUploadService( + this.apiService, + this.fileUploadService + ); + + this.sendApiService = this.sendApiService = new SendApiService( + this.apiService, + this.fileUploadService, + this.sendService + ); this.cipherService = new CipherService( this.cryptoService, this.settingsService, this.apiService, - this.fileUploadService, this.i18nService, null, this.logService, this.stateService, - this.encryptService + this.encryptService, + this.cipherFileUploadService ); this.broadcasterService = new BroadcasterService(); @@ -258,15 +280,6 @@ export class Main { this.policyService = new PolicyService(this.stateService, this.organizationService); - this.sendService = new SendService( - this.cryptoService, - this.apiService, - this.fileUploadService, - this.i18nService, - this.cryptoFunctionService, - this.stateService - ); - this.keyConnectorService = new KeyConnectorService( this.stateService, this.cryptoService, @@ -338,6 +351,7 @@ export class Main { this.providerService, this.folderApiService, this.organizationService, + this.sendApiService, async (expired: boolean) => await this.logout() ); diff --git a/apps/cli/src/commands/send/create.command.ts b/apps/cli/src/commands/send/create.command.ts index 2fab074f44..c5bd9e590a 100644 --- a/apps/cli/src/commands/send/create.command.ts +++ b/apps/cli/src/commands/send/create.command.ts @@ -2,7 +2,8 @@ import * as fs from "fs"; import * as path from "path"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SendType } from "@bitwarden/common/enums/sendType"; import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; @@ -16,7 +17,8 @@ export class SendCreateCommand { constructor( private sendService: SendService, private stateService: StateService, - private environmentService: EnvironmentService + private environmentService: EnvironmentService, + private sendApiService: SendApiService ) {} async run(requestJson: any, cmdOptions: Record) { @@ -120,8 +122,8 @@ export class SendCreateCommand { encSend.deletionDate = sendView.deletionDate; encSend.expirationDate = sendView.expirationDate; - await this.sendService.saveWithServer([encSend, fileData]); - const newSend = await this.sendService.get(encSend.id); + await this.sendApiService.save([encSend, fileData]); + const newSend = await this.sendService.getFromState(encSend.id); const decSend = await newSend.decrypt(); const res = new SendResponse(decSend, this.environmentService.getWebVaultUrl()); return Response.success(res); diff --git a/apps/cli/src/commands/send/delete.command.ts b/apps/cli/src/commands/send/delete.command.ts index 6138382565..f70e2a01e7 100644 --- a/apps/cli/src/commands/send/delete.command.ts +++ b/apps/cli/src/commands/send/delete.command.ts @@ -1,19 +1,20 @@ -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { Response } from "../../models/response"; export class SendDeleteCommand { - constructor(private sendService: SendService) {} + constructor(private sendService: SendService, private sendApiService: SendApiService) {} async run(id: string) { - const send = await this.sendService.get(id); + const send = await this.sendService.getFromState(id); if (send == null) { return Response.notFound(); } try { - await this.sendService.deleteWithServer(id); + await this.sendApiService.delete(id); return Response.success(); } catch (e) { return Response.error(e); diff --git a/apps/cli/src/commands/send/edit.command.ts b/apps/cli/src/commands/send/edit.command.ts index 4fc92fb89a..94a8106bbe 100644 --- a/apps/cli/src/commands/send/edit.command.ts +++ b/apps/cli/src/commands/send/edit.command.ts @@ -1,4 +1,5 @@ -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SendType } from "@bitwarden/common/enums/sendType"; @@ -12,7 +13,8 @@ export class SendEditCommand { constructor( private sendService: SendService, private stateService: StateService, - private getCommand: SendGetCommand + private getCommand: SendGetCommand, + private sendApiService: SendApiService ) {} async run(requestJson: string, cmdOptions: Record): Promise { @@ -45,7 +47,7 @@ export class SendEditCommand { req.id = req.id.toLowerCase(); } - const send = await this.sendService.get(req.id); + const send = await this.sendService.getFromState(req.id); if (send == null) { return Response.notFound(); @@ -72,7 +74,7 @@ export class SendEditCommand { encSend.deletionDate = sendView.deletionDate; encSend.expirationDate = sendView.expirationDate; - await this.sendService.saveWithServer([encSend, encFileData]); + await this.sendApiService.save([encSend, encFileData]); } catch (e) { return Response.error(e); } diff --git a/apps/cli/src/commands/send/get.command.ts b/apps/cli/src/commands/send/get.command.ts index 37367276a8..c6ae693dbb 100644 --- a/apps/cli/src/commands/send/get.command.ts +++ b/apps/cli/src/commands/send/get.command.ts @@ -3,7 +3,7 @@ import * as program from "commander"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { Utils } from "@bitwarden/common/misc/utils"; import { SendView } from "@bitwarden/common/models/view/send.view"; @@ -66,12 +66,12 @@ export class SendGetCommand extends DownloadCommand { private async getSendView(id: string): Promise { if (Utils.isGuid(id)) { - const send = await this.sendService.get(id); + const send = await this.sendService.getFromState(id); if (send != null) { return await send.decrypt(); } } else if (id.trim() !== "") { - let sends = await this.sendService.getAllDecrypted(); + let sends = await this.sendService.getAllDecryptedFromState(); sends = this.searchService.searchSends(sends, id); if (sends.length > 1) { return sends; diff --git a/apps/cli/src/commands/send/list.command.ts b/apps/cli/src/commands/send/list.command.ts index c37040b255..21349e521f 100644 --- a/apps/cli/src/commands/send/list.command.ts +++ b/apps/cli/src/commands/send/list.command.ts @@ -1,6 +1,6 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { Response } from "../../models/response"; import { ListResponse } from "../../models/response/list.response"; @@ -14,7 +14,7 @@ export class SendListCommand { ) {} async run(cmdOptions: Record): Promise { - let sends = await this.sendService.getAllDecrypted(); + let sends = await this.sendService.getAllDecryptedFromState(); const normalizedOptions = new Options(cmdOptions); if (normalizedOptions.search != null && normalizedOptions.search.trim() !== "") { diff --git a/apps/cli/src/commands/send/receive.command.ts b/apps/cli/src/commands/send/receive.command.ts index 824f7d1a93..20759fbd30 100644 --- a/apps/cli/src/commands/send/receive.command.ts +++ b/apps/cli/src/commands/send/receive.command.ts @@ -6,6 +6,7 @@ import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; import { SendType } from "@bitwarden/common/enums/sendType"; import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; import { Utils } from "@bitwarden/common/misc/utils"; @@ -29,7 +30,8 @@ export class SendReceiveCommand extends DownloadCommand { cryptoService: CryptoService, private cryptoFunctionService: CryptoFunctionService, private platformUtilsService: PlatformUtilsService, - private environmentService: EnvironmentService + private environmentService: EnvironmentService, + private sendApiService: SendApiService ) { super(cryptoService); } @@ -84,7 +86,7 @@ export class SendReceiveCommand extends DownloadCommand { process.stdout.write(response?.text?.text); return Response.success(); case SendType.File: { - const downloadData = await this.apiService.getSendFileDownloadData( + const downloadData = await this.sendApiService.getSendFileDownloadData( response, this.sendAccessRequest, apiUrl @@ -135,7 +137,11 @@ export class SendReceiveCommand extends DownloadCommand { key: ArrayBuffer ): Promise { try { - const sendResponse = await this.apiService.postSendAccess(id, this.sendAccessRequest, url); + const sendResponse = await this.sendApiService.postSendAccess( + id, + this.sendAccessRequest, + url + ); const sendAccess = new SendAccess(sendResponse); this.decKey = await this.cryptoService.makeSendKey(key); diff --git a/apps/cli/src/commands/send/remove-password.command.ts b/apps/cli/src/commands/send/remove-password.command.ts index 53805fa003..0c2e35ad25 100644 --- a/apps/cli/src/commands/send/remove-password.command.ts +++ b/apps/cli/src/commands/send/remove-password.command.ts @@ -1,14 +1,15 @@ -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { Response } from "../../models/response"; import { SendResponse } from "../../models/response/send.response"; export class SendRemovePasswordCommand { - constructor(private sendService: SendService) {} + constructor(private sendService: SendService, private sendApiService: SendApiService) {} async run(id: string) { try { - await this.sendService.removePasswordWithServer(id); + await this.sendApiService.removePassword(id); const updatedSend = await this.sendService.get(id); const decSend = await updatedSend.decrypt(); diff --git a/apps/cli/src/commands/serve.command.ts b/apps/cli/src/commands/serve.command.ts index 370981370c..3bb459383c 100644 --- a/apps/cli/src/commands/serve.command.ts +++ b/apps/cli/src/commands/serve.command.ts @@ -133,9 +133,10 @@ export class ServeCommand { this.sendCreateCommand = new SendCreateCommand( this.main.sendService, this.main.stateService, - this.main.environmentService + this.main.environmentService, + this.main.sendApiService ); - this.sendDeleteCommand = new SendDeleteCommand(this.main.sendService); + this.sendDeleteCommand = new SendDeleteCommand(this.main.sendService, this.main.sendApiService); this.sendGetCommand = new SendGetCommand( this.main.sendService, this.main.environmentService, @@ -145,14 +146,18 @@ export class ServeCommand { this.sendEditCommand = new SendEditCommand( this.main.sendService, this.main.stateService, - this.sendGetCommand + this.sendGetCommand, + this.main.sendApiService ); this.sendListCommand = new SendListCommand( this.main.sendService, this.main.environmentService, this.main.searchService ); - this.sendRemovePasswordCommand = new SendRemovePasswordCommand(this.main.sendService); + this.sendRemovePasswordCommand = new SendRemovePasswordCommand( + this.main.sendService, + this.main.sendApiService + ); } async run(options: program.OptionValues) { diff --git a/apps/cli/src/send.program.ts b/apps/cli/src/send.program.ts index 78c607f041..a9a0f29244 100644 --- a/apps/cli/src/send.program.ts +++ b/apps/cli/src/send.program.ts @@ -109,7 +109,8 @@ export class SendProgram extends Program { this.main.cryptoService, this.main.cryptoFunctionService, this.main.platformUtilsService, - this.main.environmentService + this.main.environmentService, + this.main.sendApiService ); const response = await cmd.run(url, options); this.processResponse(response); @@ -259,7 +260,12 @@ export class SendProgram extends Program { this.main.searchService, this.main.cryptoService ); - const cmd = new SendEditCommand(this.main.sendService, this.main.stateService, getCmd); + const cmd = new SendEditCommand( + this.main.sendService, + this.main.stateService, + getCmd, + this.main.sendApiService + ); const response = await cmd.run(encodedJson, options); this.processResponse(response); }); @@ -273,7 +279,7 @@ export class SendProgram extends Program { }) .action(async (id: string) => { await this.exitIfLocked(); - const cmd = new SendDeleteCommand(this.main.sendService); + const cmd = new SendDeleteCommand(this.main.sendService, this.main.sendApiService); const response = await cmd.run(id); this.processResponse(response); }); @@ -287,7 +293,7 @@ export class SendProgram extends Program { }) .action(async (id: string) => { await this.exitIfLocked(); - const cmd = new SendRemovePasswordCommand(this.main.sendService); + const cmd = new SendRemovePasswordCommand(this.main.sendService, this.main.sendApiService); const response = await cmd.run(id); this.processResponse(response); }); @@ -327,7 +333,8 @@ export class SendProgram extends Program { const cmd = new SendCreateCommand( this.main.sendService, this.main.stateService, - this.main.environmentService + this.main.environmentService, + this.main.sendApiService ); return await cmd.run(encodedJson, options); } diff --git a/apps/desktop/src/app/send/add-edit.component.ts b/apps/desktop/src/app/send/add-edit.component.ts index 5596165c4f..d6b90f32a0 100644 --- a/apps/desktop/src/app/send/add-edit.component.ts +++ b/apps/desktop/src/app/send/add-edit.component.ts @@ -7,7 +7,8 @@ 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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -25,7 +26,8 @@ export class AddEditComponent extends BaseAddEditComponent { stateService: StateService, messagingService: MessagingService, policyService: PolicyService, - logService: LogService + logService: LogService, + sendApiService: SendApiService ) { super( i18nService, @@ -36,7 +38,8 @@ export class AddEditComponent extends BaseAddEditComponent { messagingService, policyService, logService, - stateService + stateService, + sendApiService ); } diff --git a/apps/desktop/src/app/send/send.component.ts b/apps/desktop/src/app/send/send.component.ts index 3d40120356..85918bd526 100644 --- a/apps/desktop/src/app/send/send.component.ts +++ b/apps/desktop/src/app/send/send.component.ts @@ -7,7 +7,8 @@ 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 { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { SendView } from "@bitwarden/common/models/view/send.view"; @@ -44,7 +45,8 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro searchService: SearchService, policyService: PolicyService, private searchBarService: SearchBarService, - logService: LogService + logService: LogService, + sendApiService: SendApiService ) { super( sendService, @@ -54,7 +56,8 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro ngZone, searchService, policyService, - logService + logService, + sendApiService ); // eslint-disable-next-line rxjs-angular/prefer-takeuntil this.searchBarService.searchText$.subscribe((searchText) => { diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts index e1b147ca60..169a35e59c 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -9,6 +9,7 @@ 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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -41,7 +42,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnChanges, private broadcasterService: BroadcasterService, private ngZone: NgZone, logService: LogService, - organizationService: OrganizationService + organizationService: OrganizationService, + sendApiService: SendApiService ) { super( cipherService, @@ -56,7 +58,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnChanges, policyService, logService, passwordRepromptService, - organizationService + organizationService, + sendApiService ); } diff --git a/apps/web/src/app/send/access.component.ts b/apps/web/src/app/send/access.component.ts index 002523b90f..3b8e1531ff 100644 --- a/apps/web/src/app/send/access.component.ts +++ b/apps/web/src/app/send/access.component.ts @@ -7,6 +7,7 @@ import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunc import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; import { SEND_KDF_ITERATIONS } from "@bitwarden/common/enums/kdfType"; import { SendType } from "@bitwarden/common/enums/sendType"; import { Utils } from "@bitwarden/common/misc/utils"; @@ -48,7 +49,8 @@ export class AccessComponent implements OnInit { private platformUtilsService: PlatformUtilsService, private route: ActivatedRoute, private cryptoService: CryptoService, - private fileDownloadService: FileDownloadService + private fileDownloadService: FileDownloadService, + private sendApiService: SendApiService ) {} get sendText() { @@ -93,7 +95,7 @@ export class AccessComponent implements OnInit { return; } - const downloadData = await this.apiService.getSendFileDownloadData( + const downloadData = await this.sendApiService.getSendFileDownloadData( this.send, this.accessRequest ); @@ -157,9 +159,9 @@ export class AccessComponent implements OnInit { try { let sendResponse: SendAccessResponse = null; if (this.loading) { - sendResponse = await this.apiService.postSendAccess(this.id, this.accessRequest); + sendResponse = await this.sendApiService.postSendAccess(this.id, this.accessRequest); } else { - this.formPromise = this.apiService.postSendAccess(this.id, this.accessRequest); + this.formPromise = this.sendApiService.postSendAccess(this.id, this.accessRequest); sendResponse = await this.formPromise; } this.passwordRequired = false; diff --git a/apps/web/src/app/send/add-edit.component.ts b/apps/web/src/app/send/add-edit.component.ts index b9aa49af09..167196c9b4 100644 --- a/apps/web/src/app/send/add-edit.component.ts +++ b/apps/web/src/app/send/add-edit.component.ts @@ -7,7 +7,8 @@ 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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -27,7 +28,8 @@ export class AddEditComponent extends BaseAddEditComponent { stateService: StateService, messagingService: MessagingService, policyService: PolicyService, - logService: LogService + logService: LogService, + sendApiService: SendApiService ) { super( i18nService, @@ -38,7 +40,8 @@ export class AddEditComponent extends BaseAddEditComponent { messagingService, policyService, logService, - stateService + stateService, + sendApiService ); } diff --git a/apps/web/src/app/send/send.component.ts b/apps/web/src/app/send/send.component.ts index 0b2bfe65bb..13f9fcd0db 100644 --- a/apps/web/src/app/send/send.component.ts +++ b/apps/web/src/app/send/send.component.ts @@ -8,7 +8,8 @@ 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 { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { SendView } from "@bitwarden/common/models/view/send.view"; import { Icons } from "@bitwarden/components"; @@ -36,7 +37,8 @@ export class SendComponent extends BaseSendComponent { policyService: PolicyService, private modalService: ModalService, private broadcasterService: BroadcasterService, - logService: LogService + logService: LogService, + sendApiService: SendApiService ) { super( sendService, @@ -46,7 +48,8 @@ export class SendComponent extends BaseSendComponent { ngZone, searchService, policyService, - logService + logService, + sendApiService ); } diff --git a/apps/web/src/app/settings/change-password.component.ts b/apps/web/src/app/settings/change-password.component.ts index 946af00fcd..a5e72e3ba1 100644 --- a/apps/web/src/app/settings/change-password.component.ts +++ b/apps/web/src/app/settings/change-password.component.ts @@ -11,7 +11,7 @@ import { MessagingService } from "@bitwarden/common/abstractions/messaging.servi import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service"; import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -240,7 +240,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent { request.ciphers.push(new CipherWithIdRequest(cipher)); } - const sends = await this.sendService.getAll(); + const sends = await firstValueFrom(this.sendService.sends$); await Promise.all( sends.map(async (send) => { const cryptoKey = await this.cryptoService.decryptToBytes(send.key, null); diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index ef17c7ec2c..23d73f622f 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -7,6 +7,7 @@ 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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { TotpService } from "@bitwarden/common/abstractions/totp.service"; import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service"; @@ -56,7 +57,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On protected policyService: PolicyService, organizationService: OrganizationService, logService: LogService, - passwordRepromptService: PasswordRepromptService + passwordRepromptService: PasswordRepromptService, + sendApiService: SendApiService ) { super( cipherService, @@ -71,7 +73,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On policyService, logService, passwordRepromptService, - organizationService + organizationService, + sendApiService ); } diff --git a/apps/web/src/app/vault/org-vault/add-edit.component.ts b/apps/web/src/app/vault/org-vault/add-edit.component.ts index 5e3d62f923..763ec0348d 100644 --- a/apps/web/src/app/vault/org-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/org-vault/add-edit.component.ts @@ -7,6 +7,7 @@ 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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { TotpService } from "@bitwarden/common/abstractions/totp.service"; import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service"; @@ -47,7 +48,8 @@ export class AddEditComponent extends BaseAddEditComponent { policyService: PolicyService, logService: LogService, passwordRepromptService: PasswordRepromptService, - organizationService: OrganizationService + organizationService: OrganizationService, + sendApiService: SendApiService ) { super( cipherService, @@ -64,7 +66,8 @@ export class AddEditComponent extends BaseAddEditComponent { policyService, organizationService, logService, - passwordRepromptService + passwordRepromptService, + sendApiService ); } diff --git a/apps/web/src/auth/settings/emergency-access/emergency-add-edit.component.ts b/apps/web/src/auth/settings/emergency-access/emergency-add-edit.component.ts index f3ca697682..4edddea0d2 100644 --- a/apps/web/src/auth/settings/emergency-access/emergency-add-edit.component.ts +++ b/apps/web/src/auth/settings/emergency-access/emergency-add-edit.component.ts @@ -6,6 +6,7 @@ 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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { TotpService } from "@bitwarden/common/abstractions/totp.service"; import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service"; @@ -43,7 +44,8 @@ export class EmergencyAddEditComponent extends BaseAddEditComponent { policyService: PolicyService, passwordRepromptService: PasswordRepromptService, organizationService: OrganizationService, - logService: LogService + logService: LogService, + sendApiService: SendApiService ) { super( cipherService, @@ -60,7 +62,8 @@ export class EmergencyAddEditComponent extends BaseAddEditComponent { policyService, organizationService, logService, - passwordRepromptService + passwordRepromptService, + sendApiService ); } diff --git a/libs/angular/src/components/send/add-edit.component.ts b/libs/angular/src/components/send/add-edit.component.ts index da00c502fa..2aa0277be8 100644 --- a/libs/angular/src/components/send/add-edit.component.ts +++ b/libs/angular/src/components/send/add-edit.component.ts @@ -7,7 +7,8 @@ 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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type"; @@ -58,7 +59,8 @@ export class AddEditComponent implements OnInit, OnDestroy { protected messagingService: MessagingService, protected policyService: PolicyService, private logService: LogService, - protected stateService: StateService + protected stateService: StateService, + protected sendApiService: SendApiService ) { this.typeOptions = [ { name: i18nService.t("sendTypeFile"), value: SendType.File }, @@ -127,7 +129,7 @@ export class AddEditComponent implements OnInit, OnDestroy { if (this.send == null) { if (this.editMode) { - const send = await this.loadSend(); + const send = this.loadSend(); this.send = await send.decrypt(); } else { this.send = new SendView(); @@ -191,7 +193,7 @@ export class AddEditComponent implements OnInit, OnDestroy { } this.formPromise = this.encryptSend(file).then(async (encSend) => { - const uploadPromise = this.sendService.saveWithServer(encSend); + const uploadPromise = this.sendApiService.save(encSend); await uploadPromise; if (this.send.id == null) { this.send.id = encSend[0].id; @@ -241,7 +243,7 @@ export class AddEditComponent implements OnInit, OnDestroy { } try { - this.deletePromise = this.sendService.deleteWithServer(this.send.id); + this.deletePromise = this.sendApiService.delete(this.send.id); await this.deletePromise; this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend")); await this.load(); @@ -270,7 +272,7 @@ export class AddEditComponent implements OnInit, OnDestroy { this.showOptions = !this.showOptions; } - protected async loadSend(): Promise { + protected loadSend(): Send { return this.sendService.get(this.sendId); } diff --git a/libs/angular/src/components/send/send.component.ts b/libs/angular/src/components/send/send.component.ts index 63c51c59a3..5635e8a156 100644 --- a/libs/angular/src/components/send/send.component.ts +++ b/libs/angular/src/components/send/send.component.ts @@ -6,7 +6,8 @@ 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 { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { SendService } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type"; import { SendType } from "@bitwarden/common/enums/sendType"; @@ -47,7 +48,8 @@ export class SendComponent implements OnInit, OnDestroy { protected ngZone: NgZone, protected searchService: SearchService, protected policyService: PolicyService, - private logService: LogService + private logService: LogService, + protected sendApiService: SendApiService ) {} async ngOnInit() { @@ -66,8 +68,9 @@ export class SendComponent implements OnInit, OnDestroy { async load(filter: (send: SendView) => boolean = null) { this.loading = true; - const sends = await this.sendService.getAllDecrypted(); - this.sends = sends; + this.sendService.sendViews$.pipe(takeUntil(this.destroy$)).subscribe((sends) => { + this.sends = sends; + }); if (this.onSuccessfulLoad != null) { await this.onSuccessfulLoad(); } else { @@ -134,7 +137,7 @@ export class SendComponent implements OnInit, OnDestroy { } try { - this.actionPromise = this.sendService.removePasswordWithServer(s.id); + this.actionPromise = this.sendApiService.removePassword(s.id); await this.actionPromise; if (this.onSuccessfulRemovePassword != null) { this.onSuccessfulRemovePassword(); @@ -165,7 +168,7 @@ export class SendComponent implements OnInit, OnDestroy { } try { - this.actionPromise = this.sendService.deleteWithServer(s.id); + this.actionPromise = this.sendApiService.delete(s.id); await this.actionPromise; if (this.onSuccessfulDelete != null) { diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index bd54e3c819..dc8349599b 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -15,7 +15,7 @@ import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/ import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service"; -import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/fileUpload.service"; +import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/file-upload/file-upload.service"; import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "@bitwarden/common/abstractions/formValidationErrors.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; @@ -29,7 +29,8 @@ import { import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service"; import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service"; -import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstractions/send.service"; +import { SendApiService as SendApiServiceAbstraction } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; +import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstractions/send/send.service.abstraction"; import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service"; import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.service"; @@ -96,14 +97,15 @@ import { EnvironmentService } from "@bitwarden/common/services/environment.servi import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { ExportService } from "@bitwarden/common/services/export.service"; -import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; +import { FileUploadService } from "@bitwarden/common/services/file-upload/file-upload.service"; import { FormValidationErrorsService } from "@bitwarden/common/services/formValidationErrors.service"; import { NotificationsService } from "@bitwarden/common/services/notifications.service"; import { OrgDomainApiService } from "@bitwarden/common/services/organization-domain/org-domain-api.service"; import { OrgDomainService } from "@bitwarden/common/services/organization-domain/org-domain.service"; import { OrganizationUserServiceImplementation } from "@bitwarden/common/services/organization-user/organization-user.service.implementation"; import { SearchService } from "@bitwarden/common/services/search.service"; -import { SendService } from "@bitwarden/common/services/send.service"; +import { SendApiService } from "@bitwarden/common/services/send/send-api.service"; +import { SendService } from "@bitwarden/common/services/send/send.service"; import { SettingsService } from "@bitwarden/common/services/settings.service"; import { StateService } from "@bitwarden/common/services/state.service"; import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service"; @@ -121,6 +123,7 @@ import { UsernameGenerationServiceAbstraction, } from "@bitwarden/common/tools/generator/username"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderService as FolderServiceAbstraction, @@ -130,6 +133,7 @@ import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@ import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync-notifier.service.abstraction"; import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service"; @@ -230,40 +234,50 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; I18nServiceAbstraction, ], }, + { + provide: FileUploadServiceAbstraction, + useClass: FileUploadService, + deps: [LoginServiceAbstraction], + }, + { + provide: CipherFileUploadServiceAbstraction, + useClass: CipherFileUploadService, + deps: [ApiServiceAbstraction, FileUploadServiceAbstraction], + }, { provide: CipherServiceAbstraction, useFactory: ( cryptoService: CryptoServiceAbstraction, settingsService: SettingsServiceAbstraction, apiService: ApiServiceAbstraction, - fileUploadService: FileUploadServiceAbstraction, i18nService: I18nServiceAbstraction, injector: Injector, logService: LogService, stateService: StateServiceAbstraction, - encryptService: EncryptService + encryptService: EncryptService, + fileUploadService: CipherFileUploadServiceAbstraction ) => new CipherService( cryptoService, settingsService, apiService, - fileUploadService, i18nService, () => injector.get(SearchServiceAbstraction), logService, stateService, - encryptService + encryptService, + fileUploadService ), deps: [ CryptoServiceAbstraction, SettingsServiceAbstraction, ApiServiceAbstraction, - FileUploadServiceAbstraction, I18nServiceAbstraction, Injector, // TODO: Get rid of this circular dependency! LogService, StateServiceAbstraction, EncryptService, + CipherFileUploadServiceAbstraction, ], }, { @@ -359,9 +373,19 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; ], }, { - provide: FileUploadServiceAbstraction, - useClass: FileUploadService, - deps: [LogService, ApiServiceAbstraction], + provide: SendServiceAbstraction, + useClass: SendService, + deps: [ + CryptoServiceAbstraction, + I18nServiceAbstraction, + CryptoFunctionServiceAbstraction, + StateServiceAbstraction, + ], + }, + { + provide: SendApiServiceAbstraction, + useClass: SendApiService, + deps: [ApiServiceAbstraction, FileUploadServiceAbstraction, SendServiceAbstraction], }, { provide: SyncServiceAbstraction, @@ -382,6 +406,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; ProviderServiceAbstraction, FolderApiServiceAbstraction, OrganizationServiceAbstraction, + SendApiServiceAbstraction, LOGOUT_CALLBACK, ], }, @@ -508,18 +533,6 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; useClass: PolicyApiService, deps: [PolicyServiceAbstraction, ApiServiceAbstraction, StateServiceAbstraction], }, - { - provide: SendServiceAbstraction, - useClass: SendService, - deps: [ - CryptoServiceAbstraction, - ApiServiceAbstraction, - FileUploadServiceAbstraction, - I18nServiceAbstraction, - CryptoFunctionServiceAbstraction, - StateServiceAbstraction, - ], - }, { provide: KeyConnectorServiceAbstraction, useClass: KeyConnectorService, diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index b41ad26765..bb3fd3b159 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -7,6 +7,7 @@ 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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { SendApiService } from "@bitwarden/common/abstractions/send/send-api.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CollectionService } from "@bitwarden/common/admin-console/abstractions/collection.service"; import { @@ -99,7 +100,8 @@ export class AddEditComponent implements OnInit, OnDestroy { protected policyService: PolicyService, private logService: LogService, protected passwordRepromptService: PasswordRepromptService, - private organizationService: OrganizationService + private organizationService: OrganizationService, + protected sendApiService: SendApiService ) { this.typeOptions = [ { name: i18nService.t("typeLogin"), value: CipherType.Login }, diff --git a/libs/common/spec/services/send.service.spec.ts b/libs/common/spec/services/send.service.spec.ts new file mode 100644 index 0000000000..32856cf61f --- /dev/null +++ b/libs/common/spec/services/send.service.spec.ts @@ -0,0 +1,178 @@ +import { any, mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; + +import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { StateService } from "@bitwarden/common/abstractions/state.service"; +import { SendData } from "@bitwarden/common/models/data/send.data"; +import { EncString } from "@bitwarden/common/models/domain/enc-string"; +import { Send } from "@bitwarden/common/models/domain/send"; +import { SendView } from "@bitwarden/common/models/view/send.view"; +import { ContainerService } from "@bitwarden/common/services/container.service"; +import { SendService } from "@bitwarden/common/services/send/send.service"; + +describe("SendService", () => { + const cryptoService = mock(); + const i18nService = mock(); + const cryptoFunctionService = mock(); + const encryptService = mock(); + + let sendService: SendService; + + let stateService: MockProxy; + let activeAccount: BehaviorSubject; + let activeAccountUnlocked: BehaviorSubject; + + beforeEach(() => { + activeAccount = new BehaviorSubject("123"); + activeAccountUnlocked = new BehaviorSubject(true); + + stateService = mock(); + stateService.activeAccount$ = activeAccount; + stateService.activeAccountUnlocked$ = activeAccountUnlocked; + (window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService); + + stateService.getEncryptedSends.calledWith(any()).mockResolvedValue({ + "1": sendData("1", "Test Send"), + }); + + stateService.getDecryptedSends + .calledWith(any()) + .mockResolvedValue([sendView("1", "Test Send")]); + + sendService = new SendService(cryptoService, i18nService, cryptoFunctionService, stateService); + }); + + afterEach(() => { + activeAccount.complete(); + activeAccountUnlocked.complete(); + }); + + describe("get", () => { + it("exists", async () => { + const result = sendService.get("1"); + + expect(result).toEqual(send("1", "Test Send")); + }); + + it("does not exist", async () => { + const result = sendService.get("2"); + + expect(result).toBe(undefined); + }); + }); + + it("getAll", async () => { + const sends = await sendService.getAll(); + const send1 = sends[0]; + + expect(sends).toHaveLength(1); + expect(send1).toEqual(send("1", "Test Send")); + }); + + describe("getFromState", () => { + it("exists", async () => { + const result = await sendService.getFromState("1"); + + expect(result).toEqual(send("1", "Test Send")); + }); + it("does not exist", async () => { + const result = await sendService.getFromState("2"); + + expect(result).toBe(null); + }); + }); + + it("getAllDecryptedFromState", async () => { + await sendService.getAllDecryptedFromState(); + + expect(stateService.getDecryptedSends).toHaveBeenCalledTimes(1); + }); + + // InternalSendService + + it("upsert", async () => { + await sendService.upsert(sendData("2", "Test 2")); + + expect(await firstValueFrom(sendService.sends$)).toEqual([ + send("1", "Test Send"), + send("2", "Test 2"), + ]); + }); + + it("replace", async () => { + await sendService.replace({ "2": sendData("2", "test 2") }); + + expect(await firstValueFrom(sendService.sends$)).toEqual([send("2", "test 2")]); + }); + + it("clear", async () => { + await sendService.clear(); + + expect(await firstValueFrom(sendService.sends$)).toEqual([]); + }); + + describe("delete", () => { + it("exists", async () => { + await sendService.delete("1"); + + expect(stateService.getEncryptedSends).toHaveBeenCalledTimes(2); + expect(stateService.setEncryptedSends).toHaveBeenCalledTimes(1); + }); + + it("does not exist", async () => { + sendService.delete("1"); + + expect(stateService.getEncryptedSends).toHaveBeenCalledTimes(2); + }); + }); + + // Send object helper functions + + function sendData(id: string, name: string) { + const data = new SendData({} as any); + data.id = id; + data.name = name; + data.disabled = false; + data.accessCount = 2; + data.accessId = "1"; + data.revisionDate = null; + data.expirationDate = null; + data.deletionDate = null; + data.notes = "Notes!!"; + data.key = null; + return data; + } + + function sendView(id: string, name: string) { + const data = new SendView({} as any); + data.id = id; + data.name = name; + data.disabled = false; + data.accessCount = 2; + data.accessId = "1"; + data.revisionDate = null; + data.expirationDate = null; + data.deletionDate = null; + data.notes = "Notes!!"; + data.key = null; + return data; + } + + function send(id: string, name: string) { + const data = new Send({} as any); + data.id = id; + data.name = new EncString(name); + data.disabled = false; + data.accessCount = 2; + data.accessId = "1"; + data.revisionDate = null; + data.expirationDate = null; + data.deletionDate = null; + data.notes = new EncString("Notes!!"); + data.key = null; + return data; + } +}); diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 3bf7c3f1f9..a3523d74bf 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -112,8 +112,6 @@ import { KeysRequest } from "../models/request/keys.request"; import { OrganizationImportRequest } from "../models/request/organization-import.request"; import { PreloginRequest } from "../models/request/prelogin.request"; import { RegisterRequest } from "../models/request/register.request"; -import { SendAccessRequest } from "../models/request/send-access.request"; -import { SendRequest } from "../models/request/send.request"; import { StorageRequest } from "../models/request/storage.request"; import { UpdateAvatarRequest } from "../models/request/update-avatar.request"; import { UpdateDomainsRequest } from "../models/request/update-domains.request"; @@ -125,12 +123,7 @@ import { DomainsResponse } from "../models/response/domains.response"; import { EventResponse } from "../models/response/event.response"; import { ListResponse } from "../models/response/list.response"; import { ProfileResponse } from "../models/response/profile.response"; -import { SendAccessResponse } from "../models/response/send-access.response"; -import { SendFileDownloadDataResponse } from "../models/response/send-file-download-data.response"; -import { SendFileUploadDataResponse } from "../models/response/send-file-upload-data.response"; -import { SendResponse } from "../models/response/send.response"; import { UserKeyResponse } from "../models/response/user-key.response"; -import { SendAccessView } from "../models/view/send-access.view"; import { AttachmentRequest } from "../vault/models/request/attachment.request"; import { CipherBulkDeleteRequest } from "../vault/models/request/cipher-bulk-delete.request"; import { CipherBulkMoveRequest } from "../vault/models/request/cipher-bulk-move.request"; @@ -213,31 +206,6 @@ export abstract class ApiService { getUserBillingHistory: () => Promise; getUserBillingPayment: () => Promise; - getSend: (id: string) => Promise; - postSendAccess: ( - id: string, - request: SendAccessRequest, - apiUrl?: string - ) => Promise; - getSends: () => Promise>; - postSend: (request: SendRequest) => Promise; - postFileTypeSend: (request: SendRequest) => Promise; - postSendFile: (sendId: string, fileId: string, data: FormData) => Promise; - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - postSendFileLegacy: (data: FormData) => Promise; - putSend: (id: string, request: SendRequest) => Promise; - putSendRemovePassword: (id: string) => Promise; - deleteSend: (id: string) => Promise; - getSendFileDownloadData: ( - send: SendAccessView, - request: SendAccessRequest, - apiUrl?: string - ) => Promise; - renewSendFileUploadUrl: (sendId: string, fileId: string) => Promise; - getCipher: (id: string) => Promise; getFullCipherDetails: (id: string) => Promise; getCipherAdmin: (id: string) => Promise; diff --git a/libs/common/src/abstractions/file-upload/file-upload.service.ts b/libs/common/src/abstractions/file-upload/file-upload.service.ts new file mode 100644 index 0000000000..35dc8a1d1c --- /dev/null +++ b/libs/common/src/abstractions/file-upload/file-upload.service.ts @@ -0,0 +1,18 @@ +import { FileUploadType } from "../../enums/fileUploadType"; +import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; +import { EncString } from "../../models/domain/enc-string"; + +export abstract class FileUploadService { + upload: ( + uploadData: { url: string; fileUploadType: FileUploadType }, + fileName: EncString, + encryptedFileData: EncArrayBuffer, + fileUploadMethods: FileUploadApiMethods + ) => Promise; +} + +export type FileUploadApiMethods = { + postDirect: (fileData: FormData) => Promise; + renewFileUploadUrl: () => Promise; + rollback: () => Promise; +}; diff --git a/libs/common/src/abstractions/fileUpload.service.ts b/libs/common/src/abstractions/fileUpload.service.ts deleted file mode 100644 index 0dbf8b3a6a..0000000000 --- a/libs/common/src/abstractions/fileUpload.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; -import { EncString } from "../models/domain/enc-string"; -import { SendFileUploadDataResponse } from "../models/response/send-file-upload-data.response"; -import { AttachmentUploadDataResponse } from "../vault/models/response/attachment-upload-data.response"; - -export abstract class FileUploadService { - uploadSendFile: ( - uploadData: SendFileUploadDataResponse, - fileName: EncString, - encryptedFileData: EncArrayBuffer - ) => Promise; - uploadCipherAttachment: ( - admin: boolean, - uploadData: AttachmentUploadDataResponse, - fileName: EncString, - encryptedFileData: EncArrayBuffer - ) => Promise; -} diff --git a/libs/common/src/abstractions/send.service.ts b/libs/common/src/abstractions/send.service.ts deleted file mode 100644 index db212e33ce..0000000000 --- a/libs/common/src/abstractions/send.service.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SendData } from "../models/data/send.data"; -import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; -import { Send } from "../models/domain/send"; -import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; -import { SendView } from "../models/view/send.view"; - -export abstract class SendService { - clearCache: () => Promise; - encrypt: ( - model: SendView, - file: File | ArrayBuffer, - password: string, - key?: SymmetricCryptoKey - ) => Promise<[Send, EncArrayBuffer]>; - get: (id: string) => Promise; - getAll: () => Promise; - getAllDecrypted: () => Promise; - saveWithServer: (sendData: [Send, EncArrayBuffer]) => Promise; - upsert: (send: SendData | SendData[]) => Promise; - replace: (sends: { [id: string]: SendData }) => Promise; - clear: (userId: string) => Promise; - delete: (id: string | string[]) => Promise; - deleteWithServer: (id: string) => Promise; - removePasswordWithServer: (id: string) => Promise; -} diff --git a/libs/common/src/abstractions/send/send-api.service.abstraction.ts b/libs/common/src/abstractions/send/send-api.service.abstraction.ts new file mode 100644 index 0000000000..462c7293dd --- /dev/null +++ b/libs/common/src/abstractions/send/send-api.service.abstraction.ts @@ -0,0 +1,40 @@ +import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; +import { Send } from "../../models/domain/send"; +import { SendAccessRequest } from "../../models/request/send-access.request"; +import { SendRequest } from "../../models/request/send.request"; +import { ListResponse } from "../../models/response/list.response"; +import { SendAccessResponse } from "../../models/response/send-access.response"; +import { SendFileDownloadDataResponse } from "../../models/response/send-file-download-data.response"; +import { SendFileUploadDataResponse } from "../../models/response/send-file-upload-data.response"; +import { SendResponse } from "../../models/response/send.response"; +import { SendAccessView } from "../../models/view/send-access.view"; + +export abstract class SendApiService { + getSend: (id: string) => Promise; + postSendAccess: ( + id: string, + request: SendAccessRequest, + apiUrl?: string + ) => Promise; + getSends: () => Promise>; + postSend: (request: SendRequest) => Promise; + postFileTypeSend: (request: SendRequest) => Promise; + postSendFile: (sendId: string, fileId: string, data: FormData) => Promise; + /** + * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. + * This method still exists for backward compatibility with old server versions. + */ + postSendFileLegacy: (data: FormData) => Promise; + putSend: (id: string, request: SendRequest) => Promise; + putSendRemovePassword: (id: string) => Promise; + deleteSend: (id: string) => Promise; + getSendFileDownloadData: ( + send: SendAccessView, + request: SendAccessRequest, + apiUrl?: string + ) => Promise; + renewSendFileUploadUrl: (sendId: string, fileId: string) => Promise; + removePassword: (id: string) => Promise; + delete: (id: string) => Promise; + save: (sendData: [Send, EncArrayBuffer]) => Promise; +} diff --git a/libs/common/src/abstractions/send/send.service.abstraction.ts b/libs/common/src/abstractions/send/send.service.abstraction.ts new file mode 100644 index 0000000000..eb09949574 --- /dev/null +++ b/libs/common/src/abstractions/send/send.service.abstraction.ts @@ -0,0 +1,39 @@ +import { Observable } from "rxjs"; + +import { SendData } from "../../models/data/send.data"; +import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; +import { Send } from "../../models/domain/send"; +import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; +import { SendView } from "../../models/view/send.view"; + +export abstract class SendService { + sends$: Observable; + sendViews$: Observable; + + encrypt: ( + model: SendView, + file: File | ArrayBuffer, + password: string, + key?: SymmetricCryptoKey + ) => Promise<[Send, EncArrayBuffer]>; + get: (id: string) => Send; + /** + * @deprecated Do not call this, use the sends$ observable collection + */ + getAll: () => Promise; + /** + * @deprecated Only use in CLI + */ + getFromState: (id: string) => Promise; + /** + * @deprecated Only use in CLI + */ + getAllDecryptedFromState: () => Promise; +} + +export abstract class InternalSendService extends SendService { + upsert: (send: SendData | SendData[]) => Promise; + replace: (sends: { [id: string]: SendData }) => Promise; + clear: (userId: string) => Promise; + delete: (id: string | string[]) => Promise; +} diff --git a/libs/common/src/abstractions/state.service.ts b/libs/common/src/abstractions/state.service.ts index 368a6c8683..664c20e537 100644 --- a/libs/common/src/abstractions/state.service.ts +++ b/libs/common/src/abstractions/state.service.ts @@ -120,7 +120,13 @@ export abstract class StateService { value: Map, options?: StorageOptions ) => Promise; + /** + * @deprecated Do not call this directly, use SendService + */ getDecryptedSends: (options?: StorageOptions) => Promise; + /** + * @deprecated Do not call this directly, use SendService + */ setDecryptedSends: (value: SendView[], options?: StorageOptions) => Promise; getDefaultUriMatch: (options?: StorageOptions) => Promise; setDefaultUriMatch: (value: UriMatchType, options?: StorageOptions) => Promise; @@ -237,7 +243,13 @@ export abstract class StateService { setEncryptedPrivateKey: (value: string, options?: StorageOptions) => Promise; getEncryptedProviderKeys: (options?: StorageOptions) => Promise; setEncryptedProviderKeys: (value: any, options?: StorageOptions) => Promise; + /** + * @deprecated Do not call this directly, use SendService + */ getEncryptedSends: (options?: StorageOptions) => Promise<{ [id: string]: SendData }>; + /** + * @deprecated Do not call this directly, use SendService + */ setEncryptedSends: (value: { [id: string]: SendData }, options?: StorageOptions) => Promise; getEntityId: (options?: StorageOptions) => Promise; setEntityId: (value: string, options?: StorageOptions) => Promise; diff --git a/libs/common/src/models/domain/send-file.ts b/libs/common/src/models/domain/send-file.ts index 706dde8c9c..531db1b0a2 100644 --- a/libs/common/src/models/domain/send-file.ts +++ b/libs/common/src/models/domain/send-file.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { SendFileData } from "../data/send-file.data"; import { SendFileView } from "../view/send-file.view"; @@ -41,4 +43,14 @@ export class SendFile extends Domain { ); return view; } + + static fromJSON(obj: Jsonify) { + if (obj == null) { + return null; + } + + return Object.assign(new SendFile(), obj, { + fileName: EncString.fromJSON(obj.fileName), + }); + } } diff --git a/libs/common/src/models/domain/send-text.ts b/libs/common/src/models/domain/send-text.ts index 8c71d4d14b..083ef898c7 100644 --- a/libs/common/src/models/domain/send-text.ts +++ b/libs/common/src/models/domain/send-text.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { SendTextData } from "../data/send-text.data"; import { SendTextView } from "../view/send-text.view"; @@ -36,4 +38,14 @@ export class SendText extends Domain { key ); } + + static fromJSON(obj: Jsonify) { + if (obj == null) { + return null; + } + + return Object.assign(new SendText(), obj, { + text: EncString.fromJSON(obj.text), + }); + } } diff --git a/libs/common/src/models/domain/send.ts b/libs/common/src/models/domain/send.ts index 6c6126cfc9..678e5ce5ac 100644 --- a/libs/common/src/models/domain/send.ts +++ b/libs/common/src/models/domain/send.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { SendType } from "../../enums/sendType"; import { Utils } from "../../misc/utils"; import { SendData } from "../data/send.data"; @@ -102,4 +104,25 @@ export class Send extends Domain { return model; } + + static fromJSON(obj: Jsonify) { + if (obj == null) { + return null; + } + + const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); + const expirationDate = obj.expirationDate == null ? null : new Date(obj.expirationDate); + const deletionDate = obj.deletionDate == null ? null : new Date(obj.deletionDate); + + return Object.assign(new Send(), obj, { + key: EncString.fromJSON(obj.key), + name: EncString.fromJSON(obj.name), + notes: EncString.fromJSON(obj.notes), + text: SendText.fromJSON(obj.text), + file: SendFile.fromJSON(obj.file), + revisionDate, + expirationDate, + deletionDate, + }); + } } diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 0833bcbdc4..9a6b29d48c 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -121,8 +121,6 @@ import { KeysRequest } from "../models/request/keys.request"; import { OrganizationImportRequest } from "../models/request/organization-import.request"; import { PreloginRequest } from "../models/request/prelogin.request"; import { RegisterRequest } from "../models/request/register.request"; -import { SendAccessRequest } from "../models/request/send-access.request"; -import { SendRequest } from "../models/request/send.request"; import { StorageRequest } from "../models/request/storage.request"; import { UpdateAvatarRequest } from "../models/request/update-avatar.request"; import { UpdateDomainsRequest } from "../models/request/update-domains.request"; @@ -135,12 +133,7 @@ import { ErrorResponse } from "../models/response/error.response"; import { EventResponse } from "../models/response/event.response"; import { ListResponse } from "../models/response/list.response"; import { ProfileResponse } from "../models/response/profile.response"; -import { SendAccessResponse } from "../models/response/send-access.response"; -import { SendFileDownloadDataResponse } from "../models/response/send-file-download-data.response"; -import { SendFileUploadDataResponse } from "../models/response/send-file-upload-data.response"; -import { SendResponse } from "../models/response/send.response"; import { UserKeyResponse } from "../models/response/user-key.response"; -import { SendAccessView } from "../models/view/send-access.view"; import { AttachmentRequest } from "../vault/models/request/attachment.request"; import { CipherBulkDeleteRequest } from "../vault/models/request/cipher-bulk-delete.request"; import { CipherBulkMoveRequest } from "../vault/models/request/cipher-bulk-move.request"; @@ -485,103 +478,6 @@ export class ApiService implements ApiServiceAbstraction { return new BillingPaymentResponse(r); } - // Send APIs - - async getSend(id: string): Promise { - const r = await this.send("GET", "/sends/" + id, null, true, true); - return new SendResponse(r); - } - - async postSendAccess( - id: string, - request: SendAccessRequest, - apiUrl?: string - ): Promise { - const addSendIdHeader = (headers: Headers) => { - headers.set("Send-Id", id); - }; - const r = await this.send( - "POST", - "/sends/access/" + id, - request, - false, - true, - apiUrl, - addSendIdHeader - ); - return new SendAccessResponse(r); - } - - async getSendFileDownloadData( - send: SendAccessView, - request: SendAccessRequest, - apiUrl?: string - ): Promise { - const addSendIdHeader = (headers: Headers) => { - headers.set("Send-Id", send.id); - }; - const r = await this.send( - "POST", - "/sends/" + send.id + "/access/file/" + send.file.id, - request, - false, - true, - apiUrl, - addSendIdHeader - ); - return new SendFileDownloadDataResponse(r); - } - - async getSends(): Promise> { - const r = await this.send("GET", "/sends", null, true, true); - return new ListResponse(r, SendResponse); - } - - async postSend(request: SendRequest): Promise { - const r = await this.send("POST", "/sends", request, true, true); - return new SendResponse(r); - } - - async postFileTypeSend(request: SendRequest): Promise { - const r = await this.send("POST", "/sends/file/v2", request, true, true); - return new SendFileUploadDataResponse(r); - } - - async renewSendFileUploadUrl( - sendId: string, - fileId: string - ): Promise { - const r = await this.send("GET", "/sends/" + sendId + "/file/" + fileId, null, true, true); - return new SendFileUploadDataResponse(r); - } - - postSendFile(sendId: string, fileId: string, data: FormData): Promise { - return this.send("POST", "/sends/" + sendId + "/file/" + fileId, data, true, false); - } - - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async postSendFileLegacy(data: FormData): Promise { - const r = await this.send("POST", "/sends/file", data, true, true); - return new SendResponse(r); - } - - async putSend(id: string, request: SendRequest): Promise { - const r = await this.send("PUT", "/sends/" + id, request, true, true); - return new SendResponse(r); - } - - async putSendRemovePassword(id: string): Promise { - const r = await this.send("PUT", "/sends/" + id + "/remove-password", null, true, true); - return new SendResponse(r); - } - - deleteSend(id: string): Promise { - return this.send("DELETE", "/sends/" + id, null, true, false); - } - // Cipher APIs async getCipher(id: string): Promise { diff --git a/libs/common/src/services/file-upload/file-upload.service.ts b/libs/common/src/services/file-upload/file-upload.service.ts new file mode 100644 index 0000000000..51be3f5a0f --- /dev/null +++ b/libs/common/src/services/file-upload/file-upload.service.ts @@ -0,0 +1,52 @@ +import { + FileUploadApiMethods, + FileUploadService as FileUploadServiceAbstraction, +} from "../../abstractions/file-upload/file-upload.service"; +import { LogService } from "../../abstractions/log.service"; +import { FileUploadType } from "../../enums/fileUploadType"; +import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; +import { EncString } from "../../models/domain/enc-string"; +import { AzureFileUploadService } from "../azureFileUpload.service"; +import { BitwardenFileUploadService } from "../bitwardenFileUpload.service"; + +export class FileUploadService implements FileUploadServiceAbstraction { + private azureFileUploadService: AzureFileUploadService; + private bitwardenFileUploadService: BitwardenFileUploadService; + + constructor(protected logService: LogService) { + this.azureFileUploadService = new AzureFileUploadService(logService); + this.bitwardenFileUploadService = new BitwardenFileUploadService(); + } + + async upload( + uploadData: { url: string; fileUploadType: FileUploadType }, + fileName: EncString, + encryptedFileData: EncArrayBuffer, + fileUploadMethods: FileUploadApiMethods + ) { + try { + switch (uploadData.fileUploadType) { + case FileUploadType.Direct: + await this.bitwardenFileUploadService.upload( + fileName.encryptedString, + encryptedFileData, + (fd) => fileUploadMethods.postDirect(fd) + ); + break; + case FileUploadType.Azure: { + await this.azureFileUploadService.upload( + uploadData.url, + encryptedFileData, + fileUploadMethods.renewFileUploadUrl + ); + break; + } + default: + throw new Error("Unknown file upload type"); + } + } catch (e) { + await fileUploadMethods.rollback(); + throw e; + } + } +} diff --git a/libs/common/src/services/fileUpload.service.ts b/libs/common/src/services/fileUpload.service.ts deleted file mode 100644 index f452ed2c0c..0000000000 --- a/libs/common/src/services/fileUpload.service.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { FileUploadService as FileUploadServiceAbstraction } from "../abstractions/fileUpload.service"; -import { LogService } from "../abstractions/log.service"; -import { FileUploadType } from "../enums/fileUploadType"; -import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; -import { EncString } from "../models/domain/enc-string"; -import { SendFileUploadDataResponse } from "../models/response/send-file-upload-data.response"; -import { AttachmentUploadDataResponse } from "../vault/models/response/attachment-upload-data.response"; - -import { AzureFileUploadService } from "./azureFileUpload.service"; -import { BitwardenFileUploadService } from "./bitwardenFileUpload.service"; - -export class FileUploadService implements FileUploadServiceAbstraction { - private azureFileUploadService: AzureFileUploadService; - private bitwardenFileUploadService: BitwardenFileUploadService; - - constructor(private logService: LogService, private apiService: ApiService) { - this.azureFileUploadService = new AzureFileUploadService(logService); - this.bitwardenFileUploadService = new BitwardenFileUploadService(); - } - - async uploadSendFile( - uploadData: SendFileUploadDataResponse, - fileName: EncString, - encryptedFileData: EncArrayBuffer - ) { - try { - switch (uploadData.fileUploadType) { - case FileUploadType.Direct: - await this.bitwardenFileUploadService.upload( - fileName.encryptedString, - encryptedFileData, - (fd) => - this.apiService.postSendFile( - uploadData.sendResponse.id, - uploadData.sendResponse.file.id, - fd - ) - ); - break; - case FileUploadType.Azure: { - const renewalCallback = async () => { - const renewalResponse = await this.apiService.renewSendFileUploadUrl( - uploadData.sendResponse.id, - uploadData.sendResponse.file.id - ); - return renewalResponse.url; - }; - await this.azureFileUploadService.upload( - uploadData.url, - encryptedFileData, - renewalCallback - ); - break; - } - default: - throw new Error("Unknown file upload type"); - } - } catch (e) { - await this.apiService.deleteSend(uploadData.sendResponse.id); - throw e; - } - } - - async uploadCipherAttachment( - admin: boolean, - uploadData: AttachmentUploadDataResponse, - encryptedFileName: EncString, - encryptedFileData: EncArrayBuffer - ) { - const response = admin ? uploadData.cipherMiniResponse : uploadData.cipherResponse; - try { - switch (uploadData.fileUploadType) { - case FileUploadType.Direct: - await this.bitwardenFileUploadService.upload( - encryptedFileName.encryptedString, - encryptedFileData, - (fd) => this.apiService.postAttachmentFile(response.id, uploadData.attachmentId, fd) - ); - break; - case FileUploadType.Azure: { - const renewalCallback = async () => { - const renewalResponse = await this.apiService.renewAttachmentUploadUrl( - response.id, - uploadData.attachmentId - ); - return renewalResponse.url; - }; - await this.azureFileUploadService.upload( - uploadData.url, - encryptedFileData, - renewalCallback - ); - break; - } - default: - throw new Error("Unknown file upload type."); - } - } catch (e) { - if (admin) { - await this.apiService.deleteCipherAttachmentAdmin(response.id, uploadData.attachmentId); - } else { - await this.apiService.deleteCipherAttachment(response.id, uploadData.attachmentId); - } - throw e; - } - } -} diff --git a/libs/common/src/services/send/send-api.service.ts b/libs/common/src/services/send/send-api.service.ts new file mode 100644 index 0000000000..917899112a --- /dev/null +++ b/libs/common/src/services/send/send-api.service.ts @@ -0,0 +1,251 @@ +import { SendType } from "../../../../common/src/enums/sendType"; +import { Utils } from "../../../../common/src/misc/utils"; +import { ErrorResponse } from "../../../../common/src/models/response/error.response"; +import { ApiService } from "../../abstractions/api.service"; +import { + FileUploadApiMethods, + FileUploadService, +} from "../../abstractions/file-upload/file-upload.service"; +import { SendApiService as SendApiServiceAbstraction } from "../../abstractions/send/send-api.service.abstraction"; +import { InternalSendService } from "../../abstractions/send/send.service.abstraction"; +import { SendData } from "../../models/data/send.data"; +import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; +import { Send } from "../../models/domain/send"; +import { SendAccessRequest } from "../../models/request/send-access.request"; +import { SendRequest } from "../../models/request/send.request"; +import { ListResponse } from "../../models/response/list.response"; +import { SendAccessResponse } from "../../models/response/send-access.response"; +import { SendFileDownloadDataResponse } from "../../models/response/send-file-download-data.response"; +import { SendFileUploadDataResponse } from "../../models/response/send-file-upload-data.response"; +import { SendResponse } from "../../models/response/send.response"; +import { SendAccessView } from "../../models/view/send-access.view"; + +export class SendApiService implements SendApiServiceAbstraction { + constructor( + private apiService: ApiService, + private fileUploadService: FileUploadService, + private sendService: InternalSendService + ) {} + + async getSend(id: string): Promise { + const r = await this.apiService.send("GET", "/sends/" + id, null, true, true); + return new SendResponse(r); + } + + async postSendAccess( + id: string, + request: SendAccessRequest, + apiUrl?: string + ): Promise { + const addSendIdHeader = (headers: Headers) => { + headers.set("Send-Id", id); + }; + const r = await this.apiService.send( + "POST", + "/sends/access/" + id, + request, + false, + true, + apiUrl, + addSendIdHeader + ); + return new SendAccessResponse(r); + } + + async getSendFileDownloadData( + send: SendAccessView, + request: SendAccessRequest, + apiUrl?: string + ): Promise { + const addSendIdHeader = (headers: Headers) => { + headers.set("Send-Id", send.id); + }; + const r = await this.apiService.send( + "POST", + "/sends/" + send.id + "/access/file/" + send.file.id, + request, + false, + true, + apiUrl, + addSendIdHeader + ); + return new SendFileDownloadDataResponse(r); + } + + async getSends(): Promise> { + const r = await this.apiService.send("GET", "/sends", null, true, true); + return new ListResponse(r, SendResponse); + } + + async postSend(request: SendRequest): Promise { + const r = await this.apiService.send("POST", "/sends", request, true, true); + return new SendResponse(r); + } + + async postFileTypeSend(request: SendRequest): Promise { + const r = await this.apiService.send("POST", "/sends/file/v2", request, true, true); + return new SendFileUploadDataResponse(r); + } + + async renewSendFileUploadUrl( + sendId: string, + fileId: string + ): Promise { + const r = await this.apiService.send( + "GET", + "/sends/" + sendId + "/file/" + fileId, + null, + true, + true + ); + return new SendFileUploadDataResponse(r); + } + + postSendFile(sendId: string, fileId: string, data: FormData): Promise { + return this.apiService.send("POST", "/sends/" + sendId + "/file/" + fileId, data, true, false); + } + + /** + * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. + * This method still exists for backward compatibility with old server versions. + */ + async postSendFileLegacy(data: FormData): Promise { + const r = await this.apiService.send("POST", "/sends/file", data, true, true); + return new SendResponse(r); + } + + async putSend(id: string, request: SendRequest): Promise { + const r = await this.apiService.send("PUT", "/sends/" + id, request, true, true); + return new SendResponse(r); + } + + async putSendRemovePassword(id: string): Promise { + const r = await this.apiService.send( + "PUT", + "/sends/" + id + "/remove-password", + null, + true, + true + ); + return new SendResponse(r); + } + + deleteSend(id: string): Promise { + return this.apiService.send("DELETE", "/sends/" + id, null, true, false); + } + + async save(sendData: [Send, EncArrayBuffer]): Promise { + const response = await this.upload(sendData); + + const data = new SendData(response); + await this.sendService.upsert(data); + } + + async delete(id: string): Promise { + await this.deleteSend(id); + await this.sendService.delete(id); + } + + async removePassword(id: string): Promise { + const response = await this.putSendRemovePassword(id); + const data = new SendData(response); + await this.sendService.upsert(data); + } + + // Send File Upload methoids + + private async upload(sendData: [Send, EncArrayBuffer]): Promise { + const request = new SendRequest(sendData[0], sendData[1]?.buffer.byteLength); + let response: SendResponse; + if (sendData[0].id == null) { + if (sendData[0].type === SendType.Text) { + response = await this.postSend(request); + } else { + try { + const uploadDataResponse = await this.postFileTypeSend(request); + response = uploadDataResponse.sendResponse; + await this.fileUploadService.upload( + uploadDataResponse, + sendData[0].file.fileName, + sendData[1], + this.generateMethods(uploadDataResponse, response) + ); + } catch (e) { + if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { + response = await this.legacyServerSendFileUpload(sendData, request); + } else if (e instanceof ErrorResponse) { + throw new Error((e as ErrorResponse).getSingleMessage()); + } else { + throw e; + } + } + } + sendData[0].id = response.id; + sendData[0].accessId = response.accessId; + } else { + response = await this.putSend(sendData[0].id, request); + } + return response; + } + + private generateMethods( + uploadData: SendFileUploadDataResponse, + response: SendResponse + ): FileUploadApiMethods { + return { + postDirect: this.generatePostDirectCallback(response), + renewFileUploadUrl: this.generateRenewFileUploadUrlCallback(response.id, response.file.id), + rollback: this.generateRollbackCallback(response.id), + }; + } + + private generatePostDirectCallback(sendResponse: SendResponse) { + return (data: FormData) => { + return this.postSendFile(sendResponse.id, sendResponse.file.id, data); + }; + } + + private generateRenewFileUploadUrlCallback(sendId: string, fileId: string) { + return async () => { + const renewResponse = await this.renewSendFileUploadUrl(sendId, fileId); + return renewResponse?.url; + }; + } + + private generateRollbackCallback(sendId: string) { + return () => { + return this.deleteSend(sendId); + }; + } + + /** + * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. + * This method still exists for backward compatibility with old server versions. + */ + async legacyServerSendFileUpload( + sendData: [Send, EncArrayBuffer], + request: SendRequest + ): Promise { + const fd = new FormData(); + try { + const blob = new Blob([sendData[1].buffer], { type: "application/octet-stream" }); + fd.append("model", JSON.stringify(request)); + fd.append("data", blob, sendData[0].file.fileName.encryptedString); + } catch (e) { + if (Utils.isNode && !Utils.isBrowser) { + fd.append("model", JSON.stringify(request)); + fd.append( + "data", + Buffer.from(sendData[1].buffer) as any, + { + filepath: sendData[0].file.fileName.encryptedString, + contentType: "application/octet-stream", + } as any + ); + } else { + throw e; + } + } + return await this.postSendFileLegacy(fd); + } +} diff --git a/libs/common/src/services/send.service.ts b/libs/common/src/services/send/send.service.ts similarity index 53% rename from libs/common/src/services/send.service.ts rename to libs/common/src/services/send/send.service.ts index a9acaab630..c30b750b44 100644 --- a/libs/common/src/services/send.service.ts +++ b/libs/common/src/services/send/send.service.ts @@ -1,37 +1,58 @@ -import { ApiService } from "../abstractions/api.service"; -import { CryptoService } from "../abstractions/crypto.service"; -import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; -import { FileUploadService } from "../abstractions/fileUpload.service"; -import { I18nService } from "../abstractions/i18n.service"; -import { SendService as SendServiceAbstraction } from "../abstractions/send.service"; -import { StateService } from "../abstractions/state.service"; -import { SEND_KDF_ITERATIONS } from "../enums/kdfType"; -import { SendType } from "../enums/sendType"; -import { Utils } from "../misc/utils"; -import { SendData } from "../models/data/send.data"; -import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; -import { EncString } from "../models/domain/enc-string"; -import { Send } from "../models/domain/send"; -import { SendFile } from "../models/domain/send-file"; -import { SendText } from "../models/domain/send-text"; -import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; -import { SendRequest } from "../models/request/send.request"; -import { ErrorResponse } from "../models/response/error.response"; -import { SendResponse } from "../models/response/send.response"; -import { SendView } from "../models/view/send.view"; +import { BehaviorSubject, concatMap } from "rxjs"; + +import { CryptoService } from "../../abstractions/crypto.service"; +import { CryptoFunctionService } from "../../abstractions/cryptoFunction.service"; +import { I18nService } from "../../abstractions/i18n.service"; +import { InternalSendService as InternalSendServiceAbstraction } from "../../abstractions/send/send.service.abstraction"; +import { StateService } from "../../abstractions/state.service"; +import { SEND_KDF_ITERATIONS } from "../../enums/kdfType"; +import { SendType } from "../../enums/sendType"; +import { Utils } from "../../misc/utils"; +import { SendData } from "../../models/data/send.data"; +import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; +import { EncString } from "../../models/domain/enc-string"; +import { Send } from "../../models/domain/send"; +import { SendFile } from "../../models/domain/send-file"; +import { SendText } from "../../models/domain/send-text"; +import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; +import { SendView } from "../../models/view/send.view"; + +export class SendService implements InternalSendServiceAbstraction { + protected _sends: BehaviorSubject = new BehaviorSubject([]); + protected _sendViews: BehaviorSubject = new BehaviorSubject([]); + + sends$ = this._sends.asObservable(); + sendViews$ = this._sendViews.asObservable(); -export class SendService implements SendServiceAbstraction { constructor( private cryptoService: CryptoService, - private apiService: ApiService, - private fileUploadService: FileUploadService, private i18nService: I18nService, private cryptoFunctionService: CryptoFunctionService, private stateService: StateService - ) {} + ) { + this.stateService.activeAccountUnlocked$ + .pipe( + concatMap(async (unlocked) => { + if (Utils.global.bitwardenContainerService == null) { + return; + } + + if (!unlocked) { + this._sends.next([]); + this._sendViews.next([]); + return; + } + + const data = await this.stateService.getEncryptedSends(); + + await this.updateObservables(data); + }) + ) + .subscribe(); + } async clearCache(): Promise { - await this.stateService.setDecryptedSends(null); + await this._sendViews.next([]); } async encrypt( @@ -87,7 +108,12 @@ export class SendService implements SendServiceAbstraction { return [send, fileData]; } - async get(id: string): Promise { + get(id: string): Send { + const sends = this._sends.getValue(); + return sends.find((send) => send.id === id); + } + + async getFromState(id: string): Promise { const sends = await this.stateService.getEncryptedSends(); // eslint-disable-next-line if (sends == null || !sends.hasOwnProperty(id)) { @@ -109,7 +135,7 @@ export class SendService implements SendServiceAbstraction { return response; } - async getAllDecrypted(): Promise { + async getAllDecryptedFromState(): Promise { let decSends = await this.stateService.getDecryptedSends(); if (decSends != null) { return decSends; @@ -134,79 +160,11 @@ export class SendService implements SendServiceAbstraction { return decSends; } - async saveWithServer(sendData: [Send, EncArrayBuffer]): Promise { - const request = new SendRequest(sendData[0], sendData[1]?.buffer.byteLength); - let response: SendResponse; - if (sendData[0].id == null) { - if (sendData[0].type === SendType.Text) { - response = await this.apiService.postSend(request); - } else { - try { - const uploadDataResponse = await this.apiService.postFileTypeSend(request); - response = uploadDataResponse.sendResponse; - - await this.fileUploadService.uploadSendFile( - uploadDataResponse, - sendData[0].file.fileName, - sendData[1] - ); - } catch (e) { - if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { - response = await this.legacyServerSendFileUpload(sendData, request); - } else if (e instanceof ErrorResponse) { - throw new Error((e as ErrorResponse).getSingleMessage()); - } else { - throw e; - } - } - } - sendData[0].id = response.id; - sendData[0].accessId = response.accessId; - } else { - response = await this.apiService.putSend(sendData[0].id, request); - } - - const data = new SendData(response); - await this.upsert(data); - } - - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async legacyServerSendFileUpload( - sendData: [Send, EncArrayBuffer], - request: SendRequest - ): Promise { - const fd = new FormData(); - try { - const blob = new Blob([sendData[1].buffer], { type: "application/octet-stream" }); - fd.append("model", JSON.stringify(request)); - fd.append("data", blob, sendData[0].file.fileName.encryptedString); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append("model", JSON.stringify(request)); - fd.append( - "data", - Buffer.from(sendData[1].buffer) as any, - { - filepath: sendData[0].file.fileName.encryptedString, - contentType: "application/octet-stream", - } as any - ); - } else { - throw e; - } - } - return await this.apiService.postSendFileLegacy(fd); - } - async upsert(send: SendData | SendData[]): Promise { let sends = await this.stateService.getEncryptedSends(); if (sends == null) { sends = {}; } - if (send instanceof SendData) { const s = send as SendData; sends[s.id] = s; @@ -219,14 +177,13 @@ export class SendService implements SendServiceAbstraction { await this.replace(sends); } - async replace(sends: { [id: string]: SendData }): Promise { - await this.stateService.setDecryptedSends(null); - await this.stateService.setEncryptedSends(sends); - } - - async clear(): Promise { - await this.stateService.setDecryptedSends(null); - await this.stateService.setEncryptedSends(null); + async clear(userId?: string): Promise { + if (userId == null || userId == (await this.stateService.getUserId())) { + this._sends.next([]); + this._sendViews.next([]); + } + await this.stateService.setDecryptedSends(null, { userId: userId }); + await this.stateService.setEncryptedSends(null, { userId: userId }); } async delete(id: string | string[]): Promise { @@ -249,15 +206,9 @@ export class SendService implements SendServiceAbstraction { await this.replace(sends); } - async deleteWithServer(id: string): Promise { - await this.apiService.deleteSend(id); - await this.delete(id); - } - - async removePasswordWithServer(id: string): Promise { - const response = await this.apiService.putSendRemovePassword(id); - const data = new SendData(response); - await this.upsert(data); + async replace(sends: { [id: string]: SendData }): Promise { + await this.updateObservables(sends); + await this.stateService.setEncryptedSends(sends); } private parseFile(send: Send, file: File, key: SymmetricCryptoKey): Promise { @@ -292,4 +243,21 @@ export class SendService implements SendServiceAbstraction { const encFileData = await this.cryptoService.encryptToBytes(data, key); return [encFileName, encFileData]; } + + private async updateObservables(sendsMap: { [id: string]: SendData }) { + const sends = Object.values(sendsMap || {}).map((f) => new Send(f)); + this._sends.next(sends); + + if (await this.cryptoService.hasKey()) { + this._sendViews.next(await this.decryptSends(sends)); + } + } + + private async decryptSends(sends: Send[]) { + const decryptSendPromises = sends.map((s) => s.decrypt()); + const decryptedSends = await Promise.all(decryptSendPromises); + + decryptedSends.sort(Utils.getSortFunction(this.i18nService, "name")); + return decryptedSends; + } } diff --git a/libs/common/src/vault/abstractions/file-upload/cipher-file-upload.service.ts b/libs/common/src/vault/abstractions/file-upload/cipher-file-upload.service.ts new file mode 100644 index 0000000000..a8bd35bb25 --- /dev/null +++ b/libs/common/src/vault/abstractions/file-upload/cipher-file-upload.service.ts @@ -0,0 +1,15 @@ +import { EncArrayBuffer } from "../../../models/domain/enc-array-buffer"; +import { EncString } from "../../../models/domain/enc-string"; +import { SymmetricCryptoKey } from "../../../models/domain/symmetric-crypto-key"; +import { Cipher } from "../../models/domain/cipher"; +import { CipherResponse } from "../../models/response/cipher.response"; + +export abstract class CipherFileUploadService { + upload: ( + cipher: Cipher, + encFileName: EncString, + encData: EncArrayBuffer, + admin: boolean, + dataEncKey: [SymmetricCryptoKey, EncString] + ) => Promise; +} diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index fc498d29ab..0a56748ece 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -4,7 +4,6 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { ApiService } from "../../abstractions/api.service"; import { CryptoService } from "../../abstractions/crypto.service"; import { EncryptService } from "../../abstractions/encrypt.service"; -import { FileUploadService } from "../../abstractions/fileUpload.service"; import { I18nService } from "../../abstractions/i18n.service"; import { LogService } from "../../abstractions/log.service"; import { SearchService } from "../../abstractions/search.service"; @@ -13,6 +12,7 @@ import { StateService } from "../../abstractions/state.service"; import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; import { EncString } from "../../models/domain/enc-string"; import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; +import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service"; import { Cipher } from "../models/domain/cipher"; import { CipherService } from "./cipher.service"; @@ -25,7 +25,7 @@ describe("Cipher Service", () => { let stateService: SubstituteOf; let settingsService: SubstituteOf; let apiService: SubstituteOf; - let fileUploadService: SubstituteOf; + let cipherFileUploadService: SubstituteOf; let i18nService: SubstituteOf; let searchService: SubstituteOf; let logService: SubstituteOf; @@ -38,7 +38,7 @@ describe("Cipher Service", () => { stateService = Substitute.for(); settingsService = Substitute.for(); apiService = Substitute.for(); - fileUploadService = Substitute.for(); + cipherFileUploadService = Substitute.for(); i18nService = Substitute.for(); searchService = Substitute.for(); logService = Substitute.for(); @@ -51,12 +51,12 @@ describe("Cipher Service", () => { cryptoService, settingsService, apiService, - fileUploadService, i18nService, () => searchService, logService, stateService, - encryptService + encryptService, + cipherFileUploadService ); }); @@ -67,8 +67,8 @@ describe("Cipher Service", () => { await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData); - fileUploadService + cipherFileUploadService .received(1) - .uploadCipherAttachment(Arg.any(), Arg.any(), new EncString(ENCRYPTED_TEXT), ENCRYPTED_BYTES); + .upload(Arg.any(), Arg.any(), ENCRYPTED_BYTES, Arg.any(), Arg.any()); }); }); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 60c8e59f97..8a9b186043 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -3,7 +3,6 @@ import { firstValueFrom } from "rxjs"; import { ApiService } from "../../abstractions/api.service"; import { CryptoService } from "../../abstractions/crypto.service"; import { EncryptService } from "../../abstractions/encrypt.service"; -import { FileUploadService } from "../../abstractions/fileUpload.service"; import { I18nService } from "../../abstractions/i18n.service"; import { LogService } from "../../abstractions/log.service"; import { SearchService } from "../../abstractions/search.service"; @@ -21,6 +20,7 @@ import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; import { ErrorResponse } from "../../models/response/error.response"; import { View } from "../../models/view/view"; import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; +import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service"; import { CipherType } from "../enums/cipher-type"; import { CipherData } from "../models/data/cipher.data"; import { Attachment } from "../models/domain/attachment"; @@ -33,7 +33,6 @@ import { LoginUri } from "../models/domain/login-uri"; import { Password } from "../models/domain/password"; import { SecureNote } from "../models/domain/secure-note"; import { SortedCiphersCache } from "../models/domain/sorted-ciphers-cache"; -import { AttachmentRequest } from "../models/request/attachment.request"; import { CipherBulkDeleteRequest } from "../models/request/cipher-bulk-delete.request"; import { CipherBulkMoveRequest } from "../models/request/cipher-bulk-move.request"; import { CipherBulkRestoreRequest } from "../models/request/cipher-bulk-restore.request"; @@ -58,12 +57,12 @@ export class CipherService implements CipherServiceAbstraction { private cryptoService: CryptoService, private settingsService: SettingsService, private apiService: ApiService, - private fileUploadService: FileUploadService, private i18nService: I18nService, private searchService: () => SearchService, private logService: LogService, private stateService: StateService, - private encryptService: EncryptService + private encryptService: EncryptService, + private cipherFileUploadService: CipherFileUploadService ) {} async getDecryptedCipherCache(): Promise { @@ -725,41 +724,13 @@ export class CipherService implements CipherServiceAbstraction { const dataEncKey = await this.cryptoService.makeEncKey(key); const encData = await this.cryptoService.encryptToBytes(data, dataEncKey[0]); - const request: AttachmentRequest = { - key: dataEncKey[1].encryptedString, - fileName: encFileName.encryptedString, - fileSize: encData.buffer.byteLength, - adminRequest: admin, - }; - - let response: CipherResponse; - try { - const uploadDataResponse = await this.apiService.postCipherAttachment(cipher.id, request); - response = admin ? uploadDataResponse.cipherMiniResponse : uploadDataResponse.cipherResponse; - await this.fileUploadService.uploadCipherAttachment( - admin, - uploadDataResponse, - encFileName, - encData - ); - } catch (e) { - if ( - (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) || - (e as ErrorResponse).statusCode === 405 - ) { - response = await this.legacyServerAttachmentFileUpload( - admin, - cipher.id, - encFileName, - encData, - dataEncKey[1] - ); - } else if (e instanceof ErrorResponse) { - throw new Error((e as ErrorResponse).getSingleMessage()); - } else { - throw e; - } - } + const response = await this.cipherFileUploadService.upload( + cipher, + encFileName, + encData, + admin, + dataEncKey + ); const cData = new CipherData(response, cipher.collectionIds); if (!admin) { @@ -768,52 +739,6 @@ export class CipherService implements CipherServiceAbstraction { return new Cipher(cData); } - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async legacyServerAttachmentFileUpload( - admin: boolean, - cipherId: string, - encFileName: EncString, - encData: EncArrayBuffer, - key: EncString - ) { - const fd = new FormData(); - try { - const blob = new Blob([encData.buffer], { type: "application/octet-stream" }); - fd.append("key", key.encryptedString); - fd.append("data", blob, encFileName.encryptedString); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append("key", key.encryptedString); - fd.append( - "data", - Buffer.from(encData.buffer) as any, - { - filepath: encFileName.encryptedString, - contentType: "application/octet-stream", - } as any - ); - } else { - throw e; - } - } - - let response: CipherResponse; - try { - if (admin) { - response = await this.apiService.postCipherAttachmentAdminLegacy(cipherId, fd); - } else { - response = await this.apiService.postCipherAttachmentLegacy(cipherId, fd); - } - } catch (e) { - throw new Error((e as ErrorResponse).getSingleMessage()); - } - - return response; - } - async saveCollectionsWithServer(cipher: Cipher): Promise { const request = new CipherCollectionsRequest(cipher.collectionIds); await this.apiService.putCipherCollections(cipher.id, request); diff --git a/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts b/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts new file mode 100644 index 0000000000..cbeaf988ef --- /dev/null +++ b/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts @@ -0,0 +1,157 @@ +import { ApiService } from "../../../abstractions/api.service"; +import { + FileUploadApiMethods, + FileUploadService, +} from "../../../abstractions/file-upload/file-upload.service"; +import { Utils } from "../../../misc/utils"; +import { EncArrayBuffer } from "../../../models/domain/enc-array-buffer"; +import { EncString } from "../../../models/domain/enc-string"; +import { SymmetricCryptoKey } from "../../../models/domain/symmetric-crypto-key"; +import { ErrorResponse } from "../../../models/response/error.response"; +import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "../../abstractions/file-upload/cipher-file-upload.service"; +import { Cipher } from "../../models/domain/cipher"; +import { AttachmentRequest } from "../../models/request/attachment.request"; +import { AttachmentUploadDataResponse } from "../../models/response/attachment-upload-data.response"; +import { CipherResponse } from "../../models/response/cipher.response"; + +export class CipherFileUploadService implements CipherFileUploadServiceAbstraction { + constructor(private apiService: ApiService, private fileUploadService: FileUploadService) {} + + async upload( + cipher: Cipher, + encFileName: EncString, + encData: EncArrayBuffer, + admin: boolean, + dataEncKey: [SymmetricCryptoKey, EncString] + ): Promise { + const request: AttachmentRequest = { + key: dataEncKey[1].encryptedString, + fileName: encFileName.encryptedString, + fileSize: encData.buffer.byteLength, + adminRequest: admin, + }; + + let response: CipherResponse; + try { + const uploadDataResponse = await this.apiService.postCipherAttachment(cipher.id, request); + response = admin ? uploadDataResponse.cipherMiniResponse : uploadDataResponse.cipherResponse; + await this.fileUploadService.upload( + uploadDataResponse, + encFileName, + encData, + this.generateMethods(uploadDataResponse, response, request.adminRequest) + ); + } catch (e) { + if ( + (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) || + (e as ErrorResponse).statusCode === 405 + ) { + response = await this.legacyServerAttachmentFileUpload( + request.adminRequest, + cipher.id, + encFileName, + encData, + dataEncKey[1] + ); + } else if (e instanceof ErrorResponse) { + throw new Error((e as ErrorResponse).getSingleMessage()); + } else { + throw e; + } + } + return response; + } + + private generateMethods( + uploadData: AttachmentUploadDataResponse, + response: CipherResponse, + isAdmin: boolean + ): FileUploadApiMethods { + return { + postDirect: this.generatePostDirectCallback(uploadData, isAdmin), + renewFileUploadUrl: this.generateRenewFileUploadUrlCallback(uploadData, response, isAdmin), + rollback: this.generateRollbackCallback(response, uploadData, isAdmin), + }; + } + + private generatePostDirectCallback(uploadData: AttachmentUploadDataResponse, isAdmin: boolean) { + return (data: FormData) => { + const response = isAdmin ? uploadData.cipherMiniResponse : uploadData.cipherResponse; + return this.apiService.postAttachmentFile(response.id, uploadData.attachmentId, data); + }; + } + + private generateRenewFileUploadUrlCallback( + uploadData: AttachmentUploadDataResponse, + response: CipherResponse, + isAdmin: boolean + ) { + return async () => { + const renewResponse = await this.apiService.renewAttachmentUploadUrl( + response.id, + uploadData.attachmentId + ); + return renewResponse?.url; + }; + } + + private generateRollbackCallback( + response: CipherResponse, + uploadData: AttachmentUploadDataResponse, + isAdmin: boolean + ) { + return () => { + if (isAdmin) { + return this.apiService.deleteCipherAttachmentAdmin(response.id, uploadData.attachmentId); + } else { + return this.apiService.deleteCipherAttachment(response.id, uploadData.attachmentId); + } + }; + } + + /** + * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. + * This method still exists for backward compatibility with old server versions. + */ + async legacyServerAttachmentFileUpload( + admin: boolean, + cipherId: string, + encFileName: EncString, + encData: EncArrayBuffer, + key: EncString + ) { + const fd = new FormData(); + try { + const blob = new Blob([encData.buffer], { type: "application/octet-stream" }); + fd.append("key", key.encryptedString); + fd.append("data", blob, encFileName.encryptedString); + } catch (e) { + if (Utils.isNode && !Utils.isBrowser) { + fd.append("key", key.encryptedString); + fd.append( + "data", + Buffer.from(encData.buffer) as any, + { + filepath: encFileName.encryptedString, + contentType: "application/octet-stream", + } as any + ); + } else { + throw e; + } + } + + let response: CipherResponse; + try { + if (admin) { + response = await this.apiService.postCipherAttachmentAdminLegacy(cipherId, fd); + } else { + response = await this.apiService.postCipherAttachmentLegacy(cipherId, fd); + } + } catch (e) { + throw new Error((e as ErrorResponse).getSingleMessage()); + } + + return response; + } +} diff --git a/libs/common/src/vault/services/sync/sync.service.ts b/libs/common/src/vault/services/sync/sync.service.ts index e9a76c19cf..88ec0b8548 100644 --- a/libs/common/src/vault/services/sync/sync.service.ts +++ b/libs/common/src/vault/services/sync/sync.service.ts @@ -2,7 +2,8 @@ import { ApiService } from "../../../abstractions/api.service"; import { CryptoService } from "../../../abstractions/crypto.service"; import { LogService } from "../../../abstractions/log.service"; import { MessagingService } from "../../../abstractions/messaging.service"; -import { SendService } from "../../../abstractions/send.service"; +import { SendApiService } from "../../../abstractions/send/send-api.service.abstraction"; +import { InternalSendService } from "../../../abstractions/send/send.service.abstraction"; import { SettingsService } from "../../../abstractions/settings.service"; import { StateService } from "../../../abstractions/state.service"; import { CollectionService } from "../../../admin-console/abstractions/collection.service"; @@ -47,13 +48,14 @@ export class SyncService implements SyncServiceAbstraction { private collectionService: CollectionService, private messagingService: MessagingService, private policyService: InternalPolicyService, - private sendService: SendService, + private sendService: InternalSendService, private logService: LogService, private keyConnectorService: KeyConnectorService, private stateService: StateService, private providerService: ProviderService, private folderApiService: FolderApiServiceAbstraction, private organizationService: InternalOrganizationService, + private sendApiService: SendApiService, private logoutCallback: (expired: boolean) => Promise ) {} @@ -230,12 +232,12 @@ export class SyncService implements SyncServiceAbstraction { this.syncStarted(); if (await this.stateService.getIsAuthenticated()) { try { - const localSend = await this.sendService.get(notification.id); + const localSend = this.sendService.get(notification.id); if ( (!isEdit && localSend == null) || (isEdit && localSend != null && localSend.revisionDate < notification.revisionDate) ) { - const remoteSend = await this.apiService.getSend(notification.id); + const remoteSend = await this.sendApiService.getSend(notification.id); if (remoteSend != null) { await this.sendService.upsert(new SendData(remoteSend)); this.messagingService.send("syncedUpsertedSend", { sendId: notification.id });