1import httplib2 2import pytest 3import tests 4from six.moves import urllib 5 6 7def test_credentials(): 8 c = httplib2.Credentials() 9 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")) 17 c.clear() 18 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 23 24 25def test_basic(): 26 # Test Basic Authentication 27 http = httplib2.Http() 28 password = tests.gen_password() 29 handler = tests.http_reflect_with_auth( 30 allow_scheme="basic", allow_credentials=(("joe", password),) 31 ) 32 with tests.server_request(handler, request_count=3) as uri: 33 response, content = http.request(uri, "GET") 34 assert response.status == 401 35 http.add_credentials("joe", password) 36 response, content = http.request(uri, "GET") 37 assert response.status == 200 38 39 40def test_basic_for_domain(): 41 # Test Basic Authentication 42 http = httplib2.Http() 43 password = tests.gen_password() 44 handler = tests.http_reflect_with_auth( 45 allow_scheme="basic", allow_credentials=(("joe", password),) 46 ) 47 with tests.server_request(handler, request_count=4) as uri: 48 response, content = http.request(uri, "GET") 49 assert response.status == 401 50 http.add_credentials("joe", password, "example.org") 51 response, content = http.request(uri, "GET") 52 assert response.status == 401 53 domain = urllib.parse.urlparse(uri)[1] 54 http.add_credentials("joe", password, domain) 55 response, content = http.request(uri, "GET") 56 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() 64 allowed = [("joe", password1)] # exploit shared mutable list 65 handler = tests.http_reflect_with_auth( 66 allow_scheme="basic", allow_credentials=allowed 67 ) 68 with tests.server_request(handler, request_count=7) as uri: 69 http.add_credentials("fred", password2) 70 response, content = http.request(uri, "GET") 71 assert response.status == 401 72 http.add_credentials("joe", password1) 73 response, content = http.request(uri, "GET") 74 assert response.status == 200 75 allowed[0] = ("fred", password2) 76 response, content = http.request(uri, "GET") 77 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() 84 handler = tests.http_reflect_with_auth( 85 allow_scheme="digest", allow_credentials=(("joe", password),) 86 ) 87 with tests.server_request(handler, request_count=3) as uri: 88 response, content = http.request(uri, "GET") 89 assert response.status == 401 90 http.add_credentials("joe", password) 91 response, content = http.request(uri, "GET") 92 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( 102 allow_scheme="digest", 103 allow_credentials=(("joe", password),), 104 out_renew_nonce=grenew_nonce, 105 ) 106 with tests.server_request(handler, request_count=5) as uri: 107 http.add_credentials("joe", password) 108 response1, _ = http.request(uri, "GET") 109 info = httplib2._parse_www_authenticate(response1, "authentication-info") 110 assert response1.status == 200 111 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 116 grenew_nonce[0]() 117 response3, content = http.request(uri, "GET") 118 info3 = httplib2._parse_www_authenticate(response3, "authentication-info") 119 assert response3.status == 200 120 assert info3.get("digest", {}).get("nc") == "00000001", info3 121 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( 130 allow_scheme="digest", 131 allow_credentials=(("joe", password),), 132 out_renew_nonce=grenew_nonce, 133 out_requests=requests, 134 ) 135 with tests.server_request(handler, request_count=4) as uri: 136 http.add_credentials("joe", password) 137 response, _ = http.request(uri, "GET") 138 assert response.status == 200 139 info = httplib2._parse_www_authenticate( 140 requests[0][1].headers, "www-authenticate" 141 ) 142 grenew_nonce[0]() 143 response, _ = http.request(uri, "GET") 144 assert response.status == 200 145 assert not response.fromcache 146 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 != "" 154 assert nonce1 != nonce2, (nonce1, nonce2) 155 156 157@pytest.mark.parametrize( 158 "data", 159 ( 160 ({}, {}), 161 ({"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 ), 211 # multiple schema choice 212 ( 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 ), 221 # 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 239 ( 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")) 257def 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( 271 { 272 "www-authenticate": 'OAuth "Facebook Platform" "invalid_token" "Invalid OAuth access token."' 273 } 274 ) 275 276 277def test_digest_object(): 278 credentials = ("joe", "password") 279 host = None 280 request_uri = "/test/digest/" 281 headers = {} 282 response = { 283 "www-authenticate": 'Digest realm="myrealm", nonce="KBAA=35", algorithm=MD5, qop="auth"' 284 } 285 content = b"" 286 287 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"] 292 working_request = ( 293 '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"' 297 ) 298 assert our_request == working_request 299 300 301def test_digest_object_with_opaque(): 302 credentials = ("joe", "password") 303 host = None 304 request_uri = "/digest/opaque/" 305 headers = {} 306 response = { 307 "www-authenticate": 'Digest realm="myrealm", nonce="30352fd", algorithm=MD5, ' 308 'qop="auth", opaque="atestopaque"' 309 } 310 content = "" 311 312 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"] 317 working_request = ( 318 '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"' 322 ) 323 assert our_request == working_request 324 325 326def test_digest_object_stale(): 327 credentials = ("joe", "password") 328 host = None 329 request_uri = "/digest/stale/" 330 headers = {} 331 response = httplib2.Response({}) 332 response["www-authenticate"] = ( 333 'Digest realm="myrealm", nonce="bd669f", ' 334 'algorithm=MD5, qop="auth", stale=true' 335 ) 336 response.status = 401 337 content = b"" 338 d = httplib2.DigestAuthentication( 339 credentials, host, request_uri, headers, response, content, None 340 ) 341 # Returns true to force a retry 342 assert d.response(response, content) 343 344 345def test_digest_object_auth_info(): 346 credentials = ("joe", "password") 347 host = None 348 request_uri = "/digest/nextnonce/" 349 headers = {} 350 response = httplib2.Response({}) 351 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 ) 360 # Returns true to force a retry 361 assert not d.response(response, content) 362 assert d.challenge["nonce"] == "fred" 363 assert d.challenge["nc"] == 1 364 365 366def test_wsse_algorithm(): 367 digest = httplib2._wsse_username_token( 368 "d36e316282959a9ed4c89851497a717f", "2003-12-15T14:43:07Z", "taadtaadpstcsm" 369 ) 370 expected = b"quR/EWLAV4xLf9Zqyw4pDmfV9OY=" 371 assert expected == digest 372