Skip to content
Permalink
Browse files

Allow different auth providers via plugin system.

- Remove the NOVA_RAX_AUTH hack and provide (temporary) compatibility
  with the new system.
- Example plugin for RAX and HP provided here :
    RAX - https://github.com/emonty/rackspace-auth-openstack
    HP - https://github.com/emonty/hpcloud-auth-openstack
- Plugin are allowed to specify their own auth_url directly.
- Thanks to mtaylor for helping on this.

Change-Id: Ie96835be617c6a20d9c3fc3bd1536083aecfdc0b
  • Loading branch information...
chmouel committed Aug 2, 2012
1 parent f15974b commit 86c713b17ac8984b54ff767d83ab41037e7a7833
Showing with 237 additions and 37 deletions.
  1. +44 −26 novaclient/client.py
  2. +9 −0 novaclient/exceptions.py
  3. +20 −8 novaclient/shell.py
  4. +4 −2 novaclient/v1_1/client.py
  5. +159 −0 tests/test_auth_plugins.py
  6. +1 −1 tests/v1_1/test_auth.py
@@ -7,25 +7,26 @@
OpenStack Client interface. Handles the REST calls and responses.
"""

import httplib2

has_keyring = False
try:
import keyring
has_keyring = True
except ImportError:
pass

import logging
import os
import time
import urlparse

import httplib2
import pkg_resources

try:
import json
except ImportError:
import simplejson as json

has_keyring = False
try:
import keyring
has_keyring = True
except ImportError:
pass

# Python 2.5 compat fix
if not hasattr(urlparse, 'parse_qsl'):
import cgi
@@ -36,21 +37,32 @@
from novaclient import utils


def get_auth_system_url(auth_system):
"""Load plugin-based auth_url"""
ep_name = 'openstack.client.auth_url'
for ep in pkg_resources.iter_entry_points(ep_name):
if ep.name == auth_system:
return ep.load()()
raise exceptions.AuthSystemNotFound(auth_system)


class HTTPClient(httplib2.Http):

USER_AGENT = 'python-novaclient'

def __init__(self, user, password, projectid, auth_url, insecure=False,
timeout=None, proxy_tenant_id=None,
def __init__(self, user, password, projectid, auth_url=None,
insecure=False, timeout=None, proxy_tenant_id=None,
proxy_token=None, region_name=None,
endpoint_type='publicURL', service_type=None,
service_name=None, volume_service_name=None,
timings=False, bypass_url=None, no_cache=False,
http_log_debug=False):
http_log_debug=False, auth_system='keystone'):
super(HTTPClient, self).__init__(timeout=timeout)
self.user = user
self.password = password
self.projectid = projectid
if not auth_url and auth_system and auth_system != 'keystone':
auth_url = get_auth_system_url(auth_system)
self.auth_url = auth_url.rstrip('/')
self.version = 'v1.1'
self.region_name = region_name
@@ -75,6 +87,8 @@ def __init__(self, user, password, projectid, auth_url, insecure=False,
self.force_exception_to_status_code = True
self.disable_ssl_certificate_validation = insecure

self.auth_system = auth_system

self._logger = logging.getLogger(__name__)
if self.http_log_debug:
ch = logging.StreamHandler()
@@ -199,7 +213,6 @@ def _extract_service_catalog(self, url, resp, body, extract_token=True):
self.auth_url = url
self.service_catalog = \
service_catalog.ServiceCatalog(body)

if extract_token:
self.auth_token = self.service_catalog.get_token()

@@ -289,13 +302,20 @@ def authenticate(self):
admin_url = urlparse.urlunsplit(
(scheme, new_netloc, path, query, frag))

# FIXME(chmouel): This is to handle backward compatibiliy when
# we didn't have a plugin mechanism for the auth_system. This
# should be removed in the future and have people move to
# OS_AUTH_SYSTEM=rackspace instead.
if "NOVA_RAX_AUTH" in os.environ:
self.auth_system = "rackspace"

auth_url = self.auth_url
if self.version == "v2.0": # FIXME(chris): This should be better.
while auth_url:
if "NOVA_RAX_AUTH" in os.environ:
auth_url = self._rax_auth(auth_url)
else:
if not self.auth_system or self.auth_system == 'keystone':
auth_url = self._v2_auth(auth_url)
else:
auth_url = self._plugin_auth(auth_url)

# Are we acting on behalf of another user via an
# existing token? If so, our actual endpoints may
@@ -354,6 +374,14 @@ def _v1_auth(self, url):
else:
raise exceptions.from_response(resp, body)

def _plugin_auth(self, auth_url):
"""Load plugin-based authentication"""
ep_name = 'openstack.client.authenticate'
for ep in pkg_resources.iter_entry_points(ep_name):
if ep.name == self.auth_system:
return ep.load()(self, auth_url)
raise exceptions.AuthSystemNotFound(self.auth_system)

def _v2_auth(self, url):
"""Authenticate against a v2.0 auth service."""
body = {"auth": {
@@ -365,16 +393,6 @@ def _v2_auth(self, url):

self._authenticate(url, body)

def _rax_auth(self, url):
"""Authenticate against the Rackspace auth service."""
body = {"auth": {
"RAX-KSKEY:apiKeyCredentials": {
"username": self.user,
"apiKey": self.password,
"tenantName": self.projectid}}}

self._authenticate(url, body)

def _authenticate(self, url, body):
"""Authenticate and extract the service catalog."""
token_url = url + "/tokens"
@@ -22,6 +22,15 @@ class NoUniqueMatch(Exception):
pass


class AuthSystemNotFound(Exception):
"""When the user specify a AuthSystem but not installed."""
def __init__(self, auth_system):
self.auth_system = auth_system

def __str__(self):
return "AuthSystemNotFound: %s" % repr(self.auth_system)


class NoTokenLookupException(Exception):
"""This form of authentication does not support looking up
endpoints from an existing token."""
@@ -116,6 +116,10 @@ def get_base_parser(self):
default=utils.env('OS_REGION_NAME', 'NOVA_REGION_NAME'),
help='Defaults to env[OS_REGION_NAME].')

parser.add_argument('--os_auth_system',
default=utils.env('OS_AUTH_SYSTEM'),
help='Defaults to env[OS_AUTH_SYSTEM].')

parser.add_argument('--service_type',
help='Defaults to compute for most actions')

@@ -312,16 +316,16 @@ def main(self, argv):
return 0

(os_username, os_password, os_tenant_name, os_auth_url,
os_region_name, endpoint_type, insecure,
os_region_name, os_auth_system, endpoint_type, insecure,
service_type, service_name, volume_service_name,
username, apikey, projectid, url, region_name,
bypass_url, no_cache) = (
args.os_username, args.os_password,
args.os_tenant_name, args.os_auth_url,
args.os_region_name, args.endpoint_type,
args.insecure, args.service_type, args.service_name,
args.volume_service_name, args.username,
args.apikey, args.projectid,
args.os_region_name, args.os_auth_system,
args.endpoint_type, args.insecure, args.service_type,
args.service_name, args.volume_service_name,
args.username, args.apikey, args.projectid,
args.url, args.region_name,
args.bypass_url, args.no_cache)

@@ -361,11 +365,19 @@ def main(self, argv):

if not os_auth_url:
if not url:
raise exc.CommandError("You must provide an auth url "
"via either --os_auth_url or env[OS_AUTH_URL]")
if os_auth_system and os_auth_system != 'keystone':
os_auth_url = \
client.get_auth_system_url(os_auth_system)
else:
os_auth_url = url

if not os_auth_url:
raise exc.CommandError("You must provide an auth url "
"via either --os_auth_url or env[OS_AUTH_URL] "
"or specify an auth_system which defines a "
"default url with --os_auth_system "
"or env[OS_AUTH_SYSTEM")

if not os_region_name and region_name:
os_region_name = region_name

@@ -383,7 +395,7 @@ def main(self, argv):
os_password, os_tenant_name, os_auth_url, insecure,
region_name=os_region_name, endpoint_type=endpoint_type,
extensions=self.extensions, service_type=service_type,
service_name=service_name,
service_name=service_name, auth_system=os_auth_system,
volume_service_name=volume_service_name,
timings=args.timings, bypass_url=bypass_url,
no_cache=no_cache, http_log_debug=options.debug)
@@ -41,13 +41,14 @@ class Client(object):
"""

# FIXME(jesse): project_id isn't required to authenticate
def __init__(self, username, api_key, project_id, auth_url,
def __init__(self, username, api_key, project_id, auth_url=None,
insecure=False, timeout=None, proxy_tenant_id=None,
proxy_token=None, region_name=None,
endpoint_type='publicURL', extensions=None,
service_type='compute', service_name=None,
volume_service_name=None, timings=False,
bypass_url=None, no_cache=False, http_log_debug=False):
bypass_url=None, no_cache=False, http_log_debug=False,
auth_system='keystone'):
# FIXME(comstud): Rename the api_key argument above when we
# know it's not being used as keyword argument
password = api_key
@@ -92,6 +93,7 @@ def __init__(self, username, api_key, project_id, auth_url,
auth_url,
insecure=insecure,
timeout=timeout,
auth_system=auth_system,
proxy_token=proxy_token,
proxy_tenant_id=proxy_tenant_id,
region_name=region_name,

0 comments on commit 86c713b

Please sign in to comment.
You can’t perform that action at this time.