Skip to content

Commit 54cbc00

Browse files
committed
Implement GH-17321: Add setAuthorizer to Pdo\Sqlite
1 parent 67a349d commit 54cbc00

10 files changed

+319
-14
lines changed

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ PHP NEWS
7676
Pdo\Pgsql::prepare(…, [ PDO::ATTR_PREFETCH => 0 ]) make fetch() lazy
7777
instead of storing the whole result set in memory (Guillaume Outters)
7878

79+
- PDO_SQLITE:
80+
. Implement GH-17321: Add setAuthorizer to Pdo\Sqlite. (nielsdos)
81+
7982
- PGSQL:
8083
. Added pg_close_stmt to close a prepared statement while allowing
8184
its name to be reused. (David Carlier)

UPGRADING

+3
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ PHP 8.5 UPGRADE NOTES
234234
. Added enchant_dict_remove() to put a word on the exclusion list and
235235
remove it from the session dictionary.
236236

237+
- Pdo\Sqlite:
238+
. Added support for setAuthorizer() like Sqlite3 has.
239+
237240
- PGSQL:
238241
. pg_close_stmt offers an alternative way to close a prepared
239242
statement from the DEALLOCATE sql command in that we can reuse

ext/pdo_sqlite/pdo_sqlite.c

+30
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,36 @@ PHP_METHOD(Pdo_Sqlite, openBlob)
332332
}
333333
}
334334

335+
PHP_METHOD(Pdo_Sqlite, setAuthorizer)
336+
{
337+
zend_fcall_info fci = empty_fcall_info;
338+
zend_fcall_info_cache fcc = empty_fcall_info_cache;
339+
340+
ZEND_PARSE_PARAMETERS_START(1, 1)
341+
Z_PARAM_FUNC_NO_TRAMPOLINE_FREE_OR_NULL(fci, fcc)
342+
ZEND_PARSE_PARAMETERS_END();
343+
344+
pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
345+
PDO_CONSTRUCT_CHECK_WITH_CLEANUP(free_fcc);
346+
pdo_sqlite_db_handle *db_handle = (pdo_sqlite_db_handle *) dbh->driver_data;
347+
348+
/* Clear previously set callback */
349+
if (ZEND_FCC_INITIALIZED(db_handle->authorizer_fcc)) {
350+
zend_fcc_dtor(&db_handle->authorizer_fcc);
351+
}
352+
353+
/* Only enable userland authorizer if argument is not NULL */
354+
if (ZEND_FCI_INITIALIZED(fci)) {
355+
zend_fcc_dup(&db_handle->authorizer_fcc, &fcc);
356+
}
357+
358+
return;
359+
360+
free_fcc:
361+
zend_release_fcall_info_cache(&fcc);
362+
RETURN_THROWS();
363+
}
364+
335365
static int php_sqlite_collation_callback(void *context, int string1_len, const void *string1,
336366
int string2_len, const void *string2)
337367
{

ext/pdo_sqlite/pdo_sqlite.stub.php

+12
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ class Sqlite extends \PDO
3333
/** @cvalue PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES */
3434
public const int ATTR_EXTENDED_RESULT_CODES = UNKNOWN;
3535

36+
/** @cvalue SQLITE_OK */
37+
public const int OK = UNKNOWN;
38+
39+
/* Constants for authorizer return */
40+
41+
/** @cvalue SQLITE_DENY */
42+
public const int DENY = UNKNOWN;
43+
/** @cvalue SQLITE_IGNORE */
44+
public const int IGNORE = UNKNOWN;
45+
3646
// Registers an aggregating User Defined Function for use in SQL statements
3747
public function createAggregate(
3848
string $name,
@@ -63,4 +73,6 @@ public function openBlob(
6373
?string $dbname = "main",
6474
int $flags = \Pdo\Sqlite::OPEN_READONLY
6575
) {}
76+
77+
public function setAuthorizer(?callable $callback): void {}
6678
}

ext/pdo_sqlite/pdo_sqlite_arginfo.h

+25-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/pdo_sqlite/php_pdo_sqlite_int.h

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ typedef struct {
5050
pdo_sqlite_error_info einfo;
5151
struct pdo_sqlite_func *funcs;
5252
struct pdo_sqlite_collation *collations;
53+
zend_fcall_info_cache authorizer_fcc;
5354
} pdo_sqlite_db_handle;
5455

5556
typedef struct {

ext/pdo_sqlite/sqlite_driver.c

+64-13
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ static void pdo_sqlite_cleanup_callbacks(pdo_sqlite_db_handle *H)
9797
{
9898
struct pdo_sqlite_func *func;
9999

100+
if (ZEND_FCC_INITIALIZED(H->authorizer_fcc)) {
101+
zend_fcc_dtor(&H->authorizer_fcc);
102+
}
103+
100104
while (H->funcs) {
101105
func = H->funcs;
102106
H->funcs = func->next;
@@ -701,6 +705,10 @@ static void pdo_sqlite_get_gc(pdo_dbh_t *dbh, zend_get_gc_buffer *gc_buffer)
701705
{
702706
pdo_sqlite_db_handle *H = dbh->driver_data;
703707

708+
if (ZEND_FCC_INITIALIZED(H->authorizer_fcc)) {
709+
zend_get_gc_buffer_add_fcc(gc_buffer, &H->authorizer_fcc);
710+
}
711+
704712
struct pdo_sqlite_func *func = H->funcs;
705713
while (func) {
706714
if (ZEND_FCC_INITIALIZED(func->func)) {
@@ -771,24 +779,69 @@ static char *make_filename_safe(const char *filename)
771779
return estrdup(filename);
772780
}
773781

774-
static int authorizer(void *autharg, int access_type, const char *arg3, const char *arg4,
775-
const char *arg5, const char *arg6)
782+
#define ZVAL_NULLABLE_STRING(zv, str) do { \
783+
if ((str)) { \
784+
ZVAL_STRING((zv), (str)); \
785+
} else { \
786+
ZVAL_NULL(zv); \
787+
} \
788+
} while (0)
789+
790+
static int authorizer(void *autharg, int access_type, const char *arg1, const char *arg2,
791+
const char *arg3, const char *arg4)
776792
{
777-
char *filename;
778-
switch (access_type) {
779-
case SQLITE_ATTACH: {
780-
filename = make_filename_safe(arg3);
793+
if (PG(open_basedir) && *PG(open_basedir)) {
794+
if (access_type == SQLITE_ATTACH) {
795+
char *filename = make_filename_safe(arg1);
781796
if (!filename) {
782797
return SQLITE_DENY;
783798
}
784799
efree(filename);
785-
return SQLITE_OK;
786800
}
801+
}
787802

788-
default:
789-
/* access allowed */
790-
return SQLITE_OK;
803+
pdo_sqlite_db_handle *db_obj = autharg;
804+
805+
/* fallback to access allowed if authorizer callback is not defined */
806+
if (!ZEND_FCC_INITIALIZED(db_obj->authorizer_fcc)) {
807+
return SQLITE_OK;
808+
}
809+
810+
/* call userland authorizer callback, if set */
811+
zval retval;
812+
zval argv[5];
813+
814+
ZVAL_LONG(&argv[0], access_type);
815+
ZVAL_NULLABLE_STRING(&argv[1], arg1);
816+
ZVAL_NULLABLE_STRING(&argv[2], arg2);
817+
ZVAL_NULLABLE_STRING(&argv[3], arg3);
818+
ZVAL_NULLABLE_STRING(&argv[4], arg4);
819+
820+
int authreturn = SQLITE_DENY;
821+
822+
zend_call_known_fcc(&db_obj->authorizer_fcc, &retval, /* argc */ 5, argv, /* named_params */ NULL);
823+
if (Z_ISUNDEF(retval)) {
824+
ZEND_ASSERT(EG(exception));
825+
} else {
826+
if (Z_TYPE(retval) != IS_LONG) {
827+
zend_throw_exception_ex(php_pdo_get_exception(), 0, "The authorizer callback returned an invalid type: expected int");
828+
} else {
829+
authreturn = Z_LVAL(retval);
830+
831+
if (authreturn != SQLITE_OK && authreturn != SQLITE_IGNORE && authreturn != SQLITE_DENY) {
832+
zend_throw_exception_ex(php_pdo_get_exception(), 0, "The authorizer callback returned an invalid value: %d", authreturn);
833+
authreturn = SQLITE_DENY;
834+
}
835+
}
791836
}
837+
838+
zval_ptr_dtor(&retval);
839+
zval_ptr_dtor(&argv[1]);
840+
zval_ptr_dtor(&argv[2]);
841+
zval_ptr_dtor(&argv[3]);
842+
zval_ptr_dtor(&argv[4]);
843+
844+
return authreturn;
792845
}
793846

794847
static int pdo_sqlite_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */
@@ -830,9 +883,7 @@ static int pdo_sqlite_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{
830883
goto cleanup;
831884
}
832885

833-
if (PG(open_basedir) && *PG(open_basedir)) {
834-
sqlite3_set_authorizer(H->db, authorizer, NULL);
835-
}
886+
sqlite3_set_authorizer(H->db, authorizer, H);
836887

837888
if (driver_options) {
838889
timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, timeout);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
--TEST--
2+
Pdo\Sqlite user authorizer callback
3+
--EXTENSIONS--
4+
pdo_sqlite
5+
--FILE--
6+
<?php
7+
8+
$db = new Pdo\Sqlite('sqlite::memory:');
9+
10+
$db->setAuthorizer(function (int $action) {
11+
if ($action == 21 /* SELECT */) {
12+
return Pdo\Sqlite::OK;
13+
}
14+
15+
return Pdo\Sqlite::DENY;
16+
});
17+
18+
// This query should be accepted
19+
var_dump($db->query('SELECT 1;'));
20+
21+
try {
22+
// This one should fail
23+
var_dump($db->exec('CREATE TABLE test (a, b);'));
24+
} catch (\Exception $e) {
25+
echo $e->getMessage() . "\n";
26+
}
27+
28+
// Test disabling the authorizer
29+
$db->setAuthorizer(null);
30+
31+
// This should now succeed
32+
var_dump($db->exec('CREATE TABLE test (a); INSERT INTO test VALUES (42);'));
33+
var_dump($db->exec('SELECT a FROM test;'));
34+
35+
// Test if we are getting the correct arguments
36+
$db->setAuthorizer(function (int $action) {
37+
$constants = (new ReflectionClass('SQLite3'))->getConstants();
38+
$constants = array_flip($constants);
39+
40+
var_dump($constants[$action], implode(',', array_slice(func_get_args(), 1)));
41+
return Pdo\Sqlite::OK;
42+
});
43+
44+
var_dump($db->exec('SELECT * FROM test WHERE a = 42;'));
45+
var_dump($db->exec('DROP TABLE test;'));
46+
47+
// Try to return something invalid from the authorizer
48+
$db->setAuthorizer(function () {
49+
return 'FAIL';
50+
});
51+
52+
try {
53+
var_dump($db->query('SELECT 1;'));
54+
} catch (\Exception $e) {
55+
echo $e->getMessage() . "\n";
56+
}
57+
58+
$db->setAuthorizer(function () {
59+
return 4200;
60+
});
61+
62+
try {
63+
var_dump($db->query('SELECT 1;'));
64+
} catch (\Exception $e) {
65+
echo $e->getMessage() . "\n";
66+
}
67+
68+
?>
69+
--EXPECTF--
70+
object(PDOStatement)#%d (1) {
71+
["queryString"]=>
72+
string(9) "SELECT 1;"
73+
}
74+
SQLSTATE[HY000]: General error: 23 not authorized
75+
int(1)
76+
int(1)
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+
int(1)
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+
int(1)
101+
The authorizer callback returned an invalid type: expected int
102+
The authorizer callback returned an invalid value: 4200

0 commit comments

Comments
 (0)