fix: stricter public reads
This commit is contained in:
@@ -12,7 +12,7 @@ from lnbits.core.wasm_ext.api.models import (
|
||||
PayInvoiceRequest,
|
||||
StorageAppendPublicRequest,
|
||||
StorageGetRequest,
|
||||
StoragePaginatedRequest,
|
||||
StoragePublicPaginatedRequest,
|
||||
WalletBalanceRequest,
|
||||
WebsocketPublishRequest,
|
||||
)
|
||||
@@ -79,6 +79,7 @@ async def test_host_api_filters_public_paginated_storage_rows(
|
||||
policies=[
|
||||
{
|
||||
"table_name": "messages",
|
||||
"source_id_field": "thread_id",
|
||||
"public_fields": ["id", "thread_id", "message"],
|
||||
}
|
||||
],
|
||||
@@ -87,15 +88,16 @@ async def test_host_api_filters_public_paginated_storage_rows(
|
||||
)
|
||||
|
||||
response = await api.storage_get_public_paginated(
|
||||
StoragePaginatedRequest(
|
||||
StoragePublicPaginatedRequest(
|
||||
table="messages",
|
||||
filters={"thread_id": "thread-1"},
|
||||
filters={},
|
||||
search="hello",
|
||||
search_fields=["message"],
|
||||
sort_by="id",
|
||||
descending=False,
|
||||
limit=25,
|
||||
offset=0,
|
||||
source_id="thread-1",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -103,11 +105,55 @@ async def test_host_api_filters_public_paginated_storage_rows(
|
||||
{"id": "message-1", "thread_id": "thread-1", "message": "Hello"}
|
||||
]
|
||||
assert response.total == 1
|
||||
storage_mock.assert_awaited_once()
|
||||
storage_mock.assert_awaited_once_with(
|
||||
"demoext",
|
||||
"messages",
|
||||
{"thread_id": "thread-1"},
|
||||
search="hello",
|
||||
search_fields=["message"],
|
||||
sort_by="id",
|
||||
descending=False,
|
||||
limit=25,
|
||||
offset=0,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_host_api_public_paginated_storage_rejects_private_query_fields():
|
||||
api = ExtensionHostAPI(
|
||||
"demoext",
|
||||
[
|
||||
ExtensionPermission(
|
||||
id="ext.storage.read_public",
|
||||
policies=[
|
||||
{
|
||||
"table_name": "messages",
|
||||
"source_id_field": "thread_id",
|
||||
"public_fields": ["id", "message"],
|
||||
}
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
with pytest.raises(PermissionError, match="non-public fields"):
|
||||
await api.storage_get_public_paginated(
|
||||
StoragePublicPaginatedRequest(
|
||||
table="messages",
|
||||
filters={"admin_note": "secret"},
|
||||
search=None,
|
||||
search_fields=[],
|
||||
sort_by=None,
|
||||
descending=False,
|
||||
limit=25,
|
||||
offset=0,
|
||||
source_id="thread-1",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_host_api_public_paginated_storage_requires_source_policy():
|
||||
api = ExtensionHostAPI(
|
||||
"demoext",
|
||||
[
|
||||
@@ -123,17 +169,52 @@ async def test_host_api_public_paginated_storage_rejects_private_query_fields():
|
||||
],
|
||||
)
|
||||
|
||||
with pytest.raises(PermissionError, match="non-public fields"):
|
||||
with pytest.raises(PermissionError, match="source ID field policy"):
|
||||
await api.storage_get_public_paginated(
|
||||
StoragePaginatedRequest(
|
||||
StoragePublicPaginatedRequest(
|
||||
table="messages",
|
||||
filters={"admin_note": "secret"},
|
||||
filters={},
|
||||
search=None,
|
||||
search_fields=[],
|
||||
sort_by=None,
|
||||
descending=False,
|
||||
limit=25,
|
||||
offset=0,
|
||||
source_id="thread-1",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_host_api_public_paginated_storage_rejects_conflicting_source_filter():
|
||||
api = ExtensionHostAPI(
|
||||
"demoext",
|
||||
[
|
||||
ExtensionPermission(
|
||||
id="ext.storage.read_public",
|
||||
policies=[
|
||||
{
|
||||
"table_name": "messages",
|
||||
"source_id_field": "thread_id",
|
||||
"public_fields": ["id", "message"],
|
||||
}
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
with pytest.raises(PermissionError, match="does not match source_id"):
|
||||
await api.storage_get_public_paginated(
|
||||
StoragePublicPaginatedRequest(
|
||||
table="messages",
|
||||
filters={"thread_id": "thread-2"},
|
||||
search=None,
|
||||
search_fields=[],
|
||||
sort_by=None,
|
||||
descending=False,
|
||||
limit=25,
|
||||
offset=0,
|
||||
source_id="thread-1",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ def test_validate_wasm_permissions_stores_narrower_policy_grant():
|
||||
"policies": [
|
||||
{
|
||||
"table_name": "tip_jars",
|
||||
"source_id_field": "wallet_id",
|
||||
"public_fields": ["id", "title", "description"],
|
||||
}
|
||||
],
|
||||
@@ -80,6 +81,7 @@ def test_validate_wasm_permissions_stores_narrower_policy_grant():
|
||||
policies=[
|
||||
{
|
||||
"table_name": "tip_jars",
|
||||
"source_id_field": "wallet_id",
|
||||
"public_fields": ["id", "title"],
|
||||
}
|
||||
],
|
||||
@@ -95,6 +97,7 @@ def test_validate_wasm_permissions_stores_narrower_policy_grant():
|
||||
policies=[
|
||||
{
|
||||
"table_name": "tip_jars",
|
||||
"source_id_field": "wallet_id",
|
||||
"public_fields": ["id", "title"],
|
||||
}
|
||||
],
|
||||
@@ -102,6 +105,42 @@ def test_validate_wasm_permissions_stores_narrower_policy_grant():
|
||||
]
|
||||
|
||||
|
||||
def test_validate_wasm_permissions_rejects_public_read_source_field_omission():
|
||||
ext_info = make_installable_extension("demoext")
|
||||
extension_config = _wasm_config(
|
||||
"demoext",
|
||||
[
|
||||
{
|
||||
"id": "ext.storage.read_public",
|
||||
"policies": [
|
||||
{
|
||||
"table_name": "messages",
|
||||
"source_id_field": "conversation_id",
|
||||
"public_fields": ["id", "body"],
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="broader policies"):
|
||||
validate_wasm_extension_permissions(
|
||||
ext_info,
|
||||
[
|
||||
ExtensionPermission(
|
||||
id="ext.storage.read_public",
|
||||
policies=[
|
||||
{
|
||||
"table_name": "messages",
|
||||
"public_fields": ["id", "body"],
|
||||
}
|
||||
],
|
||||
)
|
||||
],
|
||||
extension_config,
|
||||
)
|
||||
|
||||
|
||||
def test_validate_wasm_permissions_allows_narrower_public_append_grant():
|
||||
ext_info = make_installable_extension("demoext")
|
||||
extension_config = _wasm_config(
|
||||
|
||||
Reference in New Issue
Block a user