assay/lua/policy/
credential.rs1use mlua::{Lua, Value};
9
10use super::Policy;
11
12const MARK: char = '\u{1}';
13const TAG: &str = "assay-cred";
14
15pub fn placeholder(name: &str, field: &str) -> String {
16 format!("{MARK}{TAG}{MARK}{name}{MARK}{field}{MARK}")
17}
18
19pub fn contains_placeholder(text: &str) -> bool {
20 text.contains(MARK) && text.contains(TAG)
21}
22
23pub fn register(lua: &Lua, policy: &Policy) -> mlua::Result<()> {
26 let declared: Vec<(String, Vec<String>)> = policy
27 .credentials
28 .iter()
29 .map(|(name, fields)| (name.clone(), fields.keys().cloned().collect()))
30 .collect();
31
32 let get = lua.create_function(move |lua, name: String| {
33 let Some((_, fields)) = declared.iter().find(|(n, _)| *n == name) else {
34 return Err(mlua::Error::runtime(format!(
35 "credential: '{name}' is not declared in the policy"
36 )));
37 };
38 let handle = lua.create_table()?;
39 for field in fields {
40 handle.set(field.as_str(), placeholder(&name, field))?;
41 }
42 Ok(handle)
43 })?;
44
45 let table = lua.create_table()?;
46 table.set("get", get)?;
47 lua.globals().set("credential", table)
48}
49
50pub fn substitute(lua: &Lua, policy: &Policy, value: Value) -> mlua::Result<Value> {
54 match value {
55 Value::String(s) => {
56 let text = s.to_str()?.to_string();
57 if !contains_placeholder(&text) {
58 return Ok(Value::String(s));
59 }
60 Ok(Value::String(lua.create_string(expand(policy, &text))?))
61 }
62 Value::Table(t) => {
63 let out = lua.create_table()?;
64 for pair in t.pairs::<Value, Value>() {
65 let (k, v) = pair?;
66 out.set(k, substitute(lua, policy, v)?)?;
67 }
68 Ok(Value::Table(out))
69 }
70 other => Ok(other),
71 }
72}
73
74fn expand(policy: &Policy, text: &str) -> String {
75 let mut out = text.to_string();
76 for (name, fields) in &policy.credentials {
77 for (field, env_key) in fields {
78 let token = placeholder(name, field);
79 if !out.contains(&token) {
80 continue;
81 }
82 let resolved = std::env::var(env_key).unwrap_or_default();
83 out = out.replace(&token, &resolved);
84 }
85 }
86 out
87}
88
89pub fn reject_in_url(url: &str) -> mlua::Result<()> {
92 if contains_placeholder(url) {
93 return Err(mlua::Error::runtime(
94 "credential: a credential handle cannot be used in a URL",
95 ));
96 }
97 Ok(())
98}