Merge branch 'dont-give-guests-admin' into 'next'

fix(accounts): don't give guests admin

See merge request famedly/conduit!591
This commit is contained in:
Timo Kösters 2024-03-04 17:00:14 +00:00
commit 57575b7c6f
3 changed files with 237 additions and 239 deletions

View file

@ -239,13 +239,22 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
// If this is the first real user, grant them admin privileges // If this is the first real user, grant them admin privileges
// Note: the server user, @conduit:servername, is generated first // Note: the server user, @conduit:servername, is generated first
if services().users.count()? == 2 { if !is_guest {
services() if let Some(admin_room) = services().admin.get_admin_room()? {
.admin if services()
.make_user_admin(&user_id, displayname) .rooms
.await?; .state_cache
.room_joined_count(&admin_room)?
== Some(1)
{
services()
.admin
.make_user_admin(&user_id, displayname)
.await?;
warn!("Granting {} admin privileges as the first user", user_id); warn!("Granting {} admin privileges as the first user", user_id);
}
}
} }
Ok(register::v3::Response { Ok(register::v3::Response {

View file

@ -23,7 +23,7 @@ use ruma::{
}, },
TimelineEventType, TimelineEventType,
}, },
EventId, OwnedRoomAliasId, RoomAliasId, RoomId, RoomVersionId, ServerName, UserId, EventId, OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId, RoomVersionId, ServerName, UserId,
}; };
use serde_json::value::to_raw_value; use serde_json::value::to_raw_value;
use tokio::sync::{mpsc, Mutex, MutexGuard}; use tokio::sync::{mpsc, Mutex, MutexGuard};
@ -214,60 +214,51 @@ impl Service {
let conduit_user = UserId::parse(format!("@conduit:{}", services().globals.server_name())) let conduit_user = UserId::parse(format!("@conduit:{}", services().globals.server_name()))
.expect("@conduit:server_name is valid"); .expect("@conduit:server_name is valid");
let conduit_room = services() if let Ok(Some(conduit_room)) = services().admin.get_admin_room() {
.rooms let send_message = |message: RoomMessageEventContent,
.alias mutex_lock: &MutexGuard<'_, ()>| {
.resolve_local_alias( services()
format!("#admins:{}", services().globals.server_name()) .rooms
.as_str() .timeline
.try_into() .build_and_append_pdu(
.expect("#admins:server_name is a valid room alias"), PduBuilder {
) event_type: TimelineEventType::RoomMessage,
.expect("Database data for admin room alias must be valid") content: to_raw_value(&message)
.expect("Admin room must exist"); .expect("event is valid, we just created it"),
unsigned: None,
state_key: None,
redacts: None,
},
&conduit_user,
&conduit_room,
mutex_lock,
)
.unwrap();
};
let send_message = |message: RoomMessageEventContent, mutex_lock: &MutexGuard<'_, ()>| { loop {
services() tokio::select! {
.rooms Some(event) = receiver.recv() => {
.timeline let message_content = match event {
.build_and_append_pdu( AdminRoomEvent::SendMessage(content) => content,
PduBuilder { AdminRoomEvent::ProcessMessage(room_message) => self.process_admin_message(room_message).await
event_type: TimelineEventType::RoomMessage, };
content: to_raw_value(&message)
.expect("event is valid, we just created it"),
unsigned: None,
state_key: None,
redacts: None,
},
&conduit_user,
&conduit_room,
mutex_lock,
)
.unwrap();
};
loop { let mutex_state = Arc::clone(
tokio::select! { services().globals
Some(event) = receiver.recv() => { .roomid_mutex_state
let message_content = match event { .write()
AdminRoomEvent::SendMessage(content) => content, .unwrap()
AdminRoomEvent::ProcessMessage(room_message) => self.process_admin_message(room_message).await .entry(conduit_room.to_owned())
}; .or_default(),
);
let mutex_state = Arc::clone( let state_lock = mutex_state.lock().await;
services().globals
.roomid_mutex_state
.write()
.unwrap()
.entry(conduit_room.to_owned())
.or_default(),
);
let state_lock = mutex_state.lock().await; send_message(message_content, &state_lock);
send_message(message_content, &state_lock); drop(state_lock);
}
drop(state_lock);
} }
} }
} }
@ -1105,6 +1096,21 @@ impl Service {
Ok(()) Ok(())
} }
/// Gets the room ID of the admin room
///
/// Errors are propagated from the database, and will have None if there is no admin room
pub(crate) fn get_admin_room(&self) -> Result<Option<OwnedRoomId>> {
let admin_room_alias: Box<RoomAliasId> =
format!("#admins:{}", services().globals.server_name())
.try_into()
.expect("#admins:server_name is a valid alias name");
services()
.rooms
.alias
.resolve_local_alias(&admin_room_alias)
}
/// Invite the user to the conduit admin room. /// Invite the user to the conduit admin room.
/// ///
/// In conduit, this is equivalent to granting admin privileges. /// In conduit, this is equivalent to granting admin privileges.
@ -1113,102 +1119,93 @@ impl Service {
user_id: &UserId, user_id: &UserId,
displayname: String, displayname: String,
) -> Result<()> { ) -> Result<()> {
let admin_room_alias: Box<RoomAliasId> = if let Some(room_id) = services().admin.get_admin_room()? {
format!("#admins:{}", services().globals.server_name()) let mutex_state = Arc::clone(
.try_into() services()
.expect("#admins:server_name is a valid alias name"); .globals
let room_id = services() .roomid_mutex_state
.rooms .write()
.alias .unwrap()
.resolve_local_alias(&admin_room_alias)? .entry(room_id.clone())
.expect("Admin room must exist"); .or_default(),
);
let state_lock = mutex_state.lock().await;
let mutex_state = Arc::clone( // Use the server user to grant the new admin's power level
services() let conduit_user =
.globals UserId::parse_with_server_name("conduit", services().globals.server_name())
.roomid_mutex_state .expect("@conduit:server_name is valid");
.write()
.unwrap()
.entry(room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
// Use the server user to grant the new admin's power level // Invite and join the real user
let conduit_user = services().rooms.timeline.build_and_append_pdu(
UserId::parse_with_server_name("conduit", services().globals.server_name()) PduBuilder {
.expect("@conduit:server_name is valid"); event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Invite,
displayname: None,
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
&conduit_user,
&room_id,
&state_lock,
)?;
services().rooms.timeline.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: Some(displayname),
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
user_id,
&room_id,
&state_lock,
)?;
// Invite and join the real user // Set power level
services().rooms.timeline.build_and_append_pdu( let mut users = BTreeMap::new();
PduBuilder { users.insert(conduit_user.to_owned(), 100.into());
event_type: TimelineEventType::RoomMember, users.insert(user_id.to_owned(), 100.into());
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Invite,
displayname: None,
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
&conduit_user,
&room_id,
&state_lock,
)?;
services().rooms.timeline.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: Some(displayname),
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
user_id,
&room_id,
&state_lock,
)?;
// Set power level services().rooms.timeline.build_and_append_pdu(
let mut users = BTreeMap::new(); PduBuilder {
users.insert(conduit_user.to_owned(), 100.into()); event_type: TimelineEventType::RoomPowerLevels,
users.insert(user_id.to_owned(), 100.into()); content: to_raw_value(&RoomPowerLevelsEventContent {
users,
..Default::default()
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&state_lock,
)?;
services().rooms.timeline.build_and_append_pdu( // Send welcome message
PduBuilder { services().rooms.timeline.build_and_append_pdu(
event_type: TimelineEventType::RoomPowerLevels,
content: to_raw_value(&RoomPowerLevelsEventContent {
users,
..Default::default()
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&state_lock,
)?;
// Send welcome message
services().rooms.timeline.build_and_append_pdu(
PduBuilder { PduBuilder {
event_type: TimelineEventType::RoomMessage, event_type: TimelineEventType::RoomMessage,
content: to_raw_value(&RoomMessageEventContent::text_html( content: to_raw_value(&RoomMessageEventContent::text_html(
@ -1224,7 +1221,7 @@ impl Service {
&room_id, &room_id,
&state_lock, &state_lock,
)?; )?;
}
Ok(()) Ok(())
} }
} }

View file

@ -28,7 +28,7 @@ use ruma::{
state_res, state_res,
state_res::{Event, RoomVersion}, state_res::{Event, RoomVersion},
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
OwnedServerName, RoomAliasId, RoomId, ServerName, UserId, OwnedServerName, RoomId, ServerName, UserId,
}; };
use serde::Deserialize; use serde::Deserialize;
use serde_json::value::{to_raw_value, RawValue as RawJsonValue}; use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
@ -448,12 +448,6 @@ impl Service {
.search .search
.index_pdu(shortroomid, &pdu_id, &body)?; .index_pdu(shortroomid, &pdu_id, &body)?;
let admin_room = services().rooms.alias.resolve_local_alias(
<&RoomAliasId>::try_from(
format!("#admins:{}", services().globals.server_name()).as_str(),
)
.expect("#admins:server_name is a valid room alias"),
)?;
let server_user = format!("@conduit:{}", services().globals.server_name()); let server_user = format!("@conduit:{}", services().globals.server_name());
let to_conduit = body.starts_with(&format!("{server_user}: ")) let to_conduit = body.starts_with(&format!("{server_user}: "))
@ -466,8 +460,10 @@ impl Service {
let from_conduit = pdu.sender == server_user let from_conduit = pdu.sender == server_user
&& services().globals.emergency_password().is_none(); && services().globals.emergency_password().is_none();
if to_conduit && !from_conduit && admin_room.as_ref() == Some(&pdu.room_id) { if let Some(admin_room) = services().admin.get_admin_room()? {
services().admin.process_message(body); if to_conduit && !from_conduit && admin_room == pdu.room_id {
services().admin.process_message(body);
}
} }
} }
} }
@ -820,89 +816,85 @@ impl Service {
let (pdu, pdu_json) = let (pdu, pdu_json) =
self.create_hash_and_sign_event(pdu_builder, sender, room_id, state_lock)?; self.create_hash_and_sign_event(pdu_builder, sender, room_id, state_lock)?;
let admin_room = services().rooms.alias.resolve_local_alias( if let Some(admin_room) = services().admin.get_admin_room()? {
<&RoomAliasId>::try_from( if admin_room == room_id {
format!("#admins:{}", services().globals.server_name()).as_str(), match pdu.event_type() {
) TimelineEventType::RoomEncryption => {
.expect("#admins:server_name is a valid room alias"), warn!("Encryption is not allowed in the admins room");
)?; return Err(Error::BadRequest(
if admin_room.filter(|v| v == room_id).is_some() { ErrorKind::Forbidden,
match pdu.event_type() { "Encryption is not allowed in the admins room.",
TimelineEventType::RoomEncryption => { ));
warn!("Encryption is not allowed in the admins room"); }
return Err(Error::BadRequest( TimelineEventType::RoomMember => {
ErrorKind::Forbidden, #[derive(Deserialize)]
"Encryption is not allowed in the admins room.", struct ExtractMembership {
)); membership: MembershipState,
}
let target = pdu
.state_key()
.filter(|v| v.starts_with('@'))
.unwrap_or(sender.as_str());
let server_name = services().globals.server_name();
let server_user = format!("@conduit:{}", server_name);
let content = serde_json::from_str::<ExtractMembership>(pdu.content.get())
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
if content.membership == MembershipState::Leave {
if target == server_user {
warn!("Conduit user cannot leave from admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Conduit user cannot leave from admins room.",
));
}
let count = services()
.rooms
.state_cache
.room_members(room_id)
.filter_map(|m| m.ok())
.filter(|m| m.server_name() == server_name)
.filter(|m| m != target)
.count();
if count < 2 {
warn!("Last admin cannot leave from admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Last admin cannot leave from admins room.",
));
}
}
if content.membership == MembershipState::Ban && pdu.state_key().is_some() {
if target == server_user {
warn!("Conduit user cannot be banned in admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Conduit user cannot be banned in admins room.",
));
}
let count = services()
.rooms
.state_cache
.room_members(room_id)
.filter_map(|m| m.ok())
.filter(|m| m.server_name() == server_name)
.filter(|m| m != target)
.count();
if count < 2 {
warn!("Last admin cannot be banned in admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Last admin cannot be banned in admins room.",
));
}
}
}
_ => {}
} }
TimelineEventType::RoomMember => {
#[derive(Deserialize)]
struct ExtractMembership {
membership: MembershipState,
}
let target = pdu
.state_key()
.filter(|v| v.starts_with('@'))
.unwrap_or(sender.as_str());
let server_name = services().globals.server_name();
let server_user = format!("@conduit:{}", server_name);
let content = serde_json::from_str::<ExtractMembership>(pdu.content.get())
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
if content.membership == MembershipState::Leave {
if target == server_user {
warn!("Conduit user cannot leave from admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Conduit user cannot leave from admins room.",
));
}
let count = services()
.rooms
.state_cache
.room_members(room_id)
.filter_map(|m| m.ok())
.filter(|m| m.server_name() == server_name)
.filter(|m| m != target)
.count();
if count < 2 {
warn!("Last admin cannot leave from admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Last admin cannot leave from admins room.",
));
}
}
if content.membership == MembershipState::Ban && pdu.state_key().is_some() {
if target == server_user {
warn!("Conduit user cannot be banned in admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Conduit user cannot be banned in admins room.",
));
}
let count = services()
.rooms
.state_cache
.room_members(room_id)
.filter_map(|m| m.ok())
.filter(|m| m.server_name() == server_name)
.filter(|m| m != target)
.count();
if count < 2 {
warn!("Last admin cannot be banned in admins room");
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Last admin cannot be banned in admins room.",
));
}
}
}
_ => {}
} }
} }