diff --git a/src/api/server_server.rs b/src/api/server_server.rs index 13a6d648..7d4eccbc 100644 --- a/src/api/server_server.rs +++ b/src/api/server_server.rs @@ -23,6 +23,7 @@ use ruma::{ }, event::{get_event, get_missing_events, get_room_state, get_room_state_ids}, keys::{claim_keys, get_keys}, + knock::{create_knock_event_template, send_knock}, membership::{create_invite, create_join_event, prepare_join_event}, openid::get_openid_userinfo, query::{get_profile_information, get_room_information}, @@ -1402,14 +1403,16 @@ pub async fn create_join_event_template_route( .transpose()?; if let Some(join_rules_event_content) = join_rules_event_content { - if matches!( - join_rules_event_content.join_rule, - JoinRule::Restricted { .. } | JoinRule::KnockRestricted { .. } - ) { + if let JoinRule::Restricted { .. } = join_rules_event_content.join_rule { return Err(Error::BadRequest( ErrorKind::UnableToAuthorizeJoin, "Conduit does not support restricted rooms yet.", )); + } else if let JoinRule::KnockRestricted { .. } = join_rules_event_content.join_rule { + return Err(Error::BadRequest( + ErrorKind::UnableToAuthorizeJoin, + "This room is restricted but accepts knocking.", + )); } } @@ -1423,17 +1426,8 @@ pub async fn create_join_event_template_route( )); } - let content = to_raw_value(&RoomMemberEventContent { - avatar_url: None, - blurhash: None, - displayname: None, - is_direct: None, - membership: MembershipState::Join, - third_party_invite: None, - reason: None, - join_authorized_via_users_server: None, - }) - .expect("member event is valid value"); + let content = to_raw_value(&RoomMemberEventContent::new(MembershipState::Join)) + .expect("member event is valid value"); let (_pdu, mut pdu_json) = services().rooms.timeline.create_hash_and_sign_event( PduBuilder { @@ -1750,6 +1744,123 @@ pub async fn create_invite_route( }) } +/// # `GET /_matrix/federation/v1/make_knock/{roomId}/{userId}` +/// +/// Creates a knock template. +pub async fn create_knock_event_template_route( + body: Ruma, +) -> Result { + if !services().rooms.metadata.exists(&body.room_id)? { + return Err(Error::BadRequest( + ErrorKind::NotFound, + "Room is unknown to this server.", + )); + } + + let sender_servername = body + .sender_servername + .as_ref() + .expect("server is authenticated"); + + services() + .rooms + .event_handler + .acl_check(sender_servername, &body.room_id)?; + + let mutex_state = Arc::clone( + services() + .globals + .roomid_mutex_state + .write() + .await + .entry(body.room_id.to_owned()) + .or_default(), + ); + let state_lock = mutex_state.lock().await; + + // TODO: Conduit does not implement restricted join rules yet, we always reject + let join_rules_event = services().rooms.state_accessor.room_state_get( + &body.room_id, + &StateEventType::RoomJoinRules, + "", + )?; + + let join_rules_event_content: Option = join_rules_event + .as_ref() + .map(|join_rules_event| { + serde_json::from_str(join_rules_event.content.get()).map_err(|e| { + warn!("Invalid join rules event: {}", e); + Error::bad_database("Invalid join rules event in db.") + }) + }) + .transpose()?; + + // TODO: Return 403 if the user was banned + if let Some(join_rules_event_content) = join_rules_event_content { + if matches!( + join_rules_event_content.join_rule, + JoinRule::Restricted { .. } + ) { + return Err(Error::BadRequest( + ErrorKind::UnableToAuthorizeJoin, + "Conduit does not support restricted rooms yet.", + )); + } + } + + let room_version = services().rooms.state.get_room_version(&body.room_id)?; + if !body.ver.contains(&room_version) { + return Err(Error::BadRequest( + ErrorKind::IncompatibleRoomVersion { room_version }, + "Room version not supported.", + )); + } + + let content = to_raw_value(&RoomMemberEventContent::new(MembershipState::Knock)) + .expect("member event is valid value"); + + let (_pdu, mut pdu_json) = services().rooms.timeline.create_hash_and_sign_event( + PduBuilder { + event_type: TimelineEventType::RoomMember, + content, + unsigned: None, + state_key: Some(body.user_id.to_string()), + redacts: None, + }, + &body.user_id, + &body.room_id, + &state_lock, + )?; + + drop(state_lock); + + pdu_json.remove("event_id"); + + Ok(create_knock_event_template::v1::Response { + room_version, + event: to_raw_value(&pdu_json).expect("CanonicalJson can be serialized to JSON"), + }) +} + +/// # `PUT /_matrix/federation/v1/send_knock/{roomId}/{eventId}` +/// +/// Submits a signed knock event. +pub async fn create_knock_event_route( + body: Ruma, +) -> Result { + let sender_servername = body + .sender_servername + .as_ref() + .expect("server is authenticated"); + + let create_join_event::v1::RoomState { state, .. } = + create_join_event(sender_servername, &body.room_id, &body.pdu).await?; + + let knock_room_state = state.into_iter().map(Raw::from_json).collect(); + + Ok(send_knock::v1::Response { knock_room_state }) +} + /// # `GET /_matrix/federation/v1/user/devices/{userId}` /// /// Gets information on all devices of the user.