mirror of https://github.com/postgres/postgres
This commit introduces trigger on login event, allowing to fire some actions right on the user connection. This can be useful for logging or connection check purposes as well as for some personalization of environment. Usage details are described in the documentation included, but shortly usage is the same as for other triggers: create function returning event_trigger and then create event trigger on login event. In order to prevent the connection time overhead when there are no triggers the commit introduces pg_database.dathasloginevt flag, which indicates database has active login triggers. This flag is set by CREATE/ALTER EVENT TRIGGER command, and unset at connection time when no active triggers found. Author: Konstantin Knizhnik, Mikhail Gribkov Discussion: https://postgr.es/m/0d46d29f-4558-3af9-9c85-7774e14a7709%40postgrespro.ru Reviewed-by: Pavel Stehule, Takayuki Tsunakawa, Greg Nancarrow, Ivan Panchenko Reviewed-by: Daniel Gustafsson, Teodor Sigaev, Robert Haas, Andres Freund Reviewed-by: Tom Lane, Andrey Sokolov, Zhihong Yu, Sergey Shinderuk Reviewed-by: Gregory Stark, Nikita Malakhov, Ted Yupull/145/head
parent
c558e6fd92
commit
e83d1b0c40
@ -0,0 +1,189 @@ |
||||
# Copyright (c) 2021-2023, PostgreSQL Global Development Group |
||||
|
||||
# Tests of authentication via login trigger. Mostly for rejection via |
||||
# exception, because this scenario cannot be covered with *.sql/*.out regress |
||||
# tests. |
||||
|
||||
use strict; |
||||
use warnings; |
||||
|
||||
use PostgreSQL::Test::Cluster; |
||||
use PostgreSQL::Test::Utils; |
||||
use Test::More; |
||||
|
||||
# Execute a psql command and compare its output towards given regexps |
||||
sub psql_command |
||||
{ |
||||
local $Test::Builder::Level = $Test::Builder::Level + 1; |
||||
my ($node, $sql, $expected_ret, $test_name, %params) = @_; |
||||
|
||||
my $connstr; |
||||
if (defined($params{connstr})) |
||||
{ |
||||
$connstr = $params{connstr}; |
||||
} |
||||
else |
||||
{ |
||||
$connstr = ''; |
||||
} |
||||
|
||||
# Execute command |
||||
my ($ret, $stdout, $stderr) = |
||||
$node->psql('postgres', $sql, connstr => "$connstr"); |
||||
|
||||
# Check return code |
||||
is($ret, $expected_ret, "$test_name: exit code $expected_ret"); |
||||
|
||||
# Check stdout |
||||
if (defined($params{log_like})) |
||||
{ |
||||
my @log_like = @{ $params{log_like} }; |
||||
while (my $regex = shift @log_like) |
||||
{ |
||||
like($stdout, $regex, "$test_name: log matches"); |
||||
} |
||||
} |
||||
if (defined($params{log_unlike})) |
||||
{ |
||||
my @log_unlike = @{ $params{log_unlike} }; |
||||
while (my $regex = shift @log_unlike) |
||||
{ |
||||
unlike($stdout, $regex, "$test_name: log unmatches"); |
||||
} |
||||
} |
||||
if (defined($params{log_exact})) |
||||
{ |
||||
is($stdout, $params{log_exact}, "$test_name: log equals"); |
||||
} |
||||
|
||||
# Check stderr |
||||
if (defined($params{err_like})) |
||||
{ |
||||
my @err_like = @{ $params{err_like} }; |
||||
while (my $regex = shift @err_like) |
||||
{ |
||||
like($stderr, $regex, "$test_name: err matches"); |
||||
} |
||||
} |
||||
if (defined($params{err_unlike})) |
||||
{ |
||||
my @err_unlike = @{ $params{err_unlike} }; |
||||
while (my $regex = shift @err_unlike) |
||||
{ |
||||
unlike($stderr, $regex, "$test_name: err unmatches"); |
||||
} |
||||
} |
||||
if (defined($params{err_exact})) |
||||
{ |
||||
is($stderr, $params{err_exact}, "$test_name: err equals"); |
||||
} |
||||
|
||||
return; |
||||
} |
||||
|
||||
# New node |
||||
my $node = PostgreSQL::Test::Cluster->new('main'); |
||||
$node->init(extra => [ '--locale=C', '--encoding=UTF8' ]); |
||||
$node->append_conf( |
||||
'postgresql.conf', q{ |
||||
wal_level = 'logical' |
||||
max_replication_slots = 4 |
||||
max_wal_senders = 4 |
||||
}); |
||||
$node->start; |
||||
|
||||
# Create temporary roles and log table |
||||
psql_command( |
||||
$node, 'CREATE ROLE alice WITH LOGIN; |
||||
CREATE ROLE mallory WITH LOGIN; |
||||
CREATE TABLE user_logins(id serial, who text); |
||||
GRANT SELECT ON user_logins TO public; |
||||
', 0, 'create tmp objects', |
||||
log_exact => '', |
||||
err_exact => ''), |
||||
; |
||||
|
||||
# Create login event function and trigger |
||||
psql_command( |
||||
$node, |
||||
'CREATE FUNCTION on_login_proc() RETURNS event_trigger AS $$ |
||||
BEGIN |
||||
INSERT INTO user_logins (who) VALUES (SESSION_USER); |
||||
IF SESSION_USER = \'mallory\' THEN |
||||
RAISE EXCEPTION \'Hello %! You are NOT welcome here!\', SESSION_USER; |
||||
END IF; |
||||
RAISE NOTICE \'Hello %! You are welcome!\', SESSION_USER; |
||||
END; |
||||
$$ LANGUAGE plpgsql SECURITY DEFINER; |
||||
', 0, 'create trigger function', |
||||
log_exact => '', |
||||
err_exact => ''); |
||||
|
||||
psql_command( |
||||
$node, |
||||
'CREATE EVENT TRIGGER on_login_trigger ' |
||||
. 'ON login EXECUTE PROCEDURE on_login_proc();', 0, |
||||
'create event trigger', |
||||
log_exact => '', |
||||
err_exact => ''); |
||||
psql_command( |
||||
$node, 'ALTER EVENT TRIGGER on_login_trigger ENABLE ALWAYS;', 0, |
||||
'alter event trigger', |
||||
log_exact => '', |
||||
err_like => [qr/You are welcome/]); |
||||
|
||||
# Check the two requests were logged via login trigger |
||||
psql_command( |
||||
$node, 'SELECT COUNT(*) FROM user_logins;', 0, 'select count', |
||||
log_exact => '2', |
||||
err_like => [qr/You are welcome/]); |
||||
|
||||
# Try to log as allowed Alice and disallowed Mallory (two times) |
||||
psql_command( |
||||
$node, 'SELECT 1;', 0, 'try alice', |
||||
connstr => 'user=alice', |
||||
log_exact => '1', |
||||
err_like => [qr/You are welcome/], |
||||
err_unlike => [qr/You are NOT welcome/]); |
||||
psql_command( |
||||
$node, 'SELECT 1;', 2, 'try mallory', |
||||
connstr => 'user=mallory', |
||||
log_exact => '', |
||||
err_like => [qr/You are NOT welcome/], |
||||
err_unlike => [qr/You are welcome/]); |
||||
psql_command( |
||||
$node, 'SELECT 1;', 2, 'try mallory', |
||||
connstr => 'user=mallory', |
||||
log_exact => '', |
||||
err_like => [qr/You are NOT welcome/], |
||||
err_unlike => [qr/You are welcome/]); |
||||
|
||||
# Check that Alice's login record is here, while the Mallory's one is not |
||||
psql_command( |
||||
$node, 'SELECT * FROM user_logins;', 0, 'select *', |
||||
log_like => [qr/3\|alice/], |
||||
log_unlike => [qr/mallory/], |
||||
err_like => [qr/You are welcome/]); |
||||
|
||||
# Check total number of successful logins so far |
||||
psql_command( |
||||
$node, 'SELECT COUNT(*) FROM user_logins;', 0, 'select count', |
||||
log_exact => '5', |
||||
err_like => [qr/You are welcome/]); |
||||
|
||||
# Cleanup the temporary stuff |
||||
psql_command( |
||||
$node, 'DROP EVENT TRIGGER on_login_trigger;', 0, |
||||
'drop event trigger', |
||||
log_exact => '', |
||||
err_like => [qr/You are welcome/]); |
||||
psql_command( |
||||
$node, 'DROP TABLE user_logins; |
||||
DROP FUNCTION on_login_proc; |
||||
DROP ROLE mallory; |
||||
DROP ROLE alice; |
||||
', 0, 'cleanup', |
||||
log_exact => '', |
||||
err_exact => ''); |
||||
|
||||
done_testing(); |
Loading…
Reference in new issue