blob: aa2bf87c28a2c243e55940a8e161ef30911a9ed5 [file] [log] [blame]
Vadim Shtayurac4c76b62014-01-13 15:05:41 -08001#!/usr/bin/env python
2# Copyright 2013 The Swarming Authors. All rights reserved.
3# Use of this source code is governed under the Apache License, Version 2.0 that
4# can be found in the LICENSE file.
5
6"""Client tool to perform various authentication related tasks."""
7
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04008__version__ = '0.3.1'
Vadim Shtayurac4c76b62014-01-13 15:05:41 -08009
10import optparse
11import sys
12
13from third_party import colorama
14from third_party.depot_tools import fix_encoding
15from third_party.depot_tools import subcommand
16
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -040017from utils import on_error
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080018from utils import net
19from utils import oauth
20from utils import tools
21
22
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080023class AuthServiceError(Exception):
24 """Unexpected response from authentication service."""
25
26
27class AuthService(object):
28 """Represents remote Authentication service."""
29
30 def __init__(self, url):
31 self._service = net.get_http_service(url)
32
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080033 def login(self, allow_user_interaction):
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080034 """Refreshes cached access token or creates a new one."""
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080035 return self._service.login(allow_user_interaction)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080036
37 def logout(self):
38 """Purges cached access token."""
39 return self._service.logout()
40
41 def get_current_identity(self):
42 """Returns identity associated with currently used credentials.
43
44 Identity is a string:
45 user:<email> - if using OAuth or cookie based authentication.
46 bot:<id> - if using HMAC based authentication.
47 anonymous:anonymous - if not authenticated.
48 """
49 identity = self._service.json_request('GET', '/auth/api/v1/accounts/self')
50 if not identity:
51 raise AuthServiceError('Failed to fetch identity')
52 return identity['identity']
53
54
Vadim Shtayura6b555c12014-07-23 16:22:18 -070055def add_auth_options(parser):
56 """Adds command line options related to authentication."""
57 parser.auth_group = optparse.OptionGroup(parser, 'Authentication')
58 parser.auth_group.add_option(
59 '--auth-method',
60 metavar='METHOD',
61 default=net.get_default_auth_config()[0],
62 help='Authentication method to use: %s. [default: %%default]' %
63 ', '.join(name for name, _ in net.AUTH_METHODS))
64 parser.add_option_group(parser.auth_group)
65 oauth.add_oauth_options(parser)
66
67
68def process_auth_options(parser, options):
69 """Configures process-wide authentication parameters based on |options|."""
70 # Validate that authentication method is known.
71 if options.auth_method not in dict(net.AUTH_METHODS):
72 parser.error('Invalid --auth-method value: %s' % options.auth_method)
73
74 # Process the rest of the flags based on actual method used.
75 # Only oauth is configurable now.
76 config = None
77 if options.auth_method == 'oauth':
78 config = oauth.extract_oauth_config_from_options(options)
79
80 # Now configure 'net' globally to use this for every request.
81 net.configure_auth(options.auth_method, config)
82
83
84def ensure_logged_in(server_url):
85 """Checks that user is logged in, asking to do it if not.
86
87 Aborts the process with exit code 1 if user is not logged it. Noop when used
88 on bots.
89 """
90 if net.get_auth_method() not in ('cookie', 'oauth'):
91 return
92 server_url = server_url.lower().rstrip('/')
93 assert server_url.startswith(('https://', 'http://localhost:')), server_url
94 service = AuthService(server_url)
95 service.login(False)
96 identity = service.get_current_identity()
97 if identity == 'anonymous:anonymous':
98 print >> sys.stderr, (
99 'Please login to %s: \n'
100 ' python auth.py login --service=%s' % (server_url, server_url))
101 sys.exit(1)
102 email = identity.split(':')[1]
103 print >> sys.stderr, 'Logged in to %s: %s' % (server_url, email)
104
105
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800106@subcommand.usage('[options]')
107def CMDlogin(parser, args):
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800108 """Runs interactive login flow and stores auth token/cookie on disk."""
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800109 (options, args) = parser.parse_args(args)
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800110 process_auth_options(parser, options)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800111 service = AuthService(options.service)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800112 if service.login(True):
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800113 print 'Logged in as \'%s\'.' % service.get_current_identity()
114 return 0
115 else:
116 print 'Login failed or canceled.'
117 return 1
118
119
120@subcommand.usage('[options]')
121def CMDlogout(parser, args):
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800122 """Purges cached auth token/cookie."""
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800123 (options, args) = parser.parse_args(args)
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800124 process_auth_options(parser, options)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800125 service = AuthService(options.service)
126 service.logout()
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800127 return 0
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800128
129
130@subcommand.usage('[options]')
131def CMDcheck(parser, args):
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800132 """Shows identity associated with currently cached auth token/cookie."""
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800133 (options, args) = parser.parse_args(args)
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800134 process_auth_options(parser, options)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800135 service = AuthService(options.service)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800136 service.login(False)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800137 print service.get_current_identity()
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800138 return 0
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800139
140
141class OptionParserAuth(tools.OptionParserWithLogging):
142 def __init__(self, **kwargs):
143 tools.OptionParserWithLogging.__init__(self, prog='auth.py', **kwargs)
144 self.server_group = tools.optparse.OptionGroup(self, 'Server')
145 self.server_group.add_option(
146 '-S', '--service',
147 metavar='URL', default='',
148 help='Service to use')
149 self.add_option_group(self.server_group)
150 add_auth_options(self)
151
152 def parse_args(self, *args, **kwargs):
153 options, args = tools.OptionParserWithLogging.parse_args(
154 self, *args, **kwargs)
155 options.service = options.service.rstrip('/')
156 if not options.service:
157 self.error('--service is required.')
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400158 on_error.report_on_exception_exit(options.service)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800159 return options, args
160
161
162def main(args):
163 dispatcher = subcommand.CommandDispatcher(__name__)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400164 return dispatcher.execute(OptionParserAuth(version=__version__), args)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800165
166
167if __name__ == '__main__':
168 fix_encoding.fix_encoding()
169 tools.disable_buffering()
170 colorama.init()
171 sys.exit(main(sys.argv[1:]))