blob: ecb88ffda4552e344e6a5571f87755d4385152dd [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
20
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070021import six
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -070022from six.moves import urllib
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070023
24
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -070025CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
26CLOCK_SKEW = datetime.timedelta(seconds=CLOCK_SKEW_SECS)
27
28
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070029def copy_docstring(source_class):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070030 """Decorator that copies a method's docstring from another class.
31
32 Args:
33 source_class (type): The class that has the documented method.
34
35 Returns:
36 Callable: A decorator that will copy the docstring of the same
37 named method in the source class to the decorated method.
38 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -070039
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070040 def decorator(method):
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070041 """Decorator implementation.
42
43 Args:
Jon Wayne Parrott0a0be142016-10-14 14:53:29 -070044 method (Callable): The method to copy the docstring to.
Jon Wayne Parrott2c253a52016-10-14 14:50:58 -070045
46 Returns:
47 Callable: the same method passed in with an updated docstring.
48
49 Raises:
50 ValueError: if the method already has a docstring.
51 """
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070052 if method.__doc__:
Bu Sun Kim9eec0912019-10-21 17:04:21 -070053 raise ValueError("Method already has a docstring.")
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070054
55 source_method = getattr(source_class, method.__name__)
56 method.__doc__ = source_method.__doc__
57
58 return method
Bu Sun Kim9eec0912019-10-21 17:04:21 -070059
Jon Wayne Parrott6b21d752016-10-14 14:49:35 -070060 return decorator
61
62
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070063def utcnow():
64 """Returns the current UTC datetime.
65
66 Returns:
67 datetime: The current time in UTC.
68 """
69 return datetime.datetime.utcnow()
70
71
72def datetime_to_secs(value):
73 """Convert a datetime object to the number of seconds since the UNIX epoch.
74
75 Args:
76 value (datetime): The datetime to convert.
77
78 Returns:
79 int: The number of seconds since the UNIX epoch.
80 """
81 return calendar.timegm(value.utctimetuple())
82
83
Bu Sun Kim9eec0912019-10-21 17:04:21 -070084def to_bytes(value, encoding="utf-8"):
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070085 """Converts a string value to bytes, if necessary.
86
87 Unfortunately, ``six.b`` is insufficient for this task since in
88 Python 2 because it does not modify ``unicode`` objects.
89
90 Args:
91 value (Union[str, bytes]): The value to be converted.
92 encoding (str): The encoding to use to convert unicode to bytes.
93 Defaults to "utf-8".
94
95 Returns:
96 bytes: The original value converted to bytes (if unicode) or as
97 passed in if it started out as bytes.
98
99 Raises:
100 ValueError: If the value could not be converted to bytes.
101 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700102 result = value.encode(encoding) if isinstance(value, six.text_type) else value
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700103 if isinstance(result, six.binary_type):
104 return result
105 else:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700106 raise ValueError("{0!r} could not be converted to bytes".format(value))
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700107
108
109def from_bytes(value):
110 """Converts bytes to a string value, if necessary.
111
112 Args:
113 value (Union[str, bytes]): The value to be converted.
114
115 Returns:
116 str: The original value converted to unicode (if bytes) or as passed in
117 if it started out as unicode.
118
119 Raises:
120 ValueError: If the value could not be converted to unicode.
121 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700122 result = value.decode("utf-8") if isinstance(value, six.binary_type) else value
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700123 if isinstance(result, six.text_type):
124 return result
125 else:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700126 raise ValueError("{0!r} could not be converted to unicode".format(value))
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -0700127
128
129def update_query(url, params, remove=None):
130 """Updates a URL's query parameters.
131
132 Replaces any current values if they are already present in the URL.
133
134 Args:
135 url (str): The URL to update.
136 params (Mapping[str, str]): A mapping of query parameter
137 keys to values.
138 remove (Sequence[str]): Parameters to remove from the query string.
139
140 Returns:
141 str: The URL with updated query parameters.
142
143 Examples:
144
145 >>> url = 'http://example.com?a=1'
146 >>> update_query(url, {'a': '2'})
147 http://example.com?a=2
148 >>> update_query(url, {'b': '3'})
149 http://example.com?a=1&b=3
150 >> update_query(url, {'b': '3'}, remove=['a'])
151 http://example.com?b=3
152
153 """
154 if remove is None:
155 remove = []
156
157 # Split the URL into parts.
158 parts = urllib.parse.urlparse(url)
159 # Parse the query string.
160 query_params = urllib.parse.parse_qs(parts.query)
161 # Update the query parameters with the new parameters.
162 query_params.update(params)
163 # Remove any values specified in remove.
164 query_params = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700165 key: value for key, value in six.iteritems(query_params) if key not in remove
166 }
Jon Wayne Parrott64a9d6e2016-10-07 14:02:53 -0700167 # Re-encoded the query string.
168 new_query = urllib.parse.urlencode(query_params, doseq=True)
169 # Unsplit the url.
170 new_parts = parts._replace(query=new_query)
171 return urllib.parse.urlunparse(new_parts)
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700172
173
174def scopes_to_string(scopes):
175 """Converts scope value to a string suitable for sending to OAuth 2.0
176 authorization servers.
177
178 Args:
179 scopes (Sequence[str]): The sequence of scopes to convert.
180
181 Returns:
182 str: The scopes formatted as a single string.
183 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700184 return " ".join(scopes)
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700185
186
187def string_to_scopes(scopes):
188 """Converts stringifed scopes value to a list.
189
190 Args:
191 scopes (Union[Sequence, str]): The string of space-separated scopes
192 to convert.
193 Returns:
194 Sequence(str): The separated scopes.
195 """
196 if not scopes:
197 return []
198
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700199 return scopes.split(" ")
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800200
201
202def padded_urlsafe_b64decode(value):
203 """Decodes base64 strings lacking padding characters.
204
205 Google infrastructure tends to omit the base64 padding characters.
206
207 Args:
208 value (Union[str, bytes]): The encoded value.
209
210 Returns:
211 bytes: The decoded value
212 """
213 b64string = to_bytes(value)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700214 padded = b64string + b"=" * (-len(b64string) % 4)
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800215 return base64.urlsafe_b64decode(padded)
Aditya Natrajae7e4f32019-02-15 00:02:10 -0500216
217
218def unpadded_urlsafe_b64encode(value):
219 """Encodes base64 strings removing any padding characters.
220
221 `rfc 7515`_ defines Base64url to NOT include any padding
222 characters, but the stdlib doesn't do that by default.
223
224 _rfc7515: https://tools.ietf.org/html/rfc7515#page-6
225
226 Args:
227 value (Union[str|bytes]): The bytes-like value to encode
228
229 Returns:
230 Union[str|bytes]: The encoded value
231 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700232 return base64.urlsafe_b64encode(value).rstrip(b"=")