#![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
enum Who {
Admin,
Editor,
}
use cfg_traits::{Permissions, Properties};
use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::Contains};
use frame_system::pallet_prelude::*;
pub use weights::WeightInfo;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use crate::weights::WeightInfo;
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Scope: Member + Parameter + MaxEncodedLen;
type Role: Member + Parameter;
type Storage: Member
+ Parameter
+ Properties<Property = Self::Role>
+ Default
+ MaxEncodedLen;
type Editors: Contains<(Self::AccountId, Option<Self::Role>, Self::Scope, Self::Role)>;
type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
#[pallet::constant]
type MaxRolesPerScope: Get<u32>;
type WeightInfo: WeightInfo;
}
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::storage]
#[pallet::getter(fn permission)]
pub type Permission<T: Config> =
StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Blake2_128Concat, T::Scope, T::Storage>;
#[pallet::storage]
#[pallet::getter(fn permission_count)]
pub type PermissionCount<T: Config> = StorageMap<_, Blake2_128Concat, T::Scope, u32>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Added {
to: T::AccountId,
scope: T::Scope,
role: T::Role,
},
Removed {
from: T::AccountId,
scope: T::Scope,
role: T::Role,
},
Purged {
from: T::AccountId,
scope: T::Scope,
},
}
#[pallet::error]
pub enum Error<T> {
RoleAlreadyGiven,
RoleNotGiven,
NoRoles,
NoEditor,
WrongParameters,
TooManyRoles,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(T::WeightInfo::add_as_admin().max(T::WeightInfo::add_as_editor()))]
#[pallet::call_index(0)]
pub fn add(
origin: OriginFor<T>,
with_role: T::Role,
to: T::AccountId,
scope: T::Scope,
role: T::Role,
) -> DispatchResultWithPostInfo {
let who = Self::ensure_admin_or_editor(origin, with_role, scope.clone(), role.clone())?;
Pallet::<T>::do_add(scope, to, role)?;
match who {
Who::Editor => Ok(Some(T::WeightInfo::add_as_editor()).into()),
Who::Admin => Ok(Some(T::WeightInfo::add_as_admin()).into()),
}
}
#[pallet::weight(T::WeightInfo::remove_as_editor().max(T::WeightInfo::remove_as_admin()))]
#[pallet::call_index(1)]
pub fn remove(
origin: OriginFor<T>,
with_role: T::Role,
from: T::AccountId,
scope: T::Scope,
role: T::Role,
) -> DispatchResultWithPostInfo {
let who = Self::ensure_admin_or_editor(origin, with_role, scope.clone(), role.clone())?;
Pallet::<T>::do_remove(scope, from, role)?;
match who {
Who::Editor => Ok(Some(T::WeightInfo::remove_as_editor()).into()),
Who::Admin => Ok(Some(T::WeightInfo::remove_as_admin()).into()),
}
}
#[pallet::weight(T::WeightInfo::purge())]
#[pallet::call_index(2)]
pub fn purge(origin: OriginFor<T>, scope: T::Scope) -> DispatchResult {
let from = ensure_signed(origin)?;
ensure!(
Permission::<T>::contains_key(from.clone(), scope.clone()),
Error::<T>::NoRoles
);
Permission::<T>::remove(from.clone(), scope.clone());
Self::deposit_event(Event::<T>::Purged { from, scope });
Ok(())
}
#[pallet::weight(T::WeightInfo::admin_purge())]
#[pallet::call_index(3)]
pub fn admin_purge(
origin: OriginFor<T>,
from: T::AccountId,
scope: T::Scope,
) -> DispatchResult {
Self::ensure_admin(origin)?;
ensure!(
Permission::<T>::contains_key(from.clone(), scope.clone()),
Error::<T>::NoRoles
);
Permission::<T>::remove(from.clone(), scope.clone());
Self::deposit_event(Event::<T>::Purged { from, scope });
Ok(())
}
}
}
impl<T: Config> Pallet<T> {
fn ensure_admin_or_editor(
origin: OriginFor<T>,
with_role: T::Role,
scope: T::Scope,
role: T::Role,
) -> Result<Who, DispatchError> {
match Self::ensure_admin(origin.clone()) {
Ok(()) => Ok(Who::Admin),
_ => {
let editor = ensure_signed(origin)?;
let is_editor = Permission::<T>::get(editor.clone(), scope.clone())
.map(|roles| {
roles.exists(with_role.clone())
&& T::Editors::contains(&(editor, Some(with_role), scope, role))
})
.unwrap_or(false);
ensure!(is_editor, Error::<T>::NoEditor);
Ok(Who::Editor)
}
}
}
fn ensure_admin(origin: OriginFor<T>) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin).map_or(Err(Error::<T>::NoEditor.into()), |_| Ok(()))
}
fn do_add(scope: T::Scope, to: T::AccountId, role: T::Role) -> DispatchResult {
PermissionCount::<T>::try_mutate(scope.clone(), |perm_count| {
let num_permissions = perm_count.map_or(1, |count| count + 1);
if num_permissions > T::MaxRolesPerScope::get() {
return Err(Error::<T>::TooManyRoles.into());
}
*perm_count = Some(num_permissions);
Permission::<T>::try_mutate(
to.clone(),
scope.clone(),
|maybe_roles| -> DispatchResult {
let mut roles = maybe_roles.take().unwrap_or_default();
if roles.exists(role.clone()) {
Err(Error::<T>::RoleAlreadyGiven.into())
} else {
roles
.add(role.clone())
.map_err(|_| Error::<T>::WrongParameters)?;
*maybe_roles = Some(roles);
Ok(())
}
},
)
})?;
Self::deposit_event(Event::<T>::Added { to, scope, role });
Ok(())
}
fn do_remove(scope: T::Scope, from: T::AccountId, role: T::Role) -> DispatchResult {
PermissionCount::<T>::try_mutate(scope.clone(), |perm_count| {
let num_permissions = perm_count.map_or(0, |count| count - 1);
if num_permissions == 0 {
*perm_count = None;
} else {
*perm_count = Some(num_permissions);
}
Permission::<T>::try_mutate(
from.clone(),
scope.clone(),
|maybe_roles| -> DispatchResult {
let mut roles = maybe_roles.take().ok_or(Error::<T>::NoRoles)?;
if roles.exists(role.clone()) {
roles
.rm(role.clone())
.map_err(|_| Error::<T>::WrongParameters)?;
if roles.empty() {
*maybe_roles = None
} else {
*maybe_roles = Some(roles)
}
Ok(())
} else {
Err(Error::<T>::RoleNotGiven.into())
}
},
)
})?;
Self::deposit_event(Event::<T>::Removed { from, scope, role });
Ok(())
}
}
impl<T: Config> Permissions<T::AccountId> for Pallet<T> {
type Error = DispatchError;
type Ok = ();
type Role = T::Role;
type Scope = T::Scope;
fn has(scope: T::Scope, who: T::AccountId, role: T::Role) -> bool {
Permission::<T>::get(who, scope).map_or(false, |roles| roles.exists(role))
}
fn add(scope: T::Scope, who: T::AccountId, role: T::Role) -> Result<(), DispatchError> {
Pallet::<T>::do_add(scope, who, role)
}
fn remove(scope: T::Scope, who: T::AccountId, role: T::Role) -> Result<(), DispatchError> {
Pallet::<T>::do_remove(scope, who, role)
}
}