Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(25)

Side by Side Diff: third_party/requests/auth.py

Issue 24076010: Add 'requests' library to third_party. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/swarm_client
Patch Set: Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- coding: utf-8 -*-
2
3 """
4 requests.auth
5 ~~~~~~~~~~~~~
6
7 This module contains the authentication handlers for Requests.
8 """
9
10 import os
11 import re
12 import time
13 import hashlib
14 import logging
15
16 from base64 import b64encode
17
18 from .compat import urlparse, str
19 from .utils import parse_dict_header
20
21
22 log = logging.getLogger(__name__)
23
24 CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
25 CONTENT_TYPE_MULTI_PART = 'multipart/form-data'
26
27
28 def _basic_auth_str(username, password):
29 """Returns a Basic Auth string."""
30
31 return 'Basic ' + b64encode(('%s:%s' % (username, password)).encode('latin1' )).strip().decode('latin1')
32
33
34 class AuthBase(object):
35 """Base class that all auth implementations derive from"""
36
37 def __call__(self, r):
38 raise NotImplementedError('Auth hooks must be callable.')
39
40
41 class HTTPBasicAuth(AuthBase):
42 """Attaches HTTP Basic Authentication to the given Request object."""
43 def __init__(self, username, password):
44 self.username = username
45 self.password = password
46
47 def __call__(self, r):
48 r.headers['Authorization'] = _basic_auth_str(self.username, self.passwor d)
49 return r
50
51
52 class HTTPProxyAuth(HTTPBasicAuth):
53 """Attaches HTTP Proxy Authentication to a given Request object."""
54 def __call__(self, r):
55 r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.p assword)
56 return r
57
58
59 class HTTPDigestAuth(AuthBase):
60 """Attaches HTTP Digest Authentication to the given Request object."""
61 def __init__(self, username, password):
62 self.username = username
63 self.password = password
64 self.last_nonce = ''
65 self.nonce_count = 0
66 self.chal = {}
67
68 def build_digest_header(self, method, url):
69
70 realm = self.chal['realm']
71 nonce = self.chal['nonce']
72 qop = self.chal.get('qop')
73 algorithm = self.chal.get('algorithm')
74 opaque = self.chal.get('opaque')
75
76 if algorithm is None:
77 _algorithm = 'MD5'
78 else:
79 _algorithm = algorithm.upper()
80 # lambdas assume digest modules are imported at the top level
81 if _algorithm == 'MD5':
82 def md5_utf8(x):
83 if isinstance(x, str):
84 x = x.encode('utf-8')
85 return hashlib.md5(x).hexdigest()
86 hash_utf8 = md5_utf8
87 elif _algorithm == 'SHA':
88 def sha_utf8(x):
89 if isinstance(x, str):
90 x = x.encode('utf-8')
91 return hashlib.sha1(x).hexdigest()
92 hash_utf8 = sha_utf8
93 # XXX MD5-sess
94 KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
95
96 if hash_utf8 is None:
97 return None
98
99 # XXX not implemented yet
100 entdig = None
101 p_parsed = urlparse(url)
102 path = p_parsed.path
103 if p_parsed.query:
104 path += '?' + p_parsed.query
105
106 A1 = '%s:%s:%s' % (self.username, realm, self.password)
107 A2 = '%s:%s' % (method, path)
108
109 if qop == 'auth':
110 if nonce == self.last_nonce:
111 self.nonce_count += 1
112 else:
113 self.nonce_count = 1
114
115 ncvalue = '%08x' % self.nonce_count
116 s = str(self.nonce_count).encode('utf-8')
117 s += nonce.encode('utf-8')
118 s += time.ctime().encode('utf-8')
119 s += os.urandom(8)
120
121 cnonce = (hashlib.sha1(s).hexdigest()[:16])
122 noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, hash_utf 8(A2))
123 respdig = KD(hash_utf8(A1), noncebit)
124 elif qop is None:
125 respdig = KD(hash_utf8(A1), "%s:%s" % (nonce, hash_utf8(A2)))
126 else:
127 # XXX handle auth-int.
128 return None
129
130 self.last_nonce = nonce
131
132 # XXX should the partial digests be encoded too?
133 base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
134 'response="%s"' % (self.username, realm, nonce, path, respdig)
135 if opaque:
136 base += ', opaque="%s"' % opaque
137 if algorithm:
138 base += ', algorithm="%s"' % algorithm
139 if entdig:
140 base += ', digest="%s"' % entdig
141 if qop:
142 base += ', qop=auth, nc=%s, cnonce="%s"' % (ncvalue, cnonce)
143
144 return 'Digest %s' % (base)
145
146 def handle_401(self, r, **kwargs):
147 """Takes the given response and tries digest-auth, if needed."""
148
149 num_401_calls = getattr(self, 'num_401_calls', 1)
150 s_auth = r.headers.get('www-authenticate', '')
151
152 if 'digest' in s_auth.lower() and num_401_calls < 2:
153
154 setattr(self, 'num_401_calls', num_401_calls + 1)
155 pat = re.compile(r'digest ', flags=re.IGNORECASE)
156 self.chal = parse_dict_header(pat.sub('', s_auth, count=1))
157
158 # Consume content and release the original connection
159 # to allow our new request to reuse the same one.
160 r.content
161 r.raw.release_conn()
162
163 r.request.headers['Authorization'] = self.build_digest_header(r.requ est.method, r.request.url)
164 _r = r.connection.send(r.request, **kwargs)
165 _r.history.append(r)
166
167 return _r
168
169 setattr(self, 'num_401_calls', 1)
170 return r
171
172 def __call__(self, r):
173 # If we have a saved nonce, skip the 401
174 if self.last_nonce:
175 r.headers['Authorization'] = self.build_digest_header(r.method, r.ur l)
176 r.register_hook('response', self.handle_401)
177 return r
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698