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:
Chmouel Boudjnah 2011-10-05 11:55:01 -04:00
parent 74752c8d26
commit 310675f773

159
bin/swift
View File

@ -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')