From 5f7e7aef20bd4479c8262d0dc8f8f208317107e3 Mon Sep 17 00:00:00 2001 From: voommen-livefront Date: Thu, 27 Mar 2025 15:56:51 -0500 Subject: [PATCH] PM-919 created service send-download --- apps/cli/src/oss-serve-configurator.ts | 1 + .../service-container/service-container.ts | 12 ++++ .../src/tools/send/commands/get.command.ts | 6 ++ apps/cli/src/tools/send/send.program.ts | 68 ++++--------------- .../send-download.service.ts} | 53 ++------------- 5 files changed, 37 insertions(+), 103 deletions(-) rename apps/cli/src/tools/send/{commands/send-download.command.ts => services/send-download.service.ts} (81%) diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index dec09447839..35559879000 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -164,6 +164,7 @@ export class OssServeConfigurator { this.serviceContainer.searchService, this.serviceContainer.encryptService, this.serviceContainer.apiService, + this.serviceContainer.sendDownloadService, ); this.sendEditCommand = new SendEditCommand( this.serviceContainer.sendService, diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index b7f423e8ff7..f1ce40230aa 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -185,6 +185,7 @@ import { I18nService } from "../platform/services/i18n.service"; import { LowdbStorageService } from "../platform/services/lowdb-storage.service"; import { NodeApiService } from "../platform/services/node-api.service"; import { NodeEnvSecureStorageService } from "../platform/services/node-env-secure-storage.service"; +import { SendDownloadService } from "../tools/send/services/send-download.service"; // Polyfills global.DOMParser = new jsdom.JSDOM().window.DOMParser; @@ -283,6 +284,7 @@ export class ServiceContainer { cipherAuthorizationService: CipherAuthorizationService; ssoUrlService: SsoUrlService; masterPasswordApiService: MasterPasswordApiServiceAbstraction; + sendDownloadService: SendDownloadService; constructor() { let p = null; @@ -849,6 +851,16 @@ export class ServiceContainer { ); this.masterPasswordApiService = new MasterPasswordApiService(this.apiService, this.logService); + + this.sendDownloadService = new SendDownloadService( + this.environmentService, + this.encryptService, + this.apiService, + this.platformUtilsService, + this.keyService, + this.cryptoFunctionService, + this.sendApiService, + ); } async logout() { diff --git a/apps/cli/src/tools/send/commands/get.command.ts b/apps/cli/src/tools/send/commands/get.command.ts index 9cfae6bbbd6..93f0b8e5e65 100644 --- a/apps/cli/src/tools/send/commands/get.command.ts +++ b/apps/cli/src/tools/send/commands/get.command.ts @@ -14,6 +14,7 @@ import { SendService } from "@bitwarden/common/tools/send/services/send.service. import { DownloadCommand } from "../../../commands/download.command"; import { Response } from "../../../models/response"; import { SendResponse } from "../models/send.response"; +import { SendDownloadService } from "../services/send-download.service"; export class SendGetCommand extends DownloadCommand { constructor( @@ -22,6 +23,7 @@ export class SendGetCommand extends DownloadCommand { private searchService: SearchService, encryptService: EncryptService, apiService: ApiService, + private sendDownloadService: SendDownloadService, ) { super(encryptService, apiService); } @@ -67,6 +69,10 @@ export class SendGetCommand extends DownloadCommand { } } + if (options?.file || options?.output || options?.raw) { + return this.sendDownloadService.download(sends, options); + } + return selector(sends); } diff --git a/apps/cli/src/tools/send/send.program.ts b/apps/cli/src/tools/send/send.program.ts index 5a366eb49e9..c299bfd85cf 100644 --- a/apps/cli/src/tools/send/send.program.ts +++ b/apps/cli/src/tools/send/send.program.ts @@ -23,7 +23,6 @@ import { SendReceiveCommand, SendRemovePasswordCommand, } from "./commands"; -import { SendDownloadCommand } from "./commands/send-download.command"; import { SendFileResponse } from "./models/send-file.response"; import { SendTextResponse } from "./models/send-text.response"; import { SendResponse } from "./models/send.response"; @@ -68,7 +67,6 @@ export class SendProgram extends BaseProgram { .addCommand(this.editCommand()) .addCommand(this.removePasswordCommand()) .addCommand(this.deleteCommand()) - .addCommand(this.downloadCommand()) .action(async (data: string, options: OptionValues) => { const encodedJson = this.makeSendJson(data, options); @@ -161,64 +159,19 @@ export class SendProgram extends BaseProgram { }); } - private downloadCommand(): Command { - return ( - new Command("download") - .arguments("") - .description("Downloads file attached to the send owned by you.") - .option("--file", "Specifies to return the file content of a Send", true) - .option("--password ", "Password needed to access the Send.") - .option( - "--passwordfile ", - "Path to a file containing the Sends password as its first line", - ) - // .option("--obj", "Return the Send's json object rather than the Send's content") - .option("--output ", "Specify a file path to save a File-type Send to") - .option("--raw", "Return the raw content of a Send", true) - .on("--help", () => { - writeLn(""); - writeLn(" Id:"); - writeLn(""); - writeLn(" Search term or Send's globally unique `id`."); - writeLn(""); - writeLn( - " If raw output is specified and no output filename or directory is given for", - ); - writeLn(" an attachment query, the attachment content is written to stdout."); - writeLn(""); - writeLn(" Examples:"); - writeLn(""); - writeLn(" bw send download searchText"); - writeLn(" bw send download id"); - writeLn(" bw send download id --file"); - writeLn(" bw send download id --file --output ../Photos/photo.jpg"); - writeLn(" bw send download id --file --raw"); - writeLn("", true); - }) - .action(async (id: string, options: OptionValues) => { - await this.exitIfLocked(); - const cmd = new SendDownloadCommand( - this.serviceContainer.sendService, - this.serviceContainer.environmentService, - this.serviceContainer.searchService, - this.serviceContainer.encryptService, - this.serviceContainer.apiService, - this.serviceContainer.platformUtilsService, - this.serviceContainer.keyService, - this.serviceContainer.cryptoFunctionService, - this.serviceContainer.sendApiService, - ); - const response = await cmd.run(id, options); - this.processResponse(response); - }) - ); - } private getCommand(): Command { return new Command("get") .arguments("") .description("Get Sends owned by you.") - .option("--output ", "Output directory or filename for attachment.") .option("--text", "Specifies to return the text content of a Send") + .option("--file", "Specifies to return the file content of a Send", true) + .option("--password ", "Password needed to access the Send.") + .option( + "--passwordfile ", + "Path to a file containing the Sends password as its first line", + ) + .option("--raw", "Return the raw content of a Send", true) + .option("--output ", "Output directory or filename for attachment.") .on("--help", () => { writeLn(""); writeLn(" Id:"); @@ -233,6 +186,9 @@ export class SendProgram extends BaseProgram { writeLn(" bw send get searchText"); writeLn(" bw send get id"); writeLn(" bw send get searchText --text"); + writeLn(" bw send download id --file"); + writeLn(" bw send download id --output ../Photos/photo.jpg"); + writeLn(" bw send download id --file --raw"); writeLn("", true); }) .action(async (id: string, options: OptionValues) => { @@ -243,6 +199,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.searchService, this.serviceContainer.encryptService, this.serviceContainer.apiService, + this.serviceContainer.sendDownloadService, ); const response = await cmd.run(id, options); this.processResponse(response); @@ -303,6 +260,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.searchService, this.serviceContainer.encryptService, this.serviceContainer.apiService, + this.serviceContainer.sendDownloadService, ); const cmd = new SendEditCommand( this.serviceContainer.sendService, diff --git a/apps/cli/src/tools/send/commands/send-download.command.ts b/apps/cli/src/tools/send/services/send-download.service.ts similarity index 81% rename from apps/cli/src/tools/send/commands/send-download.command.ts rename to apps/cli/src/tools/send/services/send-download.service.ts index 51b2589ccb8..2519edc65a5 100644 --- a/apps/cli/src/tools/send/commands/send-download.command.ts +++ b/apps/cli/src/tools/send/services/send-download.service.ts @@ -5,7 +5,6 @@ import * as inquirer from "inquirer"; import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -19,7 +18,6 @@ import { SendAccessRequest } from "@bitwarden/common/tools/send/models/request/s import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; -import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { KeyService } from "@bitwarden/key-management"; import { NodeUtils } from "@bitwarden/node/node-utils"; @@ -28,13 +26,12 @@ import { Response } from "../../../models/response"; import { SendAccessResponse } from "../models/send-access.response"; import { SendResponse } from "../models/send.response"; -export class SendDownloadCommand extends DownloadCommand { +// Note: DownloadCommand is actually an abstract class +export class SendDownloadService extends DownloadCommand { private decKey: SymmetricCryptoKey; constructor( - private sendService: SendService, protected environmentService: EnvironmentService, - private searchService: SearchService, encryptService: EncryptService, apiService: ApiService, protected platformUtilsService: PlatformUtilsService, @@ -45,38 +42,14 @@ export class SendDownloadCommand extends DownloadCommand { super(encryptService, apiService); } - async run(id: string, options: OptionValues) { - const serveCommand = process.env.BW_SERVE === "true"; - if (serveCommand && !Utils.isGuid(id)) { - return Response.badRequest("`" + id + "` is not a GUID."); - } - - let sends = await this.getSendView(id); - if (sends == null) { - return Response.notFound(); - } - + async download(sends: SendView, options: OptionValues) { const env = await firstValueFrom(this.environmentService.environment$); const webVaultUrl = env.getWebVaultUrl(); const selector = async (s: SendView): Promise => Response.success(new SendResponse(s, webVaultUrl)); - // we have multiple results. Attempt to narrow down the results - if (Array.isArray(sends)) { - // if we have multiple results, return the ids - if (sends.length > 1) { - return Response.multipleResults(sends.map((s) => s.id)); - } - - // if we greater than zero results pick the first one - if (sends.length > 0) { - sends = sends[0]; - } else { - return Response.notFound(); - } - } - + // only attempt to download if requested if (options?.file || options?.output || options?.raw) { // generate the send url const sendWithUrl = new SendResponse(sends, webVaultUrl); @@ -142,6 +115,7 @@ export class SendDownloadCommand extends DownloadCommand { return selector(sends); } + private createUrlObject(url: string): URL | null { try { return new URL(url); @@ -160,23 +134,6 @@ export class SendDownloadCommand extends DownloadCommand { return Utils.fromBufferToB64(passwordHash); } - private async getSendView(id: string): Promise { - if (Utils.isGuid(id)) { - const send = await this.sendService.getFromState(id); - if (send != null) { - return await send.decrypt(); - } - } else if (id.trim() !== "") { - let sends = await this.sendService.getAllDecryptedFromState(); - sends = this.searchService.searchSends(sends, id); - if (sends.length > 1) { - return sends; - } else if (sends.length > 0) { - return sends[0]; - } - } - } - private async sendRequest( url: string, id: string,