Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
= 0.6.25

== DFX

- feat: add --call-as-user flag to allow a User Identity to directly access canister mgmt functions, bypass Wallet (#1476)
Comment thread
p-shahi marked this conversation as resolved.
Outdated

Added `--call-as-user` flag to `dfx canister` and `dfx deploy`. This allows users to call canister management functionality with their Identity as the Sender (bypassing their Wallet canister.)

== Agents

=== Rust Agent
Expand Down
25 changes: 24 additions & 1 deletion e2e/tests-dfx/wallet.bash
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ setup() {
mkdir "$x"
export HOME="$x"

dfx_new
dfx_new hello
}

teardown() {
Expand Down Expand Up @@ -45,3 +45,26 @@ teardown() {
assert_command dfx identity --network actuallylocal deploy-wallet "${ID_TWO}"
assert_match "The wallet canister \"${ID}\"\ already exists for user \"default\" on \"actuallylocal\" network."
}

@test "bypass wallet call as user" {
install_asset greet
dfx_start
assert_command dfx canister --call-as-user create --all
assert_command dfx build
assert_command dfx canister --call-as-user install hello
assert_command dfx canister --call-as-user call "$(dfx canister id hello)" greet '("DFINITY")'
assert_match '("Hello, DFINITY!")'
# Wallet should not have been created/should not exist
assert_command_fail test -f .dfx/local/wallets.json
}

@test "bypass wallet call as user: deploy" {
install_asset greet
dfx_start
assert_command dfx deploy --call-as-user
assert_command dfx canister --call-as-user call "$(dfx canister id hello)" greet '("DFINITY")'
assert_match '("Hello, DFINITY!")'
# Wallet should not have been created/should not exist
assert_command_fail test -f .dfx/local/wallets.json
}

17 changes: 14 additions & 3 deletions src/dfx/src/commands/canister/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,30 @@ pub struct CanisterCreateOpts {
with_cycles: Option<String>,
}

pub async fn exec(env: &dyn Environment, opts: CanisterCreateOpts) -> DfxResult {
pub async fn exec(
env: &dyn Environment,
opts: CanisterCreateOpts,
call_as_user: bool,
) -> DfxResult {
let config = env.get_config_or_anyhow()?;
let timeout = expiry_duration();

fetch_root_key_if_needed(env).await?;
let with_cycles = opts.with_cycles.as_deref();
if let Some(canister_name) = opts.canister_name.clone() {
create_canister(env, canister_name.as_str(), timeout, with_cycles).await
create_canister(
env,
canister_name.as_str(),
timeout,
with_cycles,
call_as_user,
)
.await
} else if opts.all {
// Create all canisters.
if let Some(canisters) = &config.get_config().canisters {
for canister_name in canisters.keys() {
create_canister(env, canister_name, timeout, with_cycles).await?;
create_canister(env, canister_name, timeout, with_cycles, call_as_user).await?;
}
}
Ok(())
Expand Down
13 changes: 9 additions & 4 deletions src/dfx/src/commands/canister/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ async fn delete_canister(
env: &dyn Environment,
canister_name: &str,
timeout: Duration,
call_as_user: bool,
) -> DfxResult {
let log = env.get_logger();
let mut canister_id_store = CanisterIdStore::for_env(env)?;
Expand All @@ -37,25 +38,29 @@ async fn delete_canister(
canister_id.to_text(),
);

canister::delete_canister(env, canister_id, timeout).await?;
canister::delete_canister(env, canister_id, timeout, call_as_user).await?;

canister_id_store.remove(canister_name)?;

Ok(())
}

pub async fn exec(env: &dyn Environment, opts: CanisterDeleteOpts) -> DfxResult {
pub async fn exec(
env: &dyn Environment,
opts: CanisterDeleteOpts,
call_as_user: bool,
) -> DfxResult {
let config = env.get_config_or_anyhow()?;
let timeout = expiry_duration();

fetch_root_key_if_needed(env).await?;

if let Some(canister_name) = opts.canister_name.as_deref() {
delete_canister(env, canister_name, timeout).await
delete_canister(env, canister_name, timeout, call_as_user).await
} else if opts.all {
if let Some(canisters) = &config.get_config().canisters {
for canister_name in canisters.keys() {
delete_canister(env, canister_name, timeout).await?;
delete_canister(env, canister_name, timeout, call_as_user).await?;
}
}
Ok(())
Expand Down
8 changes: 7 additions & 1 deletion src/dfx/src/commands/canister/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ fn get_memory_allocation(
}))
}

pub async fn exec(env: &dyn Environment, opts: CanisterInstallOpts) -> DfxResult {
pub async fn exec(
env: &dyn Environment,
opts: CanisterInstallOpts,
call_as_user: bool,
) -> DfxResult {
let config = env.get_config_or_anyhow()?;
let agent = env
.get_agent()
Expand Down Expand Up @@ -121,6 +125,7 @@ pub async fn exec(env: &dyn Environment, opts: CanisterInstallOpts) -> DfxResult
mode,
memory_allocation,
timeout,
call_as_user,
)
.await
} else if opts.all {
Expand Down Expand Up @@ -152,6 +157,7 @@ pub async fn exec(env: &dyn Environment, opts: CanisterInstallOpts) -> DfxResult
mode,
memory_allocation,
timeout,
call_as_user,
)
.await?;
}
Expand Down
20 changes: 13 additions & 7 deletions src/dfx/src/commands/canister/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ pub struct CanisterOpts {
#[clap(long)]
network: Option<String>,

/// Performs the call with the user Identity as the Sender of messages.
/// Bypasses the Wallet canister.
#[clap(long)]
call_as_user: bool,

#[clap(subcommand)]
subcmd: SubCommand,
}
Expand All @@ -48,18 +53,19 @@ enum SubCommand {
pub fn exec(env: &dyn Environment, opts: CanisterOpts) -> DfxResult {
let agent_env = create_agent_environment(env, opts.network.clone())?;
let mut runtime = Runtime::new().expect("Unable to create a runtime");
let call_as_user = opts.call_as_user;
runtime.block_on(async {
match opts.subcmd {
SubCommand::Call(v) => call::exec(&agent_env, v).await,
SubCommand::Create(v) => create::exec(&agent_env, v).await,
SubCommand::Delete(v) => delete::exec(&agent_env, v).await,
SubCommand::Create(v) => create::exec(&agent_env, v, call_as_user).await,
SubCommand::Delete(v) => delete::exec(&agent_env, v, call_as_user).await,
SubCommand::Id(v) => id::exec(&agent_env, v).await,
SubCommand::Install(v) => install::exec(&agent_env, v).await,
SubCommand::Install(v) => install::exec(&agent_env, v, call_as_user).await,
SubCommand::RequestStatus(v) => request_status::exec(&agent_env, v).await,
SubCommand::SetController(v) => set_controller::exec(&agent_env, v).await,
SubCommand::Start(v) => start::exec(&agent_env, v).await,
SubCommand::Status(v) => status::exec(&agent_env, v).await,
SubCommand::Stop(v) => stop::exec(&agent_env, v).await,
SubCommand::SetController(v) => set_controller::exec(&agent_env, v, call_as_user).await,
SubCommand::Start(v) => start::exec(&agent_env, v, call_as_user).await,
SubCommand::Status(v) => status::exec(&agent_env, v, call_as_user).await,
SubCommand::Stop(v) => stop::exec(&agent_env, v, call_as_user).await,
}
})
}
11 changes: 9 additions & 2 deletions src/dfx/src/commands/canister/set_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct SetControllerOpts {
new_controller: String,
}

pub async fn exec(env: &dyn Environment, opts: SetControllerOpts) -> DfxResult {
pub async fn exec(env: &dyn Environment, opts: SetControllerOpts, call_as_user: bool) -> DfxResult {
let timeout = expiry_duration();
fetch_root_key_if_needed(env).await?;

Expand All @@ -43,7 +43,14 @@ pub async fn exec(env: &dyn Environment, opts: SetControllerOpts) -> DfxResult {
}
};

set_controller(env, canister_id, controller_principal, timeout).await?;
set_controller(
env,
canister_id,
controller_principal,
timeout,
call_as_user,
)
.await?;

println!(
"Set {:?} as controller of {:?}.",
Expand Down
9 changes: 5 additions & 4 deletions src/dfx/src/commands/canister/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async fn start_canister(
env: &dyn Environment,
canister_name: &str,
timeout: Duration,
call_as_user: bool,
) -> DfxResult {
let log = env.get_logger();
let canister_id_store = CanisterIdStore::for_env(env)?;
Expand All @@ -37,23 +38,23 @@ async fn start_canister(
canister_id.to_text(),
);

canister::start_canister(env, canister_id, timeout).await?;
canister::start_canister(env, canister_id, timeout, call_as_user).await?;

Ok(())
}

pub async fn exec(env: &dyn Environment, opts: CanisterStartOpts) -> DfxResult {
pub async fn exec(env: &dyn Environment, opts: CanisterStartOpts, call_as_user: bool) -> DfxResult {
let config = env.get_config_or_anyhow()?;
fetch_root_key_if_needed(env).await?;

let timeout = expiry_duration();

if let Some(canister_name) = opts.canister_name.as_deref() {
start_canister(env, &canister_name, timeout).await
start_canister(env, &canister_name, timeout, call_as_user).await
} else if opts.all {
if let Some(canisters) = &config.get_config().canisters {
for canister_name in canisters.keys() {
start_canister(env, &canister_name, timeout).await?;
start_canister(env, &canister_name, timeout, call_as_user).await?;
}
}
Ok(())
Expand Down
13 changes: 9 additions & 4 deletions src/dfx/src/commands/canister/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,34 @@ async fn canister_status(
env: &dyn Environment,
canister_name: &str,
timeout: Duration,
call_as_user: bool,
) -> DfxResult {
let log = env.get_logger();
let canister_id_store = CanisterIdStore::for_env(env)?;
let canister_id = canister_id_store.get(canister_name)?;

let status = canister::get_canister_status(env, canister_id, timeout).await?;
let status = canister::get_canister_status(env, canister_id, timeout, call_as_user).await?;

info!(log, "Canister {}'s status is {}.", canister_name, status);
Ok(())
}

pub async fn exec(env: &dyn Environment, opts: CanisterStatusOpts) -> DfxResult {
pub async fn exec(
env: &dyn Environment,
opts: CanisterStatusOpts,
call_as_user: bool,
) -> DfxResult {
let config = env.get_config_or_anyhow()?;

fetch_root_key_if_needed(env).await?;
let timeout = expiry_duration();

if let Some(canister_name) = opts.canister_name.as_deref() {
canister_status(env, &canister_name, timeout).await
canister_status(env, &canister_name, timeout, call_as_user).await
} else if opts.all {
if let Some(canisters) = &config.get_config().canisters {
for canister_name in canisters.keys() {
canister_status(env, &canister_name, timeout).await?;
canister_status(env, &canister_name, timeout, call_as_user).await?;
}
}
Ok(())
Expand Down
15 changes: 10 additions & 5 deletions src/dfx/src/commands/canister/stop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ pub struct CanisterStopOpts {
all: bool,
}

async fn stop_canister(env: &dyn Environment, canister_name: &str, timeout: Duration) -> DfxResult {
async fn stop_canister(
env: &dyn Environment,
canister_name: &str,
timeout: Duration,
call_as_user: bool,
) -> DfxResult {
let log = env.get_logger();
let canister_id_store = CanisterIdStore::for_env(env)?;
let canister_id = canister_id_store.get(canister_name)?;
Expand All @@ -34,23 +39,23 @@ async fn stop_canister(env: &dyn Environment, canister_name: &str, timeout: Dura
canister_id.to_text(),
);

canister::stop_canister(env, canister_id, timeout).await?;
canister::stop_canister(env, canister_id, timeout, call_as_user).await?;

Ok(())
}

pub async fn exec(env: &dyn Environment, opts: CanisterStopOpts) -> DfxResult {
pub async fn exec(env: &dyn Environment, opts: CanisterStopOpts, call_as_user: bool) -> DfxResult {
let config = env.get_config_or_anyhow()?;

fetch_root_key_if_needed(env).await?;
let timeout = expiry_duration();

if let Some(canister_name) = opts.canister_name.as_deref() {
stop_canister(env, &canister_name, timeout).await
stop_canister(env, &canister_name, timeout, call_as_user).await
} else if opts.all {
if let Some(canisters) = &config.get_config().canisters {
for canister_name in canisters.keys() {
stop_canister(env, &canister_name, timeout).await?;
stop_canister(env, &canister_name, timeout, call_as_user).await?;
}
}
Ok(())
Expand Down
6 changes: 6 additions & 0 deletions src/dfx/src/commands/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ pub struct DeployOpts {
/// This amount is deducted from the wallet's cycle balance.
#[clap(long, validator(cycle_amount_validator))]
with_cycles: Option<String>,

/// Performs the call with the user Identity as the Sender of messages.
/// Bypasses the Wallet canister.
#[clap(long)]
call_as_user: bool,
}

pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult {
Expand All @@ -57,5 +62,6 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult {
argument_type,
timeout,
with_cycles,
opts.call_as_user,
))
}
Loading