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 210 211 212 213 214 215 216 217 218 219 220 221 222
//! # Http Client Interface for Custom Http Clients
use std::collections::HashMap;
use std::fmt::Debug;
use std::future;
use url::Url;
use crate::helpers::string_map_to_form_url_encoded;
/// The Http methods
#[derive(Debug, Default, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub enum HttpMethod {
/// The GET method is used to retrieve data from a server.
#[default]
GET,
/// The POST method is used to submit data to a server.
POST,
/// The PUT method is used to replace all existing data on a server with the provided data.
PUT,
/// The PATCH method is used to update a specific part of a resource on a server.
PATCH,
/// The DELETE method is used to delete a resource from a server.
DELETE,
/// The HEAD method is used to retrieve only the headers of a resource, without the actual data.
HEAD,
/// The OPTIONS method is used to retrieve the capabilities of a server.
OPTIONS,
/// The TRACE method is used to echo the received request back to the client. (Rarely used)
TRACE,
/// The CONNECT method is used to establish a tunnel through the proxy server. (For use with secure proxies)
CONNECT,
}
/// The expectations set by methods such as discover, token grant, callback etc...
#[derive(Debug, Clone, Copy)]
pub struct HttpResponseExpectations {
/// Whether or not to expect body with the response
pub body: bool,
/// Specifies if the request is using bearer auth, and checks for bearer token related errors
pub bearer: bool,
/// Specifies if the response should be of type json and validates it
pub json_body: bool,
/// Expected status code from the server
pub status_code: u16,
}
/// The client certificate
#[derive(Debug)]
pub struct ClientCertificate {
/// Client public certificate in pem format.
pub cert: String,
/// Client private certificate in pem format.
pub key: String,
}
/// # Request
/// Request is an internal struct used to create various OIDC requests.
#[derive(Debug)]
pub struct HttpRequest {
/// Url of the request without query params
pub url: Url,
/// Http method of the request
pub method: HttpMethod,
/// Headers that are sent in the request
pub headers: HashMap<String, Vec<String>>,
/// The request body to be sent
pub body: Option<String>,
/// Specifies if the request is MTLS and needs client certificate
pub mtls: bool,
/// Client certificate to be used in the request
pub client_certificate: Option<ClientCertificate>,
/// Expectations to be fullfilled by the response
pub(crate) expectations: HttpResponseExpectations,
}
impl HttpRequest {
pub(crate) fn new() -> Self {
Self {
url: Url::parse("about:blank").unwrap(),
headers: HashMap::new(),
method: HttpMethod::GET,
body: None,
client_certificate: None,
mtls: false,
expectations: HttpResponseExpectations {
body: true,
bearer: false,
status_code: 200,
json_body: true,
},
}
}
pub(crate) fn url(mut self, url: Url) -> Self {
self.url = url;
self
}
pub(crate) fn method(mut self, method: HttpMethod) -> Self {
self.method = method;
self
}
pub(crate) fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
let name = name.into();
let value = value.into();
if let Some(values) = self.headers.get_mut(&name) {
values.push(value);
} else {
let values = vec![value];
self.headers.insert(name, values);
}
self
}
pub(crate) fn header_replace(mut self, name: impl Into<String>, value: Vec<String>) -> Self {
self.headers.insert(name.into(), value);
self
}
pub(crate) fn headers(mut self, headers: HashMap<String, Vec<String>>) -> Self {
self.headers = headers;
self
}
pub(crate) fn json(mut self, json: String) -> Self {
self.headers.insert(
"content-type".to_string(),
vec!["application/json".to_string()],
);
self.body(json)
}
pub(crate) fn form(mut self, form: HashMap<String, String>) -> Self {
let form_body = string_map_to_form_url_encoded(&form).unwrap();
self.headers.insert(
"content-type".to_string(),
vec!["application/x-www-form-urlencoded".to_string()],
);
self.body(form_body)
}
pub(crate) fn body(mut self, body: String) -> Self {
self.headers.insert(
"content-length".to_string(),
vec![body.as_bytes().len().to_string()],
);
self.body = Some(body);
self
}
pub(crate) fn mtls(mut self, mtls: bool) -> Self {
self.mtls = mtls;
self
}
pub(crate) fn expect_body(mut self, expect: bool) -> Self {
self.expectations.body = expect;
self
}
pub(crate) fn expect_status_code(mut self, code: u16) -> Self {
self.expectations.status_code = code;
self
}
pub(crate) fn expect_json_body(mut self, expect: bool) -> Self {
self.expectations.json_body = expect;
self
}
pub(crate) fn expect_bearer(mut self, bearer: bool) -> Self {
self.expectations.bearer = bearer;
self
}
}
/// Represents an HTTP response received from a server.
#[derive(Debug, Clone)]
pub struct HttpResponse {
/// The HTTP status code of the response (e.g., 200 for success, 404 for Not Found).
pub status_code: u16,
/// The content type header
pub content_type: Option<String>,
/// The www authenticate header
pub www_authenticate: Option<String>,
/// The dpop nonce
pub dpop_nonce: Option<String>,
/// The optional body content of the response. None if there is no body content (String).
pub body: Option<String>,
}
/// This trait defines the interface for making HTTP requests used by the OpenID library.
/// Users who need custom HTTP clients need to implement this trait.
pub trait OidcHttpClient {
/// Gets the client certificate for the current request. Return none if the request does not need mtls
fn get_client_certificate(
&self,
_req: &HttpRequest,
) -> impl std::future::Future<Output = Option<ClientCertificate>> + Send {
future::ready(None)
}
/// Makes an HTTP request using the provided HttpRequest object.
///
/// This function takes an `HttpRequest` object as input and returns a future
/// implementing `std::future::Future<Output = Result<HttpResponse, String>>`.
/// The future resolves to either a `Result<HttpResponse, String>`.
/// * On success, the result is `Ok(HttpResponse)` containing the HTTP response.
/// * On error, the result is `Err(String)` with an error message describing the failure.
///
/// This function allows the library to be agnostic to the specific HTTP client
/// implementation used, as long as it implements this trait.
fn request(
&self,
req: HttpRequest,
) -> impl std::future::Future<Output = Result<HttpResponse, String>> + Send;
}