1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-10-30 08:10:34 +01:00

get items attachment command

This commit is contained in:
Kyle Spearrin 2018-05-17 13:28:22 -04:00
parent 05535cc134
commit df024379c8
10 changed files with 167 additions and 40 deletions

2
jslib

@ -1 +1 @@
Subproject commit ba10d0704212f2bc8fabf0d3d6ebb552fd183401 Subproject commit ed89dfaba70b60925817a0ce6f0c179b3f8bd2fb

9
package-lock.json generated
View File

@ -40,6 +40,15 @@
"integrity": "sha512-MFFKFv2X4iZy/NFl1m1E8uwE1CR96SGwJjgHma09PLtqOWoj3nqeJHMG+P/EuJGVLvC2I6MdQRQsr4TcRduIow==", "integrity": "sha512-MFFKFv2X4iZy/NFl1m1E8uwE1CR96SGwJjgHma09PLtqOWoj3nqeJHMG+P/EuJGVLvC2I6MdQRQsr4TcRduIow==",
"dev": true "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": { "@types/node-forge": {
"version": "0.7.4", "version": "0.7.4",
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-0.7.4.tgz", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-0.7.4.tgz",

View File

@ -48,6 +48,7 @@
"@types/lowdb": "^1.0.1", "@types/lowdb": "^1.0.1",
"@types/lunr": "^2.1.5", "@types/lunr": "^2.1.5",
"@types/node": "^10.0.8", "@types/node": "^10.0.8",
"@types/node-fetch": "^1.6.9",
"@types/node-forge": "^0.7.1", "@types/node-forge": "^0.7.1",
"@types/papaparse": "4.1.31", "@types/papaparse": "4.1.31",
"@types/readline-sync": "^1.4.3", "@types/readline-sync": "^1.4.3",

View File

@ -41,40 +41,13 @@ export class ExportCommand {
} }
async saveFile(csv: string, cmd: program.Command): Promise<Response> { async saveFile(csv: string, cmd: program.Command): Promise<Response> {
let p: string = null; try {
let mkdir = false; const filePath = await CliUtils.saveFile(csv, cmd.output, this.exportService.getFileName());
if (cmd.output != null && cmd.output !== '') { const res = new MessageResponse('Saved ' + filePath, null);
const osOutput = path.join(cmd.output); res.raw = filePath;
if (osOutput.indexOf(path.sep) === -1) { return Response.success(res);
p = path.join(process.cwd(), osOutput); } catch (e) {
} else { return Response.error(e.toString());
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());
} }
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));
});
});
} }
} }

View File

@ -1,10 +1,12 @@
import * as program from 'commander'; import * as program from 'commander';
import * as fet from 'node-fetch';
import { CipherType } from 'jslib/enums/cipherType'; import { CipherType } from 'jslib/enums/cipherType';
import { AuditService } from 'jslib/abstractions/audit.service'; import { AuditService } from 'jslib/abstractions/audit.service';
import { CipherService } from 'jslib/abstractions/cipher.service'; import { CipherService } from 'jslib/abstractions/cipher.service';
import { CollectionService } from 'jslib/abstractions/collection.service'; import { CollectionService } from 'jslib/abstractions/collection.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { FolderService } from 'jslib/abstractions/folder.service'; import { FolderService } from 'jslib/abstractions/folder.service';
import { TotpService } from 'jslib/abstractions/totp.service'; import { TotpService } from 'jslib/abstractions/totp.service';
@ -16,9 +18,11 @@ import { Response } from '../models/response';
import { CipherResponse } from '../models/response/cipherResponse'; import { CipherResponse } from '../models/response/cipherResponse';
import { CollectionResponse } from '../models/response/collectionResponse'; import { CollectionResponse } from '../models/response/collectionResponse';
import { FolderResponse } from '../models/response/folderResponse'; import { FolderResponse } from '../models/response/folderResponse';
import { MessageResponse } from '../models/response/messageResponse';
import { StringResponse } from '../models/response/stringResponse'; import { StringResponse } from '../models/response/stringResponse';
import { TemplateResponse } from '../models/response/templateResponse'; import { TemplateResponse } from '../models/response/templateResponse';
import { Attachment } from '../models/attachment';
import { Card } from '../models/card'; import { Card } from '../models/card';
import { Cipher } from '../models/cipher'; import { Cipher } from '../models/cipher';
import { Collection } from '../models/collection'; import { Collection } from '../models/collection';
@ -34,7 +38,7 @@ import { CliUtils } from '../utils';
export class GetCommand { export class GetCommand {
constructor(private cipherService: CipherService, private folderService: FolderService, constructor(private cipherService: CipherService, private folderService: FolderService,
private collectionService: CollectionService, private totpService: TotpService, 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> { async run(object: string, id: string, cmd: program.Command): Promise<Response> {
if (id != null) { if (id != null) {
@ -43,7 +47,12 @@ export class GetCommand {
switch (object.toLowerCase()) { switch (object.toLowerCase()) {
case 'item': 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': case 'username':
return await this.getUsername(id); return await this.getUsername(id);
case 'password': case 'password':
@ -148,6 +157,8 @@ export class GetCommand {
} }
private async getTotp(id: string) { private async getTotp(id: string) {
// TODO: premium check
const cipherResponse = await this.getCipher(id); const cipherResponse = await this.getCipher(id);
if (!cipherResponse.success) { if (!cipherResponse.success) {
return cipherResponse; return cipherResponse;
@ -182,6 +193,50 @@ export class GetCommand {
return Response.success(res); 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) { private async getFolder(id: string) {
let decFolder: FolderView = null; let decFolder: FolderView = null;
if (this.isGuid(id)) { if (this.isGuid(id)) {
@ -260,6 +315,9 @@ export class GetCommand {
case 'securenote': case 'securenote':
template = SecureNote.template(); template = SecureNote.template();
break; break;
case 'attachment':
template = Attachment.template();
break;
case 'folder': case 'folder':
template = Folder.template(); template = Folder.template();
break; break;

21
src/models/attachment.ts Normal file
View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -1,17 +1,21 @@
import { CipherView } from 'jslib/models/view/cipherView'; import { CipherView } from 'jslib/models/view/cipherView';
import { BaseResponse } from './baseResponse';
import { Cipher } from '../cipher'; import { Cipher } from '../cipher';
import { AttachmentResponse } from './attachmentResponse';
import { BaseResponse } from './baseResponse';
export class CipherResponse extends Cipher implements BaseResponse { export class CipherResponse extends Cipher implements BaseResponse {
object: string; object: string;
id: string; id: string;
attachments: AttachmentResponse[];
constructor(o: CipherView) { constructor(o: CipherView) {
super(); super();
this.object = 'item'; this.object = 'item';
this.id = o.id; this.id = o.id;
this.build(o); this.build(o);
if (o.attachments != null) {
this.attachments = o.attachments.map((a) => new AttachmentResponse(a));
}
} }
} }

View File

@ -225,6 +225,8 @@ export class Program {
program program
.command('get <object> <id>') .command('get <object> <id>')
.description('Get an object.') .description('Get an object.')
.option('--attachmentid <attachmentid>', 'Get an item\'s attachment.')
.option('--output <output>', 'Output directory or filename for attachment.')
.on('--help', () => { .on('--help', () => {
writeLn('\n Objects:'); writeLn('\n Objects:');
writeLn(''); writeLn('');
@ -248,6 +250,8 @@ export class Program {
writeLn(' bw get password https://google.com'); writeLn(' bw get password https://google.com');
writeLn(' bw get totp google.com'); writeLn(' bw get totp google.com');
writeLn(' bw get exposed yahoo.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 folder email');
writeLn(' bw get template folder'); writeLn(' bw get template folder');
writeLn(''); writeLn('');
@ -255,7 +259,8 @@ export class Program {
.action(async (object, id, cmd) => { .action(async (object, id, cmd) => {
await this.exitIfLocked(); await this.exitIfLocked();
const command = new GetCommand(this.main.cipherService, this.main.folderService, 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); const response = await command.run(object, id, cmd);
this.processResponse(response); this.processResponse(response);
}); });

View File

@ -6,6 +6,43 @@ import { CollectionView } from 'jslib/models/view/collectionView';
import { FolderView } from 'jslib/models/view/folderView'; import { FolderView } from 'jslib/models/view/folderView';
export class CliUtils { 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) { static mkdirpSync(targetDir: string, mode = 755, relative = false, relativeDir: string = null) {
const initialDir = path.isAbsolute(targetDir) ? path.sep : ''; const initialDir = path.isAbsolute(targetDir) ? path.sep : '';
const baseDir = relative ? (relativeDir != null ? relativeDir : __dirname) : '.'; const baseDir = relative ? (relativeDir != null ? relativeDir : __dirname) : '.';