Skip to main content

haste_server/auth_n/oidc/middleware/
oidc_parameter_inject.rs

1use 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    // implicit flow is deprecated for the token leakage it invites (OAuth 2.0 Security Best Current Practice ยง2.1.2).
59    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        // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services
82        let clone = self.inner.clone();
83        // take the service that was ready
84        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            // Either check the body if serializes or check the query params.
112            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            // Check for required parameters
154            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}