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 bind_dn = services
20		.users
21		.ldap_bind_dn(lowercased_user_id.localpart());
22
23	let (user_dn, is_ldap_admin) = match bind_dn {
24		| Some(user_dn) => (user_dn, false),
25		| None => {
26			debug!("Searching user in LDAP");
27
28			let dns = services.users.search_ldap(user_id).await?;
29			if dns.len() >= 2 {
30				return Err!(Ldap("LDAP search returned two or more results"));
31			}
32
33			let Some((user_dn, is_admin)) = dns.first() else {
34				return password_login(services, user_id, lowercased_user_id, password).await;
35			};
36
37			(user_dn.clone(), *is_admin)
38		},
39	};
40
41	let user_id = services
42		.users
43		.auth_ldap(&user_dn, password)
44		.await
45		.map(|()| lowercased_user_id.to_owned())?;
46
47	// LDAP users are automatically created on first login attempt. This is a very
48	// common feature that can be seen on many services using a LDAP provider for
49	// their users (synapse, Nextcloud, Jellyfin, ...).
50	//
51	// LDAP users are created with a dummy password but non-empty because an empty
52	// password is reserved for deactivated accounts. The tuwunel password field
53	// will never be read to login a LDAP user so it's not an issue.
54	if !services.users.exists(lowercased_user_id).await {
55		services
56			.users
57			.full_register(Register {
58				user_id: Some(lowercased_user_id),
59				password: Some("*"),
60				origin: Some("ldap"),
61				..Default::default()
62			})
63			.await?;
64	}
65
66	// only perform admin add/remove check if admin_filter is set
67	if !services.config.ldap.admin_filter.is_empty() {
68		let is_tuwunel_admin = services
69			.admin
70			.user_is_admin(lowercased_user_id)
71			.await;
72
73		if is_ldap_admin && !is_tuwunel_admin {
74			services
75				.admin
76				.make_user_admin(lowercased_user_id)
77				.boxed()
78				.await?;
79		} else if !is_ldap_admin && is_tuwunel_admin {
80			services
81				.admin
82				.revoke_admin(lowercased_user_id)
83				.await?;
84		}
85	}
86
87	Ok(user_id)
88}