Skip to content
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
201 changes: 199 additions & 2 deletions near-plugins-derive/src/access_controllable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,167 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
}
}

fn init_super_admin(&mut self, account_id: &::near_sdk::AccountId) -> bool {
let flag = <#bitflags_type>::from_bits(<#role_type>::acl_super_admin_permission())
.expect(#ERR_PARSE_BITFLAG);
// TODO use self.get_bearers() once the following PR is merged
// https://github.com/aurora-is-near/near-plugins/pull/12
let number_super_admins = match self.bearers.get(&flag) {
None => 0,
Some(bearers) => bearers.len(),
};
if number_super_admins > 0 {
return false;
}
let res = self.add_super_admin_unchecked(account_id);
assert!(res, "Failed to init super-admin.");
res
}

/// Makes `account_id` a super-admin __without__ checking any permissions.
/// It returns whether `account_id` is a new super-admin.
///
/// Note that there may be zero or more super-admins.
fn add_super_admin_unchecked(&mut self, account_id: &::near_sdk::AccountId) -> bool {
let flag = <#bitflags_type>::from_bits(<#role_type>::acl_super_admin_permission())
.expect(#ERR_PARSE_BITFLAG);
let mut permissions = self.get_or_init_permissions(account_id);

let is_new_super_admin = !permissions.contains(flag);
if is_new_super_admin {
permissions.insert(flag);
self.permissions.insert(account_id, &permissions);
self.add_bearer(flag, account_id);

let event = ::#cratename::access_controllable::events::SuperAdminAdded {
account: account_id.clone(),
by: ::near_sdk::env::predecessor_account_id(),
};
event.emit();
}

is_new_super_admin
}

fn is_super_admin(&self, account_id: &::near_sdk::AccountId) -> bool {
let permissions = {
match self.permissions.get(account_id) {
Some(permissions) => permissions,
None => return false,
}
};
let super_admin = <#bitflags_type>::from_bits(<#role_type>::acl_super_admin_permission())
.expect(#ERR_PARSE_BITFLAG);
permissions.contains(super_admin)
}

/// Revokes super-admin permissions from `account_id` without checking any
/// permissions. It returns whether `account_id` was a super-admin.
fn revoke_super_admin_unchecked(&mut self, account_id: &::near_sdk::AccountId) -> bool {
let flag = <#bitflags_type>::from_bits(<#role_type>::acl_super_admin_permission())
.expect(#ERR_PARSE_BITFLAG);
let mut permissions = self.get_or_init_permissions(account_id);

let was_super_admin = permissions.contains(flag);
if was_super_admin {
permissions.remove(flag);
self.permissions.insert(account_id, &permissions);
self.remove_bearer(flag, account_id);

let event = ::#cratename::access_controllable::events::SuperAdminRevoked {
account: account_id.clone(),
by: ::near_sdk::env::predecessor_account_id(),
};
event.emit();
}

was_super_admin
}

fn add_admin(&mut self, role: #role_type, account_id: &::near_sdk::AccountId) -> Option<bool> {
if !self.is_admin(role, &::near_sdk::env::predecessor_account_id()) {
return None;
}
Some(self.add_admin_unchecked(role, account_id))
}

/// Makes `account_id` an admin for role, __without__ checking any
/// permissions. Returns whether `account_id` is a new admin for `role`.
///
/// Note that any role may have multiple (or zero) admins.
fn add_admin_unchecked(&mut self, role: #role_type, account_id: &::near_sdk::AccountId) -> bool {
let flag = <#bitflags_type>::from_bits(role.acl_admin_permission())
.expect(#ERR_PARSE_BITFLAG);
let mut permissions = self.get_or_init_permissions(account_id);

let is_new_admin = !permissions.contains(flag);
if is_new_admin {
permissions.insert(flag);
self.permissions.insert(account_id, &permissions);
self.add_bearer(flag, account_id);

let event = ::#cratename::access_controllable::events::AdminAdded {
role: role.into(),
account: account_id.clone(),
by: ::near_sdk::env::predecessor_account_id(),
};
event.emit();
}

is_new_admin
}

fn is_admin(&self, role: #role_type, account_id: &::near_sdk::AccountId) -> bool {
let permissions = {
match self.permissions.get(account_id) {
Some(permissions) => permissions,
None => return false,
}
};
let super_admin = <#bitflags_type>::from_bits(<#role_type>::acl_super_admin_permission())
.expect(#ERR_PARSE_BITFLAG);
let role_admin = <#bitflags_type>::from_bits(role.acl_admin_permission())
.expect(#ERR_PARSE_BITFLAG);
permissions.contains(super_admin) || permissions.contains(role_admin)
}

fn revoke_admin(&mut self, role: #role_type, account_id: &::near_sdk::AccountId) -> Option<bool> {
if !self.is_admin(role, &::near_sdk::env::predecessor_account_id()) {
return None;
}
Some(self.revoke_admin_unchecked(role, account_id))
}

fn renounce_admin(&mut self, role: #role_type) -> bool {
self.revoke_admin_unchecked(role, &::near_sdk::env::predecessor_account_id())
}

/// Revokes admin permissions from `account_id` __without__ checking any
/// permissions. Returns whether `account_id` was an admin for `role`.
fn revoke_admin_unchecked(&mut self, role: #role_type, account_id: &::near_sdk::AccountId) -> bool {
let flag = <#bitflags_type>::from_bits(role.acl_admin_permission())
.expect(#ERR_PARSE_BITFLAG);
let mut permissions = self.get_or_init_permissions(account_id);

let was_admin = permissions.contains(flag);
if was_admin {
permissions.remove(flag);
self.permissions.insert(account_id, &permissions);
self.remove_bearer(flag, account_id);

let event = ::#cratename::access_controllable::events::AdminRevoked {
role: role.into(),
account: account_id.clone(),
by: ::near_sdk::env::predecessor_account_id(),
};
event.emit();
}

was_admin
}

/// Grants `role` to `account_id` __without__ checking any permissions.
/// Returns whether `role` was newly granted to `account_id`.
fn grant_role_unchecked(&mut self, role: #role_type, account_id: &::near_sdk::AccountId) -> bool {
let flag = <#bitflags_type>::from_bits(role.acl_permission())
.expect(#ERR_PARSE_BITFLAG);
Expand Down Expand Up @@ -185,6 +346,19 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
self.bearers.insert(&permission, &set);
}
}

/// Removes `account_id` from the set of `permission` bearers.
fn remove_bearer(&mut self, permission: #bitflags_type, account_id: &::near_sdk::AccountId) {
// If `permission` is invalid (more than one active bit), this
// function is a no-op, due to the check in `add_bearer`.
let mut set = match self.bearers.get(&permission) {
Some(set) => set,
None => return,
};
if let true = set.remove(account_id) {
self.bearers.insert(&permission, &set);
}
}
}

// Note that `#[near-bindgen]` exposes non-public functions in trait
Expand All @@ -198,10 +372,33 @@ pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream
(#storage_prefix).as_bytes()
}

fn acl_is_super_admin(&self, account_id: ::near_sdk::AccountId) -> bool {
self.#acl_field.is_super_admin(&account_id)
}

#[private]
fn acl_grant_role_unchecked(&mut self, role: String, account_id: ::near_sdk::AccountId) -> bool {
fn acl_init_super_admin(&mut self, account_id: ::near_sdk::AccountId) -> bool {
self.#acl_field.init_super_admin(&account_id)
}

fn acl_add_admin(&mut self, role: String, account_id: ::near_sdk::AccountId) -> Option<bool> {
let role = <#role_type>::try_from(role.as_str()).expect(#ERR_PARSE_ROLE);
self.#acl_field.add_admin(role, &account_id)
}

fn acl_is_admin(&self, role: String, account_id: ::near_sdk::AccountId) -> bool {
let role = <#role_type>::try_from(role.as_str()).expect(#ERR_PARSE_ROLE);
self.#acl_field.is_admin(role, &account_id)
}

fn acl_revoke_admin(&mut self, role: String, account_id: ::near_sdk::AccountId) -> Option<bool> {
let role = <#role_type>::try_from(role.as_str()).expect(#ERR_PARSE_ROLE);
self.#acl_field.revoke_admin(role, &account_id)
}

fn acl_renounce_admin(&mut self, role: String) -> bool {
let role = <#role_type>::try_from(role.as_str()).expect(#ERR_PARSE_ROLE);
self.#acl_field.grant_role_unchecked(role, &account_id)
self.#acl_field.renounce_admin(role)
}

fn acl_has_role(&self, role: String, account_id: ::near_sdk::AccountId) -> bool {
Expand Down
2 changes: 1 addition & 1 deletion near-plugins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ anyhow = "1.0"
borsh = "0.9"
tokio = { version = "1", features = ["full"] }
# Feature `unstable` is required for compiling contracts during tests.
workspaces = { version = "0.5", features = ["unstable"] }
workspaces = { version = "0.6", features = ["unstable"] }
132 changes: 129 additions & 3 deletions near-plugins/src/access_controllable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,47 @@ pub trait AccessControllable {
/// Returns the storage prefix for collections related to access control.
fn acl_storage_prefix() -> &'static [u8];

/// Grants `role` to `account_id` __without__ checking any permissions.
/// Returns whether `role` was newly granted to `account_id`.
fn acl_grant_role_unchecked(&mut self, role: String, account_id: AccountId) -> bool;
/// Adds `account_id` as super-admin __without__ checking any permissions in
/// case there are no super-admins. This function can be used to add a
/// super-admin during contract initialization. Moreover, it may provide a
/// recovery mechanism if (mistakenly) all super-admins have been removed.
///
/// The return value indicates whether `account_id` was added as
/// super-admin.
///
/// It is `#[private]` in the implementation provided by this trait, i.e.
/// only the contract itself may call this method.
fn acl_init_super_admin(&mut self, account_id: AccountId) -> bool;

/// Returns whether `account_id` is a super-admin.
fn acl_is_super_admin(&self, account_id: AccountId) -> bool;

/// Makes `account_id` an admin provided that the predecessor has sufficient
/// permissions, i.e. is an admin as defined by [`acl_is_admin`].
///
/// In case of sufficient permissions, the returned `Some(bool)` indicates
/// whether `account_id` is a new admin for `role`. Without permissions,
/// `None` is returned and internal state is not modified.
///
/// Note that any role may have multiple (or zero) admins.
fn acl_add_admin(&mut self, role: String, account_id: AccountId) -> Option<bool>;

/// Returns whether `account_id` is an admin for `role`. Super-admins are
/// admins for _every_ role.
fn acl_is_admin(&self, role: String, account_id: AccountId) -> bool;

/// Revokes admin permissions for `role` from `account_id` provided that the
/// predecessor has sufficient permissions, i.e. is an admin as defined by
/// [`acl_is_admin`].
///
/// In case of sufficient permissions, the returned `Some(bool)` indicates
/// whether `account_id` was an admin for `role`. Without permissions,
/// `None` is returned and internal state is not modified.
fn acl_revoke_admin(&mut self, role: String, account_id: AccountId) -> Option<bool>;

/// Revokes admin permissions for `role` from the predecessor. Returns
/// whether the predecessor was an admin for `role`.
fn acl_renounce_admin(&mut self, role: String) -> bool;

/// Returns whether `account_id` has been granted `role`.
fn acl_has_role(&self, role: String, account_id: AccountId) -> bool;
Expand All @@ -38,6 +76,94 @@ pub mod events {
const STANDARD: &str = "AccessControllable";
const VERSION: &str = "1.0.0";

/// Event emitted when an accout is made super-admin.
#[derive(Serialize, Clone)]
#[serde(crate = "near_sdk::serde")]
pub struct SuperAdminAdded {
/// Account that was added as super-admin.
pub account: AccountId,
/// Account that added the super-admin.
pub by: AccountId,
}

impl AsEvent<SuperAdminAdded> for SuperAdminAdded {
fn metadata(&self) -> EventMetadata<SuperAdminAdded> {
EventMetadata {
standard: STANDARD.to_string(),
version: VERSION.to_string(),
event: "super_admin_added".to_string(),
data: Some(self.clone()),
}
}
}

/// Event emitted when super-admin permissions are revoked.
#[derive(Serialize, Clone)]
#[serde(crate = "near_sdk::serde")]
pub struct SuperAdminRevoked {
/// Account from whom permissions were revoked.
pub account: AccountId,
/// Account that revoked the permissions.
pub by: AccountId,
}

impl AsEvent<SuperAdminRevoked> for SuperAdminRevoked {
fn metadata(&self) -> EventMetadata<SuperAdminRevoked> {
EventMetadata {
standard: STANDARD.to_string(),
version: VERSION.to_string(),
event: "super_admin_revoked".to_string(),
data: Some(self.clone()),
}
}
}

/// Event emitted when an account is made admin.
#[derive(Serialize, Clone)]
#[serde(crate = "near_sdk::serde")]
pub struct AdminAdded {
/// The Role for which an admin was added.
pub role: String,
/// Account that was added as admin.
pub account: AccountId,
/// Account that added the admin.
pub by: AccountId,
}

impl AsEvent<AdminAdded> for AdminAdded {
fn metadata(&self) -> EventMetadata<AdminAdded> {
EventMetadata {
standard: STANDARD.to_string(),
version: VERSION.to_string(),
event: "admin_added".to_string(),
data: Some(self.clone()),
}
}
}

/// Event emitted when admin permissions are revoked.
#[derive(Serialize, Clone)]
#[serde(crate = "near_sdk::serde")]
pub struct AdminRevoked {
/// The Role for which an admin was revoked.
pub role: String,
/// Account from whom permissions where revoked.
pub account: AccountId,
/// Account that revoked the admin.
pub by: AccountId,
}

impl AsEvent<AdminRevoked> for AdminRevoked {
fn metadata(&self) -> EventMetadata<AdminRevoked> {
EventMetadata {
standard: STANDARD.to_string(),
version: VERSION.to_string(),
event: "admin_revoked".to_string(),
data: Some(self.clone()),
}
}
}

/// Event emitted when a role is granted to an account.
#[derive(Serialize, Clone)]
#[serde(crate = "near_sdk::serde")]
Expand Down
Loading