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:
commit
9f78171488
2
jslib
2
jslib
@ -1 +1 @@
|
||||
Subproject commit 72e3893f8eee79f1e3678839aa194f1096c343ea
|
||||
Subproject commit e9db844285e21525f5152e782063f04e02543553
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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)));
|
||||
|
39
src/commands/restore.command.ts
Normal file
39
src/commands/restore.command.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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) => {
|
||||
|
Loading…
Reference in New Issue
Block a user