blob: 55adf5bc3c7ba53c31718a87ace26a90a703dad9 [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
Tres Seaver560cf1e2021-08-03 16:35:54 -040020import urllib
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070021
22
arithmetic1728738611b2021-09-09 17:09:54 -070023# Token server doesn't provide a new a token when doing refresh unless the
24# token is expiring within 30 seconds, so refresh threshold should not be
25# more than 30 seconds. Otherwise auth lib will send tons of refresh requests
26# until 30 seconds before the expiration, and cause a spike of CPU usage.
27REFRESH_THRESHOLD = datetime.timedelta(seconds=20)
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -070028
29
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070030def copy_docstring(source_class):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070031 """Decorator that copies a method's docstring from another class.
32
33 Args:
34 source_class (type): The class that has the documented method.
35
36 Returns:
37 Callable: A decorator that will copy the docstring of the same
38 named method in the source class to the decorated method.
39 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -070040
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070041 def decorator(method):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070042 """Decorator implementation.
43
44 Args:
Jon Wayne Parrott0a0be142016-10-14 14:53:29 -070045 method (Callable): The method to copy the docstring to.
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070046
47 Returns:
48 Callable: the same method passed in with an updated docstring.
49
50 Raises:
51 ValueError: if the method already has a docstring.
52 """
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070053 if method.__doc__:
Bu Sun Kim9eec0912019-10-21 17:04:21 -070054 raise ValueError("Method already has a docstring.")
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070055
56 source_method = getattr(source_class, method.__name__)
57 method.__doc__ = source_method.__doc__
58
59 return method
Bu Sun Kim9eec0912019-10-21 17:04:21 -070060
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070061 return decorator
62
63
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070064def utcnow():
65 """Returns the current UTC datetime.
66
67 Returns:
68 datetime: The current time in UTC.
69 """
70 return datetime.datetime.utcnow()
71
72
73def datetime_to_secs(value):
74 """Convert a datetime object to the number of seconds since the UNIX epoch.
75
76 Args:
77 value (datetime): The datetime to convert.
78
79 Returns:
80 int: The number of seconds since the UNIX epoch.
81 """
82 return calendar.timegm(value.utctimetuple())
83
84
Bu Sun Kim9eec0912019-10-21 17:04:21 -070085def to_bytes(value, encoding="utf-8"):
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070086 """Converts a string value to bytes, if necessary.
87
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070088 Args:
89 value (Union[str, bytes]): The value to be converted.
90 encoding (str): The encoding to use to convert unicode to bytes.
91 Defaults to "utf-8".
92
93 Returns:
94 bytes: The original value converted to bytes (if unicode) or as
95 passed in if it started out as bytes.
96
97 Raises:
98 ValueError: If the value could not be converted to bytes.
99 """
Tres Seaver560cf1e2021-08-03 16:35:54 -0400100 result = value.encode(encoding) if isinstance(value, str) else value
101 if isinstance(result, bytes):
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700102 return result
103 else:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700104 raise ValueError("{0!r} could not be converted to bytes".format(value))
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700105
106
107def from_bytes(value):
108 """Converts bytes to a string value, if necessary.
109
110 Args:
111 value (Union[str, bytes]): The value to be converted.
112
113 Returns:
114 str: The original value converted to unicode (if bytes) or as passed in
115 if it started out as unicode.
116
117 Raises:
118 ValueError: If the value could not be converted to unicode.
119 """
Tres Seaver560cf1e2021-08-03 16:35:54 -0400120 result = value.decode("utf-8") if isinstance(value, bytes) else value
121 if isinstance(result, str):
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700122 return result
123 else:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700124 raise ValueError("{0!r} could not be converted to unicode".format(value))
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -0700125
126
127def update_query(url, params, remove=None):
128 """Updates a URL's query parameters.
129
130 Replaces any current values if they are already present in the URL.
131
132 Args:
133 url (str): The URL to update.
134 params (Mapping[str, str]): A mapping of query parameter
135 keys to values.
136 remove (Sequence[str]): Parameters to remove from the query string.
137
138 Returns:
139 str: The URL with updated query parameters.
140
141 Examples:
142
143 >>> url = 'http://example.com?a=1'
144 >>> update_query(url, {'a': '2'})
145 http://example.com?a=2
146 >>> update_query(url, {'b': '3'})
147 http://example.com?a=1&b=3
148 >> update_query(url, {'b': '3'}, remove=['a'])
149 http://example.com?b=3
150
151 """
152 if remove is None:
153 remove = []
154
155 # Split the URL into parts.
156 parts = urllib.parse.urlparse(url)
157 # Parse the query string.
158 query_params = urllib.parse.parse_qs(parts.query)
159 # Update the query parameters with the new parameters.
160 query_params.update(params)
161 # Remove any values specified in remove.
162 query_params = {
Tres Seaver560cf1e2021-08-03 16:35:54 -0400163 key: value for key, value in query_params.items() if key not in remove
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700164 }
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -0700165 # Re-encoded the query string.
166 new_query = urllib.parse.urlencode(query_params, doseq=True)
167 # Unsplit the url.
168 new_parts = parts._replace(query=new_query)
169 return urllib.parse.urlunparse(new_parts)
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700170
171
172def scopes_to_string(scopes):
173 """Converts scope value to a string suitable for sending to OAuth 2.0
174 authorization servers.
175
176 Args:
177 scopes (Sequence[str]): The sequence of scopes to convert.
178
179 Returns:
180 str: The scopes formatted as a single string.
181 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700182 return " ".join(scopes)
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700183
184
185def string_to_scopes(scopes):
186 """Converts stringifed scopes value to a list.
187
188 Args:
189 scopes (Union[Sequence, str]): The string of space-separated scopes
190 to convert.
191 Returns:
192 Sequence(str): The separated scopes.
193 """
194 if not scopes:
195 return []
196
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700197 return scopes.split(" ")
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800198
199
200def padded_urlsafe_b64decode(value):
201 """Decodes base64 strings lacking padding characters.
202
203 Google infrastructure tends to omit the base64 padding characters.
204
205 Args:
206 value (Union[str, bytes]): The encoded value.
207
208 Returns:
209 bytes: The decoded value
210 """
211 b64string = to_bytes(value)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700212 padded = b64string + b"=" * (-len(b64string) % 4)
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800213 return base64.urlsafe_b64decode(padded)
Aditya Natrajae7e4f32019-02-15 00:02:10 -0500214
215
216def unpadded_urlsafe_b64encode(value):
217 """Encodes base64 strings removing any padding characters.
218
219 `rfc 7515`_ defines Base64url to NOT include any padding
220 characters, but the stdlib doesn't do that by default.
221
222 _rfc7515: https://tools.ietf.org/html/rfc7515#page-6
223
224 Args:
225 value (Union[str|bytes]): The bytes-like value to encode
226
227 Returns:
228 Union[str|bytes]: The encoded value
229 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700230 return base64.urlsafe_b64encode(value).rstrip(b"=")