From 4753d9aa7cf97a7b83ce75a4a2bd8796b3cc7052 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 18 Sep 2019 00:16:32 +0100 Subject: [PATCH 1/3] do requestToken before adding a 3pid For enhanced realism (TM), hit the requestToken endpoint before attempting to add a 3pid to a user account. Currently, we can get away without hitting requestToken, since it just delegates to the IS, which we stub out. However, MSC2078 changes this so that a homeserver is free to handle requestToken itself, which makes it a required part of the flow. This PR therefore lays the groundwork for future work which will handle the homeserver implementing requestToken itself. --- lib/SyTest/Identity/Server.pm | 44 ++++++++ tests/12login/01threepid-and-password.pl | 138 ++++++++++++++++++++--- tests/54identity.pl | 96 +++------------- 3 files changed, 184 insertions(+), 94 deletions(-) diff --git a/lib/SyTest/Identity/Server.pm b/lib/SyTest/Identity/Server.pm index ede349def..42c1eda4f 100644 --- a/lib/SyTest/Identity/Server.pm +++ b/lib/SyTest/Identity/Server.pm @@ -7,11 +7,13 @@ use base qw( Net::Async::HTTP::Server ); use Crypt::NaCl::Sodium; use List::Util qw( any ); +use List::UtilsBy qw( extract_first_by ); use Protocol::Matrix qw( encode_base64_unpadded sign_json ); use MIME::Base64 qw ( encode_base64url ); use SyTest::HTTPServer::Request; use HTTP::Response; use Digest::SHA qw( sha256 ); +use Struct::Dumb qw( struct ); my $crypto_sign = Crypt::NaCl::Sodium->sign; @@ -22,6 +24,8 @@ my $next_token = 0; # `access_token` parameter my $ID_ACCESS_TOKEN = "swordfish"; +struct Awaiter => [qw( future path_match )]; + sub _init { my $self = shift; @@ -34,6 +38,7 @@ sub _init $self->{bindings} = {}; $self->{invites} = {}; + $self->{awaiters} = []; # String for peppering hashed lookup requests $self->{lookup_pepper} = "matrixrocks"; @@ -68,6 +73,34 @@ sub rotate_keys }; } +=head2 await_request + + $server->await_request( + path => "/_matrix/identity/api/v1/" + )->then( sub { + my ( $req ) = @_; + }); + +Wait for a request to arrive at the identity server. + +=cut + +sub await_request +{ + my ( $self, %params ) = @_; + + my $path_match = $params{path}; + my $f = $self->loop->new_future; + my $awaiter = Awaiter( $f, $path_match ); + push @{$self->{awaiters}}, $awaiter; + + $f->on_cancel( sub { + extract_first_by { $_ == $awaiter } @{$self->{awaiters}}; + }); + + return $f; +} + =head2 on_request Handles incoming HTTP requests to this server. @@ -80,6 +113,17 @@ sub on_request my ( $req ) = @_; my $path = $req->path; + + # first see if we've got an awaiter for this request + my $awaiter = extract_first_by { + $_->path_match eq $path + } @{$self->{awaiters}}; + + if( $awaiter ) { + $awaiter->future->done( $req ); + return; + } + my $key_name; if( diff --git a/tests/12login/01threepid-and-password.pl b/tests/12login/01threepid-and-password.pl index b5313e026..fa32b1b13 100644 --- a/tests/12login/01threepid-and-password.pl +++ b/tests/12login/01threepid-and-password.pl @@ -1,21 +1,116 @@ my $password = "my secure password"; +=head2 validate_email_for_user -test "Can login with 3pid and password using m.login.password", - requires => [ local_user_fixture( password => $password ), id_server_fixture() ], + validate_email_for_user( + $user, $address, $id_server, + )->then( sub { + my ( $sid, $client_secret ) = @_; + }); - check => sub { - my ( $user, $id_server ) = @_; +Runs through the `/r0/account/3pid/email/requestToken` flow for verifying +that an email address belogs to the user. Doesn't add the address to the +account. - my $http = $user->http; +Returns the session id and client secret which can then be used for binding +the address. - my $medium = "email"; - my $address = 'bob@example.com'; - my $client_secret = "a client secret"; - my $id_access_token = $id_server->get_access_token(); +=cut + +sub validate_email_for_user { + my ( $user, $address, $id_server ) = @_; + + # fixme: synapse screws up the escaping of non-alpha chars. + my $client_secret = join "", map { chr 65 + rand 26 } 1 .. 20; + + my $sid; + + return Future->needs_all( + do_request_json_for( + $user, + method => "POST", + uri => "/r0/account/3pid/email/requestToken", + content => { + client_secret => $client_secret, + email => $address, + send_attempt => 1, + id_server => $id_server->name, + id_access_token => $id_server->get_access_token(), + }, + )->then( sub { + my ( $resp ) = @_; + log_if_fail "requestToken response", $resp; + + $sid = $resp->{sid}; + Future->done; + }), + + # we now expect a callout to our test ID server. + await_id_validation( $id_server, $address ), + )->then( sub { + Future->done( $sid, $client_secret ); + }); +} + +push our @EXPORT, qw( validate_email_for_user ); + +# wait for a call to /requestToken on the test IS, and act as if the +# email has been validated. +sub await_id_validation { + my ( $id_server, $address ) = @_; + + $id_server->await_request( + path=>"/_matrix/identity/api/v1/validate/email/requestToken", + )->then( sub { + my ( $req ) = @_; + my $body = $req->body_from_json; + + log_if_fail "ID server /requestToken request", $body; + assert_eq( $body->{email}, $address ); + my $sid = $id_server->validate_identity( 'email', $address, $body->{client_secret} ); + $req->respond_json({ + sid => $sid, + }); + Future->done(); + }); +} + +=head2 add_email_for_user + + add_email_for_user( + $user, $address, $id_server, %params + ); - my $sid = $id_server->validate_identity( $medium, $address, $client_secret ); +Add the given email address to the homeserver account, including the +verfication steps. +C<%params> may include: + +=over + +=item bind => SCALAR + +If truthy, we will ask the server to ask the IS to bind the email +address. False by default. + +=back + +=cut + +sub add_email_for_user { + my ( $user, $address, $id_server, %params ) = @_; + + my $bind = $params{bind} // 0; + + my $id_access_token = $id_server->get_access_token(); + + # start by requesting an email validation. + validate_email_for_user( + $user, $address, $id_server, + )->then( sub { + my ( $sid, $client_secret ) = @_; + + # now tell the HS to add the 3pid do_request_json_for( $user, method => "POST", uri => "/r0/account/3pid", @@ -26,16 +121,33 @@ sid => $sid, client_secret => $client_secret, }, - bind => JSON::false, + bind => $bind ? JSON::true : JSON::false, }, - )->then( sub { + ); + }); +} + +push @EXPORT, qw( add_email_for_user ); + +test "Can login with 3pid and password using m.login.password", + requires => [ local_user_fixture( password => $password ), id_server_fixture() ], + + check => sub { + my ( $user, $id_server ) = @_; + + my $http = $user->http; + + my $address = 'bob@example.com'; + + add_email_for_user( $user, $address, $id_server ) + ->then( sub { $http->do_request_json( method => "POST", uri => "/r0/login", content => { type => "m.login.password", - medium => $medium, + medium => 'email', address => $address, password => $password, } diff --git a/tests/54identity.pl b/tests/54identity.pl index 943c245a9..c528b35f1 100644 --- a/tests/54identity.pl +++ b/tests/54identity.pl @@ -5,24 +5,10 @@ my ( $http, $user, $id_server ) = @_; my $medium = "email"; - my $address = 'bob@example.com'; - my $client_secret = "a client secret"; - my $id_access_token = $id_server->get_access_token; + my $address = 'bob1@example.com'; - my $sid = $id_server->validate_identity( $medium, $address, $client_secret ); - - do_request_json_for( $user, - method => "POST", - uri => "/r0/account/3pid", - content => { - three_pid_creds => { - id_server => $id_server->name, - id_access_token => $id_access_token, - sid => $sid, - client_secret => $client_secret, - }, - bind => JSON::true, - }, + add_email_for_user( + $user, $address, $id_server, bind => 1, )->then( sub { my $res = $id_server->lookup_identity( $medium, $address ); @@ -40,24 +26,10 @@ my ( $http, $user, $id_server ) = @_; my $medium = "email"; - my $address = 'bob@example.com'; - my $client_secret = "a client secret"; - my $id_access_token = $id_server->get_access_token; + my $address = 'bob2@example.com'; - my $sid = $id_server->validate_identity( $medium, $address, $client_secret ); - - do_request_json_for( $user, - method => "POST", - uri => "/r0/account/3pid", - content => { - three_pid_creds => { - id_server => $id_server->name, - id_access_token => $id_access_token, - sid => $sid, - client_secret => $client_secret, - }, - bind => JSON::true, - }, + add_email_for_user( + $user, $address, $id_server, bind => 1, )->then( sub { my $res = $id_server->lookup_identity( $medium, $address ); assert_eq( $res, $user->user_id ); @@ -86,7 +58,7 @@ my ( $http, $user, $id_server ) = @_; my $medium = "email"; - my $address = 'bob@example.com'; + my $address = 'bob3@example.com'; # Bind the 3PID out of band of the homeserver $id_server->bind_identity( undef, $medium, $address, $user->user_id ); @@ -117,24 +89,10 @@ my ( $http, $user, $id_server ) = @_; my $medium = "email"; - my $address = 'bob@example.com'; - my $client_secret = "a client secret"; - my $id_access_token = $id_server->get_access_token; + my $address = 'bob4@example.com'; - my $sid = $id_server->validate_identity( $medium, $address, $client_secret ); - - do_request_json_for( $user, - method => "POST", - uri => "/r0/account/3pid", - content => { - three_pid_creds => { - id_server => $id_server->name, - id_access_token => $id_access_token, - sid => $sid, - client_secret => $client_secret, - }, - bind => JSON::true, - }, + add_email_for_user( + $user, $address, $id_server, bind => 1, )->then( sub { my $res = $id_server->lookup_identity( $medium, $address ); assert_eq( $res, $user->user_id ); @@ -157,21 +115,9 @@ my $medium = "email"; my $address = 'bobby@example.com'; - my $client_secret = "53kr3t"; - my $sid = $id_server->validate_identity( $medium, $address, $client_secret ); - - do_request_json_for( $user, - method => "POST", - uri => "/r0/account/3pid", - content => { - three_pid_creds => { - id_server => $id_server->name, - sid => $sid, - client_secret => $client_secret, - }, - bind => JSON::true, - }, + add_email_for_user( + $user, $address, $id_server, bind => 1, )->then( sub { my $res = $id_server->lookup_identity( $medium, $address ); assert_eq( $res, $user->user_id ); @@ -201,22 +147,10 @@ my ( $http, $user, $id_server ) = @_; my $medium = "email"; - my $address = 'bobby@example.com'; - my $client_secret = "53kr3t"; - - my $sid = $id_server->validate_identity( $medium, $address, $client_secret ); + my $address = 'bobby2@example.com'; - do_request_json_for( $user, - method => "POST", - uri => "/r0/account/3pid", - content => { - three_pid_creds => { - id_server => $id_server->name, - sid => $sid, - client_secret => $client_secret, - }, - bind => JSON::true, - }, + add_email_for_user( + $user, $address, $id_server, bind => 1, )->then( sub { my $res = $id_server->lookup_identity( $medium, $address ); assert_eq( $res, $user->user_id ); From 7c0d471853d00c7e11a19d1dcf2202788008fe33 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Wed, 18 Sep 2019 14:08:15 +0100 Subject: [PATCH 2/3] Update tests/12login/01threepid-and-password.pl Co-Authored-By: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> --- tests/12login/01threepid-and-password.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/12login/01threepid-and-password.pl b/tests/12login/01threepid-and-password.pl index fa32b1b13..a639cbfb9 100644 --- a/tests/12login/01threepid-and-password.pl +++ b/tests/12login/01threepid-and-password.pl @@ -9,7 +9,7 @@ =head2 validate_email_for_user }); Runs through the `/r0/account/3pid/email/requestToken` flow for verifying -that an email address belogs to the user. Doesn't add the address to the +that an email address belongs to the user. Doesn't add the address to the account. Returns the session id and client secret which can then be used for binding From 092e6e73a08d7e91ffdd58132b02776c79114b0b Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 18 Sep 2019 14:09:50 +0100 Subject: [PATCH 3/3] Update example path for await_request --- lib/SyTest/Identity/Server.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/SyTest/Identity/Server.pm b/lib/SyTest/Identity/Server.pm index 42c1eda4f..15ed74fe1 100644 --- a/lib/SyTest/Identity/Server.pm +++ b/lib/SyTest/Identity/Server.pm @@ -76,7 +76,7 @@ sub rotate_keys =head2 await_request $server->await_request( - path => "/_matrix/identity/api/v1/" + path => "/_matrix/identity/...", )->then( sub { my ( $req ) = @_; });