use std::{
collections::{HashMap, HashSet},
fmt::Display,
hash::Hash,
};
use crate::{
canister::call::call_canister,
identity::{caller, CanisterId, UserId},
times::schedulable::async_execute,
};
use super::recordable::{format_option, format_option_with_func};
#[derive(candid::CandidType, serde::Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub enum Permission {
Permitted(String),
Forbidden(String),
}
impl Permission {
pub fn is_permit(&self) -> bool {
matches!(self, Self::Permitted(_))
}
pub fn is_forbid(&self) -> bool {
matches!(self, Self::Forbidden(_))
}
pub fn by_permit(name: &str) -> Self {
Permission::Permitted(name.to_string())
}
pub fn by_forbid(name: &str) -> Self {
Permission::Forbidden(name.to_string())
}
pub fn name<'a>(&'a self) -> &'a str {
match self {
Permission::Permitted(name) => name,
Permission::Forbidden(name) => name,
}
}
}
#[derive(candid::CandidType, serde::Deserialize, Debug, Default)]
pub struct Permissions {
pub host: Option<CanisterId>, pub listener: Option<CanisterId>, pub permissions: HashSet<Permission>, pub user_permissions: HashMap<UserId, HashSet<Permission>>, pub role_permissions: HashMap<String, HashSet<Permission>>, pub user_roles: HashMap<UserId, HashSet<String>>, }
#[derive(candid::CandidType, serde::Deserialize, Debug, Clone)]
pub enum PermissionUpdatedArg<T: candid::CandidType + Eq + Hash> {
UpdateUserPermission(UserId, Option<HashSet<T>>),
UpdateRolePermission(String, Option<HashSet<T>>),
UpdateUserRole(UserId, Option<HashSet<String>>),
}
#[derive(candid::CandidType, serde::Deserialize, Debug, Clone)]
pub struct PermissionReplacedArg<T: candid::CandidType + Eq + Hash> {
pub permissions: HashSet<T>,
pub user_permissions: HashMap<UserId, HashSet<T>>,
pub role_permissions: HashMap<String, HashSet<T>>,
pub user_roles: HashMap<UserId, HashSet<String>>,
}
pub trait Permissable {
fn permission_host_find(&self) -> Option<CanisterId>;
fn permission_users(&self) -> HashSet<UserId>;
fn permission_has(&self, user_id: &UserId, permission: &Permission) -> bool;
fn permission_owned(&self, user_id: &UserId) -> HashMap<&Permission, bool>;
fn permission_assigned(&self, user_id: &UserId) -> Option<&HashSet<Permission>>;
fn permission_roles_all(&self) -> &HashMap<String, HashSet<Permission>>;
fn permission_roles_by_user(&self, user_id: &UserId) -> Option<&HashSet<String>>;
fn permission_host_update(&mut self, host: Option<CanisterId>, notice: bool);
fn permission_reset(&mut self, permissions: HashSet<Permission>);
fn permission_update(&mut self, args: Vec<PermissionUpdatedArg<Permission>>);
fn permission_replace(
&mut self,
arg: PermissionReplacedArg<Permission>,
) -> PermissionReplacedArg<Permission>;
}
impl Permissions {
fn permission_register(&self) {
if let Some(host) = self.host {
let arg = PermissionReplacedArg {
permissions: self.permissions.clone(),
user_permissions: self.user_permissions.clone(),
role_permissions: self.role_permissions.clone(),
user_roles: self.user_roles.clone(),
};
let _ = async_execute(move || {
ic_cdk::spawn(async move {
call_canister::<(PermissionReplacedArg<Permission>,), ()>(
host,
"business_permission_register",
(arg,),
)
.await
.unwrap();
});
});
}
}
fn permission_notice_updated(&self, args: Vec<PermissionUpdatedArg<Permission>>) {
let caller = caller();
if let Some(host) = self.host {
if caller != host {
let args = args.clone();
let _ = async_execute(move || {
ic_cdk::spawn(async move {
call_canister::<(Vec<PermissionUpdatedArg<Permission>>,), ()>(
host,
"business_permission_update",
(args,),
)
.await
.unwrap();
});
});
}
}
if let Some(listener) = self.listener {
if caller != listener {
let _ = async_execute(move || {
ic_cdk::spawn(async move {
call_canister::<(Vec<PermissionUpdatedArg<String>>,), ()>(
listener,
"permission_update",
(args.into_iter().map(|a| a.into()).collect(),),
)
.await
.unwrap();
});
});
}
}
}
fn permission_notice_replaced(&self, arg: PermissionReplacedArg<Permission>) {
let caller = caller();
if let Some(host) = self.host {
if caller != host {
let arg = arg.clone();
let _ = async_execute(move || {
ic_cdk::spawn(async move {
call_canister::<(PermissionReplacedArg<Permission>,), ()>(
host,
"business_permission_replace",
(arg,),
)
.await
.unwrap();
});
});
}
}
if let Some(listener) = self.listener {
if caller != listener {
let _ = async_execute(move || {
ic_cdk::spawn(async move {
call_canister::<(PermissionReplacedArg<String>,), ()>(
listener,
"permission_replace",
(arg.into(),),
)
.await
.unwrap();
});
});
}
}
}
}
fn assure_exist<T: Clone + Eq + Hash + Display, F: Fn(&T) -> bool>(
list: &Option<HashSet<T>>,
f: F,
tips: &str,
) {
if let Some(list) = list {
for t in list {
if !f(t) {
panic!("{} {} is invalid.", tips, t)
}
}
}
}
impl Permissable for Permissions {
fn permission_host_find(&self) -> Option<CanisterId> {
self.host
}
fn permission_users(&self) -> HashSet<UserId> {
let mut users: HashSet<&UserId> = self.user_roles.keys().collect();
users.extend(self.user_permissions.keys());
users.into_iter().map(|u| *u).collect()
}
fn permission_has(&self, user_id: &UserId, permission: &Permission) -> bool {
if let Some(permissions) = self.user_permissions.get(user_id) {
if permissions.contains(permission) {
return match permission {
Permission::Permitted(_) => true,
Permission::Forbidden(_) => false,
};
}
}
if let Some(roles) = self.user_roles.get(user_id) {
for role in roles {
if let Some(permissions) = self.role_permissions.get(role) {
if permissions.contains(permission) {
return match permission {
Permission::Permitted(_) => true,
Permission::Forbidden(_) => false,
};
}
}
}
}
match permission {
Permission::Permitted(_) => false,
Permission::Forbidden(_) => true,
}
}
fn permission_owned(&self, user_id: &UserId) -> HashMap<&Permission, bool> {
self.permissions
.iter()
.map(|permission| (permission, self.permission_has(user_id, permission)))
.collect()
}
fn permission_assigned(&self, user_id: &UserId) -> Option<&HashSet<Permission>> {
self.user_permissions.get(user_id)
}
fn permission_roles_all(&self) -> &HashMap<String, HashSet<Permission>> {
&self.role_permissions
}
fn permission_roles_by_user(&self, user_id: &UserId) -> Option<&HashSet<String>> {
self.user_roles.get(user_id)
}
fn permission_host_update(&mut self, host: Option<CanisterId>, notice: bool) {
self.host = host;
if notice {
self.permission_register();
}
}
fn permission_reset(&mut self, permissions: HashSet<Permission>) {
self.permissions = permissions;
self.role_permissions
.iter_mut()
.for_each(|(_, permissions)| {
let mut removed = Vec::new();
for permission in permissions.iter() {
if !self.permissions.contains(permission) {
removed.push(permission.clone());
}
}
for permission in removed {
permissions.remove(&permission);
}
});
self.user_permissions
.iter_mut()
.for_each(|(_, permissions)| {
let mut removed = Vec::new();
for permission in permissions.iter() {
if !self.permissions.contains(permission) {
removed.push(permission.clone());
}
}
for permission in removed {
permissions.remove(&permission);
}
});
}
fn permission_update(&mut self, args: Vec<PermissionUpdatedArg<Permission>>) {
let mut changed = false;
for arg in args.iter() {
match arg {
PermissionUpdatedArg::UpdateUserPermission(user_id, permissions) => {
assure_exist(permissions, |p| self.permissions.contains(p), "Permission");
let exist = self.user_permissions.get(user_id);
if let Some(permissions) = &permissions {
if let Some(exist) = exist {
if exist == permissions {
continue;
}
}
} else {
if let None = exist {
continue;
}
}
if let Some(permissions) = permissions {
self.user_permissions
.insert(user_id.clone(), permissions.clone());
} else {
self.user_permissions.remove(user_id);
}
changed = true;
}
PermissionUpdatedArg::UpdateRolePermission(role, permissions) => {
assure_exist(permissions, |p| self.permissions.contains(p), "Permission");
let exist = self.role_permissions.get(role);
if let Some(permissions) = permissions {
if let Some(exist) = exist {
if exist == permissions {
continue;
}
}
} else {
if let None = exist {
continue;
}
}
if let Some(permissions) = permissions {
self.role_permissions
.insert(role.clone(), permissions.clone());
} else {
self.role_permissions.remove(role);
self.user_roles.iter_mut().for_each(|(_, roles)| {
let mut removed = Vec::new();
for role in roles.iter() {
if !self.role_permissions.contains_key(role) {
removed.push(role.clone());
}
}
for role in removed {
roles.remove(&role);
}
});
}
changed = true;
}
PermissionUpdatedArg::UpdateUserRole(user_id, roles) => {
assure_exist(roles, |r| self.role_permissions.contains_key(r), "Role");
let exist = self.user_roles.get(user_id);
if let Some(roles) = &roles {
if let Some(exist) = exist {
if exist == roles {
continue;
}
}
} else {
if let None = exist {
continue;
}
}
if let Some(roles) = roles {
self.user_roles.insert(*user_id, roles.clone());
} else {
self.user_roles.remove(user_id);
}
changed = true;
}
}
}
if changed {
self.permission_notice_updated(args);
}
}
fn permission_replace(
&mut self,
arg: PermissionReplacedArg<Permission>,
) -> PermissionReplacedArg<Permission> {
let permissions = std::mem::replace(&mut self.permissions, arg.permissions.clone());
let user_permissions =
std::mem::replace(&mut self.user_permissions, arg.user_permissions.clone());
let role_permissions =
std::mem::replace(&mut self.role_permissions, arg.role_permissions.clone());
let user_roles = std::mem::replace(&mut self.user_roles, arg.user_roles.clone());
self.permission_notice_replaced(arg);
PermissionReplacedArg {
permissions,
user_permissions,
role_permissions,
user_roles,
}
}
}
impl From<PermissionUpdatedArg<Permission>> for PermissionUpdatedArg<String> {
fn from(value: PermissionUpdatedArg<Permission>) -> Self {
match value {
PermissionUpdatedArg::UpdateUserPermission(user_id, permissions) => {
PermissionUpdatedArg::UpdateUserPermission(
user_id,
permissions.and_then(|ps| {
Some(ps.into_iter().map(|p| p.name().to_string()).collect())
}),
)
}
PermissionUpdatedArg::UpdateRolePermission(role, permissions) => {
PermissionUpdatedArg::UpdateRolePermission(
role,
permissions.and_then(|ps| {
Some(ps.into_iter().map(|p| p.name().to_string()).collect())
}),
)
}
PermissionUpdatedArg::UpdateUserRole(user_id, roles) => {
PermissionUpdatedArg::UpdateUserRole(user_id, roles)
}
}
}
}
impl From<PermissionReplacedArg<Permission>> for PermissionReplacedArg<String> {
fn from(value: PermissionReplacedArg<Permission>) -> Self {
PermissionReplacedArg {
permissions: value
.permissions
.into_iter()
.map(|p| p.name().to_string())
.collect(),
user_permissions: value
.user_permissions
.into_iter()
.map(|(user_id, permissions)| {
(
user_id,
permissions
.into_iter()
.map(|p| p.name().to_string())
.collect(),
)
})
.collect(),
role_permissions: value
.role_permissions
.into_iter()
.map(|(role, permissions)| {
(
role,
permissions
.into_iter()
.map(|p| p.name().to_string())
.collect(),
)
})
.collect(),
user_roles: value.user_roles,
}
}
}
impl PermissionUpdatedArg<String> {
pub fn into_permission<F: Fn(&str) -> Permission>(
self,
f: F,
) -> PermissionUpdatedArg<Permission> {
match self {
PermissionUpdatedArg::UpdateUserPermission(user_id, permissions) => {
PermissionUpdatedArg::UpdateUserPermission(
user_id,
permissions.and_then(|ps| Some(ps.into_iter().map(|p| f(&p)).collect())),
)
}
PermissionUpdatedArg::UpdateRolePermission(role, permissions) => {
PermissionUpdatedArg::UpdateRolePermission(
role,
permissions.and_then(|ps| Some(ps.into_iter().map(|p| f(&p)).collect())),
)
}
PermissionUpdatedArg::UpdateUserRole(user_id, roles) => {
PermissionUpdatedArg::UpdateUserRole(user_id, roles)
}
}
}
}
impl PermissionReplacedArg<String> {
pub fn into_permission<F: Fn(&str) -> Permission>(
self,
f: F,
) -> PermissionReplacedArg<Permission> {
PermissionReplacedArg {
permissions: self.permissions.iter().map(|p| f(&p)).collect(),
user_permissions: self
.user_permissions
.into_iter()
.map(|(user_id, permissions)| {
(user_id, permissions.iter().map(|p| f(&p)).collect())
})
.collect(),
role_permissions: self
.role_permissions
.into_iter()
.map(|(role, permissions)| (role, permissions.into_iter().map(|p| f(&p)).collect()))
.collect(),
user_roles: self.user_roles,
}
}
}
impl Display for Permission {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Permission::Permitted(name) => f.write_str(&format!("Permitted({})", name)),
Permission::Forbidden(name) => f.write_str(&format!("Forbidden({})", name)),
}
}
}
impl Display for Permissions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!(
"Permissions {{ host: {}, listener: {}, permissions: {}, user_permissions: {}, role_permissions: {}, user_roles: {} }}",
format_option(&self.host),
format_option(&self.listener),
format!(
"[{}]",
self.permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",")
),
format!(
"[{}]",
self.user_permissions
.iter()
.map(|(user_id, permissions)| format!(
"{{user: {}, permissions: [{}]}}",
user_id.to_text(),
permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",")
))
.collect::<Vec<_>>()
.join(",")
),
format!(
"[{}]",
self.role_permissions
.iter()
.map(|(role, permissions)| format!(
"{{role: {}, permissions: [{}]}}",
role,
permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",")
))
.collect::<Vec<_>>()
.join(",")
),
format!(
"[{}]",
self.user_roles
.iter()
.map(|(user_id, roles)| format!(
"{{user: {}, roles: [{}]}}",
user_id.to_text(),
roles.iter().map(|s|s.clone()).collect::<Vec<_>>().join(",")
))
.collect::<Vec<_>>()
.join(",")
)
))
}
}
impl Display for PermissionUpdatedArg<String> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UpdateUserPermission(user_id, permissions) => f.write_str(&format!(
"update user: {} permissions: {}",
user_id.to_text(),
format_option_with_func(permissions, |permissions| format!(
"[{}]",
permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",")
))
)),
Self::UpdateRolePermission(role, permissions) => f.write_str(&format!(
"update role: {} permissions: {}",
role,
format_option_with_func(permissions, |permissions| format!(
"[{}]",
permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",")
))
)),
Self::UpdateUserRole(user_id, roles) => f.write_str(&format!(
"update user: {} roles: {}",
user_id.to_text(),
format_option_with_func(roles, |roles| format!(
"[{}]",
roles
.iter()
.map(|r| r.clone())
.collect::<Vec<_>>()
.join(",")
))
)),
}
}
}
impl Display for PermissionReplacedArg<String> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!(
"replace with permissions: [{}] user_permissions: [{}] role_permissions: [{}] user_roles: [{}]",
self.permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(","),
self.user_permissions
.iter()
.map(|(user_id, permissions)| format!(
"({}, [{}])",
user_id.to_text(),
permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",")
))
.collect::<Vec<_>>()
.join(","),
self.role_permissions
.iter()
.map(|(role, permissions)| format!(
"({}, [{}])",
role,
permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",")
))
.collect::<Vec<_>>()
.join(","),
self.user_roles
.iter()
.map(|(user_id, roles)| format!(
"({}, [{}])",
user_id.to_text(),
roles
.iter()
.map(|p| p.clone())
.collect::<Vec<_>>()
.join(",")
))
.collect::<Vec<_>>()
.join(","),
))
}
}
impl Display for PermissionUpdatedArg<Permission> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UpdateUserPermission(user_id, permissions) => f.write_str(&format!(
"update user: {} permissions: {}",
user_id.to_text(),
format_option_with_func(permissions, |permissions| format!(
"[{}]",
permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",")
))
)),
Self::UpdateRolePermission(role, permissions) => f.write_str(&format!(
"update role: {} permissions: {}",
role,
format_option_with_func(permissions, |permissions| format!(
"[{}]",
permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",")
))
)),
Self::UpdateUserRole(user_id, roles) => f.write_str(&format!(
"update user: {} roles: {}",
user_id.to_text(),
format_option_with_func(roles, |roles| format!(
"[{}]",
roles
.iter()
.map(|r| r.clone())
.collect::<Vec<_>>()
.join(",")
))
)),
}
}
}
impl Display for PermissionReplacedArg<Permission> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!(
"replace with permissions: [{}] user_permissions: [{}] role_permissions: [{}] user_roles: [{}]",
self.permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(","),
self.user_permissions
.iter()
.map(|(user_id, permissions)| format!(
"({}, [{}])",
user_id.to_text(),
permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",")
))
.collect::<Vec<_>>()
.join(","),
self.role_permissions
.iter()
.map(|(role, permissions)| format!(
"({}, [{}])",
role,
permissions
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",")
))
.collect::<Vec<_>>()
.join(","),
self.user_roles
.iter()
.map(|(user_id, roles)| format!(
"({}, [{}])",
user_id.to_text(),
roles
.iter()
.map(|p| p.clone())
.collect::<Vec<_>>()
.join(",")
))
.collect::<Vec<_>>()
.join(","),
))
}
}