Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] #4244: Allow granting/revoking role's permissions #4349

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
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(())
}
2 changes: 1 addition & 1 deletion client/tests/integration/triggers/trigger_rollback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use test_network::*;

#[test]
fn failed_trigger_revert() -> Result<()> {
let (_rt, _peer, client) = <PeerBuilder>::new().with_port(11_110).start_with_runtime();
let (_rt, _peer, client) = <PeerBuilder>::new().with_port(11_150).start_with_runtime();
wait_for_genesis_committed(&[client.clone()], 0);

//When
Expand Down
Binary file modified configs/swarm/executor.wasm
Binary file not shown.
8 changes: 4 additions & 4 deletions core/src/smartcontracts/isi/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ pub mod isi {
}
}

impl Execute for Grant<PermissionToken> {
impl Execute for Grant<PermissionToken, Account> {
#[metrics(+"grant_account_permission")]
fn execute(self, _authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> {
let account_id = self.destination_id;
Expand Down Expand Up @@ -296,7 +296,7 @@ pub mod isi {
}
}

impl Execute for Revoke<PermissionToken> {
impl Execute for Revoke<PermissionToken, Account> {
#[metrics(+"revoke_account_permission")]
fn execute(self, _authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> {
let account_id = self.destination_id;
Expand All @@ -320,7 +320,7 @@ pub mod isi {
}
}

impl Execute for Grant<RoleId> {
impl Execute for Grant<RoleId, Account> {
#[metrics(+"grant_account_role")]
fn execute(self, _authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> {
let account_id = self.destination_id;
Expand Down Expand Up @@ -371,7 +371,7 @@ pub mod isi {
}
}

impl Execute for Revoke<RoleId> {
impl Execute for Revoke<RoleId, Account> {
#[metrics(+"revoke_account_role")]
fn execute(self, _authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> {
let account_id = self.destination_id;
Expand Down
2 changes: 2 additions & 0 deletions core/src/smartcontracts/isi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,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 @@ -188,6 +189,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> {
mversic marked this conversation as resolved.
Show resolved Hide resolved
#[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) {
mversic marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -247,15 +247,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 added to 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 @@ -272,7 +276,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 @@ -656,7 +660,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
Loading
Loading