Skip to content

Commit

Permalink
feat(rust): identity create can import an identity
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianbenavides committed Mar 20, 2024
1 parent 7b23c1c commit b562b8c
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ impl CliState {
/// Once a identity has been created, store it.
/// If there is no previous default identity we set it as the default identity
#[instrument(skip_all, fields(name = %name, identifier = %identifier, vault_name = %vault_name))]
async fn store_named_identity(
pub async fn store_named_identity(
&self,
identifier: &Identifier,
name: &str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::cli_state::{UsersRepository, UsersSqlxDatabase};
/// These functions create repository implementations to access data
/// stored in the database
impl CliState {
pub(super) fn change_history_repository(&self) -> Arc<dyn ChangeHistoryRepository> {
pub fn change_history_repository(&self) -> Arc<dyn ChangeHistoryRepository> {
Arc::new(ChangeHistorySqlxDatabase::new(self.database()))
}

Expand Down
126 changes: 67 additions & 59 deletions implementations/rust/ockam/ockam_command/src/identity/create.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::terminal::OckamColor;
use crate::{docs, fmt_log, fmt_ok, Command, CommandGlobalOpts};
use async_trait::async_trait;
use clap::Args;
use colorful::Colorful;
use tokio::sync::Mutex;
use tokio::try_join;

use miette::IntoDiagnostic;
use ockam::identity::models::ChangeHistory;
use ockam::identity::IdentitiesVerification;
use ockam_api::cli_state::random_name;
use ockam_api::color_primary;
use ockam_node::Context;

use crate::terminal::OckamColor;

use crate::{docs, fmt_log, fmt_ok, Command, CommandGlobalOpts};
use ockam_vault::SoftwareVaultForVerifyingSignatures;

const LONG_ABOUT: &str = include_str!("./static/create/long_about.txt");
const AFTER_LONG_HELP: &str = include_str!("./static/create/after_long_help.txt");
Expand All @@ -31,70 +31,50 @@ pub struct CreateCommand {
/// Key ID to use for the identity creation
#[arg(short, long)]
pub key_id: Option<String>,

/// Identity to import in hex format
#[arg(long, value_name = "IDENTITY", conflicts_with = "key_id")]
identity: Option<String>,
}

#[async_trait]
impl Command for CreateCommand {
const NAME: &'static str = "identity create";

async fn async_run(self, _ctx: &Context, opts: CommandGlobalOpts) -> miette::Result<()> {
if let Some(identity) = self.identity.clone() {
self.import(opts, identity).await?;
} else {
self.create(opts).await?;
};
Ok(())
}
}

impl CreateCommand {
async fn create(self, opts: CommandGlobalOpts) -> miette::Result<()> {
opts.terminal.write_line(&fmt_log!(
"Creating identity {}...\n",
&self
.name
.to_string()
.color(OckamColor::PrimaryResource.color())
color_primary(&self.name)
))?;

let is_finished: Mutex<bool> = Mutex::new(false);
// This variable is used so that the output message does not clobber the spinner output.
let is_default_vault_created: Mutex<Option<String>> = Mutex::new(None);

let send_req = async {
let existing_vaults = opts.state.get_named_vaults().await?.len();

let vault = match &self.vault {
Some(vault_name) => opts.state.get_or_create_named_vault(vault_name).await?,
None => opts.state.get_or_create_default_named_vault().await?,
};
let updated_vaults = opts.state.get_named_vaults().await?.len();

// If a new vault has been created display a message
if updated_vaults > existing_vaults {
*is_default_vault_created.lock().await = Some(vault.name());
};

let identity = match &self.key_id {
Some(key_id) => {
opts.state
.create_identity_with_key_id(&self.name, &vault.name(), key_id.as_ref())
.await?
}
None => {
opts.state
.create_identity_with_name_and_vault(&self.name, &vault.name())
.await?
}
};

*is_finished.lock().await = true;
Ok(identity.identifier())
let vault = match &self.vault {
Some(vault_name) => opts.state.get_or_create_named_vault(vault_name).await?,
None => opts.state.get_or_create_default_named_vault().await?,
};

let output_messages = vec![format!("Creating identity...")];

let progress_output = opts
.terminal
.progress_output(&output_messages, &is_finished);

let (identifier, _) = try_join!(send_req, progress_output)?;

if let Some(vault_name) = is_default_vault_created.lock().await.clone() {
opts.terminal.write_line(&fmt_log!(
"Default vault created named {}\n",
vault_name.color(OckamColor::PrimaryResource.color())
))?;
}
let identity = match &self.key_id {
Some(key_id) => {
opts.state
.create_identity_with_key_id(&self.name, &vault.name(), key_id.as_ref())
.await?
}
None => {
opts.state
.create_identity_with_name_and_vault(&self.name, &vault.name())
.await?
}
};
let identifier = identity.identifier().to_string();

opts.terminal
.stdout()
Expand All @@ -118,6 +98,34 @@ impl Command for CreateCommand {

Ok(())
}

async fn import(self, opts: CommandGlobalOpts, identity: String) -> miette::Result<()> {
opts.terminal.write_line(&fmt_log!(
"Importing identity {}...\n",
color_primary(&self.name)
))?;

let named_vault = opts.state.get_named_vault_or_default(&self.vault).await?;
let change_history = ChangeHistory::import_from_string(&identity).into_diagnostic()?;
let identifier = IdentitiesVerification::new(
opts.state.change_history_repository(),
SoftwareVaultForVerifyingSignatures::create(),
)
.import_from_change_history(None, change_history)
.await
.into_diagnostic()?;
opts.state
.store_named_identity(&identifier, &self.name, &named_vault.name())
.await?;
opts.terminal
.stdout()
.plain(fmt_ok!(
"Identity imported successfully with name {}",
color_primary(&self.name)
))
.write_line()?;
Ok(())
}
}

#[cfg(test)]
Expand Down
5 changes: 2 additions & 3 deletions implementations/rust/ockam/ockam_command/src/identity/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,9 @@ impl ShowCommand {
let identity = opts.state.get_identity_by_optional_name(name).await?;

let (plain, json) = if full {
let change_history = identity.change_history();

if Some(EncodeFormat::Hex) == encoding {
let encoded = hex::encode(change_history.export().into_diagnostic()?);
let change_history = identity.change_history();
let encoded = change_history.export_as_string().into_diagnostic()?;
let json = to_string_pretty(&json!({"encoded": &encoded}));
(encoded, json)
} else {
Expand Down
17 changes: 17 additions & 0 deletions implementations/rust/ockam/ockam_command/tests/bats/identity.bats
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,20 @@ teardown() {
run_success "$OCKAM" identity default "${i}"
assert_output "${i}"
}

@test "identity - export/import" {
# Create and export
run_success "$OCKAM" identity create
run_success "$OCKAM" identity show --full --encoding hex
exported=$output

# Remove it
run_success "$OCKAM" identity delete --all --yes
run_success "$OCKAM" identity list --output json
assert_output --partial "[]"

# Import it back
run_success "$OCKAM" identity create --identity "$exported"
run_success "$OCKAM" identity show --full --encoding hex
assert_output "$exported"
}

0 comments on commit b562b8c

Please sign in to comment.