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