pdk_unit/backends/
ldap.rs1use crate::{Backend, UnitHttpMessage, UnitHttpRequest, UnitHttpResponse};
6use base64::engine::general_purpose::STANDARD as BASE64;
7use base64::Engine;
8use std::cell::RefCell;
9use std::collections::HashMap;
10
11#[derive(Default)]
12pub struct LdapBackend {
13 #[allow(clippy::type_complexity)]
14 configs: RefCell<HashMap<Option<UnitLdapConfig>, Vec<(String, String)>>>,
15}
16
17#[derive(PartialEq, Eq, Hash, Default)]
34pub struct UnitLdapConfig {
35 server_url: String,
36 server_user_dn: String,
37 server_user_password: String,
38 search_base: String,
39 search_filter: String,
40 search_in_subtree: bool,
41}
42
43impl UnitLdapConfig {
44 pub fn server_url(mut self, url: impl Into<String>) -> Self {
46 self.server_url = url.into();
47 self
48 }
49
50 pub fn server_user_dn(mut self, dn: impl Into<String>) -> Self {
52 self.server_user_dn = dn.into();
53 self
54 }
55
56 pub fn server_user_password(mut self, pass: impl Into<String>) -> Self {
58 self.server_user_password = pass.into();
59 self
60 }
61
62 pub fn search_base(mut self, base: impl Into<String>) -> Self {
64 self.search_base = base.into();
65 self
66 }
67
68 pub fn search_filter(mut self, filter: impl Into<String>) -> Self {
70 self.search_filter = filter.into();
71 self
72 }
73
74 pub fn search_in_subtree(mut self) -> Self {
76 self.search_in_subtree = true;
77 self
78 }
79}
80
81impl LdapBackend {
82 pub fn add_data<U: Into<String>, P: Into<String>>(
83 &self,
84 config: Option<UnitLdapConfig>,
85 user: U,
86 pass: P,
87 ) {
88 self.configs
89 .borrow_mut()
90 .entry(config)
91 .or_default()
92 .push((user.into(), pass.into()));
93 }
94}
95
96impl Backend for LdapBackend {
97 fn call(&self, req: UnitHttpRequest) -> UnitHttpResponse {
98 let headers = req.headers();
99
100 let headers: HashMap<String, String> = headers
101 .iter()
102 .map(|(k, v)| (k.to_ascii_lowercase(), v.clone()))
103 .collect();
104
105 let config = headers
106 .get("x-flex-authentication-ldap-url")
107 .map(|url| UnitLdapConfig {
108 server_url: url.clone(),
109 server_user_dn: headers
110 .get("x-flex-authentication-ldap-bind-dn")
111 .cloned()
112 .unwrap_or_default(),
113 server_user_password: headers
114 .get("x-flex-authentication-ldap-bind-pass")
115 .cloned()
116 .unwrap_or_default(),
117 search_base: headers
118 .get("x-flex-authentication-ldap-search-base")
119 .cloned()
120 .unwrap_or_default(),
121 search_filter: headers
122 .get("x-flex-authentication-ldap-search-filter")
123 .cloned()
124 .unwrap_or_default(),
125 search_in_subtree: headers
126 .get("x-flex-authentication-ldap-search-in-subtree")
127 .is_some_and(|v| v == "true"),
128 });
129
130 let credential = match headers
131 .get("authorization")
132 .and_then(|v| v.strip_prefix("Basic "))
133 .map(|v| v.to_string())
134 {
135 Some(c) => c,
136 None => return UnitHttpResponse::new(401),
137 };
138
139 let (user, pass) = match base64_decode_credentials(&credential) {
140 Some(pair) => pair,
141 None => return UnitHttpResponse::new(400),
142 };
143
144 let configs = self.configs.borrow();
145 let found = configs
146 .get(&config)
147 .into_iter()
148 .chain(configs.get(&None))
149 .flat_map(|pairs| pairs.iter())
150 .any(|(u, p)| u == &user && p == &pass);
151
152 if found {
153 UnitHttpResponse::new(200)
154 } else {
155 UnitHttpResponse::new(401)
156 }
157 }
158}
159
160fn base64_decode_credentials(encoded: &str) -> Option<(String, String)> {
161 let decoded = BASE64.decode(encoded.as_bytes()).ok()?;
162 let s = String::from_utf8(decoded).ok()?;
163 let mut parts = s.splitn(2, ':');
164 let user = parts.next()?.to_string();
165 let pass = parts.next()?.to_string();
166 Some((user, pass))
167}