get items attachment command
This commit is contained in:
parent
05535cc134
commit
df024379c8
2
jslib
2
jslib
|
@ -1 +1 @@
|
|||
Subproject commit ba10d0704212f2bc8fabf0d3d6ebb552fd183401
|
||||
Subproject commit ed89dfaba70b60925817a0ce6f0c179b3f8bd2fb
|
|
@ -40,6 +40,15 @@
|
|||
"integrity": "sha512-MFFKFv2X4iZy/NFl1m1E8uwE1CR96SGwJjgHma09PLtqOWoj3nqeJHMG+P/EuJGVLvC2I6MdQRQsr4TcRduIow==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node-fetch": {
|
||||
"version": "1.6.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-1.6.9.tgz",
|
||||
"integrity": "sha512-n2r6WLoY7+uuPT7pnEtKJCmPUGyJ+cbyBR8Avnu4+m1nzz7DwBVuyIvvlBzCZ/nrpC7rIgb3D6pNavL7rFEa9g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "10.0.8"
|
||||
}
|
||||
},
|
||||
"@types/node-forge": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-0.7.4.tgz",
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
"@types/lowdb": "^1.0.1",
|
||||
"@types/lunr": "^2.1.5",
|
||||
"@types/node": "^10.0.8",
|
||||
"@types/node-fetch": "^1.6.9",
|
||||
"@types/node-forge": "^0.7.1",
|
||||
"@types/papaparse": "4.1.31",
|
||||
"@types/readline-sync": "^1.4.3",
|
||||
|
|
|
@ -41,40 +41,13 @@ export class ExportCommand {
|
|||
}
|
||||
|
||||
async saveFile(csv: string, cmd: program.Command): Promise<Response> {
|
||||
let p: string = null;
|
||||
let mkdir = false;
|
||||
if (cmd.output != null && cmd.output !== '') {
|
||||
const osOutput = path.join(cmd.output);
|
||||
if (osOutput.indexOf(path.sep) === -1) {
|
||||
p = path.join(process.cwd(), osOutput);
|
||||
} else {
|
||||
mkdir = true;
|
||||
if (osOutput.endsWith(path.sep)) {
|
||||
p = path.join(osOutput, this.exportService.getFileName());
|
||||
} else {
|
||||
p = osOutput;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p = path.join(process.cwd(), this.exportService.getFileName());
|
||||
try {
|
||||
const filePath = await CliUtils.saveFile(csv, cmd.output, this.exportService.getFileName());
|
||||
const res = new MessageResponse('Saved ' + filePath, null);
|
||||
res.raw = filePath;
|
||||
return Response.success(res);
|
||||
} catch (e) {
|
||||
return Response.error(e.toString());
|
||||
}
|
||||
|
||||
p = path.resolve(p);
|
||||
if (mkdir) {
|
||||
const dir = p.substring(0, p.lastIndexOf(path.sep));
|
||||
if (!fs.existsSync(dir)) {
|
||||
CliUtils.mkdirpSync(dir, 755);
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise<Response>((resolve, reject) => {
|
||||
fs.writeFile(p, csv, (err) => {
|
||||
if (err != null) {
|
||||
reject(Response.error('Cannot save file to ' + p));
|
||||
}
|
||||
const res = new MessageResponse('Saved ' + p + '', null);
|
||||
resolve(Response.success(res));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import * as program from 'commander';
|
||||
import * as fet from 'node-fetch';
|
||||
|
||||
import { CipherType } from 'jslib/enums/cipherType';
|
||||
|
||||
import { AuditService } from 'jslib/abstractions/audit.service';
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||
import { TotpService } from 'jslib/abstractions/totp.service';
|
||||
|
||||
|
@ -16,9 +18,11 @@ import { Response } from '../models/response';
|
|||
import { CipherResponse } from '../models/response/cipherResponse';
|
||||
import { CollectionResponse } from '../models/response/collectionResponse';
|
||||
import { FolderResponse } from '../models/response/folderResponse';
|
||||
import { MessageResponse } from '../models/response/messageResponse';
|
||||
import { StringResponse } from '../models/response/stringResponse';
|
||||
import { TemplateResponse } from '../models/response/templateResponse';
|
||||
|
||||
import { Attachment } from '../models/attachment';
|
||||
import { Card } from '../models/card';
|
||||
import { Cipher } from '../models/cipher';
|
||||
import { Collection } from '../models/collection';
|
||||
|
@ -34,7 +38,7 @@ import { CliUtils } from '../utils';
|
|||
export class GetCommand {
|
||||
constructor(private cipherService: CipherService, private folderService: FolderService,
|
||||
private collectionService: CollectionService, private totpService: TotpService,
|
||||
private auditService: AuditService) { }
|
||||
private auditService: AuditService, private cryptoService: CryptoService) { }
|
||||
|
||||
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
|
||||
if (id != null) {
|
||||
|
@ -43,7 +47,12 @@ export class GetCommand {
|
|||
|
||||
switch (object.toLowerCase()) {
|
||||
case 'item':
|
||||
return await this.getCipher(id);
|
||||
if (cmd.attachmentid === null || cmd.attachmentid === '') {
|
||||
return await this.getCipher(id);
|
||||
} else {
|
||||
|
||||
return await this.getAttachment(id, cmd);
|
||||
}
|
||||
case 'username':
|
||||
return await this.getUsername(id);
|
||||
case 'password':
|
||||
|
@ -148,6 +157,8 @@ export class GetCommand {
|
|||
}
|
||||
|
||||
private async getTotp(id: string) {
|
||||
// TODO: premium check
|
||||
|
||||
const cipherResponse = await this.getCipher(id);
|
||||
if (!cipherResponse.success) {
|
||||
return cipherResponse;
|
||||
|
@ -182,6 +193,50 @@ export class GetCommand {
|
|||
return Response.success(res);
|
||||
}
|
||||
|
||||
private async getAttachment(id: string, cmd: program.Command) {
|
||||
// TODO: Premium check
|
||||
|
||||
const cipherResponse = await this.getCipher(id);
|
||||
if (!cipherResponse.success) {
|
||||
return cipherResponse;
|
||||
}
|
||||
|
||||
const cipher = cipherResponse.data as CipherResponse;
|
||||
if (cipher.attachments == null || cipher.attachments.length === 0) {
|
||||
return Response.error('No attachments available for this item.');
|
||||
}
|
||||
|
||||
const attachment = cipher.attachments.filter((a) =>
|
||||
a.id === cmd.attachmentid || a.fileName === cmd.attachmentid);
|
||||
if (attachment.length === 0) {
|
||||
return Response.error('Attachment `' + cmd.attachmentid + '` was not found.');
|
||||
}
|
||||
if (attachment.length > 1) {
|
||||
return Response.multipleResults(attachment.map((a) => a.id));
|
||||
}
|
||||
|
||||
const response = await fet.default(new fet.Request(attachment[0].url, { headers: { cache: 'no-cache' } }));
|
||||
if (response.status !== 200) {
|
||||
return Response.error('A ' + response.status + ' error occurred while downloading the attachment.');
|
||||
}
|
||||
|
||||
try {
|
||||
const buf = await response.arrayBuffer();
|
||||
const key = await this.cryptoService.getOrgKey(cipher.organizationId);
|
||||
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
|
||||
const filePath = await CliUtils.saveFile(new Buffer(decBuf), cmd.output, attachment[0].fileName);
|
||||
const res = new MessageResponse('Saved ' + filePath, null);
|
||||
res.raw = filePath;
|
||||
return Response.success(res);
|
||||
} catch (e) {
|
||||
if (typeof (e) === 'string') {
|
||||
return Response.error(e);
|
||||
} else {
|
||||
return Response.error('An error occurred while saving the attachment.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getFolder(id: string) {
|
||||
let decFolder: FolderView = null;
|
||||
if (this.isGuid(id)) {
|
||||
|
@ -260,6 +315,9 @@ export class GetCommand {
|
|||
case 'securenote':
|
||||
template = SecureNote.template();
|
||||
break;
|
||||
case 'attachment':
|
||||
template = Attachment.template();
|
||||
break;
|
||||
case 'folder':
|
||||
template = Folder.template();
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { AttachmentView } from 'jslib/models/view/attachmentView';
|
||||
|
||||
export class Attachment {
|
||||
static template(): Attachment {
|
||||
const req = new Attachment();
|
||||
req.fileName = 'photo.jpg';
|
||||
return req;
|
||||
}
|
||||
|
||||
static toView(req: Attachment, view = new AttachmentView()) {
|
||||
view.fileName = req.fileName;
|
||||
return view;
|
||||
}
|
||||
|
||||
fileName: string;
|
||||
|
||||
// Use build method instead of ctor so that we can control order of JSON stringify for pretty print
|
||||
build(o: AttachmentView) {
|
||||
this.fileName = o.fileName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { AttachmentView } from 'jslib/models/view/attachmentView';
|
||||
|
||||
import { Attachment } from '../attachment';
|
||||
|
||||
export class AttachmentResponse extends Attachment {
|
||||
id: string;
|
||||
size: number;
|
||||
sizeName: string;
|
||||
url: string;
|
||||
|
||||
constructor(o: AttachmentView) {
|
||||
super();
|
||||
this.id = o.id;
|
||||
this.build(o);
|
||||
this.size = o.size;
|
||||
this.sizeName = o.sizeName;
|
||||
this.url = o.url;
|
||||
}
|
||||
}
|
|
@ -1,17 +1,21 @@
|
|||
import { CipherView } from 'jslib/models/view/cipherView';
|
||||
|
||||
import { BaseResponse } from './baseResponse';
|
||||
|
||||
import { Cipher } from '../cipher';
|
||||
import { AttachmentResponse } from './attachmentResponse';
|
||||
import { BaseResponse } from './baseResponse';
|
||||
|
||||
export class CipherResponse extends Cipher implements BaseResponse {
|
||||
object: string;
|
||||
id: string;
|
||||
attachments: AttachmentResponse[];
|
||||
|
||||
constructor(o: CipherView) {
|
||||
super();
|
||||
this.object = 'item';
|
||||
this.id = o.id;
|
||||
this.build(o);
|
||||
if (o.attachments != null) {
|
||||
this.attachments = o.attachments.map((a) => new AttachmentResponse(a));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -225,6 +225,8 @@ export class Program {
|
|||
program
|
||||
.command('get <object> <id>')
|
||||
.description('Get an object.')
|
||||
.option('--attachmentid <attachmentid>', 'Get an item\'s attachment.')
|
||||
.option('--output <output>', 'Output directory or filename for attachment.')
|
||||
.on('--help', () => {
|
||||
writeLn('\n Objects:');
|
||||
writeLn('');
|
||||
|
@ -248,6 +250,8 @@ export class Program {
|
|||
writeLn(' bw get password https://google.com');
|
||||
writeLn(' bw get totp google.com');
|
||||
writeLn(' bw get exposed yahoo.com');
|
||||
writeLn(' bw get item google --attachmentid b857igwl1dzrs2 --output ./photo.jpg');
|
||||
writeLn(' bw get item google --attachmentid photo.jpg --raw');
|
||||
writeLn(' bw get folder email');
|
||||
writeLn(' bw get template folder');
|
||||
writeLn('');
|
||||
|
@ -255,7 +259,8 @@ export class Program {
|
|||
.action(async (object, id, cmd) => {
|
||||
await this.exitIfLocked();
|
||||
const command = new GetCommand(this.main.cipherService, this.main.folderService,
|
||||
this.main.collectionService, this.main.totpService, this.main.auditService);
|
||||
this.main.collectionService, this.main.totpService, this.main.auditService,
|
||||
this.main.cryptoService);
|
||||
const response = await command.run(object, id, cmd);
|
||||
this.processResponse(response);
|
||||
});
|
||||
|
|
37
src/utils.ts
37
src/utils.ts
|
@ -6,6 +6,43 @@ import { CollectionView } from 'jslib/models/view/collectionView';
|
|||
import { FolderView } from 'jslib/models/view/folderView';
|
||||
|
||||
export class CliUtils {
|
||||
static saveFile(data: string | Buffer, output: string, defaultFileName: string) {
|
||||
let p: string = null;
|
||||
let mkdir = false;
|
||||
if (output != null && output !== '') {
|
||||
const osOutput = path.join(output);
|
||||
if (osOutput.indexOf(path.sep) === -1) {
|
||||
p = path.join(process.cwd(), osOutput);
|
||||
} else {
|
||||
mkdir = true;
|
||||
if (osOutput.endsWith(path.sep)) {
|
||||
p = path.join(osOutput, defaultFileName);
|
||||
} else {
|
||||
p = osOutput;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p = path.join(process.cwd(), defaultFileName);
|
||||
}
|
||||
|
||||
p = path.resolve(p);
|
||||
if (mkdir) {
|
||||
const dir = p.substring(0, p.lastIndexOf(path.sep));
|
||||
if (!fs.existsSync(dir)) {
|
||||
CliUtils.mkdirpSync(dir, 755);
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
fs.writeFile(p, data, 'utf8', (err) => {
|
||||
if (err != null) {
|
||||
reject('Cannot save file to ' + p);
|
||||
}
|
||||
resolve(p);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static mkdirpSync(targetDir: string, mode = 755, relative = false, relativeDir: string = null) {
|
||||
const initialDir = path.isAbsolute(targetDir) ? path.sep : '';
|
||||
const baseDir = relative ? (relativeDir != null ? relativeDir : __dirname) : '.';
|
||||
|
|
Loading…
Reference in New Issue