Skip to content

Commit

Permalink
[feature] hyperledger-iroha#4244: Allow granting/revoking role's perm…
Browse files Browse the repository at this point in the history
…issions

Signed-off-by: Shanin Roman <shanin1000@yandex.ru>
  • Loading branch information
Erigara committed Mar 6, 2024
1 parent 18e8529 commit caf6972
Show file tree
Hide file tree
Showing 12 changed files with 301 additions and 24 deletions.
84 changes: 84 additions & 0 deletions client/tests/integration/roles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,87 @@ fn role_permissions_unified() {
"permission tokens for role aren't deduplicated"
);
}

#[test]
fn grant_revoke_role_permissions() -> Result<()> {
let chain_id = ChainId::from("0");

let (_rt, _peer, test_client) = <PeerBuilder>::new().with_port(11_245).start_with_runtime();
wait_for_genesis_committed(&vec![test_client.clone()], 0);

let alice_id = AccountId::from_str("alice@wonderland")?;
let mouse_id = AccountId::from_str("mouse@wonderland")?;

// Registering Mouse
let mouse_key_pair = KeyPair::generate();
let register_mouse = Register::account(Account::new(
mouse_id.clone(),
mouse_key_pair.public_key().clone(),
));
test_client.submit_blocking(register_mouse)?;

// Registering role
let role_id = RoleId::from_str("ACCESS_TO_MOUSE_METADATA")?;
let role = Role::new(role_id.clone());
let register_role = Register::role(role);
test_client.submit_blocking(register_role)?;

// Transfer domain ownership to Mouse
let domain_id = DomainId::from_str("wonderland")?;
let transfer_domain = Transfer::domain(alice_id.clone(), domain_id, mouse_id.clone());
test_client.submit_blocking(transfer_domain)?;

// Mouse grants role to Alice
let grant_role = Grant::role(role_id.clone(), alice_id.clone());
let grant_role_tx = TransactionBuilder::new(chain_id.clone(), mouse_id.clone())
.with_instructions([grant_role])
.sign(&mouse_key_pair);
test_client.submit_transaction_blocking(&grant_role_tx)?;

let set_key_value = SetKeyValue::account(
mouse_id.clone(),
Name::from_str("key").expect("Valid"),
Value::String("value".to_owned()),
);
let permission = PermissionToken::new(
"CanSetKeyValueInUserAccount".parse()?,
&json!({ "account_id": mouse_id }),
);
let grant_role_permission = Grant::role_permission(permission.clone(), role_id.clone());
let revoke_role_permission = Revoke::role_permission(permission.clone(), role_id.clone());

// Alice can't modify Mouse's metadata without proper permission token
let found_permissions = test_client
.request(FindPermissionTokensByAccountId::new(alice_id.clone()))?
.collect::<QueryResult<Vec<_>>>()?;
assert!(!found_permissions.contains(&permission));
let _ = test_client
.submit_blocking(set_key_value.clone())
.expect_err("shouldn't be able to modify metadata");

// Alice can modify Mouse's metadata after permission token is granted to role
let grant_role_permission_tx = TransactionBuilder::new(chain_id.clone(), mouse_id.clone())
.with_instructions([grant_role_permission])
.sign(&mouse_key_pair);
test_client.submit_transaction_blocking(&grant_role_permission_tx)?;
let found_permissions = test_client
.request(FindPermissionTokensByAccountId::new(alice_id.clone()))?
.collect::<QueryResult<Vec<_>>>()?;
assert!(found_permissions.contains(&permission));
test_client.submit_blocking(set_key_value.clone())?;

// Alice can't modify Mouse's metadata after permission token is removed from role
let revoke_role_permission_tx = TransactionBuilder::new(chain_id.clone(), mouse_id.clone())
.with_instructions([revoke_role_permission])
.sign(&mouse_key_pair);
test_client.submit_transaction_blocking(&revoke_role_permission_tx)?;
let found_permissions = test_client
.request(FindPermissionTokensByAccountId::new(alice_id.clone()))?
.collect::<QueryResult<Vec<_>>>()?;
assert!(!found_permissions.contains(&permission));
let _ = test_client
.submit_blocking(set_key_value.clone())
.expect_err("shouldn't be able to modify metadata");

Ok(())
}
Binary file modified configs/swarm/executor.wasm
Binary file not shown.
2 changes: 2 additions & 0 deletions core/src/smartcontracts/isi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ impl Execute for GrantBox {
match self {
Self::PermissionToken(sub_isi) => sub_isi.execute(authority, wsv),
Self::Role(sub_isi) => sub_isi.execute(authority, wsv),
Self::RolePermissionToken(sub_isi) => sub_isi.execute(authority, wsv),
}
}
}
Expand All @@ -218,6 +219,7 @@ impl Execute for RevokeBox {
match self {
Self::PermissionToken(sub_isi) => sub_isi.execute(authority, wsv),
Self::Role(sub_isi) => sub_isi.execute(authority, wsv),
Self::RolePermissionToken(sub_isi) => sub_isi.execute(authority, wsv),
}
}
}
Expand Down
60 changes: 60 additions & 0 deletions core/src/smartcontracts/isi/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,66 @@ pub mod isi {
}
}

impl Execute for Grant<PermissionToken, Role> {
#[metrics(+"grant_role_permission")]
fn execute(self, _authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> {
let role_id = self.destination_id;
let permission_token = self.object;
let permission_token_id = permission_token.definition_id.clone();

if !wsv
.permission_token_schema()
.token_ids
.contains(&permission_token_id)
{
return Err(FindError::PermissionToken(permission_token_id).into());
}

let Some(role) = wsv.world.roles.get_mut(&role_id) else {
return Err(FindError::Role(role_id).into());
};

if !role.permissions.insert(permission_token.clone()) {
return Err(RepetitionError {
instruction_type: InstructionType::Grant,
id: permission_token.definition_id.into(),
}
.into());
}

wsv.emit_events(Some(RoleEvent::PermissionAdded(RolePermissionChanged {
role_id,
permission_token_id,
})));

Ok(())
}
}

impl Execute for Revoke<PermissionToken, Role> {
#[metrics(+"grant_role_permission")]
fn execute(self, _authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> {
let role_id = self.destination_id;
let permission_token = self.object;
let permission_token_id = permission_token.definition_id.clone();

let Some(role) = wsv.world.roles.get_mut(&role_id) else {
return Err(FindError::Role(role_id).into());
};

if !role.permissions.remove(&permission_token) {
return Err(FindError::PermissionToken(permission_token_id).into());
}

wsv.emit_events(Some(RoleEvent::PermissionRemoved(RolePermissionChanged {
role_id,
permission_token_id,
})));

Ok(())
}
}

impl Execute for SetParameter {
#[metrics(+"set_parameter")]
fn execute(self, _authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> {
Expand Down
12 changes: 8 additions & 4 deletions data_model/src/events/data/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,19 @@ mod role {
/// [`PermissionToken`]s with particular [`Id`](crate::permission::token::PermissionTokenId)
/// were removed from the role.
#[has_origin(permission_removed => &permission_removed.role_id)]
PermissionRemoved(PermissionRemoved),
PermissionRemoved(RolePermissionChanged),
/// [`PermissionToken`]s with particular [`Id`](crate::permission::token::PermissionTokenId)
/// were removed from the role.
#[has_origin(permission_added => &permission_added.role_id)]
PermissionAdded(RolePermissionChanged),
}
}

#[model]
pub mod model {
use super::*;

/// Information about permissions removed from [`Role`]
/// Depending on the wrapping event, [`RolePermissionChanged`] role represents the added or removed role's permission
#[derive(
Debug,
Clone,
Expand All @@ -271,7 +275,7 @@ mod role {
)]
#[getset(get = "pub")]
#[ffi_type]
pub struct PermissionRemoved {
pub struct RolePermissionChanged {
pub role_id: RoleId,
// TODO: Skipped temporarily because of FFI
#[getset(skip)]
Expand Down Expand Up @@ -655,7 +659,7 @@ pub mod prelude {
executor::{ExecutorEvent, ExecutorFilter},
peer::{PeerEvent, PeerEventFilter, PeerFilter},
permission::PermissionTokenSchemaUpdateEvent,
role::{PermissionRemoved, RoleEvent, RoleEventFilter, RoleFilter},
role::{RoleEvent, RoleEventFilter, RoleFilter, RolePermissionChanged},
trigger::{
TriggerEvent, TriggerEventFilter, TriggerFilter, TriggerNumberOfExecutionsChanged,
},
Expand Down
32 changes: 30 additions & 2 deletions data_model/src/isi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,10 @@ impl_instruction! {
Transfer<Asset, Metadata, Account>,
Grant<PermissionToken, Account>,
Grant<RoleId, Account>,
Grant<PermissionToken, Role>,
Revoke<PermissionToken, Account>,
Revoke<RoleId, Account>,
Revoke<PermissionToken, Role>,
SetParameter,
NewParameter,
Upgrade,
Expand Down Expand Up @@ -971,6 +973,16 @@ mod transparent {
}
}

impl Grant<PermissionToken, Role> {
/// Constructs a new [`Grant`] for giving a [`PermissionToken`] to [`Role`].
pub fn role_permission(permission_token: PermissionToken, to: RoleId) -> Self {
Self {
object: permission_token,
destination_id: to,
}
}
}

impl_display! {
Grant<O, D>
where
Expand All @@ -985,7 +997,8 @@ mod transparent {

impl_into_box! {
Grant<PermissionToken, Account> |
Grant<RoleId, Account>
Grant<RoleId, Account> |
Grant<PermissionToken, Role>
=> GrantBox => InstructionBox[Grant],
=> GrantBoxRef<'a> => InstructionBoxRef<'a>[Grant]
}
Expand Down Expand Up @@ -1021,6 +1034,16 @@ mod transparent {
}
}

impl Revoke<PermissionToken, Role> {
/// Constructs a new [`Revoke`] for removing a [`PermissionToken`] from [`Role`].
pub fn role_permission(permission_token: PermissionToken, from: RoleId) -> Self {
Self {
object: permission_token,
destination_id: from,
}
}
}

impl_display! {
Revoke<O, D>
where
Expand All @@ -1035,7 +1058,8 @@ mod transparent {

impl_into_box! {
Revoke<PermissionToken, Account> |
Revoke<RoleId, Account>
Revoke<RoleId, Account> |
Revoke<PermissionToken, Role>
=> RevokeBox => InstructionBox[Revoke],
=> RevokeBoxRef<'a> => InstructionBoxRef<'a>[Revoke]
}
Expand Down Expand Up @@ -1327,6 +1351,8 @@ isi_box! {
PermissionToken(Grant<PermissionToken, Account>),
/// Grant [`Role`] to [`Account`].
Role(Grant<RoleId, Account>),
/// Grant [`PermissionToken`] to [`Role`].
RolePermissionToken(Grant<PermissionToken, Role>),
}
}

Expand All @@ -1342,6 +1368,8 @@ isi_box! {
PermissionToken(Revoke<PermissionToken, Account>),
/// Revoke [`Role`] from [`Account`].
Role(Revoke<RoleId, Account>),
/// Revoke [`PermissionToken`] from [`Account`].
RolePermissionToken(Revoke<PermissionToken, Role>),
}
}

Expand Down
2 changes: 2 additions & 0 deletions data_model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,11 @@ mod seal {

Grant<PermissionToken, Account>,
Grant<RoleId, Account>,
Grant<PermissionToken, Role>,

Revoke<PermissionToken, Account>,
Revoke<RoleId, Account>,
Revoke<PermissionToken, Role>,

SetParameter,
NewParameter,
Expand Down
6 changes: 6 additions & 0 deletions data_model/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,12 @@ pub trait Visit {
// Visit GrantBox
visit_grant_account_permission(&Grant<PermissionToken, Account>),
visit_grant_account_role(&Grant<RoleId, Account>),
visit_grant_role_permission(&Grant<PermissionToken, Role>),

// Visit RevokeBox
visit_revoke_account_permission(&Revoke<PermissionToken, Account>),
visit_revoke_account_role(&Revoke<RoleId, Account>),
visit_revoke_role_permission(&Revoke<PermissionToken, Role>),
}
}

Expand Down Expand Up @@ -389,13 +391,15 @@ pub fn visit_grant<V: Visit + ?Sized>(visitor: &mut V, authority: &AccountId, is
match isi {
GrantBox::PermissionToken(obj) => visitor.visit_grant_account_permission(authority, obj),
GrantBox::Role(obj) => visitor.visit_grant_account_role(authority, obj),
GrantBox::RolePermissionToken(obj) => visitor.visit_grant_role_permission(authority, obj),
}
}

pub fn visit_revoke<V: Visit + ?Sized>(visitor: &mut V, authority: &AccountId, isi: &RevokeBox) {
match isi {
RevokeBox::PermissionToken(obj) => visitor.visit_revoke_account_permission(authority, obj),
RevokeBox::Role(obj) => visitor.visit_revoke_account_role(authority, obj),
RevokeBox::RolePermissionToken(obj) => visitor.visit_revoke_role_permission(authority, obj),
}
}

Expand Down Expand Up @@ -448,6 +452,8 @@ leaf_visitors! {
visit_unregister_role(&Unregister<Role>),
visit_grant_account_role(&Grant<RoleId, Account>),
visit_revoke_account_role(&Revoke<RoleId, Account>),
visit_grant_role_permission(&Grant<PermissionToken, Role>),
visit_revoke_role_permission(&Revoke<PermissionToken, Role>),
visit_register_trigger(&Register<Trigger<TriggeringFilterBox>>),
visit_unregister_trigger(&Unregister<Trigger<TriggeringFilterBox>>),
visit_mint_trigger_repetitions(&Mint<u32, Trigger<TriggeringFilterBox>>),
Expand Down
Loading

0 comments on commit caf6972

Please sign in to comment.