mirror of
https://chromium.googlesource.com/chromium/tools/depot_tools.git
synced 2026-01-11 18:51:29 +00:00
depot_tools: Remove depot-tools-auth
Users must use luci-auth now. Bug: 1001756 Change-Id: I04cab6bdbfbd958f386a4cab761dfe4d34073afc Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/1849810 Commit-Queue: Edward Lesmes <ehmaldonado@chromium.org> Reviewed-by: Anthony Polito <apolito@google.com>
This commit is contained in:
79
auth.py
79
auth.py
@@ -50,13 +50,6 @@ OAUTH_SCOPE_GERRIT = 'https://www.googleapis.com/auth/gerritcodereview'
|
||||
# Deprecated. Use OAUTH_SCOPE_EMAIL instead.
|
||||
OAUTH_SCOPES = OAUTH_SCOPE_EMAIL
|
||||
|
||||
# Path to a file with cached OAuth2 credentials used by default relative to the
|
||||
# home dir (see _get_token_cache_path). It should be a safe location accessible
|
||||
# only to a current user: knowing content of this file is roughly equivalent to
|
||||
# knowing account password. Single file can hold multiple independent tokens
|
||||
# identified by token_cache_key (see Authenticator).
|
||||
OAUTH_TOKENS_CACHE = '.depot_tools_oauth2_tokens'
|
||||
|
||||
|
||||
# Authentication configuration extracted from command line options.
|
||||
# See doc string for 'make_auth_config' for meaning of fields.
|
||||
@@ -381,79 +374,38 @@ def auth_config_to_command_options(auth_config):
|
||||
return opts
|
||||
|
||||
|
||||
def get_authenticator_for_host(hostname, config, scopes=OAUTH_SCOPE_EMAIL):
|
||||
def get_authenticator(config, scopes=OAUTH_SCOPE_EMAIL):
|
||||
"""Returns Authenticator instance to access given host.
|
||||
|
||||
Args:
|
||||
hostname: a naked hostname or http(s)://<hostname>[/] URL. Used to derive
|
||||
a cache key for token cache.
|
||||
config: AuthConfig instance.
|
||||
scopes: space separated oauth scopes. Defaults to OAUTH_SCOPE_EMAIL.
|
||||
|
||||
Returns:
|
||||
Authenticator object.
|
||||
|
||||
Raises:
|
||||
AuthenticationError if hostname is invalid.
|
||||
"""
|
||||
hostname = hostname.lower().rstrip('/')
|
||||
# Append some scheme, otherwise urlparse puts hostname into parsed.path.
|
||||
if '://' not in hostname:
|
||||
hostname = 'https://' + hostname
|
||||
parsed = urlparse.urlparse(hostname)
|
||||
|
||||
if parsed.path or parsed.params or parsed.query or parsed.fragment:
|
||||
raise AuthenticationError(
|
||||
'Expecting a hostname or root host URL, got %s instead' % hostname)
|
||||
return Authenticator(parsed.netloc, config, scopes)
|
||||
return Authenticator(config, scopes)
|
||||
|
||||
|
||||
class Authenticator(object):
|
||||
"""Object that knows how to refresh access tokens when needed.
|
||||
|
||||
Args:
|
||||
token_cache_key: string key of a section of the token cache file to use
|
||||
to keep the tokens. See hostname_to_token_cache_key.
|
||||
config: AuthConfig object that holds authentication configuration.
|
||||
"""
|
||||
|
||||
def __init__(self, token_cache_key, config, scopes):
|
||||
def __init__(self, config, scopes):
|
||||
assert isinstance(config, AuthConfig)
|
||||
assert config.use_oauth2
|
||||
self._access_token = None
|
||||
self._config = config
|
||||
self._lock = threading.Lock()
|
||||
self._token_cache_key = token_cache_key
|
||||
self._external_token = None
|
||||
self._scopes = scopes
|
||||
if config.refresh_token_json:
|
||||
self._external_token = _read_refresh_token_json(config.refresh_token_json)
|
||||
logging.debug('Using auth config %r', config)
|
||||
|
||||
def login(self):
|
||||
"""Performs interactive login flow if necessary.
|
||||
|
||||
Raises:
|
||||
AuthenticationError on error or if interrupted.
|
||||
"""
|
||||
if self._external_token:
|
||||
raise AuthenticationError(
|
||||
'Can\'t run login flow when using --auth-refresh-token-json.')
|
||||
return self.get_access_token(
|
||||
force_refresh=True, allow_user_interaction=True)
|
||||
|
||||
def logout(self):
|
||||
"""Revokes the refresh token and deletes it from the cache.
|
||||
|
||||
Returns True if had some credentials cached.
|
||||
"""
|
||||
with self._lock:
|
||||
self._access_token = None
|
||||
had_creds = bool(_get_luci_auth_credentials(self._scopes))
|
||||
subprocess2.check_call(
|
||||
['luci-auth', 'logout', '-scopes', self._scopes])
|
||||
return had_creds
|
||||
|
||||
def has_cached_credentials(self):
|
||||
"""Returns True if long term credentials (refresh token) are in cache.
|
||||
|
||||
@@ -526,16 +478,6 @@ class Authenticator(object):
|
||||
|
||||
return self._access_token
|
||||
|
||||
def get_token_info(self):
|
||||
"""Returns a result of /oauth2/v2/tokeninfo call with token info."""
|
||||
access_token = self.get_access_token()
|
||||
resp, content = httplib2.Http().request(
|
||||
uri='https://www.googleapis.com/oauth2/v2/tokeninfo?%s' % (
|
||||
urllib.urlencode({'access_token': access_token.token})))
|
||||
if resp.status == 200:
|
||||
return json.loads(content)
|
||||
raise AuthenticationError('Failed to fetch the token info: %r' % content)
|
||||
|
||||
def authorize(self, http):
|
||||
"""Monkey patches authentication logic of httplib2.Http instance.
|
||||
|
||||
@@ -684,20 +626,6 @@ class Authenticator(object):
|
||||
## Private functions.
|
||||
|
||||
|
||||
def _get_token_cache_path():
|
||||
# On non Win just use HOME.
|
||||
if sys.platform != 'win32':
|
||||
return os.path.join(os.path.expanduser('~'), OAUTH_TOKENS_CACHE)
|
||||
# Prefer USERPROFILE over HOME, since HOME is overridden in
|
||||
# git-..._bin/cmd/git.cmd to point to depot_tools. depot-tools-auth.py script
|
||||
# (and all other scripts) doesn't use this override and thus uses another
|
||||
# value for HOME. git.cmd doesn't touch USERPROFILE though, and usually
|
||||
# USERPROFILE == HOME on Windows.
|
||||
if 'USERPROFILE' in os.environ:
|
||||
return os.path.join(os.environ['USERPROFILE'], OAUTH_TOKENS_CACHE)
|
||||
return os.path.join(os.path.expanduser('~'), OAUTH_TOKENS_CACHE)
|
||||
|
||||
|
||||
def _is_headless():
|
||||
"""True if machine doesn't seem to have a display."""
|
||||
return sys.platform == 'linux2' and not os.environ.get('DISPLAY')
|
||||
@@ -750,6 +678,7 @@ def _get_luci_auth_credentials(scopes):
|
||||
user_agent=None,
|
||||
revoke_uri=None)
|
||||
|
||||
|
||||
def _run_oauth_dance(scopes):
|
||||
"""Perform full 3-legged OAuth2 flow with the browser.
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
base_dir=$(dirname "$0")
|
||||
|
||||
PYTHONDONTWRITEBYTECODE=1 exec python "$base_dir/depot-tools-auth.py" "$@"
|
||||
@@ -1,12 +0,0 @@
|
||||
@echo off
|
||||
:: Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
:: Use of this source code is governed by a BSD-style license that can be
|
||||
:: found in the LICENSE file.
|
||||
setlocal
|
||||
|
||||
:: Ensure that "depot_tools" is somewhere in PATH so this tool can be used
|
||||
:: standalone, but allow other PATH manipulations to take priority.
|
||||
set PATH=%PATH%;%~dp0
|
||||
|
||||
:: Defer control.
|
||||
python "%~dp0\depot-tools-auth.py" %*
|
||||
@@ -1,104 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Manages cached OAuth2 tokens used by other depot_tools scripts.
|
||||
|
||||
Usage:
|
||||
depot-tools-auth login codereview.chromium.org
|
||||
depot-tools-auth info codereview.chromium.org
|
||||
depot-tools-auth logout codereview.chromium.org
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
import optparse
|
||||
import sys
|
||||
import os
|
||||
|
||||
import auth
|
||||
import setup_color
|
||||
import subcommand
|
||||
|
||||
__version__ = '1.0'
|
||||
|
||||
|
||||
@subcommand.usage('<hostname>')
|
||||
def CMDlogin(parser, args):
|
||||
"""Performs interactive login and caches authentication token."""
|
||||
# Forcefully relogin, revoking previous token.
|
||||
hostname, authenticator = parser.parse_args(args)
|
||||
authenticator.logout()
|
||||
authenticator.login()
|
||||
print_token_info(hostname, authenticator)
|
||||
return 0
|
||||
|
||||
|
||||
@subcommand.usage('<hostname>')
|
||||
def CMDlogout(parser, args):
|
||||
"""Revokes cached authentication token and removes it from disk."""
|
||||
_, authenticator = parser.parse_args(args)
|
||||
done = authenticator.logout()
|
||||
print('Done.' if done else 'Already logged out.')
|
||||
return 0
|
||||
|
||||
|
||||
@subcommand.usage('<hostname>')
|
||||
def CMDinfo(parser, args):
|
||||
"""Shows email associated with a cached authentication token."""
|
||||
# If no token is cached, AuthenticationError will be caught in 'main'.
|
||||
hostname, authenticator = parser.parse_args(args)
|
||||
print_token_info(hostname, authenticator)
|
||||
return 0
|
||||
|
||||
|
||||
def print_token_info(hostname, authenticator):
|
||||
token_info = authenticator.get_token_info()
|
||||
print('Logged in to %s as %s.' % (hostname, token_info['email']))
|
||||
print('')
|
||||
print('To login with a different email run:')
|
||||
print(' depot-tools-auth login %s' % hostname)
|
||||
print('To logout and purge the authentication token run:')
|
||||
print(' depot-tools-auth logout %s' % hostname)
|
||||
|
||||
|
||||
class OptionParser(optparse.OptionParser):
|
||||
def __init__(self, *args, **kwargs):
|
||||
optparse.OptionParser.__init__(
|
||||
self, *args, prog='depot-tools-auth', version=__version__, **kwargs)
|
||||
self.add_option(
|
||||
'-v', '--verbose', action='count', default=0,
|
||||
help='Use 2 times for more debugging info')
|
||||
auth.add_auth_options(self, auth.make_auth_config(use_oauth2=True))
|
||||
|
||||
def parse_args(self, args=None, values=None):
|
||||
"""Parses options and returns (hostname, auth.Authenticator object)."""
|
||||
options, args = optparse.OptionParser.parse_args(self, args, values)
|
||||
levels = [logging.WARNING, logging.INFO, logging.DEBUG]
|
||||
logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)])
|
||||
auth_config = auth.extract_auth_config_from_options(options)
|
||||
if len(args) != 1:
|
||||
self.error('Expecting single argument (hostname).')
|
||||
if not auth_config.use_oauth2:
|
||||
self.error('This command is only usable with OAuth2 authentication')
|
||||
return args[0], auth.get_authenticator_for_host(args[0], auth_config)
|
||||
|
||||
|
||||
def main(argv):
|
||||
dispatcher = subcommand.CommandDispatcher(__name__)
|
||||
try:
|
||||
return dispatcher.execute(OptionParser(), argv)
|
||||
except auth.AuthenticationError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setup_color.init()
|
||||
try:
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
except KeyboardInterrupt:
|
||||
sys.stderr.write('interrupted\n')
|
||||
sys.exit(1)
|
||||
11
git_cl.py
11
git_cl.py
@@ -465,11 +465,7 @@ def _trigger_try_jobs(auth_config, changelist, buckets, options, patchset):
|
||||
if not requests:
|
||||
return
|
||||
|
||||
codereview_url = changelist.GetCodereviewServer()
|
||||
codereview_host = urlparse.urlparse(codereview_url).hostname
|
||||
|
||||
authenticator = auth.get_authenticator_for_host(codereview_host, auth_config)
|
||||
http = authenticator.authorize(httplib2.Http())
|
||||
http = auth.get_authenticator(auth_config).authorize(httplib2.Http())
|
||||
http.force_exception_to_status_code = True
|
||||
|
||||
batch_request = {'requests': requests}
|
||||
@@ -544,10 +540,7 @@ def fetch_try_jobs(auth_config, changelist, buildbucket_host,
|
||||
'fields': ','.join('builds.*.' + field for field in fields),
|
||||
}
|
||||
|
||||
codereview_url = changelist.GetCodereviewServer()
|
||||
codereview_host = urlparse.urlparse(codereview_url).hostname
|
||||
|
||||
authenticator = auth.get_authenticator_for_host(codereview_host, auth_config)
|
||||
authenticator = auth.get_authenticator(auth_config)
|
||||
if authenticator.has_cached_credentials():
|
||||
http = authenticator.authorize(httplib2.Http())
|
||||
else:
|
||||
|
||||
@@ -293,8 +293,7 @@ class MyActivity(object):
|
||||
|
||||
def monorail_get_auth_http(self):
|
||||
auth_config = auth.extract_auth_config_from_options(self.options)
|
||||
authenticator = auth.get_authenticator_for_host(
|
||||
'bugs.chromium.org', auth_config)
|
||||
authenticator = auth.get_authenticator(auth_config)
|
||||
# Manually use a long timeout (10m); for some users who have a
|
||||
# long history on the issue tracker, whatever the default timeout
|
||||
# is is reached.
|
||||
|
||||
@@ -1417,8 +1417,7 @@ def CheckChangedLUCIConfigs(input_api, output_api):
|
||||
|
||||
# authentication
|
||||
try:
|
||||
authenticator = auth.get_authenticator_for_host(
|
||||
LUCI_CONFIG_HOST_NAME, auth.make_auth_config())
|
||||
authenticator = auth.get_authenticator(auth.make_auth_config())
|
||||
acc_tkn = authenticator.get_access_token()
|
||||
except auth.AuthenticationError as e:
|
||||
return [output_api.PresubmitError(
|
||||
|
||||
@@ -636,7 +636,7 @@ class TestGitCl(TestCase):
|
||||
self._mocked_call('write_json', path, contents))
|
||||
self.mock(git_cl.presubmit_support, 'DoPresubmitChecks', PresubmitMock)
|
||||
self.mock(git_cl.watchlists, 'Watchlists', WatchlistsMock)
|
||||
self.mock(git_cl.auth, 'get_authenticator_for_host', AuthenticatorMock)
|
||||
self.mock(git_cl.auth, 'get_authenticator', AuthenticatorMock)
|
||||
self.mock(git_cl.gerrit_util, 'GetChangeDetail',
|
||||
lambda *args, **kwargs: self._mocked_call(
|
||||
'GetChangeDetail', *args, **kwargs))
|
||||
@@ -3021,7 +3021,8 @@ class CMDTestCaseBase(unittest.TestCase):
|
||||
return_value='https://chromium-review.googlesource.com').start()
|
||||
mock.patch('git_cl.Changelist.GetMostRecentPatchset',
|
||||
return_value=7).start()
|
||||
mock.patch('git_cl.auth.get_authenticator_for_host', AuthenticatorMock())
|
||||
mock.patch('git_cl.auth.get_authenticator',
|
||||
return_value=AuthenticatorMock()).start()
|
||||
mock.patch('git_cl.Changelist._GetChangeDetail',
|
||||
return_value=self._CHANGE_DETAIL).start()
|
||||
mock.patch('git_cl._call_buildbucket',
|
||||
|
||||
@@ -1642,8 +1642,8 @@ class CannedChecksUnittest(PresubmitTestsBase):
|
||||
presubmit.OutputApi.PresubmitPromptWarning)
|
||||
|
||||
@mock.patch('git_cl.Changelist')
|
||||
@mock.patch('auth.get_authenticator_for_host')
|
||||
def testCannedCheckChangedLUCIConfigs(self, mockGAFH, mockChangelist):
|
||||
@mock.patch('auth.get_authenticator')
|
||||
def testCannedCheckChangedLUCIConfigs(self, mockGetAuth, mockChangelist):
|
||||
affected_file1 = mock.MagicMock(presubmit.GitAffectedFile)
|
||||
affected_file1.LocalPath.return_value = 'foo.cfg'
|
||||
affected_file1.NewContents.return_value = ['test', 'foo']
|
||||
@@ -1651,7 +1651,7 @@ class CannedChecksUnittest(PresubmitTestsBase):
|
||||
affected_file2.LocalPath.return_value = 'bar.cfg'
|
||||
affected_file2.NewContents.return_value = ['test', 'bar']
|
||||
|
||||
mockGAFH().get_access_token().token = 123
|
||||
mockGetAuth().get_access_token().token = 123
|
||||
|
||||
host = 'https://host.com'
|
||||
branch = 'branch'
|
||||
|
||||
Reference in New Issue
Block a user