kanidmd_core/https/extractors/
mod.rs
1use crate::https::ServerState;
2use axum::{
3 async_trait,
4 extract::{connect_info::Connected, FromRequestParts},
5 http::{header::AUTHORIZATION as AUTHORISATION, request::Parts, StatusCode},
6};
7use axum_extra::extract::cookie::CookieJar;
8use compact_jwt::JwsCompact;
9use kanidm_proto::internal::COOKIE_BEARER_TOKEN;
10use kanidmd_lib::prelude::{ClientAuthInfo, ClientCertInfo, Source};
11use std::net::{IpAddr, SocketAddr};
12use std::str::FromStr;
13
14pub use kanidmd_lib::idm::server::DomainInfoRead;
16
17pub struct VerifiedClientInformation(pub ClientAuthInfo);
18
19#[async_trait]
20impl FromRequestParts<ServerState> for VerifiedClientInformation {
21 type Rejection = (StatusCode, &'static str);
22
23 #[instrument(level = "debug", skip_all)]
25 async fn from_request_parts(
26 parts: &mut Parts,
27 state: &ServerState,
28 ) -> Result<Self, Self::Rejection> {
29 let ClientConnInfo {
30 connection_addr: _,
31 client_ip_addr,
32 client_cert,
33 } = parts.extensions.remove::<ClientConnInfo>().ok_or((
34 StatusCode::INTERNAL_SERVER_ERROR,
35 "request info contains invalid data",
36 ))?;
37
38 let (basic_authz, bearer_token) = if let Some(header) = parts.headers.get(AUTHORISATION) {
39 if let Some((authz_type, authz_data)) = header
40 .to_str()
41 .map_err(|err| {
42 warn!(?err, "Invalid authz header, ignoring");
43 })
44 .ok()
45 .and_then(|s| s.split_once(' '))
46 {
47 let authz_type = authz_type.to_lowercase();
48
49 if authz_type == "basic" {
50 (Some(authz_data.to_string()), None)
51 } else if authz_type == "bearer" {
52 if let Ok(jwsc) = JwsCompact::from_str(authz_data) {
53 (None, Some(jwsc))
54 } else {
55 warn!("bearer jws invalid");
56 (None, None)
57 }
58 } else {
59 warn!("authorisation header invalid, ignoring");
60 (None, None)
61 }
62 } else {
63 (None, None)
64 }
65 } else {
66 let jar = CookieJar::from_headers(&parts.headers);
68
69 let value: Option<&str> = jar.get(COOKIE_BEARER_TOKEN).map(|c| c.value());
70
71 let maybe_bearer = value.and_then(|authz_data| JwsCompact::from_str(authz_data).ok());
72
73 (None, maybe_bearer)
74 };
75
76 let mut client_auth_info = ClientAuthInfo::new(
77 Source::Https(client_ip_addr),
78 client_cert,
79 bearer_token,
80 basic_authz,
81 );
82
83 let _ = state
87 .qe_r_ref
88 .pre_validate_client_auth_info(&mut client_auth_info)
89 .await;
90
91 Ok(VerifiedClientInformation(client_auth_info))
92 }
93}
94
95pub struct DomainInfo(pub DomainInfoRead);
96
97#[async_trait]
98impl FromRequestParts<ServerState> for DomainInfo {
99 type Rejection = (StatusCode, &'static str);
100
101 #[instrument(level = "debug", skip_all)]
103 async fn from_request_parts(
104 _parts: &mut Parts,
105 state: &ServerState,
106 ) -> Result<Self, Self::Rejection> {
107 Ok(DomainInfo(state.qe_r_ref.domain_info_read()))
108 }
109}
110
111#[derive(Debug, Clone)]
112pub struct ClientConnInfo {
113 #[allow(dead_code)]
116 pub connection_addr: SocketAddr,
117 pub client_ip_addr: IpAddr,
120 pub client_cert: Option<ClientCertInfo>,
122}
123
124impl Connected<ClientConnInfo> for ClientConnInfo {
126 fn connect_info(target: ClientConnInfo) -> Self {
127 target
128 }
129}
130
131impl Connected<SocketAddr> for ClientConnInfo {
133 fn connect_info(connection_addr: SocketAddr) -> Self {
134 ClientConnInfo {
135 client_ip_addr: connection_addr.ip(),
136 connection_addr,
137 client_cert: None,
138 }
139 }
140}