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:
parent
05535cc134
commit
df024379c8
2
jslib
2
jslib
@ -1 +1 @@
|
|||||||
Subproject commit ba10d0704212f2bc8fabf0d3d6ebb552fd183401
|
Subproject commit ed89dfaba70b60925817a0ce6f0c179b3f8bd2fb
|
9
package-lock.json
generated
9
package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
21
src/models/attachment.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
19
src/models/response/attachmentResponse.ts
Normal file
19
src/models/response/attachmentResponse.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
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';
|
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) : '.';
|
||||||
|
Loading…
Reference in New Issue
Block a user