blob: 81d7a8ad33731a6a8577ce1acfd28d9415469f6a [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
Vadim Shtayura36817012015-03-20 19:12:25 -07008__version__ = '0.4'
Vadim Shtayurac4c76b62014-01-13 15:05:41 -08009
Marc-Antoine Ruel79940ae2014-09-23 17:55:41 -040010import logging
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080011import optparse
12import sys
13
14from third_party import colorama
15from third_party.depot_tools import fix_encoding
16from third_party.depot_tools import subcommand
17
Marc-Antoine Ruelf74cffe2015-07-15 15:21:34 -040018from utils import logging_utils
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -040019from utils import on_error
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080020from utils import net
21from utils import oauth
22from utils import tools
23
24
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080025class AuthServiceError(Exception):
26 """Unexpected response from authentication service."""
27
28
29class AuthService(object):
30 """Represents remote Authentication service."""
31
32 def __init__(self, url):
33 self._service = net.get_http_service(url)
34
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080035 def login(self, allow_user_interaction):
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080036 """Refreshes cached access token or creates a new one."""
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080037 return self._service.login(allow_user_interaction)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080038
39 def logout(self):
40 """Purges cached access token."""
41 return self._service.logout()
42
43 def get_current_identity(self):
44 """Returns identity associated with currently used credentials.
45
46 Identity is a string:
47 user:<email> - if using OAuth or cookie based authentication.
48 bot:<id> - if using HMAC based authentication.
49 anonymous:anonymous - if not authenticated.
50 """
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -040051 identity = self._service.json_request('/auth/api/v1/accounts/self')
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080052 if not identity:
53 raise AuthServiceError('Failed to fetch identity')
54 return identity['identity']
55
56
Vadim Shtayura6b555c12014-07-23 16:22:18 -070057def add_auth_options(parser):
58 """Adds command line options related to authentication."""
59 parser.auth_group = optparse.OptionGroup(parser, 'Authentication')
Vadim Shtayura6b555c12014-07-23 16:22:18 -070060 parser.add_option_group(parser.auth_group)
61 oauth.add_oauth_options(parser)
62
63
64def process_auth_options(parser, options):
65 """Configures process-wide authentication parameters based on |options|."""
Vadim Shtayura36817012015-03-20 19:12:25 -070066 try:
67 net.set_oauth_config(oauth.extract_oauth_config_from_options(options))
68 except ValueError as exc:
69 parser.error(str(exc))
Vadim Shtayura6b555c12014-07-23 16:22:18 -070070
71
72def ensure_logged_in(server_url):
73 """Checks that user is logged in, asking to do it if not.
74
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -050075 Raises:
76 ValueError if the server_url is not acceptable.
Vadim Shtayura6b555c12014-07-23 16:22:18 -070077 """
Vadim Shtayura36817012015-03-20 19:12:25 -070078 # It's just a waste of time on a headless bot (it can't do interactive login).
79 if tools.is_headless() or net.get_oauth_config().disabled:
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -040080 return None
Vadim Shtayura6b555c12014-07-23 16:22:18 -070081 server_url = server_url.lower().rstrip('/')
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -050082 allowed = (
83 'https://',
84 'http://localhost:', 'http://127.0.0.1:', 'http://::1:')
85 if not server_url.startswith(allowed):
86 raise ValueError('URL must start with https:// or be http:// to localhost')
Vadim Shtayura6b555c12014-07-23 16:22:18 -070087 service = AuthService(server_url)
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -050088 try:
89 service.login(False)
90 except IOError:
91 raise ValueError('Failed to contact %s' % server_url)
92 try:
93 identity = service.get_current_identity()
94 except AuthServiceError:
95 raise ValueError('Failed to fetch identify from %s' % server_url)
Vadim Shtayura6b555c12014-07-23 16:22:18 -070096 if identity == 'anonymous:anonymous':
Marc-Antoine Ruelf7d737d2014-12-10 15:36:29 -050097 raise ValueError(
Vadim Shtayura6b555c12014-07-23 16:22:18 -070098 'Please login to %s: \n'
99 ' python auth.py login --service=%s' % (server_url, server_url))
Vadim Shtayura6b555c12014-07-23 16:22:18 -0700100 email = identity.split(':')[1]
Marc-Antoine Ruel79940ae2014-09-23 17:55:41 -0400101 logging.info('Logged in to %s: %s', server_url, email)
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400102 return email
Vadim Shtayura6b555c12014-07-23 16:22:18 -0700103
104
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800105@subcommand.usage('[options]')
106def CMDlogin(parser, args):
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800107 """Runs interactive login flow and stores auth token/cookie on disk."""
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800108 (options, args) = parser.parse_args(args)
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800109 process_auth_options(parser, options)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800110 service = AuthService(options.service)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800111 if service.login(True):
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800112 print 'Logged in as \'%s\'.' % service.get_current_identity()
113 return 0
114 else:
115 print 'Login failed or canceled.'
116 return 1
117
118
119@subcommand.usage('[options]')
120def CMDlogout(parser, args):
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800121 """Purges cached auth token/cookie."""
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800122 (options, args) = parser.parse_args(args)
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800123 process_auth_options(parser, options)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800124 service = AuthService(options.service)
125 service.logout()
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800126 return 0
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800127
128
129@subcommand.usage('[options]')
130def CMDcheck(parser, args):
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800131 """Shows identity associated with currently cached auth token/cookie."""
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800132 (options, args) = parser.parse_args(args)
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800133 process_auth_options(parser, options)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800134 service = AuthService(options.service)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800135 service.login(False)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800136 print service.get_current_identity()
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800137 return 0
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800138
139
Marc-Antoine Ruelf74cffe2015-07-15 15:21:34 -0400140class OptionParserAuth(logging_utils.OptionParserWithLogging):
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800141 def __init__(self, **kwargs):
Marc-Antoine Ruelf74cffe2015-07-15 15:21:34 -0400142 logging_utils.OptionParserWithLogging.__init__(
143 self, prog='auth.py', **kwargs)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800144 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):
Marc-Antoine Ruelf74cffe2015-07-15 15:21:34 -0400153 options, args = logging_utils.OptionParserWithLogging.parse_args(
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800154 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:]))