Skip to main content

tuwunel_api/client/session/
ldap.rs

1use futures::FutureExt;
2use ruma::{OwnedUserId, UserId};
3use tuwunel_core::{Err, Result, debug};
4use tuwunel_service::{Services, users::Register};
5
6use super::password_login;
7
8/// Authenticates the given user through the configured LDAP server.
9///
10/// Creates the user if the user is found in the LDAP and do not already have an
11/// account.
12#[tracing::instrument(skip_all, fields(%user_id), name = "ldap")]
13pub(super) async fn ldap_login(
14	services: &Services,
15	user_id: &UserId,
16	lowercased_user_id: &UserId,
17	password: &str,
18) -> Result<OwnedUserId> {
19	let (user_dn, is_ldap_admin) = match services.config.ldap.bind_dn.as_ref() {
20		| Some(bind_dn) if bind_dn.contains("{username}") =>
21			(bind_dn.replace("{username}", lowercased_user_id.localpart()), false),
22		| _ => {
23			debug!("Searching user in LDAP");
24
25			let dns = services.users.search_ldap(user_id).await?;
26			if dns.len() >= 2 {
27				return Err!(Ldap("LDAP search returned two or more results"));
28			}
29
30			let Some((user_dn, is_admin)) = dns.first() else {
31				return password_login(services, user_id, lowercased_user_id, password).await;
32			};
33
34			(user_dn.clone(), *is_admin)
35		},
36	};
37
38	let user_id = services
39		.users
40		.auth_ldap(&user_dn, password)
41		.await
42		.map(|()| lowercased_user_id.to_owned())?;
43
44	// LDAP users are automatically created on first login attempt. This is a very
45	// common feature that can be seen on many services using a LDAP provider for
46	// their users (synapse, Nextcloud, Jellyfin, ...).
47	//
48	// LDAP users are crated with a dummy password but non empty because an empty
49	// password is reserved for deactivated accounts. The tuwunel password field
50	// will never be read to login a LDAP user so it's not an issue.
51	if !services.users.exists(lowercased_user_id).await {
52		services
53			.users
54			.full_register(Register {
55				user_id: Some(lowercased_user_id),
56				password: Some("*"),
57				origin: Some("ldap"),
58				..Default::default()
59			})
60			.await?;
61	}
62
63	// only perform admin add/remove check if admin_filter is set
64	if !services.config.ldap.admin_filter.is_empty() {
65		let is_tuwunel_admin = services
66			.admin
67			.user_is_admin(lowercased_user_id)
68			.await;
69
70		if is_ldap_admin && !is_tuwunel_admin {
71			services
72				.admin
73				.make_user_admin(lowercased_user_id)
74				.boxed()
75				.await?;
76		} else if !is_ldap_admin && is_tuwunel_admin {
77			services
78				.admin
79				.revoke_admin(lowercased_user_id)
80				.await?;
81		}
82	}
83
84	Ok(user_id)
85}