Skip to content

Commit c710622

Browse files
committed
Merge pull request #112 from kxepal/better-basic-auth
Better BasicAuth tuple
2 parents aa2d9dd + 53b83eb commit c710622

File tree

5 files changed

+44
-46
lines changed

5 files changed

+44
-46
lines changed

aiohttp/client.py

+9-27
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
"""HTTP Client for asyncio."""
22

3-
__all__ = ['request', 'HttpClient', 'BasicAuth', 'BasicAuthEx']
3+
__all__ = ['request', 'HttpClient']
44

55
import asyncio
6-
import base64
76
import collections
87
import http.cookies
98
import json
@@ -29,10 +28,6 @@
2928
HTTP_PORT = 80
3029
HTTPS_PORT = 443
3130

32-
BasicAuth = collections.namedtuple('BasicAuth', ['login', 'password'])
33-
BasicAuthEx = collections.namedtuple('BasicAuthEx',
34-
['login', 'password', 'encoding'])
35-
3631

3732
@asyncio.coroutine
3833
def request(method, url, *,
@@ -227,11 +222,7 @@ def update_host(self, url):
227222
# basic auth info
228223
if '@' in netloc:
229224
authinfo, netloc = netloc.split('@', 1)
230-
creds = authinfo.split(':', 1)
231-
if len(creds) > 1:
232-
self.auth = BasicAuth(creds[0], creds[1])
233-
else:
234-
self.auth = BasicAuth(creds[0], '')
225+
self.auth = helpers.BasicAuth(*authinfo.split(':', 1))
235226

236227
# Record entire netloc for usage in host header
237228
self.netloc = netloc
@@ -356,24 +347,15 @@ def update_auth(self, auth):
356347
if auth is None:
357348
return
358349

359-
if not isinstance(auth, (BasicAuth, BasicAuthEx)):
350+
if not isinstance(auth, helpers.BasicAuth):
360351
warnings.warn(
361-
'BasicAuth() or BasicAuthEx() tuple is required instead ',
362-
DeprecationWarning)
352+
'BasicAuth() tuple is required instead ', DeprecationWarning)
363353

364-
if isinstance(auth, BasicAuthEx):
365-
basic_login, basic_passwd, encoding = auth
366-
else:
367-
basic_login, basic_passwd = auth
368-
encoding = 'latin1'
369-
370-
if basic_login is not None and basic_passwd is not None:
371-
self.headers['AUTHORIZATION'] = 'Basic %s' % (
372-
base64.b64encode(
373-
('%s:%s' % (basic_login, basic_passwd)).encode(encoding))
374-
.strip().decode(encoding))
375-
elif basic_login is not None or basic_passwd is not None:
376-
raise ValueError("HTTP Auth login or password is missing")
354+
if not isinstance(auth, helpers.BasicAuth):
355+
auth = helpers.BasicAuth(*auth)
356+
357+
if auth.login:
358+
self.headers['AUTHORIZATION'] = auth.encode()
377359

378360
def update_body_from_data(self, data):
379361
if isinstance(data, str):

aiohttp/connector.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
import socket
1111
import weakref
1212

13+
from .client import ClientRequest
1314
from .errors import HttpProxyError
1415
from .errors import ProxyConnectionError
15-
from .client import ClientRequest, BasicAuth, BasicAuthEx
16+
from .helpers import BasicAuth
1617

1718

1819
class Connection(object):
@@ -285,10 +286,8 @@ def __init__(self, proxy, *args, proxy_auth=None, **kwargs):
285286
self._proxy_auth = proxy_auth
286287
assert proxy.startswith('http://'), (
287288
"Only http proxy supported", proxy)
288-
assert (proxy_auth is None
289-
or isinstance(proxy_auth, (BasicAuth, BasicAuthEx))), \
290-
("proxy_auth must be None, BasicAuth() or BasicAuthEx()",
291-
proxy_auth)
289+
assert proxy_auth is None or isinstance(proxy_auth, BasicAuth), (
290+
"proxy_auth must be None or BasicAuth() tuple", proxy_auth)
292291

293292
@property
294293
def proxy(self):

aiohttp/helpers.py

+16
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
11
"""Various helper functions"""
2+
import base64
23
import io
34
import os
45
import uuid
56
import urllib.parse
7+
from collections import namedtuple
8+
9+
10+
class BasicAuth(namedtuple('BasicAuth', ['login', 'password', 'encoding'])):
11+
def __new__(cls, login, password=None, encoding='latin1'):
12+
return super().__new__(cls, login, password, encoding)
13+
14+
def __bool__(self):
15+
return self.login is not None and self.password is not None
16+
17+
def encode(self):
18+
if not self:
19+
raise ValueError("HTTP Auth login or password is missing")
20+
creds = ('%s:%s' % (self.login, self.password)).encode(self.encoding)
21+
return 'Basic %s' % base64.b64encode(creds).decode(self.encoding)
622

723

824
class FormData:

tests/test_client.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -391,13 +391,14 @@ def test_no_path(self):
391391

392392
def test_basic_auth(self):
393393
req = ClientRequest('get', 'http://python.org',
394-
auth=aiohttp.BasicAuth('nkim', '1234'))
394+
auth=aiohttp.helpers.BasicAuth('nkim', '1234'))
395395
self.assertIn('AUTHORIZATION', req.headers)
396396
self.assertEqual('Basic bmtpbToxMjM0', req.headers['AUTHORIZATION'])
397397

398398
def test_basic_auth_utf8(self):
399399
req = ClientRequest('get', 'http://python.org',
400-
auth=aiohttp.BasicAuthEx('nkim', 'секрет', 'utf-8'))
400+
auth=aiohttp.helpers.BasicAuth('nkim', 'секрет',
401+
'utf-8'))
401402
self.assertIn('AUTHORIZATION', req.headers)
402403
self.assertEqual('Basic bmtpbTrRgdC10LrRgNC10YI=',
403404
req.headers['AUTHORIZATION'])
@@ -414,15 +415,16 @@ def test_basic_auth_from_url(self):
414415

415416
req = ClientRequest(
416417
'get', 'http://nkim@python.org',
417-
auth=aiohttp.BasicAuth('nkim', '1234'))
418+
auth=aiohttp.helpers.BasicAuth('nkim', '1234'))
418419
self.assertIn('AUTHORIZATION', req.headers)
419420
self.assertEqual('Basic bmtpbToxMjM0', req.headers['AUTHORIZATION'])
420421

421422
def test_basic_auth_err(self):
422423
# missing password here
423424
self.assertRaises(
424425
ValueError, ClientRequest,
425-
'get', 'http://python.org', auth=aiohttp.BasicAuth('nkim', None))
426+
'get', 'http://python.org',
427+
auth=aiohttp.helpers.BasicAuth('nkim', None))
426428

427429
def test_no_content_length(self):
428430
req = ClientRequest('get', 'http://python.org', loop=self.loop)

tests/test_connector.py

+9-10
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,8 @@ def test_proxy_auth(self):
357357
proxy_auth=('user', 'pass'),
358358
loop=unittest.mock.Mock())
359359
self.assertEqual(ctx.exception.args[0],
360-
("proxy_auth must be None, BasicAuth() "
361-
"or BasicAuthEx()", ('user', 'pass')))
360+
("proxy_auth must be None or BasicAuth() tuple",
361+
('user', 'pass')))
362362

363363

364364
@unittest.mock.patch('aiohttp.connector.ClientRequest')
@@ -371,7 +371,7 @@ def test_proxy_override_auth(self, ClientRequestMock):
371371

372372
connector = aiohttp.ProxyConnector(
373373
'http://user:pass@proxy.example.com',
374-
proxy_auth=aiohttp.BasicAuth(None, None),
374+
proxy_auth=aiohttp.helpers.BasicAuth(None, None),
375375
loop=loop_mock)
376376

377377
def check_proxy_req(*args, **kw):
@@ -398,15 +398,16 @@ def test_proxy_connection_error(self):
398398
@unittest.mock.patch('aiohttp.connector.ClientRequest')
399399
def test_auth(self, ClientRequestMock):
400400
proxy_req = ClientRequest('GET', 'http://proxy.example.com',
401-
auth=aiohttp.BasicAuth('user', 'pass'))
401+
auth=aiohttp.helpers.BasicAuth('user',
402+
'pass'))
402403
ClientRequestMock.return_value = proxy_req
403404
self.assertIn('AUTHORIZATION', proxy_req.headers)
404405
self.assertNotIn('PROXY-AUTHORIZATION', proxy_req.headers)
405406

406407
loop_mock = unittest.mock.Mock()
407408
connector = aiohttp.ProxyConnector(
408409
'http://proxy.example.com', loop=loop_mock,
409-
proxy_auth=aiohttp.BasicAuth('user', 'pass'))
410+
proxy_auth=aiohttp.helpers.BasicAuth('user', 'pass'))
410411
connector._resolve_host = resolve_mock = unittest.mock.Mock()
411412
self._fake_coroutine(resolve_mock, [unittest.mock.MagicMock()])
412413

@@ -426,15 +427,13 @@ def test_auth(self, ClientRequestMock):
426427

427428
ClientRequestMock.assert_called_with(
428429
'GET', 'http://proxy.example.com',
429-
auth=aiohttp.BasicAuth('user', 'pass'),
430+
auth=aiohttp.helpers.BasicAuth('user', 'pass'),
430431
loop=unittest.mock.ANY, headers=unittest.mock.ANY)
431432

432-
@unittest.mock.patch('aiohttp.connector.ClientRequest')
433-
def test_auth_utf8(self, ClientRequestMock):
433+
def test_auth_utf8(self):
434434
proxy_req = ClientRequest(
435435
'GET', 'http://proxy.example.com',
436-
auth=aiohttp.BasicAuthEx('юзер', 'пасс', 'utf-8'))
437-
ClientRequestMock.return_value = proxy_req
436+
auth=aiohttp.helpers.BasicAuth('юзер', 'пасс', 'utf-8'))
438437
self.assertIn('AUTHORIZATION', proxy_req.headers)
439438

440439
@unittest.mock.patch('aiohttp.connector.ClientRequest')

0 commit comments

Comments
 (0)