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

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: openssl package is now optional (correct patch) 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
« no previous file with comments | « apply_issue.py ('k') | third_party/httplib2/LICENSE » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 import third_party.oauth2client.client as oa2client
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 if not oa2client.HAS_OPENSSL:
478 logging.error("Support for OpenSSL hasn't been found, "
479 "OAuth2 support requires it.")
480 logging.error("Installing pyopenssl will probably solve this issue.")
481 raise RuntimeError('No OpenSSL support')
482 creds = oa2client.SignedJwtAssertionCredentials(
483 client_id,
484 client_private_key,
485 'https://www.googleapis.com/auth/userinfo.email',
486 private_key_password=private_key_password,
487 user_agent=user_agent)
488
489 self._http = creds.authorize(httplib2.Http(timeout=timeout))
490
491 def Send(self,
492 request_path,
493 payload=None,
494 content_type='application/octet-stream',
495 timeout=None,
496 extra_headers=None,
497 **kwargs):
498 """Send a POST or GET request to the server.
499
500 Args:
501 request_path: path on the server to hit. This is concatenated with the
502 value of 'host' provided to the constructor.
503 payload: request is a POST if not None, GET otherwise
504 timeout: in seconds
505 extra_headers: (dict)
506 """
507 # This method signature should match upload.py:AbstractRpcServer.Send()
508 method = 'GET'
509
510 headers = self.extra_headers.copy()
511 headers.update(extra_headers or {})
512
513 if payload is not None:
514 method = 'POST'
515 headers['Content-Type'] = content_type
516 raise NotImplementedError('POST requests are not yet supported.')
517
518 prev_timeout = self._http.timeout
519 try:
520 if timeout:
521 self._http.timeout = timeout
522 # TODO(pgervais) implement some kind of retry mechanism (see upload.py).
523 url = self.host + request_path
524 if kwargs:
525 url += "?" + urllib.urlencode(kwargs)
526
527 ret = self._http.request(url,
528 method=method,
529 body=payload,
530 headers=headers)
531 if not ret[0]['content-location'].startswith(self.host):
532 upload.logging.warning('Redirection to host %s detected: '
533 'login may have failed/expired.'
534 % urlparse.urlparse(
535 ret[0]['content-location']).netloc)
536 return ret[1]
537
538 finally:
539 self._http.timeout = prev_timeout
540
541
542 class JwtOAuth2Rietveld(Rietveld):
543 """Access to Rietveld using OAuth authentication.
544
545 This class is supposed to be used only by bots, since this kind of
546 access is restricted to service accounts.
547 """
548 # The parent__init__ is not called on purpose.
549 # pylint: disable=W0231
550 def __init__(self,
551 url,
552 client_id,
553 client_private_key_file,
554 private_key_password=None,
555 extra_headers=None):
556 if private_key_password is None: # '' means 'empty password'
557 private_key_password = 'notasecret'
558
559 self.url = url.rstrip('/')
560 with open(client_private_key_file, 'rb') as f:
561 client_private_key = f.read()
562 self.rpc_server = OAuthRpcServer(url,
563 client_id,
564 client_private_key,
565 private_key_password=private_key_password,
566 extra_headers=extra_headers or {})
567 self._xsrf_token = None
568 self._xsrf_token_time = None
569
570
441 class CachingRietveld(Rietveld): 571 class CachingRietveld(Rietveld):
442 """Caches the common queries. 572 """Caches the common queries.
443 573
444 Not to be used in long-standing processes, like the commit queue. 574 Not to be used in long-standing processes, like the commit queue.
445 """ 575 """
446 def __init__(self, *args, **kwargs): 576 def __init__(self, *args, **kwargs):
447 super(CachingRietveld, self).__init__(*args, **kwargs) 577 super(CachingRietveld, self).__init__(*args, **kwargs)
448 self._cache = {} 578 self._cache = {}
449 579
450 def _lookup(self, function_name, args, update): 580 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 689 def trigger_try_jobs( # pylint:disable=R0201
560 self, issue, patchset, reason, clobber, revision, builders_and_tests, 690 self, issue, patchset, reason, clobber, revision, builders_and_tests,
561 master=None): 691 master=None):
562 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' % 692 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' %
563 (builders_and_tests, issue)) 693 (builders_and_tests, issue))
564 694
565 def trigger_distributed_try_jobs( # pylint:disable=R0201 695 def trigger_distributed_try_jobs( # pylint:disable=R0201
566 self, issue, patchset, reason, clobber, revision, masters): 696 self, issue, patchset, reason, clobber, revision, masters):
567 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' % 697 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' %
568 (masters, issue)) 698 (masters, issue))
OLDNEW
« no previous file with comments | « apply_issue.py ('k') | third_party/httplib2/LICENSE » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698