1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
|
# 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
import logging
import stevedore
from keystoneclient.auth import base
from openstackclient.common import exceptions as exc
from openstackclient.common import utils
from openstackclient.i18n import _
LOG = logging.getLogger(__name__)
# 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 = stevedore.ExtensionManager(
base.PLUGIN_NAMESPACE,
invoke_on_load=False,
propagate_map_exceptions=True,
)
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 in get_plugin_list():
for o in plugin.plugin.get_options():
os_name = o.dest.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 select_auth_plugin(options):
"""Pick an auth plugin based on --os-auth-type or other options"""
auth_plugin_name = None
# Do the token/url check first as this must override the default
# 'password' set by os-client-config
# Also, url and token are not copied into o-c-c's auth dict (yet?)
if options.auth.get('url') and options.auth.get('token'):
# service token authentication
auth_plugin_name = 'token_endpoint'
elif options.auth_type in [plugin.name for plugin in PLUGIN_LIST]:
# A direct plugin name was given, use it
auth_plugin_name = options.auth_type
elif options.auth.get('username'):
if options.identity_api_version == '3':
auth_plugin_name = 'v3password'
elif options.identity_api_version.startswith('2'):
auth_plugin_name = 'v2password'
else:
# let keystoneclient figure it out itself
auth_plugin_name = 'osc_password'
elif options.auth.get('token'):
if options.identity_api_version == '3':
auth_plugin_name = 'v3token'
elif options.identity_api_version.startswith('2'):
auth_plugin_name = 'v2token'
else:
# let keystoneclient figure it out itself
auth_plugin_name = 'token'
else:
# The ultimate default is similar to the original behaviour,
# but this time with version discovery
auth_plugin_name = 'osc_password'
LOG.debug("Auth plugin %s selected", auth_plugin_name)
return auth_plugin_name
def build_auth_params(auth_plugin_name, cmd_options):
auth_params = dict(cmd_options.auth)
if auth_plugin_name:
LOG.debug('auth_type: %s', auth_plugin_name)
auth_plugin_class = base.get_plugin_class(auth_plugin_name)
# grab tenant from project for v2.0 API compatibility
if auth_plugin_name.startswith("v2"):
if 'project_id' in auth_params:
auth_params['tenant_id'] = auth_params['project_id']
del auth_params['project_id']
if 'project_name' in auth_params:
auth_params['tenant_name'] = auth_params['project_name']
del auth_params['project_name']
else:
LOG.debug('no auth_type')
# delay the plugin choice, grab every option
auth_plugin_class = None
plugin_options = set([o.replace('-', '_') for o in get_options_list()])
for option in plugin_options:
LOG.debug('fetching option %s', option)
auth_params[option] = getattr(cmd_options.auth, option, None)
return (auth_plugin_class, auth_params)
def check_valid_auth_options(options, auth_plugin_name, required_scope=True):
"""Perform basic option checking, provide helpful error messages.
:param required_scope: indicate whether a scoped token is required
"""
msg = ''
if auth_plugin_name.endswith('password'):
if not options.auth.get('username'):
msg += _('Set a username with --os-username, OS_USERNAME,'
' or auth.username\n')
if not options.auth.get('auth_url'):
msg += _('Set an authentication URL, with --os-auth-url,'
' OS_AUTH_URL or auth.auth_url\n')
if (required_scope and not
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')):
msg += _('Set a scope, such as a project or domain, set a '
'project scope with --os-project-name, OS_PROJECT_NAME '
'or auth.project_name, set a domain scope with '
'--os-domain-name, OS_DOMAIN_NAME or auth.domain_name')
elif auth_plugin_name.endswith('token'):
if not options.auth.get('token'):
msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n')
if not options.auth.get('auth_url'):
msg += _('Set a service AUTH_URL, with --os-auth-url, '
'OS_AUTH_URL or auth.auth_url\n')
elif auth_plugin_name == 'token_endpoint':
if not options.auth.get('token'):
msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n')
if not options.auth.get('url'):
msg += _('Set a service URL, with --os-url, OS_URL or auth.url\n')
if msg:
raise exc.CommandError('Missing parameter(s): \n%s' % msg)
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 = [plugin.name for plugin in get_plugin_list()]
parser.add_argument(
'--os-auth-type',
metavar='<auth-type>',
dest='auth_type',
default=utils.env('OS_AUTH_TYPE'),
help='Select an authentication type. Available types: ' +
', '.join(available_plugins) +
'. Default: selected based on --os-username/--os-token' +
' (Env: OS_AUTH_TYPE)',
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='<auth-%s>' % o,
dest=o.replace('-', '_'),
default=envs.get(
OPTIONS_LIST[o]['env'],
utils.env(OPTIONS_LIST[o]['env']),
),
help='%s\n(Env: %s)' % (
OPTIONS_LIST[o]['help'],
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='<auth-tenant-name>',
dest='os_project_name',
help=argparse.SUPPRESS,
)
parser.add_argument(
'--os-tenant-id',
metavar='<auth-tenant-id>',
dest='os_project_id',
help=argparse.SUPPRESS,
)
return parser
|