Add keystone v3 auth support

Enables swiftclient to authenticate using
the keystone v3 API, allowing user id's, user
domains and tenant/project domains to be
specified.

Since swiftclient imports keystoneclient, the
main changes in swiftclient/client.py are to
selectively import the correct keystoneclient
library version and pass a number of new
options to it via the get_auth() function. In
addition the get_keystoneclient_2_0 method
has been renamed get_auth_keystone to better
reflect its purpose since it now deals with
both v2 and v3 use cases.

In swiftclient/shell.py the new options are
added to the parser. To make the default help
message shorter, help for all the --os-*
options (including the existing v2 options)
is only displayed when explicitly requested
usng a new --os-help option.

A new set of unit tests is added to
test_shell.py to verify the parser. A comment
in tests/sample.conf explains how to
configure the existing functional tests to
run using keystone v3 API.

Note that to use keystone v3
with swift you will need to set
auth_version = v3.0 in the auth_token
middleware config section of
proxy-server.conf.

Change-Id: Ifda0b3263eb919a8c6a1b204ba0a1215ed6f642f
This commit is contained in:
anc 2014-03-25 08:21:21 +00:00 committed by Alistair Coles
parent 394cb57f63
commit cae12940b1
6 changed files with 597 additions and 131 deletions

@ -35,6 +35,10 @@ from swiftclient import version as swiftclient_version
from swiftclient.exceptions import ClientException
from swiftclient.utils import LengthWrapper
AUTH_VERSIONS_V1 = ('1.0', '1', 1)
AUTH_VERSIONS_V2 = ('2.0', '2', 2)
AUTH_VERSIONS_V3 = ('3.0', '3', 3)
try:
from logging import NullHandler
except ImportError:
@ -275,35 +279,57 @@ def get_auth_1_0(url, user, key, snet, **kwargs):
def get_keystoneclient_2_0(auth_url, user, key, os_options, **kwargs):
"""
Authenticate against an auth 2.0 server.
# this function is only here to preserve the historic 'public'
# interface of this module
kwargs.update({'auth_version': '2.0'})
return get_auth_keystone(auth_url, user, key, os_options, **kwargs)
We are using the keystoneclient library for our 2.0 authentication.
def get_auth_keystone(auth_url, user, key, os_options, **kwargs):
"""
Authenticate against a keystone server.
We are using the keystoneclient library for authentication.
"""
insecure = kwargs.get('insecure', False)
auth_version = kwargs.get('auth_version', '2.0')
debug = logger.isEnabledFor(logging.DEBUG) and True or False
try:
from keystoneclient.v2_0 import client as ksclient
if auth_version in AUTH_VERSIONS_V3:
from keystoneclient.v3 import client as ksclient
else:
from keystoneclient.v2_0 import client as ksclient
from keystoneclient import exceptions
except ImportError:
sys.exit('''
Auth version 2.0 requires python-keystoneclient, install it or use Auth
Auth versions 2.0 and 3 require python-keystoneclient, install it or use Auth
version 1.0 which requires ST_AUTH, ST_USER, and ST_KEY environment
variables to be set or overridden with -A, -U, or -K.''')
try:
_ksclient = ksclient.Client(username=user,
password=key,
tenant_name=os_options.get('tenant_name'),
tenant_id=os_options.get('tenant_id'),
debug=debug,
cacert=kwargs.get('cacert'),
auth_url=auth_url, insecure=insecure)
_ksclient = ksclient.Client(
username=user,
password=key,
tenant_name=os_options.get('tenant_name'),
tenant_id=os_options.get('tenant_id'),
user_id=os_options.get('user_id'),
user_domain_name=os_options.get('user_domain_name'),
user_domain_id=os_options.get('user_domain_id'),
project_name=os_options.get('project_name'),
project_id=os_options.get('project_id'),
project_domain_name=os_options.get('project_domain_name'),
project_domain_id=os_options.get('project_domain_id'),
debug=debug,
cacert=kwargs.get('cacert'),
auth_url=auth_url, insecure=insecure)
except exceptions.Unauthorized:
raise ClientException('Unauthorised. Check username, password'
' and tenant name/id')
msg = 'Unauthorized. Check username, password and tenant name/id.'
if auth_version in AUTH_VERSIONS_V3:
msg = 'Unauthorized. Check username/id, password, ' \
+ 'tenant name/id and user/tenant domain name/id.'
raise ClientException(msg)
except exceptions.AuthorizationFailure as err:
raise ClientException('Authorization Failure. %s' % err)
service_type = os_options.get('service_type') or 'object-store'
@ -335,13 +361,13 @@ def get_auth(auth_url, user, key, **kwargs):
storage_url, token = None, None
insecure = kwargs.get('insecure', False)
if auth_version in ['1.0', '1', 1]:
if auth_version in AUTH_VERSIONS_V1:
storage_url, token = get_auth_1_0(auth_url,
user,
key,
kwargs.get('snet'),
insecure=insecure)
elif auth_version in ['2.0', '2', 2]:
elif auth_version in AUTH_VERSIONS_V2 + AUTH_VERSIONS_V3:
# We are allowing to specify a token/storage-url to re-use
# without having to re-authenticate.
if (os_options.get('object_storage_url') and
@ -349,10 +375,9 @@ def get_auth(auth_url, user, key, **kwargs):
return (os_options.get('object_storage_url'),
os_options.get('auth_token'))
# We are handling a special use case here when we were
# allowing specifying the account/tenant_name with the -U
# argument
if not kwargs.get('tenant_name') and ':' in user:
# We are handling a special use case here where the user argument
# specifies both the user name and tenant name in the form tenant:user
if user and not kwargs.get('tenant_name') and ':' in user:
(os_options['tenant_name'],
user) = user.split(':')
@ -361,14 +386,17 @@ def get_auth(auth_url, user, key, **kwargs):
if kwargs.get('tenant_name'):
os_options['tenant_name'] = kwargs['tenant_name']
if not (os_options.get('tenant_name') or os_options.get('tenant_id')):
if not (os_options.get('tenant_name') or os_options.get('tenant_id')
or os_options.get('project_name')
or os_options.get('project_id')):
raise ClientException('No tenant specified')
cacert = kwargs.get('cacert', None)
storage_url, token = get_keystoneclient_2_0(auth_url, user,
key, os_options,
cacert=cacert,
insecure=insecure)
storage_url, token = get_auth_keystone(auth_url, user,
key, os_options,
cacert=cacert,
insecure=insecure,
auth_version=auth_version)
else:
raise ClientException('Unknown auth_version %s specified.'
% auth_version)

@ -22,7 +22,7 @@ import logging
from errno import EEXIST, ENOENT
from hashlib import md5
from optparse import OptionParser, SUPPRESS_HELP
from optparse import OptionParser, OptionGroup, SUPPRESS_HELP
from os import environ, listdir, makedirs, utime, _exit as os_exit
from os.path import dirname, getmtime, getsize, isdir, join, \
sep as os_path_sep
@ -1356,9 +1356,14 @@ def parse_args(parser, args, enforce_requires=True):
if len(args) > 0 and args[0] == 'tempurl':
return options, args
if (not (options.auth and options.user and options.key)):
# Use 2.0 auth if none of the old args are present
options.auth_version = '2.0'
if options.auth_version == '3.0':
# tolerate sloppy auth_version
options.auth_version = '3'
if (not (options.auth and options.user and options.key)
and options.auth_version != '3'):
# Use keystone auth if any of the old-style args are missing
options.auth_version = '2.0'
# Use new-style args if old ones not present
if not options.auth and options.os_auth_url:
@ -1370,8 +1375,15 @@ def parse_args(parser, args, enforce_requires=True):
# Specific OpenStack options
options.os_options = {
'user_id': options.os_user_id,
'user_domain_id': options.os_user_domain_id,
'user_domain_name': options.os_user_domain_name,
'tenant_id': options.os_tenant_id,
'tenant_name': options.os_tenant_name,
'project_id': options.os_project_id,
'project_name': options.os_project_name,
'project_domain_id': options.os_project_domain_id,
'project_domain_name': options.os_project_domain_name,
'service_type': options.os_service_type,
'endpoint_type': options.os_endpoint_type,
'auth_token': options.os_auth_token,
@ -1384,12 +1396,23 @@ def parse_args(parser, args, enforce_requires=True):
if (options.os_options.get('object_storage_url') and
options.os_options.get('auth_token') and
options.auth_version == '2.0'):
(options.auth_version == '2.0' or options.auth_version == '3')):
return options, args
if enforce_requires and \
not (options.auth and options.user and options.key):
exit('''
if enforce_requires:
if options.auth_version == '3':
if not options.auth:
exit('Auth version 3 requires OS_AUTH_URL to be set or ' +
'overridden with --os-auth-url')
if not (options.user or options.os_user_id):
exit('Auth version 3 requires either OS_USERNAME or ' +
'OS_USER_ID to be set or overridden with ' +
'--os-username or --os-user-id respectively.')
if not options.key:
exit('Auth version 3 requires OS_PASSWORD to be set or ' +
'overridden with --os-password')
elif not (options.auth and options.user and options.key):
exit('''
Auth version 1.0 requires ST_AUTH, ST_USER, and ST_KEY environment variables
to be set or overridden with -A, -U, or -K.
@ -1409,13 +1432,20 @@ def main(arguments=None):
version = client_version
parser = OptionParser(version='%%prog %s' % version,
usage='''
usage: %%prog [--version] [--help] [--snet] [--verbose]
usage: %%prog [--version] [--help] [--os-help] [--snet] [--verbose]
[--debug] [--info] [--quiet] [--auth <auth_url>]
[--auth-version <auth_version>] [--user <username>]
[--key <api_key>] [--retries <num_retries>]
[--os-username <auth-user-name>] [--os-password <auth-password>]
[--os-user-id <auth-user-id>]
[--os-user-domain-id <auth-user-domain-id>]
[--os-user-domain-name <auth-user-domain-name>]
[--os-tenant-id <auth-tenant-id>]
[--os-tenant-name <auth-tenant-name>]
[--os-project-id <auth-project-id>]
[--os-project-name <auth-project-name>]
[--os-project-domain-id <auth-project-domain-id>]
[--os-project-domain-name <auth-project-domain-name>]
[--os-auth-url <auth-url>] [--os-auth-token <auth-token>]
[--os-storage-url <storage-url>] [--os-region-name <region-name>]
[--os-service-type <service-type>]
@ -1449,12 +1479,25 @@ Examples:
%%prog --os-auth-url https://api.example.com/v2.0 --os-tenant-name tenant \\
--os-username user --os-password password list
%%prog --os-auth-url https://api.example.com/v3 --auth-version 3\\
--os-project-name project1 --os-project-domain-name domain1 \\
--os-username user --os-user-domain-name domain1 \\
--os-password password list
%%prog --os-auth-url https://api.example.com/v3 --auth-version 3\\
--os-project-id 0123456789abcdef0123456789abcdef \\
--os-user-id abcdef0123456789abcdef0123456789 \\
--os-password password list
%%prog --os-auth-token 6ee5eb33efad4e45ab46806eac010566 \\
--os-storage-url https://10.1.5.2:8080/v1/AUTH_ced809b6a4baea7aeab61a \\
list
%%prog list --lh
'''.strip('\n') % globals())
parser.add_option('--os-help', action='store_true', dest='os_help',
help='Show OpenStack authentication options.')
parser.add_option('--os_help', action='store_true', help=SUPPRESS_HELP)
parser.add_option('-s', '--snet', action='store_true', dest='snet',
default=False, help='Use SERVICENET internal network.')
parser.add_option('-v', '--verbose', action='count', dest='verbose',
@ -1472,7 +1515,9 @@ Examples:
help='URL for obtaining an auth token.')
parser.add_option('-V', '--auth-version',
dest='auth_version',
default=environ.get('ST_AUTH_VERSION', '1.0'),
default=environ.get('ST_AUTH_VERSION',
(environ.get('OS_AUTH_VERSION',
'1.0'))),
type=str,
help='Specify a version for authentication. '
'Defaults to 1.0.')
@ -1484,81 +1529,6 @@ Examples:
help='Key for obtaining an auth token.')
parser.add_option('-R', '--retries', type=int, default=5, dest='retries',
help='The number of times to retry a failed connection.')
parser.add_option('--os-username',
metavar='<auth-user-name>',
default=environ.get('OS_USERNAME'),
help='OpenStack username. Defaults to env[OS_USERNAME].')
parser.add_option('--os_username',
help=SUPPRESS_HELP)
parser.add_option('--os-password',
metavar='<auth-password>',
default=environ.get('OS_PASSWORD'),
help='OpenStack password. Defaults to env[OS_PASSWORD].')
parser.add_option('--os_password',
help=SUPPRESS_HELP)
parser.add_option('--os-tenant-id',
metavar='<auth-tenant-id>',
default=environ.get('OS_TENANT_ID'),
help='OpenStack tenant ID. '
'Defaults to env[OS_TENANT_ID].')
parser.add_option('--os_tenant_id',
help=SUPPRESS_HELP)
parser.add_option('--os-tenant-name',
metavar='<auth-tenant-name>',
default=environ.get('OS_TENANT_NAME'),
help='OpenStack tenant name. '
'Defaults to env[OS_TENANT_NAME].')
parser.add_option('--os_tenant_name',
help=SUPPRESS_HELP)
parser.add_option('--os-auth-url',
metavar='<auth-url>',
default=environ.get('OS_AUTH_URL'),
help='OpenStack auth URL. Defaults to env[OS_AUTH_URL].')
parser.add_option('--os_auth_url',
help=SUPPRESS_HELP)
parser.add_option('--os-auth-token',
metavar='<auth-token>',
default=environ.get('OS_AUTH_TOKEN'),
help='OpenStack token. Defaults to env[OS_AUTH_TOKEN]. '
'Used with --os-storage-url to bypass the '
'usual username/password authentication.')
parser.add_option('--os_auth_token',
help=SUPPRESS_HELP)
parser.add_option('--os-storage-url',
metavar='<storage-url>',
default=environ.get('OS_STORAGE_URL'),
help='OpenStack storage URL. '
'Defaults to env[OS_STORAGE_URL]. '
'Overrides the storage url returned during auth. '
'Will bypass authentication when used with '
'--os-auth-token.')
parser.add_option('--os_storage_url',
help=SUPPRESS_HELP)
parser.add_option('--os-region-name',
metavar='<region-name>',
default=environ.get('OS_REGION_NAME'),
help='OpenStack region name. '
'Defaults to env[OS_REGION_NAME].')
parser.add_option('--os_region_name',
help=SUPPRESS_HELP)
parser.add_option('--os-service-type',
metavar='<service-type>',
default=environ.get('OS_SERVICE_TYPE'),
help='OpenStack Service type. '
'Defaults to env[OS_SERVICE_TYPE].')
parser.add_option('--os_service_type',
help=SUPPRESS_HELP)
parser.add_option('--os-endpoint-type',
metavar='<endpoint-type>',
default=environ.get('OS_ENDPOINT_TYPE'),
help='OpenStack Endpoint type. '
'Defaults to env[OS_ENDPOINT_TYPE].')
parser.add_option('--os-cacert',
metavar='<ca-certificate>',
default=environ.get('OS_CACERT'),
help='Specify a CA bundle file to use in verifying a '
'TLS (https) server certificate. '
'Defaults to env[OS_CACERT].')
default_val = config_true_value(environ.get('SWIFTCLIENT_INSECURE'))
parser.add_option('--insecure',
action="store_true", dest="insecure",
@ -1573,7 +1543,143 @@ Examples:
help='This option is deprecated and not used anymore. '
'SSL compression should be disabled by default '
'by the system SSL library.')
os_grp = OptionGroup(parser, "OpenStack authentication options")
os_grp.add_option('--os-username',
metavar='<auth-user-name>',
default=environ.get('OS_USERNAME'),
help='OpenStack username. Defaults to env[OS_USERNAME].')
os_grp.add_option('--os_username',
help=SUPPRESS_HELP)
os_grp.add_option('--os-user-id',
metavar='<auth-user-id>',
default=environ.get('OS_USER_ID'),
help='OpenStack user ID. '
'Defaults to env[OS_USER_ID].')
os_grp.add_option('--os_user_id',
help=SUPPRESS_HELP)
os_grp.add_option('--os-user-domain-id',
metavar='<auth-user-domain-id>',
default=environ.get('OS_USER_DOMAIN_ID'),
help='OpenStack user domain ID. '
'Defaults to env[OS_USER_DOMAIN_ID].')
os_grp.add_option('--os_user_domain_id',
help=SUPPRESS_HELP)
os_grp.add_option('--os-user-domain-name',
metavar='<auth-user-domain-name>',
default=environ.get('OS_USER_DOMAIN_NAME'),
help='OpenStack user domain name. '
'Defaults to env[OS_USER_DOMAIN_NAME].')
os_grp.add_option('--os_user_domain_name',
help=SUPPRESS_HELP)
os_grp.add_option('--os-password',
metavar='<auth-password>',
default=environ.get('OS_PASSWORD'),
help='OpenStack password. Defaults to env[OS_PASSWORD].')
os_grp.add_option('--os_password',
help=SUPPRESS_HELP)
os_grp.add_option('--os-tenant-id',
metavar='<auth-tenant-id>',
default=environ.get('OS_TENANT_ID'),
help='OpenStack tenant ID. '
'Defaults to env[OS_TENANT_ID].')
os_grp.add_option('--os_tenant_id',
help=SUPPRESS_HELP)
os_grp.add_option('--os-tenant-name',
metavar='<auth-tenant-name>',
default=environ.get('OS_TENANT_NAME'),
help='OpenStack tenant name. '
'Defaults to env[OS_TENANT_NAME].')
os_grp.add_option('--os_tenant_name',
help=SUPPRESS_HELP)
os_grp.add_option('--os-project-id',
metavar='<auth-project-id>',
default=environ.get('OS_PROJECT_ID'),
help='OpenStack project ID. '
'Defaults to env[OS_PROJECT_ID].')
os_grp.add_option('--os_project_id',
help=SUPPRESS_HELP)
os_grp.add_option('--os-project-name',
metavar='<auth-project-name>',
default=environ.get('OS_PROJECT_NAME'),
help='OpenStack project name. '
'Defaults to env[OS_PROJECT_NAME].')
os_grp.add_option('--os_project_name',
help=SUPPRESS_HELP)
os_grp.add_option('--os-project-domain-id',
metavar='<auth-project-domain-id>',
default=environ.get('OS_PROJECT_DOMAIN_ID'),
help='OpenStack project domain ID. '
'Defaults to env[OS_PROJECT_DOMAIN_ID].')
os_grp.add_option('--os_project_domain_id',
help=SUPPRESS_HELP)
os_grp.add_option('--os-project-domain-name',
metavar='<auth-project-domain-name>',
default=environ.get('OS_PROJECT_DOMAIN_NAME'),
help='OpenStack project domain name. '
'Defaults to env[OS_PROJECT_DOMAIN_NAME].')
os_grp.add_option('--os_project_domain_name',
help=SUPPRESS_HELP)
os_grp.add_option('--os-auth-url',
metavar='<auth-url>',
default=environ.get('OS_AUTH_URL'),
help='OpenStack auth URL. Defaults to env[OS_AUTH_URL].')
os_grp.add_option('--os_auth_url',
help=SUPPRESS_HELP)
os_grp.add_option('--os-auth-token',
metavar='<auth-token>',
default=environ.get('OS_AUTH_TOKEN'),
help='OpenStack token. Defaults to env[OS_AUTH_TOKEN]. '
'Used with --os-storage-url to bypass the '
'usual username/password authentication.')
os_grp.add_option('--os_auth_token',
help=SUPPRESS_HELP)
os_grp.add_option('--os-storage-url',
metavar='<storage-url>',
default=environ.get('OS_STORAGE_URL'),
help='OpenStack storage URL. '
'Defaults to env[OS_STORAGE_URL]. '
'Overrides the storage url returned during auth. '
'Will bypass authentication when used with '
'--os-auth-token.')
os_grp.add_option('--os_storage_url',
help=SUPPRESS_HELP)
os_grp.add_option('--os-region-name',
metavar='<region-name>',
default=environ.get('OS_REGION_NAME'),
help='OpenStack region name. '
'Defaults to env[OS_REGION_NAME].')
os_grp.add_option('--os_region_name',
help=SUPPRESS_HELP)
os_grp.add_option('--os-service-type',
metavar='<service-type>',
default=environ.get('OS_SERVICE_TYPE'),
help='OpenStack Service type. '
'Defaults to env[OS_SERVICE_TYPE].')
os_grp.add_option('--os_service_type',
help=SUPPRESS_HELP)
os_grp.add_option('--os-endpoint-type',
metavar='<endpoint-type>',
default=environ.get('OS_ENDPOINT_TYPE'),
help='OpenStack Endpoint type. '
'Defaults to env[OS_ENDPOINT_TYPE].')
os_grp.add_option('--os_endpoint_type',
help=SUPPRESS_HELP)
os_grp.add_option('--os-cacert',
metavar='<ca-certificate>',
default=environ.get('OS_CACERT'),
help='Specify a CA bundle file to use in verifying a '
'TLS (https) server certificate. '
'Defaults to env[OS_CACERT].')
parser.disable_interspersed_args()
# call parse_args before adding os options group so that -h, --help will
# print a condensed help message without the os options
(options, args) = parse_args(parser, argv[1:], enforce_requires=False)
parser.add_option_group(os_grp)
if options.os_help:
# if openstack option help has been explicitly requested then force
# help message, now that os_options group has been added to parser
argv = ['-h']
(options, args) = parse_args(parser, argv[1:], enforce_requires=False)
parser.enable_interspersed_args()

@ -4,7 +4,8 @@ auth_host = 127.0.0.1
auth_port = 8080
auth_ssl = no
auth_prefix = /auth/
## sample config for Swift with Keystone
## sample config for Swift with Keystone v2 API
# For keystone v3 change auth_version to 3 and auth_prefix to /v3/
#auth_version = 2
#auth_host = localhost
#auth_port = 5000

@ -378,3 +378,281 @@ class TestSubcommandHelp(unittest.TestCase):
self.assertRaises(SystemExit, swiftclient.shell.main, argv)
expected = 'no help for bad_command'
self.assertEqual(out.getvalue().strip('\n'), expected)
class TestParsing(unittest.TestCase):
def _make_fake_command(self, result):
def fake_command(parser, args, thread_manager):
result[0], result[1] = swiftclient.shell.parse_args(parser, args)
return fake_command
def _make_args(self, cmd, opts, os_opts, separator='-'):
"""
Construct command line arguments for given options.
"""
args = [""]
for k, v in opts.items():
arg = "--" + k.replace("_", "-")
args = args + [arg, v]
for k, v in os_opts.items():
arg = "--os" + separator + k.replace("_", separator)
args = args + [arg, v]
args = args + [cmd]
return args
def _make_env(self, opts, os_opts):
"""
Construct a dict of environment variables for given options.
"""
env = {}
for k, v in opts.items():
key = 'ST_' + k.upper()
env[key] = v
for k, v in os_opts.items():
key = 'OS_' + k.upper()
env[key] = v
return env
def _verify_opts(self, actual_opts, opts, os_opts={}, os_opts_dict={}):
"""
Check parsed options are correct.
:param opts: v1 style options.
:param os_opts: openstack style options.
:param os_opts_dict: openstack options that should be found in the
os_options dict.
"""
# check the expected opts are set
for key, v in opts.items():
actual = getattr(actual_opts, key)
self.assertEqual(v, actual, 'Expected %s for key %s, found %s'
% (v, key, actual))
for key, v in os_opts.items():
actual = getattr(actual_opts, "os_" + key)
self.assertEqual(v, actual, 'Expected %s for key %s, found %s'
% (v, key, actual))
# check the os_options dict values are set
self.assertTrue(hasattr(actual_opts, 'os_options'))
actual_os_opts_dict = getattr(actual_opts, 'os_options')
expected_os_opts_keys = ['project_name', 'region_name',
'tenant_name',
'user_domain_name', 'endpoint_type',
'object_storage_url', 'project_domain_id',
'user_id', 'user_domain_id', 'tenant_id',
'service_type', 'project_id', 'auth_token',
'project_domain_name']
for key in expected_os_opts_keys:
self.assertTrue(key in actual_os_opts_dict)
cli_key = key
if key == 'object_storage_url':
# exceptions to the pattern...
cli_key = 'storage_url'
if cli_key in os_opts_dict:
expect = os_opts_dict[cli_key]
else:
expect = None
actual = actual_os_opts_dict[key]
self.assertEqual(expect, actual, 'Expected %s for %s, got %s'
% (expect, key, actual))
for key in actual_os_opts_dict:
self.assertTrue(key in expected_os_opts_keys)
# check that equivalent keys have equal values
equivalents = [('os_username', 'user'),
('os_auth_url', 'auth'),
('os_password', 'key')]
for pair in equivalents:
self.assertEqual(getattr(actual_opts, pair[0]),
getattr(actual_opts, pair[1]))
def test_minimum_required_args_v3(self):
opts = {"auth_version": "3"}
os_opts = {"password": "secret",
"username": "user",
"auth_url": "http://example.com:5000/v3"}
# username with domain is sufficient in args because keystone will
# assume user is in default domain
args = self._make_args("stat", opts, os_opts, '-')
result = [None, None]
fake_command = self._make_fake_command(result)
with mock.patch('swiftclient.shell.st_stat', fake_command):
swiftclient.shell.main(args)
self._verify_opts(result[0], opts, os_opts, {})
# check its ok to have user_id instead of username
os_opts = {"password": "secret",
"auth_url": "http://example.com:5000/v3"}
os_opts_dict = {"user_id": "user_ID"}
all_os_opts = os_opts.copy()
all_os_opts.update(os_opts_dict)
args = self._make_args("stat", opts, all_os_opts, '-')
result = [None, None]
fake_command = self._make_fake_command(result)
with mock.patch('swiftclient.shell.st_stat', fake_command):
swiftclient.shell.main(args)
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
# check no user credentials required if token and url supplied
os_opts = {}
os_opts_dict = {"storage_url": "http://example.com:8080/v1",
"auth_token": "0123abcd"}
args = self._make_args("stat", opts, os_opts_dict, '-')
result = [None, None]
fake_command = self._make_fake_command(result)
with mock.patch('swiftclient.shell.st_stat', fake_command):
swiftclient.shell.main(args)
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
def test_args_v3(self):
opts = {"auth_version": "3"}
os_opts = {"password": "secret",
"username": "user",
"auth_url": "http://example.com:5000/v3"}
os_opts_dict = {"user_id": "user_ID",
"project_id": "project_ID",
"tenant_id": "tenant_ID",
"project_domain_id": "project_domain_ID",
"user_domain_id": "user_domain_ID",
"tenant_name": "tenant",
"project_name": "project",
"project_domain_name": "project_domain",
"user_domain_name": "user_domain",
"auth_token": "token",
"storage_url": "http://example.com:8080/v1",
"region_name": "region",
"service_type": "service",
"endpoint_type": "endpoint"}
all_os_opts = os_opts.copy()
all_os_opts.update(os_opts_dict)
# check using hyphen separator
args = self._make_args("stat", opts, all_os_opts, '-')
result = [None, None]
fake_command = self._make_fake_command(result)
with mock.patch('swiftclient.shell.st_stat', fake_command):
swiftclient.shell.main(args)
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
# check using underscore separator
args = self._make_args("stat", opts, all_os_opts, '_')
result = [None, None]
fake_command = self._make_fake_command(result)
with mock.patch('swiftclient.shell.st_stat', fake_command):
swiftclient.shell.main(args)
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
# check using environment variables
args = self._make_args("stat", {}, {})
env = self._make_env(opts, all_os_opts)
result = [None, None]
fake_command = self._make_fake_command(result)
with mock.patch.dict(os.environ, env):
with mock.patch('swiftclient.shell.st_stat', fake_command):
swiftclient.shell.main(args)
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
# check again using OS_AUTH_VERSION instead of ST_AUTH_VERSION
env = self._make_env({}, all_os_opts)
env.update({'OS_AUTH_VERSION': '3'})
result = [None, None]
fake_command = self._make_fake_command(result)
with mock.patch.dict(os.environ, env):
with mock.patch('swiftclient.shell.st_stat', fake_command):
swiftclient.shell.main(args)
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
def test_command_args_v3(self):
result = [None, None]
fake_command = self._make_fake_command(result)
opts = {"auth_version": "3"}
os_opts = {"password": "secret",
"username": "user",
"auth_url": "http://example.com:5000/v3"}
args = self._make_args("stat", opts, os_opts)
with mock.patch('swiftclient.shell.st_stat', fake_command):
swiftclient.shell.main(args)
self.assertEqual(['stat'], result[1])
with mock.patch('swiftclient.shell.st_stat', fake_command):
args = args + ["container_name"]
swiftclient.shell.main(args)
self.assertEqual(["stat", "container_name"], result[1])
def test_insufficient_args_v3(self):
opts = {"auth_version": "3"}
os_opts = {"password": "secret",
"auth_url": "http://example.com:5000/v3"}
args = self._make_args("stat", opts, os_opts)
self.assertRaises(SystemExit, swiftclient.shell.main, args)
os_opts = {"username": "user",
"auth_url": "http://example.com:5000/v3"}
args = self._make_args("stat", opts, os_opts)
self.assertRaises(SystemExit, swiftclient.shell.main, args)
os_opts = {"username": "user",
"password": "secret"}
args = self._make_args("stat", opts, os_opts)
self.assertRaises(SystemExit, swiftclient.shell.main, args)
def test_insufficient_env_vars_v3(self):
args = self._make_args("stat", {}, {})
opts = {"auth_version": "3"}
os_opts = {"password": "secret",
"auth_url": "http://example.com:5000/v3"}
env = self._make_env(opts, os_opts)
with mock.patch.dict(os.environ, env):
self.assertRaises(SystemExit, swiftclient.shell.main, args)
os_opts = {"username": "user",
"auth_url": "http://example.com:5000/v3"}
env = self._make_env(opts, os_opts)
with mock.patch.dict(os.environ, env):
self.assertRaises(SystemExit, swiftclient.shell.main, args)
os_opts = {"username": "user",
"password": "secret"}
env = self._make_env(opts, os_opts)
with mock.patch.dict(os.environ, env):
self.assertRaises(SystemExit, swiftclient.shell.main, args)
def test_help(self):
# --help returns condensed help message
opts = {"help": ""}
os_opts = {}
args = self._make_args("stat", opts, os_opts)
mock_stdout = six.StringIO()
with mock.patch('sys.stdout', mock_stdout):
self.assertRaises(SystemExit, swiftclient.shell.main, args)
out = mock_stdout.getvalue()
self.assertTrue(out.find('[--key <api_key>]') > 0)
self.assertEqual(-1, out.find('--os-username=<auth-user-name>'))
# --help returns condensed help message, overrides --os-help
opts = {"help": ""}
os_opts = {"help": ""}
# "password": "secret",
# "username": "user",
# "auth_url": "http://example.com:5000/v3"}
args = self._make_args("", opts, os_opts)
mock_stdout = six.StringIO()
with mock.patch('sys.stdout', mock_stdout):
self.assertRaises(SystemExit, swiftclient.shell.main, args)
out = mock_stdout.getvalue()
self.assertTrue(out.find('[--key <api_key>]') > 0)
self.assertEqual(-1, out.find('--os-username=<auth-user-name>'))
## --os-help return os options help
opts = {}
args = self._make_args("", opts, os_opts)
mock_stdout = six.StringIO()
with mock.patch('sys.stdout', mock_stdout):
self.assertRaises(SystemExit, swiftclient.shell.main, args)
out = mock_stdout.getvalue()
self.assertTrue(out.find('[--key <api_key>]') > 0)
self.assertTrue(out.find('--os-username=<auth-user-name>') > 0)

@ -30,7 +30,7 @@ from six.moves.urllib.parse import urlparse
from six.moves import reload_module
# TODO: mock http connection class with more control over headers
from .utils import fake_http_connect, fake_get_keystoneclient_2_0
from .utils import fake_http_connect, fake_get_auth_keystone
from swiftclient import client as c
import swiftclient.utils
@ -287,7 +287,9 @@ class TestGetAuth(MockHttpTest):
def test_auth_v2_with_tenant_name(self):
os_options = {'tenant_name': 'asdf'}
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(os_options)
req_args = {'auth_version': '2.0'}
c.get_auth_keystone = fake_get_auth_keystone(os_options,
required_kwargs=req_args)
url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf',
os_options=os_options,
auth_version="2.0")
@ -296,7 +298,31 @@ class TestGetAuth(MockHttpTest):
def test_auth_v2_with_tenant_id(self):
os_options = {'tenant_id': 'asdf'}
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(os_options)
req_args = {'auth_version': '2.0'}
c.get_auth_keystone = fake_get_auth_keystone(os_options,
required_kwargs=req_args)
url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf',
os_options=os_options,
auth_version="2.0")
self.assertTrue(url.startswith("http"))
self.assertTrue(token)
def test_auth_v2_with_project_name(self):
os_options = {'project_name': 'asdf'}
req_args = {'auth_version': '2.0'}
c.get_auth_keystone = fake_get_auth_keystone(os_options,
required_kwargs=req_args)
url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf',
os_options=os_options,
auth_version="2.0")
self.assertTrue(url.startswith("http"))
self.assertTrue(token)
def test_auth_v2_with_project_id(self):
os_options = {'project_id': 'asdf'}
req_args = {'auth_version': '2.0'}
c.get_auth_keystone = fake_get_auth_keystone(os_options,
required_kwargs=req_args)
url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf',
os_options=os_options,
auth_version="2.0")
@ -304,7 +330,7 @@ class TestGetAuth(MockHttpTest):
self.assertTrue(token)
def test_auth_v2_no_tenant_name_or_tenant_id(self):
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0({})
c.get_auth_keystone = fake_get_auth_keystone({})
self.assertRaises(c.ClientException, c.get_auth,
'http://www.tests.com', 'asdf', 'asdf',
os_options={},
@ -313,7 +339,7 @@ class TestGetAuth(MockHttpTest):
def test_auth_v2_with_tenant_name_none_and_tenant_id_none(self):
os_options = {'tenant_name': None,
'tenant_id': None}
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(os_options)
c.get_auth_keystone = fake_get_auth_keystone(os_options)
self.assertRaises(c.ClientException, c.get_auth,
'http://www.tests.com', 'asdf', 'asdf',
os_options=os_options,
@ -321,7 +347,7 @@ class TestGetAuth(MockHttpTest):
def test_auth_v2_with_tenant_user_in_user(self):
tenant_option = {'tenant_name': 'foo'}
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(tenant_option)
c.get_auth_keystone = fake_get_auth_keystone(tenant_option)
url, token = c.get_auth('http://www.test.com', 'foo:bar', 'asdf',
os_options={},
auth_version="2.0")
@ -330,7 +356,7 @@ class TestGetAuth(MockHttpTest):
def test_auth_v2_tenant_name_no_os_options(self):
tenant_option = {'tenant_name': 'asdf'}
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(tenant_option)
c.get_auth_keystone = fake_get_auth_keystone(tenant_option)
url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf',
tenant_name='asdf',
os_options={},
@ -342,7 +368,7 @@ class TestGetAuth(MockHttpTest):
os_options = {'service_type': 'object-store',
'endpoint_type': 'internalURL',
'tenant_name': 'asdf'}
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(os_options)
c.get_auth_keystone = fake_get_auth_keystone(os_options)
url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf',
os_options=os_options,
auth_version="2.0")
@ -351,7 +377,7 @@ class TestGetAuth(MockHttpTest):
def test_auth_v2_with_tenant_user_in_user_no_os_options(self):
tenant_option = {'tenant_name': 'foo'}
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(tenant_option)
c.get_auth_keystone = fake_get_auth_keystone(tenant_option)
url, token = c.get_auth('http://www.test.com', 'foo:bar', 'asdf',
auth_version="2.0")
self.assertTrue(url.startswith("http"))
@ -360,7 +386,7 @@ class TestGetAuth(MockHttpTest):
def test_auth_v2_with_os_region_name(self):
os_options = {'region_name': 'good-region',
'tenant_name': 'asdf'}
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(os_options)
c.get_auth_keystone = fake_get_auth_keystone(os_options)
url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf',
os_options=os_options,
auth_version="2.0")
@ -370,14 +396,14 @@ class TestGetAuth(MockHttpTest):
def test_auth_v2_no_endpoint(self):
os_options = {'region_name': 'unknown_region',
'tenant_name': 'asdf'}
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(
c.get_auth_keystone = fake_get_auth_keystone(
os_options, c.ClientException)
self.assertRaises(c.ClientException, c.get_auth,
'http://www.tests.com', 'asdf', 'asdf',
os_options=os_options, auth_version='2.0')
def test_auth_v2_ks_exception(self):
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(
c.get_auth_keystone = fake_get_auth_keystone(
{}, c.ClientException)
self.assertRaises(c.ClientException, c.get_auth,
'http://www.tests.com', 'asdf', 'asdf',
@ -386,7 +412,7 @@ class TestGetAuth(MockHttpTest):
def test_auth_v2_cacert(self):
os_options = {'tenant_name': 'foo'}
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(
c.get_auth_keystone = fake_get_auth_keystone(
os_options, None)
auth_url_secure = 'https://www.tests.com'
@ -414,7 +440,7 @@ class TestGetAuth(MockHttpTest):
def test_auth_v2_insecure(self):
os_options = {'tenant_name': 'foo'}
c.get_keystoneclient_2_0 = fake_get_keystoneclient_2_0(
c.get_auth_keystone = fake_get_auth_keystone(
os_options, None)
auth_url_secure = 'https://www.tests.com'
@ -439,6 +465,29 @@ class TestGetAuth(MockHttpTest):
os_options=os_options, auth_version='2.0',
insecure=False)
def test_auth_v3_with_tenant_name(self):
# check the correct auth version is passed to get_auth_keystone
os_options = {'tenant_name': 'asdf'}
req_args = {'auth_version': '3'}
c.get_auth_keystone = fake_get_auth_keystone(os_options,
required_kwargs=req_args)
url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf',
os_options=os_options,
auth_version="3")
self.assertTrue(url.startswith("http"))
self.assertTrue(token)
def test_get_keystone_client_2_0(self):
# check the correct auth version is passed to get_auth_keystone
os_options = {'tenant_name': 'asdf'}
req_args = {'auth_version': '2.0'}
c.get_auth_keystone = fake_get_auth_keystone(os_options,
required_kwargs=req_args)
url, token = c.get_keystoneclient_2_0('http://www.test.com', 'asdf',
'asdf', os_options=os_options)
self.assertTrue(url.startswith("http"))
self.assertTrue(token)
class TestGetAccount(MockHttpTest):

@ -16,11 +16,11 @@ from requests import RequestException
from time import sleep
def fake_get_keystoneclient_2_0(os_options, exc=None, **kwargs):
def fake_get_keystoneclient_2_0(auth_url,
user,
key,
actual_os_options, **actual_kwargs):
def fake_get_auth_keystone(os_options, exc=None, **kwargs):
def fake_get_auth_keystone(auth_url,
user,
key,
actual_os_options, **actual_kwargs):
if exc:
raise exc('test')
if actual_os_options != os_options:
@ -37,9 +37,13 @@ def fake_get_keystoneclient_2_0(os_options, exc=None, **kwargs):
actual_kwargs['cacert'] is None:
from swiftclient import client as c
raise c.ClientException("unverified-certificate")
if 'required_kwargs' in kwargs:
for k, v in kwargs['required_kwargs'].items():
if v != actual_kwargs.get(k):
return "", None
return "http://url/", "token"
return fake_get_keystoneclient_2_0
return fake_get_auth_keystone
def fake_http_connect(*code_iter, **kwargs):