Skip to main content

tuwunel_api_client/session/
ldap.rs

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