use serde_json::Value;
use sha2::{Digest, Sha256};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AuthorizationSummary {
Action {
action_type: String,
identity: String,
details: Vec<(String, String)>,
},
Commit {
message: String,
},
Opaque {
digest_hex: String,
},
}
impl AuthorizationSummary {
pub fn from_signed_bytes(signed_bytes: &[u8]) -> Self {
if let Some(action) = parse_action(signed_bytes) {
return action;
}
if let Some(commit) = parse_commit(signed_bytes) {
return commit;
}
AuthorizationSummary::Opaque {
digest_hex: hex::encode(Sha256::digest(signed_bytes)),
}
}
}
fn parse_action(bytes: &[u8]) -> Option<AuthorizationSummary> {
let value: Value = serde_json::from_slice(bytes).ok()?;
let obj = value.as_object()?;
let action_type = obj.get("type")?.as_str()?.to_string();
let identity = obj.get("identity")?.as_str()?.to_string();
let payload = obj.get("payload")?;
let details = payload
.as_object()
.map(|m| {
m.iter()
.map(|(k, v)| (k.clone(), render_scalar(v)))
.collect()
})
.unwrap_or_default();
Some(AuthorizationSummary::Action {
action_type,
identity,
details,
})
}
fn parse_commit(bytes: &[u8]) -> Option<AuthorizationSummary> {
let text = std::str::from_utf8(bytes).ok()?;
if !text.starts_with("tree ") {
return None;
}
let message = text
.split_once("\n\n")
.map(|(_, m)| m.trim_end().to_string())
.unwrap_or_default();
Some(AuthorizationSummary::Commit { message })
}
fn render_scalar(v: &Value) -> String {
match v {
Value::String(s) => s.clone(),
other => other.to_string(),
}
}
impl fmt::Display for AuthorizationSummary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuthorizationSummary::Action {
action_type,
identity,
details,
} => {
write!(f, "action {action_type} by {identity}")?;
for (k, v) in details {
write!(f, "; {k}={v}")?;
}
Ok(())
}
AuthorizationSummary::Commit { message } => {
let subject = message.lines().next().unwrap_or("");
write!(f, "commit: {subject}")
}
AuthorizationSummary::Opaque { digest_hex } => {
write!(f, "opaque payload sha256:{digest_hex}")
}
}
}
}