Commit 733d4ec8 authored by Andrii Shyshkalov's avatar Andrii Shyshkalov Committed by Commit Bot

[gerrit_util] learn about and use LUCI_CONTEXT when available.

Unfortunately, w/o rewrite of gerrit_util, one can't take advantage
of existing auth.Authenticator (which also doesn't know about ~/.netrc
or .gitcookies, but that's fixable). This rewrite, however, will likely
cause issues for nasty scripts outside of depot_tools which use
gerrit_util as a Python library. So, I followed outdated way of
gerrit_util :(

Also contains refactoring of auth library, which was kept backwards
compatible for the same reasons as above.

R=vadimsh@chromium.org

Test-with: led tool
Test-artifact: https://ci.chromium.org/swarming/task/3cf4687dfd26ca10?server=chromium-swarm.appspot.com
Bug: 834536
Change-Id: I6b84b8b5732aee5d345e2b2ba44f47aaecc0f6c5
Reviewed-on: https://chromium-review.googlesource.com/1018488Reviewed-by: 's avatarVadim Shtayura <vadimsh@chromium.org>
Commit-Queue: Andrii Shyshkalov <tandrii@chromium.org>
parent 5c62ed59
This diff is collapsed.
......@@ -26,6 +26,7 @@ import urllib
import urlparse
from cStringIO import StringIO
import auth
import gclient_utils
import subprocess2
from third_party import httplib2
......@@ -86,6 +87,10 @@ class Authenticator(object):
Probes the local system and its environment and identifies the
Authenticator instance to use.
"""
# LUCI Context takes priority since it's normally present only on bots,
# which then must use it.
if LuciContextAuthenticator.is_luci():
return LuciContextAuthenticator()
if GceAuthenticator.is_gce():
return GceAuthenticator()
return CookiesAuthenticator()
......@@ -207,17 +212,17 @@ class CookiesAuthenticator(Authenticator):
return self.netrc.authenticators(host)
def get_auth_header(self, host):
auth = self._get_auth_for_host(host)
if auth:
return 'Basic %s' % (base64.b64encode('%s:%s' % (auth[0], auth[2])))
a = self._get_auth_for_host(host)
if a:
return 'Basic %s' % (base64.b64encode('%s:%s' % (a[0], a[2])))
return None
def get_auth_email(self, host):
"""Best effort parsing of email to be used for auth for the given host."""
auth = self._get_auth_for_host(host)
if not auth:
a = self._get_auth_for_host(host)
if not a:
return None
login = auth[0]
login = a[0]
# login typically looks like 'git-xxx.example.com'
if not login.startswith('git-') or '.' not in login:
return None
......@@ -303,14 +308,36 @@ class GceAuthenticator(Authenticator):
return '%(token_type)s %(access_token)s' % token_dict
class LuciContextAuthenticator(Authenticator):
"""Authenticator implementation that uses LUCI_CONTEXT ambient local auth.
"""
@staticmethod
def is_luci():
return auth.has_luci_context_local_auth()
def __init__(self):
self._access_token = None
self._ensure_fresh()
def _ensure_fresh(self):
if not self._access_token or self._access_token.needs_refresh():
self._access_token = auth.get_luci_context_access_token(
scopes=' '.join([auth.OAUTH_SCOPE_EMAIL, auth.OAUTH_SCOPE_GERRIT]))
def get_auth_header(self, _host):
self._ensure_fresh()
return 'Bearer %s' % self._access_token.token
def CreateHttpConn(host, path, reqtype='GET', headers=None, body=None):
"""Opens an https connection to a gerrit service, and sends a request."""
headers = headers or {}
bare_host = host.partition(':')[0]
auth = Authenticator.get().get_auth_header(bare_host)
if auth:
headers.setdefault('Authorization', auth)
a = Authenticator.get().get_auth_header(bare_host)
if a:
headers.setdefault('Authorization', a)
else:
LOGGER.debug('No authorization found for %s.' % bare_host)
......
......@@ -7,4 +7,4 @@ the Build and Test Yeti.
Then suddenly, and without delay, Batty the Build and Test Yeti
😒
😒😒
......@@ -24,10 +24,9 @@ from third_party import mock
import auth
class TestGetLuciContextAccessToken(auto_stub.TestCase):
mock_env = {'LUCI_CONTEXT': 'default/test/path'}
class TestLuciContext(auto_stub.TestCase):
def _mock_local_auth(self, account_id, secret, rpc_port):
self.mock(os, 'environ', {'LUCI_CONTEXT': 'default/test/path'})
self.mock(auth, '_load_luci_context', mock.Mock())
auth._load_luci_context.return_value = {
'local_auth': {
......@@ -43,8 +42,10 @@ class TestGetLuciContextAccessToken(auto_stub.TestCase):
self.mock(httplib2.Http, 'request', mock.Mock())
httplib2.Http.request.return_value = (mock_resp, content)
def test_correct_local_auth_format(self):
self._mock_local_auth('dead', 'beef', 10)
def test_all_good(self):
self._mock_local_auth('account', 'secret', 8080)
self.assertTrue(auth.has_luci_context_local_auth())
expiry_time = datetime.datetime.min + datetime.timedelta(hours=1)
resp_content = {
'error_code': None,
......@@ -54,23 +55,23 @@ class TestGetLuciContextAccessToken(auto_stub.TestCase):
- datetime.datetime.utcfromtimestamp(0)).total_seconds(),
}
self._mock_loc_server_resp(200, json.dumps(resp_content))
token = auth._get_luci_context_access_token(
self.mock_env, datetime.datetime.min)
params = auth._get_luci_context_local_auth_params(os.environ)
token = auth._get_luci_context_access_token(params, datetime.datetime.min)
self.assertEquals(token.token, 'token')
def test_no_account_id(self):
self._mock_local_auth(None, 'secret', 8080)
self.assertFalse(auth.has_luci_context_local_auth())
self.assertIsNone(auth.get_luci_context_access_token())
def test_incorrect_port_format(self):
self._mock_local_auth('foo', 'bar', 'bar')
self._mock_local_auth('account', 'secret', 'port')
self.assertFalse(auth.has_luci_context_local_auth())
with self.assertRaises(auth.LuciContextAuthError):
auth._get_luci_context_access_token(self.mock_env, datetime.datetime.min)
def test_no_account_id(self):
self._mock_local_auth(None, 'bar', 10)
token = auth._get_luci_context_access_token(
self.mock_env, datetime.datetime.min)
self.assertIsNone(token)
auth.get_luci_context_access_token()
def test_expired_token(self):
self._mock_local_auth('dead', 'beef', 10)
params = auth._LuciContextLocalAuthParams('account', 'secret', 8080)
resp_content = {
'error_code': None,
'error_message': None,
......@@ -80,10 +81,10 @@ class TestGetLuciContextAccessToken(auto_stub.TestCase):
self._mock_loc_server_resp(200, json.dumps(resp_content))
with self.assertRaises(auth.LuciContextAuthError):
auth._get_luci_context_access_token(
self.mock_env, datetime.datetime.utcfromtimestamp(1))
params, datetime.datetime.utcfromtimestamp(1))
def test_incorrect_expiry_format(self):
self._mock_local_auth('dead', 'beef', 10)
params = auth._LuciContextLocalAuthParams('account', 'secret', 8080)
resp_content = {
'error_code': None,
'error_message': None,
......@@ -92,13 +93,13 @@ class TestGetLuciContextAccessToken(auto_stub.TestCase):
}
self._mock_loc_server_resp(200, json.dumps(resp_content))
with self.assertRaises(auth.LuciContextAuthError):
auth._get_luci_context_access_token(self.mock_env, datetime.datetime.min)
auth._get_luci_context_access_token(params, datetime.datetime.min)
def test_incorrect_response_content_format(self):
self._mock_local_auth('dead', 'beef', 10)
params = auth._LuciContextLocalAuthParams('account', 'secret', 8080)
self._mock_loc_server_resp(200, '5')
with self.assertRaises(auth.LuciContextAuthError):
auth._get_luci_context_access_token(self.mock_env, datetime.datetime.min)
auth._get_luci_context_access_token(params, datetime.datetime.min)
if __name__ == '__main__':
......
......@@ -663,6 +663,8 @@ class TestGitCl(TestCase):
self.mock(git_cl.gerrit_util, 'SetReview',
lambda h, i, msg=None, labels=None, notify=None:
self._mocked_call('SetReview', h, i, msg, labels, notify))
self.mock(git_cl.gerrit_util.LuciContextAuthenticator, 'is_luci',
staticmethod(lambda: False))
self.mock(git_cl.gerrit_util.GceAuthenticator, 'is_gce',
classmethod(lambda _: False))
self.mock(git_cl, 'DieWithError',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment