Skip to content

Commit

Permalink
do requestToken before adding a 3pid (#706)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
richvdh authored Sep 18, 2019
1 parent d1c6763 commit 476476e
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 94 deletions.
44 changes: 44 additions & 0 deletions lib/SyTest/Identity/Server.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -34,6 +38,7 @@ sub _init

$self->{bindings} = {};
$self->{invites} = {};
$self->{awaiters} = [];

# String for peppering hashed lookup requests
$self->{lookup_pepper} = "matrixrocks";
Expand Down Expand Up @@ -68,6 +73,34 @@ sub rotate_keys
};
}

=head2 await_request
$server->await_request(
path => "/_matrix/identity/...",
)->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.
Expand All @@ -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(
Expand Down
138 changes: 125 additions & 13 deletions tests/12login/01threepid-and-password.pl
Original file line number Diff line number Diff line change
@@ -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 belongs 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",
Expand All @@ -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,
}
Expand Down
96 changes: 15 additions & 81 deletions tests/54identity.pl
Original file line number Diff line number Diff line change
Expand Up @@ -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 );

Expand All @@ -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 );
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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 );
Expand All @@ -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 );
Expand Down Expand Up @@ -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 );
Expand Down

0 comments on commit 476476e

Please sign in to comment.