Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google_auth_httplib2.py: 48%

88 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

1# Copyright 2016 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"""Transport adapter for httplib2.""" 

16 

17from __future__ import absolute_import 

18 

19import http.client 

20import logging 

21 

22from google.auth import exceptions 

23from google.auth import transport 

24import httplib2 

25 

26 

27_LOGGER = logging.getLogger(__name__) 

28# Properties present in file-like streams / buffers. 

29_STREAM_PROPERTIES = ("read", "seek", "tell") 

30 

31 

32class _Response(transport.Response): 

33 """httplib2 transport response adapter. 

34 

35 Args: 

36 response (httplib2.Response): The raw httplib2 response. 

37 data (bytes): The response body. 

38 """ 

39 

40 def __init__(self, response, data): 

41 self._response = response 

42 self._data = data 

43 

44 @property 

45 def status(self): 

46 """int: The HTTP status code.""" 

47 return self._response.status 

48 

49 @property 

50 def headers(self): 

51 """Mapping[str, str]: The HTTP response headers.""" 

52 return dict(self._response) 

53 

54 @property 

55 def data(self): 

56 """bytes: The response body.""" 

57 return self._data 

58 

59 

60class Request(transport.Request): 

61 """httplib2 request adapter. 

62 

63 This class is used internally for making requests using various transports 

64 in a consistent way. If you use :class:`AuthorizedHttp` you do not need 

65 to construct or use this class directly. 

66 

67 This class can be useful if you want to manually refresh a 

68 :class:`~google.auth.credentials.Credentials` instance:: 

69 

70 import google_auth_httplib2 

71 import httplib2 

72 

73 http = httplib2.Http() 

74 request = google_auth_httplib2.Request(http) 

75 

76 credentials.refresh(request) 

77 

78 Args: 

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

80 requests. 

81 

82 .. automethod:: __call__ 

83 """ 

84 

85 def __init__(self, http): 

86 self.http = http 

87 

88 def __call__( 

89 self, url, method="GET", body=None, headers=None, timeout=None, **kwargs 

90 ): 

91 """Make an HTTP request using httplib2. 

92 

93 Args: 

94 url (str): The URI to be requested. 

95 method (str): The HTTP method to use for the request. Defaults 

96 to 'GET'. 

97 body (bytes): The payload / body in HTTP request. 

98 headers (Mapping[str, str]): Request headers. 

99 timeout (Optional[int]): The number of seconds to wait for a 

100 response from the server. This is ignored by httplib2 and will 

101 issue a warning. 

102 kwargs: Additional arguments passed throught to the underlying 

103 :meth:`httplib2.Http.request` method. 

104 

105 Returns: 

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

107 

108 Raises: 

109 google.auth.exceptions.TransportError: If any exception occurred. 

110 """ 

111 if timeout is not None: 

112 _LOGGER.warning( 

113 "httplib2 transport does not support per-request timeout. " 

114 "Set the timeout when constructing the httplib2.Http instance." 

115 ) 

116 

117 try: 

118 _LOGGER.debug("Making request: %s %s", method, url) 

119 response, data = self.http.request( 

120 url, method=method, body=body, headers=headers, **kwargs 

121 ) 

122 return _Response(response, data) 

123 # httplib2 should catch the lower http error, this is a bug and 

124 # needs to be fixed there. Catch the error for the meanwhile. 

125 except (httplib2.HttpLib2Error, http.client.HTTPException) as exc: 

126 raise exceptions.TransportError(exc) 

127 

128 

129def _make_default_http(): 

130 """Returns a default httplib2.Http instance.""" 

131 return httplib2.Http() 

132 

133 

134class AuthorizedHttp(object): 

135 """A httplib2 HTTP class with credentials. 

136 

137 This class is used to perform requests to API endpoints that require 

138 authorization:: 

139 

140 from google.auth.transport._httplib2 import AuthorizedHttp 

141 

142 authed_http = AuthorizedHttp(credentials) 

143 

144 response = authed_http.request( 

145 'https://www.googleapis.com/storage/v1/b') 

146 

147 This class implements :meth:`request` in the same way as 

148 :class:`httplib2.Http` and can usually be used just like any other 

149 instance of :class:``httplib2.Http`. 

150 

151 The underlying :meth:`request` implementation handles adding the 

152 credentials' headers to the request and refreshing credentials as needed. 

153 """ 

154 

155 def __init__( 

156 self, 

157 credentials, 

158 http=None, 

159 refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, 

160 max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, 

161 ): 

162 """ 

163 Args: 

164 credentials (google.auth.credentials.Credentials): The credentials 

165 to add to the request. 

166 http (httplib2.Http): The underlying HTTP object to 

167 use to make requests. If not specified, a 

168 :class:`httplib2.Http` instance will be constructed. 

169 refresh_status_codes (Sequence[int]): Which HTTP status codes 

170 indicate that credentials should be refreshed and the request 

171 should be retried. 

172 max_refresh_attempts (int): The maximum number of times to attempt 

173 to refresh the credentials and retry the request. 

174 """ 

175 

176 if http is None: 

177 http = _make_default_http() 

178 

179 self.http = http 

180 self.credentials = credentials 

181 self._refresh_status_codes = refresh_status_codes 

182 self._max_refresh_attempts = max_refresh_attempts 

183 # Request instance used by internal methods (for example, 

184 # credentials.refresh). 

185 self._request = Request(self.http) 

186 

187 def close(self): 

188 """Calls httplib2's Http.close""" 

189 self.http.close() 

190 

191 def request( 

192 self, 

193 uri, 

194 method="GET", 

195 body=None, 

196 headers=None, 

197 redirections=httplib2.DEFAULT_MAX_REDIRECTS, 

198 connection_type=None, 

199 **kwargs 

200 ): 

201 """Implementation of httplib2's Http.request.""" 

202 

203 _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0) 

204 

205 # Make a copy of the headers. They will be modified by the credentials 

206 # and we want to pass the original headers if we recurse. 

207 request_headers = headers.copy() if headers is not None else {} 

208 

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

210 

211 # Check if the body is a file-like stream, and if so, save the body 

212 # stream position so that it can be restored in case of refresh. 

213 body_stream_position = None 

214 if all(getattr(body, stream_prop, None) for stream_prop in _STREAM_PROPERTIES): 

215 body_stream_position = body.tell() 

216 

217 # Make the request. 

218 response, content = self.http.request( 

219 uri, 

220 method, 

221 body=body, 

222 headers=request_headers, 

223 redirections=redirections, 

224 connection_type=connection_type, 

225 **kwargs 

226 ) 

227 

228 # If the response indicated that the credentials needed to be 

229 # refreshed, then refresh the credentials and re-attempt the 

230 # request. 

231 # A stored token may expire between the time it is retrieved and 

232 # the time the request is made, so we may need to try twice. 

233 if ( 

234 response.status in self._refresh_status_codes 

235 and _credential_refresh_attempt < self._max_refresh_attempts 

236 ): 

237 

238 _LOGGER.info( 

239 "Refreshing credentials due to a %s response. Attempt %s/%s.", 

240 response.status, 

241 _credential_refresh_attempt + 1, 

242 self._max_refresh_attempts, 

243 ) 

244 

245 self.credentials.refresh(self._request) 

246 

247 # Restore the body's stream position if needed. 

248 if body_stream_position is not None: 

249 body.seek(body_stream_position) 

250 

251 # Recurse. Pass in the original headers, not our modified set. 

252 return self.request( 

253 uri, 

254 method, 

255 body=body, 

256 headers=headers, 

257 redirections=redirections, 

258 connection_type=connection_type, 

259 _credential_refresh_attempt=_credential_refresh_attempt + 1, 

260 **kwargs 

261 ) 

262 

263 return response, content 

264 

265 def add_certificate(self, key, cert, domain, password=None): 

266 """Proxy to httplib2.Http.add_certificate.""" 

267 self.http.add_certificate(key, cert, domain, password=password) 

268 

269 @property 

270 def connections(self): 

271 """Proxy to httplib2.Http.connections.""" 

272 return self.http.connections 

273 

274 @connections.setter 

275 def connections(self, value): 

276 """Proxy to httplib2.Http.connections.""" 

277 self.http.connections = value 

278 

279 @property 

280 def follow_redirects(self): 

281 """Proxy to httplib2.Http.follow_redirects.""" 

282 return self.http.follow_redirects 

283 

284 @follow_redirects.setter 

285 def follow_redirects(self, value): 

286 """Proxy to httplib2.Http.follow_redirects.""" 

287 self.http.follow_redirects = value 

288 

289 @property 

290 def timeout(self): 

291 """Proxy to httplib2.Http.timeout.""" 

292 return self.http.timeout 

293 

294 @timeout.setter 

295 def timeout(self, value): 

296 """Proxy to httplib2.Http.timeout.""" 

297 self.http.timeout = value 

298 

299 @property 

300 def redirect_codes(self): 

301 """Proxy to httplib2.Http.redirect_codes.""" 

302 return self.http.redirect_codes 

303 

304 @redirect_codes.setter 

305 def redirect_codes(self, value): 

306 """Proxy to httplib2.Http.redirect_codes.""" 

307 self.http.redirect_codes = value