From 03bc8f82bbffd18c0ab405c05da01413097734b7 Mon Sep 17 00:00:00 2001 From: themylogin Date: Mon, 28 Oct 2024 07:47:50 +0100 Subject: [PATCH 1/2] Fix `__job_by_credential_and_id` crashing for non-full-admins and internally ran jobs --- .../middlewared/service/core_service.py | 5 ++ tests/api2/test_job_logs.py | 53 +++++++++++++++---- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/middlewared/middlewared/service/core_service.py b/src/middlewared/middlewared/service/core_service.py index 5b9723ebb03c5..4f52abc66dbd6 100644 --- a/src/middlewared/middlewared/service/core_service.py +++ b/src/middlewared/middlewared/service/core_service.py @@ -101,6 +101,11 @@ def __job_by_credential_and_id(self, credential, job_id): job = self.middleware.jobs[job_id] + if job.credentials is None: + raise CallError( + 'Only users with full administrative privileges can download internal job logs', errno.EPERM, + ) + if job.credentials.user['username'] == credential.user['username']: return job diff --git a/tests/api2/test_job_logs.py b/tests/api2/test_job_logs.py index c6240c7befff0..3d4278519d553 100644 --- a/tests/api2/test_job_logs.py +++ b/tests/api2/test_job_logs.py @@ -1,10 +1,20 @@ +import errno + +import pytest import requests +from middlewared.service_exception import CallError from middlewared.test.integration.assets.account import unprivileged_user_client -from middlewared.test.integration.utils import mock, url +from middlewared.test.integration.utils import call, mock, url + + +@pytest.fixture(scope="module") +def c(): + with unprivileged_user_client(allowlist=[{"method": "CALL", "resource": "test.test1"}]) as c: + yield c -def test_job_download_logs(): +def test_job_download_logs(c): with mock("test.test1", """ from middlewared.service import job @@ -12,16 +22,37 @@ def test_job_download_logs(): def mock(self, job, *args): job.logs_fd.write(b'Job logs') """): - with unprivileged_user_client(allowlist=[{"method": "CALL", "resource": "test.test1"}]) as c: - jid = c.call("test.test1") + jid = c.call("test.test1") + + c.call("core.job_wait", jid, job=True) + + path = c.call("core.job_download_logs", jid, 'logs.txt') - c.call("core.job_wait", jid, job=True) + r = requests.get(f"{url()}{path}") + r.raise_for_status() + + assert r.headers["Content-Disposition"] == "attachment; filename=\"logs.txt\"" + assert r.headers["Content-Type"] == "application/octet-stream" + assert r.text == "Job logs" + + +def test_job_download_logs_unprivileged_downloads_internal_logs(c): + with mock("test.test1", """ + def mock(self, *args): + job = self.middleware.call_sync("test.test2") + job.wait_sync(raise_error=True) + return job.id + """): + with mock("test.test2", """ + from middlewared.service import job - path = c.call("core.job_download_logs", jid, 'logs.txt') + @job(logs=True) + def mock(self, job, *args): + job.logs_fd.write(b'Job logs') + """): + jid = call("test.test1") - r = requests.get(f"{url()}{path}") - r.raise_for_status() + with pytest.raises(CallError) as ve: + c.call("core.job_download_logs", jid, 'logs.txt') - assert r.headers["Content-Disposition"] == "attachment; filename=\"logs.txt\"" - assert r.headers["Content-Type"] == "application/octet-stream" - assert r.text == "Job logs" + assert ve.value.errno == errno.EPERM From 197b8c851788f844fc52c02b2b03eed4ec3c969a Mon Sep 17 00:00:00 2001 From: themylogin Date: Tue, 29 Oct 2024 13:01:11 +0100 Subject: [PATCH 2/2] Fix error message wording --- src/middlewared/middlewared/service/core_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middlewared/middlewared/service/core_service.py b/src/middlewared/middlewared/service/core_service.py index 4f52abc66dbd6..2bd5494f83ae6 100644 --- a/src/middlewared/middlewared/service/core_service.py +++ b/src/middlewared/middlewared/service/core_service.py @@ -103,7 +103,7 @@ def __job_by_credential_and_id(self, credential, job_id): if job.credentials is None: raise CallError( - 'Only users with full administrative privileges can download internal job logs', errno.EPERM, + 'Only users with full administrative privileges can access internally ran jobs', errno.EPERM, ) if job.credentials.user['username'] == credential.user['username']: