Commit b4a587df authored by Edward Lemur's avatar Edward Lemur Committed by Commit Bot

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: 's avatarAnthony Polito <apolito@google.com>
parent 71681bf1
......@@ -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.
......
#!/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" "$@"
@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" %*
#!/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)
......@@ -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'
......
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