blob: 1b08ab87f83ce9c652ead1a9bef78b1dbecd76c0 [file] [log] [blame]
Jon Wayne Parrott8713a712016-10-04 14:19:01 -07001# 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.
14
15"""Helper functions for commonly used utilities."""
16
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -080017import base64
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070018import calendar
19import datetime
arithmetic1728bd0ccc52021-11-01 10:22:26 -070020import sys
arithmetic17285bd5ccf2021-10-21 15:25:46 -070021
22import six
23from six.moves import urllib
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070024
25
arithmetic1728738611b2021-09-09 17:09:54 -070026# Token server doesn't provide a new a token when doing refresh unless the
27# token is expiring within 30 seconds, so refresh threshold should not be
28# more than 30 seconds. Otherwise auth lib will send tons of refresh requests
29# until 30 seconds before the expiration, and cause a spike of CPU usage.
30REFRESH_THRESHOLD = datetime.timedelta(seconds=20)
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -070031
32
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070033def copy_docstring(source_class):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070034 """Decorator that copies a method's docstring from another class.
35
36 Args:
37 source_class (type): The class that has the documented method.
38
39 Returns:
40 Callable: A decorator that will copy the docstring of the same
41 named method in the source class to the decorated method.
42 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -070043
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070044 def decorator(method):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070045 """Decorator implementation.
46
47 Args:
Jon Wayne Parrott0a0be142016-10-14 14:53:29 -070048 method (Callable): The method to copy the docstring to.
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070049
50 Returns:
51 Callable: the same method passed in with an updated docstring.
52
53 Raises:
54 ValueError: if the method already has a docstring.
55 """
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070056 if method.__doc__:
Bu Sun Kim9eec0912019-10-21 17:04:21 -070057 raise ValueError("Method already has a docstring.")
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070058
59 source_method = getattr(source_class, method.__name__)
60 method.__doc__ = source_method.__doc__
61
62 return method
Bu Sun Kim9eec0912019-10-21 17:04:21 -070063
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070064 return decorator
65
66
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070067def utcnow():
68 """Returns the current UTC datetime.
69
70 Returns:
71 datetime: The current time in UTC.
72 """
73 return datetime.datetime.utcnow()
74
75
76def datetime_to_secs(value):
77 """Convert a datetime object to the number of seconds since the UNIX epoch.
78
79 Args:
80 value (datetime): The datetime to convert.
81
82 Returns:
83 int: The number of seconds since the UNIX epoch.
84 """
85 return calendar.timegm(value.utctimetuple())
86
87
Bu Sun Kim9eec0912019-10-21 17:04:21 -070088def to_bytes(value, encoding="utf-8"):
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070089 """Converts a string value to bytes, if necessary.
90
arithmetic17285bd5ccf2021-10-21 15:25:46 -070091 Unfortunately, ``six.b`` is insufficient for this task since in
92 Python 2 because it does not modify ``unicode`` objects.
93
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070094 Args:
95 value (Union[str, bytes]): The value to be converted.
96 encoding (str): The encoding to use to convert unicode to bytes.
97 Defaults to "utf-8".
98
99 Returns:
100 bytes: The original value converted to bytes (if unicode) or as
101 passed in if it started out as bytes.
102
103 Raises:
104 ValueError: If the value could not be converted to bytes.
105 """
arithmetic17285bd5ccf2021-10-21 15:25:46 -0700106 result = value.encode(encoding) if isinstance(value, six.text_type) else value
107 if isinstance(result, six.binary_type):
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700108 return result
109 else:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700110 raise ValueError("{0!r} could not be converted to bytes".format(value))
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700111
112
113def from_bytes(value):
114 """Converts bytes to a string value, if necessary.
115
116 Args:
117 value (Union[str, bytes]): The value to be converted.
118
119 Returns:
120 str: The original value converted to unicode (if bytes) or as passed in
121 if it started out as unicode.
122
123 Raises:
124 ValueError: If the value could not be converted to unicode.
125 """
arithmetic17285bd5ccf2021-10-21 15:25:46 -0700126 result = value.decode("utf-8") if isinstance(value, six.binary_type) else value
127 if isinstance(result, six.text_type):
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700128 return result
129 else:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700130 raise ValueError("{0!r} could not be converted to unicode".format(value))
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -0700131
132
133def update_query(url, params, remove=None):
134 """Updates a URL's query parameters.
135
136 Replaces any current values if they are already present in the URL.
137
138 Args:
139 url (str): The URL to update.
140 params (Mapping[str, str]): A mapping of query parameter
141 keys to values.
142 remove (Sequence[str]): Parameters to remove from the query string.
143
144 Returns:
145 str: The URL with updated query parameters.
146
147 Examples:
148
149 >>> url = 'http://example.com?a=1'
150 >>> update_query(url, {'a': '2'})
151 http://example.com?a=2
152 >>> update_query(url, {'b': '3'})
153 http://example.com?a=1&b=3
154 >> update_query(url, {'b': '3'}, remove=['a'])
155 http://example.com?b=3
156
157 """
158 if remove is None:
159 remove = []
160
161 # Split the URL into parts.
162 parts = urllib.parse.urlparse(url)
163 # Parse the query string.
164 query_params = urllib.parse.parse_qs(parts.query)
165 # Update the query parameters with the new parameters.
166 query_params.update(params)
167 # Remove any values specified in remove.
168 query_params = {
arithmetic17285bd5ccf2021-10-21 15:25:46 -0700169 key: value for key, value in six.iteritems(query_params) if key not in remove
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700170 }
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -0700171 # Re-encoded the query string.
172 new_query = urllib.parse.urlencode(query_params, doseq=True)
173 # Unsplit the url.
174 new_parts = parts._replace(query=new_query)
175 return urllib.parse.urlunparse(new_parts)
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700176
177
178def scopes_to_string(scopes):
179 """Converts scope value to a string suitable for sending to OAuth 2.0
180 authorization servers.
181
182 Args:
183 scopes (Sequence[str]): The sequence of scopes to convert.
184
185 Returns:
186 str: The scopes formatted as a single string.
187 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700188 return " ".join(scopes)
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700189
190
191def string_to_scopes(scopes):
192 """Converts stringifed scopes value to a list.
193
194 Args:
195 scopes (Union[Sequence, str]): The string of space-separated scopes
196 to convert.
197 Returns:
198 Sequence(str): The separated scopes.
199 """
200 if not scopes:
201 return []
202
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700203 return scopes.split(" ")
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800204
205
206def padded_urlsafe_b64decode(value):
207 """Decodes base64 strings lacking padding characters.
208
209 Google infrastructure tends to omit the base64 padding characters.
210
211 Args:
212 value (Union[str, bytes]): The encoded value.
213
214 Returns:
215 bytes: The decoded value
216 """
217 b64string = to_bytes(value)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700218 padded = b64string + b"=" * (-len(b64string) % 4)
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800219 return base64.urlsafe_b64decode(padded)
Aditya Natrajae7e4f32019-02-15 00:02:10 -0500220
221
222def unpadded_urlsafe_b64encode(value):
223 """Encodes base64 strings removing any padding characters.
224
225 `rfc 7515`_ defines Base64url to NOT include any padding
226 characters, but the stdlib doesn't do that by default.
227
228 _rfc7515: https://tools.ietf.org/html/rfc7515#page-6
229
230 Args:
231 value (Union[str|bytes]): The bytes-like value to encode
232
233 Returns:
234 Union[str|bytes]: The encoded value
235 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700236 return base64.urlsafe_b64encode(value).rstrip(b"=")
arithmetic1728bd0ccc52021-11-01 10:22:26 -0700237
238
239def is_python_3():
240 """Check if the Python interpreter is Python 2 or 3.
241
242 Returns:
243 bool: True if the Python interpreter is Python 3 and False otherwise.
244 """
245 return sys.version_info > (3, 0)