diff --git a/CHANGELOG b/CHANGELOG index c9deac72..c16f5730 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ # Changelog -## v2.1.0 +## v2.2.0 ### Breaking changes @@ -11,6 +11,7 @@ - When TabPy is running with no console attached it is not failing with 500 when trying to respond with 401 status. +- tabpy.query() failing when auth is configured. ### Improvements diff --git a/tabpy/VERSION b/tabpy/VERSION index 50aea0e7..e3a4f193 100755 --- a/tabpy/VERSION +++ b/tabpy/VERSION @@ -1 +1 @@ -2.1.0 \ No newline at end of file +2.2.0 \ No newline at end of file diff --git a/tabpy/tabpy_server/handlers/evaluation_plane_handler.py b/tabpy/tabpy_server/handlers/evaluation_plane_handler.py index 2ad55568..5170f925 100644 --- a/tabpy/tabpy_server/handlers/evaluation_plane_handler.py +++ b/tabpy/tabpy_server/handlers/evaluation_plane_handler.py @@ -10,18 +10,19 @@ class RestrictedTabPy: - def __init__(self, protocol, port, logger, timeout): + def __init__(self, protocol, port, logger, timeout, headers): self.protocol = protocol self.port = port self.logger = logger self.timeout = timeout + self.headers = headers def query(self, name, *args, **kwargs): url = f"{self.protocol}://localhost:{self.port}/query/{name}" self.logger.log(logging.DEBUG, f"Querying {url}...") internal_data = {"data": args or kwargs} data = json.dumps(internal_data) - headers = {"content-type": "application/json"} + headers = self.headers response = requests.post( url=url, data=data, headers=headers, timeout=self.timeout, verify=False ) @@ -73,7 +74,6 @@ def _post_impl(self): "the format _arg1, _arg2, _argN", ) return - function_to_evaluate = f"def _user_script(tabpy{arguments_str}):\n" for u in user_code.splitlines(): function_to_evaluate += " " + u + "\n" @@ -126,7 +126,7 @@ def post(self): @gen.coroutine def _call_subprocess(self, function_to_evaluate, arguments): restricted_tabpy = RestrictedTabPy( - self.protocol, self.port, self.logger, self.eval_timeout + self.protocol, self.port, self.logger, self.eval_timeout, self.request.headers ) # Exec does not run the function, so it does not block. exec(function_to_evaluate, globals()) diff --git a/tabpy/tabpy_server/handlers/util.py b/tabpy/tabpy_server/handlers/util.py index ae9d7387..c9fc0e43 100644 --- a/tabpy/tabpy_server/handlers/util.py +++ b/tabpy/tabpy_server/handlers/util.py @@ -8,7 +8,6 @@ class AuthErrorStates(Enum): NotAuthorized = auto() NotRequired = auto() - def hash_password(username, pwd): """ Hashes password using PKDBF2 method: diff --git a/tests/integration/resources/deploy_and_evaluate_model_auth.conf b/tests/integration/resources/deploy_and_evaluate_model_auth.conf new file mode 100644 index 00000000..a15293fc --- /dev/null +++ b/tests/integration/resources/deploy_and_evaluate_model_auth.conf @@ -0,0 +1,57 @@ +[TabPy] +# TABPY_QUERY_OBJECT_PATH = /tmp/query_objects +TABPY_PORT = 9009 +# TABPY_STATE_PATH = ./tabpy/tabpy_server + +# Where static pages live +# TABPY_STATIC_PATH = ./tabpy/tabpy_server/static + +# For how to configure TabPy authentication read +# Authentication section in docs/server-config.md. +TABPY_PWD_FILE = ./tests/integration/resources/pwdfile.txt + +# To set up secure TabPy uncomment and modify the following lines. +# Note only PEM-encoded x509 certificates are supported. +# TABPY_TRANSFER_PROTOCOL = https +# TABPY_CERTIFICATE_FILE = path/to/certificate/file.crt +# TABPY_KEY_FILE = path/to/key/file.key + +# Log additional request details including caller IP, full URL, client +# end user info if provided. +# TABPY_LOG_DETAILS = true + +# Configure how long a custom script provided to the /evaluate method +# will run before throwing a TimeoutError. +# The value should be a float representing the timeout time in seconds. +#TABPY_EVALUATE_TIMEOUT = 30 + +[loggers] +keys=root + +[handlers] +keys=rootHandler,rotatingFileHandler + +[formatters] +keys=rootFormatter + +[logger_root] +level=DEBUG +handlers=rootHandler,rotatingFileHandler +qualname=root +propagete=0 + +[handler_rootHandler] +class=StreamHandler +level=DEBUG +formatter=rootFormatter +args=(sys.stdout,) + +[handler_rotatingFileHandler] +class=handlers.RotatingFileHandler +level=DEBUG +formatter=rootFormatter +args=('tabpy_log.log', 'a', 1000000, 5) + +[formatter_rootFormatter] +format=%(asctime)s [%(levelname)s] (%(filename)s:%(module)s:%(lineno)d): %(message)s +datefmt=%Y-%m-%d,%H:%M:%S diff --git a/tests/integration/test_deploy_and_evaluate_model_auth_on.py b/tests/integration/test_deploy_and_evaluate_model_auth_on.py new file mode 100644 index 00000000..56f92793 --- /dev/null +++ b/tests/integration/test_deploy_and_evaluate_model_auth_on.py @@ -0,0 +1,34 @@ +from . import integ_test_base + + +class TestDeployAndEvaluateModelAuthOn(integ_test_base.IntegTestBase): + def _get_config_file_name(self) -> str: + return "./tests/integration/resources/deploy_and_evaluate_model_auth.conf" + + def _get_port(self) -> str: + return "9009" + + def test_deploy_and_evaluate_model(self): + # Uncomment the following line to preserve + # test case output and other files (config, state, ect.) + # in system temp folder. + # self.set_delete_temp_folder(False) + + self.deploy_models(self._get_username(), self._get_password()) + + headers = { + "Content-Type": "application/json", + "Authorization": "Basic dXNlcjE6UEBzc3cwcmQ=", + "Host": "localhost:9009", + } + payload = """{ + "data": { "_arg1": ["happy", "sad", "neutral"] }, + "script": + "return tabpy.query('Sentiment Analysis',_arg1)['response']" + }""" + + conn = self._get_connection() + conn.request("POST", "/evaluate", payload, headers) + SentimentAnalysis_eval = conn.getresponse() + self.assertEqual(200, SentimentAnalysis_eval.status) + SentimentAnalysis_eval.read() diff --git a/tests/unit/server_tests/test_endpoint_handler.py b/tests/unit/server_tests/test_endpoint_handler.py index 31b778ed..2f2d20c8 100755 --- a/tests/unit/server_tests/test_endpoint_handler.py +++ b/tests/unit/server_tests/test_endpoint_handler.py @@ -162,4 +162,3 @@ def test_creds_no_auth_fails(self): }, ) self.assertEqual(400, response.code) - \ No newline at end of file