#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use frame_support::{
decl_error, decl_event, decl_module, decl_storage,
ensure,
traits::Get,
dispatch::DispatchResult
};
use sp_runtime::RuntimeDebug;
use sp_std::{collections::btree_set::BTreeSet, iter::FromIterator, prelude::*};
use frame_system::{self as system, ensure_signed};
use df_traits::{
PermissionChecker, SpaceFollowsProvider, SpaceForRolesProvider,
moderation::{IsAccountBlocked, IsContentBlocked},
};
use pallet_permissions::{Module as Permissions, SpacePermission, SpacePermissionSet};
use pallet_utils::{Module as Utils, Error as UtilsError, SpaceId, User, WhoAndWhen, Content};
pub mod functions;
pub mod rpc;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
type RoleId = u64;
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)]
pub struct Role<T: Trait> {
pub created: WhoAndWhen<T>,
pub updated: Option<WhoAndWhen<T>>,
pub id: RoleId,
pub space_id: SpaceId,
pub disabled: bool,
pub expires_at: Option<T::BlockNumber>,
pub content: Content,
pub permissions: SpacePermissionSet,
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)]
pub struct RoleUpdate {
pub disabled: Option<bool>,
pub content: Option<Content>,
pub permissions: Option<SpacePermissionSet>,
}
pub trait Trait: system::Trait
+ pallet_permissions::Trait
+ pallet_utils::Trait
{
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
type MaxUsersToProcessPerDeleteRole: Get<u16>;
type Spaces: SpaceForRolesProvider<AccountId=Self::AccountId>;
type SpaceFollows: SpaceFollowsProvider<AccountId=Self::AccountId>;
type IsAccountBlocked: IsAccountBlocked<Self::AccountId>;
type IsContentBlocked: IsContentBlocked;
}
decl_event!(
pub enum Event<T> where
<T as system::Trait>::AccountId
{
RoleCreated(AccountId, SpaceId, RoleId),
RoleUpdated(AccountId, RoleId),
RoleDeleted(AccountId, RoleId),
RoleGranted(AccountId, RoleId, Vec<User<AccountId>>),
RoleRevoked(AccountId, RoleId, Vec<User<AccountId>>),
}
);
decl_error! {
pub enum Error for Module<T: Trait> {
RoleNotFound,
RoleIdOverflow,
NoPermissionToManageRoles,
NoUpdatesProvided,
NoPermissionsProvided,
NoUsersProvided,
TooManyUsersToDelete,
RoleAlreadyDisabled,
RoleAlreadyEnabled,
}
}
pub const FIRST_ROLE_ID: u64 = 1;
decl_storage! {
trait Store for Module<T: Trait> as PermissionsModule {
pub NextRoleId get(fn next_role_id): RoleId = FIRST_ROLE_ID;
pub RoleById get(fn role_by_id):
map hasher(twox_64_concat) RoleId => Option<Role<T>>;
pub UsersByRoleId get(fn users_by_role_id):
map hasher(twox_64_concat) RoleId => Vec<User<T::AccountId>>;
pub RoleIdsBySpaceId get(fn role_ids_by_space_id):
map hasher(twox_64_concat) SpaceId => Vec<RoleId>;
pub RoleIdsByUserInSpace get(fn role_ids_by_user_in_space): double_map
hasher(blake2_128_concat) User<T::AccountId>,
hasher(twox_64_concat) SpaceId
=> Vec<RoleId>;
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
const MaxUsersToProcessPerDeleteRole: u16 = T::MaxUsersToProcessPerDeleteRole::get();
type Error = Error<T>;
fn deposit_event() = default;
#[weight = 10_000 + T::DbWeight::get().reads_writes(2, 3)]
pub fn create_role(
origin,
space_id: SpaceId,
time_to_live: Option<T::BlockNumber>,
content: Content,
permissions: Vec<SpacePermission>
) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(!permissions.is_empty(), Error::<T>::NoPermissionsProvided);
Utils::<T>::is_valid_content(content.clone())?;
ensure!(T::IsContentBlocked::is_allowed_content(content.clone(), space_id), UtilsError::<T>::ContentIsBlocked);
Self::ensure_role_manager(who.clone(), space_id)?;
let permissions_set = BTreeSet::from_iter(permissions.into_iter());
let new_role = Role::<T>::new(who.clone(), space_id, time_to_live, content, permissions_set)?;
let next_role_id = new_role.id.checked_add(1).ok_or(Error::<T>::RoleIdOverflow)?;
NextRoleId::put(next_role_id);
<RoleById<T>>::insert(new_role.id, new_role.clone());
RoleIdsBySpaceId::mutate(space_id, |role_ids| { role_ids.push(new_role.id) });
Self::deposit_event(RawEvent::RoleCreated(who, space_id, new_role.id));
Ok(())
}
#[weight = 10_000 + T::DbWeight::get().reads_writes(2, 1)]
pub fn update_role(origin, role_id: RoleId, update: RoleUpdate) -> DispatchResult {
let who = ensure_signed(origin)?;
let has_updates =
update.disabled.is_some() ||
update.content.is_some() ||
update.permissions.is_some();
ensure!(has_updates, Error::<T>::NoUpdatesProvided);
let mut role = Self::require_role(role_id)?;
Self::ensure_role_manager(who.clone(), role.space_id)?;
let mut is_update_applied = false;
if let Some(disabled) = update.disabled {
if disabled != role.disabled {
role.set_disabled(disabled)?;
is_update_applied = true;
}
}
if let Some(content) = update.content {
if content != role.content {
Utils::<T>::is_valid_content(content.clone())?;
ensure!(T::IsContentBlocked::is_allowed_content(content.clone(), role.space_id), UtilsError::<T>::ContentIsBlocked);
role.content = content;
is_update_applied = true;
}
}
if let Some(permissions) = update.permissions {
if !permissions.is_empty() {
let permissions_diff: Vec<_> = permissions.symmetric_difference(&role.permissions).cloned().collect();
if !permissions_diff.is_empty() {
role.permissions = permissions;
is_update_applied = true;
}
}
}
if is_update_applied {
role.updated = Some(WhoAndWhen::<T>::new(who.clone()));
<RoleById<T>>::insert(role_id, role);
Self::deposit_event(RawEvent::RoleUpdated(who, role_id));
}
Ok(())
}
#[weight = 1_000_000 + T::DbWeight::get().reads_writes(6, 5)]
pub fn delete_role(origin, role_id: RoleId) -> DispatchResult {
let who = ensure_signed(origin)?;
let role = Self::require_role(role_id)?;
Self::ensure_role_manager(who.clone(), role.space_id)?;
let users = Self::users_by_role_id(role_id);
ensure!(
users.len() <= T::MaxUsersToProcessPerDeleteRole::get() as usize,
Error::<T>::TooManyUsersToDelete
);
let role_idx_by_space_opt = Self::role_ids_by_space_id(role.space_id).iter()
.position(|x| { *x == role_id });
if let Some(role_idx) = role_idx_by_space_opt {
RoleIdsBySpaceId::mutate(role.space_id, |n| { n.swap_remove(role_idx) });
}
role.revoke_from_users(users);
<RoleById<T>>::remove(role_id);
<UsersByRoleId<T>>::remove(role_id);
Self::deposit_event(RawEvent::RoleDeleted(who, role_id));
Ok(())
}
#[weight = 1_000_000 + T::DbWeight::get().reads_writes(4, 2)]
pub fn grant_role(origin, role_id: RoleId, users: Vec<User<T::AccountId>>) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(!users.is_empty(), Error::<T>::NoUsersProvided);
let users_set: BTreeSet<User<T::AccountId>> = Utils::<T>::convert_users_vec_to_btree_set(users)?;
let role = Self::require_role(role_id)?;
Self::ensure_role_manager(who.clone(), role.space_id)?;
for user in users_set.iter() {
if !Self::users_by_role_id(role_id).contains(&user) {
<UsersByRoleId<T>>::mutate(role_id, |users| { users.push(user.clone()); });
}
if !Self::role_ids_by_user_in_space(user.clone(), role.space_id).contains(&role_id) {
<RoleIdsByUserInSpace<T>>::mutate(user.clone(), role.space_id, |roles| { roles.push(role_id); })
}
}
Self::deposit_event(RawEvent::RoleGranted(who, role_id, users_set.iter().cloned().collect()));
Ok(())
}
#[weight = 1_000_000 + T::DbWeight::get().reads_writes(4, 2)]
pub fn revoke_role(origin, role_id: RoleId, users: Vec<User<T::AccountId>>) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(!users.is_empty(), Error::<T>::NoUsersProvided);
let role = Self::require_role(role_id)?;
Self::ensure_role_manager(who.clone(), role.space_id)?;
role.revoke_from_users(users.clone());
Self::deposit_event(RawEvent::RoleRevoked(who, role_id, users));
Ok(())
}
}
}