Skip to content

Commit

Permalink
Do not validate the results of method calls made by a full administra…
Browse files Browse the repository at this point in the history
…tor through the legacy WebSocket API (#14926)
  • Loading branch information
themylogin authored Nov 14, 2024
1 parent 14834f6 commit 0b1728d
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 6 deletions.
17 changes: 11 additions & 6 deletions src/middlewared/middlewared/api/base/handler/version.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import enum
import functools
from types import ModuleType
from typing import Callable

from middlewared.api.base import BaseModel, ForUpdateMetaclass
from .accept import validate_model
Expand Down Expand Up @@ -97,7 +99,8 @@ def adapt(self, value: dict, model_name: str, version1: str, version2: str) -> d
current_version_model = current_version.models[model_name]
except KeyError:
raise APIVersionDoesNotContainModelException(current_version.version, model_name)
value = validate_model(current_version_model, value)

value_factory = functools.partial(validate_model, current_version_model, value)

if version1_index < version2_index:
step = 1
Expand All @@ -109,14 +112,16 @@ def adapt(self, value: dict, model_name: str, version1: str, version2: str) -> d
for version_index in range(version1_index + step, version2_index + step, step):
new_version = self.versions[self.versions_history[version_index]]

value = self._adapt_model(value, model_name, current_version, new_version, direction)
value_factory = functools.partial(
self._adapt_model, value_factory, model_name, current_version, new_version, direction,
)

current_version = new_version

return value
return value_factory()

def _adapt_model(self, value: dict, model_name: str, current_version: APIVersion, new_version: APIVersion,
direction: Direction):
def _adapt_model(self, value_factory: Callable[[], dict], model_name: str, current_version: APIVersion,
new_version: APIVersion, direction: Direction):
try:
current_model = current_version.models[model_name]
except KeyError:
Expand All @@ -127,7 +132,7 @@ def _adapt_model(self, value: dict, model_name: str, current_version: APIVersion
except KeyError:
raise APIVersionDoesNotContainModelException(new_version.version, model_name) from None

return self._adapt_value(value, current_model, new_model, direction)
return self._adapt_value(value_factory(), current_model, new_model, direction)

def _adapt_value(self, value: dict, current_model: type[BaseModel], new_model: type[BaseModel],
direction: Direction):
Expand Down
43 changes: 43 additions & 0 deletions tests/api2/test_legacy_websocket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import logging

import pytest

from truenas_api_client import Client

from middlewared.test.integration.assets.api_key import api_key
from middlewared.test.integration.assets.cloud_sync import credential
from middlewared.test.integration.utils import call, mock, password, websocket_url

logger = logging.getLogger(__name__)


@pytest.fixture(scope="module")
def c():
with Client(websocket_url() + "/websocket") as c:
c.call("auth.login_ex", {
"mechanism": "PASSWORD_PLAIN",
"username": "root",
"password": password(),
})
yield c


def test_adapts_cloud_credentials(c):
with credential({
"provider": {
"type": "FTP",
"host": "localhost",
"port": 21,
"user": "test",
"pass": "",
},
}) as cred:
result = c.call("cloudsync.credentials.get_instance", cred["id"])
assert result["provider"] == "FTP"


def test_does_not_perform_output_validation_for_full_admin(c):
with api_key():
key = call("api_key.query")[0]
with mock("api_key.item_extend", return_value={**key, "invalid_field": 1}):
c.call("api_key.get_instance", key["id"])

0 comments on commit 0b1728d

Please sign in to comment.