Skip to content
Snippets Groups Projects
membership.rs 38.8 KiB
Newer Older
  • Learn to ignore specific revisions
  •         .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).await;
    
    Timo Kösters's avatar
    Timo Kösters committed
    pub async fn leave_room(user_id: &UserId, room_id: &RoomId) -> 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 mut event: RoomMemberEventContent = serde_json::from_str(
    
    Timo Kösters's avatar
    Timo Kösters committed
                services()
                    .rooms
                    .state_accessor
                    .room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
    
                    .ok_or(Error::BadRequest(
                        ErrorKind::BadState,
                        "Cannot leave a room you are not a member of.",
                    ))?
                    .content
                    .get(),
            )
            .map_err(|_| Error::bad_database("Invalid member event in database."))?;
    
            event.membership = MembershipState::Leave;
    
            services().rooms.timeline.build_and_append_pdu(
                PduBuilder {
                    event_type: RoomEventType::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,
                    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 {
    
    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 {
                    room_id,
                    event_id: &event_id,
                    pdu: &PduEvent::convert_to_outgoing_federation_event(leave_event.clone()),
                },
            )
            .await?;