File: google_auth_httplib2.py

package info (click to toggle)
google-auth-httplib2 0.0.4-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, sid
  • size: 136 kB
  • sloc: python: 318; makefile: 5
file content (262 lines) | stat: -rw-r--r-- 9,391 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# Copyright 2016 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.

"""Transport adapter for httplib2."""

from __future__ import absolute_import

import logging

from google.auth import exceptions
from google.auth import transport
import httplib2
from six.moves import http_client


_LOGGER = logging.getLogger(__name__)
# Properties present in file-like streams / buffers.
_STREAM_PROPERTIES = ('read', 'seek', 'tell')


class _Response(transport.Response):
    """httplib2 transport response adapter.

    Args:
        response (httplib2.Response): The raw httplib2 response.
        data (bytes): The response body.
    """
    def __init__(self, response, data):
        self._response = response
        self._data = data

    @property
    def status(self):
        """int: The HTTP status code."""
        return self._response.status

    @property
    def headers(self):
        """Mapping[str, str]: The HTTP response headers."""
        return dict(self._response)

    @property
    def data(self):
        """bytes: The response body."""
        return self._data


class Request(transport.Request):
    """httplib2 request adapter.

    This class is used internally for making requests using various transports
    in a consistent way. If you use :class:`AuthorizedHttp` you do not need
    to construct or use this class directly.

    This class can be useful if you want to manually refresh a
    :class:`~google.auth.credentials.Credentials` instance::

        import google_auth_httplib2
        import httplib2

        http = httplib2.Http()
        request = google_auth_httplib2.Request(http)

        credentials.refresh(request)

    Args:
        http (httplib2.Http): The underlying http object to use to make
            requests.

    .. automethod:: __call__
    """
    def __init__(self, http):
        self.http = http

    def __call__(self, url, method='GET', body=None, headers=None,
                 timeout=None, **kwargs):
        """Make an HTTP request using httplib2.

        Args:
            url (str): The URI to be requested.
            method (str): The HTTP method to use for the request. Defaults
                to 'GET'.
            body (bytes): The payload / body in HTTP request.
            headers (Mapping[str, str]): Request headers.
            timeout (Optional[int]): The number of seconds to wait for a
                response from the server. This is ignored by httplib2 and will
                issue a warning.
            kwargs: Additional arguments passed throught to the underlying
                :meth:`httplib2.Http.request` method.

        Returns:
            google.auth.transport.Response: The HTTP response.

        Raises:
            google.auth.exceptions.TransportError: If any exception occurred.
        """
        if timeout is not None:
            _LOGGER.warning(
                'httplib2 transport does not support per-request timeout. '
                'Set the timeout when constructing the httplib2.Http instance.'
            )

        try:
            _LOGGER.debug('Making request: %s %s', method, url)
            response, data = self.http.request(
                url, method=method, body=body, headers=headers, **kwargs)
            return _Response(response, data)
        # httplib2 should catch the lower http error, this is a bug and
        # needs to be fixed there.  Catch the error for the meanwhile.
        except (httplib2.HttpLib2Error, http_client.HTTPException) as exc:
            raise exceptions.TransportError(exc)


def _make_default_http():
    """Returns a default httplib2.Http instance."""
    return httplib2.Http()


class AuthorizedHttp(object):
    """A httplib2 HTTP class with credentials.

    This class is used to perform requests to API endpoints that require
    authorization::

        from google.auth.transport._httplib2 import AuthorizedHttp

        authed_http = AuthorizedHttp(credentials)

        response = authed_http.request(
            'https://www.googleapis.com/storage/v1/b')

    This class implements :meth:`request` in the same way as
    :class:`httplib2.Http` and can usually be used just like any other
    instance of :class:``httplib2.Http`.

    The underlying :meth:`request` implementation handles adding the
    credentials' headers to the request and refreshing credentials as needed.
    """
    def __init__(self, credentials, http=None,
                 refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES,
                 max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS):
        """
        Args:
            credentials (google.auth.credentials.Credentials): The credentials
                to add to the request.
            http (httplib2.Http): The underlying HTTP object to
                use to make requests. If not specified, a
                :class:`httplib2.Http` instance will be constructed.
            refresh_status_codes (Sequence[int]): Which HTTP status codes
                indicate that credentials should be refreshed and the request
                should be retried.
            max_refresh_attempts (int): The maximum number of times to attempt
                to refresh the credentials and retry the request.
        """

        if http is None:
            http = _make_default_http()

        self.http = http
        self.credentials = credentials
        self._refresh_status_codes = refresh_status_codes
        self._max_refresh_attempts = max_refresh_attempts
        # Request instance used by internal methods (for example,
        # credentials.refresh).
        self._request = Request(self.http)

    def request(self, uri, method='GET', body=None, headers=None,
                **kwargs):
        """Implementation of httplib2's Http.request."""

        _credential_refresh_attempt = kwargs.pop(
            '_credential_refresh_attempt', 0)

        # Make a copy of the headers. They will be modified by the credentials
        # and we want to pass the original headers if we recurse.
        request_headers = headers.copy() if headers is not None else {}

        self.credentials.before_request(
            self._request, method, uri, request_headers)

        # Check if the body is a file-like stream, and if so, save the body
        # stream position so that it can be restored in case of refresh.
        body_stream_position = None
        if all(getattr(body, stream_prop, None) for stream_prop in
               _STREAM_PROPERTIES):
            body_stream_position = body.tell()

        # Make the request.
        response, content = self.http.request(
            uri, method, body=body, headers=request_headers, **kwargs)

        # If the response indicated that the credentials needed to be
        # refreshed, then refresh the credentials and re-attempt the
        # request.
        # A stored token may expire between the time it is retrieved and
        # the time the request is made, so we may need to try twice.
        if (response.status in self._refresh_status_codes
                and _credential_refresh_attempt < self._max_refresh_attempts):

            _LOGGER.info(
                'Refreshing credentials due to a %s response. Attempt %s/%s.',
                response.status, _credential_refresh_attempt + 1,
                self._max_refresh_attempts)

            self.credentials.refresh(self._request)

            # Restore the body's stream position if needed.
            if body_stream_position is not None:
                body.seek(body_stream_position)

            # Recurse. Pass in the original headers, not our modified set.
            return self.request(
                uri, method, body=body, headers=headers,
                _credential_refresh_attempt=_credential_refresh_attempt + 1,
                **kwargs)

        return response, content

    def add_certificate(self, key, cert, domain, password=None):
        """Proxy to httplib2.Http.add_certificate."""
        self.http.add_certificate(key, cert, domain, password=password)

    @property
    def connections(self):
        """Proxy to httplib2.Http.connections."""
        return self.http.connections

    @connections.setter
    def connections(self, value):
        """Proxy to httplib2.Http.connections."""
        self.http.connections = value

    @property
    def follow_redirects(self):
        """Proxy to httplib2.Http.follow_redirects."""
        return self.http.follow_redirects

    @follow_redirects.setter
    def follow_redirects(self, value):
        """Proxy to httplib2.Http.follow_redirects."""
        self.http.follow_redirects = value

    @property
    def timeout(self):
        """Proxy to httplib2.Http.timeout."""
        return self.http.timeout

    @timeout.setter
    def timeout(self, value):
        """Proxy to httplib2.Http.timeout."""
        self.http.timeout = value