Skip to content
Snippets Groups Projects
membership.rs 52.6 KiB
Newer Older
  • Learn to ignore specific revisions
  •                     },
                    )
                    .await?;
    
                if let Some(signed_raw) = send_join_response.room_state.event {
                    let (signed_event_id, signed_value) =
                        match gen_event_id_canonical_json(&signed_raw, &room_version_id) {
                            Ok(t) => t,
                            Err(_) => {
                                // Event could not be converted to canonical json
                                return Err(Error::BadRequest(
                                    ErrorKind::InvalidParam,
                                    "Could not convert event to canonical json.",
                                ));
                            }
                        };
    
    
    Nyaaori's avatar
    Nyaaori committed
                    if signed_event_id != event_id {
    
                        return Err(Error::BadRequest(
                            ErrorKind::InvalidParam,
                            "Server sent event with wrong event id",
                        ));
                    }
    
                    drop(state_lock);
                    let pub_key_map = RwLock::new(BTreeMap::new());
                    services()
                        .rooms
                        .event_handler
                        .handle_incoming_pdu(
                            &remote_server,
                            &signed_event_id,
                            room_id,
                            signed_value,
                            true,
                            &pub_key_map,
                        )
                        .await?;
                } else {
                    return Err(error);
                }
            } else {
                return Err(error);
            }
        }
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
    
    async fn make_join_request(
        sender_user: &UserId,
        room_id: &RoomId,
        servers: &[OwnedServerName],
    ) -> Result<(
        federation::membership::prepare_join_event::v1::Response,
        OwnedServerName,
    )> {
        let mut make_join_response_and_server = Err(Error::BadServerResponse(
            "No server available to assist in joining.",
        ));
    
        for remote_server in servers {
            if remote_server == services().globals.server_name() {
                continue;
            }
    
            info!("Asking {remote_server} for make_join");
    
            let make_join_response = services()
                .sending
                .send_federation_request(
                    remote_server,
                    federation::membership::prepare_join_event::v1::Request {
    
    Jonas Platte's avatar
    Jonas Platte committed
                        room_id: room_id.to_owned(),
                        user_id: sender_user.to_owned(),
                        ver: services().globals.supported_room_versions(),
    
                    },
                )
                .await;
    
            make_join_response_and_server = make_join_response.map(|r| (r, remote_server.clone()));
    
            if make_join_response_and_server.is_ok() {
                break;
            }
        }
    
        make_join_response_and_server
    }
    
    
    fn validate_and_add_event_id(
    
    Jonas Platte's avatar
    Jonas Platte committed
        pdu: &RawJsonValue,
    
        room_version: &RoomVersionId,
    
        pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
    
    Timo Kösters's avatar
    Timo Kösters committed
    ) -> Result<(OwnedEventId, CanonicalJsonObject)> {
    
    Jonas Platte's avatar
    Jonas Platte committed
        let mut value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| {
    
            error!("Invalid PDU in server response: {:?}: {:?}", pdu, e);
    
            Error::BadServerResponse("Invalid PDU in server response")
        })?;
    
        let event_id = EventId::parse(format!(
    
            ruma::signatures::reference_hash(&value, room_version)
    
                .expect("ruma can calculate reference hashes")
        ))
        .expect("ruma's reference hashes are valid event ids");
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        let back_off = |id| match services()
            .globals
            .bad_event_ratelimiter
            .write()
            .unwrap()
            .entry(id)
        {
    
            Entry::Vacant(e) => {
                e.insert((Instant::now(), 1));
            }
            Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
        };
    
    
        if let Some((time, tries)) = services()
    
            .globals
            .bad_event_ratelimiter
            .read()
            .unwrap()
            .get(&event_id)
        {
            // Exponential backoff
            let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
            if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
                min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
            }
    
            if time.elapsed() < min_elapsed_duration {
                debug!("Backing off from {}", event_id);
                return Err(Error::BadServerResponse("bad event, still backing off"));
            }
        }
    
        if let Err(e) = ruma::signatures::verify_event(
            &*pub_key_map
                .read()
                .map_err(|_| Error::bad_database("RwLock is poisoned."))?,
            &value,
            room_version,
        ) {
    
            warn!("Event {} failed verification {:?} {}", event_id, pdu, e);
    
            return Err(Error::BadServerResponse("Event failed verification."));
        }
    
    
        value.insert(
            "event_id".to_owned(),
    
            CanonicalJsonValue::String(event_id.as_str().to_owned()),
    
    pub(crate) async fn invite_helper<'a>(
    
        sender_user: &UserId,
        user_id: &UserId,
        room_id: &RoomId,
    
        is_direct: bool,
    ) -> Result<()> {
    
        if user_id.server_name() != services().globals.server_name() {
    
            let (pdu, pdu_json, invite_room_state) = {
    
    Timo Kösters's avatar
    Timo Kösters committed
                let mutex_state = Arc::clone(
    
    Timo Kösters's avatar
    Timo Kösters committed
                    services()
                        .globals
    
    Timo Kösters's avatar
    Timo Kösters committed
                        .roomid_mutex_state
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .entry(room_id.to_owned())
    
    Timo Kösters's avatar
    Timo Kösters committed
                let state_lock = mutex_state.lock().await;
    
    Jonas Platte's avatar
    Jonas Platte committed
                let content = to_raw_value(&RoomMemberEventContent {
    
                    avatar_url: None,
                    displayname: None,
                    is_direct: Some(is_direct),
                    membership: MembershipState::Invite,
                    third_party_invite: None,
                    blurhash: None,
    
                    join_authorized_via_users_server: None,
    
                })
                .expect("member event is valid value");
    
    Timo Kösters's avatar
    Timo Kösters committed
                let (pdu, pdu_json) = services().rooms.timeline.create_hash_and_sign_event(
                    PduBuilder {
    
    Kévin Commaille's avatar
    Kévin Commaille committed
                        event_type: TimelineEventType::RoomMember,
    
    Timo Kösters's avatar
    Timo Kösters committed
                        content,
                        unsigned: None,
                        state_key: Some(user_id.to_string()),
                        redacts: None,
                    },
                    sender_user,
                    room_id,
                    &state_lock,
                )?;
    
                let invite_room_state = services().rooms.state.calculate_invite_state(&pdu)?;
    
    Timo Kösters's avatar
    Timo Kösters committed
                drop(state_lock);
    
                (pdu, pdu_json, invite_room_state)
    
    Jonas Platte's avatar
    Jonas Platte committed
            let room_version_id = services().rooms.state.get_room_version(room_id)?;
    
            let response = services()
    
                .sending
                .send_federation_request(
                    user_id.server_name(),
                    create_invite::v2::Request {
    
    Jonas Platte's avatar
    Jonas Platte committed
                        room_id: room_id.to_owned(),
    
    Nyaaori's avatar
    Nyaaori committed
                        event_id: (*pdu.event_id).to_owned(),
    
    Jonas Platte's avatar
    Jonas Platte committed
                        room_version: room_version_id.clone(),
                        event: PduEvent::convert_to_outgoing_federation_event(pdu_json.clone()),
                        invite_room_state,
    
                    },
                )
                .await?;
    
            let pub_key_map = RwLock::new(BTreeMap::new());
    
            // We do not add the event_id field to the pdu here because of signature and hashes checks
    
    Jonas Platte's avatar
    Jonas Platte committed
            let (event_id, value) = match gen_event_id_canonical_json(&response.event, &room_version_id)
    
                Ok(t) => t,
                Err(_) => {
                    // Event could not be converted to canonical json
                    return Err(Error::BadRequest(
                        ErrorKind::InvalidParam,
                        "Could not convert event to canonical json.",
                    ));
                }
            };
    
    
    Jonas Platte's avatar
    Jonas Platte committed
            if *pdu.event_id != *event_id {
    
                warn!("Server {} changed invite event, that's not allowed in the spec: ours: {:?}, theirs: {:?}", user_id.server_name(), pdu_json, value);
            }
    
    
    Timo Kösters's avatar
    Timo Kösters committed
            let origin: OwnedServerName = serde_json::from_value(
    
                serde_json::to_value(value.get("origin").ok_or(Error::BadRequest(
                    ErrorKind::InvalidParam,
                    "Event needs an origin field.",
                ))?)
                .expect("CanonicalJson is valid json value"),
            )
            .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Origin field is invalid."))?;
    
    
    Timo Kösters's avatar
    Timo Kösters committed
            let pdu_id: Vec<u8> = services()
                .rooms
                .event_handler
                .handle_incoming_pdu(&origin, &event_id, room_id, value, true, &pub_key_map)
                .await?
                .ok_or(Error::BadRequest(
                    ErrorKind::InvalidParam,
                    "Could not accept incoming PDU as timeline event.",
                ))?;
    
    Timo Kösters's avatar
    Timo Kösters committed
            // Bind to variable because of lifetimes
    
    Timo Kösters's avatar
    Timo Kösters committed
            let servers = services()
                .rooms
                .state_cache
    
                .room_servers(room_id)
                .filter_map(|r| r.ok())
    
                .filter(|server| &**server != services().globals.server_name());
    
            services().sending.send_pdu(servers, &pdu_id)?;
    
    Timo Kösters's avatar
    Timo Kösters committed
        if !services()
            .rooms
            .state_cache
    
    Nyaaori's avatar
    Nyaaori committed
            .is_joined(sender_user, room_id)?
    
    Timo Kösters's avatar
    Timo Kösters committed
        {
    
            return Err(Error::BadRequest(
                ErrorKind::Forbidden,
                "You don't have permission to view this room.",
            ));
        }
    
    
    Timo Kösters's avatar
    Timo Kösters committed
        let mutex_state = Arc::clone(
    
    Timo Kösters's avatar
    Timo Kösters committed
            services()
                .globals
    
    Timo Kösters's avatar
    Timo Kösters committed
                .roomid_mutex_state
    
    Jonas Platte's avatar
    Jonas Platte committed
                .entry(room_id.to_owned())
    
    Timo Kösters's avatar
    Timo Kösters committed
        let state_lock = mutex_state.lock().await;
    
    Timo Kösters's avatar
    Timo Kösters committed
        services().rooms.timeline.build_and_append_pdu(
    
            PduBuilder {
    
    Kévin Commaille's avatar
    Kévin Commaille committed
                event_type: TimelineEventType::RoomMember,
    
    Jonas Platte's avatar
    Jonas Platte committed
                content: to_raw_value(&RoomMemberEventContent {
                    membership: MembershipState::Invite,
    
                    displayname: services().users.displayname(user_id)?,
                    avatar_url: services().users.avatar_url(user_id)?,
    
                    is_direct: Some(is_direct),
    
                    third_party_invite: None,
    
                    blurhash: services().users.blurhash(user_id)?,
    
                    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,
            },
    
    Timo Kösters's avatar
    Timo Kösters committed
            &state_lock,
    
    Timo Kösters's avatar
    Timo Kösters committed
        drop(state_lock);
    
    // Make a user leave all their joined rooms
    pub async fn leave_all_rooms(user_id: &UserId) -> Result<()> {
        let all_rooms = services()
            .rooms
    
    Timo Kösters's avatar
    Timo Kösters committed
            .state_cache
    
            .rooms_joined(user_id)
    
    Timo Kösters's avatar
    Timo Kösters committed
            .chain(
                services()
                    .rooms
                    .state_cache
                    .rooms_invited(user_id)
                    .map(|t| t.map(|(r, _)| r)),
            )
    
            .collect::<Vec<_>>();
    
        for room_id in all_rooms {
            let room_id = match room_id {
                Ok(room_id) => room_id,
                Err(_) => continue,
            };
    
            let _ = leave_room(user_id, &room_id, None).await;
    
    pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<String>) -> Result<()> {
    
        // Ask a remote server if we don't have this room
    
    Timo Kösters's avatar
    Timo Kösters committed
        if !services().rooms.metadata.exists(room_id)?
            && room_id.server_name() != services().globals.server_name()
        {
    
            if let Err(e) = remote_leave_room(user_id, room_id).await {
                warn!("Failed to leave room {} remotely: {}", user_id, e);
                // Don't tell the client about this error
            }
    
    Timo Kösters's avatar
    Timo Kösters committed
            let last_state = services()
                .rooms
                .state_cache
    
                .invite_state(user_id, room_id)?
    
    Timo Kösters's avatar
    Timo Kösters committed
                .map_or_else(
                    || services().rooms.state_cache.left_state(user_id, room_id),
                    |s| Ok(Some(s)),
                )?;
    
            // We always drop the invite, we can't rely on other servers
            services().rooms.state_cache.update_membership(
                room_id,
                user_id,
                MembershipState::Leave,
                user_id,
                last_state,
                true,
            )?;
        } else {
            let mutex_state = Arc::clone(
    
    Timo Kösters's avatar
    Timo Kösters committed
                services()
                    .globals
    
                    .roomid_mutex_state
                    .write()
                    .unwrap()
                    .entry(room_id.to_owned())
                    .or_default(),
            );
            let state_lock = mutex_state.lock().await;
    
    
            let member_event = services().rooms.state_accessor.room_state_get(
                room_id,
                &StateEventType::RoomMember,
                user_id.as_str(),
            )?;
    
            // Fix for broken rooms
            let member_event = match member_event {
                None => {
                    error!("Trying to leave a room you are not a member of.");
    
                    services().rooms.state_cache.update_membership(
                        room_id,
                        user_id,
                        MembershipState::Leave,
                        user_id,
                        None,
                        true,
                    )?;
                    return Ok(());
                }
                Some(e) => e,
            };
    
            let mut event: RoomMemberEventContent = serde_json::from_str(member_event.content.get())
                .map_err(|_| Error::bad_database("Invalid member event in database."))?;
    
            event.membership = MembershipState::Leave;
    
            services().rooms.timeline.build_and_append_pdu(
                PduBuilder {
    
    Kévin Commaille's avatar
    Kévin Commaille committed
                    event_type: TimelineEventType::RoomMember,
    
                    content: to_raw_value(&event).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,
            )?;
    
    Timo Kösters's avatar
    Timo Kösters committed
    async fn remote_leave_room(user_id: &UserId, room_id: &RoomId) -> Result<()> {
    
        let mut make_leave_response_and_server = Err(Error::BadServerResponse(
            "No server available to assist in leaving.",
        ));
    
        let invite_state = services()
            .rooms
    
    Timo Kösters's avatar
    Timo Kösters committed
            .state_cache
    
            .invite_state(user_id, room_id)?
            .ok_or(Error::BadRequest(
                ErrorKind::BadState,
                "User is not invited.",
            ))?;
    
        let servers: HashSet<_> = invite_state
            .iter()
            .filter_map(|event| serde_json::from_str(event.json().get()).ok())
            .filter_map(|event: serde_json::Value| event.get("sender").cloned())
            .filter_map(|sender| sender.as_str().map(|s| s.to_owned()))
            .filter_map(|sender| UserId::parse(sender).ok())
            .map(|user| user.server_name().to_owned())
            .collect();
    
        for remote_server in servers {
            let make_leave_response = services()
                .sending
                .send_federation_request(
                    &remote_server,
    
    Jonas Platte's avatar
    Jonas Platte committed
                    federation::membership::prepare_leave_event::v1::Request {
                        room_id: room_id.to_owned(),
                        user_id: user_id.to_owned(),
                    },
    
            make_leave_response_and_server = make_leave_response.map(|r| (r, remote_server));
    
            if make_leave_response_and_server.is_ok() {
                break;
    
        let (make_leave_response, remote_server) = make_leave_response_and_server?;
    
        let room_version_id = match make_leave_response.room_version {
    
    Timo Kösters's avatar
    Timo Kösters committed
            Some(version)
                if services()
                    .globals
                    .supported_room_versions()
                    .contains(&version) =>
            {
                version
            }
    
            _ => return Err(Error::BadServerResponse("Room version is not supported")),
        };
    
    Timo Kösters's avatar
    Timo Kösters committed
        let mut leave_event_stub = serde_json::from_str::<CanonicalJsonObject>(
            make_leave_response.event.get(),
        )
        .map_err(|_| Error::BadServerResponse("Invalid make_leave event json received from server."))?;
    
        // TODO: Is origin needed?
        leave_event_stub.insert(
            "origin".to_owned(),
            CanonicalJsonValue::String(services().globals.server_name().as_str().to_owned()),
        );
        leave_event_stub.insert(
            "origin_server_ts".to_owned(),
            CanonicalJsonValue::Integer(
                utils::millis_since_unix_epoch()
                    .try_into()
                    .expect("Timestamp is valid js_int value"),
            ),
        );
        // We don't leave the event id in the pdu because that's only allowed in v1 or v2 rooms
        leave_event_stub.remove("event_id");
    
        // In order to create a compatible ref hash (EventID) the `hashes` field needs to be present
        ruma::signatures::hash_and_sign_event(
            services().globals.server_name().as_str(),
            services().globals.keypair(),
            &mut leave_event_stub,
            &room_version_id,
        )
        .expect("event is valid, we just created it");
    
        // Generate event id
        let event_id = EventId::parse(format!(
            "${}",
            ruma::signatures::reference_hash(&leave_event_stub, &room_version_id)
                .expect("ruma can calculate reference hashes")
        ))
        .expect("ruma's reference hashes are valid event ids");
    
        // Add event_id back
        leave_event_stub.insert(
            "event_id".to_owned(),
            CanonicalJsonValue::String(event_id.as_str().to_owned()),
        );
    
        // It has enough fields to be called a proper event now
        let leave_event = leave_event_stub;
    
    Timo Kösters's avatar
    Timo Kösters committed
        services()
            .sending
    
            .send_federation_request(
                &remote_server,
                federation::membership::create_leave_event::v2::Request {
    
    Jonas Platte's avatar
    Jonas Platte committed
                    room_id: room_id.to_owned(),
                    event_id,
                    pdu: PduEvent::convert_to_outgoing_federation_event(leave_event.clone()),