use rustlavel_http::Request;
use rustlavel_oauth::OAuthError;
pub struct Params(Vec<(String, String)>);
impl Params {
pub fn from_query(request: &Request) -> Params {
Params(request.query_pairs().to_vec())
}
pub fn from_body(request: &mut Request) -> Params {
Params(request.form().to_vec())
}
pub fn get(&self, name: &str) -> Result<Option<&str>, OAuthError> {
let mut found =
self.0.iter().filter(|(key, _)| key == name).map(|(_, value)| value.as_str());
let first = found.next();
if found.next().is_some() {
return Err(OAuthError::invalid_request(format!(
"the `{name}` parameter was sent more than once. RFC 6749 §3.1 forbids that, \
because which copy each layer reads is exactly the ambiguity an attacker needs."
)));
}
Ok(first.filter(|value| !value.is_empty()))
}
pub fn required(&self, name: &str) -> Result<&str, OAuthError> {
self.get(name)?.ok_or_else(|| {
OAuthError::invalid_request(format!("the `{name}` parameter is required"))
})
}
pub fn has(&self, name: &str) -> bool {
self.0.iter().any(|(key, _)| key == name)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rustlavel_http::Method;
fn query(target: &str) -> Params {
Params::from_query(&Request::new(Method::Get, target))
}
#[test]
fn reads_a_value_and_reports_a_missing_one() {
let params = query("/authorize?client_id=web");
assert_eq!(params.get("client_id").expect("read"), Some("web"));
assert_eq!(params.get("state").expect("read"), None);
let error = params.required("state").unwrap_err();
assert!(error.to_string().contains("`state` parameter is required"));
}
#[test]
fn an_empty_value_is_treated_as_absent() {
let params = query("/authorize?redirect_uri=&code=");
assert_eq!(params.get("redirect_uri").expect("read"), None);
assert!(params.required("code").is_err());
assert!(params.has("redirect_uri"));
}
#[test]
fn a_repeated_parameter_is_refused_rather_than_resolved() {
let params = query("/authorize?redirect_uri=https://a.test/cb&redirect_uri=https://evil");
let error = params.get("redirect_uri").unwrap_err();
assert_eq!(error.code, rustlavel_oauth::OAuthErrorCode::InvalidRequest);
assert!(error.to_string().contains("more than once"));
assert!(params.required("redirect_uri").is_err(), "and `required` refuses it too");
}
#[test]
fn a_repeated_parameter_is_refused_even_when_both_copies_agree() {
let params = query("/authorize?scope=read&scope=read");
assert!(params.get("scope").is_err());
}
#[test]
fn the_body_is_read_for_a_post_and_the_query_is_not() {
let mut request = Request::new(Method::Post, "/token?client_secret=from-url")
.with_form(&[("grant_type", "client_credentials")]);
let params = Params::from_body(&mut request);
assert_eq!(params.get("grant_type").expect("read"), Some("client_credentials"));
assert_eq!(params.get("client_secret").expect("read"), None);
}
}