Skip to content
36 changes: 36 additions & 0 deletions aiohttp/helpers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Various helper functions"""
import base64
import binascii
import datetime
import functools
import io
Expand Down Expand Up @@ -31,6 +32,41 @@ def __new__(cls, login, password='', encoding='latin1'):

return super().__new__(cls, login, password, encoding)

@classmethod
def decode(cls, auth_header, encoding='latin1'):
"""Create a :class:`BasicAuth` object from an ``Authorization`` HTTP
header.

:param auth_header: The value of the ``Authorization`` header.
:type auth_header: str
Copy link
Member

@kxepal kxepal May 23, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use :param str auth_header: ... today, but aiohttp doesn't uses docstring in code, but separate files under doc/ directory.

:param encoding: The character encoding used on the password.
:type encoding: str

:returns: The decoded authentication.
:rtype: :class:`BasicAuth`

:raises ValueError: if the headers are unable to be decoded.

"""
split = auth_header.strip().split(' ')
if len(split) == 2:
if split[0].strip().lower() != 'basic':
raise ValueError('Unknown authorization method %s' % split[0])
to_decode = split[1]
elif len(split) == 1:
to_decode = split[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this case exists?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because https://github.com/rdegges/python-basicauth implements it. Happy to remove it if you want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be better strict to the specification where auth scheme is mandatory. Unlikely Authorization: Zm9vOmJhcg== will be a valid basic auth header.

else:
raise ValueError('Could not parse authorization header.')

try:
username, _, password = base64.b64decode(
to_decode.encode('ascii')
).decode(encoding).partition(':')
except binascii.Error:
raise ValueError('Invalid base64 encoding.')

return cls(username, password)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't encoding be passed here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops. Fixed.


def encode(self):
"""Encode credentials."""
creds = ('%s:%s' % (self.login, self.password)).encode(self.encoding)
Expand Down
16 changes: 16 additions & 0 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ def test_basic_auth4():
assert auth.encode() == 'Basic bmtpbTpwd2Q='


def test_basic_auth_decode():
auth = helpers.BasicAuth.decode('Basic bmtpbTpwd2Q=')
assert auth.login == 'nkim'
assert auth.password == 'pwd'


def test_basic_auth_decode_not_basic():
with pytest.raises(ValueError):
auth = helpers.BasicAuth.decode('Complex bmtpbTpwd2Q=')


def test_basic_auth_decode_bad_base64():
with pytest.raises(ValueError):
auth = helpers.BasicAuth.decode('Basic bmtpbTpwd2Q')


def test_invalid_formdata_params():
with pytest.raises(TypeError):
helpers.FormData('asdasf')
Expand Down