feat: public get

This commit is contained in:
Vlad Stan
2026-07-01 16:57:22 +03:00
parent 3fdf22a917
commit b6347f69e2
3 changed files with 86 additions and 7 deletions
+82 -4
View File
@@ -5,10 +5,10 @@ import json
import logging import logging
import secrets import secrets
import time import time
from collections.abc import Awaitable, Callable, Iterable from collections.abc import Awaitable, Callable, Iterable, Mapping
from dataclasses import dataclass from dataclasses import dataclass
from functools import wraps from functools import wraps
from typing import TypeVar, cast, get_type_hints from typing import Any, TypeVar, cast, get_type_hints
from pydantic import BaseModel from pydantic import BaseModel
@@ -124,13 +124,13 @@ class ExtensionAPI:
def __init__( def __init__(
self, self,
extension_id: str, extension_id: str,
permissions: Iterable[str], permissions: Iterable[Any],
*, *,
user_id: str | None = None, user_id: str | None = None,
wallet_id: str | None = None, wallet_id: str | None = None,
) -> None: ) -> None:
self.extension_id = extension_id self.extension_id = extension_id
self.permissions = set(permissions) self.permissions, self.permission_policies = self._permission_data(permissions)
self.user_id = user_id self.user_id = user_id
self.wallet_id = wallet_id self.wallet_id = wallet_id
@@ -154,6 +154,30 @@ class ExtensionAPI:
row = await storage_get_row(self.extension_id, request.table, request.id) row = await storage_get_row(self.extension_id, request.table, request.id)
return StorageGetResponse(data_json=json.dumps(row) if row else None) return StorageGetResponse(data_json=json.dumps(row) if row else None)
@extension_api_method(
method_id="storage.get_public",
namespace="storage",
name="Get public storage row",
host_name="storage_get_public",
sdk_name="getPublic",
description="Read one public row from an extension storage table.",
required_permission="ext.storage.read_public",
require_auth=False,
)
async def storage_get_public(
self, request: StorageGetRequest
) -> StorageGetResponse:
public_fields = self._public_storage_fields(request.table)
row = await storage_get_row(self.extension_id, request.table, request.id)
if not row:
return StorageGetResponse()
public_row = {
field_name: value
for field_name, value in row.items()
if field_name in public_fields
}
return StorageGetResponse(data_json=json.dumps(public_row))
@extension_api_method( @extension_api_method(
method_id="storage.set", method_id="storage.set",
namespace="storage", namespace="storage",
@@ -328,6 +352,60 @@ class ExtensionAPI:
log("extension:%s %s", self.extension_id, request.message) log("extension:%s %s", self.extension_id, request.message)
return LogResponse() return LogResponse()
@staticmethod
def _permission_data(
permissions: Iterable[Any],
) -> tuple[set[str], dict[str, dict[str, Any]]]:
permission_ids: set[str] = set()
policies: dict[str, dict[str, Any]] = {}
for permission in permissions:
if isinstance(permission, str):
permission_ids.add(permission)
continue
permission_id: str | None = None
policy: Any = None
if isinstance(permission, Mapping):
permission_id = permission.get("id") # type: ignore[assignment]
policy = permission.get("policy")
else:
permission_id = getattr(permission, "id", None)
policy = getattr(permission, "policy", None)
if not permission_id:
continue
permission_ids.add(permission_id)
if isinstance(policy, dict):
policies[permission_id] = policy
return permission_ids, policies
def _public_storage_fields(self, table: str) -> set[str]:
policy = self.permission_policies.get("ext.storage.read_public") or {}
tables = policy.get("tables")
if not isinstance(tables, list):
raise PermissionError(
"Public storage reads require a tables policy for "
"'ext.storage.read_public'."
)
for table_policy in tables:
if not isinstance(table_policy, dict):
continue
if table_policy.get("table_name") != table:
continue
public_fields = table_policy.get("public_fields")
if not isinstance(public_fields, list) or not all(
isinstance(field, str) and field for field in public_fields
):
raise PermissionError(
f"Public storage table '{table}' has no valid public fields."
)
return set(public_fields)
raise PermissionError(f"Storage table '{table}' is not publicly readable.")
def list_extension_api_methods( def list_extension_api_methods(
api_cls: type[ExtensionAPI] = ExtensionAPI, api_cls: type[ExtensionAPI] = ExtensionAPI,
+3 -3
View File
@@ -193,11 +193,11 @@ def _get_registered_extension(app: FastAPI, ext_id: str) -> WasmExtension:
return register_wasm_extension(app, ext_id) return register_wasm_extension(app, ext_id)
async def _extension_permissions(extension: WasmExtension) -> set[str]: async def _extension_permissions(extension: WasmExtension) -> list[Any]:
installed_extension = await get_installed_extension(extension.id) installed_extension = await get_installed_extension(extension.id)
if not installed_extension: if not installed_extension:
return set() return []
return {permission.id for permission in installed_extension.permissions} return installed_extension.permissions
def _user_id(user: Any | None) -> str | None: def _user_id(user: Any | None) -> str | None:
+1
View File
@@ -523,6 +523,7 @@ window.localisation.en = {
extension_permissions_request: 'This extension requests these permissions:', extension_permissions_request: 'This extension requests these permissions:',
extension_permissions_grant_install: 'Grant and install', extension_permissions_grant_install: 'Grant and install',
extension_permission_ext_storage_read: 'Read extension storage', extension_permission_ext_storage_read: 'Read extension storage',
extension_permission_ext_storage_read_public: 'Read public extension storage',
extension_permission_ext_storage_write: 'Write extension storage', extension_permission_ext_storage_write: 'Write extension storage',
extension_permission_payments_watch: 'Watch payments', extension_permission_payments_watch: 'Watch payments',
extension_permission_wallet_create_invoice: 'Create invoices', extension_permission_wallet_create_invoice: 'Create invoices',