mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2025-09-11 01:32:49 +02:00
Merge remote-tracking branch 'origin/main' into illegal-car-mods
This commit is contained in:
commit
098eb8abca
102 changed files with 2666 additions and 1117 deletions
|
@ -53,6 +53,9 @@ jemalloc_stats = [
|
|||
"conduwuit-core/jemalloc_stats",
|
||||
"conduwuit-database/jemalloc_stats",
|
||||
]
|
||||
ldap = [
|
||||
"dep:ldap3"
|
||||
]
|
||||
media_thumbnail = [
|
||||
"dep:image",
|
||||
]
|
||||
|
@ -89,6 +92,8 @@ image.workspace = true
|
|||
image.optional = true
|
||||
ipaddress.workspace = true
|
||||
itertools.workspace = true
|
||||
ldap3.workspace = true
|
||||
ldap3.optional = true
|
||||
log.workspace = true
|
||||
loole.workspace = true
|
||||
lru-cache.workspace = true
|
||||
|
@ -112,6 +117,7 @@ webpage.optional = true
|
|||
blurhash.workspace = true
|
||||
blurhash.optional = true
|
||||
recaptcha-verify = { version = "0.1.5", default-features = false }
|
||||
ctor.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -38,7 +38,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
|
|||
|
||||
// Create a user for the server
|
||||
let server_user = services.globals.server_user.as_ref();
|
||||
services.users.create(server_user, None)?;
|
||||
services.users.create(server_user, None, None).await?;
|
||||
|
||||
let create_content = {
|
||||
use RoomVersionId::*;
|
||||
|
|
|
@ -109,7 +109,10 @@ impl Service {
|
|||
)?;
|
||||
|
||||
if !self.services.users.exists(&appservice_user_id).await {
|
||||
self.services.users.create(&appservice_user_id, None)?;
|
||||
self.services
|
||||
.users
|
||||
.create(&appservice_user_id, None, None)
|
||||
.await?;
|
||||
} else if self
|
||||
.services
|
||||
.users
|
||||
|
@ -120,7 +123,8 @@ impl Service {
|
|||
// Reactivate the appservice user if it was accidentally deactivated
|
||||
self.services
|
||||
.users
|
||||
.set_password(&appservice_user_id, None)?;
|
||||
.set_password(&appservice_user_id, None)
|
||||
.await?;
|
||||
}
|
||||
|
||||
self.registration_info
|
||||
|
|
|
@ -41,6 +41,11 @@ impl crate::Service for Service {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
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| {
|
||||
error!("Could not set the configured emergency password for the server user: {e}");
|
||||
})
|
||||
|
@ -57,7 +62,8 @@ impl Service {
|
|||
|
||||
self.services
|
||||
.users
|
||||
.set_password(server_user, self.services.config.emergency_password.as_deref())?;
|
||||
.set_password(server_user, self.services.config.emergency_password.as_deref())
|
||||
.await?;
|
||||
|
||||
let (ruleset, pwd_set) = match self.services.config.emergency_password {
|
||||
| Some(_) => (Ruleset::server_default(server_user), true),
|
||||
|
|
|
@ -215,8 +215,8 @@ async fn db_lt_12(services: &Services) -> Result<()> {
|
|||
for username in &services
|
||||
.users
|
||||
.list_local_users()
|
||||
.map(UserId::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<OwnedUserId>>()
|
||||
.await
|
||||
{
|
||||
let user = match UserId::parse_with_server_name(username.as_str(), &services.server.name)
|
||||
|
@ -295,8 +295,8 @@ async fn db_lt_13(services: &Services) -> Result<()> {
|
|||
for username in &services
|
||||
.users
|
||||
.list_local_users()
|
||||
.map(UserId::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<OwnedUserId>>()
|
||||
.await
|
||||
{
|
||||
let user = match UserId::parse_with_server_name(username.as_str(), &services.server.name)
|
||||
|
|
|
@ -33,6 +33,7 @@ pub mod users;
|
|||
extern crate conduwuit_core as conduwuit;
|
||||
extern crate conduwuit_database as database;
|
||||
|
||||
use ctor::{ctor, dtor};
|
||||
pub(crate) use service::{Args, Dep, Service};
|
||||
|
||||
pub use crate::services::Services;
|
||||
|
|
|
@ -183,8 +183,8 @@ impl Service {
|
|||
.services
|
||||
.users
|
||||
.list_local_users()
|
||||
.map(UserId::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<OwnedUserId>>()
|
||||
.await
|
||||
{
|
||||
let presence = self.db.get_presence(user_id).await;
|
||||
|
|
|
@ -178,7 +178,7 @@ impl Service {
|
|||
pub fn get_pushkeys<'a>(
|
||||
&'a self,
|
||||
sender: &'a UserId,
|
||||
) -> impl Stream<Item = &str> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a str> + Send + 'a {
|
||||
let prefix = (sender, Interfix);
|
||||
self.db
|
||||
.senderkey_pusher
|
||||
|
|
|
@ -178,7 +178,7 @@ impl Service {
|
|||
pub fn local_aliases_for_room<'a>(
|
||||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
) -> impl Stream<Item = &RoomAliasId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a RoomAliasId> + Send + 'a {
|
||||
let prefix = (room_id, Interfix);
|
||||
self.db
|
||||
.aliasid_alias
|
||||
|
@ -188,7 +188,9 @@ impl Service {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self), level = "debug")]
|
||||
pub fn all_local_aliases<'a>(&'a self) -> impl Stream<Item = (&RoomId, &str)> + Send + 'a {
|
||||
pub fn all_local_aliases<'a>(
|
||||
&'a self,
|
||||
) -> impl Stream<Item = (&'a RoomId, &'a str)> + Send + 'a {
|
||||
self.db
|
||||
.alias_roomid
|
||||
.stream()
|
||||
|
|
|
@ -60,7 +60,7 @@ impl Data {
|
|||
target: ShortEventId,
|
||||
from: PduCount,
|
||||
dir: Direction,
|
||||
) -> impl Stream<Item = (PduCount, impl Event)> + Send + '_ {
|
||||
) -> impl Stream<Item = (PduCount, impl Event)> + Send + 'a {
|
||||
// Query from exact position then filter excludes it (saturating_inc could skip
|
||||
// events at min/max boundaries)
|
||||
let from_unsigned = from.into_unsigned();
|
||||
|
|
|
@ -65,7 +65,7 @@ impl Data {
|
|||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
since: u64,
|
||||
) -> impl Stream<Item = ReceiptItem<'_>> + Send + 'a {
|
||||
) -> impl Stream<Item = ReceiptItem<'a>> + Send + 'a {
|
||||
type Key<'a> = (&'a RoomId, u64, &'a UserId);
|
||||
type KeyVal<'a> = (Key<'a>, CanonicalJsonObject);
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ impl Service {
|
|||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
since: u64,
|
||||
) -> impl Stream<Item = ReceiptItem<'_>> + Send + 'a {
|
||||
) -> impl Stream<Item = ReceiptItem<'a>> + Send + 'a {
|
||||
self.db.readreceipts_since(room_id, since)
|
||||
}
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ pub fn deindex_pdu(&self, shortroomid: ShortRoomId, pdu_id: &RawPduId, message_b
|
|||
pub async fn search_pdus<'a>(
|
||||
&'a self,
|
||||
query: &'a RoomQuery<'a>,
|
||||
) -> Result<(usize, impl Stream<Item = impl Event + use<>> + Send + '_)> {
|
||||
) -> Result<(usize, impl Stream<Item = impl Event + use<>> + Send + 'a)> {
|
||||
let pdu_ids: Vec<_> = self.search_pdu_ids(query).await?.collect().await;
|
||||
|
||||
let filter = &query.criteria.filter;
|
||||
|
@ -137,10 +137,10 @@ pub async fn search_pdus<'a>(
|
|||
// result is modeled as a stream such that callers don't have to be refactored
|
||||
// though an additional async/wrap still exists for now
|
||||
#[implement(Service)]
|
||||
pub async fn search_pdu_ids(
|
||||
&self,
|
||||
query: &RoomQuery<'_>,
|
||||
) -> Result<impl Stream<Item = RawPduId> + Send + '_ + use<'_>> {
|
||||
pub async fn search_pdu_ids<'a>(
|
||||
&'a self,
|
||||
query: &'a RoomQuery<'_>,
|
||||
) -> Result<impl Stream<Item = RawPduId> + Send + 'a + use<'a>> {
|
||||
let shortroomid = self.services.short.get_shortroomid(query.room_id).await?;
|
||||
|
||||
let pdu_ids = self.search_pdu_ids_query_room(query, shortroomid).await;
|
||||
|
@ -173,7 +173,7 @@ fn search_pdu_ids_query_words<'a>(
|
|||
&'a self,
|
||||
shortroomid: ShortRoomId,
|
||||
word: &'a str,
|
||||
) -> impl Stream<Item = RawPduId> + Send + '_ {
|
||||
) -> impl Stream<Item = RawPduId> + Send + 'a {
|
||||
self.search_pdu_ids_query_word(shortroomid, word)
|
||||
.map(move |key| -> RawPduId {
|
||||
let key = &key[prefix_len(word)..];
|
||||
|
@ -183,11 +183,11 @@ fn search_pdu_ids_query_words<'a>(
|
|||
|
||||
/// Iterate over raw database results for a word
|
||||
#[implement(Service)]
|
||||
fn search_pdu_ids_query_word(
|
||||
&self,
|
||||
fn search_pdu_ids_query_word<'a>(
|
||||
&'a self,
|
||||
shortroomid: ShortRoomId,
|
||||
word: &str,
|
||||
) -> impl Stream<Item = Val<'_>> + Send + '_ + use<'_> {
|
||||
word: &'a str,
|
||||
) -> impl Stream<Item = Val<'a>> + Send + 'a + use<'a> {
|
||||
// rustc says const'ing this not yet stable
|
||||
let end_id: RawPduId = PduId {
|
||||
shortroomid,
|
||||
|
|
|
@ -62,7 +62,7 @@ pub async fn get_or_create_shorteventid(&self, event_id: &EventId) -> ShortEvent
|
|||
pub fn multi_get_or_create_shorteventid<'a, I>(
|
||||
&'a self,
|
||||
event_ids: I,
|
||||
) -> impl Stream<Item = ShortEventId> + Send + '_
|
||||
) -> impl Stream<Item = ShortEventId> + Send + 'a
|
||||
where
|
||||
I: Iterator<Item = &'a EventId> + Clone + Debug + Send + 'a,
|
||||
{
|
||||
|
|
|
@ -388,8 +388,7 @@ impl Service {
|
|||
pub fn get_forward_extremities<'a>(
|
||||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
_state_lock: &'a RoomMutexGuard,
|
||||
) -> impl Stream<Item = &EventId> + Send + '_ {
|
||||
) -> impl Stream<Item = &'a EventId> + Send + 'a {
|
||||
let prefix = (room_id, Interfix);
|
||||
|
||||
self.db
|
||||
|
|
|
@ -144,7 +144,7 @@ pub fn clear_appservice_in_room_cache(&self) { self.appservice_in_room_cache.wri
|
|||
pub fn room_servers<'a>(
|
||||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
) -> impl Stream<Item = &ServerName> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a ServerName> + Send + 'a {
|
||||
let prefix = (room_id, Interfix);
|
||||
self.db
|
||||
.roomserverids
|
||||
|
@ -167,7 +167,7 @@ pub async fn server_in_room<'a>(&'a self, server: &'a ServerName, room_id: &'a R
|
|||
pub fn server_rooms<'a>(
|
||||
&'a self,
|
||||
server: &'a ServerName,
|
||||
) -> impl Stream<Item = &RoomId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a RoomId> + Send + 'a {
|
||||
let prefix = (server, Interfix);
|
||||
self.db
|
||||
.serverroomids
|
||||
|
@ -202,7 +202,7 @@ pub fn get_shared_rooms<'a>(
|
|||
&'a self,
|
||||
user_a: &'a UserId,
|
||||
user_b: &'a UserId,
|
||||
) -> impl Stream<Item = &RoomId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a RoomId> + Send + 'a {
|
||||
use conduwuit::utils::set;
|
||||
|
||||
let a = self.rooms_joined(user_a);
|
||||
|
@ -216,7 +216,7 @@ pub fn get_shared_rooms<'a>(
|
|||
pub fn room_members<'a>(
|
||||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
) -> impl Stream<Item = &UserId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a UserId> + Send + 'a {
|
||||
let prefix = (room_id, Interfix);
|
||||
self.db
|
||||
.roomuserid_joined
|
||||
|
@ -239,7 +239,7 @@ pub async fn room_joined_count(&self, room_id: &RoomId) -> Result<u64> {
|
|||
pub fn local_users_in_room<'a>(
|
||||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
) -> impl Stream<Item = &UserId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a UserId> + Send + 'a {
|
||||
self.room_members(room_id)
|
||||
.ready_filter(|user| self.services.globals.user_is_local(user))
|
||||
}
|
||||
|
@ -251,7 +251,7 @@ pub fn local_users_in_room<'a>(
|
|||
pub fn active_local_users_in_room<'a>(
|
||||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
) -> impl Stream<Item = &UserId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a UserId> + Send + 'a {
|
||||
self.local_users_in_room(room_id)
|
||||
.filter(|user| self.services.users.is_active(user))
|
||||
}
|
||||
|
@ -273,7 +273,7 @@ pub async fn room_invited_count(&self, room_id: &RoomId) -> Result<u64> {
|
|||
pub fn room_useroncejoined<'a>(
|
||||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
) -> impl Stream<Item = &UserId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a UserId> + Send + 'a {
|
||||
let prefix = (room_id, Interfix);
|
||||
self.db
|
||||
.roomuseroncejoinedids
|
||||
|
@ -288,7 +288,7 @@ pub fn room_useroncejoined<'a>(
|
|||
pub fn room_members_invited<'a>(
|
||||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
) -> impl Stream<Item = &UserId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a UserId> + Send + 'a {
|
||||
let prefix = (room_id, Interfix);
|
||||
self.db
|
||||
.roomuserid_invitecount
|
||||
|
@ -303,7 +303,7 @@ pub fn room_members_invited<'a>(
|
|||
pub fn room_members_knocked<'a>(
|
||||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
) -> impl Stream<Item = &UserId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a UserId> + Send + 'a {
|
||||
let prefix = (room_id, Interfix);
|
||||
self.db
|
||||
.roomuserid_knockedcount
|
||||
|
@ -347,7 +347,7 @@ pub async fn get_left_count(&self, room_id: &RoomId, user_id: &UserId) -> Result
|
|||
pub fn rooms_joined<'a>(
|
||||
&'a self,
|
||||
user_id: &'a UserId,
|
||||
) -> impl Stream<Item = &RoomId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a RoomId> + Send + 'a {
|
||||
self.db
|
||||
.userroomid_joined
|
||||
.keys_raw_prefix(user_id)
|
||||
|
|
|
@ -49,7 +49,7 @@ pub async fn update_membership(
|
|||
#[allow(clippy::collapsible_if)]
|
||||
if !self.services.globals.user_is_local(user_id) {
|
||||
if !self.services.users.exists(user_id).await {
|
||||
self.services.users.create(user_id, None)?;
|
||||
self.services.users.create(user_id, None, None).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ pub async fn servers_route_via(&self, room_id: &RoomId) -> Result<Vec<OwnedServe
|
|||
pub fn servers_invite_via<'a>(
|
||||
&'a self,
|
||||
room_id: &'a RoomId,
|
||||
) -> impl Stream<Item = &ServerName> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a ServerName> + Send + 'a {
|
||||
type KeyVal<'a> = (Ignore, Vec<&'a ServerName>);
|
||||
|
||||
self.db
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
#[cfg(feature = "ldap")]
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::BTreeMap, mem, sync::Arc};
|
||||
|
||||
#[cfg(feature = "ldap")]
|
||||
use conduwuit::result::LogErr;
|
||||
use conduwuit::{
|
||||
Err, Error, Result, Server, at, debug_warn, err, trace,
|
||||
Err, Error, Result, Server, at, debug_warn, err, is_equal_to, trace,
|
||||
utils::{self, ReadyExt, stream::TryIgnore, string::Unquoted},
|
||||
};
|
||||
#[cfg(feature = "ldap")]
|
||||
use conduwuit_core::{debug, error};
|
||||
use database::{Deserialized, Ignore, Interfix, Json, Map};
|
||||
use futures::{Stream, StreamExt, TryFutureExt};
|
||||
#[cfg(feature = "ldap")]
|
||||
use ldap3::{LdapConnAsync, Scope, SearchEntry};
|
||||
use ruma::{
|
||||
DeviceId, KeyId, MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OneTimeKeyId,
|
||||
OneTimeKeyName, OwnedDeviceId, OwnedKeyId, OwnedMxcUri, OwnedUserId, RoomId, UInt, UserId,
|
||||
|
@ -63,6 +71,7 @@ struct Data {
|
|||
userid_displayname: Arc<Map>,
|
||||
userid_lastonetimekeyupdate: Arc<Map>,
|
||||
userid_masterkeyid: Arc<Map>,
|
||||
userid_origin: Arc<Map>,
|
||||
userid_password: Arc<Map>,
|
||||
userid_suspension: Arc<Map>,
|
||||
userid_selfsigningkeyid: Arc<Map>,
|
||||
|
@ -100,6 +109,7 @@ impl crate::Service for Service {
|
|||
userid_displayname: args.db["userid_displayname"].clone(),
|
||||
userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(),
|
||||
userid_masterkeyid: args.db["userid_masterkeyid"].clone(),
|
||||
userid_origin: args.db["userid_origin"].clone(),
|
||||
userid_password: args.db["userid_password"].clone(),
|
||||
userid_suspension: args.db["userid_suspension"].clone(),
|
||||
userid_selfsigningkeyid: args.db["userid_selfsigningkeyid"].clone(),
|
||||
|
@ -136,9 +146,21 @@ impl Service {
|
|||
}
|
||||
|
||||
/// 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]
|
||||
pub fn create(&self, user_id: &UserId, password: Option<&str>) -> Result<()> {
|
||||
self.set_password(user_id, password)
|
||||
pub async fn create(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
password: Option<&str>,
|
||||
origin: Option<&str>,
|
||||
) -> Result<()> {
|
||||
self.db
|
||||
.userid_origin
|
||||
.insert(user_id, origin.unwrap_or("password"));
|
||||
self.set_password(user_id, password).await
|
||||
}
|
||||
|
||||
/// Deactivate account
|
||||
|
@ -152,7 +174,7 @@ impl Service {
|
|||
// result in an empty string, so the user will not be able to log in again.
|
||||
// Systems like changing the password without logging in should check if the
|
||||
// account is deactivated.
|
||||
self.set_password(user_id, None)?;
|
||||
self.set_password(user_id, None).await?;
|
||||
|
||||
// TODO: Unhook 3PID
|
||||
Ok(())
|
||||
|
@ -253,13 +275,34 @@ impl Service {
|
|||
.ready_filter_map(|(u, p): (&UserId, &[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.
|
||||
pub async fn password_hash(&self, user_id: &UserId) -> Result<String> {
|
||||
self.db.userid_password.get(user_id).await.deserialized()
|
||||
}
|
||||
|
||||
/// Hash and set the user's password to the Argon2 hash
|
||||
pub 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
|
||||
.map(utils::hash::password)
|
||||
.transpose()
|
||||
|
@ -379,7 +422,7 @@ impl Service {
|
|||
pub fn all_device_ids<'a>(
|
||||
&'a self,
|
||||
user_id: &'a UserId,
|
||||
) -> impl Stream<Item = &DeviceId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a DeviceId> + Send + 'a {
|
||||
let prefix = (user_id, Interfix);
|
||||
self.db
|
||||
.userdeviceid_metadata
|
||||
|
@ -727,7 +770,7 @@ impl Service {
|
|||
user_id: &'a UserId,
|
||||
from: u64,
|
||||
to: Option<u64>,
|
||||
) -> impl Stream<Item = &UserId> + Send + 'a {
|
||||
) -> impl Stream<Item = &'a UserId> + Send + 'a {
|
||||
self.keys_changed_user_or_room(user_id.as_str(), from, to)
|
||||
.map(|(user_id, ..)| user_id)
|
||||
}
|
||||
|
@ -738,7 +781,7 @@ impl Service {
|
|||
room_id: &'a RoomId,
|
||||
from: u64,
|
||||
to: Option<u64>,
|
||||
) -> impl Stream<Item = (&UserId, u64)> + Send + 'a {
|
||||
) -> impl Stream<Item = (&'a UserId, u64)> + Send + 'a {
|
||||
self.keys_changed_user_or_room(room_id.as_str(), from, to)
|
||||
}
|
||||
|
||||
|
@ -747,7 +790,7 @@ impl Service {
|
|||
user_or_room_id: &'a str,
|
||||
from: u64,
|
||||
to: Option<u64>,
|
||||
) -> impl Stream<Item = (&UserId, u64)> + Send + 'a {
|
||||
) -> impl Stream<Item = (&'a UserId, u64)> + Send + 'a {
|
||||
type KeyVal<'a> = ((&'a str, u64), &'a UserId);
|
||||
|
||||
let to = to.unwrap_or(u64::MAX);
|
||||
|
@ -1132,6 +1175,154 @@ impl Service {
|
|||
self.db.useridprofilekey_value.del(key);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ldap"))]
|
||||
pub async fn search_ldap(&self, _user_id: &UserId) -> Result<Vec<(String, bool)>> {
|
||||
Err!(FeatureDisabled("ldap"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "ldap")]
|
||||
pub async fn search_ldap(&self, user_id: &UserId) -> Result<Vec<(String, 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) = LdapConnAsync::new(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, 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, false))
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !config.admin_filter.is_empty() {
|
||||
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, 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) = LdapConnAsync::new(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(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue