Commit Graph
61 Commits
Author SHA1 Message Date
Kegan DougalandClaude Fable 5.1 6e7672f595 C3: Serve MSC4430 member key rooms to clients
What a client sees of a member key room: every event names a member key as
its sender and, through MSC4428, as its stable ID; `member_info` resolves
the keys to users; the requester is recognised by their key wherever the
server decides what they may see or which events are theirs; and `/members`
and `/joined_members` are keyed by key. One homeserver only, but the code
answers for members of other servers too (a verified link names the user it
claims, anything else is nobody).

`member_info`. MSC4428's map, which only named erased users (the stable ID
of an ordinary room is the user ID and the entry is omitted), now gives
every member key an entry: the user behind a key we hold, the user named by
a verified link for another server's key, and `null` for an erased user or a
key nobody vouches for (an unverified claim is anyone's to make). The
resolution is one batched store lookup (`get_user_ids_for_identities`,
exposed by the member key handler as `user_ids_for_identities`), so the map
costs rooms without member keys nothing. The map already covers `/sync`
(full and incremental), sliding sync, `/members` and `/joined_members`,
which are keyed by state key and so by key; nothing there changes. An
erasure is recorded on the change stream under the stable ID that changed,
which in these rooms is the member's key, so the entry that flips to `null`
is the one clients hold.

Visibility. `filter_and_transform_events_for_client` decided what the
requester may see from their membership under their user ID, which the state
of a member key room does not hold: a joined user saw nothing. It now looks
the requester up under their key in each member key room the batch touches,
for both the allow check and the membership the event is annotated with, and
maps senders back to users, per room (a key is only a user in the room it
was looked up in), for the two per-user checks: erased senders, whose events
are pruned for users who joined later, so a key with no attributable user is
not erased; and ignored senders, where a member may be ignored as the user
behind their key or as the key itself, since a key with no attributable user
is the only handle a client has on it.
`filter_event_for_clients_with_state`, which push evaluation uses to decide
who may see an event, does the same per local user, for the ignore lists as
for the memberships, including for the out-of-band membership events it
returns to their target. The push evaluator itself resolves the sender (so a
user is not notified of their own events), the invitee and the inviter (so
an invite notifies its target, and the invitee's invite rules see the
inviter as a user, where one is attributable).

The requester's own events. The serializer echoes `transaction_id` and
`delay_id` only to the sender's own session, by comparing the sender with
the requester's user ID, which in a member key room never matches.
`SerializeEventConfig.requester_member_keys` (a new keyword-only argument,
`None` by default, so existing constructions are unchanged) carries the
requester's keys and the Rust serializer accepts a sender among them;
`create_config` fills it from the cached per-user keys when the feature is
on. Keys are unique across rooms, so a key of the requester's can only ever
be their own sender.

Sync. Where the handlers compared the requester's user ID with a sender or
a membership state key, they now use the identity the room knows them by
(`room_identity_for_user`: their key where they have one, their user ID
otherwise, or the cached map of their keys per room): the incremental sync
reads the requester's previous membership under their key, so a change to
their own membership (a display name) is an ordinary timeline event rather
than the room sent again as newly joined, and a leave is newly left; the
heroes of a nameless room no longer include the requester; the lazy-loading
full sync fetches the requester's own membership under their key; a leave
the user sent themselves is told from a kick by their keys per room, in
sync and in the sliding sync room lists; the join-race guard asks the
user-keyed membership table; and sliding sync's `$ME` is the requester's
key, including in the computation of which required state has changed
between requests. An invite's sender is resolved to a user (where
attributable) for the ignore list and the invite rules, in sync and in
both sliding sync room lists: a key on the ignore list hides the invite
too, and an unattributable key no longer reaches the invite rules, which
parse user IDs and would have failed the whole sync. The members handed to
presence and to device lists, by sync, by the device handler for newly
joined and left rooms, and by the room initial sync, are mapped to users
first, so clients are told about users' devices and presence, not keys'.
The three storage queries that find a user's membership changes in
`current_state_delta_stream` and `room_memberships` share one clause that,
with the feature on, also matches the keys we hold for the user, each
scoped to its room (keys are public strings anyone can name as a state key
elsewhere) and spelled out from the cached per-user keys so that every arm
of the clause can use the tables' indexes; a user with no keys, and the
feature being off, get the legacy clause byte for byte. Sliding sync's
"newly joined" and "newly left" detection, and the device handler's
shared-room tracking, run on those queries.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-15 09:22:39 +02:00
Kegan DougalandClaude Fable 5.1 6c5fccd908 C1: Persist MSC4430 member key membership keyed by user where the server needs a user
In an MSC4430 member key room the membership state keys and senders are
per-room Ed25519 keys, not user IDs. The room's own tables keep those keys
(`current_state_events`, `room_memberships`, the state and auth chains), but
several tables and caches are keyed by *user* and are what everything
downstream of persistence consults to answer "which rooms is this user in":
`local_current_membership`, the sliding sync membership snapshots,
`event_txn_id_device_id` (a foreign key to `devices`), `get_rooms_for_user`
and its relatives, and the membership stream cache that gates incremental
sync. This change fills those with the user behind each key we hold, on the
main process and on workers, and leaves rooms without member keys on exactly
the path they took before. The member key room version is not registered, so
none of it is reachable.

The local identity map. Storage cannot import the handler that owns identity
resolution for the rest of the server, so the storage half lives on the member
keys store: `MemberKeysWorkerStore.get_local_identity_map_txn(txn, room_id,
identities)` and its async counterpart `get_local_identity_map`. Both turn
room member identities into local user IDs: our users' IDs map to themselves,
member keys we hold *in that room* map to their user, and everything else
(other servers' users, other members' keys, and our own keys for other rooms)
is dropped. Member keys are only looked up when `msc4430_enabled` is on; off,
a member-key-shaped state key is as malformed as it always was and is dropped
as `is_mine_id` dropped it. The lookup is one batched query, and none at all
when no identity is a member key, so rooms without member keys cost nothing.
The map is built once per persisted batch at the top of `_persist_events_txn`
from every sender, every membership state key and every membership change in
the state delta, and threaded to `_persist_transaction_ids_txn`,
`_store_room_members_txn` and `_update_current_state_txn`; the only identities
not known until inside the transaction are the room's members when we are
leaving it, which `_update_current_state_txn` adds with a second lookup. The
partial-state resync path (`update_current_state`) builds its own map for its
delta. The async form serves `_calculate_sliding_sync_table_changes` and the
persistence controller, from the cached per-key lookups; keys never change
once created, so a cached answer can only be missing, never wrong.

What is keyed by user now. `_update_current_state_txn` writes
`local_current_membership` rows for the mapped users; the sliding sync
snapshot rows carry the mapped user (and the member key as `sender`, which is
what the room says); `_store_room_members_txn` does the same for out-of-band
memberships (remote invites and their rejections); `_persist_transaction_ids_txn`
stores the mapped sender, and `get_already_persisted_events` (the
persistence-time deduplication of a transaction ID) looks it up the same way,
so two racing sends of one transaction ID in a member key room still
collapse to one event; a sender the map does not know is stored as before,
so the foreign key makes a key that fails to map fail loudly rather than
silently drop the transaction ID; the persisted-events metric counts events
from our keys as local.

Caches and workers. Membership cache invalidations are keyed by the event's
state key, in `_invalidate_caches_for_event` (every event, and the events
stream for workers) and in `_invalidate_state_caches` (current state changes,
and the caches stream). In a member key room that key is the member key, so
the user-keyed caches `_invalidate_caches_for_event` touches
(`get_rooms_for_user`, `_get_rooms_for_local_user_where_membership_is_inner`,
`get_sliding_sync_rooms_for_user_from_membership_snapshots`,
`get_invited_rooms_for_local_user`, `did_forget`,
`get_forgotten_rooms_for_user` and the membership stream cache) are now also
invalidated for the user behind the key: once per membership event at the
event's own position, next to the per-event invalidation, and once from
`_update_current_state_txn` at the batch's position for members with no
event in the batch (a state reset, or everyone in the room when we leave it).
Workers get these as a row of their own on the caches stream
(`MEMBER_KEY_USERS_CACHE_NAME`, keys `[room_id, stream_ordering, *user_ids]`,
handled by `_invalidate_user_caches_for_member_keys`): the events stream rows
name the key and a worker cannot map it, and the current-state rows would
record the change at the caches stream's own position, which the membership
stream cache ignores when it is below the worker's starting event position
and misplaces when it is above. Only users whose state key is a key get these
rows; a user whose state key is their user ID has already had exactly these
invalidations, so rooms without member keys see no extra rows.
`_invalidate_state_caches` itself no longer parses member keys as user IDs: a
key names no host, so a membership change under one drops the
`is_host_joined` / `is_host_invited` entries for the whole room, which needs
those two caches to be tree caches keyed by room first. That is the one
change not gated on member keys: the caches change shape for every room (an
entry can be dropped by its room prefix), the alternative being to flush them
for every room on every change in a member key room. The replication client's
notifier wake-up on workers parsed the membership state key as a user ID,
which for a key raised and aborted the whole batch of replication rows
(nothing in that batch, in any room, woke a sync on that worker); it now
wakes the user behind one of our keys, and nobody for anyone else's.

Reads. `get_rooms_for_user` and `get_rooms_for_users` union
`local_current_membership` for local users when the flag is on; it is keyed
by user ID in every room and agrees with `current_state_events` in rooms
without member keys, so the union adds only the member key rooms. With the
flag off the legacy query runs byte for byte.
`get_membership_changes_for_user` also matches the user's member keys
(`room_memberships.user_id` is the state key) when the flag is on, scoped to
each event's room: keys are public strings, so a membership event in room B
naming the user's room A key is not a membership change of theirs. In the
persistence controller, extremity pruning groups senders by origin to drop
an old extremity sooner when the same origin sends again; in a member key
room a key is its own origin (the stricter reading), and the "never drop our
own events" walk uses the map rather than `is_mine_id`.
`_is_server_still_joined` maps the delta's member keys to users and passes
user IDs to `is_local_host_in_room_ignoring_users`, which queries
`local_current_membership`; before this the server would not have noticed
its last member leaving a member key room.

Remote member keys. Those reach `handle_potentially_left_users_txn` as
opaque identities (nothing in the map), where "do we still share a room with
them" is answered from `current_state_events` by state key and the
unsubscribe is a no-op delete plus one cache invalidation.

Run against the unchanged code, the tests show how an unadapted site fails:
thirteen of the fourteen persisting tests end in `MemberKeyHasNoDomainError`
(500) from `_invalidate_state_caches`, the first site on every path to parse
a member key as a user ID, which hides the ones behind it
(`_update_current_state_txn`, `_calculate_sliding_sync_table_changes`,
`_persist_transaction_ids_txn`, `_prune_extremities`,
`_is_server_still_joined`, the replication client, all adapted here). The one
silent failure was the out-of-band path in `_store_room_members_txn`:
`is_mine_id` answered False for the invitee's key and the invite was simply
never recorded for the user.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-15 09:22:39 +02:00
Jason LittleandPaul Chobert 7530874a12 Raise default room version to "12" (#20130)
Requires
* #19768 
* #19782 
* https://github.com/matrix-org/complement/pull/915

To meet the requirements for bumping Synapse to support [Matrix spec to
1.16](https://github.com/element-hq/synapse/issues/19414) the default
room version should be incremented to "12".

Other than the two separate Synapse PRs above(which are included here
but marked as "[diverted]" and should be removed prior to review) there
is only [one other real
change](https://github.com/element-hq/synapse/commit/c005e96c665b5a098a9b95d10f33faf224564be9)
to the code base itself to fix a `KeyError` during logging for a `/sync`
test against unknown room versions. Everything else should be on the
unit tests themselves.

Standard unit test running applies, should be nothing special to test
this outright.
`poetry run trial -jN tests` and similar for Postgresql.

Probably ok to review commit-by-commit

I took the liberty of writing a [room creating
helper](https://github.com/element-hq/synapse/commit/5ba81ad5d4a0a6787b7626bdb3293f633ea4097a)
for the two test series that try and test the sharding of the
`event_persister` workers. I'm not certain it stands up to scrutiny, but
at least does not do any funny mocking when producing room v12
appropriate room IDs.

I also took the liberty of writing an [assertion
helper](https://github.com/element-hq/synapse/pull/20130/commits/e408cef241d1b9c6a88f069eb5832de053790f9b)
for comparing lists of dicts for a select subset of keys/values. This is
used to compare stripped state selections while waiting on
https://github.com/element-hq/synapse/pull/19723 to be completed.

### Pull Request Checklist

<!-- Please read
https://element-hq.github.io/synapse/latest/development/contributing_guide.html
before submitting your pull request -->

* [x] Pull request is based on the develop branch
* [x] Pull request includes a [changelog
file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog).
The entry should:
- Be a short description of your change which makes sense to users.
"Fixed a bug that prevented receiving messages from other servers."
instead of "Moved X method from `EventStore` to `EventWorkerStore`.".
  - Use markdown where necessary, mostly for `code blocks`.
  - End with either a period (.) or an exclamation mark (!).
  - Start with a capital letter.
- Feel free to credit yourself, by adding a sentence "Contributed by
@github_username." or "Contributed by [Your Name]." to the end of the
entry.
* [x] [Code
style](https://element-hq.github.io/synapse/latest/code_style.html) is
correct (run the
[linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters))

---------

Co-authored-by: Paul Chobert <paul@chobert.fr>
2026-09-17 18:32:51 +00:00
Paul Chobert 8c11b13f63 Fix state events missing from MSC4222 state_after when the since token falls inside a persist batch (#20171)
This PR fixes the issue described as comment here:
https://github.com/element-hq/synapse/issues/18793#issuecomment-3502202379

In Element Call, this shows up as ghost participants: someone who left
the call keeps being displayed until a later state change refreshes the
room.

The bug is not specific to Element Call: any state event can be
affected, RTC membership just changes often enough to make it visible.

## What happens

Alice has a client syncing against a homeserver where events are
persisted by one worker (the event persister) and `/sync` is served by
another (the sync worker). Her client is parked in a long-poll: `GET
/sync?since=s99&timeout=30000`.

Bob joins a call at the same moment Carol sends a message. Carol's
message reaches the persister first; Bob's `m.call.member` arrives while
that write is still in flight, so the per-room persist queue groups them
into one transaction:

```
events (each gets its own stream ordering):
    stream_ordering 100:  m.room.message   Carol
    stream_ordering 101:  m.call.member    Bob        (state)

current_state_delta_stream (how state_after finds state changes):
    stream_id 100 ────►  (m.call.member, @bob) -> $bob_join_call
          ▲
          └─ stamped with the batch MINIMUM (100), not the event's own 101
              (see `_update_current_state_txn`)
```

The transaction commits: both events and the delta row are now in the
database, atomically.

The persister then announces the new events over replication, one RDATA
token per stream ordering — rows are only merged into one token when
they share a position, and 100 and 101 don't. So the sync worker's
events-stream position steps 99 → 100 → 101, and on reaching 100 it
pokes the notifier.

Alice's long-poll wakes at exactly that moment. Her response is built at
the worker's *current* position — `end = 100` — with RDATA 101 still in
the queue:

```
Sync A  (since=99, end=100):
  timeline:     events   99 < ordering ≤ 100  →  [Carol's message]
  state_after:  deltas   99 < stream_id ≤ 100 →  [$bob_join_call]  ← delivered EARLY
  next_batch:   s100                                               ← mid-batch token
```

No race on the client's side is needed: the server *hands out* the
mid-batch token as `next_batch`. Alice's client re-polls with it, as
every sync client does. The worker has meanwhile processed RDATA 101:

```
Sync B  (since=100, end=101):
  timeline:     events   100 < ordering ≤ 101  →  [Bob's m.call.member @101]  ✓
  state_after:  deltas   100 < stream_id ≤ 101 →  []     row is stamped 100   ✗
```

A state event in the timeline with an empty `state_after`. An MSC4222
client trusts `state_after` over timeline state events, so Alice's copy
of Bob's call membership never updates from this response.

On a single process this cannot happen: the batch's stream IDs are
released as a whole, so the position visible to `/sync` jumps 99 → 101
and `s100` is never handed out. Only a process that learns its position
from replication — any sync worker — ticks through the middle of a
batch.


### Pull Request Checklist

<!-- Please read
https://element-hq.github.io/synapse/latest/development/contributing_guide.html
before submitting your pull request -->

* [x] Pull request is based on the develop branch
* [x] Pull request includes a [changelog
file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog).
The entry should:
- Be a short description of your change which makes sense to users.
"Fixed a bug that prevented receiving messages from other servers."
instead of "Moved X method from `EventStore` to `EventWorkerStore`.".
  - Use markdown where necessary, mostly for `code blocks`.
  - End with either a period (.) or an exclamation mark (!).
  - Start with a capital letter.
- Feel free to credit yourself, by adding a sentence "Contributed by
@github_username." or "Contributed by [Your Name]." to the end of the
entry.
* [x] [Code
style](https://element-hq.github.io/synapse/latest/code_style.html) is
correct (run the
[linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters))
2026-09-16 11:07:25 +02:00
ff034c1f62 Fix flakey test failing with "twisted.protocols.amp.TooLong" (#19832)
This was all done through claude.

## Fix flaky `test_edu_large_messages_not_splitting_one_user` (`TooLong`
under `trial -jN`)

  ### Problem

  The "Build .deb packages" CI step intermittently failed with:

  ```
  twisted.protocols.amp.TooLong
...in
tests.rest.client.test_sendtodevice.SendToDeviceTestCase.test_edu_large_messages_not_splitting_one_user
  ```

The deb build runs the suite with `twisted.trial -j2`. In that mode,
worker log events are shipped to the manager process over Twisted's AMP
protocol, which encodes each value with a 2-byte length prefix — so any
single log line of **64 KiB or more** raises `TooLong`.

  ### Root cause

Not a bug in the to-device/EDU logic, and **not** debug logging enabled
by the build (it runs at the default `ERROR` level). It's a **log-level
leak between tests sharing a `-j2` worker**:

- `tests/logging/test_loggers.py::ExplicitlyConfiguredLoggerTestCase`
calls `root_logger.setLevel(logging.DEBUG)` directly and never restores
it (no `setUp`/`tearDown`/`addCleanup`).
- When that test runs before
`test_edu_large_messages_not_splitting_one_user` **in the same worker
process**, the root logger is left at `DEBUG`.
- That test deliberately builds an EDU of exactly `SOFT_MAX_EDU_SIZE -
1` (65 535) bytes. Storing it triggers `synapse/storage/database.py`'s
`[SQL values]` DEBUG log, which dumps the full query params — producing
a **65 708-byte**
  line that overflows AMP's cap.
  
It looks "flaky" purely because of `-j2` scheduling: whether the two
tests land on the same worker, and in what order.
  
  ### Fix

  Three commits:

1. **Restore the root logger level in
`ExplicitlyConfiguredLoggerTestCase`** — `addCleanup` to put the level
back. Fixes the root cause.
2. **Truncate oversized log lines in the test log handler**
(`ToTwistedHandler.emit`) — caps lines at 1000 chars so no debug line
can break `trial -jN`, regardless of which query is logged (defense in
depth).
3. **Truncate values in `[SQL values]` debug logging** — caps the logged
param repr at 1000 chars (guarded by `isEnabledFor(DEBUG)` to keep the
hot path lazy). Keeps production debug logs sane too.

  ### Testing

- The reproduction (`trial -j2 tests.logging.test_loggers <the EDU
test>`) went from **~3/8 failing** to **10/10 passing**.
- Confirmed the root level is restored after the logging tests, and that
the `[SQL values]` line is now capped (~50 KB with a `[truncated]`
marker, was 65 708).
- `ruff` + `mypy` clean; `tests.logging.test_loggers` and
`tests.rest.client.test_sendtodevice` pass (14/14).

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Eric Eastwood <erice@element.io>
2026-06-10 17:33:21 +01:00
Erik JohnstonandEric Eastwood 9e2a076144 Port Event class to Rust (#19701)
Ports the event class to Rust.

The main difference here are:
1. There is now a single event class
2. We now validate a lot more at event construction time than we
previously did (we basically checked nothing before). This required some
changes to the tests, including
https://github.com/matrix-org/sytest/pull/1423

Reviewable commit-by-commit.

### Overview of Event Rust structure

The format of the event struct in Rust is quite different than that in
Python.

The top-level looks like:

```rust
pub struct Event {
    /// The parsed event JSON.
    fields: FormattedEvent,

    /// The event ID. For format v1 this is read directly from the JSON;
    /// for v2+ it is computed from the canonical-JSON hash at
    /// construction time and cached here.
    event_id: Arc<str>,

    /// Synapse-internal per-event state that lives outside the federated
    /// JSON (e.g. outlier flag, soft-failure, stream positions).
    #[pyo3(get)]
    internal_metadata: EventInternalMetadata,

    /// The room version this event was parsed for.
    #[pyo3(get)]
    room_version: &'static RoomVersion,

    /// `None` for accepted events; otherwise a short reason set by auth
    /// when the event was rejected.
    rejected_reason: Option<Box<str>>,
}
```

which includes the actual parsed event in `FormattedEvent`, plus the
rest of the event metadata.

```rust
pub struct FormattedEvent<E = Arc<EventFormatEnum>> {
    #[serde(default)]
    pub signatures: Signatures,

    #[serde(default)]
    pub unsigned: Unsigned,

    #[serde(flatten)]
    pub specific_fields: E,

    #[serde(flatten)]
    pub common_fields: Arc<EventCommonFields>,
}
```

The struct is further split into the common fields, format specific
fields, plus the signatures and unsigned. We split out the signature and
unsigned fields as they are mutable, so when we clone the event we can
still share the common and specific fields and only copy signature and
unsigned.

The `specific_fields` are the fields that depend on the format version.
They can either be a specific format (e.g. `E = EventFormatV1`) or a
type-erased enum `EventFormatEnum` that is across all room versions:

```rust
pub enum EventFormatEnum {
    V1(EventFormatV1),
    V2V3(EventFormatV2V3),
    V4(EventFormatV4),
    VMSC4242(EventFormatVMSC4242),
}
```

For example:

```rust
/// Shared flat-list encoding of `auth_events` and `prev_events`, reused
/// by every format from v2/v3 onwards.
#[derive(Serialize, Deserialize)]
pub struct SimpleAuthPrevEvents {
    pub auth_events: Vec<String>,
    pub prev_events: Vec<String>,
}

/// Version-specific fields for room versions 3-10.
#[derive(Serialize, Deserialize)]
pub struct EventFormatV2V3 {
    pub room_id: Box<str>,
    #[serde(flatten)]
    pub auth_prev_events: SimpleAuthPrevEvents,
}
```


### Dev notes

As discussed in
[`#element-backend-internal:matrix.org`](https://matrix.to/#/!SGNQGPGUwtcPBUotTL:matrix.org/$3gTjDO440GbAz57cXcCawwiyFLiD0crrarvS1uhzKOY?via=jki.re&via=element.io&via=matrix.org)

---------

Co-authored-by: Eric Eastwood <erice@element.io>
2026-06-02 11:05:38 +01:00
Erik Johnston ff55aff5b2 Fix up event-construction in tests ahead of the Rust event port (#19781)
When we port the `Event` class to Rust, the constructor will check for
the existence of required fields. To support that, we tidy up the test
code where we construct fake events to add all the required fields.

There should be no behavioural changes.

Review commit-by-commit.
2026-05-15 10:12:42 +01:00
Erik Johnston c430c16df4 Port event content to Rust (#19725)
Based on #19708.

This is on the path to porting the entire event class to Rust, as
`event.content` will then return the new Rust class `JsonObject`.

This PR adds a pure Rust `JsonObject` class that is a `Mapping`
representing a json-style object. It uses `serde_json::Value` as its
in-memory representation and `pythonize` for conversion when a field is
looked up on the object.

I'm not thrilled with the name, but couldn't think of a better one.

This also adds `JsonObject` handling to the JSON serialisation functions
we use, as well as to the `freeze(..)` function.

Reviewable commit-by-commit.
2026-05-08 14:19:03 +01:00
Olivier 'reivilibre 52c05c5ca4 Introduce spam_checker_spammy internal event metadata. (#19453)
Follows: #19365

Part of: MSC4354 Sticky Events (experimental feature #19409)

This PR introduces a `spam_checker_spammy` flag, analogous to
`policy_server_spammy`, as an explicit flag
that an event was decided to be spammy by a spam-checker module.

The original Sticky Events PR (#18968) just reused
`policy_server_spammy`, but it didn't sit right with me
because we (at least appear to be experimenting with features that)
allow users to opt-in to seeing
`policy_server_spammy` events (presumably for moderation purposes).

Keeping these flags separate felt best, therefore.

As for why we need this flag: soon soft-failed status won't be
permanent, at least for sticky events.
The spam checker modules currently work by making events soft-failed.
We want to prevent spammy events from getting
reconsidered/un-soft-failed, so it seems like we need
a flag to track spam-checker spamminess *separately* from soft-failed.

Should be commit-by-commit friendly, but is also small.

---------

Signed-off-by: Olivier 'reivilibre <oliverw@matrix.org>
2026-04-15 16:53:23 +01:00
Andrew Ferrazzutti fcac7e0282 Write union types as X | Y where possible (#19111)
aka PEP 604, added in Python 3.10
2025-11-06 14:02:33 -06:00
Andrew Ferrazzutti fc244bb592 Use type hinting generics in standard collections (#19046)
aka PEP 585, added in Python 3.9

 - https://peps.python.org/pep-0585/
 - https://docs.astral.sh/ruff/rules/non-pep585-annotation/
2025-10-22 16:48:19 -05:00
Eric Eastwood 5a9ca1e3d9 Introduce Clock.call_when_running(...) to include logcontext by default (#18944)
Introduce `Clock.call_when_running(...)` to wrap startup code in a
logcontext, ensuring we can identify which server generated the logs.

Background:

>  Ideally, nothing from the Synapse homeserver would be logged against the `sentinel` 
>  logcontext as we want to know which server the logs came from. In practice, this is not 
>  always the case yet especially outside of request handling. 
>   
>  Global things outside of Synapse (e.g. Twisted reactor code) should run in the 
>  `sentinel` logcontext. It's only when it calls into application code that a logcontext 
>  gets activated. This means the reactor should be started in the `sentinel` logcontext, 
>  and any time an awaitable yields control back to the reactor, it should reset the 
>  logcontext to be the `sentinel` logcontext. This is important to avoid leaking the 
>  current logcontext to the reactor (which would then get picked up and associated with 
>  the next thing the reactor does). 
>
> *-- `docs/log_contexts.md`

Also adds a lint to prefer `Clock.call_when_running(...)` over
`reactor.callWhenRunning(...)`

Part of https://github.com/element-hq/synapse/issues/18905
2025-09-22 10:27:59 -05:00
Travis Ralston 5b8b45a16d Allow admins to see policy server-flagged events (#18585) 2025-07-29 19:57:33 +01:00
9d43bec326 Bump ruff from 0.7.3 to 0.11.10 (#18451)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Andrew Morgan <andrew@amorgan.xyz>
Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
2025-05-20 15:23:30 +01:00
Andrew MorganandEric Eastwood fd5d3d852d Don't check the at_hash (access token hash) in OIDC ID Tokens if we don't use the access token (#18374)
Co-authored-by: Eric Eastwood <erice@element.io>
2025-05-02 12:16:14 +01:00
Will Hunt d17295e5c3 Store hashes of media files, and allow quarantining by hash. (#18277)
This PR makes a few radical changes to media. This now stores the SHA256
hash of each file stored in the database (excluding thumbnails, more on
that later). If a set of media is quarantined, any additional uploads of
the same file contents or any other files with the same hash will be
quarantined at the same time.

Currently this does NOT:
 - De-duplicate media, although a future extension could be to do that.
- Run any background jobs to identify the hashes of older files. This
could also be a future extension, though the value of doing so is
limited to combat the abuse of recent media.
- Hash thumbnails. It's assumed that thumbnails are parented to some
form of media, so you'd likely be wanting to quarantine the media and
the thumbnail at the same time.
2025-03-27 17:26:34 +00:00
Andrew Morgan 57efc8c03e Add media tests for a CMYK JPEG image (#17786) 2024-10-23 18:26:01 +01:00
Quentin Gliech 7d52ce7d4b Format files with Ruff (#17643)
I thought ruff check would also format, but it doesn't.

This runs ruff format in CI and dev scripts. The first commit is just a
run of `ruff format .` in the root directory.
2024-09-02 12:39:04 +01:00
Eric Eastwood 22aeb78b77 Add rooms.required_state to Sliding Sync /sync (#17342)
Also handles excluding rooms with partial state when people are asking for room membership events unless it's `$LAZY` room membership.
2024-07-04 12:25:36 -05:00
Erik Johnston 23740eaa3d Correctly mention previous copyright (#16820)
During the migration the automated script to update the copyright
headers accidentally got rid of some of the existing copyright lines.
Reinstate them.
2024-01-23 11:26:48 +00:00
Patrick Cloke 8e1e62c9e0 Update license headers 2023-11-21 15:29:58 -05:00
Patrick Cloke a8a46b1336 Replace simple_async_mock with AsyncMock (#16180)
Python 3.8 has a native AsyncMock, use it instead of a custom
implementation.
2023-08-25 09:27:21 -04:00
Patrick Cloke daf11e26ef Replace make_awaitable with AsyncMock (#16179)
Python 3.8 provides a native AsyncMock, we can replace the
homegrown version we have.
2023-08-24 19:38:46 -04:00
Eric Eastwood 30a5076da8 Log when events are (unexpectedly) filtered out of responses in tests (#14213)
See https://github.com/matrix-org/synapse/pull/14095#discussion_r990335492

This is useful because when see that a relevant event is an `outlier` or `soft-failed`, then that's a good unexpected indicator explaining why it's not showing up. `filter_events_for_client` is used in `/sync`, `/messages`, `/context` which are all common end-to-end assertion touch points (also notifications, relations).
2023-06-01 21:27:18 -05:00
Quentin Gliech 14a5be9c4d Handle errors when introspecting tokens
This returns a proper 503 when the introspection endpoint is not working
for some reason, which should avoid logging out clients in those cases.
2023-05-30 09:43:06 -04:00
Eric Eastwood 1903c7e5ed Remove duplicate timestamp from test logs (_trial_temp/test.log) (#15636)
Fix https://github.com/matrix-org/synapse/issues/15618

### Before

```
2023-05-17 22:51:36-0500 [-] 2023-05-17 22:51:36,889 - synapse.server - 338 - INFO - sentinel - Finished setting up.
```

### After

```
2023-05-19 18:16:20-0500 [-] synapse.server - 338 - INFO - sentinel - Finished setting up.
```


### Dev notes

The `Twisted.Logger` controls the `2023-05-19 18:16:20-0500 [-]` prefix, see : [`twisted/twisted` -> `src/twisted/logger/_format.py#L362-L374`](https://github.com/twisted/twisted/blob/34b161e66bc7c9f9efbb95e82c770a863933e498/src/twisted/logger/_format.py#L362-L374)

And we delegate our logs to the Twisted Logger for the tests which puts it in `_trial_temp/test.log`
2023-05-22 13:49:01 -05:00
David Robertson c071cd5a0e Ensure fed-sender catchup does not block for full state (#15248)
* Reproduce bad scenario in test
* Avoid catchup optimisation for partial state rooms
2023-03-13 12:31:19 +00:00
ShayandErik Johnston 03bccd542b Add a class UnpersistedEventContext to allow for the batching up of storing state groups (#14675)
* add class UnpersistedEventContext

* modify create new client event to create unpersistedeventcontexts

* persist event contexts after creation

* fix tests to persist unpersisted event contexts

* cleanup

* misc lints + cleanup

* changelog + fix comments

* lints

* fix batch insertion?

* reduce redundant calculation

* add unpersisted event classes

* rework compute_event_context, split into function that returns unpersisted event context and then persists it

* use calculate_context_info to create unpersisted event contexts

* update typing

* $%#^&*

* black

* fix comments and consolidate classes, use attr.s for class

* requested changes

* lint

* requested changes

* requested changes

* refactor to be stupidly explicit

* clearer renaming and flow

* make partial state non-optional

* update docstrings

---------

Co-authored-by: Erik Johnston <erik@matrix.org>
2023-02-09 13:05:02 -08:00
Patrick Cloke 30509a1010 Add more missing type hints to tests. (#15028) 2023-02-08 16:29:49 -05:00
Patrick Cloke 8e9fc28c6a Reload the pyo3-log config when the Python logging config changes. (#14976)
Since pyo3-log is initialized very early in the Python start-up
it caches the state of the loggers before they're fully initialized
(and thus are essentially disabled). Whenever we reload the
logging configuration we now also tell pyo3-log to discard
any cached logging configuration it has; it will refetch the
current logging configuration from Python at the next point
it logs.

This fixes Rust log lines not appearing in the homeserver logs.
2023-02-03 08:27:31 -05:00
Quentin Gliech cc3a52b33d Support OIDC backchannel logouts (#11414)
If configured an OIDC IdP can log a user's session out of
Synapse when they log out of the identity provider.

The IdP sends a request directly to Synapse (and must be
configured with an endpoint) when a user logs out.
2022-10-31 13:07:30 -04:00
Quentin Gliech 9192d74b0b Refactor OIDC tests to better mimic an actual OIDC provider. (#13910)
This implements a fake OIDC server, which intercepts calls to the HTTP client.
Improves accuracy of tests by covering more internal methods.

One particular example was the ID token validation, which previously mocked.

This uncovered an incorrect dependency: Synapse actually requires at least
authlib 0.15.1, not 0.14.0.
2022-10-25 14:25:02 +00:00
Erik Johnston 1e453053cb Rename storage classes (#12913) 2022-05-31 12:17:50 +00:00
David Robertson 6463244375 Remove unused # type: ignores (#12531)
Over time we've begun to use newer versions of mypy, typeshed, stub
packages---and of course we've improved our own annotations. This makes
some type ignore comments no longer necessary. I have removed them.

There was one exception: a module that imports `select.epoll`. The
ignore is redundant on Linux, but I've kept it ignored for those of us
who work on the source tree using not-Linux. (#11771)

I'm more interested in the config line which enforces this. I want
unused ignores to be reported, because I think it's useful feedback when
annotating to know when you've fixed a problem you had to previously
ignore.

* Installing extras before typechecking

Lacking an easy way to install all extras generically, let's bite the bullet and
make install the hand-maintained `all` extra before typechecking.

Now that https://github.com/matrix-org/backend-meta/pull/6 is merged to
the release/v1 branch.
2022-04-27 14:03:44 +01:00
Richard van der Hoff e24ff8ebe3 Remove HomeServer.get_datastore() (#12031)
The presence of this method was confusing, and mostly present for backwards
compatibility. Let's get rid of it.

Part of #11733
2022-02-23 11:04:02 +00:00
David Robertson 88a78c6577 Cache empty responses from /user/devices (#11587)
If we've never made a request to a remote homeserver, we should cache the response---even if the response is "this user has no devices".
2022-01-05 13:33:28 +00:00
Patrick Cloke bfb4b858a9 Create a constant for a small png image in tests. (#10834)
To avoid duplicating it between a few tests.
2021-09-16 12:01:14 -04:00
Jonathan de Jong 93729719b8 Use inline type hints in tests/ (#10350)
This PR is tantamount to running:

    python3.8 -m com2ann -v 6 tests/

(com2ann requires python 3.8 to run)
2021-07-13 11:52:58 +01:00
Jonathan de Jong 4b965c862d Remove redundant "coding: utf-8" lines (#9786)
Part of #9744

Removes all redundant `# -*- coding: utf-8 -*-` lines from files, as python 3 automatically reads source code as utf-8 now.

`Signed-off-by: Jonathan de Jong <jonathan@automatia.nl>`
2021-04-14 15:34:27 +01:00
Dan Callahan 1d5f0e3529 Bump black configuration to target py36 (#9781)
Signed-off-by: Dan Callahan <danc@element.io>
2021-04-13 10:41:34 +01:00
Patrick Cloke 0b3112123d Use mock from the stdlib. (#9772) 2021-04-09 13:44:38 -04:00
Patrick Cloke 7fdc6cefb3 Fix additional type hints. (#9543)
Type hint fixes due to Twisted 21.2.0 adding type hints.
2021-03-09 07:41:32 -05:00
Richard van der Hoff 02070c69fa Fix bugs in handling clientRedirectUrl, and improve OIDC tests (#9127, #9128)
* Factor out a common TestHtmlParser

Looks like I'm doing this in a few different places.

* Improve OIDC login test

Complete the OIDC login flow, rather than giving up halfway through.

* Ensure that OIDC login works with multiple OIDC providers

* Fix bugs in handling clientRedirectUrl

 - don't drop duplicate query-params, or params with no value
 - allow utf-8 in query-params
2021-01-18 14:52:49 +00:00
Richard van der Hoff 01333681bc Preparatory refactoring of the SamlHandlerTestCase (#8938)
* move simple_async_mock to test_utils

... so that it can be re-used

* Remove references to `SamlHandler._map_saml_response_to_user` from tests

This method is going away, so we can no longer use it as a test point. Instead,
factor out a higher-level method which takes a SAML object, and verify correct
behaviour by mocking out `AuthHandler.complete_sso_login`.

* changelog
2020-12-15 20:56:10 +00:00
Patrick Cloke 1619802228 Various clean-ups to the logging context code (#8935) 2020-12-14 14:19:47 -05:00
Richard van der Hoff 76469898ee Factor out FakeResponse from test_oidc 2020-12-02 18:30:29 +00:00
Patrick Cloke fd7c743445 Fail test cases if they fail to await all awaitables (#8690) 2020-10-30 07:15:07 -04:00
Dan Callahan aff1eb7c67 Tell Black to format code for Python 3.5 (#8664)
This allows trailing commas in multi-line arg lists.

Minor, but we might as well keep our formatting current with regard to
our minimum supported Python version.

Signed-off-by: Dan Callahan <danc@element.io>
2020-10-27 23:26:36 +00:00
Jonathan de Jong 7c407efdc8 Update test logging to be able to accept braces (#8335) 2020-09-18 07:56:40 -04:00
Erik Johnston fe8ed1b46f Make StreamToken.room_key be a RoomStreamToken instance. (#8281) 2020-09-11 12:22:55 +01:00