2021-04-23 21:04:11 +02:00
|
|
|
import * as chalk from 'chalk';
|
2021-02-03 18:44:33 +01:00
|
|
|
import * as program from 'commander';
|
|
|
|
import * as fs from 'fs';
|
|
|
|
import * as path from 'path';
|
|
|
|
|
|
|
|
import { Response } from 'jslib/cli/models/response';
|
|
|
|
|
|
|
|
import { SendType } from 'jslib/enums/sendType';
|
|
|
|
|
|
|
|
import { Utils } from 'jslib/misc/utils';
|
|
|
|
|
|
|
|
import { GetCommand } from './commands/get.command';
|
|
|
|
import { SendCreateCommand } from './commands/send/create.command';
|
|
|
|
import { SendDeleteCommand } from './commands/send/delete.command';
|
|
|
|
import { SendEditCommand } from './commands/send/edit.command';
|
|
|
|
import { SendGetCommand } from './commands/send/get.command';
|
|
|
|
import { SendListCommand } from './commands/send/list.command';
|
|
|
|
import { SendReceiveCommand } from './commands/send/receive.command';
|
|
|
|
import { SendRemovePasswordCommand } from './commands/send/removePassword.command';
|
|
|
|
|
|
|
|
import { SendFileResponse } from './models/response/sendFileResponse';
|
|
|
|
import { SendResponse } from './models/response/sendResponse';
|
|
|
|
import { SendTextResponse } from './models/response/sendTextResponse';
|
|
|
|
|
|
|
|
import { Main } from './bw';
|
|
|
|
import { Program } from './program';
|
|
|
|
import { CliUtils } from './utils';
|
|
|
|
|
|
|
|
const writeLn = CliUtils.writeLn;
|
|
|
|
|
|
|
|
export class SendProgram extends Program {
|
|
|
|
constructor(main: Main) {
|
|
|
|
super(main);
|
|
|
|
}
|
|
|
|
|
2021-04-12 17:13:00 +02:00
|
|
|
async register() {
|
2021-02-03 18:44:33 +01:00
|
|
|
program.addCommand(this.sendCommand());
|
|
|
|
// receive is accessible both at `bw receive` and `bw send receive`
|
|
|
|
program.addCommand(this.receiveCommand());
|
|
|
|
}
|
|
|
|
|
|
|
|
private sendCommand(): program.Command {
|
|
|
|
return new program.Command('send')
|
|
|
|
.arguments('<data>')
|
|
|
|
.description('Work with Bitwarden sends. A Send can be quickly created using this command or subcommands can be used to fine-tune the Send', {
|
2021-02-08 21:11:58 +01:00
|
|
|
data: 'The data to Send. Specify as a filepath with the --file option',
|
2021-02-03 18:44:33 +01:00
|
|
|
})
|
|
|
|
.option('-f, --file', 'Specifies that <data> is a filepath')
|
|
|
|
.option('-d, --deleteInDays <days>', 'The number of days in the future to set deletion date, defaults to 7', '7')
|
2021-05-11 21:55:04 +02:00
|
|
|
.option('-a, --maxAccessCount <amount>', 'The amount of max possible accesses.')
|
2021-02-03 18:44:33 +01:00
|
|
|
.option('--hidden', 'Hide <data> in web by default. Valid only if --file is not set.')
|
|
|
|
.option('-n, --name <name>', 'The name of the Send. Defaults to a guid for text Sends and the filename for files.')
|
|
|
|
.option('--notes <notes>', 'Notes to add to the Send.')
|
|
|
|
.option('--fullObject', 'Specifies that the full Send object should be returned rather than just the access url.')
|
|
|
|
.addCommand(this.listCommand())
|
|
|
|
.addCommand(this.templateCommand())
|
|
|
|
.addCommand(this.getCommand())
|
|
|
|
.addCommand(this.receiveCommand())
|
|
|
|
.addCommand(this.createCommand())
|
|
|
|
.addCommand(this.editCommand())
|
|
|
|
.addCommand(this.removePasswordCommand())
|
|
|
|
.addCommand(this.deleteCommand())
|
|
|
|
.action(async (data: string, options: program.OptionValues) => {
|
|
|
|
const encodedJson = this.makeSendJson(data, options);
|
|
|
|
|
|
|
|
let response: Response;
|
|
|
|
if (encodedJson instanceof Response) {
|
|
|
|
response = encodedJson;
|
|
|
|
} else {
|
|
|
|
response = await this.runCreate(encodedJson, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.processResponse(response);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private receiveCommand(): program.Command {
|
|
|
|
return new program.Command('receive')
|
|
|
|
.arguments('<url>')
|
|
|
|
.description('Access a Bitwarden Send from a url')
|
|
|
|
.option('--password <password>', 'Password needed to access the Send.')
|
|
|
|
.option('--passwordenv <passwordenv>', 'Environment variable storing the Send\'s password')
|
|
|
|
.option('--passwordfile <passwordfile>', 'Path to a file containing the Send\s password as its first line')
|
|
|
|
.option('--obj', 'Return the Send\'s json object rather than the Send\'s content')
|
2021-04-14 18:09:47 +02:00
|
|
|
.option('--output <location>', 'Specify a file path to save a File-type Send to')
|
2021-02-03 18:44:33 +01:00
|
|
|
.on('--help', () => {
|
|
|
|
writeLn('');
|
|
|
|
writeLn('If a password is required, the provided password is used or the user is prompted.');
|
|
|
|
writeLn('', true);
|
|
|
|
})
|
|
|
|
.action(async (url: string, options: program.OptionValues) => {
|
|
|
|
const cmd = new SendReceiveCommand(this.main.apiService, this.main.cryptoService,
|
|
|
|
this.main.cryptoFunctionService, this.main.platformUtilsService, this.main.environmentService);
|
|
|
|
const response = await cmd.run(url, options);
|
|
|
|
this.processResponse(response);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private listCommand(): program.Command {
|
|
|
|
return new program.Command('list')
|
|
|
|
|
|
|
|
.description('List all the Sends owned by you')
|
2021-04-23 21:04:11 +02:00
|
|
|
.on('--help', () => { writeLn(chalk('This is in the list command')); })
|
2021-02-03 18:44:33 +01:00
|
|
|
.action(async (options: program.OptionValues) => {
|
|
|
|
await this.exitIfLocked();
|
|
|
|
const cmd = new SendListCommand(this.main.sendService, this.main.environmentService,
|
|
|
|
this.main.searchService);
|
|
|
|
const response = await cmd.run(options);
|
|
|
|
this.processResponse(response);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private templateCommand(): program.Command {
|
|
|
|
return new program.Command('template')
|
|
|
|
.arguments('<object>')
|
|
|
|
.description('Get json templates for send objects', {
|
2021-02-08 21:11:58 +01:00
|
|
|
object: 'Valid objects are: send, send.text, send.file',
|
2021-02-03 18:44:33 +01:00
|
|
|
})
|
2021-02-04 05:51:59 +01:00
|
|
|
.action(async object => {
|
2021-02-03 18:44:33 +01:00
|
|
|
const cmd = new GetCommand(this.main.cipherService, this.main.folderService,
|
|
|
|
this.main.collectionService, this.main.totpService, this.main.auditService, this.main.cryptoService,
|
|
|
|
this.main.userService, this.main.searchService, this.main.apiService, this.main.sendService,
|
|
|
|
this.main.environmentService);
|
|
|
|
const response = await cmd.run('template', object, null);
|
|
|
|
this.processResponse(response);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private getCommand(): program.Command {
|
|
|
|
return new program.Command('get')
|
|
|
|
.arguments('<id>')
|
|
|
|
.description('Get Sends owned by you.')
|
|
|
|
.option('--output <output>', 'Output directory or filename for attachment.')
|
|
|
|
.option('--text', 'Specifies to return the text content of a Send')
|
|
|
|
.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 get send searchText');
|
|
|
|
writeLn(' bw get send id');
|
|
|
|
writeLn(' bw get send searchText --text');
|
|
|
|
writeLn(' bw get send searchText --file');
|
|
|
|
writeLn(' bw get send searchText --file --output ../Photos/photo.jpg');
|
|
|
|
writeLn(' bw get send searchText --file --raw');
|
|
|
|
writeLn('', true);
|
|
|
|
})
|
|
|
|
.action(async (id: string, options: program.OptionValues) => {
|
|
|
|
await this.exitIfLocked();
|
|
|
|
const cmd = new SendGetCommand(this.main.sendService, this.main.environmentService,
|
|
|
|
this.main.searchService, this.main.cryptoService);
|
|
|
|
const response = await cmd.run(id, options);
|
|
|
|
this.processResponse(response);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private createCommand(): program.Command {
|
|
|
|
return new program.Command('create')
|
|
|
|
.arguments('[encodedJson]')
|
|
|
|
.description('create a Send', {
|
|
|
|
encodedJson: 'JSON object to upload. Can also be piped in through stdin.',
|
|
|
|
})
|
|
|
|
.option('--file <path>', 'file to Send. Can also be specified in parent\'s JSON.')
|
|
|
|
.option('--text <text>', 'text to Send. Can also be specified in parent\'s JSON.')
|
|
|
|
.option('--hidden', 'text hidden flag. Valid only with the --text option.')
|
|
|
|
.option('--password <password>', 'optional password to access this Send. Can also be specified in JSON')
|
|
|
|
.on('--help', () => {
|
|
|
|
writeLn('');
|
|
|
|
writeLn('Note:');
|
|
|
|
writeLn(' Options specified in JSON take precedence over command options');
|
|
|
|
writeLn('', true);
|
|
|
|
})
|
2021-05-11 21:56:38 +02:00
|
|
|
.action(async (encodedJson: string, options: program.OptionValues, args: { parent: program.Command }) => {
|
|
|
|
// Work-around to support `--fullObject` option for `send create --fullObject`
|
|
|
|
// Calling `option('--fullObject', ...)` above won't work due to Commander doesn't like same option
|
|
|
|
// to be defind on both parent-command and sub-command
|
|
|
|
const { fullObject = false } = args.parent.opts();
|
|
|
|
const mergedOptions = {
|
|
|
|
...options,
|
2021-05-14 14:30:26 +02:00
|
|
|
fullObject: fullObject,
|
2021-05-11 21:56:38 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const response = await this.runCreate(encodedJson, mergedOptions);
|
2021-02-03 18:44:33 +01:00
|
|
|
this.processResponse(response);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private editCommand(): program.Command {
|
|
|
|
return new program.Command('edit')
|
|
|
|
.arguments('[encodedJson]')
|
|
|
|
.description('edit a Send', {
|
2021-02-08 21:11:58 +01:00
|
|
|
encodedJson: 'Updated JSON object to save. If not provided, encodedJson is read from stdin.',
|
2021-02-03 18:44:33 +01:00
|
|
|
})
|
|
|
|
.option('--itemid <itemid>', 'Overrides the itemId provided in [encodedJson]')
|
|
|
|
.on('--help', () => {
|
|
|
|
writeLn('');
|
|
|
|
writeLn('Note:');
|
|
|
|
writeLn(' You cannot update a File-type Send\'s file. Just delete and remake it');
|
|
|
|
writeLn('', true);
|
|
|
|
})
|
|
|
|
.action(async (encodedJson: string, options: program.OptionValues) => {
|
|
|
|
await this.exitIfLocked();
|
2021-04-20 21:55:04 +02:00
|
|
|
const getCmd = new SendGetCommand(this.main.sendService, this.main.environmentService,
|
|
|
|
this.main.searchService, this.main.cryptoService);
|
|
|
|
const cmd = new SendEditCommand(this.main.sendService, this.main.userService, getCmd);
|
2021-02-03 18:44:33 +01:00
|
|
|
const response = await cmd.run(encodedJson, options);
|
|
|
|
this.processResponse(response);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private deleteCommand(): program.Command {
|
|
|
|
return new program.Command('delete')
|
|
|
|
.arguments('<id>')
|
|
|
|
.description('delete a Send', {
|
2021-02-08 21:11:58 +01:00
|
|
|
id: 'The id of the Send to delete.',
|
2021-02-03 18:44:33 +01:00
|
|
|
})
|
|
|
|
.action(async (id: string) => {
|
|
|
|
await this.exitIfLocked();
|
|
|
|
const cmd = new SendDeleteCommand(this.main.sendService);
|
|
|
|
const response = await cmd.run(id);
|
|
|
|
this.processResponse(response);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private removePasswordCommand(): program.Command {
|
|
|
|
return new program.Command('remove-password')
|
|
|
|
.arguments('<id>')
|
|
|
|
.description('removes the saved password from a Send.', {
|
2021-02-08 21:11:58 +01:00
|
|
|
id: 'The id of the Send to alter.',
|
2021-02-03 18:44:33 +01:00
|
|
|
})
|
|
|
|
.action(async (id: string) => {
|
|
|
|
await this.exitIfLocked();
|
|
|
|
const cmd = new SendRemovePasswordCommand(this.main.sendService);
|
|
|
|
const response = await cmd.run(id);
|
|
|
|
this.processResponse(response);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private makeSendJson(data: string, options: program.OptionValues) {
|
|
|
|
let sendFile = null;
|
|
|
|
let sendText = null;
|
|
|
|
let name = Utils.newGuid();
|
|
|
|
let type = SendType.Text;
|
|
|
|
if (options.file != null) {
|
|
|
|
data = path.resolve(data);
|
|
|
|
if (!fs.existsSync(data)) {
|
|
|
|
return Response.badRequest('data path does not exist');
|
|
|
|
}
|
|
|
|
|
|
|
|
sendFile = SendFileResponse.template(data);
|
|
|
|
name = path.basename(data);
|
|
|
|
type = SendType.File;
|
|
|
|
} else {
|
|
|
|
sendText = SendTextResponse.template(data, options.hidden);
|
|
|
|
}
|
|
|
|
|
2021-02-26 19:47:11 +01:00
|
|
|
const template = Utils.assign(SendResponse.template(null, options.deleteInDays), {
|
2021-02-03 18:44:33 +01:00
|
|
|
name: options.name ?? name,
|
|
|
|
notes: options.notes,
|
|
|
|
file: sendFile,
|
|
|
|
text: sendText,
|
2021-02-08 21:11:58 +01:00
|
|
|
type: type,
|
2021-02-03 18:44:33 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
return Buffer.from(JSON.stringify(template), 'utf8').toString('base64');
|
|
|
|
}
|
|
|
|
|
|
|
|
private async runCreate(encodedJson: string, options: program.OptionValues) {
|
|
|
|
await this.exitIfLocked();
|
|
|
|
const cmd = new SendCreateCommand(this.main.sendService, this.main.userService,
|
|
|
|
this.main.environmentService);
|
|
|
|
return await cmd.run(encodedJson, options);
|
|
|
|
}
|
|
|
|
}
|