Repository: mamba-org/quetz (language: python)

Issue:
transmutation plugin does not check permissions
so normal user can create a transmutation job with PUT /api/transmutation endpoint


Discussion / hints:
we might even remove this endpoint and replace it with POST /jobs endpoint, that would be more universal
is this still the case with the new endpoint?
no, but we still need to remove the old `/api/transmutation` endpoint

Fix patch:
diff --git a/plugins/quetz_transmutation/quetz_transmutation/api.py b/plugins/quetz_transmutation/quetz_transmutation/api.py
deleted file mode 100644
index fc8f698e..00000000
--- a/plugins/quetz_transmutation/quetz_transmutation/api.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import logging
-import pickle
-
-from fastapi import APIRouter, Depends
-
-from quetz import authorization
-from quetz.deps import get_db, get_rules
-from quetz.jobs.models import Job
-
-from .jobs import transmutation
-from .rest_models import PackageSpec
-
-router = APIRouter()
-logger = logging.getLogger("quetz.jobs")
-
-transmutation_serialized = pickle.dumps(transmutation)
-
-
-@router.put("/api/transmutation", tags=["plugins"])
-def put_transmutation(
-    package_spec: PackageSpec,
-    db=Depends(get_db),
-    auth: authorization.Rules = Depends(get_rules),
-):
-    user = auth.get_user()
-    job = Job(
-        owner_id=user,
-        manifest=transmutation_serialized,
-        items_spec=package_spec.package_spec,
-    )
-    db.add(job)
-    db.commit()
diff --git a/plugins/quetz_transmutation/quetz_transmutation/main.py b/plugins/quetz_transmutation/quetz_transmutation/main.py
deleted file mode 100644
index 3b3a0e4f..00000000
--- a/plugins/quetz_transmutation/quetz_transmutation/main.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import quetz
-
-from .api import router
-
-
-@quetz.hookimpl
-def register_router():
-    return router
-
-
-@quetz.hookimpl
-def post_add_package_version(version, condainfo):
-    # Implement your logic
-    pass
diff --git a/plugins/quetz_transmutation/setup.py b/plugins/quetz_transmutation/setup.py
index 9d0aae31..56111910 100644
--- a/plugins/quetz_transmutation/setup.py
+++ b/plugins/quetz_transmutation/setup.py
@@ -3,8 +3,8 @@
 setup(
     name="quetz-transmutation",
     install_requires="quetz",
+    version="0.1.0",
     entry_points={
-        "quetz": ["quetz-transmutation = quetz_transmutation.main"],
         "quetz.jobs": ["quetz-transmutation = quetz_transmutation.jobs"],
     },
     packages=["quetz_transmutation"],


Test patch:
diff --git a/plugins/quetz_transmutation/tests/conftest.py b/plugins/quetz_transmutation/tests/conftest.py
index e1501d71..4771e348 100644
--- a/plugins/quetz_transmutation/tests/conftest.py
+++ b/plugins/quetz_transmutation/tests/conftest.py
@@ -6,7 +6,7 @@
 from quetz.authorization import SERVER_OWNER
 from quetz.config import Config
 from quetz.dao import Dao
-from quetz.db_models import Profile, User
+from quetz.db_models import ApiKey, Profile, User
 from quetz.rest_models import Channel, Package
 from quetz.tasks.workers import SubprocessWorker
 
@@ -39,6 +39,15 @@ def user(db, user_role):
     db.commit()
 
 
+@pytest.fixture
+def api_key(db, user):
+
+    key = ApiKey(user_id=user.id, owner_id=user.id, key="apikey")
+    db.add(key)
+    db.commit()
+    return key
+
+
 @pytest.fixture
 def auth_client(client, user):
     """authenticated client"""
diff --git a/plugins/quetz_transmutation/tests/test_main.py b/plugins/quetz_transmutation/tests/test_main.py
index 2cb303a1..66e505bf 100644
--- a/plugins/quetz_transmutation/tests/test_main.py
+++ b/plugins/quetz_transmutation/tests/test_main.py
@@ -3,6 +3,12 @@
 import pytest
 from httpx import AsyncClient
 
+from quetz.authorization import (
+    SERVER_MAINTAINER,
+    SERVER_MEMBER,
+    SERVER_OWNER,
+    SERVER_USER,
+)
 from quetz.db_models import PackageVersion
 from quetz.jobs.models import Job, Task, TaskStatus
 from quetz.jobs.runner import check_status, run_jobs, run_tasks
@@ -10,13 +16,18 @@
 
 @pytest.mark.asyncio
 async def test_transmutation_endpoint(
-    auth_client, db, config, work_manager, package_version, app, channel_name
+    api_key, db, config, work_manager, package_version, app, channel_name
 ):
 
+    # we need to use asynchronous http client because the test is async
     async with AsyncClient(app=app, base_url="http://test") as ac:
-        response = await ac.put("/api/transmutation", json={"package_spec": "*"})
+        response = await ac.post(
+            "/api/jobs",
+            json={"items_spec": "*", "manifest": "quetz-transmutation:transmutation"},
+            headers={"x-api-key": api_key.key},
+        )
 
-    assert response.status_code == 200
+    assert response.status_code == 201
     run_jobs(db)
     new_jobs = run_tasks(db, work_manager)
     await new_jobs[0].wait()
@@ -68,9 +79,12 @@ def test_package_specs(
     auth_client, db, config, work_manager, package_version, spec, n_tasks
 ):
 
-    response = auth_client.put("/api/transmutation", json={"package_spec": spec})
+    response = auth_client.post(
+        "/api/jobs",
+        json={"items_spec": spec, "manifest": "quetz-transmutation:transmutation"},
+    )
 
-    assert response.status_code == 200
+    assert response.status_code == 201
     run_jobs(db)
     run_tasks(db, work_manager)
 
@@ -87,3 +101,22 @@ def test_package_specs(
         db.query(Task).delete()
     finally:
         db.commit()
+
+
+@pytest.mark.parametrize(
+    "user_role,expected_status",
+    [
+        (SERVER_OWNER, 201),
+        (SERVER_MAINTAINER, 201),
+        (SERVER_USER, 401),
+        (SERVER_MEMBER, 401),
+    ],
+)
+def test_permissions(auth_client, db, expected_status):
+
+    response = auth_client.post(
+        "/api/jobs",
+        json={"items_spec": "*", "manifest": "quetz-transmutation:transmutation"},
+    )
+
+    assert response.status_code == expected_status