haste_server/auth_n/oidc/middleware/
oidc_parameter_inject.rs1use axum::RequestExt;
2use axum::http::{Method, StatusCode};
3use axum::response::IntoResponse;
4use axum::{body::Body, extract::Request, response::Response};
5use axum::{body::to_bytes, extract::Query};
6use haste_fhir_model::r4::generated::terminology::IssueType;
7use haste_fhir_operation_error::OperationOutcomeError;
8use serde::Deserialize;
9use std::sync::Arc;
10use std::task::{Context, Poll};
11use std::{collections::HashMap, pin::Pin};
12use tower::{Layer, Service};
13
14#[derive(Deserialize, Clone, Debug)]
15pub struct OIDCParameters {
16 pub parameters: HashMap<String, String>,
17}
18
19#[derive(Clone, Debug)]
20pub struct ParameterConfig {
21 pub required_parameters: Vec<String>,
22 pub optional_parameters: Vec<String>,
23}
24
25#[derive(Clone)]
26pub struct OIDCParameterInjectLayer {
27 state: Arc<ParameterConfig>,
28}
29
30impl<S> Layer<S> for OIDCParameterInjectLayer {
31 type Service = OIDCParameterInjectService<S>;
32
33 fn layer(&self, inner: S) -> Self::Service {
34 OIDCParameterInjectService {
35 inner,
36 state: self.state.clone(),
37 }
38 }
39}
40
41impl OIDCParameterInjectLayer {
42 pub fn new(state: Arc<ParameterConfig>) -> Self {
43 OIDCParameterInjectLayer { state }
44 }
45}
46
47#[derive(Clone)]
48pub struct OIDCParameterInjectService<S> {
49 inner: S,
50 state: Arc<ParameterConfig>,
51}
52
53fn validate_parameter(param_name: &str, param_value: &str) -> Result<(), String> {
54 if param_value.is_empty() {
55 return Err("Parameter cannot be empty".to_string());
56 }
57
58 if param_name == "response_type" && param_value != "code" {
60 return Err(format!("Invalid response_type: {}", param_value));
61 }
62
63 Ok(())
64}
65
66impl<'a, T> Service<Request<Body>> for OIDCParameterInjectService<T>
67where
68 T: Service<Request, Response = Response> + Send + 'static + Clone,
69 T::Future: Send + 'static,
70 T::Error: IntoResponse,
71{
72 type Response = T::Response;
73 type Error = T::Error;
74 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
75
76 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
77 self.inner.poll_ready(cx)
78 }
79
80 fn call(&mut self, mut request: Request) -> Self::Future {
81 let clone = self.inner.clone();
83 let mut inner = std::mem::replace(&mut self.inner, clone);
85 let parameter_config = self.state.clone();
86
87 Box::pin(async move {
88 let Ok(Query(query_params)) = request
89 .extract_parts::<Query<HashMap<String, String>>>()
90 .await
91 else {
92 return Ok((StatusCode::BAD_REQUEST, "".to_string()).into_response());
93 };
94
95 let (parts, body) = request.into_parts();
96 let bytes = to_bytes(body, 10000).await;
97 let Ok(bytes) = bytes else {
98 return Ok((
99 StatusCode::BAD_REQUEST,
100 "Body was to large size limit 10k bytes".to_string(),
101 )
102 .into_response());
103 };
104
105 let content_type = parts
106 .headers
107 .get(axum::http::header::CONTENT_TYPE)
108 .and_then(|v| v.to_str().ok())
109 .unwrap_or("");
110
111 let unvalidated_parameters = match parts.method {
113 Method::POST => match content_type {
114 "application/x-www-form-urlencoded" => {
115 let mut form_params =
116 serde_html_form::from_bytes::<HashMap<String, String>>(&bytes)
117 .unwrap_or_else(|_e| HashMap::new());
118 form_params.extend(query_params);
119
120 Ok(form_params)
121 }
122 "application/json" => {
123 let mut json_params =
124 serde_json::from_slice::<HashMap<String, String>>(&bytes)
125 .unwrap_or_else(|_e| HashMap::new());
126 json_params.extend(query_params);
127 Ok(json_params)
128 }
129 content_type => Err(OperationOutcomeError::error(
130 IssueType::not_supported(),
131 format!(
132 "Unsupported Content-Type for OIDC parameter injection '{}'",
133 content_type
134 ),
135 )),
136 },
137 Method::GET => Ok(query_params),
138 _ => Err(OperationOutcomeError::error(
139 IssueType::not_supported(),
140 "Unsupported HTTP method for OIDC parameter injection".to_string(),
141 )),
142 };
143
144 let Ok(unvalidated_parameters) = unvalidated_parameters else {
145 let error = unvalidated_parameters.err().unwrap();
146 return Ok(error.into_response());
147 };
148
149 let mut oidc_parameters = OIDCParameters {
150 parameters: HashMap::new(),
151 };
152
153 for (is_required, parameter_name) in parameter_config
155 .required_parameters
156 .iter()
157 .map(|p| (true, p))
158 .chain(
159 parameter_config
160 .optional_parameters
161 .iter()
162 .map(|p| (false, p)),
163 )
164 {
165 if let Some(parameter_value) = unvalidated_parameters.get(parameter_name) {
166 if let Err(_e) = validate_parameter(parameter_name, parameter_value) {
167 return Ok((
168 StatusCode::BAD_REQUEST,
169 format!("Invalid parameter: '{}'", parameter_name),
170 )
171 .into_response());
172 }
173
174 oidc_parameters
175 .parameters
176 .insert(parameter_name.clone(), parameter_value.clone());
177 } else if is_required {
178 return Ok((
179 StatusCode::BAD_REQUEST,
180 format!("Missing required parameter: '{parameter_name}'",),
181 )
182 .into_response());
183 }
184 }
185
186 let new_body = Body::from(bytes);
187 let mut new_request = Request::from_parts(parts, new_body);
188 new_request.extensions_mut().insert(oidc_parameters);
189
190 let future = inner.call(new_request);
191 let response: Response = future.await?;
192 Ok(response)
193 })
194 }
195}