needed for permssions to trigger from wasm extension json

This commit is contained in:
Arc 2026-07-23 01:24:32 +01:00
parent 03daf8b218
commit 3eb3126efd
3 changed files with 132 additions and 7 deletions

View file

@ -47,6 +47,7 @@ class ExplicitRelease(BaseModel):
details_link: str | None
paid_features: str | None
pay_link: str | None
extension_type: str | None = None
def is_version_compatible(self):
return is_lnbits_version_ok(self.min_lnbits_version, self.max_lnbits_version)
@ -111,6 +112,12 @@ class ExtensionConfig(BaseModel):
def is_version_compatible(self) -> bool:
return is_lnbits_version_ok(self.min_lnbits_version, self.max_lnbits_version)
@classmethod
async def fetch_release_config(cls, url: str) -> ExtensionConfig:
error_msg = "Cannot fetch extension release config"
config = await github_api_get(url, error_msg)
return ExtensionConfig.parse_obj(config)
@classmethod
async def fetch_github_release_config(
cls, org: str, repo: str, tag_name: str
@ -118,9 +125,7 @@ class ExtensionConfig(BaseModel):
config_url = (
f"https://raw.githubusercontent.com/{org}/{repo}/{tag_name}/config.json"
)
error_msg = "Cannot fetch GitHub extension config"
config = await github_api_get(config_url, error_msg)
return ExtensionConfig.parse_obj(config)
return await cls.fetch_release_config(config_url)
class ReleasePaymentInfo(BaseModel):
@ -350,6 +355,8 @@ class ExtensionRelease(BaseModel):
repo: str | None = None
icon: str | None = None
details_link: str | None = None
extension_type: str | None = None
permissions: list[ExtensionPermission] = []
paid_features: str | None = None
pay_link: str | None = None
@ -357,6 +364,14 @@ class ExtensionRelease(BaseModel):
paid_sats: int | None = 0
payment_hash: str | None = None
def apply_config(self, config: ExtensionConfig) -> None:
self.min_lnbits_version = config.min_lnbits_version
self.max_lnbits_version = config.max_lnbits_version
self.is_version_compatible = config.is_version_compatible()
self.warning = config.warning
self.extension_type = config.extension_type
self.permissions = config.permissions
@property
def archive_url(self) -> str:
if not self.pay_link:
@ -420,6 +435,7 @@ class ExtensionRelease(BaseModel):
warning=e.warning,
html_url=e.html_url,
details_link=e.details_link,
extension_type=e.extension_type,
pay_link=e.pay_link,
paid_features=e.paid_features,
repo=e.repo,
@ -447,10 +463,7 @@ class ExtensionRelease(BaseModel):
if not config:
continue
release.min_lnbits_version = config.min_lnbits_version
release.max_lnbits_version = config.max_lnbits_version
release.is_version_compatible = config.is_version_compatible()
release.apply_config(config)
release.icon = icon_to_github_url(f"{org}/{repo}", config.tile)
return extension_releases
@ -930,6 +943,14 @@ class InstallableExtension(BaseModel):
if e.id != ext_id:
continue
explicit_release = ExtensionRelease.from_explicit_release(url, e)
if (
explicit_release.extension_type == "wasm"
and explicit_release.details_link
):
config = await ExtensionConfig.fetch_release_config(
explicit_release.details_link
)
explicit_release.apply_config(config)
await explicit_release.check_payment_requirements()
extension_releases.append(explicit_release)

View file

@ -713,6 +713,10 @@ async def get_extension_releases(ext_id: str) -> list[ExtensionRelease]:
extension_releases: list[ExtensionRelease] = (
await InstallableExtension.get_extension_releases(ext_id)
)
for release in extension_releases:
release.permissions = validate_extension_permissions(
ext_id, release.permissions
)
installed_ext = await get_installed_extension(ext_id)
if not installed_ext:

View file

@ -21,12 +21,14 @@ from lnbits.core.models import Account, CreateInvoice
from lnbits.core.models.extensions import (
CreateExtension,
CreateExtensionReview,
ExplicitRelease,
Extension,
ExtensionConfig,
ExtensionPermission,
ExtensionPermissionsUpdate,
ExtensionRelease,
InstallableExtension,
Manifest,
PayToEnableInfo,
ReleasePaymentInfo,
UserExtensionInfo,
@ -173,6 +175,104 @@ async def test_extension_api_install_details_and_release_endpoints(mocker):
assert release_info["is_version_compatible"] is True
@pytest.mark.anyio
async def test_explicit_wasm_release_loads_install_permissions(
settings,
mocker,
):
ext_id = f"wasm_{uuid4().hex[:8]}"
non_wasm_ext_id = f"python_{uuid4().hex[:8]}"
manifest_url = "https://extensions.example/manifest.json"
details_link = f"https://extensions.example/{ext_id}/config.json"
explicit_release = ExplicitRelease(
id=ext_id,
name="Explicit WASM Extension",
version="1.0.0",
archive=f"https://extensions.example/{ext_id}.zip",
hash="archive-hash",
repo=f"https://github.com/example/{ext_id}",
icon=None,
short_description="Explicit WASM release",
min_lnbits_version="0.1.0",
max_lnbits_version=None,
html_url=None,
warning=None,
info_notification=None,
critical_notification=None,
details_link=details_link,
paid_features=None,
pay_link=None,
extension_type="wasm",
)
non_wasm_release = explicit_release.copy(
update={
"id": non_wasm_ext_id,
"name": "Explicit Python Extension",
"details_link": f"https://extensions.example/{non_wasm_ext_id}/config.json",
"extension_type": None,
}
)
config_permissions = [ExtensionPermission(id="wallet.list")]
config = ExtensionConfig(
name=ext_id,
short_description="Explicit WASM release",
min_lnbits_version="0.1.0",
max_lnbits_version=None,
extension_type="wasm",
permissions=config_permissions,
)
async def fetch_manifest(url):
if url == manifest_url:
return Manifest(extensions=[explicit_release, non_wasm_release])
return Manifest()
mocker.patch.object(settings, "lnbits_extensions_manifests", [manifest_url])
mocker.patch.object(
settings,
"lnbits_extensions_builder_manifest_url",
"https://extensions.example/builder.json",
)
mocker.patch.object(
InstallableExtension,
"fetch_manifest",
mocker.AsyncMock(side_effect=fetch_manifest),
)
fetch_config_mock = mocker.patch.object(
ExtensionConfig,
"fetch_release_config",
mocker.AsyncMock(return_value=config),
)
releases = await InstallableExtension.get_extension_releases(ext_id)
assert len(releases) == 1
assert releases[0].extension_type == "wasm"
assert releases[0].permissions == config_permissions
fetch_config_mock.assert_awaited_once_with(details_link)
fetch_config_mock.reset_mock()
non_wasm_releases = await InstallableExtension.get_extension_releases(
non_wasm_ext_id
)
assert len(non_wasm_releases) == 1
assert non_wasm_releases[0].extension_type is None
assert non_wasm_releases[0].permissions == []
fetch_config_mock.assert_not_awaited()
mocker.patch.object(
InstallableExtension,
"get_extension_releases",
mocker.AsyncMock(return_value=releases),
)
mocker.patch(
"lnbits.core.views.extension_api.get_installed_extension",
mocker.AsyncMock(return_value=None),
)
api_releases = await get_extension_releases(ext_id)
assert api_releases[0].permissions == config_permissions
@pytest.mark.anyio
async def test_extension_api_installs_wasm_with_granted_permissions(
tmp_path,