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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « apply_issue.py ('k') | third_party/httplib2/LICENSE » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: rietveld.py
diff --git a/rietveld.py b/rietveld.py
index aad998e9c3e49d922ca38317c0e9c1381d88885a..da3ac40ee952a21b51cf299103e825d7ebd1a542 100644
--- a/rietveld.py
+++ b/rietveld.py
@@ -20,11 +20,16 @@ import logging
import re
import ssl
import time
+import urllib
import urllib2
+import urlparse
-from third_party import upload
import patch
+from third_party import upload
+import third_party.oauth2client.client as oa2client
+from third_party import httplib2
+
# Hack out upload logging.info()
upload.logging = logging.getLogger('upload')
# Mac pylint choke on this line.
@@ -39,8 +44,6 @@ class Rietveld(object):
# It happens when the presubmit check is ran out of process, the cookie
# needed to be recreated from the credentials. Instead, it should pass the
# email and the cookie.
- self.email = email
- self.password = password
if email and password:
get_creds = lambda: (email, password)
self.rpc_server = upload.HttpRpcServer(
@@ -438,6 +441,133 @@ class Rietveld(object):
Send = get
+class OAuthRpcServer(object):
+ def __init__(self,
+ host,
+ client_id,
+ client_private_key,
+ private_key_password='notasecret',
+ user_agent=None,
+ timeout=None,
+ extra_headers=None):
+ """Wrapper around httplib2.Http() that handles authentication.
+
+ client_id: client id for service account
+ client_private_key: encrypted private key, as a string
+ private_key_password: password used to decrypt the private key
+ """
+
+ # Enforce https
+ host_parts = urlparse.urlparse(host)
+
+ if host_parts.scheme == 'https': # fine
+ self.host = host
+ elif host_parts.scheme == 'http':
+ upload.logging.warning('Changing protocol to https')
+ self.host = 'https' + host[4:]
+ else:
+ msg = 'Invalid url provided: %s' % host
+ upload.logging.error(msg)
+ raise ValueError(msg)
+
+ self.host = self.host.rstrip('/')
+
+ self.extra_headers = extra_headers or {}
+
+ if not oa2client.HAS_OPENSSL:
+ logging.error("Support for OpenSSL hasn't been found, "
+ "OAuth2 support requires it.")
+ logging.error("Installing pyopenssl will probably solve this issue.")
+ raise RuntimeError('No OpenSSL support')
+ creds = oa2client.SignedJwtAssertionCredentials(
+ client_id,
+ client_private_key,
+ 'https://www.googleapis.com/auth/userinfo.email',
+ private_key_password=private_key_password,
+ user_agent=user_agent)
+
+ self._http = creds.authorize(httplib2.Http(timeout=timeout))
+
+ def Send(self,
+ request_path,
+ payload=None,
+ content_type='application/octet-stream',
+ timeout=None,
+ extra_headers=None,
+ **kwargs):
+ """Send a POST or GET request to the server.
+
+ Args:
+ request_path: path on the server to hit. This is concatenated with the
+ value of 'host' provided to the constructor.
+ payload: request is a POST if not None, GET otherwise
+ timeout: in seconds
+ extra_headers: (dict)
+ """
+ # This method signature should match upload.py:AbstractRpcServer.Send()
+ method = 'GET'
+
+ headers = self.extra_headers.copy()
+ headers.update(extra_headers or {})
+
+ if payload is not None:
+ method = 'POST'
+ headers['Content-Type'] = content_type
+ raise NotImplementedError('POST requests are not yet supported.')
+
+ prev_timeout = self._http.timeout
+ try:
+ if timeout:
+ self._http.timeout = timeout
+ # TODO(pgervais) implement some kind of retry mechanism (see upload.py).
+ url = self.host + request_path
+ if kwargs:
+ url += "?" + urllib.urlencode(kwargs)
+
+ ret = self._http.request(url,
+ method=method,
+ body=payload,
+ headers=headers)
+ if not ret[0]['content-location'].startswith(self.host):
+ upload.logging.warning('Redirection to host %s detected: '
+ 'login may have failed/expired.'
+ % urlparse.urlparse(
+ ret[0]['content-location']).netloc)
+ return ret[1]
+
+ finally:
+ self._http.timeout = prev_timeout
+
+
+class JwtOAuth2Rietveld(Rietveld):
+ """Access to Rietveld using OAuth authentication.
+
+ This class is supposed to be used only by bots, since this kind of
+ access is restricted to service accounts.
+ """
+ # The parent__init__ is not called on purpose.
+ # pylint: disable=W0231
+ def __init__(self,
+ url,
+ client_id,
+ client_private_key_file,
+ private_key_password=None,
+ extra_headers=None):
+ if private_key_password is None: # '' means 'empty password'
+ private_key_password = 'notasecret'
+
+ self.url = url.rstrip('/')
+ with open(client_private_key_file, 'rb') as f:
+ client_private_key = f.read()
+ self.rpc_server = OAuthRpcServer(url,
+ client_id,
+ client_private_key,
+ private_key_password=private_key_password,
+ extra_headers=extra_headers or {})
+ self._xsrf_token = None
+ self._xsrf_token_time = None
+
+
class CachingRietveld(Rietveld):
"""Caches the common queries.
« 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