Skip to content

Commit

Permalink
feat: identity new/import now has a --force flag (#2141)
Browse files Browse the repository at this point in the history
# Description

Adds the flag `--force` to `dfx identity new` and `dfx identity import`. 

Closes #1040

# How Has This Been Tested?

e2e tests added.

# Checklist:

- [x] The title of this PR complies with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
- [x] I have edited the CHANGELOG accordingly.
- [ ] I have made corresponding changes to the documentation.
  • Loading branch information
sesi200 authored Apr 21, 2022
1 parent 633ce44 commit 128a20f
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 14 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ dfx identity import identity_name identity.pem

If you want to get your identity out of dfx, you can use `dfx identity export identityname > exported_identity.pem`. But be careful with storing this file as it is not protected with your password.

=== feat: Identity new/import now has a --force flag

If you want to script identity creation and don't care about overwriting existing identities, you now can use the `--force` flag for the commands `dfx identity new` and `dfx identity import`.

=== fix: Do not automatically create a wallet on IC

When running `dfx deploy --network ic`, `dfx canister --network ic create`, or `dfx identity --network ic get-wallet` dfx no longer automatically creates a cycles wallet for the user if none is configured. Instead, it will simply report that no wallet was found for that user.
Expand Down
30 changes: 26 additions & 4 deletions e2e/tests-dfx/identity_command.bash
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ teardown() {

@test "identity new: creates a new identity" {
assert_command dfx identity new --disable-encryption alice
assert_match 'Creating identity: "alice".' "$stderr"
assert_match 'Created identity: "alice".' "$stderr"
assert_command head "$DFX_CONFIG_ROOT/.config/dfx/identity/alice/identity.pem"
assert_match "BEGIN PRIVATE KEY"
Expand All @@ -81,6 +80,15 @@ teardown() {
assert_match "Identity already exists"
}

@test "identity new: --force re-creates an identity" {
assert_command dfx identity new --disable-encryption alice
dfx identity use alice
PRINCIPAL_1="$(dfx identity get-principal)"
assert_command dfx identity new --disable-encryption --force alice
PRINCIPAL_2="$(dfx identity get-principal)"
assert_neq "$PRINCIPAL_1" "$PRINCIPAL_2"
}

@test "identity new: create an HSM-backed identity" {
assert_command dfx identity new --disable-encryption --hsm-pkcs11-lib-path /something/else/somewhere.so --hsm-key-id abcd4321 bob
assert_command jq -r .hsm.pkcs11_lib_path "$DFX_CONFIG_ROOT/.config/dfx/identity/bob/identity.json"
Expand Down Expand Up @@ -338,16 +346,31 @@ teardown() {
@test "identity: import" {
openssl ecparam -name secp256k1 -genkey -out identity.pem
assert_command dfx identity import --disable-encryption alice identity.pem
assert_match 'Creating identity: "alice".' "$stderr"
assert_match 'Created identity: "alice".' "$stderr"
assert_command diff identity.pem "$DFX_CONFIG_ROOT/.config/dfx/identity/alice/identity.pem"
assert_eq ""
}

@test "identity: import can only overwrite identity with --force" {
openssl ecparam -name secp256k1 -genkey -out identity.pem
openssl ecparam -name secp256k1 -genkey -out identity2.pem
assert_command dfx identity import --disable-encryption alice identity.pem
assert_match 'Created identity: "alice".' "$stderr"
dfx identity use alice
PRINCIPAL_1="$(dfx identity get-principal)"

assert_command_fail dfx identity import --disable-encryption alice identity2.pem
assert_match "Identity already exists."
assert_command dfx identity import --disable-encryption --force alice identity2.pem
assert_match 'Created identity: "alice".'
PRINCIPAL_2="$(dfx identity get-principal)"

assert_neq "$PRINCIPAL_1" "$PRINCIPAL_2"
}

@test "identity: import default" {
assert_command dfx identity new --disable-encryption alice
assert_command dfx identity import --disable-encryption bob "$DFX_CONFIG_ROOT/.config/dfx/identity/alice/identity.pem"
assert_match 'Creating identity: "bob".' "$stderr"
assert_match 'Created identity: "bob".' "$stderr"
assert_command diff "$DFX_CONFIG_ROOT/.config/dfx/identity/alice/identity.pem" "$DFX_CONFIG_ROOT/.config/dfx/identity/bob/identity.pem"
assert_eq ""
Expand All @@ -361,7 +384,6 @@ teardown() {
echo -n 1 >> bob.pem
tail -n 3 alice.pem > bob.pem
assert_command_fail dfx identity import --disable-encryption bob bob.pem
assert_match 'Creating identity: "bob".' "$stderr"
assert_match 'Invalid Ed25519 private key in PEM file' "$stderr"
}

Expand Down
7 changes: 5 additions & 2 deletions src/dfx/src/commands/identity/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ pub struct ImportOpts {
/// I you want the convenience of not having to type your password (but at the risk of having your PEM file compromised), you can disable the encryption.
#[clap(long)]
disable_encryption: bool,

/// If the identity already exists, remove and re-import it.
#[clap(long)]
force: bool,
}

/// Executes the import subcommand.
pub fn exec(env: &dyn Environment, opts: ImportOpts) -> DfxResult {
let log = env.get_logger();
let name = opts.identity.as_str();
info!(log, r#"Creating identity: "{}"."#, name);
let params = IdentityCreationParameters::PemFile {
src_pem_file: opts.pem_file,
disable_encryption: opts.disable_encryption,
};
IdentityManager::new(env)?.create_new_identity(name, params)?;
IdentityManager::new(env)?.create_new_identity(name, params, opts.force)?;
info!(log, r#"Created identity: "{}"."#, name);
Ok(())
}
7 changes: 5 additions & 2 deletions src/dfx/src/commands/identity/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ pub struct NewIdentityOpts {
/// I you want the convenience of not having to type your password (but at the risk of having your PEM file compromised), you can disable the encryption.
#[clap(long)]
disable_encryption: bool,

/// If the identity already exists, remove and re-create it.
#[clap(long)]
force: bool,
}

pub fn exec(env: &dyn Environment, opts: NewIdentityOpts) -> DfxResult {
let name = opts.identity.as_str();

let log = env.get_logger();
info!(log, r#"Creating identity: "{}"."#, name);

let creation_parameters = match (opts.hsm_pkcs11_lib_path, opts.hsm_key_id) {
(Some(pkcs11_lib_path), Some(key_id)) => Hardware {
Expand All @@ -47,7 +50,7 @@ pub fn exec(env: &dyn Environment, opts: NewIdentityOpts) -> DfxResult {
},
};

IdentityManager::new(env)?.create_new_identity(name, creation_parameters)?;
IdentityManager::new(env)?.create_new_identity(name, creation_parameters, opts.force)?;

info!(log, r#"Created identity: "{}"."#, name);
Ok(())
Expand Down
14 changes: 10 additions & 4 deletions src/dfx/src/lib/identity/identity_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,19 @@ impl IdentityManager {
}

/// Create a new identity (name -> generated key)
///
/// `force`: If the identity already exists, remove and re-create it.
pub fn create_new_identity(
&self,
&mut self,
name: &str,
parameters: IdentityCreationParameters,
force: bool,
) -> DfxResult {
if name == ANONYMOUS_IDENTITY_NAME {
return Err(DfxError::new(IdentityError::CannotCreateAnonymousIdentity()));
}

DfxIdentity::create(self, name, parameters)
DfxIdentity::create(self, name, parameters, force)
}

/// Return a sorted list of all available identity names
Expand Down Expand Up @@ -269,16 +272,19 @@ impl IdentityManager {
}

/// Select an identity by name to use by default
pub fn use_identity_named(&self, name: &str) -> DfxResult {
pub fn use_identity_named(&mut self, name: &str) -> DfxResult {
self.require_identity_exists(name)?;
self.write_default_identity(name)
self.write_default_identity(name)?;
self.configuration.default = name.to_string();
Ok(())
}

fn write_default_identity(&self, name: &str) -> DfxResult {
let config = Configuration {
default: String::from(name),
};
write_configuration(&self.identity_json_path, &config)
.context(format!("Failed to write to {:?}", self.identity_json_path))
}

/// Determines if there are enough files present to consider the identity as existing.
Expand Down
25 changes: 23 additions & 2 deletions src/dfx/src/lib/identity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,30 @@ pub struct Identity {
}

impl Identity {
/// Creates a new identity.
///
/// `force`: If the identity already exists, remove it and re-create.
pub fn create(
manager: &IdentityManager,
manager: &mut IdentityManager,
name: &str,
parameters: IdentityCreationParameters,
force: bool,
) -> DfxResult {
let identity_in_use = name;
// cannot delete an identity in use. Use anonymous identity temporarily if we force-overwrite the identity currently in use
let temporarily_use_anonymous_identity = identity_in_use == name && force;

if manager.require_identity_exists(name).is_ok() {
bail!("Identity already exists.");
if force {
if temporarily_use_anonymous_identity {
manager.use_identity_named(ANONYMOUS_IDENTITY_NAME)?;
}
manager
.remove(name)
.context("Cannot remove pre-existing identity.")?;
} else {
bail!("Identity already exists.");
}
}

fn create(identity_dir: &Path) -> DfxResult {
Expand Down Expand Up @@ -140,6 +157,10 @@ impl Identity {
// Everything is created. Now move from the temporary directory to the actual identity location.
let identity_dir = manager.get_identity_dir_path(name);
std::fs::rename(temp_identity_dir, identity_dir)?;

if temporarily_use_anonymous_identity {
manager.use_identity_named(identity_in_use)?;
}
Ok(())
}

Expand Down

0 comments on commit 128a20f

Please sign in to comment.