use crate::error::WorkflowResult;
use crate::expression::evaluate_expression_str;
use serde_json::Value;
use swf_core::models::authentication::ReferenceableAuthenticationPolicy;
type VarsMap = std::collections::HashMap<String, Value>;
type AuthDefs = std::collections::HashMap<String, ReferenceableAuthenticationPolicy>;
pub(crate) struct DigestAuthInfo {
pub username: String,
pub password: String,
}
pub(crate) struct DigestAuthParams<'a> {
pub username: &'a str,
pub password: &'a str,
pub realm: &'a str,
pub method: &'a str,
pub uri: &'a str,
pub nonce: &'a str,
pub qop: Option<&'a str>,
pub nc: &'a str,
pub cnonce: &'a str,
pub algorithm: &'a str,
}
pub(crate) struct DigestHeaderParams<'a> {
pub username: &'a str,
pub realm: &'a str,
pub nonce: &'a str,
pub uri: &'a str,
pub response: &'a str,
pub opaque: Option<&'a str>,
pub qop: Option<&'a str>,
pub nc: &'a str,
pub cnonce: &'a str,
}
pub(crate) struct DigestChallenge {
pub realm: String,
pub nonce: String,
pub opaque: Option<String>,
pub algorithm: String,
pub qop: Option<String>,
}
pub(crate) fn extract_digest_info(
policy: &ReferenceableAuthenticationPolicy,
auth_definitions: Option<&AuthDefs>,
input: &Value,
vars: &VarsMap,
task_name: &str,
) -> WorkflowResult<Option<DigestAuthInfo>> {
let resolved_policy = match policy {
ReferenceableAuthenticationPolicy::Policy(def) => def,
ReferenceableAuthenticationPolicy::Reference(ref_ref) => match auth_definitions {
Some(defs) => match defs.get(&ref_ref.use_) {
Some(ReferenceableAuthenticationPolicy::Policy(def)) => def,
_ => return Ok(None),
},
None => return Ok(None),
},
};
if let Some(ref digest) = resolved_policy.digest {
if let Some(ref username_expr) = digest.username {
let username = evaluate_expression_str(username_expr, input, vars, task_name)?;
let password = digest
.password
.as_deref()
.map(|p| evaluate_expression_str(p, input, vars, task_name))
.transpose()?
.unwrap_or_default();
return Ok(Some(DigestAuthInfo { username, password }));
}
}
Ok(None)
}
pub(crate) fn parse_digest_challenge(www_auth: &str) -> Option<DigestChallenge> {
let header = www_auth.strip_prefix("Digest")?.trim();
let mut realm = None;
let mut nonce = None;
let mut opaque = None;
let mut algorithm = "MD5".to_string();
let mut qop = None;
let re = regex_lazy();
for cap in re.captures_iter(header) {
let key = match cap.get(1) {
Some(m) => m.as_str(),
None => continue,
};
let value = match cap.get(2) {
Some(m) => m.as_str(),
None => continue,
};
match key {
"realm" => realm = Some(value.to_string()),
"nonce" => nonce = Some(value.to_string()),
"opaque" => opaque = Some(value.to_string()),
"algorithm" => algorithm = value.to_string(),
"qop" => qop = Some(value.to_string()),
_ => {}
}
}
Some(DigestChallenge {
realm: realm?,
nonce: nonce?,
opaque,
algorithm,
qop,
})
}
fn regex_lazy() -> std::sync::MutexGuard<'static, regex::Regex> {
use regex::Regex;
use std::sync::Mutex;
static RE: std::sync::OnceLock<Mutex<Regex>> = std::sync::OnceLock::new();
let guard = RE
.get_or_init(|| {
Mutex::new(Regex::new(r#"([A-Za-z]+)="([^"]*)""#).expect("static regex is valid"))
})
.lock()
.unwrap_or_else(|e| e.into_inner());
guard
}
pub(crate) fn rand_nonce() -> u32 {
use std::time::{SystemTime, UNIX_EPOCH};
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let mut x = seed.wrapping_add(1);
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
(x & 0xFFFFFFFF) as u32
}
pub(crate) fn md5_hex(input: &str) -> String {
use md5::{Digest, Md5};
let mut hasher = Md5::new();
hasher.update(input.as_bytes());
let result = hasher.finalize();
result.iter().map(|b| format!("{:02x}", b)).collect()
}
pub(crate) fn compute_digest_response(params: &DigestAuthParams) -> String {
let ha1 = md5_hex(&format!(
"{}:{}:{}",
params.username, params.realm, params.password
));
let ha1 = if params.algorithm.eq_ignore_ascii_case("MD5-sess") {
md5_hex(&format!("{}:{}:{}", ha1, params.nonce, params.cnonce))
} else {
ha1
};
let ha2 = md5_hex(&format!("{}:{}", params.method, params.uri));
match params.qop {
Some(qop_val) => md5_hex(&format!(
"{}:{}:{}:{}:{}:{}",
ha1, params.nonce, params.nc, params.cnonce, qop_val, ha2
)),
None => md5_hex(&format!("{}:{}:{}", ha1, params.nonce, ha2)),
}
}
pub(crate) fn build_digest_auth_header(params: &DigestHeaderParams) -> String {
let mut parts = vec![
format!("username=\"{}\"", params.username),
format!("realm=\"{}\"", params.realm),
format!("nonce=\"{}\"", params.nonce),
format!("uri=\"{}\"", params.uri),
format!("response=\"{}\"", params.response),
];
if let Some(q) = params.qop {
parts.push(format!("qop={}", q));
parts.push(format!("nc={}", params.nc));
parts.push(format!("cnonce=\"{}\"", params.cnonce));
}
if let Some(op) = params.opaque {
parts.push(format!("opaque=\"{}\"", op));
}
format!("Digest {}", parts.join(", "))
}