Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/auth/_helpers.py: 44%
61 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:40 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:40 +0000
1# Copyright 2015 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""Helper functions for commonly used utilities."""
17import base64
18import calendar
19import datetime
20from email.message import Message
21import sys
22import urllib
24from google.auth import exceptions
26# The smallest MDS cache used by this library stores tokens until 4 minutes from
27# expiry.
28REFRESH_THRESHOLD = datetime.timedelta(minutes=3, seconds=45)
31def copy_docstring(source_class):
32 """Decorator that copies a method's docstring from another class.
34 Args:
35 source_class (type): The class that has the documented method.
37 Returns:
38 Callable: A decorator that will copy the docstring of the same
39 named method in the source class to the decorated method.
40 """
42 def decorator(method):
43 """Decorator implementation.
45 Args:
46 method (Callable): The method to copy the docstring to.
48 Returns:
49 Callable: the same method passed in with an updated docstring.
51 Raises:
52 google.auth.exceptions.InvalidOperation: if the method already has a docstring.
53 """
54 if method.__doc__:
55 raise exceptions.InvalidOperation("Method already has a docstring.")
57 source_method = getattr(source_class, method.__name__)
58 method.__doc__ = source_method.__doc__
60 return method
62 return decorator
65def parse_content_type(header_value):
66 """Parse a 'content-type' header value to get just the plain media-type (without parameters).
68 This is done using the class Message from email.message as suggested in PEP 594
69 (because the cgi is now deprecated and will be removed in python 3.13,
70 see https://peps.python.org/pep-0594/#cgi).
72 Args:
73 header_value (str): The value of a 'content-type' header as a string.
75 Returns:
76 str: A string with just the lowercase media-type from the parsed 'content-type' header.
77 If the provided content-type is not parsable, returns 'text/plain',
78 the default value for textual files.
79 """
80 m = Message()
81 m["content-type"] = header_value
82 return (
83 m.get_content_type()
84 ) # Despite the name, actually returns just the media-type
87def utcnow():
88 """Returns the current UTC datetime.
90 Returns:
91 datetime: The current time in UTC.
92 """
93 # We used datetime.utcnow() before, since it's deprecated from python 3.12,
94 # we are using datetime.now(timezone.utc) now. "utcnow()" is offset-native
95 # (no timezone info), but "now()" is offset-aware (with timezone info).
96 # This will cause datetime comparison problem. For backward compatibility,
97 # we need to remove the timezone info.
98 now = datetime.datetime.now(datetime.timezone.utc)
99 now = now.replace(tzinfo=None)
100 return now
103def datetime_to_secs(value):
104 """Convert a datetime object to the number of seconds since the UNIX epoch.
106 Args:
107 value (datetime): The datetime to convert.
109 Returns:
110 int: The number of seconds since the UNIX epoch.
111 """
112 return calendar.timegm(value.utctimetuple())
115def to_bytes(value, encoding="utf-8"):
116 """Converts a string value to bytes, if necessary.
118 Args:
119 value (Union[str, bytes]): The value to be converted.
120 encoding (str): The encoding to use to convert unicode to bytes.
121 Defaults to "utf-8".
123 Returns:
124 bytes: The original value converted to bytes (if unicode) or as
125 passed in if it started out as bytes.
127 Raises:
128 google.auth.exceptions.InvalidValue: If the value could not be converted to bytes.
129 """
130 result = value.encode(encoding) if isinstance(value, str) else value
131 if isinstance(result, bytes):
132 return result
133 else:
134 raise exceptions.InvalidValue(
135 "{0!r} could not be converted to bytes".format(value)
136 )
139def from_bytes(value):
140 """Converts bytes to a string value, if necessary.
142 Args:
143 value (Union[str, bytes]): The value to be converted.
145 Returns:
146 str: The original value converted to unicode (if bytes) or as passed in
147 if it started out as unicode.
149 Raises:
150 google.auth.exceptions.InvalidValue: If the value could not be converted to unicode.
151 """
152 result = value.decode("utf-8") if isinstance(value, bytes) else value
153 if isinstance(result, str):
154 return result
155 else:
156 raise exceptions.InvalidValue(
157 "{0!r} could not be converted to unicode".format(value)
158 )
161def update_query(url, params, remove=None):
162 """Updates a URL's query parameters.
164 Replaces any current values if they are already present in the URL.
166 Args:
167 url (str): The URL to update.
168 params (Mapping[str, str]): A mapping of query parameter
169 keys to values.
170 remove (Sequence[str]): Parameters to remove from the query string.
172 Returns:
173 str: The URL with updated query parameters.
175 Examples:
177 >>> url = 'http://example.com?a=1'
178 >>> update_query(url, {'a': '2'})
179 http://example.com?a=2
180 >>> update_query(url, {'b': '3'})
181 http://example.com?a=1&b=3
182 >> update_query(url, {'b': '3'}, remove=['a'])
183 http://example.com?b=3
185 """
186 if remove is None:
187 remove = []
189 # Split the URL into parts.
190 parts = urllib.parse.urlparse(url)
191 # Parse the query string.
192 query_params = urllib.parse.parse_qs(parts.query)
193 # Update the query parameters with the new parameters.
194 query_params.update(params)
195 # Remove any values specified in remove.
196 query_params = {
197 key: value for key, value in query_params.items() if key not in remove
198 }
199 # Re-encoded the query string.
200 new_query = urllib.parse.urlencode(query_params, doseq=True)
201 # Unsplit the url.
202 new_parts = parts._replace(query=new_query)
203 return urllib.parse.urlunparse(new_parts)
206def scopes_to_string(scopes):
207 """Converts scope value to a string suitable for sending to OAuth 2.0
208 authorization servers.
210 Args:
211 scopes (Sequence[str]): The sequence of scopes to convert.
213 Returns:
214 str: The scopes formatted as a single string.
215 """
216 return " ".join(scopes)
219def string_to_scopes(scopes):
220 """Converts stringifed scopes value to a list.
222 Args:
223 scopes (Union[Sequence, str]): The string of space-separated scopes
224 to convert.
225 Returns:
226 Sequence(str): The separated scopes.
227 """
228 if not scopes:
229 return []
231 return scopes.split(" ")
234def padded_urlsafe_b64decode(value):
235 """Decodes base64 strings lacking padding characters.
237 Google infrastructure tends to omit the base64 padding characters.
239 Args:
240 value (Union[str, bytes]): The encoded value.
242 Returns:
243 bytes: The decoded value
244 """
245 b64string = to_bytes(value)
246 padded = b64string + b"=" * (-len(b64string) % 4)
247 return base64.urlsafe_b64decode(padded)
250def unpadded_urlsafe_b64encode(value):
251 """Encodes base64 strings removing any padding characters.
253 `rfc 7515`_ defines Base64url to NOT include any padding
254 characters, but the stdlib doesn't do that by default.
256 _rfc7515: https://tools.ietf.org/html/rfc7515#page-6
258 Args:
259 value (Union[str|bytes]): The bytes-like value to encode
261 Returns:
262 Union[str|bytes]: The encoded value
263 """
264 return base64.urlsafe_b64encode(value).rstrip(b"=")
267def is_python_3():
268 """Check if the Python interpreter is Python 2 or 3.
270 Returns:
271 bool: True if the Python interpreter is Python 3 and False otherwise.
272 """
273 return sys.version_info > (3, 0)