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

Side by Side Diff: gerrit_util.py

Issue 1350673004: gerrit_util: Add GCE metadata server auth. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Little cleanup. Created 5 years, 3 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """ 5 """
6 Utilities for requesting information for a gerrit server via https. 6 Utilities for requesting information for a gerrit server via https.
7 7
8 https://gerrit-review.googlesource.com/Documentation/rest-api.html 8 https://gerrit-review.googlesource.com/Documentation/rest-api.html
9 """ 9 """
10 10
11 import base64 11 import base64
12 import httplib 12 import httplib
13 import json 13 import json
14 import logging 14 import logging
15 import netrc 15 import netrc
16 import os 16 import os
17 import re 17 import re
18 import socket
18 import stat 19 import stat
19 import sys 20 import sys
20 import time 21 import time
21 import urllib 22 import urllib
23 import urlparse
22 from cStringIO import StringIO 24 from cStringIO import StringIO
23 25
24 _netrc_file = '_netrc' if sys.platform.startswith('win') else '.netrc'
25 _netrc_file = os.path.join(os.environ['HOME'], _netrc_file)
26 try:
27 NETRC = netrc.netrc(_netrc_file)
28 except IOError:
29 print >> sys.stderr, 'WARNING: Could not read netrc file %s' % _netrc_file
30 NETRC = netrc.netrc(os.devnull)
31 except netrc.NetrcParseError as e:
32 _netrc_stat = os.stat(e.filename)
33 if _netrc_stat.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
34 print >> sys.stderr, (
35 'WARNING: netrc file %s cannot be used because its file permissions '
36 'are insecure. netrc file permissions should be 600.' % _netrc_file)
37 else:
38 print >> sys.stderr, ('ERROR: Cannot use netrc file %s due to a parsing '
39 'error.' % _netrc_file)
40 raise
41 del _netrc_stat
42 NETRC = netrc.netrc(os.devnull)
43 del _netrc_file
44 26
45 LOGGER = logging.getLogger() 27 LOGGER = logging.getLogger()
46 TRY_LIMIT = 5 28 TRY_LIMIT = 5
47 29
30
31 class BaseAuthenticator(object):
32
33 def get_auth_header(self, host):
34 raise NotImplementedError()
35
36 @staticmethod
37 def get():
38 if GceAuthenticator.is_gce():
39 return GceAuthenticator()
40 return NetrcAuthenticator()
41
42
43 class NetrcAuthenticator(BaseAuthenticator):
44 """BaseAuthenticator implementation that uses ".netrc" for token.
45 """
46
47 def __init__(self):
48 self.netrc = self._get_netrc()
49
50 @staticmethod
51 def _get_netrc():
52 path = '_netrc' if sys.platform.startswith('win') else '.netrc'
53 path = os.path.join(os.environ['HOME'], path)
54 try:
55 return netrc.netrc(path)
56 except IOError:
57 print >> sys.stderr, 'WARNING: Could not read netrc file %s' % path
58 return netrc.netrc(os.devnull)
59 except netrc.NetrcParseError as e:
60 st = os.stat(e.path)
61 if st.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
62 print >> sys.stderr, (
63 'WARNING: netrc file %s cannot be used because its file '
64 'permissions are insecure. netrc file permissions should be '
65 '600.' % path)
66 else:
67 print >> sys.stderr, ('ERROR: Cannot use netrc file %s due to a '
68 'parsing error.' % path)
69 raise
70 return netrc.netrc(os.devnull)
71
72 def get_auth_header(self, host):
73 auth = self.netrc.authenticators(host)
74 if auth:
75 return 'Basic %s' % (base64.b64encode('%s:%s' % (auth[0], auth[2])))
76 return None
77
78
79 class GceAuthenticator(BaseAuthenticator):
80 """BaseAuthenticator implementation that uses GCE metadata service for token.
81 """
82
83 _INFO_URL = 'http://metadata.google.internal'
84 _ACQUIRE_URL = ('http://metadata/computeMetadata/v1/instance/'
85 'service-accounts/default/token')
86 _ACQUIRE_HEADERS = {"Metadata-Flavor": "Google"}
87
88 _cache_is_gce = None
89 _token_cache = None
90 _token_expiration = None
91
92 @classmethod
93 def is_gce(cls):
94 if cls._cache_is_gce is None:
95 cls._cache_is_gce = cls._test_is_gce()
96 return cls._cache_is_gce
97
98 @classmethod
99 def _test_is_gce(cls):
100 # Based on https://cloud.google.com/compute/docs/metadata#runninggce
101 try:
102 resp = cls._get(cls._INFO_URL)
103 except socket.error:
104 # Could not resolve URL.
105 return False
106 return resp.getheader('Metadata-Flavor', None) == 'Google'
107
108 @staticmethod
109 def _get(url, **kwargs):
110 p = urlparse.urlparse(url)
111 c = GetConnectionClass(protocol=p.scheme)(p.netloc)
112 c.request('GET', url, **kwargs)
113 return c.getresponse()
114
115 @classmethod
116 def _get_token_dict(cls):
117 if cls._token_cache:
118 # If it expires within 25 seconds, refresh.
119 if cls._token_expiration > time.time() - 25:
120 return cls._token_cache
121
122 resp = cls._get(cls._ACQUIRE_URL, headers=cls._ACQUIRE_HEADERS)
123 if resp.status != httplib.OK:
124 return None
125 cls._token_cache = token_dict = json.load(resp)
126 cls._token_expiration = token_dict['expires_in'] + time.time()
127 return cls._token_cache
128
129 def get_auth_header(self, _host):
130 token_dict = self._get_token_dict()
131 if not token_dict:
132 return None
133 return '%(token_type)s %(access_token)s' % token_dict
smut 2015/09/18 22:32:43 Move classes down (at least below module-level con
dnj 2015/09/18 22:37:46 Done.
134
135
48 # Controls the transport protocol used to communicate with gerrit. 136 # Controls the transport protocol used to communicate with gerrit.
49 # This is parameterized primarily to enable GerritTestCase. 137 # This is parameterized primarily to enable GerritTestCase.
50 GERRIT_PROTOCOL = 'https' 138 GERRIT_PROTOCOL = 'https'
51 139
52 140
53 class GerritError(Exception): 141 class GerritError(Exception):
54 """Exception class for errors commuicating with the gerrit-on-borg service.""" 142 """Exception class for errors commuicating with the gerrit-on-borg service."""
55 def __init__(self, http_status, *args, **kwargs): 143 def __init__(self, http_status, *args, **kwargs):
56 super(GerritError, self).__init__(*args, **kwargs) 144 super(GerritError, self).__init__(*args, **kwargs)
57 self.http_status = http_status 145 self.http_status = http_status
(...skipping 23 matching lines...) Expand all
81 return httplib.HTTPConnection 169 return httplib.HTTPConnection
82 else: 170 else:
83 raise RuntimeError( 171 raise RuntimeError(
84 "Don't know how to work with protocol '%s'" % protocol) 172 "Don't know how to work with protocol '%s'" % protocol)
85 173
86 174
87 def CreateHttpConn(host, path, reqtype='GET', headers=None, body=None): 175 def CreateHttpConn(host, path, reqtype='GET', headers=None, body=None):
88 """Opens an https connection to a gerrit service, and sends a request.""" 176 """Opens an https connection to a gerrit service, and sends a request."""
89 headers = headers or {} 177 headers = headers or {}
90 bare_host = host.partition(':')[0] 178 bare_host = host.partition(':')[0]
91 auth = NETRC.authenticators(bare_host)
92 179
180 auth = BaseAuthenticator.get().get_auth_header(bare_host)
93 if auth: 181 if auth:
94 headers.setdefault('Authorization', 'Basic %s' % ( 182 headers.setdefault('Authorization', auth)
95 base64.b64encode('%s:%s' % (auth[0], auth[2]))))
96 else: 183 else:
97 LOGGER.debug('No authorization found in netrc for %s.' % bare_host) 184 LOGGER.debug('No authorization found for %s.' % bare_host)
98 185
99 if 'Authorization' in headers and not path.startswith('a/'): 186 if 'Authorization' in headers and not path.startswith('a/'):
100 url = '/a/%s' % path 187 url = '/a/%s' % path
101 else: 188 else:
102 url = '/%s' % path 189 url = '/%s' % path
103 190
104 if body: 191 if body:
105 body = json.JSONEncoder().encode(body) 192 body = json.JSONEncoder().encode(body)
106 headers.setdefault('Content-Type', 'application/json') 193 headers.setdefault('Content-Type', 'application/json')
107 if LOGGER.isEnabledFor(logging.DEBUG): 194 if LOGGER.isEnabledFor(logging.DEBUG):
(...skipping 357 matching lines...) Expand 10 before | Expand all | Expand 10 after
465 username = review.get('email', jmsg.get('name', '')) 552 username = review.get('email', jmsg.get('name', ''))
466 raise GerritError(200, 'Unable to set %s label for user "%s"' 553 raise GerritError(200, 'Unable to set %s label for user "%s"'
467 ' on change %s.' % (label, username, change)) 554 ' on change %s.' % (label, username, change))
468 jmsg = GetChangeCurrentRevision(host, change) 555 jmsg = GetChangeCurrentRevision(host, change)
469 if not jmsg: 556 if not jmsg:
470 raise GerritError( 557 raise GerritError(
471 200, 'Could not get review information for change "%s"' % change) 558 200, 'Could not get review information for change "%s"' % change)
472 elif jmsg[0]['current_revision'] != revision: 559 elif jmsg[0]['current_revision'] != revision:
473 raise GerritError(200, 'While resetting labels on change "%s", ' 560 raise GerritError(200, 'While resetting labels on change "%s", '
474 'a new patchset was uploaded.' % change) 561 'a new patchset was uploaded.' % change)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698