blob: 8f3518cdd2710baba5f27c7b04d6147fa76a8d61 [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
23def add_auth_options(parser):
24 """Adds command line options related to authentication."""
25 parser.auth_group = optparse.OptionGroup(parser, 'Authentication')
26 parser.auth_group.add_option(
Vadim Shtayura5d1efce2014-02-04 10:55:43 -080027 '--auth-method',
28 metavar='METHOD',
Vadim Shtayura33414bf2014-02-27 11:19:34 -080029 default=net.get_default_auth_config()[0],
Vadim Shtayura5d1efce2014-02-04 10:55:43 -080030 help='Authentication method to use: %s. [default: %%default]' %
Vadim Shtayura33414bf2014-02-27 11:19:34 -080031 ', '.join(name for name, _ in net.AUTH_METHODS))
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080032 parser.add_option_group(parser.auth_group)
33 oauth.add_oauth_options(parser)
34
35
Vadim Shtayura5d1efce2014-02-04 10:55:43 -080036def process_auth_options(parser, options):
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080037 """Configures process-wide authentication parameters based on |options|."""
Vadim Shtayura33414bf2014-02-27 11:19:34 -080038 # Validate that authentication method is known.
39 if options.auth_method not in dict(net.AUTH_METHODS):
Vadim Shtayura5d1efce2014-02-04 10:55:43 -080040 parser.error('Invalid --auth-method value: %s' % options.auth_method)
Vadim Shtayura33414bf2014-02-27 11:19:34 -080041
42 # Process the rest of the flags based on actual method used.
43 # Only oauth is configurable now.
44 config = None
45 if options.auth_method == 'oauth':
46 config = oauth.extract_oauth_config_from_options(options)
47
48 # Now configure 'net' globally to use this for every request.
49 net.configure_auth(options.auth_method, config)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080050
51
52class AuthServiceError(Exception):
53 """Unexpected response from authentication service."""
54
55
56class AuthService(object):
57 """Represents remote Authentication service."""
58
59 def __init__(self, url):
60 self._service = net.get_http_service(url)
61
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080062 def login(self, allow_user_interaction):
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080063 """Refreshes cached access token or creates a new one."""
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080064 return self._service.login(allow_user_interaction)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080065
66 def logout(self):
67 """Purges cached access token."""
68 return self._service.logout()
69
70 def get_current_identity(self):
71 """Returns identity associated with currently used credentials.
72
73 Identity is a string:
74 user:<email> - if using OAuth or cookie based authentication.
75 bot:<id> - if using HMAC based authentication.
76 anonymous:anonymous - if not authenticated.
77 """
78 identity = self._service.json_request('GET', '/auth/api/v1/accounts/self')
79 if not identity:
80 raise AuthServiceError('Failed to fetch identity')
81 return identity['identity']
82
83
84@subcommand.usage('[options]')
85def CMDlogin(parser, args):
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080086 """Runs interactive login flow and stores auth token/cookie on disk."""
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080087 (options, args) = parser.parse_args(args)
Vadim Shtayura5d1efce2014-02-04 10:55:43 -080088 process_auth_options(parser, options)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080089 service = AuthService(options.service)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080090 if service.login(True):
Vadim Shtayurac4c76b62014-01-13 15:05:41 -080091 print 'Logged in as \'%s\'.' % service.get_current_identity()
92 return 0
93 else:
94 print 'Login failed or canceled.'
95 return 1
96
97
98@subcommand.usage('[options]')
99def CMDlogout(parser, args):
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800100 """Purges cached auth token/cookie."""
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800101 (options, args) = parser.parse_args(args)
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800102 process_auth_options(parser, options)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800103 service = AuthService(options.service)
104 service.logout()
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800105 return 0
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800106
107
108@subcommand.usage('[options]')
109def CMDcheck(parser, args):
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800110 """Shows identity associated with currently cached auth token/cookie."""
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800111 (options, args) = parser.parse_args(args)
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800112 process_auth_options(parser, options)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800113 service = AuthService(options.service)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800114 service.login(False)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800115 print service.get_current_identity()
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800116 return 0
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800117
118
119class OptionParserAuth(tools.OptionParserWithLogging):
120 def __init__(self, **kwargs):
121 tools.OptionParserWithLogging.__init__(self, prog='auth.py', **kwargs)
122 self.server_group = tools.optparse.OptionGroup(self, 'Server')
123 self.server_group.add_option(
124 '-S', '--service',
125 metavar='URL', default='',
126 help='Service to use')
127 self.add_option_group(self.server_group)
128 add_auth_options(self)
129
130 def parse_args(self, *args, **kwargs):
131 options, args = tools.OptionParserWithLogging.parse_args(
132 self, *args, **kwargs)
133 options.service = options.service.rstrip('/')
134 if not options.service:
135 self.error('--service is required.')
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400136 on_error.report_on_exception_exit(options.service)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800137 return options, args
138
139
140def main(args):
141 dispatcher = subcommand.CommandDispatcher(__name__)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400142 return dispatcher.execute(OptionParserAuth(version=__version__), args)
Vadim Shtayurac4c76b62014-01-13 15:05:41 -0800143
144
145if __name__ == '__main__':
146 fix_encoding.fix_encoding()
147 tools.disable_buffering()
148 colorama.init()
149 sys.exit(main(sys.argv[1:]))