use super::authz_builder::{BuildAuthzError, build_authz};
use super::service_config::ServiceConfig;
use crate::LogLevel;
use crate::authz::Authz;
use crate::authz::metrics::MetricsCollector;
use crate::bootstrap_config::BootstrapConfig;
use crate::common::policy_store::PolicyStoreMetadata;
use crate::context_data_api::DataStore;
use crate::log::interface::LogWriter;
use crate::log::{self, BaseLogEntry, LogEntry};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Clone)]
pub(crate) struct ServiceFactory<'a> {
bootstrap_config: &'a BootstrapConfig,
service_config: ServiceConfig,
log_service: log::Logger,
data_store: Arc<DataStore>,
metrics: Arc<MetricsCollector>,
container: SingletonContainer,
}
#[derive(Clone, Default)]
struct SingletonContainer {
authz_service: Option<Arc<Authz>>,
}
impl<'a> ServiceFactory<'a> {
pub(crate) fn new(
bootstrap_config: &'a BootstrapConfig,
service_config: ServiceConfig,
log_service: log::Logger,
data_store: Arc<DataStore>,
metrics: Arc<MetricsCollector>,
) -> Self {
Self {
bootstrap_config,
service_config,
log_service,
data_store,
metrics,
container: SingletonContainer::default(),
}
}
pub(crate) fn policy_store_metadata(&self) -> Option<&PolicyStoreMetadata> {
self.service_config.policy_store.metadata.as_ref()
}
pub(crate) fn http_client_for_refresh(&self) -> crate::http::HttpClient {
self.service_config.http_client.clone()
}
fn log_service(&mut self) -> log::Logger {
self.log_service.clone()
}
pub(crate) async fn authz_service(&mut self) -> Result<Arc<Authz>, ServiceInitError> {
if let Some(authz) = &self.container.authz_service {
return Ok(authz.clone());
}
let logger = self.log_service();
let policy_store = &self.service_config.policy_store;
for warn in policy_store.default_entities.warns() {
logger.log_any(
LogEntry::new(BaseLogEntry::new_system_opt_request_id(LogLevel::WARN, None))
.set_message(warn.to_string()),
);
}
if !self
.bootstrap_config
.authorization_config
.strict_schema_validation
{
let msg = if policy_store.schema_source_exists {
"CEDARLING_STRICT_SCHEMA_VALIDATION is disabled — schema present but not enforced"
} else {
"CEDARLING_STRICT_SCHEMA_VALIDATION is disabled — no schema loaded; policies run without attribute validation"
};
logger.log_any(
LogEntry::new(BaseLogEntry::new_system_opt_request_id(
LogLevel::WARN,
None,
))
.set_message(msg.to_string()),
);
}
logger.log_any(
LogEntry::new(BaseLogEntry::new_system_opt_request_id(
LogLevel::INFO,
None,
))
.set_message(format!(
"Policy store loaded: {} policies, {} issuers, {} entities",
policy_store.policies.get_set().policies().count(),
policy_store
.trusted_issuers
.as_ref()
.map_or(0, HashMap::len),
policy_store.default_entities.entities().len(),
)),
);
let authz = build_authz(
self.service_config.policy_store.clone(),
&self.bootstrap_config.jwt_config,
&self.bootstrap_config.authorization_config,
self.service_config.http_client.clone(),
&logger,
self.data_store.clone(),
self.metrics.clone(),
None,
)
.await?;
let service = Arc::new(authz);
self.container.authz_service = Some(service.clone());
Ok(service)
}
}
#[derive(Debug, thiserror::Error)]
pub enum ServiceInitError {
#[error("{0}")]
AuthzInit(String),
}
impl From<BuildAuthzError> for ServiceInitError {
fn from(e: BuildAuthzError) -> Self {
ServiceInitError::AuthzInit(e.to_string())
}
}