diff --git a/iroha/src/smartcontracts/isi/asset.rs b/iroha/src/smartcontracts/isi/asset.rs index 651360fddcd..0a757503852 100644 --- a/iroha/src/smartcontracts/isi/asset.rs +++ b/iroha/src/smartcontracts/isi/asset.rs @@ -334,25 +334,6 @@ pub mod query { } } - impl Query for FindAssetsByAccountIdAndAssetDefinitionId { - #[log] - fn execute(&self, wsv: &WorldStateView) -> Result { - let id = self - .account_id - .evaluate(wsv, &Context::default()) - .wrap_err("Failed to get account id")?; - let asset_id = self - .asset_definition_id - .evaluate(wsv, &Context::default()) - .wrap_err("Failed to get asset id")?; - Ok(wsv - .account_assets(&id)? - .into_iter() - .filter(|asset| asset.id.definition_id == asset_id) - .collect()) - } - } - impl Query for FindAssetsByDomainNameAndAssetDefinitionId { #[log] fn execute(&self, wsv: &WorldStateView) -> Result { diff --git a/iroha/src/smartcontracts/isi/query.rs b/iroha/src/smartcontracts/isi/query.rs index f91d5667b82..637df91e178 100644 --- a/iroha/src/smartcontracts/isi/query.rs +++ b/iroha/src/smartcontracts/isi/query.rs @@ -176,7 +176,6 @@ impl Query for QueryBox { FindAssetsByAccountId(query) => query.execute_into_value(wsv), FindAssetsByAssetDefinitionId(query) => query.execute_into_value(wsv), FindAssetsByDomainName(query) => query.execute_into_value(wsv), - FindAssetsByAccountIdAndAssetDefinitionId(query) => query.execute_into_value(wsv), FindAssetsByDomainNameAndAssetDefinitionId(query) => query.execute_into_value(wsv), FindAssetQuantityById(query) => query.execute_into_value(wsv), FindAllDomains(query) => query.execute_into_value(wsv), diff --git a/iroha/test_network/src/lib.rs b/iroha/test_network/src/lib.rs index eb41917ffc3..bde08b227fa 100644 --- a/iroha/test_network/src/lib.rs +++ b/iroha/test_network/src/lib.rs @@ -21,7 +21,7 @@ use iroha::{ kura::{Kura, KuraTrait}, prelude::*, queue::{Queue, QueueTrait}, - smartcontracts::permissions::IsInstructionAllowedBoxed, + smartcontracts::permissions::{IsInstructionAllowedBoxed, IsQueryAllowedBoxed}, sumeragi::{config::SumeragiConfiguration, Sumeragi, SumeragiTrait}, torii::config::ToriiConfiguration, wsv::{World, WorldTrait}, @@ -396,7 +396,8 @@ where pub async fn start_with_config_permissions( &mut self, configuration: Configuration, - permissions: impl Into> + Send + 'static, + instruction_validator: impl Into> + Send + 'static, + query_validator: impl Into> + Send + 'static, ) { let temp_dir = TempDir::new().expect("Failed to create temp dir."); let mut configuration = self.get_config(configuration); @@ -417,8 +418,8 @@ where let _temp_dir = temp_dir; let mut iroha = >::with_broker( &configuration, - permissions.into(), - AllowAll.into(), + instruction_validator.into(), + query_validator.into(), broker, ) .await @@ -437,7 +438,7 @@ where /// Starts peer with config pub async fn start_with_config(&mut self, configuration: Configuration) { - self.start_with_config_permissions(configuration, AllowAll) + self.start_with_config_permissions(configuration, AllowAll, AllowAll) .await; } @@ -484,21 +485,26 @@ where /// Starts peer with default configuration. /// Returns its info and client for connecting to it. pub async fn start_test() -> (Self, Client) { - Self::start_test_with_permissions(AllowAll.into()).await + Self::start_test_with_permissions(AllowAll.into(), AllowAll.into()).await } /// Starts peer with default configuration and specified permissions. /// Returns its info and client for connecting to it. pub async fn start_test_with_permissions( - permissions: IsInstructionAllowedBoxed, + instruction_validator: IsInstructionAllowedBoxed, + query_validator: IsQueryAllowedBoxed, ) -> (Self, Client) { let mut configuration = Configuration::test(); let mut peer = Self::new().expect("Failed to create peer."); configuration.sumeragi_configuration.trusted_peers.peers = std::iter::once(peer.id.clone()).collect(); configuration.genesis_configuration.genesis_block_path = Some(GENESIS_PATH.to_owned()); - peer.start_with_config_permissions(configuration.clone(), permissions) - .await; + peer.start_with_config_permissions( + configuration.clone(), + instruction_validator, + query_validator, + ) + .await; let client = Client::test(&peer.api_address); time::sleep(Duration::from_millis( configuration.sumeragi_configuration.pipeline_time_ms(), diff --git a/iroha_client/src/client.rs b/iroha_client/src/client.rs index e2337efa476..754c645cb2a 100644 --- a/iroha_client/src/client.rs +++ b/iroha_client/src/client.rs @@ -498,12 +498,9 @@ pub mod asset { FindAssetsByAccountId::new(account_id) } - /// Get query to get all assets by account id and definition id - pub fn by_account_id_and_definition_id( - account_id: impl Into>, - asset_definition_id: impl Into>, - ) -> FindAssetsByAccountIdAndAssetDefinitionId { - FindAssetsByAccountIdAndAssetDefinitionId::new(account_id, asset_definition_id) + /// Get query to get all assets by account id + pub fn by_id(asset_id: impl Into::Id>>) -> FindAssetById { + FindAssetById::new(asset_id) } } diff --git a/iroha_client/tests/permissions.rs b/iroha_client/tests/permissions.rs index aad3124e78c..73d38ebd15e 100644 --- a/iroha_client/tests/permissions.rs +++ b/iroha_client/tests/permissions.rs @@ -2,10 +2,10 @@ use std::thread; -use iroha::config::Configuration; +use iroha::{config::Configuration, prelude::AllowAll}; use iroha_client::client::{self, Client}; use iroha_data_model::prelude::*; -use iroha_permissions_validators::public_blockchain; +use iroha_permissions_validators::{private_blockchain, public_blockchain}; use test_network::{Peer as TestPeer, *}; use tokio::runtime::Runtime; @@ -30,6 +30,7 @@ fn permissions_disallow_asset_transfer() { let rt = Runtime::test(); let (_peer, mut iroha_client) = rt.block_on(::start_test_with_permissions( public_blockchain::default_permissions(), + AllowAll.into(), )); let pipeline_time = Configuration::pipeline_time(); @@ -88,6 +89,7 @@ fn permissions_disallow_asset_burn() { let rt = Runtime::test(); let (_not_drop, mut iroha_client) = rt.block_on(::start_test_with_permissions( public_blockchain::default_permissions(), + AllowAll.into(), )); let pipeline_time = Configuration::pipeline_time(); @@ -145,3 +147,36 @@ fn permissions_disallow_asset_burn() { let alice_assets = get_assets(&mut iroha_client, &alice_id); assert_eq!(alice_assets, alice_start_assets); } + +#[test] +fn account_can_query_only_its_own_domain() { + let rt = Runtime::test(); + let (_not_drop, mut iroha_client) = rt.block_on(::start_test_with_permissions( + AllowAll.into(), + private_blockchain::query::OnlyAccountsDomain.into(), + )); + let pipeline_time = Configuration::pipeline_time(); + + // Given + thread::sleep(pipeline_time * 2); + + let domain_name = "wonderland"; + let new_domain_name = "wonderland2"; + let register_domain = RegisterBox::new(IdentifiableBox::from(Domain::new(new_domain_name))); + + iroha_client + .submit(register_domain) + .expect("Failed to prepare state."); + + thread::sleep(pipeline_time * 2); + + // Alice can query the domain in which her account exists. + assert!(iroha_client + .request(client::domain::by_name(domain_name.to_owned())) + .is_ok()); + + // Alice can not query other domains. + assert!(iroha_client + .request(client::domain::by_name(new_domain_name.to_owned())) + .is_err()); +} diff --git a/iroha_client_cli/src/main.rs b/iroha_client_cli/src/main.rs index 3b930493a27..9f6396223d1 100644 --- a/iroha_client_cli/src/main.rs +++ b/iroha_client_cli/src/main.rs @@ -521,8 +521,9 @@ mod asset { fn run(self, cfg: &ClientConfiguration) -> Result<()> { let Self { account, asset } = self; let mut iroha_client = Client::new(cfg); + let asset_id = AssetId::new(asset, account); let value = iroha_client - .request(asset::by_account_id_and_definition_id(account, asset)) + .request(asset::by_id(asset_id)) .wrap_err("Failed to get asset.")?; println!("Get Asset result: {:?}", value); Ok(()) diff --git a/iroha_data_model/src/query.rs b/iroha_data_model/src/query.rs index f5f07c1bb0e..8b138c6666a 100644 --- a/iroha_data_model/src/query.rs +++ b/iroha_data_model/src/query.rs @@ -61,8 +61,6 @@ pub enum QueryBox { FindAssetsByAssetDefinitionId(FindAssetsByAssetDefinitionId), /// `FindAssetsByDomainName` variant. FindAssetsByDomainName(FindAssetsByDomainName), - /// `FindAssetsByAccountIdAndAssetDefinitionId` variant. - FindAssetsByAccountIdAndAssetDefinitionId(FindAssetsByAccountIdAndAssetDefinitionId), /// `FindAssetsByDomainNameAndAssetDefinitionId` variant. FindAssetsByDomainNameAndAssetDefinitionId(FindAssetsByDomainNameAndAssetDefinitionId), /// `FindAssetQuantityById` variant. @@ -681,35 +679,6 @@ pub mod asset { type Output = Vec; } - // TODO: remove as it is the same as `FindAssetById` - /// `FindAssetsByAccountIdAndAssetDefinitionId` Iroha Query will get `AccountId` and - /// `AssetDefinitionId` as inputs and find all `Asset`s owned by the `Account` - /// with this `AssetDefinition` in Iroha Peer. - #[derive( - Clone, - Debug, - Io, - Serialize, - Deserialize, - Encode, - Decode, - PartialEq, - Eq, - PartialOrd, - Ord, - IntoSchema, - )] - pub struct FindAssetsByAccountIdAndAssetDefinitionId { - /// `AccountId` under which assets should be found. - pub account_id: EvaluatesTo, - /// `AssetDefinitionId` which assets should be found. - pub asset_definition_id: EvaluatesTo, - } - - impl QueryOutput for FindAssetsByAccountIdAndAssetDefinitionId { - type Output = Vec; - } - /// `FindAssetsByDomainNameAndAssetDefinitionId` Iroha Query will get `Domain`'s name and /// `AssetDefinitionId` as inputs and find all `Asset`s under the `Domain` /// with this `AssetDefinition` in Iroha `Peer`. @@ -846,21 +815,6 @@ pub mod asset { } } - impl FindAssetsByAccountIdAndAssetDefinitionId { - /// Default `FindAssetsByAccountIdAndAssetDefinitionId` constructor. - pub fn new( - account_id: impl Into>, - asset_definition_id: impl Into>, - ) -> Self { - let account_id = account_id.into(); - let asset_definition_id = asset_definition_id.into(); - FindAssetsByAccountIdAndAssetDefinitionId { - account_id, - asset_definition_id, - } - } - } - impl FindAssetsByDomainNameAndAssetDefinitionId { /// Default `FindAssetsByDomainNameAndAssetDefinitionId` constructor pub fn new( @@ -897,8 +851,7 @@ pub mod asset { pub mod prelude { pub use super::{ FindAllAssets, FindAllAssetsDefinitions, FindAssetById, FindAssetKeyValueByIdAndKey, - FindAssetQuantityById, FindAssetsByAccountId, - FindAssetsByAccountIdAndAssetDefinitionId, FindAssetsByAssetDefinitionId, + FindAssetQuantityById, FindAssetsByAccountId, FindAssetsByAssetDefinitionId, FindAssetsByDomainName, FindAssetsByDomainNameAndAssetDefinitionId, FindAssetsByName, }; } diff --git a/iroha_permissions_validators/Cargo.toml b/iroha_permissions_validators/Cargo.toml index 03733225d45..7c8bddcb8e5 100644 --- a/iroha_permissions_validators/Cargo.toml +++ b/iroha_permissions_validators/Cargo.toml @@ -6,6 +6,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +roles = ["iroha_data_model/roles"] + [dependencies] iroha = { path = "../iroha"} iroha_data_model = { path = "../iroha_data_model" } diff --git a/iroha_permissions_validators/src/lib.rs b/iroha_permissions_validators/src/lib.rs index 67434aa169b..842afa61011 100644 --- a/iroha_permissions_validators/src/lib.rs +++ b/iroha_permissions_validators/src/lib.rs @@ -8,8 +8,8 @@ use iroha::{ prelude::*, smartcontracts::{ permissions::{ - prelude::*, HasToken, IsAllowed, IsInstructionAllowedBoxed, ValidatorApplyOr, - ValidatorBuilder, + prelude::*, HasToken, IsAllowed, IsInstructionAllowedBoxed, IsQueryAllowedBoxed, + ValidatorApplyOr, ValidatorBuilder, }, Evaluate, }, @@ -28,6 +28,16 @@ macro_rules! impl_from_item_for_instruction_validator_box { }; } +macro_rules! impl_from_item_for_query_validator_box { + ( $ty:ty ) => { + impl From<$ty> for IsQueryAllowedBoxed { + fn from(validator: $ty) -> Self { + Box::new(validator) + } + } + }; +} + macro_rules! impl_from_item_for_granted_token_validator_box { ( $ty:ty ) => { impl From<$ty> for HasTokenBoxed { @@ -78,7 +88,7 @@ pub mod private_blockchain { use super::*; /// A preconfigured set of permissions for simple use cases. - pub fn default_permissions() -> IsInstructionAllowedBoxed { + pub fn default_instructions_permissions() -> IsInstructionAllowedBoxed { ValidatorBuilder::new() .with_recursive_validator( register::ProhibitRegisterDomains.or(register::GrantedAllowedRegisterDomains), @@ -86,6 +96,11 @@ pub mod private_blockchain { .all_should_succeed() } + /// A preconfigured set of permissions for simple use cases. + pub fn default_query_permissions() -> IsQueryAllowedBoxed { + ValidatorBuilder::new().all_should_succeed() + } + /// Prohibits using `Grant` instruction at runtime. /// This means `Grant` instruction will only be used in genesis to specify rights. #[derive(Debug, Copy, Clone)] @@ -156,6 +171,389 @@ pub mod private_blockchain { } } } + + /// Query Permissions. + pub mod query { + use super::*; + + /// Allow queries that only access the data of the domain of the signer. + #[derive(Debug, Copy, Clone)] + pub struct OnlyAccountsDomain; + + impl IsAllowed for OnlyAccountsDomain { + #[allow(clippy::too_many_lines, clippy::match_same_arms)] + fn check( + &self, + authority: &AccountId, + query: &QueryBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let context = Context::new(); + match query { + QueryBox::FindAssetsByAssetDefinitionId(_) + | QueryBox::FindAssetsByName(_) + | QueryBox::FindAllAssets(_) => { + Err("Only access to the assets of the same domain is permitted.".to_owned()) + } + QueryBox::FindAllAccounts(_) | QueryBox::FindAccountsByName(_) => Err( + "Only access to the accounts of the same domain is permitted.".to_owned(), + ), + QueryBox::FindAllAssetsDefinitions(_) => Err( + "Only access to the asset definitions of the same domain is permitted." + .to_owned(), + ), + QueryBox::FindAllDomains(_) => { + Err("Only access to the domain of the account is permitted.".to_owned()) + } + #[cfg(feature = "roles")] + QueryBox::FindAllRoles(_) => Ok(()), + QueryBox::FindAllPeers(_) => Ok(()), + QueryBox::FindAccountById(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + QueryBox::FindAccountKeyValueByIdAndKey(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + QueryBox::FindAccountsByDomainName(query) => { + let domain_name = query + .domain_name + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access accounts from a different domain with name {}.", + domain_name + )) + } + } + QueryBox::FindAssetById(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if asset_id.account_id.domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different domain.", + asset_id + )) + } + } + QueryBox::FindAssetsByAccountId(query) => { + let account_id = query + .account_id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + QueryBox::FindAssetsByDomainName(query) => { + let domain_name = query + .domain_name + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access assets from a different domain with name {}.", + domain_name + )) + } + } + QueryBox::FindAssetsByDomainNameAndAssetDefinitionId(query) => { + let domain_name = query + .domain_name + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access assets from a different domain with name {}.", + domain_name + )) + } + } + QueryBox::FindAssetQuantityById(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if asset_id.account_id.domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different domain.", + asset_id + )) + } + } + QueryBox::FindAssetKeyValueByIdAndKey(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if asset_id.account_id.domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different domain.", + asset_id + )) + } + } + QueryBox::FindDomainByName(query) => { + let domain_name = query + .name + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access a different domain: {}.", + domain_name + )) + } + } + QueryBox::FindTransactionsByAccountId(query) => { + let account_id = query + .account_id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + #[cfg(feature = "roles")] + QueryBox::FindRolesByAccountId(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + QueryBox::FindPermissionTokensByAccountId(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_name == authority.domain_name { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + } + } + } + + impl_from_item_for_query_validator_box!(OnlyAccountsDomain); + + /// Allow queries that only access the signers account data. + #[derive(Debug, Copy, Clone)] + pub struct OnlyAccountsData; + + impl IsAllowed for OnlyAccountsData { + #[allow(clippy::too_many_lines, clippy::match_same_arms)] + fn check( + &self, + authority: &AccountId, + query: &QueryBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let context = Context::new(); + match query { + QueryBox::FindAccountsByName(_) + | QueryBox::FindAccountsByDomainName(_) + | QueryBox::FindAllAccounts(_) + | QueryBox::FindAllAssetsDefinitions(_) + | QueryBox::FindAssetsByAssetDefinitionId(_) + | QueryBox::FindAssetsByDomainName(_) + | QueryBox::FindAssetsByName(_) + | QueryBox::FindAllDomains(_) + | QueryBox::FindDomainByName(_) + | QueryBox::FindAssetsByDomainNameAndAssetDefinitionId(_) + | QueryBox::FindAllAssets(_) => { + Err("Only access to the assets of the same domain is permitted.".to_owned()) + } + #[cfg(feature = "roles")] + #[allow(clippy::match_same_arms)] + QueryBox::FindAllRoles(_) => Ok(()), + QueryBox::FindAllPeers(_) => Ok(()), + QueryBox::FindAccountById(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as only access to your own account is permitted..", + account_id + )) + } + } + QueryBox::FindAccountKeyValueByIdAndKey(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as only access to your own account is permitted..", + account_id + )) + } + } + QueryBox::FindAssetById(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &asset_id.account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different account.", + asset_id + )) + } + } + QueryBox::FindAssetsByAccountId(query) => { + let account_id = query + .account_id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access a different account: {}.", + account_id + )) + } + } + + QueryBox::FindAssetQuantityById(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &asset_id.account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different account.", + asset_id + )) + } + } + QueryBox::FindAssetKeyValueByIdAndKey(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &asset_id.account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different account.", + asset_id + )) + } + } + + QueryBox::FindTransactionsByAccountId(query) => { + let account_id = query + .account_id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!("Cannot access another account: {}.", account_id)) + } + } + #[cfg(feature = "roles")] + QueryBox::FindRolesByAccountId(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!("Cannot access another account: {}.", account_id)) + } + } + QueryBox::FindPermissionTokensByAccountId(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!("Cannot access another account: {}.", account_id)) + } + } + } + } + } + + impl_from_item_for_query_validator_box!(OnlyAccountsData); + } } /// Permission checks asociated with use cases that can be summarized as public blockchains.