use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::domain_types::{RoleName, Scope};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "source", content = "claim", rename_all = "snake_case")]
#[non_exhaustive]
pub enum InjectedParamSource {
Jwt(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoleDefinition {
pub name: RoleName,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub scopes: Vec<Scope>,
}
impl RoleDefinition {
#[must_use]
pub fn new(name: impl Into<String>, scopes: Vec<String>) -> Self {
Self {
name: RoleName::new(name),
description: None,
scopes: scopes.into_iter().map(Scope::new).collect(),
}
}
pub fn with_description(mut self, description: String) -> Self {
self.description = Some(description);
self
}
#[must_use]
pub fn has_scope(&self, required_scope: &str) -> bool {
self.scopes.iter().any(|scope| {
let scope = scope.as_str();
if scope == "*" {
return true; }
if scope == required_scope {
return true; }
if let Some(prefix) = scope.strip_suffix(":*") {
return required_scope.starts_with(prefix) && required_scope.contains(':');
}
if let Some(prefix) = scope.strip_suffix('*') {
return required_scope.starts_with(prefix);
}
false
})
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecurityConfig {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub role_definitions: Vec<RoleDefinition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_role: Option<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub multi_tenant: bool,
#[serde(flatten)]
pub additional: HashMap<String, serde_json::Value>,
}
impl SecurityConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_role(&mut self, role: RoleDefinition) {
self.role_definitions.push(role);
}
#[must_use]
pub fn find_role(&self, name: &str) -> Option<&RoleDefinition> {
self.role_definitions.iter().find(|r| r.name == name)
}
#[must_use]
pub fn get_role_scopes(&self, role_name: &str) -> Vec<String> {
self.find_role(role_name)
.map(|role| role.scopes.iter().map(|s| s.to_string()).collect::<Vec<String>>())
.unwrap_or_default()
}
#[must_use]
pub fn role_has_scope(&self, role_name: &str, scope: &str) -> bool {
self.find_role(role_name).is_some_and(|role| role.has_scope(scope))
}
}