use std::sync::Arc;
use super::live::{HttpLysAsk, PreparedOwner};
use super::lys_pass_adapter::LysPass;
use crate::agents::waiting::durable::ApprovalAgents;
use crate::services::lifecycle::{Lifecycle, site};
pub struct SiteRequest<'a> {
pub lifecycle: &'a Arc<Lifecycle>,
pub owners: &'a Arc<ApprovalAgents>,
pub config: &'a crate::config::types::LysConfig,
pub handle: &'a tokio::runtime::Handle,
pub operation: &'a str,
pub target: &'a str,
pub change: site::SiteChange,
pub at_ms: u64,
}
pub struct PermissionRequest<'a> {
pub lifecycle: &'a Arc<Lifecycle>,
pub owners: &'a Arc<ApprovalAgents>,
pub config: &'a crate::config::types::LysConfig,
pub handle: &'a tokio::runtime::Handle,
pub operation: &'a str,
pub words: &'a str,
pub at_ms: u64,
}
pub(crate) fn validate_permission_words(words: &str) -> Result<(), lys_pass::Error> {
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct Words {
title: String,
options: Vec<OptionWord>,
}
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct OptionWord {
option_id: String,
name: String,
kind: String,
}
let value: Words =
serde_json::from_str(words).map_err(|_| lys_pass::Error::Invalid("permission words"))?;
let mut ids = std::collections::BTreeSet::new();
if value.title.trim().is_empty()
|| value.options.is_empty()
|| value.options.iter().any(|option| {
option.option_id.is_empty()
|| option.name.is_empty()
|| !ids.insert(&option.option_id)
|| (option.kind.is_empty() || option.kind.chars().any(char::is_control))
})
{
return Err(lys_pass::Error::Invalid("permission options"));
}
Ok(())
}
impl PermissionRequest<'_> {
pub(super) fn execute(self, pass: &LysPass) -> Result<serde_json::Value, lys_pass::Error> {
validate_permission_words(self.words)?;
let limits = self
.config
.drafts
.ok_or(lys_pass::Error::CannotAsk("draft limits missing"))?;
let keeper = Arc::new(PreparedOwner::new(
self.owners.clone(),
self.lifecycle.clone(),
pass.shared_verified(),
limits.pull,
limits.waiting,
));
let mut live = HttpLysAsk::new(
self.config
.api_url
.as_deref()
.unwrap_or(&self.config.issuer),
self.handle.clone(),
)?
.with_drafts(keeper);
if let Some(path) = &self.config.app_credential_path {
let credential = std::fs::read_to_string(path)
.map_err(|_| lys_pass::Error::CannotAsk("draft connector credential unreadable"))?;
live = live.asking_as(credential.trim().to_owned());
}
let target = lys_pass::Target::new("liminal.agent", pass.holder(), "agent_permission")?;
let answer = live.ask_prepared(
pass.raw_token(),
&super::live::PreparedAct {
holder: pass.holder(),
target: &target,
operation: self.operation,
words: self.words,
at_ms: self.at_ms,
},
limits.pull,
)?;
match answer.decision {
super::lys_pass_adapter::LiveAnswer::Held { draft, .. } => {
Ok(serde_json::json!({"state":"held","draft":draft,"words":self.words}))
}
_ => Err(lys_pass::Error::CannotAsk(
"permission requires a held draft",
)),
}
}
}
impl SiteRequest<'_> {
pub(super) fn execute(self, pass: &LysPass) -> Result<serde_json::Value, lys_pass::Error> {
let limits = self
.config
.drafts
.ok_or(lys_pass::Error::CannotAsk("draft limits missing"))?;
let keeper = Arc::new(PreparedOwner::new(
self.owners.clone(),
self.lifecycle.clone(),
pass.shared_verified(),
limits.pull,
limits.waiting,
));
let mut live = HttpLysAsk::new(
self.config
.api_url
.as_deref()
.unwrap_or(&self.config.issuer),
self.handle.clone(),
)?
.with_drafts(keeper);
if let Some(path) = &self.config.app_credential_path {
let credential = std::fs::read_to_string(path)
.map_err(|_| lys_pass::Error::CannotAsk("draft connector credential unreadable"))?;
live = live.asking_as(credential.trim().to_owned());
}
site::preparation::hold_verified(
site::preparation::VerifiedContext {
lifecycle: self.lifecycle,
pass: pass.verified(),
token: pass.raw_token(),
live: &live,
at_ms: self.at_ms,
limits: limits.pull,
},
self.operation,
self.target,
self.change,
)
.map_err(|error| {
tracing::warn!(%error, "the construction act was not held");
lys_pass::Error::CannotAsk("prepared construction refused")
})
}
}
pub(crate) struct SiteHolder {
pub lifecycle: Arc<Lifecycle>,
pub owners: Arc<ApprovalAgents>,
pub config: crate::config::types::LysConfig,
pub handle: tokio::runtime::Handle,
}
impl std::fmt::Debug for SiteHolder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SiteHolder").finish_non_exhaustive()
}
}
impl crate::server::connection::services::SiteDraftHolder for SiteHolder {
fn hold(
&self,
lifecycle: &Lifecycle,
session: &dyn super::session::LysSession,
operation: &str,
target: &str,
change: site::SiteChange,
at_ms: u64,
) -> Result<serde_json::Value, site::SiteError> {
if !std::ptr::eq(lifecycle, self.lifecycle.as_ref()) {
return Err(site::SiteError::Invalid("construction owner mismatch"));
}
session
.hold_site(SiteRequest {
lifecycle: &self.lifecycle,
owners: &self.owners,
config: &self.config,
handle: &self.handle,
operation,
target,
change,
at_ms,
})
.map_err(|error| {
tracing::warn!(target = %target, %error, "a construction draft could not be held");
site::SiteError::Pass(error.name().into())
})
}
}