# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """Authentication Library""" import argparse from keystoneauth1.identity.v3 import k2k from keystoneauth1.loading import base from osc_lib import exceptions as exc from osc_lib.i18n import _ from osc_lib import utils # Initialize the list of Authentication plugins early in order # to get the command-line options PLUGIN_LIST = None # List of plugin command line options OPTIONS_LIST = {} def get_plugin_list(): """Gather plugin list and cache it""" global PLUGIN_LIST if PLUGIN_LIST is None: PLUGIN_LIST = base.get_available_plugin_names() return PLUGIN_LIST def get_options_list(): """Gather plugin options so the help action has them available""" global OPTIONS_LIST if not OPTIONS_LIST: for plugin_name in get_plugin_list(): plugin_options = base.get_plugin_options(plugin_name) for o in plugin_options: os_name = o.name.lower().replace('_', '-') os_env_name = 'OS_' + os_name.upper().replace('-', '_') OPTIONS_LIST.setdefault( os_name, {'env': os_env_name, 'help': ''}, ) # TODO(mhu) simplistic approach, would be better to only add # help texts if they vary from one auth plugin to another # also the text rendering is ugly in the CLI ... OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( plugin_name, o.help, ) return OPTIONS_LIST def check_valid_authorization_options(options, auth_plugin_name): """Validate authorization options, and provide helpful error messages.""" if (options.auth.get('project_id') and not options.auth.get('domain_id') and not options.auth.get('domain_name') and not options.auth.get('project_name') and not options.auth.get('tenant_id') and not options.auth.get('tenant_name')): raise exc.CommandError(_( 'Missing parameter(s): ' 'Set either a project or a domain scope, but not both. Set a ' 'project scope with --os-project-name, OS_PROJECT_NAME, or ' 'auth.project_name. Alternatively, set a domain scope with ' '--os-domain-name, OS_DOMAIN_NAME or auth.domain_name.' )) def check_valid_authentication_options(options, auth_plugin_name): """Validate authentication options, and provide helpful error messages :param required_scope: indicate whether a scoped token is required """ # Get all the options defined within the plugin. plugin_opts = base.get_plugin_options(auth_plugin_name) plugin_opts = {opt.dest: opt for opt in plugin_opts} # NOTE(aloga): this is an horrible hack. We need a way to specify the # required options in the plugins. Using the "required" argument for # the oslo_config.cfg.Opt does not work, as it is not possible to load the # plugin if the option is not defined, so the error will simply be: # "NoMatchingPlugin: The plugin foobar could not be found" msgs = [] # when no auth params are passed in, user advised to use os-cloud if not options.auth and auth_plugin_name != 'none': msgs.append(_( 'Set a cloud-name with --os-cloud or OS_CLOUD' )) else: if ('password' in plugin_opts and not (options.auth.get('username') or options.auth.get('user_id'))): msgs.append(_( 'Set a username with --os-username, OS_USERNAME,' ' or auth.username' ' or set a user-id with --os-user-id, OS_USER_ID,' ' or auth.user_id' )) if 'auth_url' in plugin_opts and not options.auth.get('auth_url'): msgs.append(_( 'Set an authentication URL, with --os-auth-url,' ' OS_AUTH_URL or auth.auth_url' )) if 'url' in plugin_opts and not options.auth.get('url'): msgs.append(_( 'Set a service URL, with --os-url, OS_URL or auth.url' )) if 'token' in plugin_opts and not options.auth.get('token'): msgs.append(_( 'Set a token with --os-token, OS_TOKEN or auth.token' )) if msgs: raise exc.CommandError( _('Missing parameter(s): \n%s') % '\n'.join(msgs) ) def build_auth_plugins_option_parser(parser): """Auth plugins options builder Builds dynamically the list of options expected by each available authentication plugin. """ available_plugins = list(get_plugin_list()) parser.add_argument( '--os-auth-type', metavar='', dest='auth_type', default=utils.env('OS_AUTH_TYPE'), help=_('Select an authentication type. Available types: %s.' ' Default: selected based on --os-username/--os-token' ' (Env: OS_AUTH_TYPE)') % ', '.join(available_plugins), choices=available_plugins ) # Maintain compatibility with old tenant env vars envs = { 'OS_PROJECT_NAME': utils.env( 'OS_PROJECT_NAME', default=utils.env('OS_TENANT_NAME') ), 'OS_PROJECT_ID': utils.env( 'OS_PROJECT_ID', default=utils.env('OS_TENANT_ID') ), } for o in get_options_list(): # Remove tenant options from KSC plugins and replace them below if 'tenant' not in o: parser.add_argument( '--os-' + o, metavar='' % o, dest=o.replace('-', '_'), default=envs.get( OPTIONS_LIST[o]['env'], utils.env(OPTIONS_LIST[o]['env']), ), help=_('%(help)s\n(Env: %(env)s)') % { 'help': OPTIONS_LIST[o]['help'], 'env': OPTIONS_LIST[o]['env'], }, ) # add tenant-related options for compatibility # this is deprecated but still used in some tempest tests... parser.add_argument( '--os-tenant-name', metavar='', dest='os_project_name', help=argparse.SUPPRESS, ) parser.add_argument( '--os-tenant-id', metavar='', dest='os_project_id', help=argparse.SUPPRESS, ) return parser def get_keystone2keystone_auth(local_auth, service_provider, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None): """Return Keystone 2 Keystone authentication for service provider. :param local_auth: authentication to use with the local Keystone :param service_provider: service provider id as registered in Keystone :param project_id: project id to scope to in the service provider :param project_name: project name to scope to in the service provider :param project_domain_id: id of domain in the service provider :param project_domain_name: name of domain to in the service provider :return: Keystone2Keystone auth object for service provider """ return k2k.Keystone2Keystone(local_auth, service_provider, project_id=project_id, project_name=project_name, project_domain_id=project_domain_id, project_domain_name=project_domain_name)