use std::fmt::Write;
use indexmap::IndexSet;
use serde::{Deserialize, Serialize};
use strum::{
AsRefStr, Display, EnumDiscriminants, EnumString, IntoStaticStr,
VariantArray,
};
use typeshare::typeshare;
use super::{MongoId, ResourceTarget};
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(
feature = "mongo",
derive(mongo_indexed::derive::MongoIndexed)
)]
#[cfg_attr(feature = "mongo", doc_index({ "user_target.type": 1, "user_target.id": 1 }))]
#[cfg_attr(feature = "mongo", doc_index({ "resource_target.type": 1, "resource_target.id": 1 }))]
#[cfg_attr(feature = "mongo", unique_doc_index({
"user_target.type": 1,
"user_target.id": 1,
"resource_target.type": 1,
"resource_target.id": 1
}))]
pub struct Permission {
#[serde(
default,
rename = "_id",
skip_serializing_if = "String::is_empty",
with = "bson::serde_helpers::hex_string_as_object_id"
)]
pub id: MongoId,
pub user_target: UserTarget,
pub resource_target: ResourceTarget,
#[serde(default)]
pub level: PermissionLevel,
#[serde(default)]
#[cfg_attr(feature = "utoipa", schema(value_type = Vec<SpecificPermission>))]
pub specific: IndexSet<SpecificPermission>,
}
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize, EnumDiscriminants)]
#[strum_discriminants(name(UserTargetVariant))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(
not(feature = "utoipa"),
strum_discriminants(derive(
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
Display,
EnumString,
AsRefStr
))
)]
#[cfg_attr(
feature = "utoipa",
strum_discriminants(derive(
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
Display,
EnumString,
AsRefStr,
utoipa::ToSchema
))
)]
#[serde(tag = "type", content = "id")]
pub enum UserTarget {
User(String),
UserGroup(String),
}
impl UserTarget {
pub fn extract_variant_id(self) -> (UserTargetVariant, String) {
match self {
UserTarget::User(id) => (UserTargetVariant::User, id),
UserTarget::UserGroup(id) => (UserTargetVariant::UserGroup, id),
}
}
}
#[typeshare]
#[derive(
Serialize,
Deserialize,
Debug,
Display,
EnumString,
AsRefStr,
Hash,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Default,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum PermissionLevel {
#[default]
None,
Read,
Execute,
Write,
}
impl Default for &PermissionLevel {
fn default() -> Self {
&PermissionLevel::None
}
}
#[typeshare]
#[derive(
Serialize,
Deserialize,
Debug,
Display,
EnumString,
AsRefStr,
IntoStaticStr,
VariantArray,
Hash,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum SpecificPermission {
Terminal,
Attach,
Inspect,
Logs,
Processes,
}
impl SpecificPermission {
fn all() -> IndexSet<SpecificPermission> {
SpecificPermission::VARIANTS.iter().cloned().collect()
}
}
#[typeshare]
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct PermissionLevelAndSpecifics {
pub level: PermissionLevel,
#[cfg_attr(feature = "utoipa", schema(value_type = Vec<SpecificPermission>))]
pub specific: IndexSet<SpecificPermission>,
}
impl From<PermissionLevel> for PermissionLevelAndSpecifics {
fn from(level: PermissionLevel) -> Self {
Self {
level,
specific: IndexSet::new(),
}
}
}
impl From<&Permission> for PermissionLevelAndSpecifics {
fn from(value: &Permission) -> Self {
Self {
level: value.level,
specific: value.specific.clone(),
}
}
}
impl PermissionLevel {
pub fn all(self) -> PermissionLevelAndSpecifics {
PermissionLevelAndSpecifics {
level: self,
specific: SpecificPermission::all(),
}
}
pub fn specifics(
self,
specific: IndexSet<SpecificPermission>,
) -> PermissionLevelAndSpecifics {
PermissionLevelAndSpecifics {
level: self,
specific,
}
}
fn specific(
self,
specific: SpecificPermission,
) -> PermissionLevelAndSpecifics {
PermissionLevelAndSpecifics {
level: self,
specific: [specific].into_iter().collect(),
}
}
pub fn terminal(self) -> PermissionLevelAndSpecifics {
self.specific(SpecificPermission::Terminal)
}
pub fn attach(self) -> PermissionLevelAndSpecifics {
self.specific(SpecificPermission::Attach)
}
pub fn inspect(self) -> PermissionLevelAndSpecifics {
self.specific(SpecificPermission::Inspect)
}
pub fn logs(self) -> PermissionLevelAndSpecifics {
self.specific(SpecificPermission::Logs)
}
pub fn processes(self) -> PermissionLevelAndSpecifics {
self.specific(SpecificPermission::Processes)
}
}
impl PermissionLevelAndSpecifics {
pub fn elevate(&mut self, other: &impl HasLevelAndSpecific) {
let other_level = other.level();
if other_level > self.level {
self.level = other_level;
}
self.specific.extend(other.specific().iter().cloned());
}
pub fn join(
&self,
other: &impl HasLevelAndSpecific,
) -> PermissionLevelAndSpecifics {
let mut specific = self.specific.clone();
specific.extend(other.specific().iter().cloned());
PermissionLevelAndSpecifics {
level: std::cmp::max(self.level, other.level()),
specific,
}
}
pub fn join_permission(
&self,
other: &Permission,
) -> PermissionLevelAndSpecifics {
let mut specific = self.specific.clone();
specific.extend(other.specific.iter().cloned());
PermissionLevelAndSpecifics {
level: std::cmp::max(self.level, other.level),
specific,
}
}
pub fn fulfills(
&self,
other: &PermissionLevelAndSpecifics,
) -> bool {
if self.level < other.level {
return false;
}
for specific in other.specific.iter() {
if !self.specific.contains(specific) {
return false;
}
}
true
}
pub fn fulfills_specific(
&self,
specifics: &IndexSet<SpecificPermission>,
) -> bool {
for specific in specifics.iter() {
if !self.specific.contains(specific) {
return false;
}
}
true
}
pub fn specifics_for_log(&self) -> String {
let mut res = String::new();
for specific in self.specific.iter() {
if res.is_empty() {
write!(&mut res, "{specific}").unwrap();
} else {
write!(&mut res, ", {specific}").unwrap();
}
}
res
}
pub fn specifics(
mut self,
specific: IndexSet<SpecificPermission>,
) -> PermissionLevelAndSpecifics {
self.specific = specific;
self
}
fn specific(
mut self,
specific: SpecificPermission,
) -> PermissionLevelAndSpecifics {
self.specific.insert(specific);
PermissionLevelAndSpecifics {
level: self.level,
specific: self.specific,
}
}
pub fn terminal(self) -> PermissionLevelAndSpecifics {
self.specific(SpecificPermission::Terminal)
}
pub fn attach(self) -> PermissionLevelAndSpecifics {
self.specific(SpecificPermission::Attach)
}
pub fn inspect(self) -> PermissionLevelAndSpecifics {
self.specific(SpecificPermission::Inspect)
}
pub fn logs(self) -> PermissionLevelAndSpecifics {
self.specific(SpecificPermission::Logs)
}
pub fn processes(self) -> PermissionLevelAndSpecifics {
self.specific(SpecificPermission::Processes)
}
}
pub trait HasLevelAndSpecific {
fn level(&self) -> PermissionLevel;
fn specific(&self) -> &IndexSet<SpecificPermission>;
}
impl HasLevelAndSpecific for Permission {
fn level(&self) -> PermissionLevel {
self.level
}
fn specific(&self) -> &IndexSet<SpecificPermission> {
&self.specific
}
}
impl HasLevelAndSpecific for PermissionLevelAndSpecifics {
fn level(&self) -> PermissionLevel {
self.level
}
fn specific(&self) -> &IndexSet<SpecificPermission> {
&self.specific
}
}