1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
use std::{collections::HashMap, fmt::Debug, time::Duration};
use reqwest::{header::HeaderMap, Method, StatusCode};
use url::Url;
/// # Request
/// Request is an internal struct used to create various OIDC requests.
#[derive(Debug)]
pub struct Request {
/// Url of the request without query params
pub url: String,
/// Expected status code from the server
pub expected: StatusCode,
/// Http method of the request
pub method: Method,
/// Whether or not to expect body with the response
pub expect_body: bool,
/// Specifies if the request is using bearer auth, and checks for bearer token related errors
pub bearer: bool,
/// Headers that are sent in the request
pub headers: HeaderMap,
/// Query Params that are send with the request
pub search_params: HashMap<String, Vec<String>>,
/// The request body to be sent
pub json: Option<serde_json::Value>,
/// The request form body to be sent
pub form: Option<HashMap<String, serde_json::Value>>,
/// The request body to be sent
pub body: Option<String>,
/// Expected response type
pub response_type: Option<String>,
/// Specifies if the request is MTLS and needs client certificate
pub mtls: bool,
}
impl Default for Request {
fn default() -> Self {
Self {
expect_body: true,
bearer: false,
expected: StatusCode::OK,
headers: HeaderMap::default(),
method: Method::GET,
url: "".to_string(),
search_params: HashMap::new(),
json: None,
form: None,
body: None,
response_type: None,
mtls: false,
}
}
}
impl Request {
/// Converts `search_params` to a [reqwest] compatible query params format
pub(crate) fn get_reqwest_query(&self) -> Vec<(String, String)> {
let mut query_list: Vec<(String, String)> = vec![];
for (k, v) in &self.search_params {
for val in v {
query_list.push((k.clone(), val.to_string()))
}
}
query_list
}
pub(crate) fn merge_form(&mut self, request: &Self) {
match (&mut self.form, &request.form) {
(None, Some(_)) => self.form = request.form.clone(),
(Some(own_form), Some(other_form)) => {
for (k, v) in other_form {
own_form.insert(k.to_string(), v.to_owned());
}
}
(None, None) | (Some(_), None) => {}
}
}
pub(crate) fn merge_headers(&mut self, request: &Self) {
for (k, v) in &request.headers {
self.headers.insert(k, v.clone());
}
}
}
/// # Response
/// Response is the abstracted version of the [reqwest] Response (async and blocking).
#[derive(Debug, Clone)]
pub struct Response {
/// Body from the response
pub body: Option<String>,
/// Status code of the response
pub status: StatusCode,
/// Response headers from the server
pub headers: HeaderMap,
}
impl Response {
/// Creates a new instance of Response from [reqwest::Response]
pub(crate) async fn from_async(response: reqwest::Response) -> Self {
let status = response.status();
let headers = response.headers().clone();
let body_result = response.text().await;
let mut body: Option<String> = None;
if let Ok(body_string) = body_result {
if !body_string.is_empty() {
body = Some(body_string);
}
}
Self {
body,
status,
headers,
}
}
}
/// # Request Interceptor
/// Type is a [Box]'ed [Interceptor] trait type.
pub type RequestInterceptor = Box<dyn Interceptor>;
/// # Lookup
/// Intention with this lookup is primarily for testing by
/// redirecting all requests to `localhost (127.0.0.1)` in combination with
/// the port of mock server.
///
/// *The url host, port and scheme will be swapped when building the request. No functionality is affected.*
///
/// ### *Example: *
///
/// ```
/// #[derive(Debug)]
/// struct CustomLookup;
///
/// impl Lookup for CustomLookup {
/// fn lookup(&mut self, _url: &Url) -> Url {
/// Url::parse("http://your-test-url:1234").unwrap()
/// }
/// }
///
/// RequestOptions {
/// lookup: Some(Box::new(CustomLookup{})),
/// ..Default::default()
/// }
/// ```
pub trait Lookup: Debug {
/// The url with path (no query params) of the request url is passed as the `url`
/// parameter in return expects a [Url] back.
///
/// - The Scheme and host is required from the returned [Url]. Returns
/// an error otherwise. Port is optional
///
/// - The entire url is just passed for reference.
/// Only the host:port will be replaced. Not the path.
///
/// ### *Example:*
///
/// ```
/// fn lookup(&mut self, _url: &Url) -> Url {
/// Url::parse("http://your-test-url:1234").unwrap()
/// }
/// ```
fn lookup(&mut self, url: &Url) -> Url;
}
/// # Interceptor
pub trait Interceptor: Debug {
/// This method which is called before making a request
fn intercept(&mut self, req: &Request) -> RequestOptions;
/// Clones the [Interceptor]
fn clone_box(&self) -> Box<dyn Interceptor>;
}
/// # RequestOptions
/// This struct is the return type of the [`Interceptor::intercept()`]
#[derive(Default, Debug)]
pub struct RequestOptions {
/// ### Headers that are to be appended with the request that is going to be made
pub headers: HeaderMap,
/// ### Request timeout
pub timeout: Duration,
/// ### Client public certificate in pem format.
/// The `client_crt` is ignored if `client_key` is not present
pub client_crt: Option<String>,
/// ### Client private certificate in pem format.
/// The `client_key` is ignored if `client_crt` is not present
pub client_key: Option<String>,
/// ### Client certificate in pkcs 12 format `.p12` or `.pfx`
/// Make sure to pass `client_pkcs_12_passphrase` if the
/// certificate is protected.
pub client_pkcs_12: Option<String>,
/// Passphrase for pkcs_12 certificate
pub client_pkcs_12_passphrase: Option<String>,
/// ### Server certificate in pem format
/// Useful when testing out with a self signed certificate and
/// cannot switch on the `danger_accept_invalid_certs` property
pub server_crt: Option<String>,
/// ### Lookup
/// [Lookup] trait allows you to resolve any url to a custom [Url].
pub lookup: Option<Box<dyn Lookup>>,
/// ### Accept invalid server certificates
/// Accepts self signed or unverified or expired certificates.
/// Use with caution.
pub danger_accept_invalid_certs: bool,
}