• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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