Add OAuth2 support for end users (i.e. 3-legged flow with the browser).
This CL introduces new top level command for managing cached auth tokens: $ depot-tools-auth login codereview.chromium.org $ depot-tools-auth info codereview.chromium.org $ depot-tools-auth logout codereview.chromium.org All scripts that use rietveld.Rietveld internally should be able to use cached credentials created by 'depot-tools-auth' subcommand. Also 'depot-tools-auth' is the only way to run login flow. If some scripts stumbles over expired or revoked token, it dies with the error, asking user to run 'depot-tools-auth login <hostname>'. Password login is still default. OAuth2 can be enabled by passing --oauth2 to all scripts. R=maruel@chromium.org BUG=356813 Review URL: https://codereview.chromium.org/1074673002 git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@294764 0039d316-1c4b-4281-b951-d872f2087c98changes/01/332501/1
parent
cf6a5d2026
commit
eed4df3d91
@ -0,0 +1,8 @@
|
||||
#!/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" "$@"
|
@ -0,0 +1,11 @@
|
||||
@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
|
||||
|
||||
:: This is required with cygwin only.
|
||||
PATH=%~dp0;%PATH%
|
||||
|
||||
:: Defer control.
|
||||
%~dp0python "%~dp0\depot-tools-auth.py" %*
|
@ -0,0 +1,102 @@
|
||||
#!/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
|
||||
"""
|
||||
|
||||
import logging
|
||||
import optparse
|
||||
import sys
|
||||
|
||||
from third_party import colorama
|
||||
|
||||
import auth
|
||||
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 >> sys.stderr, e
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
colorama.init()
|
||||
try:
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
except KeyboardInterrupt:
|
||||
sys.stderr.write('interrupted\n')
|
||||
sys.exit(1)
|
@ -1,103 +0,0 @@
|
||||
# Copyright (c) 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.
|
||||
|
||||
"""OAuth2 related utilities and implementation for git cl commands."""
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
|
||||
from third_party.oauth2client import tools
|
||||
from third_party.oauth2client.file import Storage
|
||||
import third_party.oauth2client.client as oa2client
|
||||
|
||||
|
||||
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
|
||||
CLIENT_ID = ('174799409470-8k3b89iov4racu9jrf7if3k4591voig3'
|
||||
'.apps.googleusercontent.com')
|
||||
CLIENT_SECRET = 'DddcCK1d6_ADwxqGDEGlsisy'
|
||||
SCOPE = 'email'
|
||||
|
||||
|
||||
def _fetch_storage(code_review_server):
|
||||
storage_dir = os.path.expanduser(os.path.join('~', '.git_cl_credentials'))
|
||||
if not os.path.isdir(storage_dir):
|
||||
os.makedirs(storage_dir)
|
||||
storage_path = os.path.join(storage_dir, code_review_server)
|
||||
storage = Storage(storage_path)
|
||||
return storage
|
||||
|
||||
|
||||
def _fetch_creds_from_storage(storage):
|
||||
logging.debug('Fetching OAuth2 credentials from local storage ...')
|
||||
credentials = storage.get()
|
||||
if not credentials or credentials.invalid:
|
||||
return None
|
||||
if not credentials.access_token or credentials.access_token_expired:
|
||||
return None
|
||||
return credentials
|
||||
|
||||
|
||||
def add_oauth2_options(parser):
|
||||
"""Add OAuth2-related options."""
|
||||
group = optparse.OptionGroup(parser, "OAuth2 options")
|
||||
group.add_option(
|
||||
'--auth-host-name',
|
||||
default='localhost',
|
||||
help='Host name to use when running a local web server '
|
||||
'to handle redirects during OAuth authorization.'
|
||||
'Default: localhost.'
|
||||
)
|
||||
group.add_option(
|
||||
'--auth-host-port',
|
||||
type=int,
|
||||
action='append',
|
||||
default=[8080, 8090],
|
||||
help='Port to use when running a local web server to handle '
|
||||
'redirects during OAuth authorization. '
|
||||
'Repeat this option to specify a list of values.'
|
||||
'Default: [8080, 8090].'
|
||||
)
|
||||
group.add_option(
|
||||
'--noauth-local-webserver',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Run a local web server to handle redirects '
|
||||
'during OAuth authorization.'
|
||||
'Default: False.'
|
||||
)
|
||||
group.add_option(
|
||||
'--no-cache',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Get fresh credentials from web server instead of using '
|
||||
'the crendentials stored on a local storage file.'
|
||||
'Default: False.'
|
||||
)
|
||||
parser.add_option_group(group)
|
||||
|
||||
|
||||
def get_oauth2_creds(options, code_review_server):
|
||||
"""Get OAuth2 credentials.
|
||||
|
||||
Args:
|
||||
options: Command line options.
|
||||
code_review_server: Code review server name, e.g., codereview.chromium.org.
|
||||
"""
|
||||
storage = _fetch_storage(code_review_server)
|
||||
creds = None
|
||||
if not options.no_cache:
|
||||
creds = _fetch_creds_from_storage(storage)
|
||||
if creds is None:
|
||||
logging.debug('Fetching OAuth2 credentials from web server...')
|
||||
flow = oa2client.OAuth2WebServerFlow(
|
||||
client_id=CLIENT_ID,
|
||||
client_secret=CLIENT_SECRET,
|
||||
scope=SCOPE,
|
||||
redirect_uri=REDIRECT_URI)
|
||||
flags = copy.deepcopy(options)
|
||||
flags.logging_level = 'WARNING'
|
||||
creds = tools.run_flow(flow, storage, flags)
|
||||
return creds
|
Loading…
Reference in New Issue