Add support for OS Auth API version 2
Add a switch -2 to allow authenticate against service providing OpenStack auth version 2.0 (ie: keystone). By default it will authenticate against v1.0 and you can explicitely force it by adding a --auth-version 1 if in future we decide to set 2.0 auth as default. It will handle the format tenant:user to indentify to a specific tenant, if this format is not specified it will assume user is the same as tenant. Change-Id: I4684ec1e0950a1dae6935486aa730eaf13d6cd46
This commit is contained in:
parent
74752c8d26
commit
310675f773
159
bin/swift
159
bin/swift
@ -33,8 +33,8 @@ import socket
|
||||
from cStringIO import StringIO
|
||||
from re import compile, DOTALL
|
||||
from tokenize import generate_tokens, STRING, NAME, OP
|
||||
from urllib import quote as _quote, unquote
|
||||
from urlparse import urlparse, urlunparse
|
||||
from urllib import quote as _quote
|
||||
from urlparse import urlparse, urlunparse, urljoin
|
||||
|
||||
try:
|
||||
from eventlet.green.httplib import HTTPException, HTTPSConnection
|
||||
@ -69,10 +69,12 @@ def quote(value, safe='/'):
|
||||
try:
|
||||
# simplejson is popular and pretty good
|
||||
from simplejson import loads as json_loads
|
||||
from simplejson import dumps as json_dumps
|
||||
except ImportError:
|
||||
try:
|
||||
# 2.6 will have a json module in the stdlib
|
||||
from json import loads as json_loads
|
||||
from json import dumps as json_dumps
|
||||
except ImportError:
|
||||
# fall back on local parser otherwise
|
||||
comments = compile(r'/\*.*\*/|//[^\r\n]*', DOTALL)
|
||||
@ -174,23 +176,18 @@ def http_connection(url, proxy=None):
|
||||
return parsed, conn
|
||||
|
||||
|
||||
def get_auth(url, user, key, snet=False):
|
||||
def get_conn(options):
|
||||
"""
|
||||
Get authentication/authorization credentials.
|
||||
|
||||
The snet parameter is used for Rackspace's ServiceNet internal network
|
||||
implementation. In this function, it simply adds *snet-* to the beginning
|
||||
of the host name for the returned storage URL. With Rackspace Cloud Files,
|
||||
use of this network path causes no bandwidth charges but requires the
|
||||
client to be running on Rackspace's ServiceNet network.
|
||||
|
||||
:param url: authentication/authorization URL
|
||||
:param user: user to authenticate as
|
||||
:param key: key or password for authorization
|
||||
:param snet: use SERVICENET internal network (see above), default is False
|
||||
:returns: tuple of (storage URL, auth token)
|
||||
:raises ClientException: HTTP GET request to auth URL failed
|
||||
Return a connection building it from the options.
|
||||
"""
|
||||
return Connection(options.auth,
|
||||
options.user,
|
||||
options.key,
|
||||
snet=options.snet,
|
||||
auth_version=options.auth_version)
|
||||
|
||||
|
||||
def _get_auth_v1_0(url, user, key, snet):
|
||||
parsed, conn = http_connection(url)
|
||||
conn.request('GET', parsed.path, '',
|
||||
{'X-Auth-User': user, 'X-Auth-Key': key})
|
||||
@ -211,6 +208,88 @@ def get_auth(url, user, key, snet=False):
|
||||
resp.getheader('x-auth-token'))
|
||||
|
||||
|
||||
def _get_auth_v2_0(url, user, key, snet):
|
||||
if ':' in user:
|
||||
tenant, user = user.split(':')
|
||||
else:
|
||||
tenant = user
|
||||
|
||||
def json_request(method, token_url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
if 'body' in kwargs:
|
||||
kwargs['headers']['Content-Type'] = 'application/json'
|
||||
kwargs['body'] = json_dumps(kwargs['body'])
|
||||
parsed, conn = http_connection(token_url)
|
||||
conn.request(method, parsed.path, **kwargs)
|
||||
resp = conn.getresponse()
|
||||
body = resp.read()
|
||||
if body:
|
||||
try:
|
||||
body = json_loads(body)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
body = None
|
||||
if resp.status < 200 or resp.status >= 300:
|
||||
raise ClientException('Auth GET failed', http_scheme=parsed.scheme,
|
||||
http_host=conn.host,
|
||||
http_port=conn.port,
|
||||
http_path=parsed.path,
|
||||
http_status=resp.status,
|
||||
http_reason=resp.reason)
|
||||
return resp, body
|
||||
body = {"auth": {"tenantName": tenant,
|
||||
"passwordCredentials":
|
||||
{"username": user, "password": key}}}
|
||||
token_url = urljoin(url, "tokens")
|
||||
resp, body = json_request("POST", token_url, body=body)
|
||||
token_id = None
|
||||
try:
|
||||
url = None
|
||||
catalogs = body['access']['serviceCatalog']
|
||||
for service in catalogs:
|
||||
if service['name'] == 'swift':
|
||||
url = service['endpoints'][0]['publicURL']
|
||||
token_id = body['access']['token']['id']
|
||||
if not url:
|
||||
raise ClientException("There is no swift endpoint " \
|
||||
"on this auth server.")
|
||||
except(KeyError, IndexError):
|
||||
raise ClientException("Error while getting answers from auth server")
|
||||
|
||||
if snet:
|
||||
parsed = list(urlparse(url))
|
||||
# Second item in the list is the netloc
|
||||
parsed[1] = 'snet-' + parsed[1]
|
||||
url = urlunparse(parsed)
|
||||
|
||||
return url, token_id
|
||||
|
||||
|
||||
def get_auth(url, user, key, snet=False, auth_version="1.0"):
|
||||
"""
|
||||
Get authentication/authorization credentials.
|
||||
|
||||
The snet parameter is used for Rackspace's ServiceNet internal network
|
||||
implementation. In this function, it simply adds *snet-* to the beginning
|
||||
of the host name for the returned storage URL. With Rackspace Cloud Files,
|
||||
use of this network path causes no bandwidth charges but requires the
|
||||
client to be running on Rackspace's ServiceNet network.
|
||||
|
||||
:param url: authentication/authorization URL
|
||||
:param user: user to authenticate as
|
||||
:param key: key or password for authorization
|
||||
:param snet: use SERVICENET internal network (see above), default is False
|
||||
:param auth_version: OpenStack authentication version (default is 1.0)
|
||||
:returns: tuple of (storage URL, auth token)
|
||||
:raises ClientException: HTTP GET request to auth URL failed
|
||||
"""
|
||||
if auth_version == "1.0" or auth_version == "1":
|
||||
return _get_auth_v1_0(url, user, key, snet)
|
||||
elif auth_version == "2.0" or auth_version == "2":
|
||||
return _get_auth_v2_0(url, user, key, snet)
|
||||
|
||||
|
||||
def get_account(url, token, marker=None, limit=None, prefix=None,
|
||||
http_conn=None, full_listing=False):
|
||||
"""
|
||||
@ -318,9 +397,12 @@ def post_account(url, token, headers, http_conn=None):
|
||||
resp.read()
|
||||
if resp.status < 200 or resp.status >= 300:
|
||||
raise ClientException('Account POST failed',
|
||||
http_scheme=parsed.scheme, http_host=conn.host,
|
||||
http_port=conn.port, http_path=path, http_status=resp.status,
|
||||
http_reason=resp.reason)
|
||||
http_scheme=parsed.scheme,
|
||||
http_host=conn.host,
|
||||
http_port=conn.port,
|
||||
http_path=parsed.path,
|
||||
http_status=resp.status,
|
||||
http_reason=resp.reason)
|
||||
|
||||
|
||||
def get_container(url, token, container, marker=None, limit=None,
|
||||
@ -749,7 +831,8 @@ class Connection(object):
|
||||
"""Convenience class to make requests that will also retry the request"""
|
||||
|
||||
def __init__(self, authurl, user, key, retries=5, preauthurl=None,
|
||||
preauthtoken=None, snet=False, starting_backoff=1):
|
||||
preauthtoken=None, snet=False, starting_backoff=1,
|
||||
auth_version=1):
|
||||
"""
|
||||
:param authurl: authenitcation URL
|
||||
:param user: user name to authenticate as
|
||||
@ -759,6 +842,7 @@ class Connection(object):
|
||||
:param preauthtoken: authentication token (if you have already
|
||||
authenticated)
|
||||
:param snet: use SERVICENET internal network default is False
|
||||
:param auth_version: Openstack auth version.
|
||||
"""
|
||||
self.authurl = authurl
|
||||
self.user = user
|
||||
@ -770,9 +854,11 @@ class Connection(object):
|
||||
self.attempts = 0
|
||||
self.snet = snet
|
||||
self.starting_backoff = starting_backoff
|
||||
self.auth_version = auth_version
|
||||
|
||||
def get_auth(self):
|
||||
return get_auth(self.authurl, self.user, self.key, snet=self.snet)
|
||||
return get_auth(self.authurl, self.user, self.key, snet=self.snet,
|
||||
auth_version=self.auth_version)
|
||||
|
||||
def http_connection(self):
|
||||
return http_connection(self.url)
|
||||
@ -1066,10 +1152,7 @@ def st_delete(parser, args, print_queue, error_queue):
|
||||
raise
|
||||
error_queue.put('Container %s not found' % repr(container))
|
||||
|
||||
url, token = get_auth(options.auth, options.user, options.key,
|
||||
snet=options.snet)
|
||||
create_connection = lambda: Connection(options.auth, options.user,
|
||||
options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
|
||||
create_connection = lambda: get_conn(options)
|
||||
object_threads = [QueueFunctionThread(object_queue, _delete_object,
|
||||
create_connection()) for _junk in xrange(10)]
|
||||
for thread in object_threads:
|
||||
@ -1242,10 +1325,7 @@ def st_download(options, args, print_queue, error_queue):
|
||||
raise
|
||||
error_queue.put('Container %s not found' % repr(container))
|
||||
|
||||
url, token = get_auth(options.auth, options.user, options.key,
|
||||
snet=options.snet)
|
||||
create_connection = lambda: Connection(options.auth, options.user,
|
||||
options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
|
||||
create_connection = lambda: get_conn(options)
|
||||
object_threads = [QueueFunctionThread(object_queue, _download_object,
|
||||
create_connection()) for _junk in xrange(10)]
|
||||
for thread in object_threads:
|
||||
@ -1323,8 +1403,8 @@ def st_list(options, args, print_queue, error_queue):
|
||||
error_queue.put('Usage: %s [options] %s' %
|
||||
(basename(argv[0]), st_list_help))
|
||||
return
|
||||
conn = Connection(options.auth, options.user, options.key,
|
||||
snet=options.snet)
|
||||
|
||||
conn = get_conn(options)
|
||||
try:
|
||||
marker = ''
|
||||
while True:
|
||||
@ -1357,7 +1437,7 @@ stat [container] [object]
|
||||
def st_stat(options, args, print_queue, error_queue):
|
||||
(options, args) = parse_args(parser, args)
|
||||
args = args[1:]
|
||||
conn = Connection(options.auth, options.user, options.key)
|
||||
conn = get_conn(options)
|
||||
if not args:
|
||||
try:
|
||||
headers = conn.head_account()
|
||||
@ -1498,7 +1578,7 @@ def st_post(options, args, print_queue, error_queue):
|
||||
if (options.read_acl or options.write_acl or options.sync_to or
|
||||
options.sync_key) and not args:
|
||||
exit('-r, -w, -t, and -k options only allowed for containers')
|
||||
conn = Connection(options.auth, options.user, options.key)
|
||||
conn = get_conn(options)
|
||||
if not args:
|
||||
headers = {}
|
||||
for item in options.meta:
|
||||
@ -1735,10 +1815,7 @@ def st_upload(options, args, print_queue, error_queue):
|
||||
else:
|
||||
object_queue.put({'path': subpath})
|
||||
|
||||
url, token = get_auth(options.auth, options.user, options.key,
|
||||
snet=options.snet)
|
||||
create_connection = lambda: Connection(options.auth, options.user,
|
||||
options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
|
||||
create_connection = lambda: get_conn(options)
|
||||
object_threads = [QueueFunctionThread(object_queue, _object_job,
|
||||
create_connection()) for _junk in xrange(10)]
|
||||
for thread in object_threads:
|
||||
@ -1809,6 +1886,12 @@ Example:
|
||||
parser.add_option('-A', '--auth', dest='auth',
|
||||
default=environ.get('ST_AUTH'),
|
||||
help='URL for obtaining an auth token')
|
||||
parser.add_option('-V', '--auth-version',
|
||||
dest='auth_version',
|
||||
default=environ.get('ST_AUTH_VERSION', '1.0'),
|
||||
type=str,
|
||||
help='Specify a version for authentication'\
|
||||
'(default: 1.0)')
|
||||
parser.add_option('-U', '--user', dest='user',
|
||||
default=environ.get('ST_USER'),
|
||||
help='User name for obtaining an auth token')
|
||||
|
Loading…
Reference in New Issue
Block a user