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. A deactivated local account is refused after a successful bind.
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	services
48		.users
49		.check_ldap_login(lowercased_user_id)
50		.await?;
51
52	// LDAP users are automatically created on first login attempt. This is a very
53	// common feature that can be seen on many services using a LDAP provider for
54	// their users (synapse, Nextcloud, Jellyfin, ...).
55	//
56	// LDAP users are created with a dummy password but non-empty because an empty
57	// password is reserved for deactivated accounts. The tuwunel password field
58	// will never be read to login a LDAP user so it's not an issue.
59	if !services.users.exists(lowercased_user_id).await {
60		services
61			.users
62			.full_register(Register {
63				user_id: Some(lowercased_user_id),
64				password: Some("*"),
65				origin: Some("ldap"),
66				..Default::default()
67			})
68			.await?;
69	}
70
71	// only perform admin add/remove check if admin_filter is set
72	if !services.config.ldap.admin_filter.is_empty() {
73		let is_tuwunel_admin = services
74			.admin
75			.user_is_admin(lowercased_user_id)
76			.await;
77
78		if is_ldap_admin && !is_tuwunel_admin {
79			services
80				.admin
81				.make_user_admin(lowercased_user_id)
82				.boxed()
83				.await?;
84		} else if !is_ldap_admin && is_tuwunel_admin {
85			services
86				.admin
87				.revoke_admin(lowercased_user_id)
88				.await?;
89		}
90	}
91
92	Ok(user_id)
93}