1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-23 11:56:00 +01:00

Merge pull request #126 from bitwarden/soft-delete-chad

[Soft Delete] Added --trash to delete cmd, added restore cmd
This commit is contained in:
Chad Scharf 2020-04-14 14:40:56 -04:00 committed by GitHub
commit 9f78171488
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 106 additions and 11 deletions

2
jslib

@ -1 +1 @@
Subproject commit 72e3893f8eee79f1e3678839aa194f1096c343ea
Subproject commit e9db844285e21525f5152e782063f04e02543553

View File

@ -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,

View File

@ -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<Response> {
if (url == null || url.trim() === '') {
private async getOrSetServer(url: string, cmd: program.Command): Promise<Response> {
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);

View File

@ -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.permanent) {
await this.cipherService.deleteWithServer(id);
} else {
await this.cipherService.softDeleteWithServer(id);
}
return Response.success();
} catch (e) {
return Response.error(e);

View File

@ -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 <id> command first.');
}
cipherView = Cipher.toView(req, cipherView);
const encCipher = await this.cipherService.encrypt(cipherView);
try {

View File

@ -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,6 +67,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 +104,12 @@ export class ListCommand {
}
return false;
});
} else if (cmd.search == null || cmd.search.trim() === '') {
ciphers = ciphers.filter((c) => cmd.trash === 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);
}
const res = new ListResponse(ciphers.map((o) => new CipherResponse(o)));

View File

@ -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<Response> {
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);
}
}
}

View File

@ -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 <folderid>', 'Filter items by folder id.')
.option('--collectionid <collectionid>', 'Filter items by collection id.')
.option('--organizationid <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 <object> <id>')
.option('--itemid <itemid>', 'Attachment\'s item id.')
.option('--organizationid <organizationid>', 'Organization id for an organization object.')
.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:');
@ -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 --permanent');
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 <object> <id>')
.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 <id> <organizationId> [encodedJson]')
.description('Share an item to an organization.')
@ -583,6 +612,12 @@ export class Program extends BaseProgram {
program
.command('config <setting> [value]')
.description('Configure CLI settings.')
.option('--web-vault <url>', 'Provides a custom web vault URL that differs from the base URL.')
.option('--api <url>', 'Provides a custom API URL that differs from the base URL.')
.option('--identity <url>', 'Provides a custom identity URL that differs from the base URL.')
.option('--icons <url>', 'Provides a custom icons service URL that differs from the base URL.')
.option('--notifications <url>', 'Provides a custom notifications URL that differs from the base URL.')
.option('--events <url>', '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) => {