blob: 11c6b1adb41c90c6a032104991cb1e5b0c903322 [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
Liron Newman45c44912021-09-07 23:07:07 +010023CLOCK_SKEW_SECS = 60 # 60 seconds
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -070024CLOCK_SKEW = datetime.timedelta(seconds=CLOCK_SKEW_SECS)
25
26
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070027def copy_docstring(source_class):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070028 """Decorator that copies a method's docstring from another class.
29
30 Args:
31 source_class (type): The class that has the documented method.
32
33 Returns:
34 Callable: A decorator that will copy the docstring of the same
35 named method in the source class to the decorated method.
36 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -070037
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070038 def decorator(method):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070039 """Decorator implementation.
40
41 Args:
Jon Wayne Parrott0a0be142016-10-14 14:53:29 -070042 method (Callable): The method to copy the docstring to.
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070043
44 Returns:
45 Callable: the same method passed in with an updated docstring.
46
47 Raises:
48 ValueError: if the method already has a docstring.
49 """
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070050 if method.__doc__:
Bu Sun Kim9eec0912019-10-21 17:04:21 -070051 raise ValueError("Method already has a docstring.")
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070052
53 source_method = getattr(source_class, method.__name__)
54 method.__doc__ = source_method.__doc__
55
56 return method
Bu Sun Kim9eec0912019-10-21 17:04:21 -070057
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070058 return decorator
59
60
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070061def utcnow():
62 """Returns the current UTC datetime.
63
64 Returns:
65 datetime: The current time in UTC.
66 """
67 return datetime.datetime.utcnow()
68
69
70def datetime_to_secs(value):
71 """Convert a datetime object to the number of seconds since the UNIX epoch.
72
73 Args:
74 value (datetime): The datetime to convert.
75
76 Returns:
77 int: The number of seconds since the UNIX epoch.
78 """
79 return calendar.timegm(value.utctimetuple())
80
81
Bu Sun Kim9eec0912019-10-21 17:04:21 -070082def to_bytes(value, encoding="utf-8"):
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070083 """Converts a string value to bytes, if necessary.
84
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070085 Args:
86 value (Union[str, bytes]): The value to be converted.
87 encoding (str): The encoding to use to convert unicode to bytes.
88 Defaults to "utf-8".
89
90 Returns:
91 bytes: The original value converted to bytes (if unicode) or as
92 passed in if it started out as bytes.
93
94 Raises:
95 ValueError: If the value could not be converted to bytes.
96 """
Tres Seaver560cf1e2021-08-03 16:35:54 -040097 result = value.encode(encoding) if isinstance(value, str) else value
98 if isinstance(result, bytes):
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070099 return result
100 else:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700101 raise ValueError("{0!r} could not be converted to bytes".format(value))
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700102
103
104def from_bytes(value):
105 """Converts bytes to a string value, if necessary.
106
107 Args:
108 value (Union[str, bytes]): The value to be converted.
109
110 Returns:
111 str: The original value converted to unicode (if bytes) or as passed in
112 if it started out as unicode.
113
114 Raises:
115 ValueError: If the value could not be converted to unicode.
116 """
Tres Seaver560cf1e2021-08-03 16:35:54 -0400117 result = value.decode("utf-8") if isinstance(value, bytes) else value
118 if isinstance(result, str):
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700119 return result
120 else:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700121 raise ValueError("{0!r} could not be converted to unicode".format(value))
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -0700122
123
124def update_query(url, params, remove=None):
125 """Updates a URL's query parameters.
126
127 Replaces any current values if they are already present in the URL.
128
129 Args:
130 url (str): The URL to update.
131 params (Mapping[str, str]): A mapping of query parameter
132 keys to values.
133 remove (Sequence[str]): Parameters to remove from the query string.
134
135 Returns:
136 str: The URL with updated query parameters.
137
138 Examples:
139
140 >>> url = 'http://example.com?a=1'
141 >>> update_query(url, {'a': '2'})
142 http://example.com?a=2
143 >>> update_query(url, {'b': '3'})
144 http://example.com?a=1&b=3
145 >> update_query(url, {'b': '3'}, remove=['a'])
146 http://example.com?b=3
147
148 """
149 if remove is None:
150 remove = []
151
152 # Split the URL into parts.
153 parts = urllib.parse.urlparse(url)
154 # Parse the query string.
155 query_params = urllib.parse.parse_qs(parts.query)
156 # Update the query parameters with the new parameters.
157 query_params.update(params)
158 # Remove any values specified in remove.
159 query_params = {
Tres Seaver560cf1e2021-08-03 16:35:54 -0400160 key: value for key, value in query_params.items() if key not in remove
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700161 }
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -0700162 # Re-encoded the query string.
163 new_query = urllib.parse.urlencode(query_params, doseq=True)
164 # Unsplit the url.
165 new_parts = parts._replace(query=new_query)
166 return urllib.parse.urlunparse(new_parts)
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700167
168
169def scopes_to_string(scopes):
170 """Converts scope value to a string suitable for sending to OAuth 2.0
171 authorization servers.
172
173 Args:
174 scopes (Sequence[str]): The sequence of scopes to convert.
175
176 Returns:
177 str: The scopes formatted as a single string.
178 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700179 return " ".join(scopes)
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700180
181
182def string_to_scopes(scopes):
183 """Converts stringifed scopes value to a list.
184
185 Args:
186 scopes (Union[Sequence, str]): The string of space-separated scopes
187 to convert.
188 Returns:
189 Sequence(str): The separated scopes.
190 """
191 if not scopes:
192 return []
193
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700194 return scopes.split(" ")
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800195
196
197def padded_urlsafe_b64decode(value):
198 """Decodes base64 strings lacking padding characters.
199
200 Google infrastructure tends to omit the base64 padding characters.
201
202 Args:
203 value (Union[str, bytes]): The encoded value.
204
205 Returns:
206 bytes: The decoded value
207 """
208 b64string = to_bytes(value)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700209 padded = b64string + b"=" * (-len(b64string) % 4)
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800210 return base64.urlsafe_b64decode(padded)
Aditya Natrajae7e4f32019-02-15 00:02:10 -0500211
212
213def unpadded_urlsafe_b64encode(value):
214 """Encodes base64 strings removing any padding characters.
215
216 `rfc 7515`_ defines Base64url to NOT include any padding
217 characters, but the stdlib doesn't do that by default.
218
219 _rfc7515: https://tools.ietf.org/html/rfc7515#page-6
220
221 Args:
222 value (Union[str|bytes]): The bytes-like value to encode
223
224 Returns:
225 Union[str|bytes]: The encoded value
226 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700227 return base64.urlsafe_b64encode(value).rstrip(b"=")