use crate::b64;
use crate::bewit::Bewit;
use crate::credentials::{Credentials, Key};
use crate::error::*;
use crate::header::Header;
use crate::mac::{Mac, MacType};
use crate::response::ResponseBuilder;
use base64::Engine;
use log::debug;
use std::borrow::Cow;
use std::str;
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use url::{Position, Url};
#[derive(Debug, Clone)]
pub struct Request<'a> {
method: &'a str,
host: &'a str,
port: u16,
path: Cow<'a, str>,
hash: Option<&'a [u8]>,
ext: Option<&'a str>,
app: Option<&'a str>,
dlg: Option<&'a str>,
}
impl<'a> Request<'a> {
pub fn make_header(&self, credentials: &Credentials) -> Result<Header> {
let nonce = random_string(10)?;
self.make_header_full(credentials, SystemTime::now(), nonce)
}
pub fn make_header_full<S>(
&self,
credentials: &Credentials,
ts: SystemTime,
nonce: S,
) -> Result<Header>
where
S: Into<String>,
{
let nonce = nonce.into();
let mac = Mac::new(
MacType::Header,
&credentials.key,
ts,
&nonce,
self.method,
self.host,
self.port,
self.path.as_ref(),
self.hash,
self.ext,
)?;
Header::new(
Some(credentials.id.clone()),
Some(ts),
Some(nonce),
Some(mac),
self.ext.map(|v| v.to_string()),
self.hash.map(|v| v.to_vec()),
self.app.map(|v| v.to_string()),
self.dlg.map(|v| v.to_string()),
)
}
pub fn make_bewit(&self, credentials: &'a Credentials, exp: SystemTime) -> Result<Bewit<'a>> {
let mac = Mac::new(
MacType::Bewit,
&credentials.key,
exp,
"",
self.method,
self.host,
self.port,
self.path.as_ref(),
self.hash,
self.ext,
)?;
let bewit = Bewit::new(&credentials.id, exp, mac, self.ext);
Ok(bewit)
}
pub fn make_bewit_with_ttl(
&self,
credentials: &'a Credentials,
ttl: Duration,
) -> Result<Bewit<'a>> {
let exp = SystemTime::now() + ttl;
self.make_bewit(credentials, exp)
}
pub fn validate_header(&self, header: &Header, key: &Key, ts_skew: Duration) -> bool {
let ts = match header.ts {
Some(ts) => ts,
None => {
debug!("missing timestamp from header");
return false;
}
};
let nonce = match header.nonce {
Some(ref nonce) => nonce,
None => {
debug!("missing nonce from header");
return false;
}
};
let header_mac = match header.mac {
Some(ref mac) => mac,
None => {
debug!("missing mac from header");
return false;
}
};
let header_hash = header.hash.as_ref().map(|hash| &hash[..]);
let header_ext = header.ext.as_ref().map(|ext| &ext[..]);
match Mac::new(
MacType::Header,
key,
ts,
nonce,
self.method,
self.host,
self.port,
self.path.as_ref(),
header_hash,
header_ext,
) {
Ok(calculated_mac) => {
if &calculated_mac != header_mac {
debug!("calculated mac doesn't match header");
return false;
}
}
Err(e) => {
debug!("unexpected mac error: {:?}", e);
return false;
}
};
if let Some(local_hash) = self.hash {
if let Some(server_hash) = header_hash {
if local_hash != server_hash {
debug!("server hash doesn't match header");
return false;
}
} else {
debug!("missing hash from header");
return false;
}
}
let now = SystemTime::now();
let skew = if now > ts {
now.duration_since(ts).unwrap()
} else {
ts.duration_since(now).unwrap()
};
if skew > ts_skew {
debug!(
"bad timestamp skew, timestamp too old? detected skew: {:?}, ts_skew: {:?}",
&skew, &ts_skew
);
return false;
}
true
}
pub fn validate_bewit(&self, bewit: &Bewit, key: &Key) -> bool {
let calculated_mac = Mac::new(
MacType::Bewit,
key,
bewit.exp(),
"",
self.method,
self.host,
self.port,
self.path.as_ref(),
self.hash,
match bewit.ext() {
Some(e) => Some(e),
None => None,
},
);
let calculated_mac = match calculated_mac {
Ok(m) => m,
Err(_) => {
return false;
}
};
if bewit.mac() != &calculated_mac {
return false;
}
let now = SystemTime::now();
if bewit.exp() < now {
return false;
}
true
}
pub fn make_response_builder(&'a self, req_header: &'a Header) -> ResponseBuilder<'a> {
ResponseBuilder::from_request_header(
req_header,
self.method,
self.host,
self.port,
self.path.as_ref(),
)
}
}
#[derive(Debug, Clone)]
pub struct RequestBuilder<'a>(Request<'a>);
impl<'a> RequestBuilder<'a> {
pub fn new(method: &'a str, host: &'a str, port: u16, path: &'a str) -> Self {
RequestBuilder(Request {
method,
host,
port,
path: Cow::Borrowed(path),
hash: None,
ext: None,
app: None,
dlg: None,
})
}
pub fn from_url(method: &'a str, url: &'a Url) -> Result<Self> {
let (host, port, path) = RequestBuilder::parse_url(url)?;
Ok(RequestBuilder(Request {
method,
host,
port,
path: Cow::Borrowed(path),
hash: None,
ext: None,
app: None,
dlg: None,
}))
}
pub fn method(mut self, method: &'a str) -> Self {
self.0.method = method;
self
}
pub fn path(mut self, path: &'a str) -> Self {
self.0.path = Cow::Borrowed(path);
self
}
pub fn host(mut self, host: &'a str) -> Self {
self.0.host = host;
self
}
pub fn port(mut self, port: u16) -> Self {
self.0.port = port;
self
}
pub fn url(self, url: &'a Url) -> Result<Self> {
let (host, port, path) = RequestBuilder::parse_url(url)?;
Ok(self.path(path).host(host).port(port))
}
pub fn hash<H: Into<Option<&'a [u8]>>>(mut self, hash: H) -> Self {
self.0.hash = hash.into();
self
}
pub fn ext<S: Into<Option<&'a str>>>(mut self, ext: S) -> Self {
self.0.ext = ext.into();
self
}
pub fn app<S: Into<Option<&'a str>>>(mut self, app: S) -> Self {
self.0.app = app.into();
self
}
pub fn dlg<S: Into<Option<&'a str>>>(mut self, dlg: S) -> Self {
self.0.dlg = dlg.into();
self
}
pub fn request(self) -> Request<'a> {
self.0
}
pub fn extract_bewit(mut self, bewit: &mut Option<Bewit<'a>>) -> Result<Self> {
const PREFIX: &str = "bewit=";
*bewit = None;
if let Some(query_index) = self.0.path.find('?') {
let (bewit_components, components): (Vec<&str>, Vec<&str>) = self.0.path
[query_index + 1..]
.split('&')
.partition(|comp| comp.starts_with(PREFIX));
if bewit_components.len() == 1 {
let bewit_str = bewit_components[0];
*bewit = Some(Bewit::from_str(&bewit_str[PREFIX.len()..])?);
let new_path = if !components.is_empty() {
format!("{}{}", &self.0.path[..=query_index], components.join("&"))
} else {
self.0.path[..query_index].to_string()
};
self.0.path = Cow::Owned(new_path);
Ok(self)
} else if bewit_components.is_empty() {
Ok(self)
} else {
Err(InvalidBewit::Multiple.into())
}
} else {
Ok(self)
}
}
fn parse_url(url: &'a Url) -> Result<(&'a str, u16, &'a str)> {
let host = url
.host_str()
.ok_or_else(|| Error::InvalidUrl(format!("url {url} has no host")))?;
let port = url
.port_or_known_default()
.ok_or_else(|| Error::InvalidUrl(format!("url {url} has no port")))?;
let path = &url[Position::BeforePath..];
Ok((host, port, path))
}
}
fn random_string(bytes: usize) -> Result<String> {
let mut bytes = vec![0u8; bytes];
crate::crypto::rand_bytes(&mut bytes)?;
Ok(b64::BEWIT_ENGINE.encode(&bytes))
}
#[cfg(all(test, any(feature = "use_ring", feature = "use_openssl")))]
mod test {
use super::*;
use crate::credentials::{Credentials, Key};
use crate::header::Header;
use std::str::FromStr;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use url::Url;
const REAL_HEADER: &str = "id=\"me\", ts=\"1491183061\", nonce=\"RVnYzW\", \
mac=\"1kqRT9EoxiZ9AA/ayOCXB+AcjfK/BoJ+n7z0gfvZotQ=\"";
const BEWIT_STR: &str =
"bWVcMTM1MzgzMjgzNFxmaXk0ZTV3QmRhcEROeEhIZUExOE5yU3JVMVUzaVM2NmdtMFhqVEpwWXlVPVw";
const INITIAL_BEWIT_STR: &str =
"T0ggTk9FU1wxMzUzODMyODM0XGZpeTRlNXdCZGFwRE54SEhlQTE4TnJTclUxVTNpUzY2Z20wWGpUSnBZeVU9XCZtdXQgYmV3aXQgbm90IHJlc2V0IQ";
#[test]
fn test_empty() {
let req = RequestBuilder::new("GET", "site", 80, "/").request();
assert_eq!(req.method, "GET");
assert_eq!(req.host, "site");
assert_eq!(req.port, 80);
assert_eq!(req.path, "/");
assert_eq!(req.hash, None);
assert_eq!(req.ext, None);
assert_eq!(req.app, None);
assert_eq!(req.dlg, None);
}
#[test]
fn test_builder() {
let hash = vec![0u8];
let req = RequestBuilder::new("GET", "example.com", 443, "/foo")
.hash(Some(&hash[..]))
.ext("ext")
.app("app")
.dlg("dlg")
.request();
assert_eq!(req.method, "GET");
assert_eq!(req.path, "/foo");
assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443);
assert_eq!(req.hash, Some(&hash[..]));
assert_eq!(req.ext, Some("ext"));
assert_eq!(req.app, Some("app"));
assert_eq!(req.dlg, Some("dlg"));
}
#[test]
fn test_builder_clone() {
let rb = RequestBuilder::new("GET", "site", 443, "/foo");
let req = rb.clone().request();
let req2 = rb.path("/bar").request();
assert_eq!(req.method, "GET");
assert_eq!(req.path, "/foo");
assert_eq!(req2.method, "GET");
assert_eq!(req2.path, "/bar");
}
#[test]
fn test_url_builder() {
let url = Url::parse("https://example.com/foo").unwrap();
let req = RequestBuilder::from_url("GET", &url).unwrap().request();
assert_eq!(req.path, "/foo");
assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443); }
#[test]
fn test_url_builder_with_query() {
let url = Url::parse("https://example.com/foo?foo=bar").unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
let bldr = bldr.extract_bewit(&mut bewit).unwrap();
assert_eq!(bewit, None);
let req = bldr.request();
assert_eq!(req.path, "/foo?foo=bar");
assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443); }
#[test]
fn test_url_builder_with_encodable_chars() {
let url = Url::parse("https://example.com/ñoo?foo=año").unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
let bldr = bldr.extract_bewit(&mut bewit).unwrap();
assert_eq!(bewit, None);
let req = bldr.request();
assert_eq!(req.path, "/%C3%B1oo?foo=a%C3%B1o");
assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443); }
#[test]
fn test_url_builder_with_empty_query() {
let url = Url::parse("https://example.com/foo?").unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
let bldr = bldr.extract_bewit(&mut bewit).unwrap();
assert_eq!(bewit, None);
let req = bldr.request();
assert_eq!(req.path, "/foo?");
assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443); }
#[test]
fn test_url_builder_with_bewit_alone() {
let url = Url::parse(&format!("https://example.com/foo?bewit={BEWIT_STR}")).unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
let bldr = bldr.extract_bewit(&mut bewit).unwrap();
assert_eq!(bewit, Some(Bewit::from_str(BEWIT_STR).unwrap()));
let req = bldr.request();
assert_eq!(req.path, "/foo"); assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443); }
#[test]
fn test_url_builder_with_bewit_first() {
let url = Url::parse(&format!("https://example.com/foo?bewit={BEWIT_STR}&a=1")).unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
let bldr = bldr.extract_bewit(&mut bewit).unwrap();
assert_eq!(bewit, Some(Bewit::from_str(BEWIT_STR).unwrap()));
let req = bldr.request();
assert_eq!(req.path, "/foo?a=1");
assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443); }
#[test]
fn test_url_builder_with_bewit_multiple() {
let url = Url::parse(&format!(
"https://example.com/foo?bewit={BEWIT_STR}&bewit={BEWIT_STR}"
))
.unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
assert!(bldr.extract_bewit(&mut bewit).is_err());
assert_eq!(bewit, None);
}
#[test]
fn test_url_builder_with_bewit_invalid() {
let url = Url::parse("https://example.com/foo?bewit=1234").unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
assert!(bldr.extract_bewit(&mut bewit).is_err());
assert_eq!(bewit, None);
}
#[test]
fn test_url_builder_with_bewit_last() {
let url = Url::parse(&format!("https://example.com/foo?a=1&bewit={BEWIT_STR}")).unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
let bldr = bldr.extract_bewit(&mut bewit).unwrap();
assert_eq!(bewit, Some(Bewit::from_str(BEWIT_STR).unwrap()));
let req = bldr.request();
assert_eq!(req.path, "/foo?a=1");
assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443); }
#[test]
fn test_url_builder_with_bewit_middle() {
let url = Url::parse(&format!(
"https://example.com/foo?a=1&bewit={BEWIT_STR}&b=2"
))
.unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
let bldr = bldr.extract_bewit(&mut bewit).unwrap();
assert_eq!(bewit, Some(Bewit::from_str(BEWIT_STR).unwrap()));
let req = bldr.request();
assert_eq!(req.path, "/foo?a=1&b=2");
assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443); }
#[test]
fn test_url_builder_with_bewit_percent_encoding() {
let url = Url::parse(&format!(
"https://example.com/foo?%66oo=1&bewit={BEWIT_STR}&%62ar=2"
))
.unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
let bldr = bldr.extract_bewit(&mut bewit).unwrap();
assert_eq!(bewit, Some(Bewit::from_str(BEWIT_STR).unwrap()));
let req = bldr.request();
assert_eq!(req.path, "/foo?%66oo=1&%62ar=2");
assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443); }
#[test]
fn test_url_builder_with_xxxbewit() {
let url = Url::parse(&format!(
"https://example.com/foo?a=1&xxxbewit={BEWIT_STR}&b=2"
))
.unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
let bldr = bldr.extract_bewit(&mut bewit).unwrap();
assert_eq!(bewit, None);
let req = bldr.request();
assert_eq!(req.path, format!("/foo?a=1&xxxbewit={BEWIT_STR}&b=2"));
assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443); }
#[test]
fn test_url_builder_with_username_password() {
let url = Url::parse("https://a:b@example.com/foo?x=y").unwrap();
let bldr = RequestBuilder::from_url("GET", &url).unwrap();
let mut bewit = Some(Bewit::from_str(INITIAL_BEWIT_STR).unwrap());
let bldr = bldr.extract_bewit(&mut bewit).unwrap();
assert_eq!(bewit, None);
let req = bldr.request();
assert_eq!(req.path, "/foo?x=y");
assert_eq!(req.host, "example.com");
assert_eq!(req.port, 443); }
#[test]
fn test_make_header_full() {
let req = RequestBuilder::new("GET", "example.com", 443, "/foo").request();
let credentials = Credentials {
id: "me".to_string(),
key: Key::new(vec![99u8; 32], crate::SHA256).unwrap(),
};
let header = req
.make_header_full(&credentials, UNIX_EPOCH + Duration::new(1000, 100), "nonny")
.unwrap();
assert_eq!(
header,
Header {
id: Some("me".to_string()),
ts: Some(UNIX_EPOCH + Duration::new(1000, 100)),
nonce: Some("nonny".to_string()),
mac: Some(Mac::from(vec![
122, 47, 2, 53, 195, 247, 185, 107, 133, 250, 61, 134, 200, 35, 118, 94, 48,
175, 237, 108, 60, 71, 4, 2, 244, 66, 41, 172, 91, 7, 233, 140
])),
ext: None,
hash: None,
app: None,
dlg: None,
}
);
}
#[test]
fn test_make_header_full_with_optional_fields() {
let hash = vec![0u8];
let req = RequestBuilder::new("GET", "example.com", 443, "/foo")
.hash(Some(&hash[..]))
.ext("ext")
.app("app")
.dlg("dlg")
.request();
let credentials = Credentials {
id: "me".to_string(),
key: Key::new(vec![99u8; 32], crate::SHA256).unwrap(),
};
let header = req
.make_header_full(&credentials, UNIX_EPOCH + Duration::new(1000, 100), "nonny")
.unwrap();
assert_eq!(
header,
Header {
id: Some("me".to_string()),
ts: Some(UNIX_EPOCH + Duration::new(1000, 100)),
nonce: Some("nonny".to_string()),
mac: Some(Mac::from(vec![
72, 123, 243, 214, 145, 81, 129, 54, 183, 90, 22, 136, 192, 146, 208, 53, 216,
138, 145, 94, 175, 204, 217, 8, 77, 16, 202, 50, 10, 144, 133, 162
])),
ext: Some("ext".to_string()),
hash: Some(hash.clone()),
app: Some("app".to_string()),
dlg: Some("dlg".to_string()),
}
);
}
#[test]
fn test_validate_matches_generated() {
let req = RequestBuilder::new("GET", "example.com", 443, "/foo").request();
let credentials = Credentials {
id: "me".to_string(),
key: Key::new(vec![99u8; 32], crate::SHA256).unwrap(),
};
let header = req
.make_header_full(&credentials, SystemTime::now(), "nonny")
.unwrap();
assert!(req.validate_header(&header, &credentials.key, Duration::from_secs(60)));
}
const ONE_YEAR_IN_SECS: u64 = 365 * 24 * 60 * 60;
#[test]
fn test_validate_real_request() {
let header = Header::from_str(REAL_HEADER).unwrap();
let credentials = Credentials {
id: "me".to_string(),
key: Key::new("tok", crate::SHA256).unwrap(),
};
let req =
RequestBuilder::new("GET", "pulse.taskcluster.net", 443, "/v1/namespaces").request();
assert!(req.validate_header(
&header,
&credentials.key,
Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
));
}
#[test]
fn test_validate_real_request_bad_creds() {
let header = Header::from_str(REAL_HEADER).unwrap();
let credentials = Credentials {
id: "me".to_string(),
key: Key::new("WRONG", crate::SHA256).unwrap(),
};
let req =
RequestBuilder::new("GET", "pulse.taskcluster.net", 443, "/v1/namespaces").request();
assert!(!req.validate_header(
&header,
&credentials.key,
Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
));
}
#[test]
fn test_validate_real_request_bad_req_info() {
let header = Header::from_str(REAL_HEADER).unwrap();
let credentials = Credentials {
id: "me".to_string(),
key: Key::new("tok", crate::SHA256).unwrap(),
};
let req = RequestBuilder::new("GET", "pulse.taskcluster.net", 443, "WRONG PATH").request();
assert!(!req.validate_header(
&header,
&credentials.key,
Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
));
}
fn make_header_without_hash() -> Header {
Header::new(
Some("dh37fgj492je"),
Some(UNIX_EPOCH + Duration::new(1353832234, 0)),
Some("j4h3g2"),
Some(Mac::from(vec![
161, 105, 122, 110, 248, 62, 129, 193, 148, 206, 239, 193, 219, 46, 137, 221, 51,
170, 135, 114, 81, 68, 145, 182, 15, 165, 145, 168, 114, 237, 52, 35,
])),
None,
None,
None,
None,
)
.unwrap()
}
fn make_header_with_hash() -> Header {
Header::new(
Some("dh37fgj492je"),
Some(UNIX_EPOCH + Duration::new(1353832234, 0)),
Some("j4h3g2"),
Some(Mac::from(vec![
189, 53, 155, 244, 203, 150, 255, 238, 135, 144, 186, 93, 6, 189, 184, 21, 150,
210, 226, 61, 93, 154, 17, 218, 142, 250, 254, 193, 123, 132, 131, 195,
])),
None,
Some(vec![1, 2, 3, 4]),
None,
None,
)
.unwrap()
}
#[test]
fn test_validate_no_hash() {
let header = make_header_without_hash();
let req = RequestBuilder::new("", "", 0, "").request();
assert!(req.validate_header(
&header,
&Key::new("tok", crate::SHA256).unwrap(),
Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
));
}
#[test]
fn test_validate_hash_in_header() {
let header = make_header_with_hash();
let req = RequestBuilder::new("", "", 0, "").request();
assert!(req.validate_header(
&header,
&Key::new("tok", crate::SHA256).unwrap(),
Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
));
}
#[test]
fn test_validate_hash_required_but_not_given() {
let header = make_header_without_hash();
let hash = vec![1, 2, 3, 4];
let req = RequestBuilder::new("", "", 0, "")
.hash(Some(&hash[..]))
.request();
assert!(!req.validate_header(
&header,
&Key::new("tok", crate::SHA256).unwrap(),
Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
));
}
#[test]
fn test_validate_hash_validated() {
let header = make_header_with_hash();
let hash = vec![1, 2, 3, 4];
let req = RequestBuilder::new("", "", 0, "")
.hash(Some(&hash[..]))
.request();
assert!(req.validate_header(
&header,
&Key::new("tok", crate::SHA256).unwrap(),
Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
));
let hash = vec![99, 99, 99, 99];
let req = RequestBuilder::new("", "", 0, "")
.hash(Some(&hash[..]))
.request();
assert!(!req.validate_header(
&header,
&Key::new("tok", crate::SHA256).unwrap(),
Duration::from_secs(1000 * ONE_YEAR_IN_SECS)
));
}
fn round_trip_bewit(req: Request, ts: SystemTime, expected: bool) {
let credentials = Credentials {
id: "me".to_string(),
key: Key::new("tok", crate::SHA256).unwrap(),
};
let bewit = req.make_bewit(&credentials, ts).unwrap();
let bewit = bewit.to_str();
let bewit = Bewit::from_str(&bewit).unwrap();
assert_eq!(req.validate_bewit(&bewit, &credentials.key), expected);
}
#[test]
fn test_validate_bewit() {
let req = RequestBuilder::new("GET", "foo.com", 443, "/x/y/z").request();
round_trip_bewit(req, SystemTime::now() + Duration::from_secs(10 * 60), true);
}
#[test]
fn test_validate_bewit_ext() {
let req = RequestBuilder::new("GET", "foo.com", 443, "/x/y/z")
.ext("abcd")
.request();
round_trip_bewit(req, SystemTime::now() + Duration::from_secs(10 * 60), true);
}
#[test]
fn test_validate_bewit_expired() {
let req = RequestBuilder::new("GET", "foo.com", 443, "/x/y/z").request();
round_trip_bewit(req, SystemTime::now() - Duration::from_secs(10 * 60), false);
}
}