use std::collections::HashMap;
#[cfg(feature = "auth-jwt")]
#[must_use]
pub fn jwt_validate(token: &str) -> bool {
use crate::auth::jwt::{Jwks, JwtValidator};
let Ok(jwks) = Jwks::from_json(br#"{"keys":[]}"#).and_then(|j| {
j.with_ec_p256(
"a3",
"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
)
}) else {
return false;
};
JwtValidator::new()
.with_hs256_secret(b"fuzz".to_vec())
.validate(token, &jwks)
.is_ok()
}
#[must_use]
pub fn rest_route(
path: &str,
query: &str,
) -> (
Option<String>,
String,
a2a_protocol_types::params::ListTasksParams,
) {
let (tenant, rest) = crate::dispatch::rest::query::strip_tenant_prefix(path);
let params = crate::dispatch::rest::query::parse_list_tasks_query(query, tenant);
(tenant.map(str::to_owned), rest.to_owned(), params)
}
#[must_use]
pub fn webhook_url_allowed(url: &str) -> bool {
crate::push::sender::validate_webhook_url(url).is_ok()
}
#[must_use]
pub fn page_token(token: &str) -> Option<(i64, u64)> {
crate::store::task_store::in_memory::decode_order_key(token)
}
#[must_use]
pub fn extensions_header(value: &str) -> Vec<String> {
let headers = HashMap::from([("a2a-extensions".to_owned(), value.to_owned())]);
crate::handler::helpers::parse_extensions_header(&headers)
}
#[must_use]
pub fn caller_key_from_forwarded_for(xff: &str, trusted_proxy_hops: usize) -> String {
let headers = HashMap::from([("x-forwarded-for".to_owned(), xff.to_owned())]);
let ctx = crate::call_context::CallContext::new("fuzz").with_http_headers(headers);
crate::rate_limit::identity::caller_key(&ctx, trusted_proxy_hops)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn forwards_the_request_line() {
let (tenant, rest, p) = rest_route(
"/acme/tasks",
"contextId=c%201&pageSize=7&includeArtifacts=true",
);
assert_eq!(tenant.as_deref(), Some("acme"));
assert_eq!(rest, "/tasks");
assert_eq!(p.tenant.as_deref(), Some("acme"));
assert_eq!(p.context_id.as_deref(), Some("c 1"));
assert_eq!(p.page_size, Some(7));
assert_eq!(p.include_artifacts, Some(true));
}
#[test]
fn forwards_the_webhook_url() {
assert!(webhook_url_allowed("https://example.com/hook"));
assert!(!webhook_url_allowed("http://2852039166/"));
}
#[test]
fn forwards_the_page_token() {
assert_eq!(page_token("12:3"), Some((12, 3)));
assert_eq!(page_token("12"), None);
}
#[test]
fn forwards_the_extensions_header() {
assert_eq!(extensions_header("urn:a, urn:b"), ["urn:a", "urn:b"]);
}
#[test]
fn forwards_the_forwarded_for_header() {
assert_eq!(
caller_key_from_forwarded_for("198.51.100.1, ::ffff:203.0.113.7", 1),
"203.0.113.7"
);
assert_eq!(
caller_key_from_forwarded_for("198.51.100.1", 2),
"anonymous"
);
}
#[cfg(feature = "auth-jwt")]
#[test]
fn forwards_the_token() {
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
let input = format!(
"{}.{}",
URL_SAFE_NO_PAD.encode(br#"{"alg":"HS256"}"#),
URL_SAFE_NO_PAD.encode(br#"{"sub":"s","exp":4102444800}"#)
);
let key = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, b"fuzz");
let sig = URL_SAFE_NO_PAD.encode(ring::hmac::sign(&key, input.as_bytes()).as_ref());
assert!(jwt_validate(&format!("{input}.{sig}")));
assert!(!jwt_validate(&format!("{input}.{sig}x")));
assert!(!jwt_validate("a.b"));
}
}