diff --git a/README.rst b/README.rst index 1912a17..637b8f8 100644 --- a/README.rst +++ b/README.rst @@ -128,6 +128,9 @@ httpserver mode This mode is faster than ``cmd`` and a bit slower than ``tcpserver``, but you can use official MJML API https://mjml.io/api or run your own HTTP-server (for example https://github.com/danihodovic/mjml-server) to render templates. +You can use any Authentication backend supported by requests (or provide your own). +See https://requests.readthedocs.io/en/latest/user/authentication/ for details. + Configure your Django:: MJML_BACKEND_MODE = 'httpserver' diff --git a/mjml/settings.py b/mjml/settings.py index 714d92a..b3e1fa8 100644 --- a/mjml/settings.py +++ b/mjml/settings.py @@ -1,4 +1,8 @@ from django.conf import settings +try: + from requests.auth import AuthBase +except ImportError: + AuthBase = None MJML_BACKEND_MODE = getattr(settings, 'MJML_BACKEND_MODE', 'cmd') assert MJML_BACKEND_MODE in {'cmd', 'tcpserver', 'httpserver'} @@ -24,6 +28,10 @@ assert 'URL' in t and isinstance(t['URL'], str) if 'HTTP_AUTH' in t: http_auth = t['HTTP_AUTH'] - assert isinstance(http_auth, (type(None), list, tuple)) - if http_auth is not None: + if AuthBase: + assert isinstance(http_auth, (type(None), list, tuple, AuthBase)) + else: + assert isinstance(http_auth, (type(None), list, tuple)) + + if isinstance(http_auth, (list, tuple)): assert len(http_auth) == 2 and isinstance(http_auth[0], str) and isinstance(http_auth[1], str) diff --git a/mjml/tools.py b/mjml/tools.py index c064fdd..1faabc7 100644 --- a/mjml/tools.py +++ b/mjml/tools.py @@ -109,7 +109,12 @@ def _mjml_render_by_httpserver(mjml_code: str) -> str: timeouts = 0 for server_conf in servers: http_auth = server_conf.get('HTTP_AUTH') - auth = requests.auth.HTTPBasicAuth(*http_auth) if http_auth else None + + auth = ( + http_auth + if isinstance(http_auth, (type(None), requests.auth.AuthBase)) + else requests.auth.HTTPBasicAuth(*http_auth) + ) try: response = requests.post( diff --git a/testprj/tests_httpserver.py b/testprj/tests_httpserver.py index 2016295..6cb5dd2 100644 --- a/testprj/tests_httpserver.py +++ b/testprj/tests_httpserver.py @@ -67,7 +67,7 @@ def test_http_server_error(self) -> None: self.assertIn(' Tag: mj-button Message: mj-button ', str(cm.exception)) @mock.patch('requests.post') - def test_http_auth(self, post_mock) -> None: + def test_http_basic_auth(self, post_mock) -> None: with safe_change_mjml_settings(): for server_conf in mjml_settings.MJML_HTTPSERVERS: server_conf['HTTP_AUTH'] = ('testuser', 'testpassword') @@ -93,6 +93,33 @@ def test_http_auth(self, post_mock) -> None: self.assertEqual(post_mock.call_args[1]['auth'].username, 'testuser') self.assertEqual(post_mock.call_args[1]['auth'].password, 'testpassword') + @mock.patch('requests.post') + def test_http_auth(self, post_mock) -> None: + with safe_change_mjml_settings(): + for server_conf in mjml_settings.MJML_HTTPSERVERS: + server_conf['HTTP_AUTH'] = requests.auth.HTTPBasicAuth('testuser', 'testpassword') + + response = requests.Response() + response.status_code = 200 + response._content = force_bytes(json.dumps({ + 'errors': [], + 'html': 'html_string', + 'mjml': 'mjml_string', + 'mjml_version': '4.5.1', + })) + response.encoding = 'utf-8' + response.headers['Content-Type'] = 'text/html; charset=utf-8' + response.headers['Content-Length'] = len(response._content) + post_mock.return_value = response + + render_tpl(self.TPLS['simple']) + + self.assertTrue(post_mock.called) + self.assertIn('auth', post_mock.call_args[1]) + self.assertIsInstance(post_mock.call_args[1]['auth'], requests.auth.HTTPBasicAuth) + self.assertEqual(post_mock.call_args[1]['auth'].username, 'testuser') + self.assertEqual(post_mock.call_args[1]['auth'].password, 'testpassword') + @unittest.skip('to run locally') def test_public_api(self) -> None: with safe_change_mjml_settings():