1
0
mirror of https://github.com/postgres/postgres.git synced 2025-12-18 05:01:01 +03:00
Files
postgres/src/test/modules/oauth_validator/t/002_client.pl
Jacob Champion ab8af1db43 oauth_validator: Avoid races in log_check()
Commit e0f373ee4 fixed up races in Cluster::connect_fails when using
log_like. t/002_client.pl didn't get the memo, though, because it
doesn't use Test::Cluster to perform its custom hook tests. (This is
probably not an issue at the moment, since the log check is only done
after authentication success and not failure, but there's no reason to
wait for someone to hit it.)

Introduce the fix, based on debug2 logging, to its use of log_check() as
well, and move the logic into the test() helper so that any additions
don't need to continually duplicate it.

Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/CAOYmi%2BmrGg%2Bn_X2MOLgeWcj3v_M00gR8uz_D7mM8z%3DdX1JYVbg%40mail.gmail.com
Backpatch-through: 18
2025-12-17 11:46:05 -08:00

169 lines
4.3 KiB
Perl

#
# Exercises the API for custom OAuth client flows, using the oauth_hook_client
# test driver.
#
# Copyright (c) 2021-2025, PostgreSQL Global Development Group
#
use strict;
use warnings FATAL => 'all';
use JSON::PP qw(encode_json);
use MIME::Base64 qw(encode_base64);
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
if (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\boauth\b/)
{
plan skip_all =>
'Potentially unsafe test oauth not enabled in PG_TEST_EXTRA';
}
#
# Cluster Setup
#
my $node = PostgreSQL::Test::Cluster->new('primary');
$node->init;
$node->append_conf('postgresql.conf', "log_connections = all\n");
$node->append_conf('postgresql.conf',
"oauth_validator_libraries = 'validator'\n");
# Needed to inspect postmaster log after connection failure:
$node->append_conf('postgresql.conf', "log_min_messages = debug2");
$node->start;
$node->safe_psql('postgres', 'CREATE USER test;');
# These tests don't use the builtin flow, and we don't have an authorization
# server running, so the address used here shouldn't matter. Use an invalid IP
# address, so if there's some cascade of errors that causes the client to
# attempt a connection, we'll fail noisily.
my $issuer = "https://256.256.256.256";
my $scope = "openid postgres";
unlink($node->data_dir . '/pg_hba.conf');
$node->append_conf(
'pg_hba.conf', qq{
local all test oauth issuer="$issuer" scope="$scope"
});
$node->reload;
$node->wait_for_log(qr/reloading configuration files/);
$ENV{PGOAUTHDEBUG} = "UNSAFE";
#
# Tests
#
my $user = "test";
my $base_connstr = $node->connstr() . " user=$user";
my $common_connstr =
"$base_connstr oauth_issuer=$issuer oauth_client_id=myID";
sub test
{
my ($test_name, %params) = @_;
my $flags = [];
if (defined($params{flags}))
{
$flags = $params{flags};
}
my @cmd = ("oauth_hook_client", @{$flags}, $common_connstr);
note "running '" . join("' '", @cmd) . "'";
my $log_start = -s $node->logfile;
my ($stdout, $stderr) = run_command(\@cmd);
if (defined($params{expected_stdout}))
{
like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
if (defined($params{expected_stderr}))
{
like($stderr, $params{expected_stderr}, "$test_name: stderr matches");
}
else
{
is($stderr, "", "$test_name: no stderr");
}
if (defined($params{log_like}))
{
# See Cluster::connect_fails(). To avoid races, we have to wait for the
# postmaster to flush the log for the finished connection.
$node->wait_for_log(
qr/DEBUG: (?:00000: )?forked new client backend, pid=(\d+) socket.*DEBUG: (?:00000: )?client backend \(PID \1\) exited with exit code \d/s,
$log_start);
$node->log_check("$test_name: log matches",
$log_start, log_like => $params{log_like});
}
}
test(
"basic synchronous hook can provide a token",
flags => [
"--token", "my-token",
"--expected-uri", "$issuer/.well-known/openid-configuration",
"--expected-scope", $scope,
],
expected_stdout => qr/connection succeeded/,
log_like => [qr/oauth_validator: token="my-token", role="$user"/]);
if ($ENV{with_libcurl} ne 'yes')
{
# libpq should help users out if no OAuth support is built in.
test(
"fails without custom hook installed",
flags => ["--no-hook"],
expected_stderr =>
qr/no OAuth flows are available \(try installing the libpq-oauth package\)/
);
}
# connect_timeout should work if the flow doesn't respond.
$common_connstr = "$common_connstr connect_timeout=1";
test(
"connect_timeout interrupts hung client flow",
flags => ["--hang-forever"],
expected_stderr => qr/failed: timeout expired/);
# Remove the timeout for later tests.
$common_connstr = "$base_connstr oauth_issuer=$issuer oauth_client_id=myID";
# Test various misbehaviors of the client hook.
my @cases = (
{
flag => "--misbehave=no-hook",
expected_error =>
qr/user-defined OAuth flow provided neither a token nor an async callback/,
},
{
flag => "--misbehave=fail-async",
expected_error => qr/user-defined OAuth flow failed/,
},
{
flag => "--misbehave=no-token",
expected_error => qr/user-defined OAuth flow did not provide a token/,
},
{
flag => "--misbehave=no-socket",
expected_error =>
qr/user-defined OAuth flow did not provide a socket for polling/,
});
foreach my $c (@cases)
{
test(
"hook misbehavior: $c->{'flag'}",
flags => [ $c->{'flag'} ],
expected_stderr => $c->{'expected_error'});
}
done_testing();