Skip to main content

tuwunel_service/users/
ldap.rs

1#![cfg(feature = "ldap")]
2
3use std::collections::HashMap;
4
5use ldap3::{LdapConnAsync, Scope, SearchEntry};
6use ruma::UserId;
7use tuwunel_core::{Result, debug, err, error, implement, result::LogErr, trace};
8
9/// Performs a LDAP search for the given user.
10///
11/// Returns the list of matching users, with a boolean for each result set
12/// to true if the user is an admin.
13#[implement(super::Service)]
14pub async fn search_ldap(&self, user_id: &UserId) -> Result<Vec<(String, bool)>> {
15	let localpart = user_id.localpart().to_owned();
16	let lowercased_localpart = localpart.to_lowercase();
17
18	let config = &self.services.server.config.ldap;
19	let uri = config
20		.uri
21		.as_ref()
22		.ok_or_else(|| err!(Ldap(error!("LDAP URI is not configured."))))?;
23
24	if uri.scheme().starts_with("ldaps") {
25		self.services.globals.init_rustls_provider()?;
26	}
27
28	debug!(?uri, "LDAP creating connection...");
29	let (conn, mut ldap) = LdapConnAsync::new(uri.as_str())
30		.await
31		.map_err(|e| err!(Ldap(error!(?user_id, "LDAP connection setup error: {e}"))))?;
32
33	let driver = self.services.server.runtime().spawn(async move {
34		match conn.drive().await {
35			| Err(e) => error!("LDAP connection error: {e}"),
36			| Ok(()) => debug!("LDAP connection completed."),
37		}
38	});
39
40	match (&config.bind_dn, &config.bind_password_file) {
41		| (Some(bind_dn), Some(bind_password_file)) => {
42			let bind_pw = String::from_utf8(std::fs::read(bind_password_file)?)?;
43			ldap.simple_bind(bind_dn, bind_pw.trim())
44				.await
45				.and_then(ldap3::LdapResult::success)
46				.map_err(|e| err!(Ldap(error!("LDAP bind error: {e}"))))?;
47		},
48		| (..) => {},
49	}
50
51	let attr = [&config.uid_attribute, &config.name_attribute];
52
53	let user_filter = &config
54		.filter
55		.replace("{username}", &lowercased_localpart);
56
57	let (entries, _result) = ldap
58		.search(&config.base_dn, Scope::Subtree, user_filter, &attr)
59		.await
60		.and_then(ldap3::SearchResult::success)
61		.inspect(|(entries, result)| trace!(?entries, ?result, "LDAP Search"))
62		.map_err(|e| err!(Ldap(error!(?attr, ?user_filter, "LDAP search error: {e}"))))?;
63
64	let mut dns: HashMap<String, bool> = entries
65		.into_iter()
66		.filter_map(|entry| {
67			let search_entry = SearchEntry::construct(entry);
68			debug!(?search_entry, "LDAP search entry");
69			search_entry
70				.attrs
71				.get(&config.uid_attribute)
72				.into_iter()
73				.chain(search_entry.attrs.get(&config.name_attribute))
74				.any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart))
75				.then_some((search_entry.dn, false))
76		})
77		.collect();
78
79	if !config.admin_filter.is_empty() {
80		let admin_base_dn = if config.admin_base_dn.is_empty() {
81			&config.base_dn
82		} else {
83			&config.admin_base_dn
84		};
85
86		let admin_filter = &config
87			.admin_filter
88			.replace("{username}", &lowercased_localpart);
89
90		let (admin_entries, _result) = ldap
91			.search(admin_base_dn, Scope::Subtree, admin_filter, &attr)
92			.await
93			.and_then(ldap3::SearchResult::success)
94			.inspect(|(entries, result)| trace!(?entries, ?result, "LDAP Admin Search"))
95			.map_err(|e| {
96				err!(Ldap(error!(?attr, ?admin_filter, "Ldap admin search error: {e}")))
97			})?;
98
99		dns.extend(admin_entries.into_iter().filter_map(|entry| {
100			let search_entry = SearchEntry::construct(entry);
101			debug!(?search_entry, "LDAP search entry");
102			search_entry
103				.attrs
104				.get(&config.uid_attribute)
105				.into_iter()
106				.chain(search_entry.attrs.get(&config.name_attribute))
107				.any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart))
108				.then_some((search_entry.dn, true))
109		}));
110	}
111
112	ldap.unbind()
113		.await
114		.map_err(|e| err!(Ldap(error!("LDAP unbind error: {e}"))))?;
115
116	driver.await.log_err().ok();
117
118	Ok(dns.drain().collect())
119}
120
121#[implement(super::Service)]
122pub async fn auth_ldap(&self, user_dn: &str, password: &str) -> Result {
123	let config = &self.services.server.config.ldap;
124	let uri = config
125		.uri
126		.as_ref()
127		.ok_or_else(|| err!(Ldap(error!("LDAP URI is not configured."))))?;
128
129	if uri.scheme().starts_with("ldaps") {
130		self.services.globals.init_rustls_provider()?;
131	}
132
133	debug!(?uri, "LDAP creating connection...");
134	let (conn, mut ldap) = LdapConnAsync::new(uri.as_str())
135		.await
136		.map_err(|e| err!(Ldap(error!(?user_dn, "LDAP connection setup error: {e}"))))?;
137
138	let driver = self.services.server.runtime().spawn(async move {
139		match conn.drive().await {
140			| Err(e) => error!("LDAP connection error: {e}"),
141			| Ok(()) => debug!("LDAP connection completed."),
142		}
143	});
144
145	ldap.simple_bind(user_dn, password)
146		.await
147		.and_then(ldap3::LdapResult::success)
148		.map_err(|e| err!(Request(Forbidden(debug_error!("LDAP authentication error: {e}")))))?;
149
150	ldap.unbind()
151		.await
152		.map_err(|e| err!(Ldap(error!("LDAP unbind error: {e}"))))?;
153
154	driver.await.log_err().ok();
155
156	Ok(())
157}