mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
refactor: Remove LDAP support
This commit is contained in:
Generated
-124
@@ -240,45 +240,6 @@ dependencies = [
|
|||||||
"winnow 1.0.2",
|
"winnow 1.0.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "asn1-rs"
|
|
||||||
version = "0.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
|
|
||||||
dependencies = [
|
|
||||||
"asn1-rs-derive",
|
|
||||||
"asn1-rs-impl",
|
|
||||||
"displaydoc",
|
|
||||||
"nom 7.1.3",
|
|
||||||
"num-traits",
|
|
||||||
"rusticata-macros",
|
|
||||||
"thiserror",
|
|
||||||
"time",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "asn1-rs-derive"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"synstructure",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "asn1-rs-impl"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "assign"
|
name = "assign"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -1222,7 +1183,6 @@ dependencies = [
|
|||||||
"image",
|
"image",
|
||||||
"ipaddress",
|
"ipaddress",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"ldap3",
|
|
||||||
"lettre",
|
"lettre",
|
||||||
"log",
|
"log",
|
||||||
"loole",
|
"loole",
|
||||||
@@ -1677,20 +1637,6 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "der-parser"
|
|
||||||
version = "10.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
|
|
||||||
dependencies = [
|
|
||||||
"asn1-rs",
|
|
||||||
"displaydoc",
|
|
||||||
"nom 7.1.3",
|
|
||||||
"num-bigint",
|
|
||||||
"num-traits",
|
|
||||||
"rusticata-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.5.8"
|
version = "0.5.8"
|
||||||
@@ -3000,41 +2946,6 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lber"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cbcf559624bfd9fe8d488329a8959766335a43a9b8b2cdd6a2c379fca02909a5"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"nom 7.1.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ldap3"
|
|
||||||
version = "0.12.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "01fe89f5e7cfb7e4701e3a38ff9f00358e026a9aee940355d88ee9d81e5c7503"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"bytes",
|
|
||||||
"futures",
|
|
||||||
"futures-util",
|
|
||||||
"lber",
|
|
||||||
"log",
|
|
||||||
"nom 7.1.3",
|
|
||||||
"percent-encoding",
|
|
||||||
"rustls",
|
|
||||||
"rustls-native-certs",
|
|
||||||
"thiserror",
|
|
||||||
"tokio",
|
|
||||||
"tokio-rustls",
|
|
||||||
"tokio-stream",
|
|
||||||
"tokio-util",
|
|
||||||
"url",
|
|
||||||
"x509-parser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leb128fmt"
|
name = "leb128fmt"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -3753,15 +3664,6 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "oid-registry"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
|
|
||||||
dependencies = [
|
|
||||||
"asn1-rs",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
@@ -4880,15 +4782,6 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rusticata-macros"
|
|
||||||
version = "4.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
|
|
||||||
dependencies = [
|
|
||||||
"nom 7.1.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -6922,23 +6815,6 @@ version = "0.6.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "x509-parser"
|
|
||||||
version = "0.18.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
|
|
||||||
dependencies = [
|
|
||||||
"asn1-rs",
|
|
||||||
"data-encoding",
|
|
||||||
"der-parser",
|
|
||||||
"lazy_static",
|
|
||||||
"nom 7.1.3",
|
|
||||||
"oid-registry",
|
|
||||||
"rusticata-macros",
|
|
||||||
"thiserror",
|
|
||||||
"time",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xml5ever"
|
name = "xml5ever"
|
||||||
version = "0.18.1"
|
version = "0.18.1"
|
||||||
|
|||||||
@@ -549,11 +549,6 @@ features = ["std"]
|
|||||||
[workspace.dependencies.maplit]
|
[workspace.dependencies.maplit]
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
||||||
[workspace.dependencies.ldap3]
|
|
||||||
version = "0.12.0"
|
|
||||||
default-features = false
|
|
||||||
features = ["sync", "tls-rustls", "rustls-provider"]
|
|
||||||
|
|
||||||
[workspace.dependencies.yansi]
|
[workspace.dependencies.yansi]
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
|||||||
// Create user
|
// Create user
|
||||||
self.services
|
self.services
|
||||||
.users
|
.users
|
||||||
.create(&user_id, Some(password.as_str()), None)
|
.create(&user_id, Some(password.as_str()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Default to pretty displayname
|
// Default to pretty displayname
|
||||||
|
|||||||
@@ -48,9 +48,6 @@ jemalloc_stats = [
|
|||||||
"conduwuit-core/jemalloc_stats",
|
"conduwuit-core/jemalloc_stats",
|
||||||
"conduwuit-service/jemalloc_stats",
|
"conduwuit-service/jemalloc_stats",
|
||||||
]
|
]
|
||||||
ldap = [
|
|
||||||
"conduwuit-service/ldap"
|
|
||||||
]
|
|
||||||
release_max_log_level = [
|
release_max_log_level = [
|
||||||
"conduwuit-core/release_max_log_level",
|
"conduwuit-core/release_max_log_level",
|
||||||
"conduwuit-service/release_max_log_level",
|
"conduwuit-service/release_max_log_level",
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ pub(crate) async fn register_route(
|
|||||||
let password = if is_guest { None } else { body.password.as_deref() };
|
let password = if is_guest { None } else { body.password.as_deref() };
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
services.users.create(&user_id, password, None).await?;
|
services.users.create(&user_id, password).await?;
|
||||||
|
|
||||||
// Set an initial display name
|
// Set an initial display name
|
||||||
let mut displayname = user_id.localpart().to_owned();
|
let mut displayname = user_id.localpart().to_owned();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use conduwuit::{
|
|||||||
utils::{self, ReadyExt, hash, stream::BroadbandExt},
|
utils::{self, ReadyExt, hash, stream::BroadbandExt},
|
||||||
warn,
|
warn,
|
||||||
};
|
};
|
||||||
use conduwuit_core::{debug_error, debug_warn};
|
use conduwuit_core::debug_error;
|
||||||
use conduwuit_service::Services;
|
use conduwuit_service::Services;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use lettre::Address;
|
use lettre::Address;
|
||||||
@@ -64,17 +64,6 @@ pub(crate) async fn password_login(
|
|||||||
lowercased_user_id: &UserId,
|
lowercased_user_id: &UserId,
|
||||||
password: &str,
|
password: &str,
|
||||||
) -> Result<OwnedUserId> {
|
) -> Result<OwnedUserId> {
|
||||||
// Restrict login to accounts only of type 'password', including untyped
|
|
||||||
// legacy accounts which are equivalent to 'password'.
|
|
||||||
if services
|
|
||||||
.users
|
|
||||||
.origin(user_id)
|
|
||||||
.await
|
|
||||||
.is_ok_and(|origin| origin != "password")
|
|
||||||
{
|
|
||||||
return Err!(Request(Forbidden("Account does not permit password login.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (hash, user_id) = match services.users.password_hash(user_id).await {
|
let (hash, user_id) = match services.users.password_hash(user_id).await {
|
||||||
| Ok(hash) => (hash, user_id),
|
| Ok(hash) => (hash, user_id),
|
||||||
| Err(_) => services
|
| Err(_) => services
|
||||||
@@ -96,71 +85,6 @@ pub(crate) async fn password_login(
|
|||||||
Ok(user_id.to_owned())
|
Ok(user_id.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticates the given user through the configured LDAP server.
|
|
||||||
///
|
|
||||||
/// Creates the user if the user is found in the LDAP and do not already have an
|
|
||||||
/// account.
|
|
||||||
#[tracing::instrument(skip_all, fields(%user_id), name = "ldap", level = "debug")]
|
|
||||||
pub(super) async fn ldap_login(
|
|
||||||
services: &Services,
|
|
||||||
user_id: &UserId,
|
|
||||||
lowercased_user_id: &UserId,
|
|
||||||
password: &str,
|
|
||||||
) -> Result<OwnedUserId> {
|
|
||||||
let (user_dn, is_ldap_admin) = match services.config.ldap.bind_dn.as_ref() {
|
|
||||||
| Some(bind_dn) if bind_dn.contains("{username}") =>
|
|
||||||
(bind_dn.replace("{username}", lowercased_user_id.localpart()), None),
|
|
||||||
| _ => {
|
|
||||||
debug!("Searching user in LDAP");
|
|
||||||
|
|
||||||
let dns = services.users.search_ldap(user_id).await?;
|
|
||||||
if dns.len() >= 2 {
|
|
||||||
return Err!(Ldap("LDAP search returned two or more results"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some((user_dn, is_admin)) = dns.first() else {
|
|
||||||
return password_login(services, user_id, lowercased_user_id, password).await;
|
|
||||||
};
|
|
||||||
|
|
||||||
(user_dn.clone(), *is_admin)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let user_id = services
|
|
||||||
.users
|
|
||||||
.auth_ldap(&user_dn, password)
|
|
||||||
.await
|
|
||||||
.map(|()| lowercased_user_id.to_owned())?;
|
|
||||||
|
|
||||||
// LDAP users are automatically created on first login attempt. This is a very
|
|
||||||
// common feature that can be seen on many services using a LDAP provider for
|
|
||||||
// their users (synapse, Nextcloud, Jellyfin, ...).
|
|
||||||
//
|
|
||||||
// LDAP users are crated with a dummy password but non empty because an empty
|
|
||||||
// password is reserved for deactivated accounts. The conduwuit password field
|
|
||||||
// will never be read to login a LDAP user so it's not an issue.
|
|
||||||
if !services.users.exists(lowercased_user_id).await {
|
|
||||||
services
|
|
||||||
.users
|
|
||||||
.create(lowercased_user_id, Some("*"), Some("ldap"))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only sync admin status if LDAP can actually determine it.
|
|
||||||
// None means LDAP cannot determine admin status (manual config required).
|
|
||||||
if let Some(is_ldap_admin) = is_ldap_admin {
|
|
||||||
let is_conduwuit_admin = services.admin.user_is_admin(lowercased_user_id).await;
|
|
||||||
|
|
||||||
if is_ldap_admin && !is_conduwuit_admin {
|
|
||||||
Box::pin(services.admin.make_user_admin(lowercased_user_id)).await?;
|
|
||||||
} else if !is_ldap_admin && is_conduwuit_admin {
|
|
||||||
Box::pin(services.admin.revoke_admin(lowercased_user_id)).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(user_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn handle_login(
|
pub(crate) async fn handle_login(
|
||||||
services: &Services,
|
services: &Services,
|
||||||
identifier: Option<&UserIdentifier>,
|
identifier: Option<&UserIdentifier>,
|
||||||
@@ -212,18 +136,7 @@ pub(crate) async fn handle_login(
|
|||||||
return Err!(Request(Forbidden("This account is not permitted to log in.")));
|
return Err!(Request(Forbidden("This account is not permitted to log in.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg!(feature = "ldap") && services.config.ldap.enable {
|
password_login(services, &user_id, &lowercased_user_id, password).await
|
||||||
match Box::pin(ldap_login(services, &user_id, &lowercased_user_id, password)).await {
|
|
||||||
| Ok(user_id) => Ok(user_id),
|
|
||||||
| Err(err) if services.config.ldap.ldap_only => Err(err),
|
|
||||||
| Err(err) => {
|
|
||||||
debug_warn!("{err}");
|
|
||||||
password_login(services, &user_id, &lowercased_user_id, password).await
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
password_login(services, &user_id, &lowercased_user_id, password).await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # `POST /_matrix/client/v3/login`
|
/// # `POST /_matrix/client/v3/login`
|
||||||
|
|||||||
@@ -2130,10 +2130,6 @@ pub struct Config {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub allow_web_indexing: bool,
|
pub allow_web_indexing: bool,
|
||||||
|
|
||||||
/// display: nested
|
|
||||||
#[serde(default)]
|
|
||||||
pub ldap: LdapConfig,
|
|
||||||
|
|
||||||
/// Configuration for antispam support
|
/// Configuration for antispam support
|
||||||
/// display: nested
|
/// display: nested
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -2295,126 +2291,6 @@ impl MatrixRtcConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize)]
|
|
||||||
#[config_example_generator(filename = "conduwuit-example.toml", section = "global.ldap")]
|
|
||||||
pub struct LdapConfig {
|
|
||||||
/// Whether to enable LDAP login.
|
|
||||||
///
|
|
||||||
/// example: "true"
|
|
||||||
#[serde(default)]
|
|
||||||
pub enable: bool,
|
|
||||||
|
|
||||||
/// Whether to force LDAP authentication or authorize classical password
|
|
||||||
/// login.
|
|
||||||
///
|
|
||||||
/// example: "true"
|
|
||||||
#[serde(default)]
|
|
||||||
pub ldap_only: bool,
|
|
||||||
|
|
||||||
/// URI of the LDAP server.
|
|
||||||
///
|
|
||||||
/// example: "ldap://ldap.example.com:389"
|
|
||||||
///
|
|
||||||
/// default: ""
|
|
||||||
#[serde(default)]
|
|
||||||
pub uri: Option<Url>,
|
|
||||||
|
|
||||||
/// StartTLS for LDAP connections.
|
|
||||||
///
|
|
||||||
/// default: false
|
|
||||||
#[serde(default)]
|
|
||||||
pub use_starttls: bool,
|
|
||||||
|
|
||||||
/// Skip TLS certificate verification, possibly dangerous.
|
|
||||||
///
|
|
||||||
/// default: false
|
|
||||||
#[serde(default)]
|
|
||||||
pub disable_tls_verification: bool,
|
|
||||||
|
|
||||||
/// Root of the searches.
|
|
||||||
///
|
|
||||||
/// example: "ou=users,dc=example,dc=org"
|
|
||||||
///
|
|
||||||
/// default: ""
|
|
||||||
#[serde(default)]
|
|
||||||
pub base_dn: String,
|
|
||||||
|
|
||||||
/// Bind DN if anonymous search is not enabled.
|
|
||||||
///
|
|
||||||
/// You can use the variable `{username}` that will be replaced by the
|
|
||||||
/// entered username. In such case, the password used to bind will be the
|
|
||||||
/// one provided for the login and not the one given by
|
|
||||||
/// `bind_password_file`. Beware: automatically granting admin rights will
|
|
||||||
/// not work if you use this direct bind instead of a LDAP search.
|
|
||||||
///
|
|
||||||
/// example: "cn=ldap-reader,dc=example,dc=org" or
|
|
||||||
/// "cn={username},ou=users,dc=example,dc=org"
|
|
||||||
///
|
|
||||||
/// default: ""
|
|
||||||
#[serde(default)]
|
|
||||||
pub bind_dn: Option<String>,
|
|
||||||
|
|
||||||
/// Path to a file on the system that contains the password for the
|
|
||||||
/// `bind_dn`.
|
|
||||||
///
|
|
||||||
/// The server must be able to access the file, and it must not be empty.
|
|
||||||
///
|
|
||||||
/// default: ""
|
|
||||||
#[serde(default)]
|
|
||||||
pub bind_password_file: Option<PathBuf>,
|
|
||||||
|
|
||||||
/// Search filter to limit user searches.
|
|
||||||
///
|
|
||||||
/// You can use the variable `{username}` that will be replaced by the
|
|
||||||
/// entered username for more complex filters.
|
|
||||||
///
|
|
||||||
/// example: "(&(objectClass=person)(memberOf=matrix))"
|
|
||||||
///
|
|
||||||
/// default: "(objectClass=*)"
|
|
||||||
#[serde(default = "default_ldap_search_filter")]
|
|
||||||
pub filter: String,
|
|
||||||
|
|
||||||
/// Attribute to use to uniquely identify the user.
|
|
||||||
///
|
|
||||||
/// example: "uid" or "cn"
|
|
||||||
///
|
|
||||||
/// default: "uid"
|
|
||||||
#[serde(default = "default_ldap_uid_attribute")]
|
|
||||||
pub uid_attribute: String,
|
|
||||||
|
|
||||||
/// Attribute containing the display name of the user.
|
|
||||||
///
|
|
||||||
/// example: "givenName" or "sn"
|
|
||||||
///
|
|
||||||
/// default: "givenName"
|
|
||||||
#[serde(default = "default_ldap_name_attribute")]
|
|
||||||
pub name_attribute: String,
|
|
||||||
|
|
||||||
/// Root of the searches for admin users.
|
|
||||||
///
|
|
||||||
/// Defaults to `base_dn` if empty.
|
|
||||||
///
|
|
||||||
/// example: "ou=admins,dc=example,dc=org"
|
|
||||||
///
|
|
||||||
/// default: ""
|
|
||||||
#[serde(default)]
|
|
||||||
pub admin_base_dn: String,
|
|
||||||
|
|
||||||
/// The LDAP search filter to find administrative users for continuwuity.
|
|
||||||
///
|
|
||||||
/// If left blank, administrative state must be configured manually for each
|
|
||||||
/// user.
|
|
||||||
///
|
|
||||||
/// You can use the variable `{username}` that will be replaced by the
|
|
||||||
/// entered username for more complex filters.
|
|
||||||
///
|
|
||||||
/// example: "(objectClass=conduwuitAdmin)" or "(uid={username})"
|
|
||||||
///
|
|
||||||
/// default: ""
|
|
||||||
#[serde(default)]
|
|
||||||
pub admin_filter: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
struct ListeningPort {
|
struct ListeningPort {
|
||||||
@@ -2935,9 +2811,3 @@ pub(super) fn default_blurhash_x_component() -> u32 { 4 }
|
|||||||
pub(super) fn default_blurhash_y_component() -> u32 { 3 }
|
pub(super) fn default_blurhash_y_component() -> u32 { 3 }
|
||||||
|
|
||||||
// end recommended & blurhashing defaults
|
// end recommended & blurhashing defaults
|
||||||
|
|
||||||
fn default_ldap_search_filter() -> String { "(objectClass=*)".to_owned() }
|
|
||||||
|
|
||||||
fn default_ldap_uid_attribute() -> String { String::from("uid") }
|
|
||||||
|
|
||||||
fn default_ldap_name_attribute() -> String { String::from("givenName") }
|
|
||||||
|
|||||||
@@ -110,8 +110,6 @@ pub enum Error {
|
|||||||
InconsistentRoomState(&'static str, ruma::OwnedRoomId),
|
InconsistentRoomState(&'static str, ruma::OwnedRoomId),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
IntoHttp(#[from] ruma::api::error::IntoHttpError),
|
IntoHttp(#[from] ruma::api::error::IntoHttpError),
|
||||||
#[error("{0}")]
|
|
||||||
Ldap(Cow<'static, str>),
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Mxc(#[from] ruma::MxcUriError),
|
Mxc(#[from] ruma::MxcUriError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ standard = [
|
|||||||
"jemalloc",
|
"jemalloc",
|
||||||
"jemalloc_conf",
|
"jemalloc_conf",
|
||||||
"journald",
|
"journald",
|
||||||
"ldap",
|
|
||||||
"media_thumbnail",
|
"media_thumbnail",
|
||||||
"systemd",
|
"systemd",
|
||||||
"url_preview",
|
"url_preview",
|
||||||
@@ -126,9 +125,6 @@ jemalloc_stats = [
|
|||||||
jemalloc_conf = [
|
jemalloc_conf = [
|
||||||
"conduwuit-core/jemalloc_conf",
|
"conduwuit-core/jemalloc_conf",
|
||||||
]
|
]
|
||||||
ldap = [
|
|
||||||
"conduwuit-api/ldap",
|
|
||||||
]
|
|
||||||
media_thumbnail = [
|
media_thumbnail = [
|
||||||
"conduwuit-service/media_thumbnail",
|
"conduwuit-service/media_thumbnail",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -52,9 +52,6 @@ jemalloc_stats = [
|
|||||||
"conduwuit-core/jemalloc_stats",
|
"conduwuit-core/jemalloc_stats",
|
||||||
"conduwuit-database/jemalloc_stats",
|
"conduwuit-database/jemalloc_stats",
|
||||||
]
|
]
|
||||||
ldap = [
|
|
||||||
"dep:ldap3"
|
|
||||||
]
|
|
||||||
media_thumbnail = [
|
media_thumbnail = [
|
||||||
"dep:image",
|
"dep:image",
|
||||||
]
|
]
|
||||||
@@ -99,8 +96,6 @@ image.workspace = true
|
|||||||
image.optional = true
|
image.optional = true
|
||||||
ipaddress.workspace = true
|
ipaddress.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
ldap3.workspace = true
|
|
||||||
ldap3.optional = true
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
loole.workspace = true
|
loole.workspace = true
|
||||||
lru-cache.workspace = true
|
lru-cache.workspace = true
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
|
|||||||
|
|
||||||
// Create a user for the server
|
// Create a user for the server
|
||||||
let server_user = services.globals.server_user.as_ref();
|
let server_user = services.globals.server_user.as_ref();
|
||||||
services.users.create(server_user, None, None).await?;
|
services.users.create(server_user, None).await?;
|
||||||
|
|
||||||
let mut create_content = {
|
let mut create_content = {
|
||||||
use RoomVersionId::*;
|
use RoomVersionId::*;
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ impl Service {
|
|||||||
if !self.services.users.exists(&appservice_user_id).await {
|
if !self.services.users.exists(&appservice_user_id).await {
|
||||||
self.services
|
self.services
|
||||||
.users
|
.users
|
||||||
.create(&appservice_user_id, None, None)
|
.create(&appservice_user_id, None)
|
||||||
.await?;
|
.await?;
|
||||||
} else if self
|
} else if self
|
||||||
.services
|
.services
|
||||||
|
|||||||
@@ -37,11 +37,6 @@ impl crate::Service for Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn worker(self: Arc<Self>) -> Result {
|
async fn worker(self: Arc<Self>) -> Result {
|
||||||
if self.services.config.ldap.enable {
|
|
||||||
warn!("emergency password feature not available with LDAP enabled.");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_emergency_access().await.inspect_err(|e| {
|
self.set_emergency_access().await.inspect_err(|e| {
|
||||||
error!("Could not set the configured emergency password for the server user: {e}");
|
error!("Could not set the configured emergency password for the server user: {e}");
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -58,17 +58,6 @@ impl Service {
|
|||||||
return Err!("Cannot issue a password reset token for the server user");
|
return Err!("Cannot issue a password reset token for the server user");
|
||||||
}
|
}
|
||||||
|
|
||||||
if self
|
|
||||||
.services
|
|
||||||
.users
|
|
||||||
.origin(&user_id)
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|_| "password".to_owned())
|
|
||||||
!= "password"
|
|
||||||
{
|
|
||||||
return Err!("Cannot issue a password reset token for non-internal user {user_id}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.services.users.is_deactivated(&user_id).await? {
|
if self.services.users.is_deactivated(&user_id).await? {
|
||||||
return Err!("Cannot issue a password reset token for deactivated user {user_id}");
|
return Err!("Cannot issue a password reset token for deactivated user {user_id}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ pub async fn update_membership(
|
|||||||
#[allow(clippy::collapsible_if)]
|
#[allow(clippy::collapsible_if)]
|
||||||
if !self.services.globals.user_is_local(user_id) {
|
if !self.services.globals.user_is_local(user_id) {
|
||||||
if !self.services.users.exists(user_id).await {
|
if !self.services.users.exists(user_id).await {
|
||||||
self.services.users.create(user_id, None, None).await?;
|
self.services.users.create(user_id, None).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -385,23 +385,6 @@ impl Service {
|
|||||||
password_verified = hash::verify_password(password, &hash).is_ok();
|
password_verified = hash::verify_password(password, &hash).is_ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If local password verification failed, try LDAP authentication
|
|
||||||
#[cfg(feature = "ldap")]
|
|
||||||
if !password_verified && self.services.config.ldap.enable {
|
|
||||||
// Search for user in LDAP to get their DN
|
|
||||||
if let Ok(dns) = self.services.users.search_ldap(&user_id).await {
|
|
||||||
if let Some((user_dn, _is_admin)) = dns.first() {
|
|
||||||
// Try to authenticate with LDAP
|
|
||||||
password_verified = self
|
|
||||||
.services
|
|
||||||
.users
|
|
||||||
.auth_ldap(user_dn, password)
|
|
||||||
.await
|
|
||||||
.is_ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if password_verified {
|
if password_verified {
|
||||||
identity.try_set_localpart(user_id.localpart().to_owned())?;
|
identity.try_set_localpart(user_id.localpart().to_owned())?;
|
||||||
|
|
||||||
|
|||||||
+4
-220
@@ -1,21 +1,13 @@
|
|||||||
pub(super) mod dehydrated_device;
|
pub(super) mod dehydrated_device;
|
||||||
|
|
||||||
#[cfg(feature = "ldap")]
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::{collections::BTreeMap, mem, net::IpAddr, sync::Arc};
|
use std::{collections::BTreeMap, mem, net::IpAddr, sync::Arc};
|
||||||
|
|
||||||
#[cfg(feature = "ldap")]
|
|
||||||
use conduwuit::result::LogErr;
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Error, Result, Server, debug_warn, err, is_equal_to, trace,
|
Err, Error, Result, Server, debug_warn, err, trace,
|
||||||
utils::{self, ReadyExt, stream::TryIgnore, string::Unquoted},
|
utils::{self, ReadyExt, stream::TryIgnore, string::Unquoted},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "ldap")]
|
|
||||||
use conduwuit_core::{debug, error};
|
|
||||||
use database::{Deserialized, Ignore, Interfix, Json, Map};
|
use database::{Deserialized, Ignore, Interfix, Json, Map};
|
||||||
use futures::{Stream, StreamExt, TryFutureExt};
|
use futures::{Stream, StreamExt, TryFutureExt};
|
||||||
#[cfg(feature = "ldap")]
|
|
||||||
use ldap3::{LdapConnAsync, LdapConnSettings, Scope, SearchEntry};
|
|
||||||
use ruma::{
|
use ruma::{
|
||||||
DeviceId, KeyId, MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OneTimeKeyId,
|
DeviceId, KeyId, MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OneTimeKeyId,
|
||||||
OneTimeKeyName, OwnedDeviceId, OwnedKeyId, OwnedMxcUri, OwnedUserId, RoomId, UInt, UserId,
|
OneTimeKeyName, OwnedDeviceId, OwnedKeyId, OwnedMxcUri, OwnedUserId, RoomId, UInt, UserId,
|
||||||
@@ -79,7 +71,6 @@ struct Data {
|
|||||||
userid_displayname: Arc<Map>,
|
userid_displayname: Arc<Map>,
|
||||||
userid_lastonetimekeyupdate: Arc<Map>,
|
userid_lastonetimekeyupdate: Arc<Map>,
|
||||||
userid_masterkeyid: Arc<Map>,
|
userid_masterkeyid: Arc<Map>,
|
||||||
userid_origin: Arc<Map>,
|
|
||||||
userid_password: Arc<Map>,
|
userid_password: Arc<Map>,
|
||||||
userid_suspension: Arc<Map>,
|
userid_suspension: Arc<Map>,
|
||||||
userid_lock: Arc<Map>,
|
userid_lock: Arc<Map>,
|
||||||
@@ -120,7 +111,6 @@ impl crate::Service for Service {
|
|||||||
userid_displayname: args.db["userid_displayname"].clone(),
|
userid_displayname: args.db["userid_displayname"].clone(),
|
||||||
userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(),
|
userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(),
|
||||||
userid_masterkeyid: args.db["userid_masterkeyid"].clone(),
|
userid_masterkeyid: args.db["userid_masterkeyid"].clone(),
|
||||||
userid_origin: args.db["userid_origin"].clone(),
|
|
||||||
userid_password: args.db["userid_password"].clone(),
|
userid_password: args.db["userid_password"].clone(),
|
||||||
userid_suspension: args.db["userid_suspension"].clone(),
|
userid_suspension: args.db["userid_suspension"].clone(),
|
||||||
userid_lock: args.db["userid_lock"].clone(),
|
userid_lock: args.db["userid_lock"].clone(),
|
||||||
@@ -178,26 +168,12 @@ impl Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new user account on this homeserver.
|
/// Create a new user account on this homeserver.
|
||||||
///
|
|
||||||
/// User origin is by default "password" (meaning that it will login using
|
|
||||||
/// its user_id/password). Users with other origins (currently only "ldap"
|
|
||||||
/// is available) have special login processes.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn create(
|
pub async fn create(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
|
||||||
&self,
|
if !self.services.globals.user_is_local(user_id) && password.is_some() {
|
||||||
user_id: &UserId,
|
return Err!("Cannot create a nonlocal user with a set password");
|
||||||
password: Option<&str>,
|
|
||||||
origin: Option<&str>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if !self.services.globals.user_is_local(user_id)
|
|
||||||
&& (password.is_some() || origin.is_some())
|
|
||||||
{
|
|
||||||
return Err!("Cannot create a nonlocal user with a set password or origin");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.db
|
|
||||||
.userid_origin
|
|
||||||
.insert(user_id, origin.unwrap_or("password"));
|
|
||||||
self.set_password(user_id, password).await?;
|
self.set_password(user_id, password).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -360,11 +336,6 @@ impl Service {
|
|||||||
.ready_filter_map(|(u, p): (OwnedUserId, &[u8])| (!p.is_empty()).then_some(u))
|
.ready_filter_map(|(u, p): (OwnedUserId, &[u8])| (!p.is_empty()).then_some(u))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the origin of the user (password/LDAP/...).
|
|
||||||
pub async fn origin(&self, user_id: &UserId) -> Result<String> {
|
|
||||||
self.db.userid_origin.get(user_id).await.deserialized()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the password hash for the given user.
|
/// Returns the password hash for the given user.
|
||||||
pub async fn password_hash(&self, user_id: &UserId) -> Result<String> {
|
pub async fn password_hash(&self, user_id: &UserId) -> Result<String> {
|
||||||
self.db.userid_password.get(user_id).await.deserialized()
|
self.db.userid_password.get(user_id).await.deserialized()
|
||||||
@@ -372,22 +343,6 @@ impl Service {
|
|||||||
|
|
||||||
/// Hash and set the user's password to the Argon2 hash
|
/// Hash and set the user's password to the Argon2 hash
|
||||||
pub async fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
|
pub async fn set_password(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
|
||||||
// Cannot change the password of a LDAP user. There are two special cases :
|
|
||||||
// - a `None` password can be used to deactivate a LDAP user
|
|
||||||
// - a "*" password is used as the default password of an active LDAP user
|
|
||||||
if cfg!(feature = "ldap")
|
|
||||||
&& password.is_some_and(|pwd| pwd != "*")
|
|
||||||
&& self
|
|
||||||
.db
|
|
||||||
.userid_origin
|
|
||||||
.get(user_id)
|
|
||||||
.await
|
|
||||||
.deserialized::<String>()
|
|
||||||
.is_ok_and(is_equal_to!("ldap"))
|
|
||||||
{
|
|
||||||
return Err!(Request(InvalidParam("Cannot change password of a LDAP user")));
|
|
||||||
}
|
|
||||||
|
|
||||||
password
|
password
|
||||||
.map(utils::hash::password)
|
.map(utils::hash::password)
|
||||||
.transpose()
|
.transpose()
|
||||||
@@ -1292,177 +1247,6 @@ impl Service {
|
|||||||
.ready_for_each(|(key, _)| self.set_profile_key(user_id, &key, None))
|
.ready_for_each(|(key, _)| self.set_profile_key(user_id, &key, None))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ldap")]
|
|
||||||
async fn create_ldap_connection(
|
|
||||||
config: &conduwuit_core::config::LdapConfig,
|
|
||||||
uri: &str,
|
|
||||||
) -> Result<(LdapConnAsync, ldap3::Ldap), ldap3::LdapError> {
|
|
||||||
let mut settings = LdapConnSettings::new();
|
|
||||||
|
|
||||||
if config.use_starttls {
|
|
||||||
settings = settings.set_starttls(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.disable_tls_verification {
|
|
||||||
settings = settings.set_no_tls_verify(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
LdapConnAsync::with_settings(settings, uri).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "ldap"))]
|
|
||||||
pub async fn search_ldap(&self, _user_id: &UserId) -> Result<Vec<(String, Option<bool>)>> {
|
|
||||||
Err!(FeatureDisabled("ldap"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ldap")]
|
|
||||||
pub async fn search_ldap(&self, user_id: &UserId) -> Result<Vec<(String, Option<bool>)>> {
|
|
||||||
let localpart = user_id.localpart().to_owned();
|
|
||||||
let lowercased_localpart = localpart.to_lowercase();
|
|
||||||
|
|
||||||
let config = &self.services.server.config.ldap;
|
|
||||||
let uri = config
|
|
||||||
.uri
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| err!(Ldap(error!("LDAP URI is not configured."))))?;
|
|
||||||
|
|
||||||
debug!(?uri, "LDAP creating connection...");
|
|
||||||
let (conn, mut ldap) = Self::create_ldap_connection(config, uri.as_str())
|
|
||||||
.await
|
|
||||||
.map_err(|e| err!(Ldap(error!(%user_id, "LDAP connection setup error: {e}"))))?;
|
|
||||||
|
|
||||||
let driver = self.services.server.runtime().spawn(async move {
|
|
||||||
match conn.drive().await {
|
|
||||||
| Err(e) => error!("LDAP connection error: {e}"),
|
|
||||||
| Ok(()) => debug!("LDAP connection completed."),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
match (&config.bind_dn, &config.bind_password_file) {
|
|
||||||
| (Some(bind_dn), Some(bind_password_file)) => {
|
|
||||||
let bind_pw = String::from_utf8(std::fs::read(bind_password_file)?)?;
|
|
||||||
ldap.simple_bind(bind_dn, bind_pw.trim())
|
|
||||||
.await
|
|
||||||
.and_then(ldap3::LdapResult::success)
|
|
||||||
.map_err(|e| err!(Ldap(error!("LDAP bind error: {e}"))))?;
|
|
||||||
},
|
|
||||||
| (..) => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
let attr = [&config.uid_attribute, &config.name_attribute];
|
|
||||||
|
|
||||||
let user_filter = &config.filter.replace("{username}", &lowercased_localpart);
|
|
||||||
|
|
||||||
let (entries, _result) = ldap
|
|
||||||
.search(&config.base_dn, Scope::Subtree, user_filter, &attr)
|
|
||||||
.await
|
|
||||||
.and_then(ldap3::SearchResult::success)
|
|
||||||
.inspect(|(entries, result)| trace!(?entries, ?result, "LDAP Search"))
|
|
||||||
.map_err(|e| err!(Ldap(error!(?attr, ?user_filter, "LDAP search error: {e}"))))?;
|
|
||||||
|
|
||||||
let mut dns: HashMap<String, Option<bool>> = entries
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|entry| {
|
|
||||||
let search_entry = SearchEntry::construct(entry);
|
|
||||||
debug!(?search_entry, "LDAP search entry");
|
|
||||||
search_entry
|
|
||||||
.attrs
|
|
||||||
.get(&config.uid_attribute)
|
|
||||||
.into_iter()
|
|
||||||
.chain(search_entry.attrs.get(&config.name_attribute))
|
|
||||||
.any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart))
|
|
||||||
.then_some((search_entry.dn, None))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if !config.admin_filter.is_empty() {
|
|
||||||
// Update all existing entries to Some(false) since we can now determine admin
|
|
||||||
// status
|
|
||||||
for admin_status in dns.values_mut() {
|
|
||||||
*admin_status = Some(false);
|
|
||||||
}
|
|
||||||
let admin_base_dn = if config.admin_base_dn.is_empty() {
|
|
||||||
&config.base_dn
|
|
||||||
} else {
|
|
||||||
&config.admin_base_dn
|
|
||||||
};
|
|
||||||
|
|
||||||
let admin_filter = &config
|
|
||||||
.admin_filter
|
|
||||||
.replace("{username}", &lowercased_localpart);
|
|
||||||
|
|
||||||
let (admin_entries, _result) = ldap
|
|
||||||
.search(admin_base_dn, Scope::Subtree, admin_filter, &attr)
|
|
||||||
.await
|
|
||||||
.and_then(ldap3::SearchResult::success)
|
|
||||||
.inspect(|(entries, result)| trace!(?entries, ?result, "LDAP Admin Search"))
|
|
||||||
.map_err(|e| {
|
|
||||||
err!(Ldap(error!(?attr, ?admin_filter, "Ldap admin search error: {e}")))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
dns.extend(admin_entries.into_iter().filter_map(|entry| {
|
|
||||||
let search_entry = SearchEntry::construct(entry);
|
|
||||||
debug!(?search_entry, "LDAP search entry");
|
|
||||||
search_entry
|
|
||||||
.attrs
|
|
||||||
.get(&config.uid_attribute)
|
|
||||||
.into_iter()
|
|
||||||
.chain(search_entry.attrs.get(&config.name_attribute))
|
|
||||||
.any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart))
|
|
||||||
.then_some((search_entry.dn, Some(true)))
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
ldap.unbind()
|
|
||||||
.await
|
|
||||||
.map_err(|e| err!(Ldap(error!("LDAP unbind error: {e}"))))?;
|
|
||||||
|
|
||||||
driver.await.log_err().ok();
|
|
||||||
|
|
||||||
Ok(dns.drain().collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "ldap"))]
|
|
||||||
pub async fn auth_ldap(&self, _user_dn: &str, _password: &str) -> Result {
|
|
||||||
Err!(FeatureDisabled("ldap"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ldap")]
|
|
||||||
pub async fn auth_ldap(&self, user_dn: &str, password: &str) -> Result {
|
|
||||||
let config = &self.services.server.config.ldap;
|
|
||||||
let uri = config
|
|
||||||
.uri
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| err!(Ldap(error!("LDAP URI is not configured."))))?;
|
|
||||||
|
|
||||||
debug!(?uri, "LDAP creating connection...");
|
|
||||||
let (conn, mut ldap) = Self::create_ldap_connection(config, uri.as_str())
|
|
||||||
.await
|
|
||||||
.map_err(|e| err!(Ldap(error!(%user_dn, "LDAP connection setup error: {e}"))))?;
|
|
||||||
|
|
||||||
let driver = self.services.server.runtime().spawn(async move {
|
|
||||||
match conn.drive().await {
|
|
||||||
| Err(e) => error!("LDAP connection error: {e}"),
|
|
||||||
| Ok(()) => debug!("LDAP connection completed."),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ldap.simple_bind(user_dn, password)
|
|
||||||
.await
|
|
||||||
.and_then(ldap3::LdapResult::success)
|
|
||||||
.map_err(|e| {
|
|
||||||
err!(Request(Forbidden(debug_error!("LDAP authentication error: {e}"))))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
ldap.unbind()
|
|
||||||
.await
|
|
||||||
.map_err(|e| err!(Ldap(error!("LDAP unbind error: {e}"))))?;
|
|
||||||
|
|
||||||
driver.await.log_err().ok();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_master_key(
|
pub fn parse_master_key(
|
||||||
|
|||||||
Reference in New Issue
Block a user