blob: 6efd6cbbcb8a913f51af686ba52b19eb67313635 [file] [log] [blame]
Sergey Shepelev0112eff2017-05-05 06:46:43 +03001import httplib2
2import pytest
3import tests
4from six.moves import urllib
5
6
7def test_credentials():
8 c = httplib2.Credentials()
Alex Yuaa1b95b2018-07-26 23:23:35 -04009 c.add("joe", "password")
10 assert tuple(c.iter("bitworking.org"))[0] == ("joe", "password")
11 assert tuple(c.iter(""))[0] == ("joe", "password")
12 c.add("fred", "password2", "wellformedweb.org")
13 assert tuple(c.iter("bitworking.org"))[0] == ("joe", "password")
14 assert len(tuple(c.iter("bitworking.org"))) == 1
15 assert len(tuple(c.iter("wellformedweb.org"))) == 2
16 assert ("fred", "password2") in tuple(c.iter("wellformedweb.org"))
Sergey Shepelev0112eff2017-05-05 06:46:43 +030017 c.clear()
Alex Yuaa1b95b2018-07-26 23:23:35 -040018 assert len(tuple(c.iter("bitworking.org"))) == 0
19 c.add("fred", "password2", "wellformedweb.org")
20 assert ("fred", "password2") in tuple(c.iter("wellformedweb.org"))
21 assert len(tuple(c.iter("bitworking.org"))) == 0
22 assert len(tuple(c.iter(""))) == 0
Sergey Shepelev0112eff2017-05-05 06:46:43 +030023
24
25def test_basic():
26 # Test Basic Authentication
27 http = httplib2.Http()
28 password = tests.gen_password()
Alex Yuaa1b95b2018-07-26 23:23:35 -040029 handler = tests.http_reflect_with_auth(
30 allow_scheme="basic", allow_credentials=(("joe", password),)
31 )
Sergey Shepelev0112eff2017-05-05 06:46:43 +030032 with tests.server_request(handler, request_count=3) as uri:
Alex Yuaa1b95b2018-07-26 23:23:35 -040033 response, content = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +030034 assert response.status == 401
Alex Yuaa1b95b2018-07-26 23:23:35 -040035 http.add_credentials("joe", password)
36 response, content = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +030037 assert response.status == 200
38
39
40def test_basic_for_domain():
41 # Test Basic Authentication
42 http = httplib2.Http()
43 password = tests.gen_password()
Alex Yuaa1b95b2018-07-26 23:23:35 -040044 handler = tests.http_reflect_with_auth(
45 allow_scheme="basic", allow_credentials=(("joe", password),)
46 )
Sergey Shepelev0112eff2017-05-05 06:46:43 +030047 with tests.server_request(handler, request_count=4) as uri:
Alex Yuaa1b95b2018-07-26 23:23:35 -040048 response, content = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +030049 assert response.status == 401
Alex Yuaa1b95b2018-07-26 23:23:35 -040050 http.add_credentials("joe", password, "example.org")
51 response, content = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +030052 assert response.status == 401
53 domain = urllib.parse.urlparse(uri)[1]
Alex Yuaa1b95b2018-07-26 23:23:35 -040054 http.add_credentials("joe", password, domain)
55 response, content = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +030056 assert response.status == 200
57
58
59def test_basic_two_credentials():
60 # Test Basic Authentication with multiple sets of credentials
61 http = httplib2.Http()
62 password1 = tests.gen_password()
63 password2 = tests.gen_password()
Alex Yuaa1b95b2018-07-26 23:23:35 -040064 allowed = [("joe", password1)] # exploit shared mutable list
65 handler = tests.http_reflect_with_auth(
66 allow_scheme="basic", allow_credentials=allowed
67 )
Sergey Shepelev0112eff2017-05-05 06:46:43 +030068 with tests.server_request(handler, request_count=7) as uri:
Alex Yuaa1b95b2018-07-26 23:23:35 -040069 http.add_credentials("fred", password2)
70 response, content = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +030071 assert response.status == 401
Alex Yuaa1b95b2018-07-26 23:23:35 -040072 http.add_credentials("joe", password1)
73 response, content = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +030074 assert response.status == 200
Alex Yuaa1b95b2018-07-26 23:23:35 -040075 allowed[0] = ("fred", password2)
76 response, content = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +030077 assert response.status == 200
78
79
80def test_digest():
81 # Test that we support Digest Authentication
82 http = httplib2.Http()
83 password = tests.gen_password()
Alex Yuaa1b95b2018-07-26 23:23:35 -040084 handler = tests.http_reflect_with_auth(
85 allow_scheme="digest", allow_credentials=(("joe", password),)
86 )
Sergey Shepelev0112eff2017-05-05 06:46:43 +030087 with tests.server_request(handler, request_count=3) as uri:
Alex Yuaa1b95b2018-07-26 23:23:35 -040088 response, content = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +030089 assert response.status == 401
Alex Yuaa1b95b2018-07-26 23:23:35 -040090 http.add_credentials("joe", password)
91 response, content = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +030092 assert response.status == 200, content.decode()
93
94
95def test_digest_next_nonce_nc():
96 # Test that if the server sets nextnonce that we reset
97 # the nonce count back to 1
98 http = httplib2.Http()
99 password = tests.gen_password()
100 grenew_nonce = [None]
101 handler = tests.http_reflect_with_auth(
Alex Yuaa1b95b2018-07-26 23:23:35 -0400102 allow_scheme="digest",
103 allow_credentials=(("joe", password),),
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300104 out_renew_nonce=grenew_nonce,
105 )
106 with tests.server_request(handler, request_count=5) as uri:
Alex Yuaa1b95b2018-07-26 23:23:35 -0400107 http.add_credentials("joe", password)
108 response1, _ = http.request(uri, "GET")
109 info = httplib2._parse_www_authenticate(response1, "authentication-info")
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300110 assert response1.status == 200
Alex Yuaa1b95b2018-07-26 23:23:35 -0400111 assert info.get("digest", {}).get("nc") == "00000001", info
112 assert not info.get("digest", {}).get("nextnonce"), info
113 response2, _ = http.request(uri, "GET")
114 info2 = httplib2._parse_www_authenticate(response2, "authentication-info")
115 assert info2.get("digest", {}).get("nc") == "00000002", info2
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300116 grenew_nonce[0]()
Alex Yuaa1b95b2018-07-26 23:23:35 -0400117 response3, content = http.request(uri, "GET")
118 info3 = httplib2._parse_www_authenticate(response3, "authentication-info")
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300119 assert response3.status == 200
Alex Yuaa1b95b2018-07-26 23:23:35 -0400120 assert info3.get("digest", {}).get("nc") == "00000001", info3
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300121
122
123def test_digest_auth_stale():
124 # Test that we can handle a nonce becoming stale
125 http = httplib2.Http()
126 password = tests.gen_password()
127 grenew_nonce = [None]
128 requests = []
129 handler = tests.http_reflect_with_auth(
Alex Yuaa1b95b2018-07-26 23:23:35 -0400130 allow_scheme="digest",
131 allow_credentials=(("joe", password),),
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300132 out_renew_nonce=grenew_nonce,
133 out_requests=requests,
134 )
135 with tests.server_request(handler, request_count=4) as uri:
Alex Yuaa1b95b2018-07-26 23:23:35 -0400136 http.add_credentials("joe", password)
137 response, _ = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300138 assert response.status == 200
Alex Yuaa1b95b2018-07-26 23:23:35 -0400139 info = httplib2._parse_www_authenticate(
140 requests[0][1].headers, "www-authenticate"
141 )
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300142 grenew_nonce[0]()
Alex Yuaa1b95b2018-07-26 23:23:35 -0400143 response, _ = http.request(uri, "GET")
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300144 assert response.status == 200
145 assert not response.fromcache
Alex Yuaa1b95b2018-07-26 23:23:35 -0400146 assert getattr(response, "_stale_digest", False)
147 info2 = httplib2._parse_www_authenticate(
148 requests[2][1].headers, "www-authenticate"
149 )
150 nonce1 = info.get("digest", {}).get("nonce", "")
151 nonce2 = info2.get("digest", {}).get("nonce", "")
152 assert nonce1 != ""
153 assert nonce2 != ""
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300154 assert nonce1 != nonce2, (nonce1, nonce2)
155
156
157@pytest.mark.parametrize(
Alex Yuaa1b95b2018-07-26 23:23:35 -0400158 "data",
159 (
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300160 ({}, {}),
Alex Yuaa1b95b2018-07-26 23:23:35 -0400161 ({"www-authenticate": ""}, {}),
162 (
163 {
164 "www-authenticate": 'Test realm="test realm" , foo=foo ,bar="bar", baz=baz,qux=qux'
165 },
166 {
167 "test": {
168 "realm": "test realm",
169 "foo": "foo",
170 "bar": "bar",
171 "baz": "baz",
172 "qux": "qux",
173 }
174 },
175 ),
176 (
177 {"www-authenticate": 'T*!%#st realm=to*!%#en, to*!%#en="quoted string"'},
178 {"t*!%#st": {"realm": "to*!%#en", "to*!%#en": "quoted string"}},
179 ),
180 (
181 {"www-authenticate": 'Test realm="a \\"test\\" realm"'},
182 {"test": {"realm": 'a "test" realm'}},
183 ),
184 ({"www-authenticate": 'Basic realm="me"'}, {"basic": {"realm": "me"}}),
185 (
186 {"www-authenticate": 'Basic realm="me", algorithm="MD5"'},
187 {"basic": {"realm": "me", "algorithm": "MD5"}},
188 ),
189 (
190 {"www-authenticate": 'Basic realm="me", algorithm=MD5'},
191 {"basic": {"realm": "me", "algorithm": "MD5"}},
192 ),
193 (
194 {"www-authenticate": 'Basic realm="me",other="fred" '},
195 {"basic": {"realm": "me", "other": "fred"}},
196 ),
197 ({"www-authenticate": 'Basic REAlm="me" '}, {"basic": {"realm": "me"}}),
198 (
199 {
200 "www-authenticate": 'Digest realm="digest1", qop="auth,auth-int", nonce="7102dd2", opaque="e9517f"'
201 },
202 {
203 "digest": {
204 "realm": "digest1",
205 "qop": "auth,auth-int",
206 "nonce": "7102dd2",
207 "opaque": "e9517f",
208 }
209 },
210 ),
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300211 # multiple schema choice
Alex Yuaa1b95b2018-07-26 23:23:35 -0400212 (
213 {
214 "www-authenticate": 'Digest realm="multi-d", nonce="8b11d0f6", opaque="cc069c" Basic realm="multi-b" '
215 },
216 {
217 "digest": {"realm": "multi-d", "nonce": "8b11d0f6", "opaque": "cc069c"},
218 "basic": {"realm": "multi-b"},
219 },
220 ),
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300221 # FIXME
222 # comma between schemas (glue for multiple headers with same name)
223 # ({'www-authenticate': 'Digest realm="2-comma-d", qop="auth-int", nonce="c0c8ff1", Basic realm="2-comma-b"'},
224 # {'digest': {'realm': '2-comma-d', 'qop': 'auth-int', 'nonce': 'c0c8ff1'},
225 # 'basic': {'realm': '2-comma-b'}}),
226 # FIXME
227 # comma between schemas + WSSE (glue for multiple headers with same name)
228 # ({'www-authenticate': 'Digest realm="com3d", Basic realm="com3b", WSSE realm="com3w", profile="token"'},
229 # {'digest': {'realm': 'com3d'}, 'basic': {'realm': 'com3b'}, 'wsse': {'realm': 'com3w', profile': 'token'}}),
230 # FIXME
231 # multiple syntax figures
232 # ({'www-authenticate':
233 # 'Digest realm="brig", qop \t=\t"\tauth,auth-int", nonce="(*)&^&$%#",opaque="5ccc"' +
234 # ', Basic REAlm="zoo", WSSE realm="very", profile="UsernameToken"'},
235 # {'digest': {'realm': 'brig', 'qop': 'auth,auth-int', 'nonce': '(*)&^&$%#', 'opaque': '5ccc'},
236 # 'basic': {'realm': 'zoo'},
237 # 'wsse': {'realm': 'very', 'profile': 'UsernameToken'}}),
238 # more quote combos
Alex Yuaa1b95b2018-07-26 23:23:35 -0400239 (
240 {
241 "www-authenticate": 'Digest realm="myrealm", nonce="KBAA=3", algorithm=MD5, qop="auth", stale=true'
242 },
243 {
244 "digest": {
245 "realm": "myrealm",
246 "nonce": "KBAA=3",
247 "algorithm": "MD5",
248 "qop": "auth",
249 "stale": "true",
250 }
251 },
252 ),
253 ),
254 ids=lambda data: str(data[0]),
255)
256@pytest.mark.parametrize("strict", (True, False), ids=("strict", "relax"))
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300257def test_parse_www_authenticate_correct(data, strict):
258 headers, info = data
259 # FIXME: move strict to parse argument
260 httplib2.USE_WWW_AUTH_STRICT_PARSING = strict
261 try:
262 assert httplib2._parse_www_authenticate(headers) == info
263 finally:
264 httplib2.USE_WWW_AUTH_STRICT_PARSING = 0
265
266
267def test_parse_www_authenticate_malformed():
268 # TODO: test (and fix) header value 'barbqwnbm-bb...:asd' leads to dead loop
269 with tests.assert_raises(httplib2.MalformedHeader):
270 httplib2._parse_www_authenticate(
Alex Yuaa1b95b2018-07-26 23:23:35 -0400271 {
272 "www-authenticate": 'OAuth "Facebook Platform" "invalid_token" "Invalid OAuth access token."'
273 }
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300274 )
275
276
277def test_digest_object():
Alex Yuaa1b95b2018-07-26 23:23:35 -0400278 credentials = ("joe", "password")
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300279 host = None
Alex Yuaa1b95b2018-07-26 23:23:35 -0400280 request_uri = "/test/digest/"
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300281 headers = {}
282 response = {
Alex Yuaa1b95b2018-07-26 23:23:35 -0400283 "www-authenticate": 'Digest realm="myrealm", nonce="KBAA=35", algorithm=MD5, qop="auth"'
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300284 }
Alex Yuaa1b95b2018-07-26 23:23:35 -0400285 content = b""
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300286
Alex Yuaa1b95b2018-07-26 23:23:35 -0400287 d = httplib2.DigestAuthentication(
288 credentials, host, request_uri, headers, response, content, None
289 )
290 d.request("GET", request_uri, headers, content, cnonce="33033375ec278a46")
291 our_request = "authorization: " + headers["authorization"]
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300292 working_request = (
Alex Yuaa1b95b2018-07-26 23:23:35 -0400293 'authorization: Digest username="joe", realm="myrealm", '
294 'nonce="KBAA=35", uri="/test/digest/"'
295 + ', algorithm=MD5, response="de6d4a123b80801d0e94550411b6283f", '
296 'qop=auth, nc=00000001, cnonce="33033375ec278a46"'
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300297 )
298 assert our_request == working_request
299
300
301def test_digest_object_with_opaque():
Alex Yuaa1b95b2018-07-26 23:23:35 -0400302 credentials = ("joe", "password")
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300303 host = None
Alex Yuaa1b95b2018-07-26 23:23:35 -0400304 request_uri = "/digest/opaque/"
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300305 headers = {}
306 response = {
Alex Yuaa1b95b2018-07-26 23:23:35 -0400307 "www-authenticate": 'Digest realm="myrealm", nonce="30352fd", algorithm=MD5, '
308 'qop="auth", opaque="atestopaque"'
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300309 }
Alex Yuaa1b95b2018-07-26 23:23:35 -0400310 content = ""
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300311
Alex Yuaa1b95b2018-07-26 23:23:35 -0400312 d = httplib2.DigestAuthentication(
313 credentials, host, request_uri, headers, response, content, None
314 )
315 d.request("GET", request_uri, headers, content, cnonce="5ec2")
316 our_request = "authorization: " + headers["authorization"]
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300317 working_request = (
Alex Yuaa1b95b2018-07-26 23:23:35 -0400318 'authorization: Digest username="joe", realm="myrealm", '
319 'nonce="30352fd", uri="/digest/opaque/", algorithm=MD5'
320 + ', response="a1fab43041f8f3789a447f48018bee48", qop=auth, nc=00000001, '
321 'cnonce="5ec2", opaque="atestopaque"'
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300322 )
323 assert our_request == working_request
324
325
326def test_digest_object_stale():
Alex Yuaa1b95b2018-07-26 23:23:35 -0400327 credentials = ("joe", "password")
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300328 host = None
Alex Yuaa1b95b2018-07-26 23:23:35 -0400329 request_uri = "/digest/stale/"
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300330 headers = {}
331 response = httplib2.Response({})
Alex Yuaa1b95b2018-07-26 23:23:35 -0400332 response["www-authenticate"] = (
333 'Digest realm="myrealm", nonce="bd669f", '
334 'algorithm=MD5, qop="auth", stale=true'
335 )
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300336 response.status = 401
Alex Yuaa1b95b2018-07-26 23:23:35 -0400337 content = b""
338 d = httplib2.DigestAuthentication(
339 credentials, host, request_uri, headers, response, content, None
340 )
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300341 # Returns true to force a retry
342 assert d.response(response, content)
343
344
345def test_digest_object_auth_info():
Alex Yuaa1b95b2018-07-26 23:23:35 -0400346 credentials = ("joe", "password")
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300347 host = None
Alex Yuaa1b95b2018-07-26 23:23:35 -0400348 request_uri = "/digest/nextnonce/"
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300349 headers = {}
350 response = httplib2.Response({})
Alex Yuaa1b95b2018-07-26 23:23:35 -0400351 response["www-authenticate"] = (
352 'Digest realm="myrealm", nonce="barney", '
353 'algorithm=MD5, qop="auth", stale=true'
354 )
355 response["authentication-info"] = 'nextnonce="fred"'
356 content = b""
357 d = httplib2.DigestAuthentication(
358 credentials, host, request_uri, headers, response, content, None
359 )
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300360 # Returns true to force a retry
361 assert not d.response(response, content)
Alex Yuaa1b95b2018-07-26 23:23:35 -0400362 assert d.challenge["nonce"] == "fred"
363 assert d.challenge["nc"] == 1
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300364
365
366def test_wsse_algorithm():
Alex Yuaa1b95b2018-07-26 23:23:35 -0400367 digest = httplib2._wsse_username_token(
368 "d36e316282959a9ed4c89851497a717f", "2003-12-15T14:43:07Z", "taadtaadpstcsm"
369 )
370 expected = b"quR/EWLAV4xLf9Zqyw4pDmfV9OY="
Sergey Shepelev0112eff2017-05-05 06:46:43 +0300371 assert expected == digest