From 49f1fac3ed1b03c0ca69b90f3e83287dbd7b3953 Mon Sep 17 00:00:00 2001 From: Chad Scharf <3904944+cscharf@users.noreply.github.com> Date: Tue, 14 Apr 2020 13:04:19 -0400 Subject: [PATCH 1/2] Added --trash to delete cmd, added restore cmd --- jslib | 2 +- src/bw.ts | 6 ++--- src/commands/config.command.ts | 13 ++++++++--- src/commands/delete.command.ts | 10 ++++++--- src/commands/edit.command.ts | 3 +++ src/commands/list.command.ts | 7 +++++- src/commands/restore.command.ts | 39 +++++++++++++++++++++++++++++++++ src/program.ts | 36 ++++++++++++++++++++++++++++++ 8 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 src/commands/restore.command.ts diff --git a/jslib b/jslib index 72e3893f8e..e9db844285 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 72e3893f8eee79f1e3678839aa194f1096c343ea +Subproject commit e9db844285e21525f5152e782063f04e02543553 diff --git a/src/bw.ts b/src/bw.ts index 495d22ecc4..7362590ff9 100644 --- a/src/bw.ts +++ b/src/bw.ts @@ -116,9 +116,9 @@ export class Main { this.i18nService); this.searchService = new SearchService(this.cipherService, this.platformUtilsService); this.policyService = new PolicyService(this.userService, this.storageService); - this.vaultTimeoutService = new VaultTimeoutService(this.cipherService, this.folderService, this.collectionService, - this.cryptoService, this.platformUtilsService, this.storageService, this.messagingService, - this.searchService, this.userService, this.tokenService, null, null); + this.vaultTimeoutService = new VaultTimeoutService(this.cipherService, this.folderService, + this.collectionService, this.cryptoService, this.platformUtilsService, this.storageService, + this.messagingService, this.searchService, this.userService, this.tokenService, null, null); this.syncService = new SyncService(this.userService, this.apiService, this.settingsService, this.folderService, this.cipherService, this.cryptoService, this.collectionService, this.storageService, this.messagingService, this.policyService, diff --git a/src/commands/config.command.ts b/src/commands/config.command.ts index d9bdf57bd4..06b5d6565b 100644 --- a/src/commands/config.command.ts +++ b/src/commands/config.command.ts @@ -13,15 +13,16 @@ export class ConfigCommand { setting = setting.toLowerCase(); switch (setting) { case 'server': - return await this.getOrSetServer(value); + return await this.getOrSetServer(value, cmd); default: return Response.badRequest('Unknown setting.'); } } - private async getOrSetServer(url: string): Promise { - if (url == null || url.trim() === '') { + private async getOrSetServer(url: string, cmd: program.Command): Promise { + if ((url == null || url.trim() === '') && + !cmd.webVault && !cmd.api && !cmd.identity && !cmd.icons && !cmd.notifications && !cmd.events) { const baseUrl = this.environmentService.baseUrl; const stringRes = new StringResponse(baseUrl == null ? 'https://bitwarden.com' : baseUrl); return Response.success(stringRes); @@ -30,6 +31,12 @@ export class ConfigCommand { url = (url === 'null' || url === 'bitwarden.com' || url === 'https://bitwarden.com' ? null : url); await this.environmentService.setUrls({ base: url, + webVault: cmd.webVault || null, + api: cmd.api || null, + identity: cmd.identity || null, + icons: cmd.icons || null, + notifications: cmd.notifications || null, + events: cmd.events || null, }); const res = new MessageResponse('Saved setting `config`.', null); return Response.success(res); diff --git a/src/commands/delete.command.ts b/src/commands/delete.command.ts index 58eeb4375d..d0ee7e43bc 100644 --- a/src/commands/delete.command.ts +++ b/src/commands/delete.command.ts @@ -20,7 +20,7 @@ export class DeleteCommand { switch (object.toLowerCase()) { case 'item': - return await this.deleteCipher(id); + return await this.deleteCipher(id, cmd); case 'attachment': return await this.deleteAttachment(id, cmd); case 'folder': @@ -32,14 +32,18 @@ export class DeleteCommand { } } - private async deleteCipher(id: string) { + private async deleteCipher(id: string, cmd: program.Command) { const cipher = await this.cipherService.get(id); if (cipher == null) { return Response.notFound(); } try { - await this.cipherService.deleteWithServer(id); + if (cmd.trash) { + await this.cipherService.softDeleteWithServer(id); + } else { + await this.cipherService.deleteWithServer(id); + } return Response.success(); } catch (e) { return Response.error(e); diff --git a/src/commands/edit.command.ts b/src/commands/edit.command.ts index 864f224427..6c4a9f57ae 100644 --- a/src/commands/edit.command.ts +++ b/src/commands/edit.command.ts @@ -70,6 +70,9 @@ export class EditCommand { } let cipherView = await cipher.decrypt(); + if (cipherView.isDeleted) { + return Response.badRequest('You may not edit a deleted cipher. Use restore item command first.'); + } cipherView = Cipher.toView(req, cipherView); const encCipher = await this.cipherService.encrypt(cipherView); try { diff --git a/src/commands/list.command.ts b/src/commands/list.command.ts index 847bf55f08..6d20df62be 100644 --- a/src/commands/list.command.ts +++ b/src/commands/list.command.ts @@ -66,6 +66,9 @@ export class ListCommand { if (cmd.folderid != null || cmd.collectionid != null || cmd.organizationid != null) { ciphers = ciphers.filter((c) => { + if (cmd.trash && !c.isDeleted) { + return false; + } if (cmd.folderid != null) { if (cmd.folderid === 'notnull' && c.folderId != null) { return true; @@ -100,10 +103,12 @@ export class ListCommand { } return false; }); + } else if (cmd.search == null || cmd.search.trim() === '') { + ciphers = ciphers.filter((c) => (cmd.trash || false) === c.isDeleted); } if (cmd.search != null && cmd.search.trim() !== '') { - ciphers = this.searchService.searchCiphersBasic(ciphers, cmd.search); + ciphers = this.searchService.searchCiphersBasic(ciphers, cmd.search, cmd.trash || false); } const res = new ListResponse(ciphers.map((o) => new CipherResponse(o))); diff --git a/src/commands/restore.command.ts b/src/commands/restore.command.ts new file mode 100644 index 0000000000..6a1b57d5ff --- /dev/null +++ b/src/commands/restore.command.ts @@ -0,0 +1,39 @@ +import * as program from 'commander'; + +import { CipherService } from 'jslib/abstractions/cipher.service'; + +import { Response } from 'jslib/cli/models/response'; + +export class RestoreCommand { + constructor(private cipherService: CipherService) { } + + async run(object: string, id: string, cmd: program.Command): Promise { + if (id != null) { + id = id.toLowerCase(); + } + + switch (object.toLowerCase()) { + case 'item': + return await this.restoreCipher(id, cmd); + default: + return Response.badRequest('Unknown object.'); + } + } + + private async restoreCipher(id: string, cmd: program.Command) { + const cipher = await this.cipherService.get(id); + if (cipher == null) { + return Response.notFound(); + } + if (cipher.deletedDate == null) { + return Response.badRequest('Cipher is not in trash.'); + } + + try { + await this.cipherService.restoreWithServer(id); + return Response.success(); + } catch (e) { + return Response.error(e); + } + } +} diff --git a/src/program.ts b/src/program.ts index 7f81cbb4e1..212267a034 100644 --- a/src/program.ts +++ b/src/program.ts @@ -16,6 +16,7 @@ import { ImportCommand } from './commands/import.command'; import { ListCommand } from './commands/list.command'; import { LockCommand } from './commands/lock.command'; import { LoginCommand } from './commands/login.command'; +import { RestoreCommand } from './commands/restore.command'; import { ShareCommand } from './commands/share.command'; import { SyncCommand } from './commands/sync.command'; import { UnlockCommand } from './commands/unlock.command'; @@ -231,6 +232,7 @@ export class Program extends BaseProgram { .option('--folderid ', 'Filter items by folder id.') .option('--collectionid ', 'Filter items by collection id.') .option('--organizationid ', 'Filter items or collections by organization id.') + .option('--trash', 'Filter items that are deleted and in the trash.') .on('--help', () => { writeLn('\n Objects:'); writeLn(''); @@ -256,6 +258,7 @@ export class Program extends BaseProgram { writeLn(' bw list items --folderid null'); writeLn(' bw list items --organizationid notnull'); writeLn(' bw list items --folderid 60556c31-e649-4b5d-8daf-fc1c391a1bf2 --organizationid notnull'); + writeLn(' bw list items --trash'); writeLn(' bw list folders --search email'); writeLn(' bw list org-members --organizationid 60556c31-e649-4b5d-8daf-fc1c391a1bf2'); writeLn('', true); @@ -393,6 +396,7 @@ export class Program extends BaseProgram { .command('delete ') .option('--itemid ', 'Attachment\'s item id.') .option('--organizationid ', 'Organization id for an organization object.') + .option('-t, --trash', 'Places the item in the trash instead of permanently deleting it (item only).') .description('Delete an object from the vault.') .on('--help', () => { writeLn('\n Objects:'); @@ -409,6 +413,7 @@ export class Program extends BaseProgram { writeLn(' Examples:'); writeLn(''); writeLn(' bw delete item 7063feab-4b10-472e-b64c-785e2b870b92'); + writeLn(' bw delete item 89c21cd2-fab0-4f69-8c6e-ab8a0168f69a --trash'); writeLn(' bw delete folder 5cdfbd80-d99f-409b-915b-f4c5d0241b02'); writeLn(' bw delete attachment b857igwl1dzrs2 --itemid 310d5ffd-e9a2-4451-af87-ea054dce0f78'); writeLn('', true); @@ -421,6 +426,30 @@ export class Program extends BaseProgram { this.processResponse(response); }); + program + .command('restore ') + .description('Restores an object from the trash.') + .on('--help', () => { + writeLn('\n Objects:'); + writeLn(''); + writeLn(' item'); + writeLn(''); + writeLn(' Id:'); + writeLn(''); + writeLn(' Object\'s globally unique `id`.'); + writeLn(''); + writeLn(' Examples:'); + writeLn(''); + writeLn(' bw restore item 7063feab-4b10-472e-b64c-785e2b870b92'); + writeLn('', true); + }) + .action(async (object, id, cmd) => { + await this.exitIfLocked(); + const command = new RestoreCommand(this.main.cipherService); + const response = await command.run(object, id, cmd); + this.processResponse(response); + }); + program .command('share [encodedJson]') .description('Share an item to an organization.') @@ -583,6 +612,12 @@ export class Program extends BaseProgram { program .command('config [value]') .description('Configure CLI settings.') + .option('--web-vault ', 'Provides a custom web vault URL that differs from the base URL.') + .option('--api ', 'Provides a custom API URL that differs from the base URL.') + .option('--identity ', 'Provides a custom identity URL that differs from the base URL.') + .option('--icons ', 'Provides a custom icons service URL that differs from the base URL.') + .option('--notifications ', 'Provides a custom notifications URL that differs from the base URL.') + .option('--events ', 'Provides a custom events URL that differs from the base URL.') .on('--help', () => { writeLn('\n Settings:'); writeLn(''); @@ -593,6 +628,7 @@ export class Program extends BaseProgram { writeLn(' bw config server'); writeLn(' bw config server https://bw.company.com'); writeLn(' bw config server bitwarden.com'); + writeLn(' bw config server --api http://localhost:4000 --identity http://localhost:33656'); writeLn('', true); }) .action(async (setting, value, cmd) => { From 5dd99636188f0de94b9056dcde9ba04b3f8eacec Mon Sep 17 00:00:00 2001 From: Chad Scharf <3904944+cscharf@users.noreply.github.com> Date: Tue, 14 Apr 2020 14:32:43 -0400 Subject: [PATCH 2/2] [Soft Delete] soft-delete by default --- src/commands/delete.command.ts | 6 +++--- src/commands/list.command.ts | 7 ++++--- src/program.ts | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/commands/delete.command.ts b/src/commands/delete.command.ts index d0ee7e43bc..ea2a1600f5 100644 --- a/src/commands/delete.command.ts +++ b/src/commands/delete.command.ts @@ -39,10 +39,10 @@ export class DeleteCommand { } try { - if (cmd.trash) { - await this.cipherService.softDeleteWithServer(id); - } else { + if (cmd.permanent) { await this.cipherService.deleteWithServer(id); + } else { + await this.cipherService.softDeleteWithServer(id); } return Response.success(); } catch (e) { diff --git a/src/commands/list.command.ts b/src/commands/list.command.ts index 6d20df62be..b28f573c93 100644 --- a/src/commands/list.command.ts +++ b/src/commands/list.command.ts @@ -58,6 +58,7 @@ export class ListCommand { private async listCiphers(cmd: program.Command) { let ciphers: CipherView[]; + cmd.trash = cmd.trash || false; if (cmd.url != null && cmd.url.trim() !== '') { ciphers = await this.cipherService.getAllDecryptedForUrl(cmd.url); } else { @@ -66,7 +67,7 @@ export class ListCommand { if (cmd.folderid != null || cmd.collectionid != null || cmd.organizationid != null) { ciphers = ciphers.filter((c) => { - if (cmd.trash && !c.isDeleted) { + if (cmd.trash !== c.isDeleted) { return false; } if (cmd.folderid != null) { @@ -104,11 +105,11 @@ export class ListCommand { return false; }); } else if (cmd.search == null || cmd.search.trim() === '') { - ciphers = ciphers.filter((c) => (cmd.trash || false) === c.isDeleted); + ciphers = ciphers.filter((c) => cmd.trash === c.isDeleted); } if (cmd.search != null && cmd.search.trim() !== '') { - ciphers = this.searchService.searchCiphersBasic(ciphers, cmd.search, cmd.trash || false); + ciphers = this.searchService.searchCiphersBasic(ciphers, cmd.search, cmd.trash); } const res = new ListResponse(ciphers.map((o) => new CipherResponse(o))); diff --git a/src/program.ts b/src/program.ts index 212267a034..6f8a40c557 100644 --- a/src/program.ts +++ b/src/program.ts @@ -396,7 +396,7 @@ export class Program extends BaseProgram { .command('delete ') .option('--itemid ', 'Attachment\'s item id.') .option('--organizationid ', 'Organization id for an organization object.') - .option('-t, --trash', 'Places the item in the trash instead of permanently deleting it (item only).') + .option('-p, --permanent', 'Permanently deletes the item instead of soft-deleting it (item only).') .description('Delete an object from the vault.') .on('--help', () => { writeLn('\n Objects:'); @@ -413,7 +413,7 @@ export class Program extends BaseProgram { writeLn(' Examples:'); writeLn(''); writeLn(' bw delete item 7063feab-4b10-472e-b64c-785e2b870b92'); - writeLn(' bw delete item 89c21cd2-fab0-4f69-8c6e-ab8a0168f69a --trash'); + writeLn(' bw delete item 89c21cd2-fab0-4f69-8c6e-ab8a0168f69a --permanent'); writeLn(' bw delete folder 5cdfbd80-d99f-409b-915b-f4c5d0241b02'); writeLn(' bw delete attachment b857igwl1dzrs2 --itemid 310d5ffd-e9a2-4451-af87-ea054dce0f78'); writeLn('', true);