From 7b81908a380569fe666d392a5137fd00806e32ac Mon Sep 17 00:00:00 2001 From: FrenchGithubUser Date: Tue, 14 Apr 2026 16:51:44 +0200 Subject: [PATCH 1/6] feat: update tests to pass with room v11 --- .github/workflows/pipeline.yml | 2 +- lib/Protocol/Matrix.pm | 42 ++++++++++++++++++++++++----- lib/SyTest/Federation/AuthChecks.pm | 29 +++++++++++++++++--- lib/SyTest/Federation/Datastore.pm | 13 ++++----- lib/SyTest/Federation/Protocol.pm | 7 ++--- lib/SyTest/Federation/Room.pm | 11 +++++--- tests/30rooms/01state.pl | 9 ++++--- 7 files changed, 87 insertions(+), 26 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index ca8a4d3c9..946446096 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -83,7 +83,7 @@ jobs: if [[ -z "$BRANCH_NAME" || $BRANCH_NAME =~ ^refs/pull/.* ]]; then continue fi - (wget -O - "https://github.com/element-hq/synapse/archive/$BRANCH_NAME.tar.gz" \ + (wget -O - "https://github.com/famedly/synapse/archive/$BRANCH_NAME.tar.gz" \ | tar -xz --strip-components=1 -C /src/) \ && echo "Successfully downloaded and extracted $BRANCH_NAME.tar.gz" \ && break diff --git a/lib/Protocol/Matrix.pm b/lib/Protocol/Matrix.pm index b301e50ba..1629c4756 100644 --- a/lib/Protocol/Matrix.pm +++ b/lib/Protocol/Matrix.pm @@ -266,11 +266,13 @@ my %ALLOWED_CONTENT_BY_TYPE = ( sub redact_event { - my ( $event ) = @_; + my ( $event, $room_version ) = @_; + $room_version //= 1; defined( my $type = $event->{type} ) or croak "Event requires a 'type'"; + delete $event->{redacts}; my $old_content = delete $event->{content}; my $old_unsigned = delete $event->{unsigned}; @@ -278,6 +280,32 @@ sub redact_event my $new_content = $event->{content} = {}; + # Room version 11+ uses updated redaction rules: + # - m.room.create: entire content property is preserved + # - m.room.power_levels: 'invite' is also preserved + # - m.room.member: 'third_party_invite.signed' is also preserved + # - m.room.redaction: 'redacts' is also preserved + if( defined $room_version and $room_version =~ /\A[0-9]+\z/ and $room_version >= 11 ) { + if( $type eq 'm.room.create' ) { + %$new_content = %{ $old_content // {} }; + $event->{unsigned}{age_ts} = $old_unsigned->{age_ts} if exists $old_unsigned->{age_ts}; + return; + } + if( $type eq 'm.room.power_levels' ) { + exists $old_content->{invite} and $new_content->{invite} = $old_content->{invite}; + } + if( $type eq 'm.room.member' ) { + my $tpi = $old_content->{third_party_invite}; + if( ref $tpi eq 'HASH' && exists $tpi->{signed} ) { + $new_content->{third_party_invite} = { signed => $tpi->{signed} }; + } + } + if( $type eq 'm.room.redaction' ) { + # In v11, 'redacts' moved into content; preserve content.redacts + exists $old_content->{redacts} and $new_content->{redacts} = $old_content->{redacts}; + } + } + if( my $allowed_content_keys = $ALLOWED_CONTENT_BY_TYPE{$type} ) { exists $old_content->{$_} and $new_content->{$_} = $old_content->{$_} for @$allowed_content_keys; @@ -288,8 +316,8 @@ sub redact_event sub redacted_event { - my ( $event ) = @_; - redact_event( $event = { %$event } ); + my ( $event, $room_version ) = @_; + redact_event( $event = { %$event }, $room_version ); return $event; } @@ -309,6 +337,7 @@ sub sign_event_json my $origin = $args{origin} or croak "Require an 'origin'"; my $key_id = $args{key_id} or croak "Require a 'key_id'"; + my $room_version = delete $args{room_version}; # 'hashes' records the original unredacted version { @@ -319,7 +348,7 @@ sub sign_event_json } # Signature is of redacted version - sign_json( my $signed = redacted_event( $event ), %args ); + sign_json( my $signed = redacted_event( $event, $room_version ), %args ); $event->{signatures} = $signed->{signatures}; } @@ -349,9 +378,10 @@ sub signed_event_json sub verify_event_json_signature { - my ( $event, @args ) = @_; + my ( $event, %args ) = @_; - verify_json_signature( redacted_event( $event ), @args ); + my $room_version = delete $args{room_version}; + verify_json_signature( redacted_event( $event, $room_version ), %args ); } =head1 AUTHOR diff --git a/lib/SyTest/Federation/AuthChecks.pm b/lib/SyTest/Federation/AuthChecks.pm index 52c9cad78..d436e2c73 100644 --- a/lib/SyTest/Federation/AuthChecks.pm +++ b/lib/SyTest/Federation/AuthChecks.pm @@ -48,9 +48,12 @@ sub auth_check_event $accepted_events, $event->{auth_events}, "m.room.create" ); + my $room_creator = _creator_for_create_event( $create_event ); + return 0 unless defined $room_creator; + { users => { - $create_event->{content}{creator} => 100, + $room_creator => 100, }, users_default => 0, @@ -86,6 +89,24 @@ sub auth_check_event return 1; } +sub _creator_for_create_event +{ + my ( $create_event ) = @_; + + $create_event or + return undef; + + my $room_version = $create_event->{content}{room_version}; + + # For room version 11+, 'creator' is absent from content, we use sender. + if( defined $room_version and $room_version =~ /\A[0-9]+\z/ and $room_version >= 11 ) { + return $create_event->{sender}; + } + + # For older room versions, 'creator' must be present explicitly. + return $create_event->{content}{creator}; +} + sub auth_check_event_m_room_create { my $self = shift; @@ -96,7 +117,8 @@ sub auth_check_event_m_room_create return 0; # Any m.room.create event is acceptable, provided that the creator matches - return $event->{sender} eq $event->{content}{creator}; + my $creator = _creator_for_create_event( $event ); + return defined( $creator ) and $event->{sender} eq $creator; } sub auth_check_event_m_room_member @@ -117,7 +139,8 @@ sub auth_check_event_m_room_member $accepted_events, $event->{auth_events}, "m.room.create" ); - if( $create_event and $event->{state_key} eq $create_event->{content}{creator} ) { + my $creator = _creator_for_create_event( $create_event ); + if( $creator and $event->{state_key} eq $creator ) { return 1; } diff --git a/lib/SyTest/Federation/Datastore.pm b/lib/SyTest/Federation/Datastore.pm index 6f6d4e0bd..0cab891dc 100644 --- a/lib/SyTest/Federation/Datastore.pm +++ b/lib/SyTest/Federation/Datastore.pm @@ -77,12 +77,13 @@ the C key. sub sign_event { my $self = shift; - my ( $event ) = @_; + my ( $event, %args ) = @_; sign_event_json( $event, - secret_key => $self->secret_key, - origin => $self->server_name, - key_id => $self->key_id, + secret_key => $self->secret_key, + origin => $self->server_name, + key_id => $self->key_id, + room_version => $args{room_version}, ); } @@ -204,12 +205,12 @@ sub create_event $event_id = $self->next_event_id( $event_id_suffix ); $event->{event_id} = $event_id; } - $self->sign_event( $event ); + $self->sign_event( $event, room_version => $room_version ); } else { die "event with explicit event_id in room v$room_version" if defined $event_id; - $self->sign_event( $event ); + $self->sign_event( $event, room_version => $room_version ); $event_id = id_for_event( $event, $room_version ); } diff --git a/lib/SyTest/Federation/Protocol.pm b/lib/SyTest/Federation/Protocol.pm index 1f8daf2af..36c5d852f 100644 --- a/lib/SyTest/Federation/Protocol.pm +++ b/lib/SyTest/Federation/Protocol.pm @@ -44,9 +44,10 @@ Calculates the reference hash of an event. sub hash_event { - my ( $event ) = @_; + my ( $event, $room_version ) = @_; + $room_version //= 1; croak "Require an event" unless ref $event eq 'HASH'; - my $redacted = redacted_event( $event ); + my $redacted = redacted_event( $event, $room_version ); delete $redacted->{signatures}; delete $redacted->{age_ts}; delete $redacted->{unsigned}; @@ -76,7 +77,7 @@ sub id_for_event return $event_id; } - my $event_hash = hash_event( $event ); + my $event_hash = hash_event( $event, $room_version ); # room v3 uses the unpadded-base64-encoded hash if( $room_version eq '3' ) { diff --git a/lib/SyTest/Federation/Room.pm b/lib/SyTest/Federation/Room.pm index f13aae5b1..ef8b97008 100644 --- a/lib/SyTest/Federation/Room.pm +++ b/lib/SyTest/Federation/Room.pm @@ -180,13 +180,16 @@ sub create_initial_events $self->room_version eq "1" ? undef : $self->room_version ); + # For room version 11+, 'creator' is absent from content, we use sender. + my $create_content = { + defined( $room_version ) ? ( room_version => $room_version ) : (), + }; + $create_content->{creator} = $creator if !defined( $room_version ) || $room_version < 11; + $self->create_and_insert_event( type => "m.room.create", - content => { - creator => $creator, - defined( $room_version ) ? ( room_version => $room_version ) : (), - }, + content => $create_content, sender => $creator, state_key => "", ); diff --git a/tests/30rooms/01state.pl b/tests/30rooms/01state.pl index 01bfc274a..be7662b48 100644 --- a/tests/30rooms/01state.pl +++ b/tests/30rooms/01state.pl @@ -32,9 +32,12 @@ $event->{sender} eq $user->user_id or die "Expected user_id to be ${\$user->user_id}"; - assert_json_keys( my $content = $event->{content}, qw( creator )); - $content->{creator} eq $user->user_id or - die "Expected creator to be ${\$user->user_id}"; + my $content = $event->{content}; + # For room version 11+, 'creator' is absent from content, we use sender. + if( exists $content->{creator} ) { + $content->{creator} eq $user->user_id or + die "Expected creator to be ${\$user->user_id}"; + } return 1; }); From e3ad3656411b29ef33172763779bdad57efa464b Mon Sep 17 00:00:00 2001 From: FrenchGithubUser Date: Tue, 16 Jun 2026 15:28:00 +0200 Subject: [PATCH 2/6] address review comments --- lib/Protocol/Matrix.pm | 13 +++++++++---- lib/SyTest/Federation/AuthChecks.pm | 10 ++++++++++ lib/SyTest/Federation/Room.pm | 5 +++-- tests/30rooms/01state.pl | 11 +++++++++-- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/lib/Protocol/Matrix.pm b/lib/Protocol/Matrix.pm index 1629c4756..66770f7ae 100644 --- a/lib/Protocol/Matrix.pm +++ b/lib/Protocol/Matrix.pm @@ -280,19 +280,24 @@ sub redact_event my $new_content = $event->{content} = {}; + $old_content //= {}; + $old_unsigned //= {}; + # Room version 11+ uses updated redaction rules: # - m.room.create: entire content property is preserved # - m.room.power_levels: 'invite' is also preserved # - m.room.member: 'third_party_invite.signed' is also preserved # - m.room.redaction: 'redacts' is also preserved - if( defined $room_version and $room_version =~ /\A[0-9]+\z/ and $room_version >= 11 ) { + # The regex /\A[0-9]+\z/ ignores room versions that are not comprised only of digits + # (e.g. custom non-numeric versions used in some federation tests). + if( $room_version =~ /\A[0-9]+\z/ and $room_version >= 11 ) { if( $type eq 'm.room.create' ) { - %$new_content = %{ $old_content // {} }; + %$new_content = %$old_content; $event->{unsigned}{age_ts} = $old_unsigned->{age_ts} if exists $old_unsigned->{age_ts}; return; } if( $type eq 'm.room.power_levels' ) { - exists $old_content->{invite} and $new_content->{invite} = $old_content->{invite}; + $new_content->{invite} = $old_content->{invite} if exists $old_content->{invite}; } if( $type eq 'm.room.member' ) { my $tpi = $old_content->{third_party_invite}; @@ -302,7 +307,7 @@ sub redact_event } if( $type eq 'm.room.redaction' ) { # In v11, 'redacts' moved into content; preserve content.redacts - exists $old_content->{redacts} and $new_content->{redacts} = $old_content->{redacts}; + $new_content->{redacts} = $old_content->{redacts} if exists $old_content->{redacts}; } } diff --git a/lib/SyTest/Federation/AuthChecks.pm b/lib/SyTest/Federation/AuthChecks.pm index d436e2c73..de1c391d0 100644 --- a/lib/SyTest/Federation/AuthChecks.pm +++ b/lib/SyTest/Federation/AuthChecks.pm @@ -89,6 +89,16 @@ sub auth_check_event return 1; } +=head2 _creator_for_create_event + + my $creator = _creator_for_create_event( $create_event ) + +Returns the creator MXID for a given C event. For room +version 11+, the creator is taken from the C field; for older +versions it is taken from C. + +=cut + sub _creator_for_create_event { my ( $create_event ) = @_; diff --git a/lib/SyTest/Federation/Room.pm b/lib/SyTest/Federation/Room.pm index ef8b97008..94a59106f 100644 --- a/lib/SyTest/Federation/Room.pm +++ b/lib/SyTest/Federation/Room.pm @@ -180,11 +180,12 @@ sub create_initial_events $self->room_version eq "1" ? undef : $self->room_version ); - # For room version 11+, 'creator' is absent from content, we use sender. my $create_content = { defined( $room_version ) ? ( room_version => $room_version ) : (), }; - $create_content->{creator} = $creator if !defined( $room_version ) || $room_version < 11; + # Default to old 'creator' field unless room version is a numeric integer >= 11. + $create_content->{creator} = $creator + unless defined( $room_version ) && $room_version =~ /\A[0-9]+\z/ && $room_version >= 11; $self->create_and_insert_event( type => "m.room.create", diff --git a/tests/30rooms/01state.pl b/tests/30rooms/01state.pl index be7662b48..b9457dad7 100644 --- a/tests/30rooms/01state.pl +++ b/tests/30rooms/01state.pl @@ -33,8 +33,15 @@ die "Expected user_id to be ${\$user->user_id}"; my $content = $event->{content}; - # For room version 11+, 'creator' is absent from content, we use sender. - if( exists $content->{creator} ) { + my $room_version = $content->{room_version} // "1"; + if( $room_version =~ /\A[0-9]+\z/ && $room_version >= 11 ) { + # Room version 11+: 'creator' must be absent from content. + exists $content->{creator} and + die "Expected no 'creator' key in content for room version $room_version"; + } else { + # Older room versions: 'creator' must be present and match sender. + exists $content->{creator} or + die "Expected 'creator' key in content for room version $room_version"; $content->{creator} eq $user->user_id or die "Expected creator to be ${\$user->user_id}"; } From c4c8bada53664ee6dc5759fd4760ecda22b6878a Mon Sep 17 00:00:00 2001 From: FrenchGithubUser Date: Tue, 16 Jun 2026 16:16:55 +0200 Subject: [PATCH 3/6] address last comment --- lib/Protocol/Matrix.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Protocol/Matrix.pm b/lib/Protocol/Matrix.pm index 66770f7ae..3478cd289 100644 --- a/lib/Protocol/Matrix.pm +++ b/lib/Protocol/Matrix.pm @@ -290,6 +290,7 @@ sub redact_event # - m.room.redaction: 'redacts' is also preserved # The regex /\A[0-9]+\z/ ignores room versions that are not comprised only of digits # (e.g. custom non-numeric versions used in some federation tests). + # (e.g. unstable room versions). if( $room_version =~ /\A[0-9]+\z/ and $room_version >= 11 ) { if( $type eq 'm.room.create' ) { %$new_content = %$old_content; From 36d23ea9947d9ca7789b523931f2523d2cb7912c Mon Sep 17 00:00:00 2001 From: FrenchGithubUser Date: Tue, 16 Jun 2026 16:17:30 +0200 Subject: [PATCH 4/6] revert synapse repo link to element-hq --- .github/workflows/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 946446096..ca8a4d3c9 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -83,7 +83,7 @@ jobs: if [[ -z "$BRANCH_NAME" || $BRANCH_NAME =~ ^refs/pull/.* ]]; then continue fi - (wget -O - "https://github.com/famedly/synapse/archive/$BRANCH_NAME.tar.gz" \ + (wget -O - "https://github.com/element-hq/synapse/archive/$BRANCH_NAME.tar.gz" \ | tar -xz --strip-components=1 -C /src/) \ && echo "Successfully downloaded and extracted $BRANCH_NAME.tar.gz" \ && break From 6e7c0af5dc9ff0789cbe9f1052ebec1495e1dde5 Mon Sep 17 00:00:00 2001 From: FrenchGithubUser Date: Tue, 16 Jun 2026 17:06:18 +0200 Subject: [PATCH 5/6] address last comment --- lib/Protocol/Matrix.pm | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/Protocol/Matrix.pm b/lib/Protocol/Matrix.pm index 3478cd289..eb0316a02 100644 --- a/lib/Protocol/Matrix.pm +++ b/lib/Protocol/Matrix.pm @@ -288,10 +288,8 @@ sub redact_event # - m.room.power_levels: 'invite' is also preserved # - m.room.member: 'third_party_invite.signed' is also preserved # - m.room.redaction: 'redacts' is also preserved - # The regex /\A[0-9]+\z/ ignores room versions that are not comprised only of digits - # (e.g. custom non-numeric versions used in some federation tests). - # (e.g. unstable room versions). - if( $room_version =~ /\A[0-9]+\z/ and $room_version >= 11 ) { + # Non-numeric (unstable) room versions are assumed to be 11+ + if( $room_version !~ /\A[0-9]+\z/ or $room_version >= 11 ) { if( $type eq 'm.room.create' ) { %$new_content = %$old_content; $event->{unsigned}{age_ts} = $old_unsigned->{age_ts} if exists $old_unsigned->{age_ts}; From dac580ceb6df6c4306f472e3c153ea97e7639326 Mon Sep 17 00:00:00 2001 From: FrenchGithubUser Date: Tue, 16 Jun 2026 17:32:53 +0200 Subject: [PATCH 6/6] non-numeric room version check update in remaining places --- lib/SyTest/Federation/AuthChecks.pm | 5 +++-- lib/SyTest/Federation/Room.pm | 5 +++-- tests/30rooms/01state.pl | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/SyTest/Federation/AuthChecks.pm b/lib/SyTest/Federation/AuthChecks.pm index de1c391d0..ec8091445 100644 --- a/lib/SyTest/Federation/AuthChecks.pm +++ b/lib/SyTest/Federation/AuthChecks.pm @@ -108,8 +108,9 @@ sub _creator_for_create_event my $room_version = $create_event->{content}{room_version}; - # For room version 11+, 'creator' is absent from content, we use sender. - if( defined $room_version and $room_version =~ /\A[0-9]+\z/ and $room_version >= 11 ) { + # For room version 11+ (or unstable/non-numeric versions), 'creator' is absent + # from content; use sender instead. + if( defined $room_version && ( $room_version !~ /\A[0-9]+\z/ || $room_version >= 11 ) ) { return $create_event->{sender}; } diff --git a/lib/SyTest/Federation/Room.pm b/lib/SyTest/Federation/Room.pm index 94a59106f..6f6f00fe1 100644 --- a/lib/SyTest/Federation/Room.pm +++ b/lib/SyTest/Federation/Room.pm @@ -183,9 +183,10 @@ sub create_initial_events my $create_content = { defined( $room_version ) ? ( room_version => $room_version ) : (), }; - # Default to old 'creator' field unless room version is a numeric integer >= 11. + # Default to old 'creator' field if no room version is specified, or room version is + # a numeric value <11. Non-numeric (unstable) versions are treated as 11+. $create_content->{creator} = $creator - unless defined( $room_version ) && $room_version =~ /\A[0-9]+\z/ && $room_version >= 11; + unless defined( $room_version ) && ( $room_version !~ /\A[0-9]+\z/ || $room_version >= 11 ); $self->create_and_insert_event( type => "m.room.create", diff --git a/tests/30rooms/01state.pl b/tests/30rooms/01state.pl index b9457dad7..cc17d1b0c 100644 --- a/tests/30rooms/01state.pl +++ b/tests/30rooms/01state.pl @@ -34,7 +34,7 @@ my $content = $event->{content}; my $room_version = $content->{room_version} // "1"; - if( $room_version =~ /\A[0-9]+\z/ && $room_version >= 11 ) { + if( $room_version !~ /\A[0-9]+\z/ || $room_version >= 11 ) { # Room version 11+: 'creator' must be absent from content. exists $content->{creator} and die "Expected no 'creator' key in content for room version $room_version";