use crate::{
agents::{Agent, ForAgent},
errors::AtomicResult,
urls,
utils::check_valid_url,
Resource, Storelike, Value,
};
#[tracing::instrument(skip(store, query_params))]
pub fn construct_invite_redirect(
store: &impl Storelike,
query_params: url::form_urlencoded::Parse,
invite_resource: &mut Resource,
for_agent: &ForAgent,
) -> AtomicResult<Resource> {
let requested_subject = invite_resource.get_subject().to_string();
let mut pub_key = None;
let mut invite_agent = None;
for (k, v) in query_params {
match k.as_ref() {
"public-key" | urls::INVITE_PUBKEY => pub_key = Some(v.to_string()),
"agent" | urls::AGENT => invite_agent = Some(v.to_string()),
_ => {}
}
}
let agent = match (pub_key, invite_agent) {
(None, None) => return Ok(invite_resource.to_owned()),
(None, Some(agent_url)) => agent_url,
(Some(public_key), None) => {
let new_agent = Agent::new_from_public_key(store, &public_key)?;
match store.get_resource(&new_agent.subject) {
Ok(_found) => {}
Err(_) => {
new_agent.to_resource()?.save_locally(store)?;
}
};
add_rights(&new_agent.subject, &new_agent.subject, true, store)?;
new_agent.subject
}
(Some(_), Some(_)) => {
return Err("Either publicKey or agent can be set - not both at the same time.".into())
}
};
let write = if let Ok(bool) = invite_resource.get(urls::WRITE_BOOL) {
bool.to_bool()?
} else {
false
};
let target = &invite_resource
.get(urls::TARGET)
.map_err(|e| {
format!(
"Invite {} does not have a target. {}",
invite_resource.get_subject(),
e
)
})?
.to_string();
if let Ok(usages_left) = invite_resource.get(urls::USAGES_LEFT) {
let num = usages_left.to_int()?;
if num == 0 {
return Err("No usages left for this invite".into());
}
let mut url = url::Url::parse(&requested_subject)?;
url.set_query(None);
invite_resource.set_subject(url.to_string());
invite_resource.set(urls::USAGES_LEFT.into(), Value::Integer(num - 1), store)?;
invite_resource
.save_locally(store)
.map_err(|e| format!("Unable to save updated Invite. {}", e))?;
}
if let Ok(expires) = invite_resource.get(urls::EXPIRES_AT) {
if expires.to_int()? > crate::utils::now() {
return Err("Invite is no longer valid".into());
}
}
let invite_creator =
crate::plugins::versioning::get_initial_commit_for_resource(target, store)?.signer;
crate::hierarchy::check_write(store, &store.get_resource(target)?, &invite_creator.into())
.map_err(|e| format!("Invite creator is not allowed to write the target. {}", e))?;
add_rights(&agent, target, write, store)?;
if write {
add_rights(&agent, target, false, store)?;
}
let mut redirect = Resource::new_instance(urls::REDIRECT, store)?;
redirect.set(
urls::DESTINATION.into(),
invite_resource.get(urls::TARGET)?.to_owned(),
store,
)?;
redirect.set(
urls::REDIRECT_AGENT.into(),
crate::Value::AtomicUrl(agent),
store,
)?;
redirect.set_subject(requested_subject);
Ok(redirect)
}
#[tracing::instrument(skip(store))]
pub fn add_rights(
agent: &str,
target: &str,
write: bool,
store: &impl Storelike,
) -> AtomicResult<()> {
check_valid_url(agent)?;
let mut target = store.get_resource(target)?;
let right = if write { urls::WRITE } else { urls::READ };
target.push(right, agent.into(), true)?;
target
.save_locally(store)
.map_err(|e| format!("Unable to save updated target resource. {}", e))?;
Ok(())
}
pub fn before_apply_commit(
store: &impl Storelike,
commit: &crate::Commit,
resource_new: &Resource,
) -> AtomicResult<()> {
let target = resource_new
.get(urls::TARGET)
.map_err(|_e| "Invite does not have required Target attribute")?;
let target_resource = store.get_resource(&target.to_string())?;
crate::hierarchy::check_write(store, &target_resource, &commit.signer.clone().into())?;
Ok(())
}