From 7ca00d6a04780ae24dc293d3a6c47d35242e9ba9 Mon Sep 17 00:00:00 2001 From: Tom Most Date: Sat, 26 Dec 2020 15:01:26 -0800 Subject: [PATCH] UTF-8 encode basic auth username and password Also accept bytes to allow user control over encoding. Closes #268. --- changelog.d/268.feature.rst | 1 + src/treq/auth.py | 19 +++++++++++++------ src/treq/test/test_auth.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 changelog.d/268.feature.rst diff --git a/changelog.d/268.feature.rst b/changelog.d/268.feature.rst new file mode 100644 index 00000000..0d7ae854 --- /dev/null +++ b/changelog.d/268.feature.rst @@ -0,0 +1 @@ +The *auth* parameter now accepts arbitrary text and `bytes` for usernames and passwords. Text is encoded as UTF-8, per :rfc:`7617`. Previously only ASCII was allowed. diff --git a/src/treq/auth.py b/src/treq/auth.py index 708eb804..73e94d7f 100644 --- a/src/treq/auth.py +++ b/src/treq/auth.py @@ -2,7 +2,8 @@ # See LICENSE for details. from __future__ import absolute_import, division, print_function -import base64 +import binascii +from typing import Union from twisted.web.http_headers import Headers from twisted.web.iweb import IAgent @@ -46,6 +47,7 @@ def request(self, method, uri, headers=None, bodyProducer=None): def add_basic_auth(agent, username, password): + # type: (IAgent, Union[str, bytes], Union[str, bytes]) -> IAgent """ Wrap an agent to add HTTP basic authentication @@ -59,16 +61,21 @@ def add_basic_auth(agent, username, password): of the *Authorization* header is server-defined. :param agent: Agent to wrap. - :param username: Username as an ASCII string. - :param password: Password as an ASCII string. + :param username: The username + :param password: The password :returns: :class:`~twisted.web.iweb.IAgent` """ - creds = base64.b64encode( - '{0}:{1}'.format(username, password).encode('ascii')) + if not isinstance(username, bytes): + username = username.encode('utf-8') + if not isinstance(password, bytes): + password = password.encode('utf-8') + + creds = binascii.b2a_base64(b'%s:%s' % (username, password)).rstrip(b'\n') return _RequestHeaderSettingAgent( agent, - Headers({b'Authorization': [b'Basic ' + creds]})) + Headers({b'Authorization': [b'Basic ' + creds]}), + ) def add_auth(agent, auth_config): diff --git a/src/treq/test/test_auth.py b/src/treq/test/test_auth.py index 1bde18db..d92be2cf 100644 --- a/src/treq/test/test_auth.py +++ b/src/treq/test/test_auth.py @@ -93,6 +93,39 @@ def test_add_basic_auth_huge(self): Headers({b'authorization': [auth]}), ) + def test_add_basic_auth_utf8(self): + """ + Basic auth username and passwords given as `str` are encoded as UTF-8. + + https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#Character_encoding_of_HTTP_authentication + """ + agent, requests = recorder() + auth = (u'\u16d7', u'\u16b9') + authAgent = add_auth(agent, auth) + + authAgent.request(b'method', b'uri') + + self.assertEqual( + requests[0].headers, + Headers({b'Authorization': [b'Basic 4ZuXOuGauQ==']}), + ) + + def test_add_basic_auth_bytes(self): + """ + Basic auth can be passed as `bytes`, allowing the user full control + over the encoding. + """ + agent, requests = recorder() + auth = (b'\x01\x0f\xff', b'\xff\xf0\x01') + authAgent = add_auth(agent, auth) + + authAgent.request(b'method', b'uri') + + self.assertEqual( + requests[0].headers, + Headers({b'Authorization': [b'Basic AQ//Ov/wAQ==']}), + ) + def test_add_unknown_auth(self): """ add_auth() raises UnknownAuthConfig when given anything other than