mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 082c44f355 | |||
| 117c581948 | |||
| cb846a3ad1 | |||
| 81b984b2cc | |||
| e2961390ee | |||
| cb75e836e0 | |||
| cb7a988b1b | |||
| aa5400bcef | |||
| ff4dddd673 | |||
| c22b17fb29 | |||
| 3da7fa24db | |||
| d15ac1d3c1 | |||
| a9ebdf58e2 | |||
| f1ab27d344 | |||
| 8bc6e6ccca | |||
| 60a3abe752 | |||
| e3b874d336 | |||
| f3f82831b4 | |||
| 26aac1408e | |||
| be8f62396a | |||
| 40996a6602 |
@@ -0,0 +1 @@
|
|||||||
|
LDAP-enabled servers will no longer have all admins demoted when LDAP-controlled admins are not configured. Contributed by @Jade
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
Added unstable support for [MSC4406: `M_SENDER_IGNORED`](https://github.com/matrix-org/matrix-spec-proposals/pull/4406).
|
||||||
|
Contributed by @nex
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Improved the handling of restricted join rules and improved the performance of local-first joins. Contributed by @nex.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
You can now set a custom User Agent for URL previews; the default one has been modified to be less likely to be rejected. Contributed by @trashpanda
|
||||||
@@ -1474,6 +1474,10 @@
|
|||||||
#
|
#
|
||||||
#url_preview_check_root_domain = false
|
#url_preview_check_root_domain = false
|
||||||
|
|
||||||
|
# User agent that is used specifically when fetching url previews.
|
||||||
|
#
|
||||||
|
#url_preview_user_agent = "continuwuity/<version> (bot; +https://continuwuity.org)"
|
||||||
|
|
||||||
# List of forbidden room aliases and room IDs as strings of regex
|
# List of forbidden room aliases and room IDs as strings of regex
|
||||||
# patterns.
|
# patterns.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -140,7 +140,6 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
|||||||
self.services.globals.server_name().to_owned(),
|
self.services.globals.server_name().to_owned(),
|
||||||
room_server_name.to_owned(),
|
room_server_name.to_owned(),
|
||||||
],
|
],
|
||||||
None,
|
|
||||||
&None,
|
&None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -549,7 +548,6 @@ pub(super) async fn force_join_list_of_local_users(
|
|||||||
&room_id,
|
&room_id,
|
||||||
Some(String::from(BULK_JOIN_REASON)),
|
Some(String::from(BULK_JOIN_REASON)),
|
||||||
&servers,
|
&servers,
|
||||||
None,
|
|
||||||
&None,
|
&None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -635,7 +633,6 @@ pub(super) async fn force_join_all_local_users(
|
|||||||
&room_id,
|
&room_id,
|
||||||
Some(String::from(BULK_JOIN_REASON)),
|
Some(String::from(BULK_JOIN_REASON)),
|
||||||
&servers,
|
&servers,
|
||||||
None,
|
|
||||||
&None,
|
&None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -675,8 +672,7 @@ pub(super) async fn force_join_room(
|
|||||||
self.services.globals.user_is_local(&user_id),
|
self.services.globals.user_is_local(&user_id),
|
||||||
"Parsed user_id must be a local user"
|
"Parsed user_id must be a local user"
|
||||||
);
|
);
|
||||||
join_room_by_id_helper(self.services, &user_id, &room_id, None, &servers, None, &None)
|
join_room_by_id_helper(self.services, &user_id, &room_id, None, &servers, &None).await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.write_str(&format!("{user_id} has been joined to {room_id}.",))
|
self.write_str(&format!("{user_id} has been joined to {room_id}.",))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -583,7 +583,6 @@ pub(crate) async fn register_route(
|
|||||||
&room_id,
|
&room_id,
|
||||||
Some("Automatically joining this room upon registration".to_owned()),
|
Some("Automatically joining this room upon registration".to_owned()),
|
||||||
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
||||||
None,
|
|
||||||
&body.appservice_info,
|
&body.appservice_info,
|
||||||
)
|
)
|
||||||
.boxed()
|
.boxed()
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ use ruma::{OwnedEventId, UserId, api::client::context::get_context, events::Stat
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Ruma,
|
Ruma,
|
||||||
client::message::{event_filter, ignored_filter, lazy_loading_witness, visibility_filter},
|
client::{
|
||||||
|
is_ignored_pdu,
|
||||||
|
message::{event_filter, ignored_filter, lazy_loading_witness, visibility_filter},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIMIT_MAX: usize = 100;
|
const LIMIT_MAX: usize = 100;
|
||||||
@@ -78,6 +81,9 @@ pub(crate) async fn get_context_route(
|
|||||||
return Err!(Request(NotFound("Event not found.")));
|
return Err!(Request(NotFound("Event not found.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return M_SENDER_IGNORED if the sender of base_event is ignored (MSC4406)
|
||||||
|
is_ignored_pdu(&services, &base_pdu, sender_user).await?;
|
||||||
|
|
||||||
let base_count = base_id.pdu_count();
|
let base_count = base_id.pdu_count();
|
||||||
|
|
||||||
let base_event = ignored_filter(&services, (base_count, base_pdu), sender_user);
|
let base_event = ignored_filter(&services, (base_count, base_pdu), sender_user);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::{borrow::Borrow, collections::HashMap, iter::once, sync::Arc};
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Result, debug, debug_info, debug_warn, err, error, info,
|
Err, Result, debug, debug_info, debug_warn, err, error, info, is_true,
|
||||||
matrix::{
|
matrix::{
|
||||||
StateKey,
|
StateKey,
|
||||||
event::{gen_event_id, gen_event_id_canonical_json},
|
event::{gen_event_id, gen_event_id_canonical_json},
|
||||||
@@ -26,7 +26,7 @@ use ruma::{
|
|||||||
api::{
|
api::{
|
||||||
client::{
|
client::{
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
membership::{ThirdPartySigned, join_room_by_id, join_room_by_id_or_alias},
|
membership::{join_room_by_id, join_room_by_id_or_alias},
|
||||||
},
|
},
|
||||||
federation::{self},
|
federation::{self},
|
||||||
},
|
},
|
||||||
@@ -34,7 +34,7 @@ use ruma::{
|
|||||||
events::{
|
events::{
|
||||||
StateEventType,
|
StateEventType,
|
||||||
room::{
|
room::{
|
||||||
join_rules::{AllowRule, JoinRule},
|
join_rules::JoinRule,
|
||||||
member::{MembershipState, RoomMemberEventContent},
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -48,9 +48,13 @@ use service::{
|
|||||||
timeline::pdu_fits,
|
timeline::pdu_fits,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use tokio::join;
|
||||||
|
|
||||||
use super::{banned_room_check, validate_remote_member_event_stub};
|
use super::{banned_room_check, validate_remote_member_event_stub};
|
||||||
use crate::Ruma;
|
use crate::{
|
||||||
|
Ruma,
|
||||||
|
server::{select_authorising_user, user_can_perform_restricted_join},
|
||||||
|
};
|
||||||
|
|
||||||
/// # `POST /_matrix/client/r0/rooms/{roomId}/join`
|
/// # `POST /_matrix/client/r0/rooms/{roomId}/join`
|
||||||
///
|
///
|
||||||
@@ -116,7 +120,6 @@ pub(crate) async fn join_room_by_id_route(
|
|||||||
&body.room_id,
|
&body.room_id,
|
||||||
body.reason.clone(),
|
body.reason.clone(),
|
||||||
&servers,
|
&servers,
|
||||||
body.third_party_signed.as_ref(),
|
|
||||||
&body.appservice_info,
|
&body.appservice_info,
|
||||||
)
|
)
|
||||||
.boxed()
|
.boxed()
|
||||||
@@ -248,7 +251,6 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
|||||||
&room_id,
|
&room_id,
|
||||||
body.reason.clone(),
|
body.reason.clone(),
|
||||||
&servers,
|
&servers,
|
||||||
body.third_party_signed.as_ref(),
|
|
||||||
appservice_info,
|
appservice_info,
|
||||||
)
|
)
|
||||||
.boxed()
|
.boxed()
|
||||||
@@ -263,7 +265,6 @@ pub async fn join_room_by_id_helper(
|
|||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
reason: Option<String>,
|
reason: Option<String>,
|
||||||
servers: &[OwnedServerName],
|
servers: &[OwnedServerName],
|
||||||
third_party_signed: Option<&ThirdPartySigned>,
|
|
||||||
appservice_info: &Option<RegistrationInfo>,
|
appservice_info: &Option<RegistrationInfo>,
|
||||||
) -> Result<join_room_by_id::v3::Response> {
|
) -> Result<join_room_by_id::v3::Response> {
|
||||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||||
@@ -351,17 +352,9 @@ pub async fn join_room_by_id_helper(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if server_in_room {
|
if server_in_room {
|
||||||
join_room_by_id_helper_local(
|
join_room_by_id_helper_local(services, sender_user, room_id, reason, servers, state_lock)
|
||||||
services,
|
.boxed()
|
||||||
sender_user,
|
.await?;
|
||||||
room_id,
|
|
||||||
reason,
|
|
||||||
servers,
|
|
||||||
third_party_signed,
|
|
||||||
state_lock,
|
|
||||||
)
|
|
||||||
.boxed()
|
|
||||||
.await?;
|
|
||||||
} else {
|
} else {
|
||||||
// Ask a remote server if we are not participating in this room
|
// Ask a remote server if we are not participating in this room
|
||||||
join_room_by_id_helper_remote(
|
join_room_by_id_helper_remote(
|
||||||
@@ -370,7 +363,6 @@ pub async fn join_room_by_id_helper(
|
|||||||
room_id,
|
room_id,
|
||||||
reason,
|
reason,
|
||||||
servers,
|
servers,
|
||||||
third_party_signed,
|
|
||||||
state_lock,
|
state_lock,
|
||||||
)
|
)
|
||||||
.boxed()
|
.boxed()
|
||||||
@@ -386,7 +378,6 @@ async fn join_room_by_id_helper_remote(
|
|||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
reason: Option<String>,
|
reason: Option<String>,
|
||||||
servers: &[OwnedServerName],
|
servers: &[OwnedServerName],
|
||||||
_third_party_signed: Option<&ThirdPartySigned>,
|
|
||||||
state_lock: RoomMutexGuard,
|
state_lock: RoomMutexGuard,
|
||||||
) -> Result {
|
) -> Result {
|
||||||
info!("Joining {room_id} over federation.");
|
info!("Joining {room_id} over federation.");
|
||||||
@@ -396,11 +387,10 @@ async fn join_room_by_id_helper_remote(
|
|||||||
|
|
||||||
info!("make_join finished");
|
info!("make_join finished");
|
||||||
|
|
||||||
let Some(room_version_id) = make_join_response.room_version else {
|
let room_version_id = make_join_response.room_version.unwrap_or(RoomVersionId::V1);
|
||||||
return Err!(BadServerResponse("Remote room version is not supported by conduwuit"));
|
|
||||||
};
|
|
||||||
|
|
||||||
if !services.server.supported_room_version(&room_version_id) {
|
if !services.server.supported_room_version(&room_version_id) {
|
||||||
|
// How did we get here?
|
||||||
return Err!(BadServerResponse(
|
return Err!(BadServerResponse(
|
||||||
"Remote room version {room_version_id} is not supported by conduwuit"
|
"Remote room version {room_version_id} is not supported by conduwuit"
|
||||||
));
|
));
|
||||||
@@ -429,10 +419,6 @@ async fn join_room_by_id_helper_remote(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
join_event_stub.insert(
|
|
||||||
"origin".to_owned(),
|
|
||||||
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
|
|
||||||
);
|
|
||||||
join_event_stub.insert(
|
join_event_stub.insert(
|
||||||
"origin_server_ts".to_owned(),
|
"origin_server_ts".to_owned(),
|
||||||
CanonicalJsonValue::Integer(
|
CanonicalJsonValue::Integer(
|
||||||
@@ -744,87 +730,45 @@ async fn join_room_by_id_helper_local(
|
|||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
reason: Option<String>,
|
reason: Option<String>,
|
||||||
servers: &[OwnedServerName],
|
servers: &[OwnedServerName],
|
||||||
_third_party_signed: Option<&ThirdPartySigned>,
|
|
||||||
state_lock: RoomMutexGuard,
|
state_lock: RoomMutexGuard,
|
||||||
) -> Result {
|
) -> Result {
|
||||||
debug_info!("We can join locally");
|
info!("Joining room locally");
|
||||||
let join_rules = services.rooms.state_accessor.get_join_rules(room_id).await;
|
|
||||||
|
|
||||||
let mut restricted_join_authorized = None;
|
let (room_version, join_rules, is_invited) = join!(
|
||||||
match join_rules {
|
services.rooms.state.get_room_version(room_id),
|
||||||
| JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted) => {
|
services.rooms.state_accessor.get_join_rules(room_id),
|
||||||
for restriction in restricted.allow {
|
services.rooms.state_cache.is_invited(sender_user, room_id)
|
||||||
match restriction {
|
);
|
||||||
| AllowRule::RoomMembership(membership) => {
|
|
||||||
if services
|
let room_version = room_version?;
|
||||||
.rooms
|
let mut auth_user: Option<OwnedUserId> = None;
|
||||||
.state_cache
|
if !is_invited && matches!(join_rules, JoinRule::Restricted(_) | JoinRule::KnockRestricted(_))
|
||||||
.is_joined(sender_user, &membership.room_id)
|
{
|
||||||
.await
|
use RoomVersionId::*;
|
||||||
{
|
if !matches!(room_version, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
|
||||||
restricted_join_authorized = Some(true);
|
// This is a restricted room, check if we can complete the join requirements
|
||||||
break;
|
// locally.
|
||||||
}
|
let needs_auth_user =
|
||||||
},
|
user_can_perform_restricted_join(services, sender_user, room_id, &room_version)
|
||||||
| AllowRule::UnstableSpamChecker => {
|
.await;
|
||||||
match services
|
if needs_auth_user.is_ok_and(is_true!()) {
|
||||||
.antispam
|
// If there was an error or the value is false, we'll try joining over
|
||||||
.meowlnir_accept_make_join(room_id.to_owned(), sender_user.to_owned())
|
// federation. Since it's Ok(true), we can authorise this locally.
|
||||||
.await
|
// If we can't select a local user, this will remain None, the join will fail,
|
||||||
{
|
// and we'll fall back to federation.
|
||||||
| Ok(()) => {
|
auth_user = select_authorising_user(services, room_id, sender_user, &state_lock)
|
||||||
restricted_join_authorized = Some(true);
|
.await
|
||||||
break;
|
.ok();
|
||||||
},
|
|
||||||
| Err(_) =>
|
|
||||||
return Err!(Request(Forbidden(
|
|
||||||
"Antispam rejected join request."
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
| _ => {},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
| _ => {},
|
|
||||||
}
|
|
||||||
let join_authorized_via_users_server = if restricted_join_authorized.is_none() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
match restricted_join_authorized.unwrap() {
|
|
||||||
| true => services
|
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.local_users_in_room(room_id)
|
|
||||||
.filter(|user| {
|
|
||||||
trace!("Checking if {user} can invite {sender_user} to {room_id}");
|
|
||||||
services.rooms.state_accessor.user_can_invite(
|
|
||||||
room_id,
|
|
||||||
user,
|
|
||||||
sender_user,
|
|
||||||
&state_lock,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.boxed()
|
|
||||||
.next()
|
|
||||||
.await
|
|
||||||
.map(ToOwned::to_owned),
|
|
||||||
| false => {
|
|
||||||
warn!(
|
|
||||||
"Join authorization failed for restricted join in room {room_id} for user \
|
|
||||||
{sender_user}"
|
|
||||||
);
|
|
||||||
return Err!(Request(Forbidden("You are not authorized to join this room.")));
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let content = RoomMemberEventContent {
|
let content = RoomMemberEventContent {
|
||||||
displayname: services.users.displayname(sender_user).await.ok(),
|
displayname: services.users.displayname(sender_user).await.ok(),
|
||||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||||
reason: reason.clone(),
|
reason: reason.clone(),
|
||||||
join_authorized_via_users_server,
|
join_authorized_via_users_server: auth_user,
|
||||||
..RoomMemberEventContent::new(MembershipState::Join)
|
..RoomMemberEventContent::new(MembershipState::Join)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -840,6 +784,7 @@ async fn join_room_by_id_helper_local(
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
else {
|
else {
|
||||||
|
info!("Joined room locally");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -847,138 +792,13 @@ async fn join_room_by_id_helper_local(
|
|||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
warn!(
|
info!(
|
||||||
?error,
|
?error,
|
||||||
servers = %servers.len(),
|
remote_servers = %servers.len(),
|
||||||
"Could not join restricted room locally, attempting remote join",
|
"Could not join room locally, attempting remote join",
|
||||||
);
|
);
|
||||||
let Ok((make_join_response, remote_server)) =
|
join_room_by_id_helper_remote(services, sender_user, room_id, reason, servers, state_lock)
|
||||||
make_join_request(services, sender_user, room_id, servers).await
|
.await
|
||||||
else {
|
|
||||||
return Err(error);
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(room_version_id) = make_join_response.room_version else {
|
|
||||||
return Err!(BadServerResponse("Remote room version is not supported by conduwuit"));
|
|
||||||
};
|
|
||||||
|
|
||||||
if !services.server.supported_room_version(&room_version_id) {
|
|
||||||
return Err!(BadServerResponse(
|
|
||||||
"Remote room version {room_version_id} is not supported by conduwuit"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut join_event_stub: CanonicalJsonObject =
|
|
||||||
serde_json::from_str(make_join_response.event.get()).map_err(|e| {
|
|
||||||
err!(BadServerResponse("Invalid make_join event json received from server: {e:?}"))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
validate_remote_member_event_stub(
|
|
||||||
&MembershipState::Join,
|
|
||||||
sender_user,
|
|
||||||
room_id,
|
|
||||||
&join_event_stub,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let join_authorized_via_users_server = join_event_stub
|
|
||||||
.get("content")
|
|
||||||
.map(|s| {
|
|
||||||
s.as_object()?
|
|
||||||
.get("join_authorised_via_users_server")?
|
|
||||||
.as_str()
|
|
||||||
})
|
|
||||||
.and_then(|s| OwnedUserId::try_from(s.unwrap_or_default()).ok());
|
|
||||||
|
|
||||||
join_event_stub.insert(
|
|
||||||
"origin".to_owned(),
|
|
||||||
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
|
|
||||||
);
|
|
||||||
join_event_stub.insert(
|
|
||||||
"origin_server_ts".to_owned(),
|
|
||||||
CanonicalJsonValue::Integer(
|
|
||||||
utils::millis_since_unix_epoch()
|
|
||||||
.try_into()
|
|
||||||
.expect("Timestamp is valid js_int value"),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
join_event_stub.insert(
|
|
||||||
"content".to_owned(),
|
|
||||||
to_canonical_value(RoomMemberEventContent {
|
|
||||||
displayname: services.users.displayname(sender_user).await.ok(),
|
|
||||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
|
||||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
|
||||||
reason,
|
|
||||||
join_authorized_via_users_server,
|
|
||||||
..RoomMemberEventContent::new(MembershipState::Join)
|
|
||||||
})
|
|
||||||
.expect("event is valid, we just created it"),
|
|
||||||
);
|
|
||||||
|
|
||||||
// We keep the "event_id" in the pdu only in v1 or
|
|
||||||
// v2 rooms
|
|
||||||
match room_version_id {
|
|
||||||
| RoomVersionId::V1 | RoomVersionId::V2 => {},
|
|
||||||
| _ => {
|
|
||||||
join_event_stub.remove("event_id");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// In order to create a compatible ref hash (EventID) the `hashes` field needs
|
|
||||||
// to be present
|
|
||||||
services
|
|
||||||
.server_keys
|
|
||||||
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
|
|
||||||
|
|
||||||
// Generate event id
|
|
||||||
let event_id = gen_event_id(&join_event_stub, &room_version_id)?;
|
|
||||||
|
|
||||||
// Add event_id back
|
|
||||||
join_event_stub
|
|
||||||
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
|
|
||||||
|
|
||||||
// It has enough fields to be called a proper event now
|
|
||||||
let join_event = join_event_stub;
|
|
||||||
|
|
||||||
let send_join_response = services
|
|
||||||
.sending
|
|
||||||
.send_synapse_request(
|
|
||||||
&remote_server,
|
|
||||||
federation::membership::create_join_event::v2::Request {
|
|
||||||
room_id: room_id.to_owned(),
|
|
||||||
event_id: event_id.clone(),
|
|
||||||
omit_members: false,
|
|
||||||
pdu: services
|
|
||||||
.sending
|
|
||||||
.convert_to_outgoing_federation_event(join_event.clone())
|
|
||||||
.await,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(signed_raw) = send_join_response.room_state.event {
|
|
||||||
let (signed_event_id, signed_value) =
|
|
||||||
gen_event_id_canonical_json(&signed_raw, &room_version_id).map_err(|e| {
|
|
||||||
err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if signed_event_id != event_id {
|
|
||||||
return Err!(Request(BadJson(
|
|
||||||
warn!(%signed_event_id, %event_id, "Server {remote_server} sent event with wrong event ID")
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(state_lock);
|
|
||||||
services
|
|
||||||
.rooms
|
|
||||||
.event_handler
|
|
||||||
.handle_incoming_pdu(&remote_server, room_id, &signed_event_id, signed_value, true)
|
|
||||||
.boxed()
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
return Err(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn make_join_request(
|
async fn make_join_request(
|
||||||
@@ -987,17 +807,16 @@ async fn make_join_request(
|
|||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
servers: &[OwnedServerName],
|
servers: &[OwnedServerName],
|
||||||
) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> {
|
) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> {
|
||||||
let mut make_join_response_and_server =
|
let mut make_join_counter: usize = 1;
|
||||||
Err!(BadServerResponse("No server available to assist in joining."));
|
|
||||||
|
|
||||||
let mut make_join_counter: usize = 0;
|
|
||||||
let mut incompatible_room_version_count: usize = 0;
|
|
||||||
|
|
||||||
for remote_server in servers {
|
for remote_server in servers {
|
||||||
if services.globals.server_is_ours(remote_server) {
|
if services.globals.server_is_ours(remote_server) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
info!("Asking {remote_server} for make_join ({make_join_counter})");
|
info!(
|
||||||
|
"Asking {remote_server} for make_join (attempt {make_join_counter}/{})",
|
||||||
|
servers.len()
|
||||||
|
);
|
||||||
let make_join_response = services
|
let make_join_response = services
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
@@ -1025,47 +844,44 @@ async fn make_join_request(
|
|||||||
warn!("make_join response from {remote_server} failed validation: {e}");
|
warn!("make_join response from {remote_server} failed validation: {e}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
make_join_response_and_server = Ok((response, remote_server.clone()));
|
return Ok((response, remote_server.clone()));
|
||||||
break;
|
|
||||||
},
|
},
|
||||||
| Err(e) => {
|
| Err(e) => match e.kind() {
|
||||||
info!("make_join request to {remote_server} failed: {e}");
|
| ErrorKind::UnableToAuthorizeJoin => {
|
||||||
if matches!(
|
|
||||||
e.kind(),
|
|
||||||
ErrorKind::IncompatibleRoomVersion { .. } | ErrorKind::UnsupportedRoomVersion
|
|
||||||
) {
|
|
||||||
incompatible_room_version_count =
|
|
||||||
incompatible_room_version_count.saturating_add(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if incompatible_room_version_count > 15 {
|
|
||||||
info!(
|
info!(
|
||||||
"15 servers have responded with M_INCOMPATIBLE_ROOM_VERSION or \
|
"{remote_server} was unable to verify the joining user satisfied \
|
||||||
M_UNSUPPORTED_ROOM_VERSION, assuming that conduwuit does not support \
|
restricted join requirements: {e}. Will continue trying."
|
||||||
the room version {room_id}: {e}"
|
|
||||||
);
|
);
|
||||||
make_join_response_and_server =
|
},
|
||||||
Err!(BadServerResponse("Room version is not supported by Conduwuit"));
|
| ErrorKind::UnableToGrantJoin => {
|
||||||
return make_join_response_and_server;
|
info!(
|
||||||
}
|
"{remote_server} believes the joining user satisfies restricted join \
|
||||||
|
rules, but is unable to authorise a join for us. Will continue trying."
|
||||||
if make_join_counter > 40 {
|
);
|
||||||
|
},
|
||||||
|
| ErrorKind::IncompatibleRoomVersion { room_version } => {
|
||||||
warn!(
|
warn!(
|
||||||
"40 servers failed to provide valid make_join response, assuming no \
|
"{remote_server} reports the room we are trying to join is \
|
||||||
server can assist in joining."
|
v{room_version}, which we do not support."
|
||||||
);
|
);
|
||||||
make_join_response_and_server =
|
return Err(e);
|
||||||
Err!(BadServerResponse("No server available to assist in joining."));
|
},
|
||||||
|
| ErrorKind::Forbidden { .. } => {
|
||||||
return make_join_response_and_server;
|
warn!("{remote_server} refuses to let us join: {e}.");
|
||||||
}
|
return Err(e);
|
||||||
|
},
|
||||||
|
| ErrorKind::NotFound => {
|
||||||
|
info!(
|
||||||
|
"{remote_server} does not know about {room_id}: {e}. Will continue \
|
||||||
|
trying."
|
||||||
|
);
|
||||||
|
},
|
||||||
|
| _ => {
|
||||||
|
info!("{remote_server} failed to make_join: {e}. Will continue trying.");
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if make_join_response_and_server.is_ok() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
info!("All {} servers were unable to assist in joining {room_id} :(", servers.len());
|
||||||
make_join_response_and_server
|
Err!(BadServerResponse("No server available to assist in joining."))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,7 +253,6 @@ async fn knock_room_by_id_helper(
|
|||||||
room_id,
|
room_id,
|
||||||
reason.clone(),
|
reason.clone(),
|
||||||
servers,
|
servers,
|
||||||
None,
|
|
||||||
&None,
|
&None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Result, at, debug_warn,
|
Err, Error, Result, at, debug_warn,
|
||||||
matrix::{
|
matrix::{
|
||||||
event::{Event, Matches},
|
event::{Event, Matches},
|
||||||
pdu::PduCount,
|
pdu::PduCount,
|
||||||
@@ -26,7 +26,7 @@ use ruma::{
|
|||||||
DeviceId, RoomId, UserId,
|
DeviceId, RoomId, UserId,
|
||||||
api::{
|
api::{
|
||||||
Direction,
|
Direction,
|
||||||
client::{filter::RoomEventFilter, message::get_message_events},
|
client::{error::ErrorKind, filter::RoomEventFilter, message::get_message_events},
|
||||||
},
|
},
|
||||||
events::{
|
events::{
|
||||||
AnyStateEvent, StateEventType,
|
AnyStateEvent, StateEventType,
|
||||||
@@ -279,23 +279,30 @@ pub(crate) async fn ignored_filter(
|
|||||||
|
|
||||||
is_ignored_pdu(services, pdu, user_id)
|
is_ignored_pdu(services, pdu, user_id)
|
||||||
.await
|
.await
|
||||||
|
.unwrap_or(true)
|
||||||
.eq(&false)
|
.eq(&false)
|
||||||
.then_some(item)
|
.then_some(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determine whether a PDU should be ignored for a given recipient user.
|
||||||
|
/// Returns True if this PDU should be ignored, returns False otherwise.
|
||||||
|
///
|
||||||
|
/// The error SenderIgnored is returned if the sender or the sender's server is
|
||||||
|
/// ignored by the relevant user. If the error cannot be returned to the user,
|
||||||
|
/// it should equate to a true value (i.e. ignored).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) async fn is_ignored_pdu<Pdu>(
|
pub(crate) async fn is_ignored_pdu<Pdu>(
|
||||||
services: &Services,
|
services: &Services,
|
||||||
event: &Pdu,
|
event: &Pdu,
|
||||||
recipient_user: &UserId,
|
recipient_user: &UserId,
|
||||||
) -> bool
|
) -> Result<bool>
|
||||||
where
|
where
|
||||||
Pdu: Event + Send + Sync,
|
Pdu: Event + Send + Sync,
|
||||||
{
|
{
|
||||||
// exclude Synapse's dummy events from bloating up response bodies. clients
|
// exclude Synapse's dummy events from bloating up response bodies. clients
|
||||||
// don't need to see this.
|
// don't need to see this.
|
||||||
if event.kind().to_cow_str() == "org.matrix.dummy_event" {
|
if event.kind().to_cow_str() == "org.matrix.dummy_event" {
|
||||||
return true;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
let sender_user = event.sender();
|
let sender_user = event.sender();
|
||||||
@@ -310,21 +317,27 @@ where
|
|||||||
|
|
||||||
if !type_ignored {
|
if !type_ignored {
|
||||||
// We cannot safely ignore this type
|
// We cannot safely ignore this type
|
||||||
return false;
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if server_ignored {
|
if server_ignored {
|
||||||
// the sender's server is ignored, so ignore this event
|
// the sender's server is ignored, so ignore this event
|
||||||
return true;
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::SenderIgnored { sender: None },
|
||||||
|
"The sender's server is ignored by this server.",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if user_ignored && !services.config.send_messages_from_ignored_users_to_client {
|
if user_ignored && !services.config.send_messages_from_ignored_users_to_client {
|
||||||
// the recipient of this PDU has the sender ignored, and we're not
|
// the recipient of this PDU has the sender ignored, and we're not
|
||||||
// configured to send ignored messages to clients
|
// configured to send ignored messages to clients
|
||||||
return true;
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::SenderIgnored { sender: Some(event.sender().to_owned()) },
|
||||||
|
"You have ignored this sender.",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Result, at, debug_warn,
|
Err, Result, at, debug_warn, err,
|
||||||
matrix::{Event, event::RelationTypeEqual, pdu::PduCount},
|
matrix::{Event, event::RelationTypeEqual, pdu::PduCount},
|
||||||
utils::{IterStream, ReadyExt, result::FlatOk, stream::WidebandExt},
|
utils::{IterStream, ReadyExt, result::FlatOk, stream::WidebandExt},
|
||||||
};
|
};
|
||||||
@@ -18,7 +18,7 @@ use ruma::{
|
|||||||
events::{TimelineEventType, relation::RelationType},
|
events::{TimelineEventType, relation::RelationType},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Ruma;
|
use crate::{Ruma, client::is_ignored_pdu};
|
||||||
|
|
||||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}`
|
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}`
|
||||||
pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
|
pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
|
||||||
@@ -118,6 +118,14 @@ async fn paginate_relations_with_filter(
|
|||||||
debug_warn!(req_evt = %target, %room_id, "Event relations requested by {sender_user} but is not allowed to see it, returning 404");
|
debug_warn!(req_evt = %target, %room_id, "Event relations requested by {sender_user} but is not allowed to see it, returning 404");
|
||||||
return Err!(Request(NotFound("Event not found.")));
|
return Err!(Request(NotFound("Event not found.")));
|
||||||
}
|
}
|
||||||
|
let target_pdu = services
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.get_pdu(target)
|
||||||
|
.await
|
||||||
|
.map_err(|_| err!(Request(NotFound("Event not found."))))?;
|
||||||
|
// Return M_SENDER_IGNORED if the sender of base_event is ignored (MSC4406)
|
||||||
|
is_ignored_pdu(services, &target_pdu, sender_user).await?;
|
||||||
|
|
||||||
let start: PduCount = from
|
let start: PduCount = from
|
||||||
.map(str::parse)
|
.map(str::parse)
|
||||||
@@ -159,6 +167,7 @@ async fn paginate_relations_with_filter(
|
|||||||
.ready_take_while(|(count, _)| Some(*count) != to)
|
.ready_take_while(|(count, _)| Some(*count) != to)
|
||||||
.take(limit)
|
.take(limit)
|
||||||
.wide_filter_map(|item| visibility_filter(services, sender_user, item))
|
.wide_filter_map(|item| visibility_filter(services, sender_user, item))
|
||||||
|
.wide_filter_map(|item| ignored_filter(services, item, sender_user))
|
||||||
.then(async |mut pdu| {
|
.then(async |mut pdu| {
|
||||||
if let Err(e) = services
|
if let Err(e) = services
|
||||||
.rooms
|
.rooms
|
||||||
@@ -214,3 +223,17 @@ async fn visibility_filter<Pdu: Event + Send + Sync>(
|
|||||||
.await
|
.await
|
||||||
.then_some(item)
|
.then_some(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn ignored_filter<Pdu: Event + Send + Sync>(
|
||||||
|
services: &Services,
|
||||||
|
item: (PduCount, Pdu),
|
||||||
|
sender_user: &UserId,
|
||||||
|
) -> Option<(PduCount, Pdu)> {
|
||||||
|
let (_, pdu) = &item;
|
||||||
|
|
||||||
|
if is_ignored_pdu(services, pdu, sender_user).await.ok()? {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ pub(crate) async fn get_room_event_route(
|
|||||||
|
|
||||||
let (mut event, visible) = try_join(event, visible).await?;
|
let (mut event, visible) = try_join(event, visible).await?;
|
||||||
|
|
||||||
if !visible || is_ignored_pdu(services, &event, body.sender_user()).await {
|
if !visible || is_ignored_pdu(services, &event, body.sender_user()).await? {
|
||||||
return Err!(Request(Forbidden("You don't have permission to view this event.")));
|
return Err!(Request(Forbidden("You don't have permission to view this event.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ pub(super) async fn ldap_login(
|
|||||||
) -> Result<OwnedUserId> {
|
) -> Result<OwnedUserId> {
|
||||||
let (user_dn, is_ldap_admin) = match services.config.ldap.bind_dn.as_ref() {
|
let (user_dn, is_ldap_admin) = match services.config.ldap.bind_dn.as_ref() {
|
||||||
| Some(bind_dn) if bind_dn.contains("{username}") =>
|
| Some(bind_dn) if bind_dn.contains("{username}") =>
|
||||||
(bind_dn.replace("{username}", lowercased_user_id.localpart()), false),
|
(bind_dn.replace("{username}", lowercased_user_id.localpart()), None),
|
||||||
| _ => {
|
| _ => {
|
||||||
debug!("Searching user in LDAP");
|
debug!("Searching user in LDAP");
|
||||||
|
|
||||||
@@ -144,12 +144,16 @@ pub(super) async fn ldap_login(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_conduwuit_admin = services.admin.user_is_admin(lowercased_user_id).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 {
|
if is_ldap_admin && !is_conduwuit_admin {
|
||||||
Box::pin(services.admin.make_user_admin(lowercased_user_id)).await?;
|
Box::pin(services.admin.make_user_admin(lowercased_user_id)).await?;
|
||||||
} else if !is_ldap_admin && is_conduwuit_admin {
|
} else if !is_ldap_admin && is_conduwuit_admin {
|
||||||
Box::pin(services.admin.revoke_admin(lowercased_user_id)).await?;
|
Box::pin(services.admin.revoke_admin(lowercased_user_id)).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(user_id)
|
Ok(user_id)
|
||||||
|
|||||||
@@ -685,8 +685,15 @@ async fn collect_required_state(
|
|||||||
required_state_request: &BTreeSet<TypeStateKey>,
|
required_state_request: &BTreeSet<TypeStateKey>,
|
||||||
) -> Vec<Raw<AnySyncStateEvent>> {
|
) -> Vec<Raw<AnySyncStateEvent>> {
|
||||||
let mut required_state = Vec::new();
|
let mut required_state = Vec::new();
|
||||||
|
let mut wildcard_types: HashSet<&StateEventType> = HashSet::new();
|
||||||
|
|
||||||
for (event_type, state_key) in required_state_request {
|
for (event_type, state_key) in required_state_request {
|
||||||
|
if wildcard_types.contains(event_type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if state_key.as_str() == "*" {
|
if state_key.as_str() == "*" {
|
||||||
|
wildcard_types.insert(event_type);
|
||||||
if let Ok(keys) = services
|
if let Ok(keys) = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
@@ -703,7 +710,6 @@ async fn collect_required_state(
|
|||||||
required_state.push(Event::into_format(event));
|
required_state.push(Event::into_format(event));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else if let Ok(event) = services
|
} else if let Ok(event) = services
|
||||||
.rooms
|
.rooms
|
||||||
|
|||||||
+93
-48
@@ -16,6 +16,8 @@ use ruma::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use serde_json::value::to_raw_value;
|
use serde_json::value::to_raw_value;
|
||||||
|
use service::rooms::state::RoomMutexGuard;
|
||||||
|
use tokio::join;
|
||||||
|
|
||||||
use crate::Ruma;
|
use crate::Ruma;
|
||||||
|
|
||||||
@@ -85,16 +87,24 @@ pub(crate) async fn create_join_event_template_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||||
let is_invited = services
|
let (is_invited, is_joined) = join!(
|
||||||
.rooms
|
services
|
||||||
.state_cache
|
.rooms
|
||||||
.is_invited(&body.user_id, &body.room_id)
|
.state_cache
|
||||||
.await;
|
.is_invited(&body.user_id, &body.room_id),
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.is_joined(&body.user_id, &body.room_id)
|
||||||
|
);
|
||||||
let join_authorized_via_users_server: Option<OwnedUserId> = {
|
let join_authorized_via_users_server: Option<OwnedUserId> = {
|
||||||
use RoomVersionId::*;
|
use RoomVersionId::*;
|
||||||
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) || is_invited {
|
if is_joined || is_invited {
|
||||||
// room version does not support restricted join rules, or the user is currently
|
// User is already joined or invited and consequently does not need an
|
||||||
// already invited
|
// authorising user
|
||||||
|
None
|
||||||
|
} else if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
|
||||||
|
// room version does not support restricted join rules
|
||||||
None
|
None
|
||||||
} else if user_can_perform_restricted_join(
|
} else if user_can_perform_restricted_join(
|
||||||
&services,
|
&services,
|
||||||
@@ -104,32 +114,10 @@ pub(crate) async fn create_join_event_template_route(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
let Some(auth_user) = services
|
Some(
|
||||||
.rooms
|
select_authorising_user(&services, &body.room_id, &body.user_id, &state_lock)
|
||||||
.state_cache
|
.await?,
|
||||||
.local_users_in_room(&body.room_id)
|
)
|
||||||
.filter(|user| {
|
|
||||||
services.rooms.state_accessor.user_can_invite(
|
|
||||||
&body.room_id,
|
|
||||||
user,
|
|
||||||
&body.user_id,
|
|
||||||
&state_lock,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.boxed()
|
|
||||||
.next()
|
|
||||||
.await
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
else {
|
|
||||||
info!(
|
|
||||||
"No local user is able to authorize the join of {} into {}",
|
|
||||||
&body.user_id, &body.room_id
|
|
||||||
);
|
|
||||||
return Err!(Request(UnableToGrantJoin(
|
|
||||||
"No user on this server is able to assist in joining."
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
Some(auth_user)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -159,9 +147,7 @@ pub(crate) async fn create_join_event_template_route(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
|
pdu_json.remove("event_id");
|
||||||
// room v3 and above removed the "event_id" field from remote PDU format
|
|
||||||
maybe_strip_event_id(&mut pdu_json, &room_version_id)?;
|
|
||||||
|
|
||||||
Ok(prepare_join_event::v1::Response {
|
Ok(prepare_join_event::v1::Response {
|
||||||
room_version: Some(room_version_id),
|
room_version: Some(room_version_id),
|
||||||
@@ -169,6 +155,38 @@ pub(crate) async fn create_join_event_template_route(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to find a user who is able to issue an invite in the target room.
|
||||||
|
pub(crate) async fn select_authorising_user(
|
||||||
|
services: &Services,
|
||||||
|
room_id: &RoomId,
|
||||||
|
user_id: &UserId,
|
||||||
|
state_lock: &RoomMutexGuard,
|
||||||
|
) -> Result<OwnedUserId> {
|
||||||
|
let auth_user = services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.local_users_in_room(room_id)
|
||||||
|
.filter(|user| {
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_can_invite(room_id, user, user_id, state_lock)
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.map(ToOwned::to_owned);
|
||||||
|
|
||||||
|
match auth_user {
|
||||||
|
| Some(auth_user) => Ok(auth_user),
|
||||||
|
| None => {
|
||||||
|
Err!(Request(UnableToGrantJoin(
|
||||||
|
"No user on this server is able to assist in joining."
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks whether the given user can join the given room via a restricted join.
|
/// Checks whether the given user can join the given room via a restricted join.
|
||||||
pub(crate) async fn user_can_perform_restricted_join(
|
pub(crate) async fn user_can_perform_restricted_join(
|
||||||
services: &Services,
|
services: &Services,
|
||||||
@@ -180,12 +198,9 @@ pub(crate) async fn user_can_perform_restricted_join(
|
|||||||
|
|
||||||
// restricted rooms are not supported on <=v7
|
// restricted rooms are not supported on <=v7
|
||||||
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
|
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
|
||||||
return Ok(false);
|
// This should be impossible as it was checked earlier on, but retain this check
|
||||||
}
|
// for safety.
|
||||||
|
unreachable!("user_can_perform_restricted_join got incompatible room version");
|
||||||
if services.rooms.state_cache.is_joined(user_id, room_id).await {
|
|
||||||
// joining user is already joined, there is nothing we need to do
|
|
||||||
return Ok(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let Ok(join_rules_event_content) = services
|
let Ok(join_rules_event_content) = services
|
||||||
@@ -205,17 +220,31 @@ pub(crate) async fn user_can_perform_restricted_join(
|
|||||||
let (JoinRule::Restricted(r) | JoinRule::KnockRestricted(r)) =
|
let (JoinRule::Restricted(r) | JoinRule::KnockRestricted(r)) =
|
||||||
join_rules_event_content.join_rule
|
join_rules_event_content.join_rule
|
||||||
else {
|
else {
|
||||||
|
// This is not a restricted room
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if r.allow.is_empty() {
|
if r.allow.is_empty() {
|
||||||
debug_info!("{room_id} is restricted but the allow key is empty");
|
// This will never be authorisable, return forbidden.
|
||||||
return Ok(false);
|
return Err!(Request(Forbidden("You are not invited to this room.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut could_satisfy = true;
|
||||||
for allow_rule in &r.allow {
|
for allow_rule in &r.allow {
|
||||||
match allow_rule {
|
match allow_rule {
|
||||||
| AllowRule::RoomMembership(membership) => {
|
| AllowRule::RoomMembership(membership) => {
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &membership.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
// Since we can't check this room, mark could_satisfy as false
|
||||||
|
// so that we can return M_UNABLE_TO_AUTHORIZE_JOIN later.
|
||||||
|
could_satisfy = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if services
|
if services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
@@ -239,6 +268,8 @@ pub(crate) async fn user_can_perform_restricted_join(
|
|||||||
| Err(_) => Err!(Request(Forbidden("Antispam rejected join request."))),
|
| Err(_) => Err!(Request(Forbidden("Antispam rejected join request."))),
|
||||||
},
|
},
|
||||||
| _ => {
|
| _ => {
|
||||||
|
// We don't recognise this join rule, so we cannot satisfy the request.
|
||||||
|
could_satisfy = false;
|
||||||
debug_info!(
|
debug_info!(
|
||||||
"Unsupported allow rule in restricted join for room {}: {:?}",
|
"Unsupported allow rule in restricted join for room {}: {:?}",
|
||||||
room_id,
|
room_id,
|
||||||
@@ -248,9 +279,23 @@ pub(crate) async fn user_can_perform_restricted_join(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err!(Request(UnableToAuthorizeJoin(
|
if could_satisfy {
|
||||||
"Joining user is not known to be in any required room."
|
// We were able to check all the restrictions and can be certain that the
|
||||||
)))
|
// prospective member is not permitted to join.
|
||||||
|
Err!(Request(Forbidden(
|
||||||
|
"You do not belong to any of the rooms or spaces required to join this room."
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
// We were unable to check all the restrictions. This usually means we aren't in
|
||||||
|
// one of the rooms this one is restricted to, ergo can't check its state for
|
||||||
|
// the user's membership, and consequently the user *might* be able to join if
|
||||||
|
// they ask another server.
|
||||||
|
Err!(Request(UnableToAuthorizeJoin(
|
||||||
|
"You do not belong to any of the recognised rooms or spaces required to join this \
|
||||||
|
room, but this server is unable to verify every requirement. You may be able to \
|
||||||
|
join via another server."
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn maybe_strip_event_id(
|
pub(crate) fn maybe_strip_event_id(
|
||||||
|
|||||||
@@ -1696,6 +1696,11 @@ pub struct Config {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub url_preview_check_root_domain: bool,
|
pub url_preview_check_root_domain: bool,
|
||||||
|
|
||||||
|
/// User agent that is used specifically when fetching url previews.
|
||||||
|
///
|
||||||
|
/// default: "continuwuity/<version> (bot; +https://continuwuity.org)"
|
||||||
|
pub url_preview_user_agent: Option<String>,
|
||||||
|
|
||||||
/// List of forbidden room aliases and room IDs as strings of regex
|
/// List of forbidden room aliases and room IDs as strings of regex
|
||||||
/// patterns.
|
/// patterns.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
|
|||||||
| Unrecognized => StatusCode::METHOD_NOT_ALLOWED,
|
| Unrecognized => StatusCode::METHOD_NOT_ALLOWED,
|
||||||
|
|
||||||
// 404
|
// 404
|
||||||
| NotFound | NotImplemented | FeatureDisabled => StatusCode::NOT_FOUND,
|
| NotFound | NotImplemented | FeatureDisabled | SenderIgnored { .. } =>
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
|
||||||
// 403
|
// 403
|
||||||
| GuestAccessForbidden
|
| GuestAccessForbidden
|
||||||
|
|||||||
@@ -8,9 +8,11 @@
|
|||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
static BRANDING: &str = "continuwuity";
|
static BRANDING: &str = "continuwuity";
|
||||||
|
static WEBSITE: &str = "https://continuwuity.org";
|
||||||
static SEMANTIC: &str = env!("CARGO_PKG_VERSION");
|
static SEMANTIC: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
static VERSION: OnceLock<String> = OnceLock::new();
|
static VERSION: OnceLock<String> = OnceLock::new();
|
||||||
|
static VERSION_UA: OnceLock<String> = OnceLock::new();
|
||||||
static USER_AGENT: OnceLock<String> = OnceLock::new();
|
static USER_AGENT: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -19,11 +21,18 @@ pub fn name() -> &'static str { BRANDING }
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn version() -> &'static str { VERSION.get_or_init(init_version) }
|
pub fn version() -> &'static str { VERSION.get_or_init(init_version) }
|
||||||
|
#[inline]
|
||||||
|
pub fn version_ua() -> &'static str { VERSION_UA.get_or_init(init_version_ua) }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn user_agent() -> &'static str { USER_AGENT.get_or_init(init_user_agent) }
|
pub fn user_agent() -> &'static str { USER_AGENT.get_or_init(init_user_agent) }
|
||||||
|
|
||||||
fn init_user_agent() -> String { format!("{}/{}", name(), version()) }
|
fn init_user_agent() -> String { format!("{}/{} (bot; +{WEBSITE})", name(), version_ua()) }
|
||||||
|
|
||||||
|
fn init_version_ua() -> String {
|
||||||
|
conduwuit_build_metadata::version_tag()
|
||||||
|
.map_or_else(|| SEMANTIC.to_owned(), |extra| format!("{SEMANTIC}+{extra}"))
|
||||||
|
}
|
||||||
|
|
||||||
fn init_version() -> String {
|
fn init_version() -> String {
|
||||||
conduwuit_build_metadata::version_tag()
|
conduwuit_build_metadata::version_tag()
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ impl crate::Service for Service {
|
|||||||
.clone()
|
.clone()
|
||||||
.and_then(Either::right);
|
.and_then(Either::right);
|
||||||
|
|
||||||
|
let url_preview_user_agent = config
|
||||||
|
.url_preview_user_agent
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| conduwuit::version::user_agent().to_owned());
|
||||||
|
|
||||||
Ok(Arc::new(Self {
|
Ok(Arc::new(Self {
|
||||||
default: base(config)?
|
default: base(config)?
|
||||||
.dns_resolver(resolver.resolver.clone())
|
.dns_resolver(resolver.resolver.clone())
|
||||||
@@ -49,6 +54,7 @@ impl crate::Service for Service {
|
|||||||
.dns_resolver(resolver.resolver.clone())
|
.dns_resolver(resolver.resolver.clone())
|
||||||
.timeout(Duration::from_secs(config.url_preview_timeout))
|
.timeout(Duration::from_secs(config.url_preview_timeout))
|
||||||
.redirect(redirect::Policy::limited(3))
|
.redirect(redirect::Policy::limited(3))
|
||||||
|
.user_agent(url_preview_user_agent)
|
||||||
.build()?,
|
.build()?,
|
||||||
|
|
||||||
extern_media: base(config)?
|
extern_media: base(config)?
|
||||||
|
|||||||
@@ -4,18 +4,83 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Event, Result, debug::INFO_SPAN_LEVEL, defer, err, implement, info,
|
Err, Event, PduEvent, Result, debug::INFO_SPAN_LEVEL, debug_error, debug_info, defer, err,
|
||||||
utils::stream::IterStream, warn,
|
implement, info, trace, utils::stream::IterStream, warn,
|
||||||
};
|
};
|
||||||
use futures::{
|
use futures::{
|
||||||
FutureExt, TryFutureExt, TryStreamExt,
|
FutureExt, TryFutureExt, TryStreamExt,
|
||||||
future::{OptionFuture, try_join5},
|
future::{OptionFuture, try_join4},
|
||||||
|
};
|
||||||
|
use ruma::{
|
||||||
|
CanonicalJsonValue, EventId, OwnedUserId, RoomId, ServerName, UserId,
|
||||||
|
events::{
|
||||||
|
StateEventType, TimelineEventType,
|
||||||
|
room::member::{MembershipState, RoomMemberEventContent},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use ruma::{CanonicalJsonValue, EventId, RoomId, ServerName, UserId, events::StateEventType};
|
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::rooms::timeline::{RawPduId, pdu_fits};
|
use crate::rooms::timeline::{RawPduId, pdu_fits};
|
||||||
|
|
||||||
|
async fn should_rescind_invite(
|
||||||
|
services: &crate::rooms::event_handler::Services,
|
||||||
|
content: &mut BTreeMap<String, CanonicalJsonValue>,
|
||||||
|
sender: &UserId,
|
||||||
|
room_id: &RoomId,
|
||||||
|
) -> Result<Option<PduEvent>> {
|
||||||
|
// We insert a bogus event ID since we can't actually calculate the right one
|
||||||
|
content.insert("event_id".to_owned(), CanonicalJsonValue::String("$rescind".to_owned()));
|
||||||
|
let pdu_event = serde_json::from_value::<PduEvent>(
|
||||||
|
serde_json::to_value(&content).expect("CanonicalJsonObj is a valid JsonValue"),
|
||||||
|
)
|
||||||
|
.map_err(|e| err!("invalid PDU: {e}"))?;
|
||||||
|
|
||||||
|
if pdu_event.room_id().is_none_or(|r| r != room_id)
|
||||||
|
&& pdu_event.sender() != sender
|
||||||
|
&& pdu_event.event_type() != &TimelineEventType::RoomMember
|
||||||
|
&& pdu_event.state_key().is_none_or(|v| v == sender.as_str())
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_user_id = UserId::parse(pdu_event.state_key().unwrap())?;
|
||||||
|
if pdu_event
|
||||||
|
.get_content::<RoomMemberEventContent>()?
|
||||||
|
.membership
|
||||||
|
!= MembershipState::Leave
|
||||||
|
{
|
||||||
|
return Ok(None); // Not a leave event
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does the target user have a pending invite?
|
||||||
|
let Ok(pending_invite_state) = services
|
||||||
|
.state_cache
|
||||||
|
.invite_state(target_user_id, room_id)
|
||||||
|
.await
|
||||||
|
else {
|
||||||
|
return Ok(None); // No pending invite, so nothing to rescind
|
||||||
|
};
|
||||||
|
for event in pending_invite_state {
|
||||||
|
if event
|
||||||
|
.get_field::<String>("type")?
|
||||||
|
.is_some_and(|t| t == "m.room.member")
|
||||||
|
|| event
|
||||||
|
.get_field::<OwnedUserId>("state_key")?
|
||||||
|
.is_some_and(|s| s == *target_user_id)
|
||||||
|
|| event
|
||||||
|
.get_field::<OwnedUserId>("sender")?
|
||||||
|
.is_some_and(|s| s == *sender)
|
||||||
|
|| event
|
||||||
|
.get_field::<RoomMemberEventContent>("content")?
|
||||||
|
.is_some_and(|c| c.membership == MembershipState::Invite)
|
||||||
|
{
|
||||||
|
return Ok(Some(pdu_event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
/// When receiving an event one needs to:
|
/// When receiving an event one needs to:
|
||||||
/// 0. Check the server is in the room
|
/// 0. Check the server is in the room
|
||||||
/// 1. Skip the PDU if we already know about it
|
/// 1. Skip the PDU if we already know about it
|
||||||
@@ -69,6 +134,7 @@ pub async fn handle_incoming_pdu<'a>(
|
|||||||
);
|
);
|
||||||
return Err!(Request(TooLarge("PDU is too large")));
|
return Err!(Request(TooLarge("PDU is too large")));
|
||||||
}
|
}
|
||||||
|
trace!("processing incoming pdu from {origin} for room {room_id} with event id {event_id}");
|
||||||
|
|
||||||
// 1.1 Check we even know about the room
|
// 1.1 Check we even know about the room
|
||||||
let meta_exists = self.services.metadata.exists(room_id).map(Ok);
|
let meta_exists = self.services.metadata.exists(room_id).map(Ok);
|
||||||
@@ -91,24 +157,14 @@ pub async fn handle_incoming_pdu<'a>(
|
|||||||
.then(|| self.acl_check(sender.server_name(), room_id))
|
.then(|| self.acl_check(sender.server_name(), room_id))
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
// Fetch create event
|
let (meta_exists, is_disabled, (), ()) = try_join4(
|
||||||
let create_event =
|
|
||||||
self.services
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(room_id, &StateEventType::RoomCreate, "");
|
|
||||||
|
|
||||||
let (meta_exists, is_disabled, (), (), ref create_event) = try_join5(
|
|
||||||
meta_exists,
|
meta_exists,
|
||||||
is_disabled,
|
is_disabled,
|
||||||
origin_acl_check,
|
origin_acl_check,
|
||||||
sender_acl_check.map(|o| o.unwrap_or(Ok(()))),
|
sender_acl_check.map(|o| o.unwrap_or(Ok(()))),
|
||||||
create_event,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await
|
||||||
|
.inspect_err(|e| debug_error!("failed to handle incoming PDU: {e}"))?;
|
||||||
if !meta_exists {
|
|
||||||
return Err!(Request(NotFound("Room is unknown to this server")));
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_disabled {
|
if is_disabled {
|
||||||
return Err!(Request(Forbidden("Federation of this room is disabled by this server.")));
|
return Err!(Request(Forbidden("Federation of this room is disabled by this server.")));
|
||||||
@@ -120,6 +176,23 @@ pub async fn handle_incoming_pdu<'a>(
|
|||||||
.server_in_room(self.services.globals.server_name(), room_id)
|
.server_in_room(self.services.globals.server_name(), room_id)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
// Is this a federated invite rescind?
|
||||||
|
// copied from https://github.com/element-hq/synapse/blob/7e4588a/synapse/handlers/federation_event.py#L255-L300
|
||||||
|
if value.get("type").and_then(|t| t.as_str()) == Some("m.room.member") {
|
||||||
|
if let Some(pdu) =
|
||||||
|
should_rescind_invite(&self.services, &mut value.clone(), sender, room_id).await?
|
||||||
|
{
|
||||||
|
debug_info!(
|
||||||
|
"Invite to {room_id} appears to have been rescinded by {sender}, marking as \
|
||||||
|
left"
|
||||||
|
);
|
||||||
|
self.services
|
||||||
|
.state_cache
|
||||||
|
.mark_as_left(sender, room_id, Some(pdu))
|
||||||
|
.await;
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
info!(
|
info!(
|
||||||
%origin,
|
%origin,
|
||||||
"Dropping inbound PDU for room we aren't participating in"
|
"Dropping inbound PDU for room we aren't participating in"
|
||||||
@@ -127,6 +200,17 @@ pub async fn handle_incoming_pdu<'a>(
|
|||||||
return Err!(Request(NotFound("This server is not participating in that room.")));
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !meta_exists {
|
||||||
|
return Err!(Request(NotFound("Room is unknown to this server")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch create event
|
||||||
|
let create_event = &(self
|
||||||
|
.services
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomCreate, "")
|
||||||
|
.await?);
|
||||||
|
|
||||||
let (incoming_pdu, val) = self
|
let (incoming_pdu, val) = self
|
||||||
.handle_outlier_pdu(origin, create_event, event_id, room_id, value, false)
|
.handle_outlier_pdu(origin, create_event, event_id, room_id, value, false)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ pub async fn parse_incoming_pdu(&self, pdu: &RawJsonValue) -> Result<Parsed> {
|
|||||||
.state
|
.state
|
||||||
.get_room_version(&room_id)
|
.get_room_version(&room_id)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| err!("Server is not in room {room_id}"))?;
|
.unwrap_or(RoomVersionId::V1);
|
||||||
let (event_id, value) = gen_event_id_canonical_json(pdu, &room_version_id).map_err(|e| {
|
let (event_id, value) = gen_event_id_canonical_json(pdu, &room_version_id).map_err(|e| {
|
||||||
err!(Request(InvalidParam("Could not convert event to canonical json: {e}")))
|
err!(Request(InvalidParam("Could not convert event to canonical json: {e}")))
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@@ -1269,12 +1269,12 @@ impl Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "ldap"))]
|
#[cfg(not(feature = "ldap"))]
|
||||||
pub async fn search_ldap(&self, _user_id: &UserId) -> Result<Vec<(String, bool)>> {
|
pub async fn search_ldap(&self, _user_id: &UserId) -> Result<Vec<(String, Option<bool>)>> {
|
||||||
Err!(FeatureDisabled("ldap"))
|
Err!(FeatureDisabled("ldap"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ldap")]
|
#[cfg(feature = "ldap")]
|
||||||
pub async fn search_ldap(&self, user_id: &UserId) -> Result<Vec<(String, bool)>> {
|
pub async fn search_ldap(&self, user_id: &UserId) -> Result<Vec<(String, Option<bool>)>> {
|
||||||
let localpart = user_id.localpart().to_owned();
|
let localpart = user_id.localpart().to_owned();
|
||||||
let lowercased_localpart = localpart.to_lowercase();
|
let lowercased_localpart = localpart.to_lowercase();
|
||||||
|
|
||||||
@@ -1318,7 +1318,7 @@ impl Service {
|
|||||||
.inspect(|(entries, result)| trace!(?entries, ?result, "LDAP Search"))
|
.inspect(|(entries, result)| trace!(?entries, ?result, "LDAP Search"))
|
||||||
.map_err(|e| err!(Ldap(error!(?attr, ?user_filter, "LDAP search error: {e}"))))?;
|
.map_err(|e| err!(Ldap(error!(?attr, ?user_filter, "LDAP search error: {e}"))))?;
|
||||||
|
|
||||||
let mut dns: HashMap<String, bool> = entries
|
let mut dns: HashMap<String, Option<bool>> = entries
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
let search_entry = SearchEntry::construct(entry);
|
let search_entry = SearchEntry::construct(entry);
|
||||||
@@ -1329,11 +1329,16 @@ impl Service {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(search_entry.attrs.get(&config.name_attribute))
|
.chain(search_entry.attrs.get(&config.name_attribute))
|
||||||
.any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart))
|
.any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart))
|
||||||
.then_some((search_entry.dn, false))
|
.then_some((search_entry.dn, None))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !config.admin_filter.is_empty() {
|
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() {
|
let admin_base_dn = if config.admin_base_dn.is_empty() {
|
||||||
&config.base_dn
|
&config.base_dn
|
||||||
} else {
|
} else {
|
||||||
@@ -1362,7 +1367,7 @@ impl Service {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(search_entry.attrs.get(&config.name_attribute))
|
.chain(search_entry.attrs.get(&config.name_attribute))
|
||||||
.any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart))
|
.any(|ids| ids.contains(&localpart) || ids.contains(&lowercased_localpart))
|
||||||
.then_some((search_entry.dn, true))
|
.then_some((search_entry.dn, Some(true)))
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user