Skip to content

Commit 7fb63b7

Browse files
author
BohwaZ
committed
Add setAuthorizer method to SQLite3, adds the possibility to define a userland callback that will be used to authorize or not an action on the database
1 parent 4152697 commit 7fb63b7

File tree

5 files changed

+266
-17
lines changed

5 files changed

+266
-17
lines changed

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: 153 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, (void*)db_obj);
166166

167167
#if SQLITE_VERSION_NUMBER >= 3026000
168168
if (SQLITE3G(dbconfig_defensive)) {
@@ -1350,6 +1350,39 @@ 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+
1361+
SQLITE3_CHECK_INITIALIZED(db_obj, db_obj->initialised, SQLite3)
1362+
1363+
zend_fcall_info fci;
1364+
zend_fcall_info_cache fcc;
1365+
1366+
ZEND_PARSE_PARAMETERS_START(1, 1)
1367+
Z_PARAM_FUNC_EX(fci, fcc, 1, 0)
1368+
ZEND_PARSE_PARAMETERS_END();
1369+
1370+
/* Disable userland authorizer if argument is NULL */
1371+
if (fci.size == 0 && db_obj->authorizer_fci.size > 0) {
1372+
zval_ptr_dtor(&db_obj->authorizer_fci.function_name);
1373+
db_obj->authorizer_fci.size = 0;
1374+
RETURN_TRUE;
1375+
}
1376+
1377+
db_obj->authorizer_fci = fci;
1378+
Z_ADDREF(db_obj->authorizer_fci.function_name);
1379+
db_obj->authorizer_fcc = fcc;
1380+
1381+
RETURN_TRUE;
1382+
}
1383+
/* }}} */
1384+
1385+
13531386
#if SQLITE_VERSION_NUMBER >= 3006011
13541387
/* {{{ proto bool SQLite3::backup(SQLite3 destination_db[, string source_dbname = "main"[, string destination_dbname = "main"]])
13551388
Backups the current database to another one. */
@@ -2118,6 +2151,7 @@ static const zend_function_entry php_sqlite3_class_methods[] = {
21182151
PHP_ME(sqlite3, createCollation, arginfo_class_SQLite3_createCollation, ZEND_ACC_PUBLIC)
21192152
PHP_ME(sqlite3, openBlob, arginfo_class_SQLite3_openBlob, ZEND_ACC_PUBLIC)
21202153
PHP_ME(sqlite3, enableExceptions, arginfo_class_SQLite3_enableExceptions, ZEND_ACC_PUBLIC)
2154+
PHP_ME(sqlite3, setAuthorizer, arginfo_class_SQLite3_setAuthorizer, ZEND_ACC_PUBLIC)
21212155
#if SQLITE_VERSION_NUMBER >= 3006011
21222156
PHP_ME(sqlite3, backup, arginfo_class_SQLite3_backup, ZEND_ACC_PUBLIC)
21232157
#endif
@@ -2158,32 +2192,91 @@ static const zend_function_entry php_sqlite3_result_class_methods[] = {
21582192

21592193
/* {{{ Authorization Callback
21602194
*/
2161-
static int php_sqlite3_authorizer(void *autharg, int access_type, const char *arg3, const char *arg4, const char *arg5, const char *arg6)
2195+
static int php_sqlite3_authorizer(void *autharg, int action, const char *arg1, const char *arg2, const char *arg3, const char *arg4)
21622196
{
2163-
switch (access_type) {
2164-
case SQLITE_ATTACH:
2165-
{
2166-
if (memcmp(arg3, ":memory:", sizeof(":memory:")) && *arg3) {
2167-
if (strncmp(arg3, "file:", 5) == 0) {
2197+
/* Check open_basedir restrictions first */
2198+
if (PG(open_basedir) && *PG(open_basedir)) {
2199+
if (action == SQLITE_ATTACH) {
2200+
if (memcmp(arg1, ":memory:", sizeof(":memory:")) && *arg1) {
2201+
if (strncmp(arg1, "file:", 5) == 0) {
21682202
/* starts with "file:" */
2169-
if (!arg3[5]) {
2203+
if (!arg1[5]) {
21702204
return SQLITE_DENY;
21712205
}
2172-
if (php_check_open_basedir(arg3 + 5)) {
2206+
if (php_check_open_basedir(arg1 + 5)) {
21732207
return SQLITE_DENY;
21742208
}
21752209
}
2176-
if (php_check_open_basedir(arg3)) {
2210+
if (php_check_open_basedir(arg1)) {
21772211
return SQLITE_DENY;
21782212
}
21792213
}
2180-
return SQLITE_OK;
21812214
}
2215+
}
21822216

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

@@ -2444,6 +2537,49 @@ PHP_MINIT_FUNCTION(sqlite3)
24442537
REGISTER_LONG_CONSTANT("SQLITE3_OPEN_READWRITE", SQLITE_OPEN_READWRITE, CONST_CS | CONST_PERSISTENT);
24452538
REGISTER_LONG_CONSTANT("SQLITE3_OPEN_CREATE", SQLITE_OPEN_CREATE, CONST_CS | CONST_PERSISTENT);
24462539

2540+
/* Class constants */
2541+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "OK", sizeof("OK") - 1, SQLITE_OK);
2542+
2543+
/* Constants for authorizer return */
2544+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DENY", sizeof("DENY") - 1, SQLITE_DENY);
2545+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "IGNORE", sizeof("IGNORE") - 1, SQLITE_IGNORE);
2546+
2547+
/* Constants for authorizer actions */
2548+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_INDEX", sizeof("CREATE_INDEX") - 1, SQLITE_CREATE_INDEX);
2549+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TABLE", sizeof("CREATE_TABLE") - 1, SQLITE_CREATE_TABLE);
2550+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_INDEX", sizeof("CREATE_TEMP_INDEX") - 1, SQLITE_CREATE_TEMP_INDEX);
2551+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_TABLE", sizeof("CREATE_TEMP_TABLE") - 1, SQLITE_CREATE_TEMP_TABLE);
2552+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_TRIGGER", sizeof("CREATE_TEMP_TRIGGER") - 1, SQLITE_CREATE_TEMP_TRIGGER);
2553+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_VIEW", sizeof("CREATE_TEMP_VIEW") - 1, SQLITE_CREATE_TEMP_VIEW);
2554+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TRIGGER", sizeof("CREATE_TRIGGER") - 1, SQLITE_CREATE_TRIGGER);
2555+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_VIEW", sizeof("CREATE_VIEW") - 1, SQLITE_CREATE_VIEW);
2556+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DELETE", sizeof("DELETE") - 1, SQLITE_DELETE);
2557+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_INDEX", sizeof("DROP_INDEX") - 1, SQLITE_DROP_INDEX);
2558+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TABLE", sizeof("DROP_TABLE") - 1, SQLITE_DROP_TABLE);
2559+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_INDEX", sizeof("DROP_TEMP_INDEX") - 1, SQLITE_DROP_TEMP_INDEX);
2560+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_TABLE", sizeof("DROP_TEMP_TABLE") - 1, SQLITE_DROP_TEMP_TABLE);
2561+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_TRIGGER", sizeof("DROP_TEMP_TRIGGER") - 1, SQLITE_DROP_TEMP_TRIGGER);
2562+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_VIEW", sizeof("DROP_TEMP_VIEW") - 1, SQLITE_DROP_TEMP_VIEW);
2563+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TRIGGER", sizeof("DROP_TRIGGER") - 1, SQLITE_DROP_TRIGGER);
2564+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_VIEW", sizeof("DROP_VIEW") - 1, SQLITE_DROP_VIEW);
2565+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "INSERT", sizeof("INSERT") - 1, SQLITE_INSERT);
2566+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "PRAGMA", sizeof("PRAGMA") - 1, SQLITE_PRAGMA);
2567+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "READ", sizeof("READ") - 1, SQLITE_READ);
2568+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "SELECT", sizeof("SELECT") - 1, SQLITE_SELECT);
2569+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "TRANSACTION", sizeof("TRANSACTION") - 1, SQLITE_TRANSACTION);
2570+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "UPDATE", sizeof("UPDATE") - 1, SQLITE_UPDATE);
2571+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "ATTACH", sizeof("ATTACH") - 1, SQLITE_ATTACH);
2572+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DETACH", sizeof("DETACH") - 1, SQLITE_DETACH);
2573+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "ALTER_TABLE", sizeof("ALTER_TABLE") - 1, SQLITE_ALTER_TABLE);
2574+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "REINDEX", sizeof("REINDEX") - 1, SQLITE_REINDEX);
2575+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "ANALYZE", sizeof("ANALYZE") - 1, SQLITE_ANALYZE);
2576+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_VTABLE", sizeof("CREATE_VTABLE") - 1, SQLITE_CREATE_VTABLE);
2577+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_VTABLE", sizeof("DROP_VTABLE") - 1, SQLITE_DROP_VTABLE);
2578+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "FUNCTION", sizeof("FUNCTION") - 1, SQLITE_FUNCTION);
2579+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "SAVEPOINT", sizeof("SAVEPOINT") - 1, SQLITE_SAVEPOINT);
2580+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "COPY", sizeof("COPY") - 1, SQLITE_COPY);
2581+
zend_declare_class_constant_long(php_sqlite3_sc_entry, "RECURSIVE", sizeof("RECURSIVE") - 1, SQLITE_RECURSIVE);
2582+
24472583
#ifdef SQLITE_DETERMINISTIC
24482584
REGISTER_LONG_CONSTANT("SQLITE3_DETERMINISTIC", SQLITE_DETERMINISTIC, CONST_CS | CONST_PERSISTENT);
24492585
#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($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
@@ -141,3 +141,7 @@ ZEND_END_ARG_INFO()
141141
#define arginfo_class_SQLite3Result_reset arginfo_class_SQLite3_close
142142

143143
#define arginfo_class_SQLite3Result_finalize arginfo_class_SQLite3_close
144+
145+
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SQLite3_setAuthorizer, 0, 0, 1)
146+
ZEND_ARG_INFO(0, callback)
147+
ZEND_END_ARG_INFO()
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)