Skip to content

Commit 7039beb

Browse files
Fix: Less restrictive content-type header check for google authentication (ignores charset) (#1382)
* fix: added function for parsing content-type headers * fix: improved too restrictive content-type check (now ignores charset param) * test: Added some new tests for the parse_content_type function in _helpers and its use in _metadata * style: Linted code --------- Co-authored-by: arithmetic1728 <[email protected]>
1 parent 3a34c7a commit 7039beb

File tree

4 files changed

+71
-1
lines changed

4 files changed

+71
-1
lines changed

google/auth/_helpers.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import base64
1818
import calendar
1919
import datetime
20+
from email.message import Message
2021
import sys
2122
import urllib
2223

@@ -63,6 +64,28 @@ def decorator(method):
6364
return decorator
6465

6566

67+
def parse_content_type(header_value):
68+
"""Parse a 'content-type' header value to get just the plain media-type (without parameters).
69+
70+
This is done using the class Message from email.message as suggested in PEP 594
71+
(because the cgi is now deprecated and will be removed in python 3.13,
72+
see https://peps.python.org/pep-0594/#cgi).
73+
74+
Args:
75+
header_value (str): The value of a 'content-type' header as a string.
76+
77+
Returns:
78+
str: A string with just the lowercase media-type from the parsed 'content-type' header.
79+
If the provided content-type is not parsable, returns 'text/plain',
80+
the default value for textual files.
81+
"""
82+
m = Message()
83+
m["content-type"] = header_value
84+
return (
85+
m.get_content_type()
86+
) # Despite the name, actually returns just the media-type
87+
88+
6689
def utcnow():
6790
"""Returns the current UTC datetime.
6891

google/auth/compute_engine/_metadata.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,10 @@ def get(
218218

219219
if response.status == http_client.OK:
220220
content = _helpers.from_bytes(response.data)
221-
if response.headers["content-type"] == "application/json":
221+
if (
222+
_helpers.parse_content_type(response.headers["content-type"])
223+
== "application/json"
224+
):
222225
try:
223226
return json.loads(content)
224227
except ValueError as caught_exc:

tests/compute_engine/test__metadata.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,24 @@ def test_get_success_json():
176176
assert result[key] == value
177177

178178

179+
def test_get_success_json_content_type_charset():
180+
key, value = "foo", "bar"
181+
182+
data = json.dumps({key: value})
183+
request = make_request(
184+
data, headers={"content-type": "application/json; charset=UTF-8"}
185+
)
186+
187+
result = _metadata.get(request, PATH)
188+
189+
request.assert_called_once_with(
190+
method="GET",
191+
url=_metadata._METADATA_ROOT + PATH,
192+
headers=_metadata._METADATA_HEADERS,
193+
)
194+
assert result[key] == value
195+
196+
179197
def test_get_success_retry():
180198
key, value = "foo", "bar"
181199

tests/test__helpers.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,32 @@ def func2(): # pragma: NO COVER
5151
_helpers.copy_docstring(SourceClass)(func2)
5252

5353

54+
def test_parse_content_type_plain():
55+
assert _helpers.parse_content_type("text/html") == "text/html"
56+
assert _helpers.parse_content_type("application/xml") == "application/xml"
57+
assert _helpers.parse_content_type("application/json") == "application/json"
58+
59+
60+
def test_parse_content_type_with_parameters():
61+
content_type_html = "text/html; charset=UTF-8"
62+
content_type_xml = "application/xml; charset=UTF-16; version=1.0"
63+
content_type_json = "application/json; charset=UTF-8; indent=2"
64+
assert _helpers.parse_content_type(content_type_html) == "text/html"
65+
assert _helpers.parse_content_type(content_type_xml) == "application/xml"
66+
assert _helpers.parse_content_type(content_type_json) == "application/json"
67+
68+
69+
def test_parse_content_type_missing_or_broken():
70+
content_type_foo = None
71+
content_type_bar = ""
72+
content_type_baz = "1234"
73+
content_type_qux = " ; charset=UTF-8"
74+
assert _helpers.parse_content_type(content_type_foo) == "text/plain"
75+
assert _helpers.parse_content_type(content_type_bar) == "text/plain"
76+
assert _helpers.parse_content_type(content_type_baz) == "text/plain"
77+
assert _helpers.parse_content_type(content_type_qux) == "text/plain"
78+
79+
5480
def test_utcnow():
5581
assert isinstance(_helpers.utcnow(), datetime.datetime)
5682

0 commit comments

Comments
 (0)