Skip to content

Commit 3958592

Browse files
BohwaZcmb69
authored andcommitted
Add setAuthorizer method to SQLite3
This adds the possibility to define a userland callback that will be used to authorize or not an action on the database.
1 parent 5f2f450 commit 3958592

File tree

7 files changed

+282
-17
lines changed

7 files changed

+282
-17
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ PHP NEWS
7979
- sodium:
8080
. Fixed bug #77646 (sign_detached() strings not terminated). (Frank)
8181

82+
- SQLite3:
83+
. Added SQLite3::setAuthorizer() and respective class constants. (bohwaz)
84+
8285
- Standard:
8386
. Fixed bug #77204 (getimagesize(): Read error! should mention file path).
8487
(peter279k)

UPGRADING

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,12 @@ PHP 8.0 UPGRADE NOTES
348348
6. New Functions
349349
========================================
350350

351+
- SQLite3:
352+
. Add SQLite3::setAuthorizer() and respective class constants to set a
353+
userland callback that will be used to authorize or not an action on the
354+
database.
355+
PR: https://github.com/php/php-src/pull/4797
356+
351357
- Standard:
352358
. Added fdiv() method, which performs a floating-point division under
353359
IEEE 754 semantics. Division by zero is considered well-defined and

ext/sqlite3/php_sqlite3_structs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ typedef struct _php_sqlite3_db_object {
7171
sqlite3 *db;
7272
php_sqlite3_func *funcs;
7373
php_sqlite3_collation *collations;
74+
zend_fcall_info authorizer_fci;
75+
zend_fcall_info_cache authorizer_fcc;
7476

7577
zend_bool exception;
7678

ext/sqlite3/sqlite3.c

Lines changed: 160 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
ZEND_DECLARE_MODULE_GLOBALS(sqlite3)
3636

3737
static PHP_GINIT_FUNCTION(sqlite3);
38-
static int php_sqlite3_authorizer(void *autharg, int access_type, const char *arg3, const char *arg4, const char *arg5, const char *arg6);
38+
static int php_sqlite3_authorizer(void *autharg, int action, const char *arg1, const char *arg2, const char *arg3, const char *arg4);
3939
static void sqlite3_param_dtor(zval *data);
4040
static int php_sqlite3_compare_stmt_zval_free(php_sqlite3_free_list **free_list, zval *statement);
4141

@@ -159,10 +159,10 @@ PHP_METHOD(sqlite3, open)
159159
#endif
160160

161161
db_obj->initialised = 1;
162+
db_obj->authorizer_fci = empty_fcall_info;
163+
db_obj->authorizer_fcc = empty_fcall_info_cache;
162164

163-
if (PG(open_basedir) && *PG(open_basedir)) {
164-
sqlite3_set_authorizer(db_obj->db, php_sqlite3_authorizer, NULL);
165-
}
165+
sqlite3_set_authorizer(db_obj->db, php_sqlite3_authorizer, db_obj);
166166

167167
#if SQLITE_VERSION_NUMBER >= 3026000
168168
if (SQLITE3G(dbconfig_defensive)) {
@@ -1350,6 +1350,40 @@ PHP_METHOD(sqlite3, enableExceptions)
13501350
}
13511351
/* }}} */
13521352

1353+
/* {{{ proto bool SQLite3::setAuthorizer(mixed callback)
1354+
Register a callback function to be used as an authorizer by SQLite. The callback should return SQLite3::OK, SQLite3::IGNORE or SQLite3::DENY. */
1355+
PHP_METHOD(sqlite3, setAuthorizer)
1356+
{
1357+
php_sqlite3_db_object *db_obj;
1358+
zval *object = ZEND_THIS;
1359+
db_obj = Z_SQLITE3_DB_P(object);
1360+
zend_fcall_info fci;
1361+
zend_fcall_info_cache fcc;
1362+
1363+
SQLITE3_CHECK_INITIALIZED(db_obj, db_obj->initialised, SQLite3)
1364+
1365+
ZEND_PARSE_PARAMETERS_START(1, 1)
1366+
Z_PARAM_FUNC_EX(fci, fcc, 1, 0)
1367+
ZEND_PARSE_PARAMETERS_END();
1368+
1369+
/* Clear previously set callback */
1370+
if (ZEND_FCI_INITIALIZED(db_obj->authorizer_fci)) {
1371+
zval_ptr_dtor(&db_obj->authorizer_fci.function_name);
1372+
db_obj->authorizer_fci.size = 0;
1373+
}
1374+
1375+
/* Only enable userland authorizer if argument is not NULL */
1376+
if (ZEND_FCI_INITIALIZED(fci)) {
1377+
db_obj->authorizer_fci = fci;
1378+
Z_ADDREF(db_obj->authorizer_fci.function_name);
1379+
db_obj->authorizer_fcc = fcc;
1380+
}
1381+
1382+
RETURN_TRUE;
1383+
}
1384+
/* }}} */
1385+
1386+
13531387
#if SQLITE_VERSION_NUMBER >= 3006011
13541388
/* {{{ proto bool SQLite3::backup(SQLite3 destination_db[, string source_dbname = "main"[, string destination_dbname = "main"]])
13551389
Backups the current database to another one. */
@@ -2118,6 +2152,7 @@ static const zend_function_entry php_sqlite3_class_methods[] = {
21182152
PHP_ME(sqlite3, createCollation, arginfo_class_SQLite3_createCollation, ZEND_ACC_PUBLIC)
21192153
PHP_ME(sqlite3, openBlob, arginfo_class_SQLite3_openBlob, ZEND_ACC_PUBLIC)
21202154
PHP_ME(sqlite3, enableExceptions, arginfo_class_SQLite3_enableExceptions, ZEND_ACC_PUBLIC)
2155+
PHP_ME(sqlite3, setAuthorizer, arginfo_class_SQLite3_setAuthorizer, ZEND_ACC_PUBLIC)
21212156
#if SQLITE_VERSION_NUMBER >= 3006011
21222157
PHP_ME(sqlite3, backup, arginfo_class_SQLite3_backup, ZEND_ACC_PUBLIC)
21232158
#endif
@@ -2158,32 +2193,92 @@ static const zend_function_entry php_sqlite3_result_class_methods[] = {
21582193

21592194
/* {{{ Authorization Callback
21602195
*/
2161-
static int php_sqlite3_authorizer(void *autharg, int access_type, const char *arg3, const char *arg4, const char *arg5, const char *arg6)
2196+
static int php_sqlite3_authorizer(void *autharg, int action, const char *arg1, const char *arg2, const char *arg3, const char *arg4)
21622197
{
2163-
switch (access_type) {
2164-
case SQLITE_ATTACH:
2165-
{
2166-
if (memcmp(arg3, ":memory:", sizeof(":memory:")) && *arg3) {
2167-
if (strncmp(arg3, "file:", 5) == 0) {
2198+
/* Check open_basedir restrictions first */
2199+
if (PG(open_basedir) && *PG(open_basedir)) {
2200+
if (action == SQLITE_ATTACH) {
2201+
if (memcmp(arg1, ":memory:", sizeof(":memory:")) && *arg1) {
2202+
if (strncmp(arg1, "file:", 5) == 0) {
21682203
/* starts with "file:" */
2169-
if (!arg3[5]) {
2204+
if (!arg1[5]) {
21702205
return SQLITE_DENY;
21712206
}
2172-
if (php_check_open_basedir(arg3 + 5)) {
2207+
if (php_check_open_basedir(arg1 + 5)) {
21732208
return SQLITE_DENY;
21742209
}
21752210
}
2176-
if (php_check_open_basedir(arg3)) {
2211+
if (php_check_open_basedir(arg1)) {
21772212
return SQLITE_DENY;
21782213
}
21792214
}
2180-
return SQLITE_OK;
21812215
}
2216+
}
21822217

2183-
default:
2184-
/* access allowed */
2185-
return SQLITE_OK;
2218+
php_sqlite3_db_object *db_obj = (php_sqlite3_db_object *)autharg;
2219+
zend_fcall_info *fci = &db_obj->authorizer_fci;
2220+
2221+
/* fallback to access allowed if authorizer callback is not defined */
2222+
if (fci->size == 0) {
2223+
return SQLITE_OK;
2224+
}
2225+
2226+
/* call userland authorizer callback, if set */
2227+
zval retval;
2228+
zval argv[5];
2229+
2230+
ZVAL_LONG(&argv[0], action);
2231+
2232+
if (NULL == arg1) {
2233+
ZVAL_NULL(&argv[1]);
2234+
} else {
2235+
ZVAL_STRING(&argv[1], arg1);
2236+
}
2237+
2238+
if (NULL == arg2) {
2239+
ZVAL_NULL(&argv[2]);
2240+
} else {
2241+
ZVAL_STRING(&argv[2], arg2);
2242+
}
2243+
2244+
if (NULL == arg3) {
2245+
ZVAL_NULL(&argv[3]);
2246+
} else {
2247+
ZVAL_STRING(&argv[3], arg3);
21862248
}
2249+
2250+
if (NULL == arg4) {
2251+
ZVAL_NULL(&argv[4]);
2252+
} else {
2253+
ZVAL_STRING(&argv[4], arg4);
2254+
}
2255+
2256+
fci->retval = &retval;
2257+
fci->param_count = 5;
2258+
fci->params = argv;
2259+
fci->no_separation = 0;
2260+
2261+
int authreturn = SQLITE_DENY;
2262+
2263+
if (zend_call_function(fci, &db_obj->authorizer_fcc) != SUCCESS || Z_ISUNDEF(retval)) {
2264+
php_sqlite3_error(db_obj, "An error occurred while invoking the authorizer callback");
2265+
} else {
2266+
if (Z_TYPE(retval) != IS_LONG) {
2267+
php_sqlite3_error(db_obj, "The authorizer callback returned an invalid type: expected int");
2268+
} else {
2269+
authreturn = Z_LVAL(retval);
2270+
2271+
if (authreturn != SQLITE_OK && authreturn != SQLITE_IGNORE && authreturn != SQLITE_DENY) {
2272+
php_sqlite3_error(db_obj, "The authorizer callback returned an invalid value");
2273+
authreturn = SQLITE_DENY;
2274+
}
2275+
}
2276+
}
2277+
2278+
zend_fcall_info_args_clear(fci, 0);
2279+
zval_ptr_dtor(&retval);
2280+
2281+
return authreturn;
21872282
}
21882283
/* }}} */
21892284

@@ -2223,6 +2318,11 @@ static void php_sqlite3_object_free_storage(zend_object *object) /* {{{ */
22232318
return;
22242319
}
22252320

2321+
/* Release function_name from authorizer */
2322+
if (intern->authorizer_fci.size > 0) {
2323+
zval_ptr_dtor(&intern->authorizer_fci.function_name);
2324+
}
2325+
22262326
while (intern->funcs) {
22272327
func = intern->funcs;
22282328
intern->funcs = func->next;
@@ -2444,6 +2544,49 @@ PHP_MINIT_FUNCTION(sqlite3)
24442544
REGISTER_LONG_CONSTANT("SQLITE3_OPEN_READWRITE", SQLITE_OPEN_READWRITE, CONST_CS | CONST_PERSISTENT);
24452545
REGISTER_LONG_CONSTANT("SQLITE3_OPEN_CREATE", SQLITE_OPEN_CREATE, CONST_CS | CONST_PERSISTENT);
24462546

2547+
/* Class constants */
2548+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "OK", sizeof("OK") - 1, SQLITE_OK);
2549+
2550+
/* Constants for authorizer return */
2551+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DENY", sizeof("DENY") - 1, SQLITE_DENY);
2552+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "IGNORE", sizeof("IGNORE") - 1, SQLITE_IGNORE);
2553+
2554+
/* Constants for authorizer actions */
2555+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_INDEX", sizeof("CREATE_INDEX") - 1, SQLITE_CREATE_INDEX);
2556+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TABLE", sizeof("CREATE_TABLE") - 1, SQLITE_CREATE_TABLE);
2557+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_INDEX", sizeof("CREATE_TEMP_INDEX") - 1, SQLITE_CREATE_TEMP_INDEX);
2558+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_TABLE", sizeof("CREATE_TEMP_TABLE") - 1, SQLITE_CREATE_TEMP_TABLE);
2559+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_TRIGGER", sizeof("CREATE_TEMP_TRIGGER") - 1, SQLITE_CREATE_TEMP_TRIGGER);
2560+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_VIEW", sizeof("CREATE_TEMP_VIEW") - 1, SQLITE_CREATE_TEMP_VIEW);
2561+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TRIGGER", sizeof("CREATE_TRIGGER") - 1, SQLITE_CREATE_TRIGGER);
2562+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_VIEW", sizeof("CREATE_VIEW") - 1, SQLITE_CREATE_VIEW);
2563+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DELETE", sizeof("DELETE") - 1, SQLITE_DELETE);
2564+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_INDEX", sizeof("DROP_INDEX") - 1, SQLITE_DROP_INDEX);
2565+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TABLE", sizeof("DROP_TABLE") - 1, SQLITE_DROP_TABLE);
2566+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_INDEX", sizeof("DROP_TEMP_INDEX") - 1, SQLITE_DROP_TEMP_INDEX);
2567+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_TABLE", sizeof("DROP_TEMP_TABLE") - 1, SQLITE_DROP_TEMP_TABLE);
2568+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_TRIGGER", sizeof("DROP_TEMP_TRIGGER") - 1, SQLITE_DROP_TEMP_TRIGGER);
2569+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_VIEW", sizeof("DROP_TEMP_VIEW") - 1, SQLITE_DROP_TEMP_VIEW);
2570+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TRIGGER", sizeof("DROP_TRIGGER") - 1, SQLITE_DROP_TRIGGER);
2571+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_VIEW", sizeof("DROP_VIEW") - 1, SQLITE_DROP_VIEW);
2572+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "INSERT", sizeof("INSERT") - 1, SQLITE_INSERT);
2573+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "PRAGMA", sizeof("PRAGMA") - 1, SQLITE_PRAGMA);
2574+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "READ", sizeof("READ") - 1, SQLITE_READ);
2575+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "SELECT", sizeof("SELECT") - 1, SQLITE_SELECT);
2576+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "TRANSACTION", sizeof("TRANSACTION") - 1, SQLITE_TRANSACTION);
2577+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "UPDATE", sizeof("UPDATE") - 1, SQLITE_UPDATE);
2578+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "ATTACH", sizeof("ATTACH") - 1, SQLITE_ATTACH);
2579+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DETACH", sizeof("DETACH") - 1, SQLITE_DETACH);
2580+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "ALTER_TABLE", sizeof("ALTER_TABLE") - 1, SQLITE_ALTER_TABLE);
2581+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "REINDEX", sizeof("REINDEX") - 1, SQLITE_REINDEX);
2582+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "ANALYZE", sizeof("ANALYZE") - 1, SQLITE_ANALYZE);
2583+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_VTABLE", sizeof("CREATE_VTABLE") - 1, SQLITE_CREATE_VTABLE);
2584+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_VTABLE", sizeof("DROP_VTABLE") - 1, SQLITE_DROP_VTABLE);
2585+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "FUNCTION", sizeof("FUNCTION") - 1, SQLITE_FUNCTION);
2586+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "SAVEPOINT", sizeof("SAVEPOINT") - 1, SQLITE_SAVEPOINT);
2587+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "COPY", sizeof("COPY") - 1, SQLITE_COPY);
2588+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "RECURSIVE", sizeof("RECURSIVE") - 1, SQLITE_RECURSIVE);
2589+
24472590
#ifdef SQLITE_DETERMINISTIC
24482591
REGISTER_LONG_CONSTANT("SQLITE3_DETERMINISTIC", SQLITE_DETERMINISTIC, CONST_CS | CONST_PERSISTENT);
24492592
#endif

ext/sqlite3/sqlite3.stub.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ function enableExceptions(bool $enableExceptions = false) {}
7070

7171
/** @return bool */
7272
function enableExtendedResultCodes(bool $enable = true) {}
73+
74+
/** @return bool */
75+
function setAuthorizer(?callable $callback) {}
7376
}
7477

7578
class SQLite3Stmt

ext/sqlite3/sqlite3_arginfo.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SQLite3_enableExtendedResultCodes, 0, 0, 0)
9191
ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0)
9292
ZEND_END_ARG_INFO()
9393

94+
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SQLite3_setAuthorizer, 0, 0, 1)
95+
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 1)
96+
ZEND_END_ARG_INFO()
97+
9498
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SQLite3Stmt___construct, 0, 0, 2)
9599
ZEND_ARG_OBJ_INFO(0, sqlite3, SQLite3, 0)
96100
ZEND_ARG_TYPE_INFO(0, sql, IS_STRING, 0)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
--TEST--
2+
SQLite3 user authorizer callback
3+
--SKIPIF--
4+
<?php require_once(__DIR__ . '/skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
8+
$db = new SQLite3(':memory:');
9+
$db->enableExceptions(true);
10+
11+
$db->setAuthorizer(function (int $action) {
12+
if ($action == SQLite3::SELECT) {
13+
return SQLite3::OK;
14+
}
15+
16+
return SQLite3::DENY;
17+
});
18+
19+
// This query should be accepted
20+
var_dump($db->querySingle('SELECT 1;'));
21+
22+
try {
23+
// This one should fail
24+
var_dump($db->querySingle('CREATE TABLE test (a, b);'));
25+
} catch (\Exception $e) {
26+
echo $e->getMessage() . "\n";
27+
}
28+
29+
// Test disabling the authorizer
30+
$db->setAuthorizer(null);
31+
32+
// This should now succeed
33+
var_dump($db->exec('CREATE TABLE test (a); INSERT INTO test VALUES (42);'));
34+
var_dump($db->querySingle('SELECT a FROM test;'));
35+
36+
// Test if we are getting the correct arguments
37+
$db->setAuthorizer(function (int $action) {
38+
$constants = (new ReflectionClass('SQLite3'))->getConstants();
39+
$constants = array_flip($constants);
40+
41+
var_dump($constants[$action], implode(',', array_slice(func_get_args(), 1)));
42+
return SQLITE3::OK;
43+
});
44+
45+
var_dump($db->exec('SELECT * FROM test WHERE a = 42;'));
46+
var_dump($db->exec('DROP TABLE test;'));
47+
48+
// Try to return something invalid from the authorizer
49+
$db->setAuthorizer(function () {
50+
return 'FAIL';
51+
});
52+
53+
try {
54+
var_dump($db->querySingle('SELECT 1;'));
55+
} catch (\Exception $e) {
56+
echo $e->getMessage() . "\n";
57+
echo $e->getPrevious()->getMessage() . "\n";
58+
}
59+
60+
$db->setAuthorizer(function () {
61+
return 4200;
62+
});
63+
64+
try {
65+
var_dump($db->querySingle('SELECT 1;'));
66+
} catch (\Exception $e) {
67+
echo $e->getMessage() . "\n";
68+
echo $e->getPrevious()->getMessage() . "\n";
69+
}
70+
71+
?>
72+
--EXPECTF--
73+
int(1)
74+
Unable to prepare statement: 23, not authorized
75+
bool(true)
76+
int(42)
77+
string(6) "SELECT"
78+
string(3) ",,,"
79+
string(4) "READ"
80+
string(12) "test,a,main,"
81+
string(4) "READ"
82+
string(12) "test,a,main,"
83+
bool(true)
84+
string(6) "DELETE"
85+
string(20) "sqlite_master,,main,"
86+
string(10) "DROP_TABLE"
87+
string(11) "test,,main,"
88+
string(6) "DELETE"
89+
string(11) "test,,main,"
90+
string(6) "DELETE"
91+
string(20) "sqlite_master,,main,"
92+
string(4) "READ"
93+
string(28) "sqlite_master,tbl_name,main,"
94+
string(4) "READ"
95+
string(24) "sqlite_master,type,main,"
96+
string(6) "UPDATE"
97+
string(28) "sqlite_master,rootpage,main,"
98+
string(4) "READ"
99+
string(28) "sqlite_master,rootpage,main,"
100+
bool(true)
101+
Unable to prepare statement: 23, not authorized
102+
The authorizer callback returned an invalid type: expected int
103+
Unable to prepare statement: 23, not authorized
104+
The authorizer callback returned an invalid value

0 commit comments

Comments
 (0)