add escalator cli framework
[escalator.git] / client / escalatorclient / openstack / common / apiclient / auth.py
1 # Copyright 2013 OpenStack Foundation
2 # Copyright 2013 Spanish National Research Council.
3 # All Rights Reserved.
4 #
5 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
6 #    not use this file except in compliance with the License. You may obtain
7 #    a copy of the License at
8 #
9 #         http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #    Unless required by applicable law or agreed to in writing, software
12 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 #    License for the specific language governing permissions and limitations
15 #    under the License.
16
17 # E0202: An attribute inherited from %s hide this method
18 # pylint: disable=E0202
19
20 ########################################################################
21 #
22 # THIS MODULE IS DEPRECATED
23 #
24 # Please refer to
25 # https://etherpad.openstack.org/p/kilo-escalatorclient-library-proposals for
26 # the discussion leading to this deprecation.
27 #
28 # We recommend checking out the python-openstacksdk project
29 # (https://launchpad.net/python-openstacksdk) instead.
30 #
31 ########################################################################
32
33 import abc
34 import argparse
35 import os
36
37 import six
38 from stevedore import extension
39
40 from escalatorclient.openstack.common.apiclient import exceptions
41
42
43 _discovered_plugins = {}
44
45
46 def discover_auth_systems():
47     """Discover the available auth-systems.
48
49     This won't take into account the old style auth-systems.
50     """
51     global _discovered_plugins
52     _discovered_plugins = {}
53
54     def add_plugin(ext):
55         _discovered_plugins[ext.name] = ext.plugin
56
57     ep_namespace = "escalatorclient.openstack.common.apiclient.auth"
58     mgr = extension.ExtensionManager(ep_namespace)
59     mgr.map(add_plugin)
60
61
62 def load_auth_system_opts(parser):
63     """Load options needed by the available auth-systems into a parser.
64
65     This function will try to populate the parser with options from the
66     available plugins.
67     """
68     group = parser.add_argument_group("Common auth options")
69     BaseAuthPlugin.add_common_opts(group)
70     for name, auth_plugin in six.iteritems(_discovered_plugins):
71         group = parser.add_argument_group(
72             "Auth-system '%s' options" % name,
73             conflict_handler="resolve")
74         auth_plugin.add_opts(group)
75
76
77 def load_plugin(auth_system):
78     try:
79         plugin_class = _discovered_plugins[auth_system]
80     except KeyError:
81         raise exceptions.AuthSystemNotFound(auth_system)
82     return plugin_class(auth_system=auth_system)
83
84
85 def load_plugin_from_args(args):
86     """Load required plugin and populate it with options.
87
88     Try to guess auth system if it is not specified. Systems are tried in
89     alphabetical order.
90
91     :type args: argparse.Namespace
92     :raises: AuthPluginOptionsMissing
93     """
94     auth_system = args.os_auth_system
95     if auth_system:
96         plugin = load_plugin(auth_system)
97         plugin.parse_opts(args)
98         plugin.sufficient_options()
99         return plugin
100
101     for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)):
102         plugin_class = _discovered_plugins[plugin_auth_system]
103         plugin = plugin_class()
104         plugin.parse_opts(args)
105         try:
106             plugin.sufficient_options()
107         except exceptions.AuthPluginOptionsMissing:
108             continue
109         return plugin
110     raise exceptions.AuthPluginOptionsMissing(["auth_system"])
111
112
113 @six.add_metaclass(abc.ABCMeta)
114 class BaseAuthPlugin(object):
115     """Base class for authentication plugins.
116
117     An authentication plugin needs to override at least the authenticate
118     method to be a valid plugin.
119     """
120
121     auth_system = None
122     opt_names = []
123     common_opt_names = [
124         "auth_system",
125         "username",
126         "password",
127         "tenant_name",
128         "token",
129         "auth_url",
130     ]
131
132     def __init__(self, auth_system=None, **kwargs):
133         self.auth_system = auth_system or self.auth_system
134         self.opts = dict((name, kwargs.get(name))
135                          for name in self.opt_names)
136
137     @staticmethod
138     def _parser_add_opt(parser, opt):
139         """Add an option to parser in two variants.
140
141         :param opt: option name (with underscores)
142         """
143         dashed_opt = opt.replace("_", "-")
144         env_var = "OS_%s" % opt.upper()
145         arg_default = os.environ.get(env_var, "")
146         arg_help = "Defaults to env[%s]." % env_var
147         parser.add_argument(
148             "--os-%s" % dashed_opt,
149             metavar="<%s>" % dashed_opt,
150             default=arg_default,
151             help=arg_help)
152         parser.add_argument(
153             "--os_%s" % opt,
154             metavar="<%s>" % dashed_opt,
155             help=argparse.SUPPRESS)
156
157     @classmethod
158     def add_opts(cls, parser):
159         """Populate the parser with the options for this plugin.
160         """
161         for opt in cls.opt_names:
162             # use `BaseAuthPlugin.common_opt_names` since it is never
163             # changed in child classes
164             if opt not in BaseAuthPlugin.common_opt_names:
165                 cls._parser_add_opt(parser, opt)
166
167     @classmethod
168     def add_common_opts(cls, parser):
169         """Add options that are common for several plugins.
170         """
171         for opt in cls.common_opt_names:
172             cls._parser_add_opt(parser, opt)
173
174     @staticmethod
175     def get_opt(opt_name, args):
176         """Return option name and value.
177
178         :param opt_name: name of the option, e.g., "username"
179         :param args: parsed arguments
180         """
181         return (opt_name, getattr(args, "os_%s" % opt_name, None))
182
183     def parse_opts(self, args):
184         """Parse the actual auth-system options if any.
185
186         This method is expected to populate the attribute `self.opts` with a
187         dict containing the options and values needed to make authentication.
188         """
189         self.opts.update(dict(self.get_opt(opt_name, args)
190                               for opt_name in self.opt_names))
191
192     def authenticate(self, http_client):
193         """Authenticate using plugin defined method.
194
195         The method usually analyses `self.opts` and performs
196         a request to authentication server.
197
198         :param http_client: client object that needs authentication
199         :type http_client: HTTPClient
200         :raises: AuthorizationFailure
201         """
202         self.sufficient_options()
203         self._do_authenticate(http_client)
204
205     @abc.abstractmethod
206     def _do_authenticate(self, http_client):
207         """Protected method for authentication.
208         """
209
210     def sufficient_options(self):
211         """Check if all required options are present.
212
213         :raises: AuthPluginOptionsMissing
214         """
215         missing = [opt
216                    for opt in self.opt_names
217                    if not self.opts.get(opt)]
218         if missing:
219             raise exceptions.AuthPluginOptionsMissing(missing)
220
221     @abc.abstractmethod
222     def token_and_endpoint(self, endpoint_type, service_type):
223         """Return token and endpoint.
224
225         :param service_type: Service type of the endpoint
226         :type service_type: string
227         :param endpoint_type: Type of endpoint.
228                               Possible values: public or publicURL,
229                               internal or internalURL,
230                               admin or adminURL
231         :type endpoint_type: string
232         :returns: tuple of token and endpoint strings
233         :raises: EndpointException
234         """