diff --git a/lib/Protocol/Matrix.pm b/lib/Protocol/Matrix.pm index b301e50ba..eb0316a02 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,36 @@ 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 + # 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}; + return; + } + if( $type eq 'm.room.power_levels' ) { + $new_content->{invite} = $old_content->{invite} if exists $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 + $new_content->{redacts} = $old_content->{redacts} if exists $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 +320,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 +341,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 +352,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 +382,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..ec8091445 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,35 @@ 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 ) = @_; + + $create_event or + return undef; + + my $room_version = $create_event->{content}{room_version}; + + # 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}; + } + + # 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 +128,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 +150,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..6f6f00fe1 100644 --- a/lib/SyTest/Federation/Room.pm +++ b/lib/SyTest/Federation/Room.pm @@ -180,13 +180,18 @@ sub create_initial_events $self->room_version eq "1" ? undef : $self->room_version ); + my $create_content = { + defined( $room_version ) ? ( room_version => $room_version ) : (), + }; + # 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 ); + $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..cc17d1b0c 100644 --- a/tests/30rooms/01state.pl +++ b/tests/30rooms/01state.pl @@ -32,9 +32,19 @@ $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}; + 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}"; + } return 1; });