From a399e215e7f6057400e99640e3058d986d75dc49 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 5 Dec 2019 17:48:04 +0000 Subject: [PATCH 01/10] Add inbound test for v2 /send_join --- tests/50federation/30room-join.pl | 121 ++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/tests/50federation/30room-join.pl b/tests/50federation/30room-join.pl index ecd35df51..753c370b9 100644 --- a/tests/50federation/30room-join.pl +++ b/tests/50federation/30room-join.pl @@ -327,6 +327,127 @@ sub assert_is_valid_pdu { }; + +test "Inbound federation supports v2 /send_join", + requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER, + local_user_and_room_fixtures( room_opts => { room_version => "1" } ), + federation_user_id_fixture() ], + + do => sub { + my ( $outbound_client, $inbound_server, $creator, $room_id, $user_id ) = @_; + my $first_home_server = $creator->server_name; + + my $local_server_name = $outbound_client->server_name; + my $datastore = $inbound_server->datastore; + + $outbound_client->do_request_json( + method => "GET", + hostname => $first_home_server, + uri => "/v1/make_join/$room_id/$user_id", + )->then( sub { + my ( $body ) = @_; + log_if_fail "make_join body", $body; + + assert_json_keys( $body, qw( event )); + + my $protoevent = $body->{event}; + + assert_json_keys( $protoevent, qw( + auth_events content depth room_id sender state_key type + )); + + assert_json_nonempty_list( my $auth_events = $protoevent->{auth_events} ); + foreach my $auth_event ( @$auth_events ) { + assert_json_list( $auth_event ); + @$auth_event == 2 or + die "Expected auth_event list element to have 2 members"; + + assert_json_string( $auth_event->[0] ); # id + assert_json_object( $auth_event->[1] ); # hashes + } + + assert_json_nonempty_list( $protoevent->{prev_events} ); + + assert_json_number( $protoevent->{depth} ); + + $protoevent->{room_id} eq $room_id or + die "Expected 'room_id' to be $room_id"; + $protoevent->{sender} eq $user_id or + die "Expected 'sender' to be $user_id"; + $protoevent->{state_key} eq $user_id or + die "Expected 'state_key' to be $user_id"; + $protoevent->{type} eq "m.room.member" or + die "Expected 'type' to be 'm.room.member'"; + + assert_json_keys( my $content = $protoevent->{content}, qw( membership ) ); + $content->{membership} eq "join" or + die "Expected 'membership' to be 'join'"; + + my %event = ( + ( map { $_ => $protoevent->{$_} } qw( + auth_events content depth prev_events room_id sender + state_key type ) ), + + event_id => $datastore->next_event_id, + origin => $local_server_name, + origin_server_ts => $inbound_server->time_ms, + ); + + $datastore->sign_event( \%event ); + + $outbound_client->do_request_json( + method => "PUT", + hostname => $first_home_server, + uri => "/v2/send_join/$room_id/$event{event_id}", + + content => \%event, + ) + })->then( sub { + my ( $response ) = @_; + + assert_json_keys( $response, qw( auth_chain state )); + + assert_json_nonempty_list( $response->{auth_chain} ); + my @auth_chain = @{ $response->{auth_chain} }; + + log_if_fail "Auth chain", \@auth_chain; + + foreach my $event ( @auth_chain ) { + assert_is_valid_pdu( $event ); + $event->{room_id} eq $room_id or + die "Expected auth_event room_id to be $room_id"; + } + + # Annoyingly, the "auth chain" isn't specified to arrive in any + # particular order. We'll have to keep walking it incrementally. + + my %accepted_authevents; + while( @auth_chain ) { + my @accepted = extract_by { + $inbound_server->auth_check_event( $_, \%accepted_authevents ) + } @auth_chain; + + unless( @accepted ) { + log_if_fail "Unacceptable auth chain", \@auth_chain; + + die "Unable to find any more acceptable auth_chain events"; + } + + $accepted_authevents{$_->{event_id}} = $_ for @accepted; + } + + assert_json_nonempty_list( $response->{state} ); + my %state = partition_by { $_->{type} } @{ $response->{state} }; + + log_if_fail "State", \%state; + + # TODO: lots more checking. Requires spec though + Future->done(1); + }); + }; + + + test "Inbound /v1/make_join rejects remote attempts to join local users to rooms", requires => [ $main::OUTBOUND_CLIENT, local_user_fixture(), From ee9ff0861a0ee60c20bef1bf7cb5bcfb21485f1c Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 5 Dec 2019 17:49:32 +0000 Subject: [PATCH 02/10] Add support for versioning endpoints in await request pairs --- lib/SyTest/Federation/Server.pm | 36 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/SyTest/Federation/Server.pm b/lib/SyTest/Federation/Server.pm index 21ab68c1f..d53bd8f5b 100644 --- a/lib/SyTest/Federation/Server.pm +++ b/lib/SyTest/Federation/Server.pm @@ -315,10 +315,10 @@ sub on_request_federation_v1_send_join sub mk_await_request_pair { my $class = shift; - my ( $shortname, $paramnames ) = @_; + my ( $shortname, $versionprefix, $paramnames ) = @_; my @paramnames = @$paramnames; - my $okey = "awaiting_$shortname"; + my $okey = "awaiting_$versionprefix\_$shortname"; my $awaitfunc = sub { my $self = shift; @@ -336,7 +336,9 @@ sub mk_await_request_pair }); }; - my $was_on_requestfunc = $class->can( "on_request_federation_v1_$shortname" ); + my $was_on_requestfunc = $class->can( + "on_request_federation_$versionprefix\_$shortname" + ); my $on_requestfunc = sub { my $self = shift; my ( $req, @pathvalues ) = @_; @@ -380,59 +382,59 @@ sub mk_await_request_pair } __PACKAGE__->mk_await_request_pair( - query_directory => [qw( ?room_alias )], + query_directory => "v1", [qw( ?room_alias )], ); __PACKAGE__->mk_await_request_pair( - query_profile => [qw( ?user_id )], + query_profile => "v1", [qw( ?user_id )], ); __PACKAGE__->mk_await_request_pair( - make_join => [qw( :room_id :user_id )], + make_join => "v1", [qw( :room_id :user_id )], ); __PACKAGE__->mk_await_request_pair( - make_leave => [qw( :room_id :user_id )], + make_leave => "v1", [qw( :room_id :user_id )], ); __PACKAGE__->mk_await_request_pair( - send_join => [qw( :room_id )], + send_join => "v1", [qw( :room_id )], ); __PACKAGE__->mk_await_request_pair( - state_ids => [qw( :room_id ?event_id )], + state_ids => "v1", [qw( :room_id ?event_id )], ); __PACKAGE__->mk_await_request_pair( - state => [qw( :room_id )], + state => "v1", [qw( :room_id )], ); __PACKAGE__->mk_await_request_pair( - get_missing_events => [qw( :room_id )], + get_missing_events => "v1", [qw( :room_id )], ); __PACKAGE__->mk_await_request_pair( - event_auth => [qw( :room_id :event_id )], + event_auth => "v1", [qw( :room_id :event_id )], ); __PACKAGE__->mk_await_request_pair( - backfill => [qw( :room_id )], + backfill => "v1", [qw( :room_id )], ); __PACKAGE__->mk_await_request_pair( - invite => [qw( :room_id )], + invite => "v1", [qw( :room_id )], ); __PACKAGE__->mk_await_request_pair( - event => [qw( :event_id )], + event => "v1", [qw( :event_id )], ); __PACKAGE__->mk_await_request_pair( - user_devices => [qw( :user_id )], + user_devices => "v1", [qw( :user_id )], ); __PACKAGE__->mk_await_request_pair( - user_keys_query => [qw( )], + user_keys_query => "v1", [qw( )], ); sub on_request_federation_v1_send From 1dd753803236534b0ac409e8792702cef3a74ea5 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 5 Dec 2019 17:50:30 +0000 Subject: [PATCH 03/10] Add handler for v2 /send_join --- lib/SyTest/Federation/Server.pm | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/SyTest/Federation/Server.pm b/lib/SyTest/Federation/Server.pm index d53bd8f5b..8e0111ac3 100644 --- a/lib/SyTest/Federation/Server.pm +++ b/lib/SyTest/Federation/Server.pm @@ -312,6 +312,33 @@ sub on_request_federation_v1_send_join } ] ); } +sub on_request_federation_v2_send_join +{ + my $self = shift; + my ( $req, $room_id ) = @_; + + my $store = $self->{datastore}; + + my $room = $store->get_room( $room_id ) or + return Future->done( response => HTTP::Response->new( + 404, "Not found", [ Content_length => 0 ], "", + ) ); + + my $event = $req->body_from_json; + + my @auth_chain = $store->get_auth_chain_events( + map { $_->[0] } @{ $event->{auth_events} } + ); + my @state_events = $room->current_state_events; + + $room->insert_event( $event ); + + Future->done( json => { + auth_chain => \@auth_chain, + state => \@state_events, + } ); +} + sub mk_await_request_pair { my $class = shift; @@ -401,6 +428,10 @@ __PACKAGE__->mk_await_request_pair( send_join => "v1", [qw( :room_id )], ); +__PACKAGE__->mk_await_request_pair( + send_join => "v2", [qw( :room_id )], +); + __PACKAGE__->mk_await_request_pair( state_ids => "v1", [qw( :room_id ?event_id )], ); From 8951f12db951d8369726034e6319503436dc7083 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 5 Dec 2019 17:58:38 +0000 Subject: [PATCH 04/10] Version the name of the await functions (if the version isn't v1) --- lib/SyTest/Federation/Server.pm | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/SyTest/Federation/Server.pm b/lib/SyTest/Federation/Server.pm index 8e0111ac3..7ff276f02 100644 --- a/lib/SyTest/Federation/Server.pm +++ b/lib/SyTest/Federation/Server.pm @@ -402,10 +402,22 @@ sub mk_await_request_pair } }; + # Don't specify the version prefix in the function name if the version is v1 so we + # don't break existing function calls. + my ( $await_request_function_name, $on_request_function_name ); + if( $versionprefix eq "v1" ) { + $await_request_function_name = "await_request_$shortname"; + $on_request_function_name = "on_request_federation_$shortname" + } + else { + $await_request_function_name = "await_request_$versionprefix\_$shortname"; + $on_request_function_name = "on_request_federation_$versionprefix\_$shortname"; + } + no strict 'refs'; no warnings 'redefine'; - *{"${class}::await_request_$shortname"} = $awaitfunc; - *{"${class}::on_request_federation_v1_$shortname"} = $on_requestfunc; + *{"${class}::$await_request_function_name"} = $awaitfunc; + *{"${class}::$on_request_function_name"} = $on_requestfunc; } __PACKAGE__->mk_await_request_pair( From 4c728a82e51194fed223585a7922acfec24b4e1f Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 5 Dec 2019 19:01:39 +0000 Subject: [PATCH 05/10] Fix on_request function name --- lib/SyTest/Federation/Server.pm | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/SyTest/Federation/Server.pm b/lib/SyTest/Federation/Server.pm index 7ff276f02..985bd8ec2 100644 --- a/lib/SyTest/Federation/Server.pm +++ b/lib/SyTest/Federation/Server.pm @@ -402,22 +402,20 @@ sub mk_await_request_pair } }; - # Don't specify the version prefix in the function name if the version is v1 so we - # don't break existing function calls. - my ( $await_request_function_name, $on_request_function_name ); + # Don't specify the version prefix in the await function name if the version is v1 so + # we don't break existing function calls. + my ( $await_request_function_name ); if( $versionprefix eq "v1" ) { $await_request_function_name = "await_request_$shortname"; - $on_request_function_name = "on_request_federation_$shortname" } else { $await_request_function_name = "await_request_$versionprefix\_$shortname"; - $on_request_function_name = "on_request_federation_$versionprefix\_$shortname"; } no strict 'refs'; no warnings 'redefine'; *{"${class}::$await_request_function_name"} = $awaitfunc; - *{"${class}::$on_request_function_name"} = $on_requestfunc; + *{"${class}::on_request_federation_$versionprefix\_$shortname"} = $on_requestfunc; } __PACKAGE__->mk_await_request_pair( From fa6b453ceedc2b91b5cc852ccc5d1d308077770b Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 5 Dec 2019 19:03:32 +0000 Subject: [PATCH 06/10] Add tests for outbout federation on v2 /send_join --- tests/50federation/30room-join.pl | 107 ++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/tests/50federation/30room-join.pl b/tests/50federation/30room-join.pl index 753c370b9..bc3ef2ef6 100644 --- a/tests/50federation/30room-join.pl +++ b/tests/50federation/30room-join.pl @@ -96,7 +96,7 @@ sub assert_is_valid_pdu { Future->done; }), - $inbound_server->await_request_send_join( $room_id )->then( sub { + $inbound_server->await_request_v2_send_join( $room_id )->then( sub { my ( $req, $room_id, $event_id ) = @_; $req->method eq "PUT" or @@ -110,11 +110,10 @@ sub assert_is_valid_pdu { ); $req->respond_json( - # /v1/send_join has an extraneous [200, ...] wrapper (see MSC1802) - my $response = [ 200, { + my $response = { auth_chain => \@auth_chain, state => [ $room->current_state_events ], - } ] + } ); log_if_fail "send_join response", $response; @@ -448,6 +447,106 @@ sub assert_is_valid_pdu { +test "Outbound federation falls back to v1 /send_join if v2 isn't available", + requires => [ local_user_fixture(), $main::INBOUND_SERVER, + federation_user_id_fixture() ], + + do => sub { + my ( $user, $inbound_server, $creator_id ) = @_; + + my $local_server_name = $inbound_server->server_name; + my $datastore = $inbound_server->datastore; + + my $room = SyTest::Federation::Room->new( + datastore => $datastore, + ); + + $room->create_initial_events( + server => $inbound_server, + creator => $creator_id, + ); + + my $room_id = $room->room_id; + + my $room_alias = "#50fed-room-alias:$local_server_name"; + $datastore->{room_aliases}{$room_alias} = $room_id; + + Future->needs_all( + # Await PDU? + + $inbound_server->await_request_make_join( $room_id, $user->user_id )->then( sub { + my ( $req, $room_id, $user_id ) = @_; + + my $proto = $room->make_join_protoevent( + user_id => $user_id, + ); + + $proto->{origin} = $inbound_server->server_name; + $proto->{origin_server_ts} = $inbound_server->time_ms; + + $req->respond_json( { + event => $proto, + } ); + + Future->done; + }), + + $inbound_server->await_request_send_join( $room_id )->then( sub { + my ( $req, $room_id, $event_id ) = @_; + + $req->method eq "PUT" or + die "Expected send_join method to be PUT"; + + my $event = $req->body_from_json; + log_if_fail "send_join event", $event; + + my @auth_chain = $datastore->get_auth_chain_events( + map { $_->[0] } @{ $event->{auth_events} } + ); + + $req->respond_json( + # /v1/send_join has an extraneous [200, ...] wrapper (see MSC1802) + my $response = [ 200, { + auth_chain => \@auth_chain, + state => [ $room->current_state_events ], + } ] + ); + + log_if_fail "send_join response", $response; + + Future->done; + }), + + do_request_json_for( $user, + method => "POST", + uri => "/r0/join/$room_alias", + + content => {}, + )->then( sub { + my ( $body ) = @_; + log_if_fail "Join response", $body; + + assert_json_keys( $body, qw( room_id )); + + $body->{room_id} eq $room_id or + die "Expected room_id to be $room_id"; + + matrix_get_my_member_event( $user, $room_id ) + })->then( sub { + my ( $event ) = @_; + + # The joining HS (i.e. the SUT) should have invented the event ID + # for my membership event. + + # TODO - sanity check the $event + + Future->done(1); + }), + ) + }; + + + test "Inbound /v1/make_join rejects remote attempts to join local users to rooms", requires => [ $main::OUTBOUND_CLIENT, local_user_fixture(), From 0ca155907915994afcf5b2b3b7fb7ead6601cbee Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 9 Dec 2019 17:19:59 +0000 Subject: [PATCH 07/10] Incorporate review --- lib/SyTest/Federation/Server.pm | 78 ++--- tests/50federation/30room-join.pl | 546 ++++++++++-------------------- 2 files changed, 199 insertions(+), 425 deletions(-) diff --git a/lib/SyTest/Federation/Server.pm b/lib/SyTest/Federation/Server.pm index 985bd8ec2..1c9928b18 100644 --- a/lib/SyTest/Federation/Server.pm +++ b/lib/SyTest/Federation/Server.pm @@ -287,29 +287,13 @@ sub on_request_federation_v1_make_join sub on_request_federation_v1_send_join { my $self = shift; - my ( $req, $room_id ) = @_; - my $store = $self->{datastore}; + $self->on_request_federation_v2_send_join( @_ )->then( sub { + my $res = @_; - my $room = $store->get_room( $room_id ) or - return Future->done( response => HTTP::Response->new( - 404, "Not found", [ Content_length => 0 ], "", - ) ); - - my $event = $req->body_from_json; - - my @auth_chain = $store->get_auth_chain_events( - map { $_->[0] } @{ $event->{auth_events} } - ); - my @state_events = $room->current_state_events; - - $room->insert_event( $event ); - - # /v1/send_join has an extraneous [ 200, ... ] wrapper (see MSC1802) - Future->done( json => [ 200, { - auth_chain => \@auth_chain, - state => \@state_events, - } ] ); + # /v1/send_join has an extraneous [ 200, ... ] wrapper (see MSC1802) + Future->done( json => [ 200, $res ] ); + }) } sub on_request_federation_v2_send_join @@ -342,10 +326,10 @@ sub on_request_federation_v2_send_join sub mk_await_request_pair { my $class = shift; - my ( $shortname, $versionprefix, $paramnames ) = @_; + my ( $versionprefix, $shortname, $paramnames ) = @_; my @paramnames = @$paramnames; - my $okey = "awaiting_$versionprefix\_$shortname"; + my $okey = "awaiting_${versionprefix}_${shortname}"; my $awaitfunc = sub { my $self = shift; @@ -364,7 +348,7 @@ sub mk_await_request_pair }; my $was_on_requestfunc = $class->can( - "on_request_federation_$versionprefix\_$shortname" + "on_request_federation_${versionprefix}_${shortname}" ); my $on_requestfunc = sub { my $self = shift; @@ -402,80 +386,72 @@ sub mk_await_request_pair } }; - # Don't specify the version prefix in the await function name if the version is v1 so - # we don't break existing function calls. - my ( $await_request_function_name ); - if( $versionprefix eq "v1" ) { - $await_request_function_name = "await_request_$shortname"; - } - else { - $await_request_function_name = "await_request_$versionprefix\_$shortname"; - } - no strict 'refs'; no warnings 'redefine'; - *{"${class}::$await_request_function_name"} = $awaitfunc; - *{"${class}::on_request_federation_$versionprefix\_$shortname"} = $on_requestfunc; + *{"${class}::await_request_${versionprefix}_${shortname}"} = $awaitfunc; + # Deprecated alternative name for v1 endpoints. + *{"${class}::await_request_${shortname}"} = $awaitfunc if ${versionprefix} eq "v1"; + *{"${class}::on_request_federation_${versionprefix}_${shortname}"} = $on_requestfunc; } __PACKAGE__->mk_await_request_pair( - query_directory => "v1", [qw( ?room_alias )], + "v1", "query_directory", [qw( ?room_alias )], ); __PACKAGE__->mk_await_request_pair( - query_profile => "v1", [qw( ?user_id )], + "v1", "query_profile", [qw( ?user_id )], ); __PACKAGE__->mk_await_request_pair( - make_join => "v1", [qw( :room_id :user_id )], + "v1", "make_join", [qw( :room_id :user_id )], ); __PACKAGE__->mk_await_request_pair( - make_leave => "v1", [qw( :room_id :user_id )], + "v1", "make_leave", [qw( :room_id :user_id )], ); __PACKAGE__->mk_await_request_pair( - send_join => "v1", [qw( :room_id )], + "v1", "send_join", [qw( :room_id )], ); __PACKAGE__->mk_await_request_pair( - send_join => "v2", [qw( :room_id )], + "v2", "send_join", [qw( :room_id )], ); __PACKAGE__->mk_await_request_pair( - state_ids => "v1", [qw( :room_id ?event_id )], + "v1", "state_ids", [qw( :room_id ?event_id )], ); __PACKAGE__->mk_await_request_pair( - state => "v1", [qw( :room_id )], + "v1", "state", [qw( :room_id )], ); __PACKAGE__->mk_await_request_pair( - get_missing_events => "v1", [qw( :room_id )], + "v1", "get_missing_events", [qw( :room_id )], ); __PACKAGE__->mk_await_request_pair( - event_auth => "v1", [qw( :room_id :event_id )], + "v1", "event_auth", [qw( :room_id :event_id )], ); __PACKAGE__->mk_await_request_pair( - backfill => "v1", [qw( :room_id )], + "v1", "backfill", [qw( :room_id )], ); __PACKAGE__->mk_await_request_pair( - invite => "v1", [qw( :room_id )], + "v1", "invite", [qw( :room_id )], ); __PACKAGE__->mk_await_request_pair( - event => "v1", [qw( :event_id )], + "v1", "event", [qw( :event_id )], ); __PACKAGE__->mk_await_request_pair( - user_devices => "v1", [qw( :user_id )], + "v1", "user_devices", [qw( :user_id )], ); __PACKAGE__->mk_await_request_pair( - user_keys_query => "v1", [qw( )], + "v1", "user_keys_query", [qw( )], ); sub on_request_federation_v1_send diff --git a/tests/50federation/30room-join.pl b/tests/50federation/30room-join.pl index bc3ef2ef6..2dcbc721c 100644 --- a/tests/50federation/30room-join.pl +++ b/tests/50federation/30room-join.pl @@ -52,102 +52,118 @@ sub assert_is_valid_pdu { } push our @EXPORT, qw( assert_is_valid_pdu ); -test "Outbound federation can send room-join requests", - requires => [ local_user_fixture(), $main::INBOUND_SERVER, - federation_user_id_fixture() ], +foreach my $versionprefix ( qw( v1 v2 ) ) { + test "Inbound federation supports $versionprefix /send_join", + requires => [ local_user_fixture(), $main::INBOUND_SERVER, + federation_user_id_fixture() ], - do => sub { - my ( $user, $inbound_server, $creator_id ) = @_; + do => sub { + my ( $user, $inbound_server, $creator_id ) = @_; - my $local_server_name = $inbound_server->server_name; - my $datastore = $inbound_server->datastore; + my $local_server_name = $inbound_server->server_name; + my $datastore = $inbound_server->datastore; - my $room = SyTest::Federation::Room->new( - datastore => $datastore, - ); + my $room = SyTest::Federation::Room->new( + datastore => $datastore, + ); - $room->create_initial_events( - server => $inbound_server, - creator => $creator_id, - ); + $room->create_initial_events( + server => $inbound_server, + creator => $creator_id, + ); - my $room_id = $room->room_id; + my $room_id = $room->room_id; - my $room_alias = "#50fed-room-alias:$local_server_name"; - $datastore->{room_aliases}{$room_alias} = $room_id; + my $room_alias = "#50fed-room-alias:$local_server_name"; + $datastore->{room_aliases}{$room_alias} = $room_id; - Future->needs_all( - # Await PDU? + my $await_request_send_join; - $inbound_server->await_request_make_join( $room_id, $user->user_id )->then( sub { - my ( $req, $room_id, $user_id ) = @_; + if( $versionprefix eq "v1" ) { + $await_request_send_join = $inbound_server->await_request_v1_send_join( $room_id ); + } + elsif( $versionprefix eq "v2" ) { + $await_request_send_join = $inbound_server->await_request_v2_send_join( $room_id ); + } - my $proto = $room->make_join_protoevent( - user_id => $user_id, - ); + Future->needs_all( + # Await PDU? - $proto->{origin} = $inbound_server->server_name; - $proto->{origin_server_ts} = $inbound_server->time_ms; + $inbound_server->await_request_make_join( $room_id, $user->user_id )->then( sub { + my ( $req, $room_id, $user_id ) = @_; - $req->respond_json( { - event => $proto, - } ); + my $proto = $room->make_join_protoevent( + user_id => $user_id, + ); - Future->done; - }), + $proto->{origin} = $inbound_server->server_name; + $proto->{origin_server_ts} = $inbound_server->time_ms; - $inbound_server->await_request_v2_send_join( $room_id )->then( sub { - my ( $req, $room_id, $event_id ) = @_; + $req->respond_json( { + event => $proto, + } ); - $req->method eq "PUT" or - die "Expected send_join method to be PUT"; + Future->done; + }), - my $event = $req->body_from_json; - log_if_fail "send_join event", $event; + $await_request_send_join->then( sub { + my ( $req, $room_id, $event_id ) = @_; - my @auth_chain = $datastore->get_auth_chain_events( - map { $_->[0] } @{ $event->{auth_events} } - ); + $req->method eq "PUT" or + die "Expected send_join method to be PUT"; + + my $event = $req->body_from_json; + log_if_fail "send_join event", $event; + + my @auth_chain = $datastore->get_auth_chain_events( + map { $_->[0] } @{ $event->{auth_events} } + ); - $req->respond_json( my $response = { auth_chain => \@auth_chain, state => [ $room->current_state_events ], + }; + + if( $versionprefix eq "v1" ) { + # /v1/send_join has an extraneous [200, ...] wrapper (see MSC1802) + $response = [ 200, $response ]; } - ); - log_if_fail "send_join response", $response; + $req->respond_json($response); - Future->done; - }), + log_if_fail "send_join response", $response; - do_request_json_for( $user, - method => "POST", - uri => "/r0/join/$room_alias", + Future->done; + }), - content => {}, - )->then( sub { - my ( $body ) = @_; - log_if_fail "Join response", $body; + do_request_json_for( $user, + method => "POST", + uri => "/r0/join/$room_alias", - assert_json_keys( $body, qw( room_id )); + content => {}, + )->then( sub { + my ( $body ) = @_; + log_if_fail "Join response", $body; - $body->{room_id} eq $room_id or - die "Expected room_id to be $room_id"; + assert_json_keys( $body, qw( room_id )); - matrix_get_my_member_event( $user, $room_id ) - })->then( sub { - my ( $event ) = @_; + $body->{room_id} eq $room_id or + die "Expected room_id to be $room_id"; - # The joining HS (i.e. the SUT) should have invented the event ID - # for my membership event. + matrix_get_my_member_event( $user, $room_id ) + })->then( sub { + my ( $event ) = @_; - # TODO - sanity check the $event + # The joining HS (i.e. the SUT) should have invented the event ID + # for my membership event. - Future->done(1); - }), - ) - }; + # TODO - sanity check the $event + + Future->done(1); + }), + ) + }; +} test "Outbound federation passes make_join failures through to the client", @@ -200,351 +216,133 @@ sub assert_is_valid_pdu { }; +foreach my $versionprefix ( qw ( v1 v2 ) ) { + test "Inbound federation supports $versionprefix /send_join", + requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER, + local_user_and_room_fixtures( room_opts => { room_version => "1" } ), + federation_user_id_fixture() ], -test "Inbound federation can receive v1 room-join requests", - requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER, - local_user_and_room_fixtures( room_opts => { room_version => "1" } ), - federation_user_id_fixture() ], - - do => sub { - my ( $outbound_client, $inbound_server, $creator, $room_id, $user_id ) = @_; - my $first_home_server = $creator->server_name; - - my $local_server_name = $outbound_client->server_name; - my $datastore = $inbound_server->datastore; - - $outbound_client->do_request_json( - method => "GET", - hostname => $first_home_server, - uri => "/v1/make_join/$room_id/$user_id", - )->then( sub { - my ( $body ) = @_; - log_if_fail "make_join body", $body; - - assert_json_keys( $body, qw( event )); - - my $protoevent = $body->{event}; - - assert_json_keys( $protoevent, qw( - auth_events content depth room_id sender state_key type - )); - - assert_json_nonempty_list( my $auth_events = $protoevent->{auth_events} ); - foreach my $auth_event ( @$auth_events ) { - assert_json_list( $auth_event ); - @$auth_event == 2 or - die "Expected auth_event list element to have 2 members"; - - assert_json_string( $auth_event->[0] ); # id - assert_json_object( $auth_event->[1] ); # hashes - } - - assert_json_nonempty_list( $protoevent->{prev_events} ); - - assert_json_number( $protoevent->{depth} ); - - $protoevent->{room_id} eq $room_id or - die "Expected 'room_id' to be $room_id"; - $protoevent->{sender} eq $user_id or - die "Expected 'sender' to be $user_id"; - $protoevent->{state_key} eq $user_id or - die "Expected 'state_key' to be $user_id"; - $protoevent->{type} eq "m.room.member" or - die "Expected 'type' to be 'm.room.member'"; - - assert_json_keys( my $content = $protoevent->{content}, qw( membership ) ); - $content->{membership} eq "join" or - die "Expected 'membership' to be 'join'"; - - my %event = ( - ( map { $_ => $protoevent->{$_} } qw( - auth_events content depth prev_events room_id sender - state_key type ) ), - - event_id => $datastore->next_event_id, - origin => $local_server_name, - origin_server_ts => $inbound_server->time_ms, - ); - - $datastore->sign_event( \%event ); - - $outbound_client->do_request_json( - method => "PUT", - hostname => $first_home_server, - uri => "/v1/send_join/$room_id/$event{event_id}", - - content => \%event, - ) - })->then( sub { - my ( $response ) = @_; - - # /v1/send_join has an extraneous [200, ...] wrapper (see MSC1802) - $response->[0] == 200 or - die "Expected first response element to be 200"; - - $response = $response->[1]; - - assert_json_keys( $response, qw( auth_chain state )); - - assert_json_nonempty_list( $response->{auth_chain} ); - my @auth_chain = @{ $response->{auth_chain} }; - - log_if_fail "Auth chain", \@auth_chain; - - foreach my $event ( @auth_chain ) { - assert_is_valid_pdu( $event ); - $event->{room_id} eq $room_id or - die "Expected auth_event room_id to be $room_id"; - } - - # Annoyingly, the "auth chain" isn't specified to arrive in any - # particular order. We'll have to keep walking it incrementally. - - my %accepted_authevents; - while( @auth_chain ) { - my @accepted = extract_by { - $inbound_server->auth_check_event( $_, \%accepted_authevents ) - } @auth_chain; - - unless( @accepted ) { - log_if_fail "Unacceptable auth chain", \@auth_chain; - - die "Unable to find any more acceptable auth_chain events"; - } - - $accepted_authevents{$_->{event_id}} = $_ for @accepted; - } - - assert_json_nonempty_list( $response->{state} ); - my %state = partition_by { $_->{type} } @{ $response->{state} }; - - log_if_fail "State", \%state; - - # TODO: lots more checking. Requires spec though - Future->done(1); - }); - }; - - - -test "Inbound federation supports v2 /send_join", - requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER, - local_user_and_room_fixtures( room_opts => { room_version => "1" } ), - federation_user_id_fixture() ], - - do => sub { - my ( $outbound_client, $inbound_server, $creator, $room_id, $user_id ) = @_; - my $first_home_server = $creator->server_name; - - my $local_server_name = $outbound_client->server_name; - my $datastore = $inbound_server->datastore; - - $outbound_client->do_request_json( - method => "GET", - hostname => $first_home_server, - uri => "/v1/make_join/$room_id/$user_id", - )->then( sub { - my ( $body ) = @_; - log_if_fail "make_join body", $body; - - assert_json_keys( $body, qw( event )); - - my $protoevent = $body->{event}; - - assert_json_keys( $protoevent, qw( - auth_events content depth room_id sender state_key type - )); + do => sub { + my ( $outbound_client, $inbound_server, $creator, $room_id, $user_id ) = @_; + my $first_home_server = $creator->server_name; - assert_json_nonempty_list( my $auth_events = $protoevent->{auth_events} ); - foreach my $auth_event ( @$auth_events ) { - assert_json_list( $auth_event ); - @$auth_event == 2 or - die "Expected auth_event list element to have 2 members"; - - assert_json_string( $auth_event->[0] ); # id - assert_json_object( $auth_event->[1] ); # hashes - } - - assert_json_nonempty_list( $protoevent->{prev_events} ); - - assert_json_number( $protoevent->{depth} ); - - $protoevent->{room_id} eq $room_id or - die "Expected 'room_id' to be $room_id"; - $protoevent->{sender} eq $user_id or - die "Expected 'sender' to be $user_id"; - $protoevent->{state_key} eq $user_id or - die "Expected 'state_key' to be $user_id"; - $protoevent->{type} eq "m.room.member" or - die "Expected 'type' to be 'm.room.member'"; - - assert_json_keys( my $content = $protoevent->{content}, qw( membership ) ); - $content->{membership} eq "join" or - die "Expected 'membership' to be 'join'"; - - my %event = ( - ( map { $_ => $protoevent->{$_} } qw( - auth_events content depth prev_events room_id sender - state_key type ) ), - - event_id => $datastore->next_event_id, - origin => $local_server_name, - origin_server_ts => $inbound_server->time_ms, - ); - - $datastore->sign_event( \%event ); + my $local_server_name = $outbound_client->server_name; + my $datastore = $inbound_server->datastore; $outbound_client->do_request_json( - method => "PUT", + method => "GET", hostname => $first_home_server, - uri => "/v2/send_join/$room_id/$event{event_id}", - - content => \%event, - ) - })->then( sub { - my ( $response ) = @_; - - assert_json_keys( $response, qw( auth_chain state )); - - assert_json_nonempty_list( $response->{auth_chain} ); - my @auth_chain = @{ $response->{auth_chain} }; + uri => "/v1/make_join/$room_id/$user_id", + )->then( sub { + my ( $body ) = @_; + log_if_fail "make_join body", $body; - log_if_fail "Auth chain", \@auth_chain; + assert_json_keys( $body, qw( event )); - foreach my $event ( @auth_chain ) { - assert_is_valid_pdu( $event ); - $event->{room_id} eq $room_id or - die "Expected auth_event room_id to be $room_id"; - } + my $protoevent = $body->{event}; - # Annoyingly, the "auth chain" isn't specified to arrive in any - # particular order. We'll have to keep walking it incrementally. + assert_json_keys( $protoevent, qw( + auth_events content depth room_id sender state_key type + )); - my %accepted_authevents; - while( @auth_chain ) { - my @accepted = extract_by { - $inbound_server->auth_check_event( $_, \%accepted_authevents ) - } @auth_chain; + assert_json_nonempty_list( my $auth_events = $protoevent->{auth_events} ); + foreach my $auth_event ( @$auth_events ) { + assert_json_list( $auth_event ); + @$auth_event == 2 or + die "Expected auth_event list element to have 2 members"; - unless( @accepted ) { - log_if_fail "Unacceptable auth chain", \@auth_chain; - - die "Unable to find any more acceptable auth_chain events"; + assert_json_string( $auth_event->[0] ); # id + assert_json_object( $auth_event->[1] ); # hashes } - $accepted_authevents{$_->{event_id}} = $_ for @accepted; - } + assert_json_nonempty_list( $protoevent->{prev_events} ); - assert_json_nonempty_list( $response->{state} ); - my %state = partition_by { $_->{type} } @{ $response->{state} }; + assert_json_number( $protoevent->{depth} ); - log_if_fail "State", \%state; + $protoevent->{room_id} eq $room_id or + die "Expected 'room_id' to be $room_id"; + $protoevent->{sender} eq $user_id or + die "Expected 'sender' to be $user_id"; + $protoevent->{state_key} eq $user_id or + die "Expected 'state_key' to be $user_id"; + $protoevent->{type} eq "m.room.member" or + die "Expected 'type' to be 'm.room.member'"; - # TODO: lots more checking. Requires spec though - Future->done(1); - }); - }; + assert_json_keys( my $content = $protoevent->{content}, qw( membership ) ); + $content->{membership} eq "join" or + die "Expected 'membership' to be 'join'"; + my %event = ( + ( map { $_ => $protoevent->{$_} } qw( + auth_events content depth prev_events room_id sender + state_key type ) ), - -test "Outbound federation falls back to v1 /send_join if v2 isn't available", - requires => [ local_user_fixture(), $main::INBOUND_SERVER, - federation_user_id_fixture() ], - - do => sub { - my ( $user, $inbound_server, $creator_id ) = @_; - - my $local_server_name = $inbound_server->server_name; - my $datastore = $inbound_server->datastore; - - my $room = SyTest::Federation::Room->new( - datastore => $datastore, - ); - - $room->create_initial_events( - server => $inbound_server, - creator => $creator_id, - ); - - my $room_id = $room->room_id; - - my $room_alias = "#50fed-room-alias:$local_server_name"; - $datastore->{room_aliases}{$room_alias} = $room_id; - - Future->needs_all( - # Await PDU? - - $inbound_server->await_request_make_join( $room_id, $user->user_id )->then( sub { - my ( $req, $room_id, $user_id ) = @_; - - my $proto = $room->make_join_protoevent( - user_id => $user_id, + event_id => $datastore->next_event_id, + origin => $local_server_name, + origin_server_ts => $inbound_server->time_ms, ); - $proto->{origin} = $inbound_server->server_name; - $proto->{origin_server_ts} = $inbound_server->time_ms; + $datastore->sign_event( \%event ); - $req->respond_json( { - event => $proto, - } ); + $outbound_client->do_request_json( + method => "PUT", + hostname => $first_home_server, + uri => "/$versionprefix/send_join/$room_id/$event{event_id}", - Future->done; - }), - - $inbound_server->await_request_send_join( $room_id )->then( sub { - my ( $req, $room_id, $event_id ) = @_; + content => \%event, + ) + })->then( sub { + my ( $response ) = @_; - $req->method eq "PUT" or - die "Expected send_join method to be PUT"; + if( $versionprefix eq "v1" ) { + # /v1/send_join has an extraneous [200, ...] wrapper (see MSC1802) + $response->[0] == 200 or + die "Expected first response element to be 200"; - my $event = $req->body_from_json; - log_if_fail "send_join event", $event; + $response = $response->[1]; + } - my @auth_chain = $datastore->get_auth_chain_events( - map { $_->[0] } @{ $event->{auth_events} } - ); + assert_json_keys( $response, qw( auth_chain state )); - $req->respond_json( - # /v1/send_join has an extraneous [200, ...] wrapper (see MSC1802) - my $response = [ 200, { - auth_chain => \@auth_chain, - state => [ $room->current_state_events ], - } ] - ); + assert_json_nonempty_list( $response->{auth_chain} ); + my @auth_chain = @{ $response->{auth_chain} }; - log_if_fail "send_join response", $response; + log_if_fail "Auth chain", \@auth_chain; - Future->done; - }), + foreach my $event ( @auth_chain ) { + assert_is_valid_pdu( $event ); + $event->{room_id} eq $room_id or + die "Expected auth_event room_id to be $room_id"; + } - do_request_json_for( $user, - method => "POST", - uri => "/r0/join/$room_alias", + # Annoyingly, the "auth chain" isn't specified to arrive in any + # particular order. We'll have to keep walking it incrementally. - content => {}, - )->then( sub { - my ( $body ) = @_; - log_if_fail "Join response", $body; + my %accepted_authevents; + while( @auth_chain ) { + my @accepted = extract_by { + $inbound_server->auth_check_event( $_, \%accepted_authevents ) + } @auth_chain; - assert_json_keys( $body, qw( room_id )); + unless( @accepted ) { + log_if_fail "Unacceptable auth chain", \@auth_chain; - $body->{room_id} eq $room_id or - die "Expected room_id to be $room_id"; + die "Unable to find any more acceptable auth_chain events"; + } - matrix_get_my_member_event( $user, $room_id ) - })->then( sub { - my ( $event ) = @_; + $accepted_authevents{$_->{event_id}} = $_ for @accepted; + } - # The joining HS (i.e. the SUT) should have invented the event ID - # for my membership event. + assert_json_nonempty_list( $response->{state} ); + my %state = partition_by { $_->{type} } @{ $response->{state} }; - # TODO - sanity check the $event + log_if_fail "State", \%state; + # TODO: lots more checking. Requires spec though Future->done(1); - }), - ) - }; - + }); + }; +} test "Inbound /v1/make_join rejects remote attempts to join local users to rooms", From 78f8a530f6e09a78eda19e12f8a5c3e2c917cb9e Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Tue, 10 Dec 2019 14:03:05 +0000 Subject: [PATCH 08/10] Fix test names --- tests/50federation/30room-join.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/50federation/30room-join.pl b/tests/50federation/30room-join.pl index 2dcbc721c..7e39e57d7 100644 --- a/tests/50federation/30room-join.pl +++ b/tests/50federation/30room-join.pl @@ -53,7 +53,7 @@ sub assert_is_valid_pdu { push our @EXPORT, qw( assert_is_valid_pdu ); foreach my $versionprefix ( qw( v1 v2 ) ) { - test "Inbound federation supports $versionprefix /send_join", + test "Outbound federation can query $versionprefix /send_join", requires => [ local_user_fixture(), $main::INBOUND_SERVER, federation_user_id_fixture() ], @@ -217,7 +217,7 @@ sub assert_is_valid_pdu { foreach my $versionprefix ( qw ( v1 v2 ) ) { - test "Inbound federation supports $versionprefix /send_join", + test "Outbound federation can receive $versionprefix /send_join", requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER, local_user_and_room_fixtures( room_opts => { room_version => "1" } ), federation_user_id_fixture() ], From ad4d77921c4a6426b56fc32be47791c91b6071ef Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Tue, 10 Dec 2019 14:26:43 +0000 Subject: [PATCH 09/10] Re-fix test names --- tests/50federation/30room-join.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/50federation/30room-join.pl b/tests/50federation/30room-join.pl index 7e39e57d7..4634be321 100644 --- a/tests/50federation/30room-join.pl +++ b/tests/50federation/30room-join.pl @@ -217,7 +217,7 @@ sub assert_is_valid_pdu { foreach my $versionprefix ( qw ( v1 v2 ) ) { - test "Outbound federation can receive $versionprefix /send_join", + test "Inbound federation can receive $versionprefix /send_join", requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER, local_user_and_room_fixtures( room_opts => { room_version => "1" } ), federation_user_id_fixture() ], From 5db16de9fda73c1558565cc2f5181cfde10cd7a9 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Tue, 10 Dec 2019 16:49:11 +0000 Subject: [PATCH 10/10] Add comment to explain what the v1 outbound test relies on --- tests/50federation/30room-join.pl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/50federation/30room-join.pl b/tests/50federation/30room-join.pl index 4634be321..d061840f0 100644 --- a/tests/50federation/30room-join.pl +++ b/tests/50federation/30room-join.pl @@ -80,6 +80,12 @@ sub assert_is_valid_pdu { my $await_request_send_join; if( $versionprefix eq "v1" ) { + # If we only expect a response on the v1 endpoint, the homeserver will try to + # hit the v2 one, get a 404 from SyTest (because we didn't call + # await_request_v2_send_join), then fall back to the v1 endpoint. We rely on + # that 404 response from SyTest and that fallback mechanism to test that the + # homeserver can query the v1 endpoint, and correctly handles responses from + # it. $await_request_send_join = $inbound_server->await_request_v1_send_join( $room_id ); } elsif( $versionprefix eq "v2" ) {