mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Database queries can search FTS5 tables on SQLite 3.46
FTS5 reads `PRAGMA data_version` through the authorizer when it opens a table on SQLite 3.46 (Debian 13), so every MATCH and the schema endpoint failed with "vtable constructor failed". A denied VACUUM INTO also explains itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5a054b97bc
commit
102aded9cc
2 changed files with 24 additions and 2 deletions
|
|
@ -51,8 +51,10 @@ ALLOWED_ACTIONS = {
|
|||
sqlite3.SQLITE_FUNCTION,
|
||||
sqlite3.SQLITE_RECURSIVE,
|
||||
}
|
||||
# PRAGMAs which only describe the schema. A PRAGMA with an argument (e.g. `PRAGMA x=1`) is refused.
|
||||
# PRAGMAs which only read, whatever their argument. Every other PRAGMA (e.g. `PRAGMA foreign_keys=OFF`) is refused.
|
||||
ALLOWED_PRAGMAS = {
|
||||
# FTS5 reads this when it opens a table, through the authorizer, on SQLite 3.46 (Debian 13).
|
||||
'data_version',
|
||||
'foreign_key_list',
|
||||
'index_info',
|
||||
'index_list',
|
||||
|
|
@ -108,7 +110,7 @@ def _raise_query_error(e: sqlite3.Error):
|
|||
message = str(e)
|
||||
if isinstance(e, sqlite3.OperationalError) and message == 'interrupted':
|
||||
message = f'Query exceeded the {QUERY_TIMEOUT:g} second time limit'
|
||||
elif 'not authorized' in message:
|
||||
elif 'not authorized' in message or 'authorization denied' in message:
|
||||
message = 'Only read-only queries (SELECT) are allowed'
|
||||
elif 'too big' in message:
|
||||
message = f'A value is too large (over {MAX_VALUE_LENGTH} bytes)'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
import sqlite3
|
||||
from http import HTTPStatus
|
||||
from unittest import mock
|
||||
|
||||
|
|
@ -89,6 +90,7 @@ async def test_database_query_select(test_session, async_client, allow_database_
|
|||
'PRAGMA foreign_keys=OFF',
|
||||
'BEGIN',
|
||||
'VACUUM',
|
||||
"VACUUM INTO 'copy.db'",
|
||||
"SELECT load_extension('evil')",
|
||||
"SELECT 1; DELETE FROM tag",
|
||||
])
|
||||
|
|
@ -100,6 +102,8 @@ async def test_database_query_refuses_writes(test_session, async_client, allow_d
|
|||
request, response = await post_query(async_client, sql)
|
||||
assert response.status_code == HTTPStatus.BAD_REQUEST, response.json
|
||||
assert response.json['code'] == 'INVALID_DATABASE_QUERY'
|
||||
if ';' not in sql:
|
||||
assert response.json['error'] == 'Only read-only queries (SELECT) are allowed'
|
||||
|
||||
request, response = await post_query(async_client, 'SELECT name FROM tag')
|
||||
assert response.json['rows'] == [['one']]
|
||||
|
|
@ -210,3 +214,19 @@ async def test_database_query_memory_limits(test_session, async_client, allow_da
|
|||
assert response.status_code == HTTPStatus.OK, response.json
|
||||
assert response.json['row_count'] == 3
|
||||
assert response.json['truncated'] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_database_query_full_text_search(test_session, async_client, allow_database_query, video_factory):
|
||||
"""FTS5 tables can be searched. FTS5 reads `PRAGMA data_version` through the authorizer on some SQLite
|
||||
versions (3.46), so that PRAGMA must be allowed even where this SQLite does not ask."""
|
||||
video_factory(title='Catching rain water')
|
||||
test_session.commit()
|
||||
|
||||
sql = "SELECT fg.title FROM file_group_fts JOIN file_group fg ON fg.id = file_group_fts.rowid" \
|
||||
" WHERE file_group_fts MATCH 'water'"
|
||||
request, response = await post_query(async_client, sql)
|
||||
assert response.status_code == HTTPStatus.OK, response.json
|
||||
assert response.json['rows'] == [['Catching rain water']]
|
||||
|
||||
assert db_query._authorizer(sqlite3.SQLITE_PRAGMA, 'data_version', None, 'main', None) == sqlite3.SQLITE_OK
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue