use std::collections::BTreeMap;
use std::fmt::Debug;
use std::sync::Arc;
use async_trait::async_trait;
use systemprompt_identifiers::UserId;
use super::registry::AuthzHookContext;
use super::types::RuleType;
pub const USER_PRECEDENCE: u16 = 0;
pub const ROLE_PRECEDENCE: u16 = 200;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubjectDimension {
pub rule_type: RuleType,
pub label: &'static str,
pub precedence: u16,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SubjectAttributes(BTreeMap<RuleType, Vec<String>>);
impl SubjectAttributes {
pub const EMPTY: Self = Self(BTreeMap::new());
#[must_use]
pub const fn new() -> Self {
Self::EMPTY
}
pub fn insert(&mut self, rule_type: RuleType, values: Vec<String>) {
self.0.insert(rule_type, values);
}
#[must_use]
pub fn values(&self, rule_type: &RuleType) -> &[String] {
self.0.get(rule_type).map_or(&[], Vec::as_slice)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl FromIterator<(RuleType, Vec<String>)> for SubjectAttributes {
fn from_iter<I: IntoIterator<Item = (RuleType, Vec<String>)>>(iter: I) -> Self {
Self(iter.into_iter().collect())
}
}
pub static NO_SUBJECT_ATTRIBUTES: SubjectAttributes = SubjectAttributes::EMPTY;
#[async_trait]
pub trait SubjectAttributeProvider: Send + Sync + Debug {
fn dimension(&self) -> SubjectDimension;
async fn values_for(&self, user_id: &UserId) -> Vec<String>;
}
pub type SharedSubjectAttributeProvider = Arc<dyn SubjectAttributeProvider>;
#[derive(Debug, Clone, Copy)]
pub struct SubjectProviderRegistration {
pub factory: fn(&AuthzHookContext) -> SharedSubjectAttributeProvider,
}
inventory::collect!(SubjectProviderRegistration);
#[must_use]
pub fn discover_subject_providers(ctx: &AuthzHookContext) -> Vec<SharedSubjectAttributeProvider> {
inventory::iter::<SubjectProviderRegistration>()
.map(|reg| (reg.factory)(ctx))
.collect()
}
#[must_use]
pub fn dimensions_of(providers: &[SharedSubjectAttributeProvider]) -> Vec<SubjectDimension> {
providers.iter().map(|p| p.dimension()).collect()
}
pub async fn gather_subject_attributes(
providers: &[SharedSubjectAttributeProvider],
user_id: &UserId,
) -> SubjectAttributes {
let mut attributes = SubjectAttributes::new();
for provider in providers {
let dimension = provider.dimension();
let values = provider.values_for(user_id).await;
attributes.insert(dimension.rule_type, values);
}
attributes
}
#[macro_export]
macro_rules! register_subject_attribute_provider {
($factory:expr) => {
::inventory::submit! {
$crate::authz::SubjectProviderRegistration {
factory: $factory,
}
}
};
}