use std::{
collections::{HashMap, HashSet},
fmt::{Debug, Display},
hash::Hash,
};
use candid::CandidType;
use serde::{Deserialize, Serialize};
use crate::{common::option::display_option_by, identity::UserId};
#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
pub enum PermissionUpdatedArg<Permission: Eq + Hash> {
UpdateUserPermission(UserId, Option<HashSet<Permission>>),
UpdateRolePermission(String, Option<HashSet<Permission>>),
UpdateUserRole(UserId, Option<HashSet<String>>),
}
#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
pub enum PermissionUpdatedError<Permission> {
InvalidPermission(Permission),
InvalidRole(String),
}
impl<Permission: Debug> Display for PermissionUpdatedError<Permission> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PermissionUpdatedError::InvalidPermission(permission) => {
write!(f, "InvalidPermission({permission:?})")
}
PermissionUpdatedError::InvalidRole(role) => write!(f, "InvalidRole({role})"),
}
}
}
impl<Permission: Debug> std::error::Error for PermissionUpdatedError<Permission> {}
pub trait Permissable<Permission: Eq + Hash> {
fn permission_users(&self) -> HashSet<&UserId>;
fn permission_roles(&self) -> HashSet<&String>;
fn permission_assigned(&self, user_id: &UserId) -> Option<&HashSet<Permission>>;
fn permission_role_assigned(&self, role: &str) -> Option<&HashSet<Permission>>;
fn permission_user_roles(&self, user_id: &UserId) -> Option<&HashSet<String>>;
fn permission_has(&self, user_id: &UserId, permission: &Permission) -> bool;
fn permission_owned(&self, user_id: &UserId) -> HashMap<&Permission, bool>;
fn permission_reset(&mut self, permissions: HashSet<Permission>);
fn permission_update(
&mut self,
args: Vec<PermissionUpdatedArg<Permission>>,
) -> Result<(), PermissionUpdatedError<Permission>>;
}
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(),
display_option_by(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,
display_option_by(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(),
display_option_by(roles, |roles| format!(
"[{}]",
roles.iter().cloned().collect::<Vec<_>>().join(",")
))
)),
}
}
}
pub mod basic {
use std::{
collections::{HashMap, HashSet},
fmt::Display,
};
use candid::CandidType;
use serde::{Deserialize, Serialize};
use crate::{
functions::types::{Permissable, PermissionUpdatedArg, PermissionUpdatedError},
identity::UserId,
};
#[derive(CandidType, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub enum Permission {
Permitted(String),
Forbidden(String),
}
impl Display for Permission {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Permission::Permitted(name) => write!(f, "Permitted({name})"),
Permission::Forbidden(name) => write!(f, "Forbidden({name})"),
}
}
}
impl Permission {
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 is_permit(&self) -> bool {
matches!(self, Self::Permitted(_))
}
pub fn is_forbid(&self) -> bool {
matches!(self, Self::Forbidden(_))
}
pub fn name(&self) -> &str {
match self {
Permission::Permitted(name) => name,
Permission::Forbidden(name) => name,
}
}
}
#[derive(CandidType, Serialize, Deserialize, Debug, Clone, Default)]
pub struct Permissions {
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>>,
}
impl Permissions {
fn assure_permission_exist(
&self,
permissions: &Option<HashSet<Permission>>,
) -> Result<(), PermissionUpdatedError<Permission>> {
if let Some(permissions) = permissions {
for permission in permissions {
if !self.permissions.contains(permission) {
return Err(PermissionUpdatedError::InvalidPermission(
permission.clone(),
));
}
}
}
Ok(())
}
fn assure_role_exist(
&self,
roles: &Option<HashSet<String>>,
) -> Result<(), PermissionUpdatedError<Permission>> {
if let Some(roles) = roles {
for role in roles {
if !self.role_permissions.contains_key(role) {
return Err(PermissionUpdatedError::InvalidRole(role.clone()));
}
}
}
Ok(())
}
}
impl Permissable<Permission> for Permissions {
fn permission_users(&self) -> HashSet<&UserId> {
let mut users: HashSet<&UserId> = self.user_roles.keys().collect();
users.extend(self.user_permissions.keys());
users
}
fn permission_roles(&self) -> HashSet<&String> {
self.role_permissions.keys().collect()
}
fn permission_assigned(&self, user_id: &UserId) -> Option<&HashSet<Permission>> {
self.user_permissions.get(user_id)
}
fn permission_role_assigned(&self, role: &str) -> Option<&HashSet<Permission>> {
self.role_permissions.get(role)
}
fn permission_user_roles(&self, user_id: &UserId) -> Option<&HashSet<String>> {
self.user_roles.get(user_id)
}
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_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>>,
) -> Result<(), PermissionUpdatedError<Permission>> {
for arg in args.iter() {
match arg {
PermissionUpdatedArg::UpdateUserPermission(user_id, permissions) => {
self.assure_permission_exist(permissions)?;
let exist = self.user_permissions.get(user_id);
if let Some(permissions) = &permissions {
if let Some(exist) = exist {
if exist == permissions {
continue;
}
}
} else if exist.is_none() {
continue;
}
if let Some(permissions) = permissions {
self.user_permissions.insert(*user_id, permissions.clone());
} else {
self.user_permissions.remove(user_id);
}
}
PermissionUpdatedArg::UpdateRolePermission(role, permissions) => {
self.assure_permission_exist(permissions)?;
let exist = self.role_permissions.get(role);
if let Some(permissions) = permissions {
if let Some(exist) = exist {
if exist == permissions {
continue;
}
}
} else if exist.is_none() {
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);
}
});
}
}
PermissionUpdatedArg::UpdateUserRole(user_id, roles) => {
self.assure_role_exist(roles)?;
let exist = self.user_roles.get(user_id);
if let Some(roles) = &roles {
if let Some(exist) = exist {
if exist == roles {
continue;
}
}
} else if exist.is_none() {
continue;
}
if let Some(roles) = roles {
self.user_roles.insert(*user_id, roles.clone());
} else {
self.user_roles.remove(user_id);
}
}
}
}
Ok(())
}
}
impl PermissionUpdatedArg<String> {
pub fn parse_permission<E, F: Fn(&str) -> Result<Permission, E>>(
self,
f: F,
) -> Result<PermissionUpdatedArg<Permission>, E> {
Ok(match self {
PermissionUpdatedArg::UpdateUserPermission(user_id, permissions) => {
PermissionUpdatedArg::UpdateUserPermission(
user_id,
permissions
.map(|ps| {
ps.into_iter()
.map(|p| f(&p))
.collect::<Result<HashSet<_>, _>>()
})
.transpose()?,
)
}
PermissionUpdatedArg::UpdateRolePermission(role, permissions) => {
PermissionUpdatedArg::UpdateRolePermission(
role,
permissions
.map(|ps| {
ps.into_iter()
.map(|p| f(&p))
.collect::<Result<HashSet<_>, _>>()
})
.transpose()?,
)
}
PermissionUpdatedArg::UpdateUserRole(user_id, roles) => {
PermissionUpdatedArg::UpdateUserRole(user_id, roles)
}
})
}
}
pub fn parse_all_permissions<'a, F, E>(
actions: &[&'a str],
parse: F,
) -> Result<Vec<Permission>, E>
where
F: Fn(&'a str) -> Result<Permission, E>,
{
let mut permissions = Vec::with_capacity(actions.len());
for name in actions {
permissions.push(parse(name)?);
}
Ok(permissions)
}
pub fn permitted_permissions(permissions: &HashSet<Permission>) -> HashSet<Permission> {
permissions
.iter()
.filter(|p| p.is_permit())
.cloned()
.collect()
}
pub fn supers_updated(
supers: &[UserId],
permissions: &HashSet<Permission>,
) -> Vec<PermissionUpdatedArg<Permission>> {
let permitted: HashSet<Permission> = permitted_permissions(permissions);
supers
.iter()
.map(|su| PermissionUpdatedArg::UpdateUserPermission(*su, Some(permitted.clone())))
.collect()
}
}