Skip to content
Snippets Groups Projects
membership.rs 37.4 KiB
Newer Older
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,
                federation::membership::prepare_leave_event::v1::Request { room_id, user_id },
            )
            .await;
        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 {
        Some(version) if services().globals.supported_room_versions().contains(&version) => version,
        _ => return Err(Error::BadServerResponse("Room version is not supported")),
    };
    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;
    services().sending
        .send_federation_request(
            &remote_server,
            federation::membership::create_leave_event::v2::Request {
                room_id,
                event_id: &event_id,
                pdu: &PduEvent::convert_to_outgoing_federation_event(leave_event.clone()),
            },
        )
        .await?;