Discord-Client
view release on metacpan or search on metacpan
t/08-rest-tls.t view on Meta::CPAN
$$buffer = '';
my $body = '{"tls":true}';
$stream->write( "HTTP/1.1 200 OK\r\nContent-Length: "
. length($body)
. "\r\nConnection: close\r\n\r\n$body" );
$stream->close_when_empty;
return 0;
}
);
$loop->add($stream);
}
);
$loop->add($listener);
$listener->listen(
addr => { family => 'inet', socktype => 'stream', ip => '127.0.0.1', port => 0 },
extensions => ['SSL'],
SSL_cert_file => "$directory/cert.pem",
SSL_key_file => "$directory/key.pem",
on_ssl_error => sub { push @handshake_errors, $_[0] },
)->get;
return {
loop => $loop,
listener => $listener,
directory => $directory,
requests => \@requests,
streams => \@streams,
base_url => 'https://127.0.0.1:' . $listener->read_handle->sockport . '/api/v10',
};
}
sub close_server {
my ($fixture) = @_;
# Close accepted connections before stopping the listener and releasing its loop resources.
for my $stream ( @{ $fixture->{streams} } ) {
$stream->close;
$fixture->{loop}->remove($stream) if $stream->loop;
}
$fixture->{listener}->close;
}
T2->subtest(
'trusted CA and matching hostname allow a real HTTPS request',
sub {
my $fixture = tls_server('127.0.0.1');
my $http = Net::Async::HTTP->new(
fail_on_error => 0,
SSL_ca_file => "$fixture->{directory}/ca.pem",
SSL_verify_mode => 1,
SSL_verifycn_scheme => 'http',
);
$fixture->{loop}->add($http);
my $rest = Discord::Client::REST->new(
token => 'FAKE_TEST_TOKEN',
loop => $fixture->{loop},
http => $http,
timeout => 2,
base_url => $fixture->{base_url},
);
# A trusted matching certificate must allow credentials to reach the test server.
my $request = $rest->get_current_user;
$fixture->{loop}->await($request);
T2->is( scalar( $request->get ), { tls => 1 }, 'HTTPS response is decoded' );
T2->is( scalar( @{ $fixture->{requests} } ), 1, 'one authenticated request reaches the server' );
T2->like(
$fixture->{requests}[0],
qr/^Authorization: Bot FAKE_TEST_TOKEN\r$/m,
'authorization is sent only after TLS succeeds'
);
# Close SDK work first, then release the injected transport and server.
$rest->close->get;
T2->ref_is( $http->loop, $fixture->{loop}, 'injected TLS transport remains caller-owned' );
$fixture->{loop}->remove($http);
close_server($fixture);
}
);
T2->subtest(
'the default HTTPS transport rejects an untrusted server before sending credentials',
sub {
my $fixture = tls_server('127.0.0.1');
my $rest = Discord::Client::REST->new(
token => 'FAKE_TEST_TOKEN',
loop => $fixture->{loop},
timeout => 2,
base_url => $fixture->{base_url},
);
# Success tests alone cannot detect disabled certificate verification.
my $request = $rest->get_current_user;
$fixture->{loop}->await($request);
T2->ok( $request->is_failed, 'untrusted certificate fails the request' );
T2->is( scalar( @{ $fixture->{requests} } ), 0, 'no HTTP headers reach the untrusted server' );
T2->is( $rest->stats->{http_started}, 1, 'the TLS failure is not retried' );
$rest->close->get;
close_server($fixture);
}
);
T2->subtest(
'a trusted CA cannot authorize the wrong server hostname',
sub {
# Trust the issuer but give the server a different IP in its certificate.
my $fixture = tls_server('127.0.0.2');
my $http = Net::Async::HTTP->new(
fail_on_error => 0,
SSL_ca_file => "$fixture->{directory}/ca.pem",
SSL_verify_mode => 1,
SSL_verifycn_scheme => 'http',
);
$fixture->{loop}->add($http);
my $rest = Discord::Client::REST->new(
token => 'FAKE_TEST_TOKEN',
loop => $fixture->{loop},
http => $http,
timeout => 2,
base_url => $fixture->{base_url},
);
# Hostname validation must reject the connection before any HTTP headers are sent.
my $request = $rest->get_current_user;
$fixture->{loop}->await($request);
T2->ok( $request->is_failed, 'hostname mismatch fails the request' );
T2->is( scalar( @{ $fixture->{requests} } ), 0, 'credentials are withheld on hostname mismatch' );
$rest->close->get;
$fixture->{loop}->remove($http);
close_server($fixture);
}
);
T2->done_testing;
( run in 0.632 second using v1.01-cache-2.11-cpan-3f962061493 )