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

Side by Side Diff: rietveld.py

Issue 183793010: Added OAuth2 authentication to apply_issue (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Fixed invocation Created 6 years, 9 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
1 # coding: utf-8 1 # coding: utf-8
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 """Defines class Rietveld to easily access a rietveld instance. 5 """Defines class Rietveld to easily access a rietveld instance.
6 6
7 Security implications: 7 Security implications:
8 8
9 The following hypothesis are made: 9 The following hypothesis are made:
10 - Rietveld enforces: 10 - Rietveld enforces:
11 - Nobody else than issue owner can upload a patch set 11 - Nobody else than issue owner can upload a patch set
12 - Verifies the issue owner credentials when creating new issues 12 - Verifies the issue owner credentials when creating new issues
13 - A issue owner can't change once the issue is created 13 - A issue owner can't change once the issue is created
14 - A patch set cannot be modified 14 - A patch set cannot be modified
15 """ 15 """
16 16
17 import copy 17 import copy
18 import json 18 import json
19 import logging 19 import logging
20 import re 20 import re
21 import ssl 21 import ssl
22 import time 22 import time
23 import urllib
23 import urllib2 24 import urllib2
25 import urlparse
26
27 import patch
24 28
25 from third_party import upload 29 from third_party import upload
26 import patch 30 from third_party.oauth2client.client import SignedJwtAssertionCredentials
31 from third_party import httplib2
27 32
28 # Hack out upload logging.info() 33 # Hack out upload logging.info()
29 upload.logging = logging.getLogger('upload') 34 upload.logging = logging.getLogger('upload')
30 # Mac pylint choke on this line. 35 # Mac pylint choke on this line.
31 upload.logging.setLevel(logging.WARNING) # pylint: disable=E1103 36 upload.logging.setLevel(logging.WARNING) # pylint: disable=E1103
32 37
33 38
34 class Rietveld(object): 39 class Rietveld(object):
35 """Accesses rietveld.""" 40 """Accesses rietveld."""
36 def __init__(self, url, email, password, extra_headers=None): 41 def __init__(self, url, email, password, extra_headers=None):
37 self.url = url.rstrip('/') 42 self.url = url.rstrip('/')
38 # TODO(maruel): It's not awesome but maybe necessary to retrieve the value. 43 # TODO(maruel): It's not awesome but maybe necessary to retrieve the value.
39 # It happens when the presubmit check is ran out of process, the cookie 44 # It happens when the presubmit check is ran out of process, the cookie
40 # needed to be recreated from the credentials. Instead, it should pass the 45 # needed to be recreated from the credentials. Instead, it should pass the
41 # email and the cookie. 46 # email and the cookie.
42 self.email = email
43 self.password = password
44 if email and password: 47 if email and password:
45 get_creds = lambda: (email, password) 48 get_creds = lambda: (email, password)
46 self.rpc_server = upload.HttpRpcServer( 49 self.rpc_server = upload.HttpRpcServer(
47 self.url, 50 self.url,
48 get_creds, 51 get_creds,
49 extra_headers=extra_headers or {}) 52 extra_headers=extra_headers or {})
50 else: 53 else:
51 if email == '': 54 if email == '':
52 # If email is given as an empty string, then assume we want to make 55 # If email is given as an empty string, then assume we want to make
53 # requests that do not need authentication. Bypass authentication by 56 # requests that do not need authentication. Bypass authentication by
(...skipping 377 matching lines...) Expand 10 before | Expand all | Expand 10 after
431 raise 434 raise
432 # If reaching this line, loop again. Uses a small backoff. 435 # If reaching this line, loop again. Uses a small backoff.
433 time.sleep(1+maxtries*2) 436 time.sleep(1+maxtries*2)
434 finally: 437 finally:
435 upload.ErrorExit = old_error_exit 438 upload.ErrorExit = old_error_exit
436 439
437 # DEPRECATED. 440 # DEPRECATED.
438 Send = get 441 Send = get
439 442
440 443
444 class OAuthRpcServer(object):
445 def __init__(self,
446 host,
447 client_id,
448 client_private_key,
449 private_key_password='notasecret',
450 user_agent=None,
451 timeout=None,
452 extra_headers=None):
453 """Wrapper around httplib2.Http() that handles authentication.
454
455 client_id: client id for service account
456 client_private_key: encrypted private key, as a string
457 private_key_password: password used to decrypt the private key
458 """
459
460 # Enforce https
461 host_parts = urlparse.urlparse(host)
462
463 if host_parts.scheme == 'https': # fine
464 self.host = host
465 elif host_parts.scheme == 'http':
466 upload.logging.warning('Changing protocol to https')
467 self.host = 'https' + host[4:]
468 else:
469 msg = 'Invalid url provided: %s' % host
470 upload.logging.error(msg)
471 raise ValueError(msg)
472
473 self.host = self.host.rstrip('/')
474
475 self.extra_headers = extra_headers or {}
476
477 creds = SignedJwtAssertionCredentials(
478 client_id,
479 client_private_key,
480 'https://www.googleapis.com/auth/userinfo.email',
481 private_key_password=private_key_password,
482 user_agent=user_agent)
483
484 self._http = creds.authorize(httplib2.Http(timeout=timeout))
485
486 def Send(self,
487 request_path,
488 payload=None,
489 content_type='application/octet-stream',
490 timeout=None,
491 extra_headers=None,
492 **kwargs):
493 """Send a POST or GET request to the server.
494
495 Args:
496 request_path: path on the server to hit. This is concatenated with the
497 value of 'host' provided to the constructor.
498 payload: request is a POST if not None, GET otherwise
499 timeout: in seconds
500 extra_headers: (dict)
501 """
502 # This method signature should match upload.py:AbstractRpcServer.Send()
503 method = 'GET'
504
505 headers = self.extra_headers.copy()
506 headers.update(extra_headers or {})
507
508 if payload is not None:
509 method = 'POST'
510 headers['Content-Type'] = content_type
511 raise NotImplementedError('POST requests are not yet supported.')
512
513 prev_timeout = self._http.timeout
514 try:
515 if timeout:
516 self._http.timeout = timeout
517 # TODO(pgervais) implement some kind of retry mechanism (see upload.py).
518 url = self.host + request_path
519 if kwargs:
520 url += "?" + urllib.urlencode(kwargs)
521
522 ret = self._http.request(url,
523 method=method,
524 body=payload,
525 headers=headers)
526 return ret[1]
527
528 finally:
529 self._http.timeout = prev_timeout
530
531
532 class JwtOAuth2Rietveld(Rietveld):
533 """Access to Rietveld using OAuth authentication.
534
535 This class is supposed to be used only by bots, since this kind of
536 access is restricted to service accounts.
537 """
538 # The parent__init__ is not called on purpose.
M-A Ruel 2014/03/22 01:09:09 Not a fan. Either split up the code into a third b
pgervais 2014/03/24 20:27:36 Well, this class is kind of a hack anyway, because
539 # pylint: disable=W0231
540 def __init__(self,
541 url,
542 client_id,
543 client_private_key_file,
544 private_key_password=None,
545 extra_headers=None):
546 if private_key_password is None: # '' means 'empty password'
547 private_key_password = 'notasecret'
548
549 self.url = url.rstrip('/')
550 with open(client_private_key_file, 'rb') as f:
551 client_private_key = f.read()
552 self.rpc_server = OAuthRpcServer(url,
553 client_id,
554 client_private_key,
555 private_key_password=private_key_password,
556 extra_headers=extra_headers or {})
557 self._xsrf_token = None
558 self._xsrf_token_time = None
559
560
441 class CachingRietveld(Rietveld): 561 class CachingRietveld(Rietveld):
442 """Caches the common queries. 562 """Caches the common queries.
443 563
444 Not to be used in long-standing processes, like the commit queue. 564 Not to be used in long-standing processes, like the commit queue.
445 """ 565 """
446 def __init__(self, *args, **kwargs): 566 def __init__(self, *args, **kwargs):
447 super(CachingRietveld, self).__init__(*args, **kwargs) 567 super(CachingRietveld, self).__init__(*args, **kwargs)
448 self._cache = {} 568 self._cache = {}
449 569
450 def _lookup(self, function_name, args, update): 570 def _lookup(self, function_name, args, update):
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after
559 def trigger_try_jobs( # pylint:disable=R0201 679 def trigger_try_jobs( # pylint:disable=R0201
560 self, issue, patchset, reason, clobber, revision, builders_and_tests, 680 self, issue, patchset, reason, clobber, revision, builders_and_tests,
561 master=None): 681 master=None):
562 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' % 682 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' %
563 (builders_and_tests, issue)) 683 (builders_and_tests, issue))
564 684
565 def trigger_distributed_try_jobs( # pylint:disable=R0201 685 def trigger_distributed_try_jobs( # pylint:disable=R0201
566 self, issue, patchset, reason, clobber, revision, masters): 686 self, issue, patchset, reason, clobber, revision, masters):
567 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' % 687 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' %
568 (masters, issue)) 688 (masters, issue))
OLDNEW
« apply_issue.py ('K') | « apply_issue.py ('k') | third_party/httplib2/LICENSE » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698