kanidmd_core/https/extractors/
mod.rs1use crate::https::ServerState;
2use axum::{
3    extract::{connect_info::Connected, FromRequestParts},
4    http::{header::AUTHORIZATION as AUTHORISATION, request::Parts, StatusCode},
5};
6use axum_extra::extract::cookie::CookieJar;
7use compact_jwt::JwsCompact;
8use kanidm_proto::internal::COOKIE_BEARER_TOKEN;
9use kanidmd_lib::prelude::{ClientAuthInfo, ClientCertInfo, Source};
10use std::net::{IpAddr, SocketAddr};
11use std::str::FromStr;
12
13pub use kanidmd_lib::idm::server::DomainInfoRead;
15
16pub struct VerifiedClientInformation(pub ClientAuthInfo);
17
18impl FromRequestParts<ServerState> for VerifiedClientInformation {
19    type Rejection = (StatusCode, &'static str);
20
21    #[instrument(level = "debug", skip_all)]
23    async fn from_request_parts(
24        parts: &mut Parts,
25        state: &ServerState,
26    ) -> Result<Self, Self::Rejection> {
27        let ClientConnInfo {
28            connection_addr: _,
29            client_ip_addr,
30            client_cert,
31        } = parts.extensions.remove::<ClientConnInfo>().ok_or((
32            StatusCode::INTERNAL_SERVER_ERROR,
33            "request info contains invalid data",
34        ))?;
35
36        let (basic_authz, bearer_token) = if let Some(header) = parts.headers.get(AUTHORISATION) {
37            if let Some((authz_type, authz_data)) = header
38                .to_str()
39                .map_err(|err| {
40                    warn!(?err, "Invalid authz header, ignoring");
41                })
42                .ok()
43                .and_then(|s| s.split_once(' '))
44            {
45                let authz_type = authz_type.to_lowercase();
46
47                if authz_type == "basic" {
48                    (Some(authz_data.to_string()), None)
49                } else if authz_type == "bearer" {
50                    if let Ok(jwsc) = JwsCompact::from_str(authz_data) {
51                        (None, Some(jwsc))
52                    } else {
53                        warn!("bearer jws invalid");
54                        (None, None)
55                    }
56                } else {
57                    warn!("authorisation header invalid, ignoring");
58                    (None, None)
59                }
60            } else {
61                (None, None)
62            }
63        } else {
64            let jar = CookieJar::from_headers(&parts.headers);
66
67            let value: Option<&str> = jar.get(COOKIE_BEARER_TOKEN).map(|c| c.value());
68
69            let maybe_bearer = value.and_then(|authz_data| JwsCompact::from_str(authz_data).ok());
70
71            (None, maybe_bearer)
72        };
73
74        let mut client_auth_info = ClientAuthInfo::new(
75            Source::Https(client_ip_addr),
76            client_cert,
77            bearer_token,
78            basic_authz,
79        );
80
81        let _ = state
85            .qe_r_ref
86            .pre_validate_client_auth_info(&mut client_auth_info)
87            .await;
88
89        Ok(VerifiedClientInformation(client_auth_info))
90    }
91}
92
93pub struct AuthorisationHeaders(pub ClientAuthInfo);
94
95impl FromRequestParts<ServerState> for AuthorisationHeaders {
96    type Rejection = (StatusCode, &'static str);
97
98    #[instrument(level = "debug", skip_all)]
100    async fn from_request_parts(
101        parts: &mut Parts,
102        _state: &ServerState,
103    ) -> Result<Self, Self::Rejection> {
104        let ClientConnInfo {
105            connection_addr: _,
106            client_ip_addr,
107            client_cert,
108        } = parts.extensions.remove::<ClientConnInfo>().ok_or((
109            StatusCode::INTERNAL_SERVER_ERROR,
110            "request info contains invalid data",
111        ))?;
112
113        let (basic_authz, bearer_token) = if let Some(header) = parts.headers.get(AUTHORISATION) {
114            if let Some((authz_type, authz_data)) = header
115                .to_str()
116                .map_err(|err| {
117                    warn!(?err, "Invalid authz header, ignoring");
118                })
119                .ok()
120                .and_then(|s| s.split_once(' '))
121            {
122                let authz_type = authz_type.to_lowercase();
123
124                if authz_type == "basic" {
125                    (Some(authz_data.to_string()), None)
126                } else if authz_type == "bearer" {
127                    if let Ok(jwsc) = JwsCompact::from_str(authz_data) {
128                        (None, Some(jwsc))
129                    } else {
130                        warn!("bearer jws invalid");
131                        (None, None)
132                    }
133                } else {
134                    warn!("authorisation header invalid, ignoring");
135                    (None, None)
136                }
137            } else {
138                (None, None)
139            }
140        } else {
141            (None, None)
142        };
143
144        let client_auth_info = ClientAuthInfo::new(
145            Source::Https(client_ip_addr),
146            client_cert,
147            bearer_token,
148            basic_authz,
149        );
150
151        Ok(AuthorisationHeaders(client_auth_info))
152    }
153}
154
155pub struct DomainInfo(pub DomainInfoRead);
156
157impl FromRequestParts<ServerState> for DomainInfo {
158    type Rejection = (StatusCode, &'static str);
159
160    #[instrument(level = "debug", skip_all)]
162    async fn from_request_parts(
163        _parts: &mut Parts,
164        state: &ServerState,
165    ) -> Result<Self, Self::Rejection> {
166        Ok(DomainInfo(state.qe_r_ref.domain_info_read()))
167    }
168}
169
170#[derive(Debug, Clone)]
171pub struct ClientConnInfo {
172    #[allow(dead_code)]
175    pub connection_addr: SocketAddr,
176    pub client_ip_addr: IpAddr,
179    pub client_cert: Option<ClientCertInfo>,
181}
182
183impl Connected<ClientConnInfo> for ClientConnInfo {
185    fn connect_info(target: ClientConnInfo) -> Self {
186        target
187    }
188}
189
190impl Connected<SocketAddr> for ClientConnInfo {
192    fn connect_info(connection_addr: SocketAddr) -> Self {
193        ClientConnInfo {
194            client_ip_addr: connection_addr.ip().to_canonical(),
195            connection_addr,
196            client_cert: None,
197        }
198    }
199}