Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(110)

Side by Side Diff: appengine/chromium_build_logs/third_party/apiclient/oauth.py

Issue 1260293009: make version of ts_mon compatible with appengine (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: clean up code Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright (C) 2010 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 """Utilities for OAuth.
16
17 Utilities for making it easier to work with OAuth.
18 """
19
20 __author__ = 'jcgregorio@google.com (Joe Gregorio)'
21
22
23 import copy
24 import httplib2
25 import logging
26 import oauth2 as oauth
27 import urllib
28 import urlparse
29
30 from oauth2client.anyjson import simplejson
31 from oauth2client.client import Credentials
32 from oauth2client.client import Flow
33 from oauth2client.client import Storage
34
35 try:
36 from urlparse import parse_qsl
37 except ImportError:
38 from cgi import parse_qsl
39
40
41 class Error(Exception):
42 """Base error for this module."""
43 pass
44
45
46 class RequestError(Error):
47 """Error occurred during request."""
48 pass
49
50
51 class MissingParameter(Error):
52 pass
53
54
55 class CredentialsInvalidError(Error):
56 pass
57
58
59 def _abstract():
60 raise NotImplementedError('You need to override this function')
61
62
63 def _oauth_uri(name, discovery, params):
64 """Look up the OAuth URI from the discovery
65 document and add query parameters based on
66 params.
67
68 name - The name of the OAuth URI to lookup, one
69 of 'request', 'access', or 'authorize'.
70 discovery - Portion of discovery document the describes
71 the OAuth endpoints.
72 params - Dictionary that is used to form the query parameters
73 for the specified URI.
74 """
75 if name not in ['request', 'access', 'authorize']:
76 raise KeyError(name)
77 keys = discovery[name]['parameters'].keys()
78 query = {}
79 for key in keys:
80 if key in params:
81 query[key] = params[key]
82 return discovery[name]['url'] + '?' + urllib.urlencode(query)
83
84
85
86 class OAuthCredentials(Credentials):
87 """Credentials object for OAuth 1.0a
88 """
89
90 def __init__(self, consumer, token, user_agent):
91 """
92 consumer - An instance of oauth.Consumer.
93 token - An instance of oauth.Token constructed with
94 the access token and secret.
95 user_agent - The HTTP User-Agent to provide for this application.
96 """
97 self.consumer = consumer
98 self.token = token
99 self.user_agent = user_agent
100 self.store = None
101
102 # True if the credentials have been revoked
103 self._invalid = False
104
105 @property
106 def invalid(self):
107 """True if the credentials are invalid, such as being revoked."""
108 return getattr(self, "_invalid", False)
109
110 def set_store(self, store):
111 """Set the storage for the credential.
112
113 Args:
114 store: callable, a callable that when passed a Credential
115 will store the credential back to where it came from.
116 This is needed to store the latest access_token if it
117 has been revoked.
118 """
119 self.store = store
120
121 def __getstate__(self):
122 """Trim the state down to something that can be pickled."""
123 d = copy.copy(self.__dict__)
124 del d['store']
125 return d
126
127 def __setstate__(self, state):
128 """Reconstitute the state of the object from being pickled."""
129 self.__dict__.update(state)
130 self.store = None
131
132 def authorize(self, http):
133 """Authorize an httplib2.Http instance with these Credentials
134
135 Args:
136 http - An instance of httplib2.Http
137 or something that acts like it.
138
139 Returns:
140 A modified instance of http that was passed in.
141
142 Example:
143
144 h = httplib2.Http()
145 h = credentials.authorize(h)
146
147 You can't create a new OAuth
148 subclass of httplib2.Authenication because
149 it never gets passed the absolute URI, which is
150 needed for signing. So instead we have to overload
151 'request' with a closure that adds in the
152 Authorization header and then calls the original version
153 of 'request()'.
154 """
155 request_orig = http.request
156 signer = oauth.SignatureMethod_HMAC_SHA1()
157
158 # The closure that will replace 'httplib2.Http.request'.
159 def new_request(uri, method='GET', body=None, headers=None,
160 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
161 connection_type=None):
162 """Modify the request headers to add the appropriate
163 Authorization header."""
164 response_code = 302
165 http.follow_redirects = False
166 while response_code in [301, 302]:
167 req = oauth.Request.from_consumer_and_token(
168 self.consumer, self.token, http_method=method, http_url=uri)
169 req.sign_request(signer, self.consumer, self.token)
170 if headers is None:
171 headers = {}
172 headers.update(req.to_header())
173 if 'user-agent' in headers:
174 headers['user-agent'] = self.user_agent + ' ' + headers['user-agent']
175 else:
176 headers['user-agent'] = self.user_agent
177
178 resp, content = request_orig(uri, method, body, headers,
179 redirections, connection_type)
180 response_code = resp.status
181 if response_code in [301, 302]:
182 uri = resp['location']
183
184 # Update the stored credential if it becomes invalid.
185 if response_code == 401:
186 logging.info('Access token no longer valid: %s' % content)
187 self._invalid = True
188 if self.store is not None:
189 self.store(self)
190 raise CredentialsInvalidError("Credentials are no longer valid.")
191
192 return resp, content
193
194 http.request = new_request
195 return http
196
197
198 class TwoLeggedOAuthCredentials(Credentials):
199 """Two Legged Credentials object for OAuth 1.0a.
200
201 The Two Legged object is created directly, not from a flow. Once you
202 authorize and httplib2.Http instance you can change the requestor and that
203 change will propogate to the authorized httplib2.Http instance. For example:
204
205 http = httplib2.Http()
206 http = credentials.authorize(http)
207
208 credentials.requestor = 'foo@example.info'
209 http.request(...)
210 credentials.requestor = 'bar@example.info'
211 http.request(...)
212 """
213
214 def __init__(self, consumer_key, consumer_secret, user_agent):
215 """
216 Args:
217 consumer_key: string, An OAuth 1.0 consumer key
218 consumer_secret: string, An OAuth 1.0 consumer secret
219 user_agent: string, The HTTP User-Agent to provide for this application.
220 """
221 self.consumer = oauth.Consumer(consumer_key, consumer_secret)
222 self.user_agent = user_agent
223 self.store = None
224
225 # email address of the user to act on the behalf of.
226 self._requestor = None
227
228 @property
229 def invalid(self):
230 """True if the credentials are invalid, such as being revoked.
231
232 Always returns False for Two Legged Credentials.
233 """
234 return False
235
236 def getrequestor(self):
237 return self._requestor
238
239 def setrequestor(self, email):
240 self._requestor = email
241
242 requestor = property(getrequestor, setrequestor, None,
243 'The email address of the user to act on behalf of')
244
245 def set_store(self, store):
246 """Set the storage for the credential.
247
248 Args:
249 store: callable, a callable that when passed a Credential
250 will store the credential back to where it came from.
251 This is needed to store the latest access_token if it
252 has been revoked.
253 """
254 self.store = store
255
256 def __getstate__(self):
257 """Trim the state down to something that can be pickled."""
258 d = copy.copy(self.__dict__)
259 del d['store']
260 return d
261
262 def __setstate__(self, state):
263 """Reconstitute the state of the object from being pickled."""
264 self.__dict__.update(state)
265 self.store = None
266
267 def authorize(self, http):
268 """Authorize an httplib2.Http instance with these Credentials
269
270 Args:
271 http - An instance of httplib2.Http
272 or something that acts like it.
273
274 Returns:
275 A modified instance of http that was passed in.
276
277 Example:
278
279 h = httplib2.Http()
280 h = credentials.authorize(h)
281
282 You can't create a new OAuth
283 subclass of httplib2.Authenication because
284 it never gets passed the absolute URI, which is
285 needed for signing. So instead we have to overload
286 'request' with a closure that adds in the
287 Authorization header and then calls the original version
288 of 'request()'.
289 """
290 request_orig = http.request
291 signer = oauth.SignatureMethod_HMAC_SHA1()
292
293 # The closure that will replace 'httplib2.Http.request'.
294 def new_request(uri, method='GET', body=None, headers=None,
295 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
296 connection_type=None):
297 """Modify the request headers to add the appropriate
298 Authorization header."""
299 response_code = 302
300 http.follow_redirects = False
301 while response_code in [301, 302]:
302 # add in xoauth_requestor_id=self._requestor to the uri
303 if self._requestor is None:
304 raise MissingParameter(
305 'Requestor must be set before using TwoLeggedOAuthCredentials')
306 parsed = list(urlparse.urlparse(uri))
307 q = parse_qsl(parsed[4])
308 q.append(('xoauth_requestor_id', self._requestor))
309 parsed[4] = urllib.urlencode(q)
310 uri = urlparse.urlunparse(parsed)
311
312 req = oauth.Request.from_consumer_and_token(
313 self.consumer, None, http_method=method, http_url=uri)
314 req.sign_request(signer, self.consumer, None)
315 if headers is None:
316 headers = {}
317 headers.update(req.to_header())
318 if 'user-agent' in headers:
319 headers['user-agent'] = self.user_agent + ' ' + headers['user-agent']
320 else:
321 headers['user-agent'] = self.user_agent
322 resp, content = request_orig(uri, method, body, headers,
323 redirections, connection_type)
324 response_code = resp.status
325 if response_code in [301, 302]:
326 uri = resp['location']
327
328 if response_code == 401:
329 logging.info('Access token no longer valid: %s' % content)
330 # Do not store the invalid state of the Credentials because
331 # being 2LO they could be reinstated in the future.
332 raise CredentialsInvalidError("Credentials are invalid.")
333
334 return resp, content
335
336 http.request = new_request
337 return http
338
339
340 class FlowThreeLegged(Flow):
341 """Does the Three Legged Dance for OAuth 1.0a.
342 """
343
344 def __init__(self, discovery, consumer_key, consumer_secret, user_agent,
345 **kwargs):
346 """
347 discovery - Section of the API discovery document that describes
348 the OAuth endpoints.
349 consumer_key - OAuth consumer key
350 consumer_secret - OAuth consumer secret
351 user_agent - The HTTP User-Agent that identifies the application.
352 **kwargs - The keyword arguments are all optional and required
353 parameters for the OAuth calls.
354 """
355 self.discovery = discovery
356 self.consumer_key = consumer_key
357 self.consumer_secret = consumer_secret
358 self.user_agent = user_agent
359 self.params = kwargs
360 self.request_token = {}
361 required = {}
362 for uriinfo in discovery.itervalues():
363 for name, value in uriinfo['parameters'].iteritems():
364 if value['required'] and not name.startswith('oauth_'):
365 required[name] = 1
366 for key in required.iterkeys():
367 if key not in self.params:
368 raise MissingParameter('Required parameter %s not supplied' % key)
369
370 def step1_get_authorize_url(self, oauth_callback='oob'):
371 """Returns a URI to redirect to the provider.
372
373 oauth_callback - Either the string 'oob' for a non-web-based application,
374 or a URI that handles the callback from the authorization
375 server.
376
377 If oauth_callback is 'oob' then pass in the
378 generated verification code to step2_exchange,
379 otherwise pass in the query parameters received
380 at the callback uri to step2_exchange.
381 """
382 consumer = oauth.Consumer(self.consumer_key, self.consumer_secret)
383 client = oauth.Client(consumer)
384
385 headers = {
386 'user-agent': self.user_agent,
387 'content-type': 'application/x-www-form-urlencoded'
388 }
389 body = urllib.urlencode({'oauth_callback': oauth_callback})
390 uri = _oauth_uri('request', self.discovery, self.params)
391
392 resp, content = client.request(uri, 'POST', headers=headers,
393 body=body)
394 if resp['status'] != '200':
395 logging.error('Failed to retrieve temporary authorization: %s', content)
396 raise RequestError('Invalid response %s.' % resp['status'])
397
398 self.request_token = dict(parse_qsl(content))
399
400 auth_params = copy.copy(self.params)
401 auth_params['oauth_token'] = self.request_token['oauth_token']
402
403 return _oauth_uri('authorize', self.discovery, auth_params)
404
405 def step2_exchange(self, verifier):
406 """Exhanges an authorized request token
407 for OAuthCredentials.
408
409 Args:
410 verifier: string, dict - either the verifier token, or a dictionary
411 of the query parameters to the callback, which contains
412 the oauth_verifier.
413 Returns:
414 The Credentials object.
415 """
416
417 if not (isinstance(verifier, str) or isinstance(verifier, unicode)):
418 verifier = verifier['oauth_verifier']
419
420 token = oauth.Token(
421 self.request_token['oauth_token'],
422 self.request_token['oauth_token_secret'])
423 token.set_verifier(verifier)
424 consumer = oauth.Consumer(self.consumer_key, self.consumer_secret)
425 client = oauth.Client(consumer, token)
426
427 headers = {
428 'user-agent': self.user_agent,
429 'content-type': 'application/x-www-form-urlencoded'
430 }
431
432 uri = _oauth_uri('access', self.discovery, self.params)
433 resp, content = client.request(uri, 'POST', headers=headers)
434 if resp['status'] != '200':
435 logging.error('Failed to retrieve access token: %s', content)
436 raise RequestError('Invalid response %s.' % resp['status'])
437
438 oauth_params = dict(parse_qsl(content))
439 token = oauth.Token(
440 oauth_params['oauth_token'],
441 oauth_params['oauth_token_secret'])
442
443 return OAuthCredentials(consumer, token, self.user_agent)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698