diff options
Diffstat (limited to 'tests/api')
| -rw-r--r-- | tests/api/__init__.py | 4 | ||||
| -rw-r--r-- | tests/api/test_eval.py | 106 | 
2 files changed, 95 insertions, 15 deletions
| diff --git a/tests/api/__init__.py b/tests/api/__init__.py index 0e6e422..5f20faf 100644 --- a/tests/api/__init__.py +++ b/tests/api/__init__.py @@ -1,10 +1,10 @@  import logging -from subprocess import CompletedProcess  from unittest import mock  from falcon import testing  from snekbox.api import SnekAPI +from snekbox.process import EvalResult  class SnekAPITestCase(testing.TestCase): @@ -13,7 +13,7 @@ class SnekAPITestCase(testing.TestCase):          self.patcher = mock.patch("snekbox.api.snekapi.NsJail", autospec=True)          self.mock_nsjail = self.patcher.start() -        self.mock_nsjail.return_value.python3.return_value = CompletedProcess( +        self.mock_nsjail.return_value.python3.return_value = EvalResult(              args=[], returncode=0, stdout="output", stderr="error"          )          self.addCleanup(self.patcher.stop) diff --git a/tests/api/test_eval.py b/tests/api/test_eval.py index 976970e..37f90e7 100644 --- a/tests/api/test_eval.py +++ b/tests/api/test_eval.py @@ -5,12 +5,19 @@ class TestEvalResource(SnekAPITestCase):      PATH = "/eval"      def test_post_valid_200(self): -        body = {"input": "foo"} -        result = self.simulate_post(self.PATH, json=body) - -        self.assertEqual(result.status_code, 200) -        self.assertEqual("output", result.json["stdout"]) -        self.assertEqual(0, result.json["returncode"]) +        cases = [ +            {"args": ["-c", "print('output')"]}, +            {"input": "print('hello')"}, +            {"input": "print('hello')", "args": ["-c"]}, +            {"input": "print('hello')", "args": [""]}, +            {"input": "pass", "args": ["-m", "timeit"]}, +        ] +        for body in cases: +            with self.subTest(): +                result = self.simulate_post(self.PATH, json=body) +                self.assertEqual(result.status_code, 200) +                self.assertEqual("output", result.json["stdout"]) +                self.assertEqual(0, result.json["returncode"])      def test_post_invalid_schema_400(self):          body = {"stuff": "foo"} @@ -20,27 +27,100 @@ class TestEvalResource(SnekAPITestCase):          expected = {              "title": "Request data failed validation", -            "description": "'input' is a required property", +            "description": "{'stuff': 'foo'} is not valid under any of the given schemas",          }          self.assertEqual(expected, result.json)      def test_post_invalid_data_400(self): -        bodies = ({"input": 400}, {"input": "", "args": [400]}) - -        for body in bodies: +        bodies = ({"args": 400}, {"args": [], "files": [215]}) +        expects = ["400 is not of type 'array'", "215 is not of type 'object'"] +        for body, expected in zip(bodies, expects):              with self.subTest():                  result = self.simulate_post(self.PATH, json=body)                  self.assertEqual(result.status_code, 400) -                expected = { +                expected_json = {                      "title": "Request data failed validation", -                    "description": "400 is not of type 'string'", +                    "description": expected, +                } +                self.assertEqual(expected_json, result.json) + +    def test_files_path(self): +        """Normal paths should work with 200.""" +        test_paths = [ +            "file.txt", +            "./0.jpg", +            "path/to/file", +            "folder/../hm", +            "folder/./to/./somewhere", +            "traversal/but/../not/beyond/../root", +            r"backslash\\okay", +            r"backslash\okay", +            "numbers/0123456789", +        ] +        for path in test_paths: +            with self.subTest(path=path): +                body = {"args": ["test.py"], "files": [{"path": path}]} +                result = self.simulate_post(self.PATH, json=body) +                self.assertEqual(result.status_code, 200) +                self.assertEqual("output", result.json["stdout"]) +                self.assertEqual(0, result.json["returncode"]) + +    def test_files_illegal_path_traversal(self): +        """Traversal beyond root should be denied with 400 error.""" +        test_paths = [ +            "../secrets", +            "../../dir", +            "dir/../../secrets", +            "dir/var/../../../file", +        ] +        for path in test_paths: +            with self.subTest(path=path): +                body = {"args": ["test.py"], "files": [{"path": path}]} +                result = self.simulate_post(self.PATH, json=body) +                self.assertEqual(result.status_code, 400) +                expected = { +                    "title": "Request file is invalid", +                    "description": f"File path '{path}' may not traverse beyond root",                  } -                  self.assertEqual(expected, result.json) +    def test_files_illegal_path_absolute(self): +        """Absolute file paths should 400-error at json schema validation stage.""" +        test_paths = [ +            "/", +            "/etc", +            "/etc/vars/secrets", +            "/absolute", +            "/file.bin", +        ] +        for path in test_paths: +            with self.subTest(path=path): +                body = {"args": ["test.py"], "files": [{"path": path}]} +                result = self.simulate_post(self.PATH, json=body) +                self.assertEqual(result.status_code, 400) +                self.assertEqual("Request data failed validation", result.json["title"]) +                self.assertIn("does not match", result.json["description"]) + +    def test_files_illegal_path_null_byte(self): +        """Paths containing \0 should 400-error at json schema validation stage.""" +        test_paths = [ +            r"etc/passwd\0", +            r"a\0b", +            r"\0", +            r"\\0", +            r"var/\0/path", +        ] +        for path in test_paths: +            with self.subTest(path=path): +                body = {"args": ["test.py"], "files": [{"path": path}]} +                result = self.simulate_post(self.PATH, json=body) +                self.assertEqual(result.status_code, 400) +                self.assertEqual("Request data failed validation", result.json["title"]) +                self.assertIn("does not match", result.json["description"]) +      def test_post_invalid_content_type_415(self):          body = "{'input': 'foo'}"          headers = {"Content-Type": "application/xml"} | 
