| Index: client/third_party/google/auth/_helpers.py
 | 
| diff --git a/client/third_party/google/auth/_helpers.py b/client/third_party/google/auth/_helpers.py
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..860b82719a2a6c5208ca1e410253fe2731818845
 | 
| --- /dev/null
 | 
| +++ b/client/third_party/google/auth/_helpers.py
 | 
| @@ -0,0 +1,217 @@
 | 
| +# Copyright 2015 Google Inc.
 | 
| +#
 | 
| +# Licensed under the Apache License, Version 2.0 (the "License");
 | 
| +# you may not use this file except in compliance with the License.
 | 
| +# You may obtain a copy of the License at
 | 
| +#
 | 
| +#      http://www.apache.org/licenses/LICENSE-2.0
 | 
| +#
 | 
| +# Unless required by applicable law or agreed to in writing, software
 | 
| +# distributed under the License is distributed on an "AS IS" BASIS,
 | 
| +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
| +# See the License for the specific language governing permissions and
 | 
| +# limitations under the License.
 | 
| +
 | 
| +"""Helper functions for commonly used utilities."""
 | 
| +
 | 
| +import base64
 | 
| +import calendar
 | 
| +import datetime
 | 
| +
 | 
| +import six
 | 
| +from six.moves import urllib
 | 
| +
 | 
| +
 | 
| +CLOCK_SKEW_SECS = 300  # 5 minutes in seconds
 | 
| +CLOCK_SKEW = datetime.timedelta(seconds=CLOCK_SKEW_SECS)
 | 
| +
 | 
| +
 | 
| +def copy_docstring(source_class):
 | 
| +    """Decorator that copies a method's docstring from another class.
 | 
| +
 | 
| +    Args:
 | 
| +        source_class (type): The class that has the documented method.
 | 
| +
 | 
| +    Returns:
 | 
| +        Callable: A decorator that will copy the docstring of the same
 | 
| +            named method in the source class to the decorated method.
 | 
| +    """
 | 
| +    def decorator(method):
 | 
| +        """Decorator implementation.
 | 
| +
 | 
| +        Args:
 | 
| +            method (Callable): The method to copy the docstring to.
 | 
| +
 | 
| +        Returns:
 | 
| +            Callable: the same method passed in with an updated docstring.
 | 
| +
 | 
| +        Raises:
 | 
| +            ValueError: if the method already has a docstring.
 | 
| +        """
 | 
| +        if method.__doc__:
 | 
| +            raise ValueError('Method already has a docstring.')
 | 
| +
 | 
| +        source_method = getattr(source_class, method.__name__)
 | 
| +        method.__doc__ = source_method.__doc__
 | 
| +
 | 
| +        return method
 | 
| +    return decorator
 | 
| +
 | 
| +
 | 
| +def utcnow():
 | 
| +    """Returns the current UTC datetime.
 | 
| +
 | 
| +    Returns:
 | 
| +        datetime: The current time in UTC.
 | 
| +    """
 | 
| +    return datetime.datetime.utcnow()
 | 
| +
 | 
| +
 | 
| +def datetime_to_secs(value):
 | 
| +    """Convert a datetime object to the number of seconds since the UNIX epoch.
 | 
| +
 | 
| +    Args:
 | 
| +        value (datetime): The datetime to convert.
 | 
| +
 | 
| +    Returns:
 | 
| +        int: The number of seconds since the UNIX epoch.
 | 
| +    """
 | 
| +    return calendar.timegm(value.utctimetuple())
 | 
| +
 | 
| +
 | 
| +def to_bytes(value, encoding='utf-8'):
 | 
| +    """Converts a string value to bytes, if necessary.
 | 
| +
 | 
| +    Unfortunately, ``six.b`` is insufficient for this task since in
 | 
| +    Python 2 because it does not modify ``unicode`` objects.
 | 
| +
 | 
| +    Args:
 | 
| +        value (Union[str, bytes]): The value to be converted.
 | 
| +        encoding (str): The encoding to use to convert unicode to bytes.
 | 
| +            Defaults to "utf-8".
 | 
| +
 | 
| +    Returns:
 | 
| +        bytes: The original value converted to bytes (if unicode) or as
 | 
| +            passed in if it started out as bytes.
 | 
| +
 | 
| +    Raises:
 | 
| +        ValueError: If the value could not be converted to bytes.
 | 
| +    """
 | 
| +    result = (value.encode(encoding)
 | 
| +              if isinstance(value, six.text_type) else value)
 | 
| +    if isinstance(result, six.binary_type):
 | 
| +        return result
 | 
| +    else:
 | 
| +        raise ValueError('{0!r} could not be converted to bytes'.format(value))
 | 
| +
 | 
| +
 | 
| +def from_bytes(value):
 | 
| +    """Converts bytes to a string value, if necessary.
 | 
| +
 | 
| +    Args:
 | 
| +        value (Union[str, bytes]): The value to be converted.
 | 
| +
 | 
| +    Returns:
 | 
| +        str: The original value converted to unicode (if bytes) or as passed in
 | 
| +            if it started out as unicode.
 | 
| +
 | 
| +    Raises:
 | 
| +        ValueError: If the value could not be converted to unicode.
 | 
| +    """
 | 
| +    result = (value.decode('utf-8')
 | 
| +              if isinstance(value, six.binary_type) else value)
 | 
| +    if isinstance(result, six.text_type):
 | 
| +        return result
 | 
| +    else:
 | 
| +        raise ValueError(
 | 
| +            '{0!r} could not be converted to unicode'.format(value))
 | 
| +
 | 
| +
 | 
| +def update_query(url, params, remove=None):
 | 
| +    """Updates a URL's query parameters.
 | 
| +
 | 
| +    Replaces any current values if they are already present in the URL.
 | 
| +
 | 
| +    Args:
 | 
| +        url (str): The URL to update.
 | 
| +        params (Mapping[str, str]): A mapping of query parameter
 | 
| +            keys to values.
 | 
| +        remove (Sequence[str]): Parameters to remove from the query string.
 | 
| +
 | 
| +    Returns:
 | 
| +        str: The URL with updated query parameters.
 | 
| +
 | 
| +    Examples:
 | 
| +
 | 
| +        >>> url = 'http://example.com?a=1'
 | 
| +        >>> update_query(url, {'a': '2'})
 | 
| +        http://example.com?a=2
 | 
| +        >>> update_query(url, {'b': '3'})
 | 
| +        http://example.com?a=1&b=3
 | 
| +        >> update_query(url, {'b': '3'}, remove=['a'])
 | 
| +        http://example.com?b=3
 | 
| +
 | 
| +    """
 | 
| +    if remove is None:
 | 
| +        remove = []
 | 
| +
 | 
| +    # Split the URL into parts.
 | 
| +    parts = urllib.parse.urlparse(url)
 | 
| +    # Parse the query string.
 | 
| +    query_params = urllib.parse.parse_qs(parts.query)
 | 
| +    # Update the query parameters with the new parameters.
 | 
| +    query_params.update(params)
 | 
| +    # Remove any values specified in remove.
 | 
| +    query_params = {
 | 
| +        key: value for key, value
 | 
| +        in six.iteritems(query_params)
 | 
| +        if key not in remove}
 | 
| +    # Re-encoded the query string.
 | 
| +    new_query = urllib.parse.urlencode(query_params, doseq=True)
 | 
| +    # Unsplit the url.
 | 
| +    new_parts = parts._replace(query=new_query)
 | 
| +    return urllib.parse.urlunparse(new_parts)
 | 
| +
 | 
| +
 | 
| +def scopes_to_string(scopes):
 | 
| +    """Converts scope value to a string suitable for sending to OAuth 2.0
 | 
| +    authorization servers.
 | 
| +
 | 
| +    Args:
 | 
| +        scopes (Sequence[str]): The sequence of scopes to convert.
 | 
| +
 | 
| +    Returns:
 | 
| +        str: The scopes formatted as a single string.
 | 
| +    """
 | 
| +    return ' '.join(scopes)
 | 
| +
 | 
| +
 | 
| +def string_to_scopes(scopes):
 | 
| +    """Converts stringifed scopes value to a list.
 | 
| +
 | 
| +    Args:
 | 
| +        scopes (Union[Sequence, str]): The string of space-separated scopes
 | 
| +            to convert.
 | 
| +    Returns:
 | 
| +        Sequence(str): The separated scopes.
 | 
| +    """
 | 
| +    if not scopes:
 | 
| +        return []
 | 
| +
 | 
| +    return scopes.split(' ')
 | 
| +
 | 
| +
 | 
| +def padded_urlsafe_b64decode(value):
 | 
| +    """Decodes base64 strings lacking padding characters.
 | 
| +
 | 
| +    Google infrastructure tends to omit the base64 padding characters.
 | 
| +
 | 
| +    Args:
 | 
| +        value (Union[str, bytes]): The encoded value.
 | 
| +
 | 
| +    Returns:
 | 
| +        bytes: The decoded value
 | 
| +    """
 | 
| +    b64string = to_bytes(value)
 | 
| +    padded = b64string + b'=' * (-len(b64string) % 4)
 | 
| +    return base64.urlsafe_b64decode(padded)
 | 
| 
 |