Skip to content

Commit

Permalink
Merge pull request #1850 from oasisprotocol/jberci/feature/access
Browse files Browse the repository at this point in the history
Add method access control module
  • Loading branch information
jberci authored Jul 23, 2024
2 parents 20e5298 + 8502877 commit 3a560e6
Show file tree
Hide file tree
Showing 12 changed files with 526 additions and 20 deletions.
4 changes: 4 additions & 0 deletions runtime-sdk/modules/evm/src/precompile/subcall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ mod test {
gas: 1_000_000,
..Default::default()
},
..Default::default()
},
);
assert!(dispatch_result.result.is_success(), "call should succeed");
Expand Down Expand Up @@ -384,6 +385,7 @@ mod test {
gas: 130_000,
..Default::default()
},
..Default::default()
},
);
assert!(
Expand Down Expand Up @@ -413,6 +415,7 @@ mod test {
gas: 120_000,
..Default::default()
},
..Default::default()
},
);
if let module::CallResult::Failed {
Expand Down Expand Up @@ -464,6 +467,7 @@ mod test {
gas: 127_710,
..Default::default()
},
..Default::default()
},
);
if let module::CallResult::Failed {
Expand Down
9 changes: 9 additions & 0 deletions runtime-sdk/modules/evm/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,18 +282,21 @@ fn test_evm_calls() {

#[test]
fn test_c10l_evm_calls_enc() {
let _guard = crypto::signature::context::test_using_chain_context();
crypto::signature::context::set_chain_context(Default::default(), "test");
do_test_evm_calls::<ConfidentialEVMConfig>(false);
}

#[test]
fn test_c10l_evm_calls_plain() {
let _guard = crypto::signature::context::test_using_chain_context();
crypto::signature::context::set_chain_context(Default::default(), "test");
do_test_evm_calls::<ConfidentialEVMConfig>(true /* force_plain */);
}

#[test]
fn test_c10l_evm_balance_transfer() {
let _guard = crypto::signature::context::test_using_chain_context();
crypto::signature::context::set_chain_context(Default::default(), "test");
let mut mock = mock::Mock::default();
let ctx = mock.create_ctx();
Expand Down Expand Up @@ -791,6 +794,7 @@ fn test_evm_runtime() {

#[test]
fn test_c10l_evm_runtime() {
let _guard = crypto::signature::context::test_using_chain_context();
crypto::signature::context::set_chain_context(Default::default(), "test");
do_test_evm_runtime::<ConfidentialEVMConfig>();
}
Expand Down Expand Up @@ -913,6 +917,7 @@ fn test_fee_refunds() {
gas: 100_000,
..Default::default()
},
..Default::default()
},
);
assert!(dispatch_result.result.is_success(), "call should succeed");
Expand Down Expand Up @@ -965,6 +970,7 @@ fn test_fee_refunds() {
gas: 100_000,
..Default::default()
},
..Default::default()
},
);
if let module::CallResult::Failed {
Expand Down Expand Up @@ -1046,6 +1052,7 @@ fn test_transfer_event() {
gas: 100_000,
..Default::default()
},
..Default::default()
},
);
assert!(dispatch_result.result.is_success(), "call should succeed");
Expand Down Expand Up @@ -1134,6 +1141,7 @@ fn test_return_value_limits() {
gas: 100_000,
..Default::default()
},
..Default::default()
},
);
let result: Vec<u8> = cbor::from_value(dispatch_result.result.unwrap()).unwrap();
Expand All @@ -1155,6 +1163,7 @@ fn test_return_value_limits() {
gas: 100_000,
..Default::default()
},
..Default::default()
},
);
if let module::CallResult::Failed {
Expand Down
34 changes: 22 additions & 12 deletions runtime-sdk/src/crypto/signature/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,32 @@ pub fn set_chain_context(runtime_id: Namespace, consensus_chain_context: &str) {
*guard = Some(ctx.into_bytes());
}

/// Test helper to serialize unit tests using the global chain context. The chain context is reset
/// when this method is called.
///
/// # Example
///
/// ```rust
/// # use oasis_runtime_sdk::crypto::signature::context::test_using_chain_context;
/// let _guard = test_using_chain_context();
/// // ... rest of the test code follows ...
/// ```
#[cfg(any(test, feature = "test"))]
pub fn test_using_chain_context() -> std::sync::MutexGuard<'static, ()> {
static TEST_USING_CHAIN_CONTEXT: Lazy<Mutex<()>> = Lazy::new(Default::default);
let guard = TEST_USING_CHAIN_CONTEXT.lock().unwrap();
*CHAIN_CONTEXT.lock().unwrap() = None;

guard
}

#[cfg(test)]
mod test {
use super::*;

static TEST_GUARD: Lazy<Mutex<()>> = Lazy::new(Default::default);

fn reset_chain_context() {
*CHAIN_CONTEXT.lock().unwrap() = None;
}

#[test]
fn test_chain_context() {
let _guard = TEST_GUARD.lock().unwrap();
reset_chain_context();
let _guard = test_using_chain_context();
set_chain_context(
"8000000000000000000000000000000000000000000000000000000000000000".into(),
"643fb06848be7e970af3b5b2d772eb8cfb30499c8162bc18ac03df2f5e22520e",
Expand All @@ -94,17 +106,15 @@ mod test {

#[test]
fn test_chain_context_not_configured() {
let _guard = TEST_GUARD.lock().unwrap();
reset_chain_context();
let _guard = test_using_chain_context();

let result = std::panic::catch_unwind(|| get_chain_context_for(b"test"));
assert!(result.is_err());
}

#[test]
fn test_chain_context_already_configured() {
let _guard = TEST_GUARD.lock().unwrap();
reset_chain_context();
let _guard = test_using_chain_context();
set_chain_context(
"8000000000000000000000000000000000000000000000000000000000000000".into(),
"643fb06848be7e970af3b5b2d772eb8cfb30499c8162bc18ac03df2f5e22520e",
Expand Down
4 changes: 4 additions & 0 deletions runtime-sdk/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ impl<R: Runtime> Dispatcher<R> {
}
}

if let Err(e) = R::Modules::before_authorized_call_dispatch(ctx, &call) {
return (e.into_call_result(), call_format_metadata);
}

let result = match R::Modules::dispatch_call(ctx, &call.method, call.body) {
module::DispatchResult::Handled(result) => result,
module::DispatchResult::Unhandled(_) => {
Expand Down
20 changes: 20 additions & 0 deletions runtime-sdk/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,18 @@ pub trait TransactionHandler {
Ok(())
}

/// Perform any action after authentication and decoding, within the transaction context.
///
/// At this point, the call has been decoded according to the call format,
/// and method authorizers have run.
fn before_authorized_call_dispatch<C: Context>(
_ctx: &C,
_call: &Call,
) -> Result<(), modules::core::Error> {
// Default implementation doesn't do anything.
Ok(())
}

/// Perform any action after call, within the transaction context.
///
/// If an error is returned the transaction call fails and updates are rolled back.
Expand Down Expand Up @@ -461,6 +473,14 @@ impl TransactionHandler for Tuple {
Ok(())
}

fn before_authorized_call_dispatch<C: Context>(
ctx: &C,
call: &Call,
) -> Result<(), modules::core::Error> {
for_tuples!( #( Tuple::before_authorized_call_dispatch(ctx, call)?; )* );
Ok(())
}

fn after_handle_call<C: Context>(
ctx: &C,
mut result: CallResult,
Expand Down
71 changes: 71 additions & 0 deletions runtime-sdk/src/modules/access/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! Method access control module.
use once_cell::unsync::Lazy;
use thiserror::Error;

use crate::{
context::Context,
module::{self, Module as _},
modules, sdk_derive,
state::CurrentState,
types::transaction,
};

#[cfg(test)]
mod test;
pub mod types;

/// Unique module name.
const MODULE_NAME: &str = "access";

/// Errors emitted by the access module.
#[derive(Error, Debug, oasis_runtime_sdk_macros::Error)]
pub enum Error {
#[error("caller is not authorized to call method")]
#[sdk_error(code = 1)]
NotAuthorized,
}

/// Module configuration.
#[allow(clippy::declare_interior_mutable_const)]
pub trait Config: 'static {
/// To filter methods by caller address, add them to this mapping.
///
/// If the mapping is empty, no method is filtered.
const METHOD_AUTHORIZATIONS: Lazy<types::Authorization> = Lazy::new(types::Authorization::new);
}

/// The method access control module.
pub struct Module<Cfg: Config> {
_cfg: std::marker::PhantomData<Cfg>,
}

#[sdk_derive(Module)]
impl<Cfg: Config> Module<Cfg> {
const NAME: &'static str = MODULE_NAME;
const VERSION: u32 = 1;
type Error = Error;
type Event = ();
type Parameters = ();
type Genesis = ();
}

impl<Cfg: Config> module::TransactionHandler for Module<Cfg> {
fn before_authorized_call_dispatch<C: Context>(
_ctx: &C,
call: &transaction::Call,
) -> Result<(), modules::core::Error> {
let tx_caller_address = CurrentState::with_env(|env| env.tx_caller_address());
#[allow(clippy::borrow_interior_mutable_const)]
if Cfg::METHOD_AUTHORIZATIONS.is_authorized(&call.method, &tx_caller_address) {
Ok(())
} else {
Err(modules::core::Error::InvalidArgument(
Error::NotAuthorized.into(),
))
}
}
}

impl<Cfg: Config> module::BlockHandler for Module<Cfg> {}

impl<Cfg: Config> module::InvariantHandler for Module<Cfg> {}
Loading

0 comments on commit 3a560e6

Please sign in to comment.