ext/pgsql: adding pg_change_password functionality.

handy call to change an user password while taking care transparently
of the password's encryption.

close GH-14262
This commit is contained in:
David Carlier 2024-05-18 07:36:57 +01:00
parent 1d38656b6d
commit 9aa3a0d702
No known key found for this signature in database
GPG Key ID: CEF290BB40D2086B
7 changed files with 123 additions and 1 deletions

1
NEWS
View File

@ -186,6 +186,7 @@ PHP NEWS
(David Carlier)
. Added pg_result_memory_size to get the query result memory usage.
(KentarouTakeda)
. Added pg_change_password to alter an user's password. (David Carlier)
- Phar:
. Fixed bug GH-12532 (PharData created from zip has incorrect timestamp).

View File

@ -513,6 +513,10 @@ PHP 8.4 UPGRADE NOTES
. Added pcntl_getqos_class to get the QoS level (aka performance and related
energy consumption) of the current process and pcntl_setqos_class to set it.
- PGSQL:
. Added pg_change_password to alter a given user's password. It handles
transparently the password encryption from the database settings.
- Sodium:
. Added the sodium_crypto_aead_aegis128l_*() and sodium_crypto_aead_aegis256l_*()
functions to support the AEGIS family of authenticated encryption algorithms,

View File

@ -67,6 +67,7 @@ if test "$PHP_PGSQL" != "no"; then
AC_CHECK_LIB(pq, lo_truncate64, AC_DEFINE(HAVE_PG_LO64,1,[PostgreSQL 9.3 or later]))
AC_CHECK_LIB(pq, PQsetErrorContextVisibility, AC_DEFINE(HAVE_PG_CONTEXT_VISIBILITY,1,[PostgreSQL 9.6 or later]))
AC_CHECK_LIB(pq, PQresultMemorySize, AC_DEFINE(HAVE_PG_RESULT_MEMORY_SIZE,1,[PostgreSQL 12 or later]))
AC_CHECK_LIB(pq, PQchangePassword, AC_DEFINE(HAVE_PG_CHANGE_PASSWORD,1,[PostgreSQL 17 or later]))
LIBS=$old_LIBS
LDFLAGS=$old_LDFLAGS

View File

@ -39,6 +39,7 @@
#include "php_pgsql.h"
#include "php_globals.h"
#include "zend_exceptions.h"
#include "zend_attributes.h"
#ifdef HAVE_PGSQL
@ -401,6 +402,47 @@ static bool _php_pgsql_identifier_is_escaped(const char *identifier, size_t len)
return true;
}
#ifndef HAVE_PG_CHANGE_PASSWORD
static PGresult *PQchangePassword(PGconn *conn, const char *user, const char *passwd)
{
/**
* It is more appropriate to let the configured password encryption algorithm
* being picked up, so we pass NULL
*/
char *enc = PQencryptPasswordConn(conn, passwd, user, NULL);
if (!enc) {
return NULL;
}
char *fmtenc = PQescapeLiteral(conn, enc, strlen(enc));
PQfreemem(enc);
if (!fmtenc) {
return NULL;
}
char *fmtuser = PQescapeIdentifier(conn, user, strlen(user));
if (!fmtuser) {
PQfreemem(fmtenc);
return NULL;
}
char *query;
spprintf(&query, 0, "ALTER USER %s PASSWORD %s", fmtuser, fmtenc);
PGresult *pg_result = PQexec(conn, query);
efree(query);
PQfreemem(fmtuser);
PQfreemem(fmtenc);
return pg_result;
}
#endif
/* {{{ PHP_INI */
PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN( "pgsql.allow_persistent", "1", PHP_INI_SYSTEM, OnUpdateBool, allow_persistent, zend_pgsql_globals, pgsql_globals)
@ -6048,4 +6090,36 @@ PHP_FUNCTION(pg_select)
}
/* }}} */
PHP_FUNCTION(pg_change_password)
{
zval *pgsql_link;
pgsql_link_handle *link;
PGresult *pg_result;
zend_string *user, *passwd;
ZEND_PARSE_PARAMETERS_START(3, 3)
Z_PARAM_OBJECT_OF_CLASS(pgsql_link, pgsql_link_ce)
Z_PARAM_STR(user)
Z_PARAM_STR(passwd)
ZEND_PARSE_PARAMETERS_END();
if (ZSTR_LEN(user) == 0) {
zend_argument_value_error(2, "cannot be empty");
RETURN_THROWS();
}
/* it is technically possible, but better to disallow it */
if (ZSTR_LEN(passwd) == 0) {
zend_argument_value_error(3, "cannot be empty");
RETURN_THROWS();
}
link = Z_PGSQL_LINK_P(pgsql_link);
CHECK_PGSQL_LINK(link);
pg_result = PQchangePassword(link->conn, ZSTR_VAL(user), ZSTR_VAL(passwd));
RETVAL_BOOL(PQresultStatus(pg_result) == PGRES_COMMAND_OK);
PQclear(pg_result);
}
#endif

View File

@ -947,6 +947,8 @@ namespace {
#ifdef HAVE_PG_RESULT_MEMORY_SIZE
function pg_result_memory_size(PgSql\Result $result): int {}
#endif
function pg_change_password(PgSql\Connection $connection, string $user, #[\SensitiveParameter] string $password): bool {}
}
namespace PgSql {

View File

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: e06a7116c1048975cbb348ffcdb36c9b65cee659 */
* Stub hash: c5cb23b6536c1908d3dcc804f5fa176323f4db07 */
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_pg_connect, 0, 1, PgSql\\Connection, MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(0, connection_string, IS_STRING, 0)
@ -465,6 +465,12 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pg_result_memory_size, 0, 1, IS_
ZEND_END_ARG_INFO()
#endif
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pg_change_password, 0, 3, _IS_BOOL, 0)
ZEND_ARG_OBJ_INFO(0, connection, PgSql\\Connection, 0)
ZEND_ARG_TYPE_INFO(0, user, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 0)
ZEND_END_ARG_INFO()
ZEND_FUNCTION(pg_connect);
ZEND_FUNCTION(pg_pconnect);
ZEND_FUNCTION(pg_connect_poll);
@ -562,6 +568,7 @@ ZEND_FUNCTION(pg_set_error_context_visibility);
#if defined(HAVE_PG_RESULT_MEMORY_SIZE)
ZEND_FUNCTION(pg_result_memory_size);
#endif
ZEND_FUNCTION(pg_change_password);
static const zend_function_entry ext_functions[] = {
ZEND_FE(pg_connect, arginfo_pg_connect)
@ -684,6 +691,7 @@ static const zend_function_entry ext_functions[] = {
#if defined(HAVE_PG_RESULT_MEMORY_SIZE)
ZEND_FE(pg_result_memory_size, arginfo_pg_result_memory_size)
#endif
ZEND_FE(pg_change_password, arginfo_pg_change_password)
ZEND_FE_END
};
@ -810,6 +818,9 @@ static void register_pgsql_symbols(int module_number)
#if defined(HAVE_PG_CONTEXT_VISIBILITY)
REGISTER_LONG_CONSTANT("PGSQL_SHOW_CONTEXT_ALWAYS", PQSHOW_CONTEXT_ALWAYS, CONST_PERSISTENT);
#endif
zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "pg_change_password", sizeof("pg_change_password") - 1), 2, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0);
}
static zend_class_entry *register_class_PgSql_Connection(void)

View File

@ -0,0 +1,29 @@
--TEST--
Changing user password with pg_change_password
--EXTENSIONS--
pgsql
--SKIPIF--
<?php include("inc/skipif.inc"); ?>
--FILE--
<?php
include('inc/config.inc');
$conn = pg_connect($conn_str);
try {
pg_change_password($conn, "", "pass");
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}
try {
pg_change_password($conn, "user", "");
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}
var_dump(pg_change_password($conn, "inexistent_user", "postitpwd"));
?>
--EXPECT--
pg_change_password(): Argument #2 ($user) cannot be empty
pg_change_password(): Argument #3 ($password) cannot be empty
bool(false)