1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 02:03:39 +00:00

[PM-24534] Archive via CLI (#16502)

* refactor `canInteract` into a component level usage.

- The default service is going to be used in the CLI which won't make use of the UI-related aspects

* all nested entities to be imported from the vault

* initial add of archive command to the cli

* add archive to oss serve

* check for deleted cipher when attempting to archive

* add searchability/list functionality for archived ciphers

* restore an archived cipher

* unarchive a cipher when a user is editing it and has lost their premium status

* add missing feature flags

* re-export only needed services from the vault

* add needed await

* add prompt when applicable for editing an archived cipher

* move cipher archive service into `common/vault`

* fix testing code
This commit is contained in:
Nick Krantz
2025-09-30 09:45:04 -05:00
committed by GitHub
parent 7848b7d480
commit 727689d827
27 changed files with 401 additions and 131 deletions

View File

@@ -2,6 +2,8 @@
// @ts-strict-ignore
import { program, Command } from "commander";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfirmCommand } from "./admin-console/commands/confirm.command";
import { ShareCommand } from "./admin-console/commands/share.command";
import { BaseProgram } from "./base-program";
@@ -13,25 +15,34 @@ import { Response } from "./models/response";
import { ExportCommand } from "./tools/export.command";
import { ImportCommand } from "./tools/import.command";
import { CliUtils } from "./utils";
import { ArchiveCommand } from "./vault/archive.command";
import { CreateCommand } from "./vault/create.command";
import { DeleteCommand } from "./vault/delete.command";
const writeLn = CliUtils.writeLn;
export class VaultProgram extends BaseProgram {
register() {
async register() {
const isArchivedEnabled = await this.serviceContainer.configService.getFeatureFlag(
FeatureFlag.PM19148_InnovationArchive,
);
program
.addCommand(this.listCommand())
.addCommand(this.listCommand(isArchivedEnabled))
.addCommand(this.getCommand())
.addCommand(this.createCommand())
.addCommand(this.editCommand())
.addCommand(this.deleteCommand())
.addCommand(this.restoreCommand())
.addCommand(this.restoreCommand(isArchivedEnabled))
.addCommand(this.shareCommand("move", false))
.addCommand(this.confirmCommand())
.addCommand(this.importCommand())
.addCommand(this.exportCommand())
.addCommand(this.shareCommand("share", true));
if (isArchivedEnabled) {
program.addCommand(this.archiveCommand());
}
}
private validateObject(requestedObject: string, validObjects: string[]): boolean {
@@ -42,7 +53,7 @@ export class VaultProgram extends BaseProgram {
Response.badRequest(
'Unknown object "' +
requestedObject +
'". Allowed objects are ' +
'". Allowed objects are: ' +
validObjects.join(", ") +
".",
),
@@ -51,7 +62,7 @@ export class VaultProgram extends BaseProgram {
return success;
}
private listCommand(): Command {
private listCommand(isArchivedEnabled: boolean): Command {
const listObjects = [
"items",
"folders",
@@ -61,7 +72,7 @@ export class VaultProgram extends BaseProgram {
"organizations",
];
return new Command("list")
const command = new Command("list")
.argument("<object>", "Valid objects are: " + listObjects.join(", "))
.description("List an array of objects from the vault.")
.option("--search <search>", "Perform a search on the listed objects.")
@@ -94,6 +105,9 @@ export class VaultProgram extends BaseProgram {
" bw list items --folderid 60556c31-e649-4b5d-8daf-fc1c391a1bf2 --organizationid notnull",
);
writeLn(" bw list items --trash");
if (isArchivedEnabled) {
writeLn(" bw list items --archived");
}
writeLn(" bw list folders --search email");
writeLn(" bw list org-members --organizationid 60556c31-e649-4b5d-8daf-fc1c391a1bf2");
writeLn("", true);
@@ -116,11 +130,18 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.accountService,
this.serviceContainer.keyService,
this.serviceContainer.cliRestrictedItemTypesService,
this.serviceContainer.cipherArchiveService,
);
const response = await command.run(object, cmd);
this.processResponse(response);
});
if (isArchivedEnabled) {
command.option("--archived", "Filter items that are archived.");
}
return command;
}
private getCommand(): Command {
@@ -286,6 +307,7 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.accountService,
this.serviceContainer.cliRestrictedItemTypesService,
this.serviceContainer.policyService,
this.serviceContainer.billingAccountProfileStateService,
);
const response = await command.run(object, id, encodedJson, cmd);
this.processResponse(response);
@@ -336,12 +358,41 @@ export class VaultProgram extends BaseProgram {
});
}
private restoreCommand(): Command {
private archiveCommand(): Command {
const archiveObjects = ["item"];
return new Command("archive")
.argument("<object>", "Valid objects are: " + archiveObjects.join(", "))
.argument("<id>", "Object's globally unique `id`.")
.description("Archive an object from the vault.")
.on("--help", () => {
writeLn("\n Examples:");
writeLn("");
writeLn(" bw archive item 7063feab-4b10-472e-b64c-785e2b870b92");
writeLn("", true);
})
.action(async (object, id) => {
if (!this.validateObject(object, archiveObjects)) {
return;
}
await this.exitIfLocked();
const command = new ArchiveCommand(
this.serviceContainer.cipherService,
this.serviceContainer.accountService,
this.serviceContainer.configService,
this.serviceContainer.cipherArchiveService,
this.serviceContainer.billingAccountProfileStateService,
);
const response = await command.run(object, id);
this.processResponse(response);
});
}
private restoreCommand(isArchivedEnabled: boolean): Command {
const restoreObjects = ["item"];
return new Command("restore")
const command = new Command("restore")
.argument("<object>", "Valid objects are: " + restoreObjects.join(", "))
.argument("<id>", "Object's globally unique `id`.")
.description("Restores an object from the trash.")
.on("--help", () => {
writeLn("\n Examples:");
writeLn("");
@@ -358,10 +409,20 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.cipherService,
this.serviceContainer.accountService,
this.serviceContainer.cipherAuthorizationService,
this.serviceContainer.cipherArchiveService,
this.serviceContainer.configService,
);
const response = await command.run(object, id);
this.processResponse(response);
});
if (isArchivedEnabled) {
command.description("Restores an object from the trash or archive.");
} else {
command.description("Restores an object from the trash.");
}
return command;
}
private shareCommand(commandName: string, deprecated: boolean): Command {