Merge "Remove unnecessary auth module"
This commit is contained in:
commit
56290c3c0d
glance_store
@ -33,7 +33,6 @@ except ImportError:
|
||||
import glance_store
|
||||
from glance_store._drivers.swift import utils as sutils
|
||||
from glance_store import capabilities
|
||||
from glance_store.common import auth
|
||||
from glance_store.common import utils as cutils
|
||||
from glance_store import driver
|
||||
from glance_store import exceptions
|
||||
@ -870,9 +869,10 @@ class MultiTenantStore(BaseStore):
|
||||
reason=reason)
|
||||
self.storage_url = self.conf_endpoint
|
||||
if not self.storage_url:
|
||||
self.storage_url = auth.get_endpoint(
|
||||
context.service_catalog, service_type=self.service_type,
|
||||
endpoint_region=self.region, endpoint_type=self.endpoint_type)
|
||||
|
||||
self.storage_url = context.service_catalog.url_for(
|
||||
service_type=self.service_type, region_name=self.region,
|
||||
endpoint_type=self.endpoint_type)
|
||||
|
||||
if self.storage_url.startswith('http://'):
|
||||
self.scheme = 'swift+http'
|
||||
|
@ -1,293 +0,0 @@
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
This auth module is intended to allow OpenStack client-tools to select from a
|
||||
variety of authentication strategies, including NoAuth (the default), and
|
||||
Keystone (an identity management system).
|
||||
|
||||
> auth_plugin = AuthPlugin(creds)
|
||||
|
||||
> auth_plugin.authenticate()
|
||||
|
||||
> auth_plugin.auth_token
|
||||
abcdefg
|
||||
|
||||
> auth_plugin.management_url
|
||||
http://service_endpoint/
|
||||
"""
|
||||
import httplib2
|
||||
import logging
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
|
||||
from six.moves import range
|
||||
from six.moves import urllib
|
||||
|
||||
from glance_store import exceptions
|
||||
from glance_store.i18n import _
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseStrategy(object):
|
||||
def __init__(self):
|
||||
self.auth_token = None
|
||||
# TODO(sirp): Should expose selecting public/internal/admin URL.
|
||||
self.management_url = None
|
||||
|
||||
def authenticate(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def is_authenticated(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def strategy(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class NoAuthStrategy(BaseStrategy):
|
||||
def authenticate(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def strategy(self):
|
||||
return 'noauth'
|
||||
|
||||
|
||||
class KeystoneStrategy(BaseStrategy):
|
||||
MAX_REDIRECTS = 10
|
||||
|
||||
def __init__(self, creds, insecure=False, configure_via_auth=True):
|
||||
self.creds = creds
|
||||
self.insecure = insecure
|
||||
self.configure_via_auth = configure_via_auth
|
||||
super(KeystoneStrategy, self).__init__()
|
||||
|
||||
def check_auth_params(self):
|
||||
# Ensure that supplied credential parameters are as required
|
||||
for required in ('username', 'password', 'auth_url',
|
||||
'strategy'):
|
||||
if self.creds.get(required) is None:
|
||||
raise exceptions.MissingCredentialError(required=required)
|
||||
if self.creds['strategy'] != 'keystone':
|
||||
raise exceptions.BadAuthStrategy(expected='keystone',
|
||||
received=self.creds['strategy'])
|
||||
# For v2.0 also check tenant is present
|
||||
if self.creds['auth_url'].rstrip('/').endswith('v2.0'):
|
||||
if self.creds.get("tenant") is None:
|
||||
raise exceptions.MissingCredentialError(required='tenant')
|
||||
|
||||
def authenticate(self):
|
||||
"""Authenticate with the Keystone service.
|
||||
|
||||
There are a few scenarios to consider here:
|
||||
|
||||
1. Which version of Keystone are we using? v1 which uses headers to
|
||||
pass the credentials, or v2 which uses a JSON encoded request body?
|
||||
|
||||
2. Keystone may respond back with a redirection using a 305 status
|
||||
code.
|
||||
|
||||
3. We may attempt a v1 auth when v2 is what's called for. In this
|
||||
case, we rewrite the url to contain /v2.0/ and retry using the v2
|
||||
protocol.
|
||||
"""
|
||||
def _authenticate(auth_url):
|
||||
# If OS_AUTH_URL is missing a trailing slash add one
|
||||
if not auth_url.endswith('/'):
|
||||
auth_url += '/'
|
||||
token_url = urllib.parse.urljoin(auth_url, "tokens")
|
||||
# 1. Check Keystone version
|
||||
is_v2 = auth_url.rstrip('/').endswith('v2.0')
|
||||
if is_v2:
|
||||
self._v2_auth(token_url)
|
||||
else:
|
||||
self._v1_auth(token_url)
|
||||
|
||||
self.check_auth_params()
|
||||
auth_url = self.creds['auth_url']
|
||||
for __ in range(self.MAX_REDIRECTS):
|
||||
try:
|
||||
_authenticate(auth_url)
|
||||
except exceptions.AuthorizationRedirect as e:
|
||||
# 2. Keystone may redirect us
|
||||
auth_url = e.url
|
||||
except exceptions.AuthorizationFailure:
|
||||
# 3. In some configurations nova makes redirection to
|
||||
# v2.0 keystone endpoint. Also, new location does not
|
||||
# contain real endpoint, only hostname and port.
|
||||
if 'v2.0' not in auth_url:
|
||||
auth_url = urllib.parse.urljoin(auth_url, 'v2.0/')
|
||||
else:
|
||||
# If we successfully auth'd, then memorize the correct auth_url
|
||||
# for future use.
|
||||
self.creds['auth_url'] = auth_url
|
||||
break
|
||||
else:
|
||||
# Guard against a redirection loop
|
||||
raise exceptions.MaxRedirectsExceeded(redirects=self.MAX_REDIRECTS)
|
||||
|
||||
def _v1_auth(self, token_url):
|
||||
creds = self.creds
|
||||
|
||||
headers = {}
|
||||
headers['X-Auth-User'] = creds['username']
|
||||
headers['X-Auth-Key'] = creds['password']
|
||||
|
||||
tenant = creds.get('tenant')
|
||||
if tenant:
|
||||
headers['X-Auth-Tenant'] = tenant
|
||||
|
||||
resp, resp_body = self._do_request(token_url, 'GET', headers=headers)
|
||||
|
||||
def _management_url(self, resp):
|
||||
for url_header in ('x-image-management-url',
|
||||
'x-server-management-url',
|
||||
'x-glance'):
|
||||
try:
|
||||
return resp[url_header]
|
||||
except KeyError as e:
|
||||
not_found = e
|
||||
raise not_found
|
||||
|
||||
if resp.status in (200, 204):
|
||||
try:
|
||||
if self.configure_via_auth:
|
||||
self.management_url = _management_url(self, resp)
|
||||
self.auth_token = resp['x-auth-token']
|
||||
except KeyError:
|
||||
raise exceptions.AuthorizationFailure()
|
||||
elif resp.status == 305:
|
||||
raise exceptions.AuthorizationRedirect(uri=resp['location'])
|
||||
elif resp.status == 400:
|
||||
raise exceptions.AuthBadRequest(url=token_url)
|
||||
elif resp.status == 401:
|
||||
raise exceptions.NotAuthenticated()
|
||||
elif resp.status == 404:
|
||||
raise exceptions.AuthUrlNotFound(url=token_url)
|
||||
else:
|
||||
raise Exception(_('Unexpected response: %s') % resp.status)
|
||||
|
||||
def _v2_auth(self, token_url):
|
||||
|
||||
creds = self.creds
|
||||
|
||||
creds = {
|
||||
"auth": {
|
||||
"tenantName": creds['tenant'],
|
||||
"passwordCredentials": {
|
||||
"username": creds['username'],
|
||||
"password": creds['password']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headers = {}
|
||||
headers['Content-Type'] = 'application/json'
|
||||
req_body = jsonutils.dumps(creds)
|
||||
|
||||
resp, resp_body = self._do_request(
|
||||
token_url, 'POST', headers=headers, body=req_body)
|
||||
|
||||
if resp.status == 200:
|
||||
resp_auth = jsonutils.loads(resp_body)['access']
|
||||
creds_region = self.creds.get('region')
|
||||
if self.configure_via_auth:
|
||||
endpoint = get_endpoint(resp_auth['serviceCatalog'],
|
||||
endpoint_region=creds_region)
|
||||
self.management_url = endpoint
|
||||
self.auth_token = resp_auth['token']['id']
|
||||
elif resp.status == 305:
|
||||
raise exceptions.RedirectException(resp['location'])
|
||||
elif resp.status == 400:
|
||||
raise exceptions.AuthBadRequest(url=token_url)
|
||||
elif resp.status == 401:
|
||||
raise exceptions.NotAuthenticated()
|
||||
elif resp.status == 404:
|
||||
raise exceptions.AuthUrlNotFound(url=token_url)
|
||||
else:
|
||||
raise Exception(_('Unexpected response: %s') % resp.status)
|
||||
|
||||
@property
|
||||
def is_authenticated(self):
|
||||
return self.auth_token is not None
|
||||
|
||||
@property
|
||||
def strategy(self):
|
||||
return 'keystone'
|
||||
|
||||
def _do_request(self, url, method, headers=None, body=None):
|
||||
headers = headers or {}
|
||||
conn = httplib2.Http()
|
||||
conn.force_exception_to_status_code = True
|
||||
conn.disable_ssl_certificate_validation = self.insecure
|
||||
headers['User-Agent'] = 'glance-client'
|
||||
resp, resp_body = conn.request(url, method, headers=headers, body=body)
|
||||
return resp, resp_body
|
||||
|
||||
|
||||
def get_plugin_from_strategy(strategy, creds=None, insecure=False,
|
||||
configure_via_auth=True):
|
||||
if strategy == 'noauth':
|
||||
return NoAuthStrategy()
|
||||
elif strategy == 'keystone':
|
||||
return KeystoneStrategy(creds, insecure,
|
||||
configure_via_auth=configure_via_auth)
|
||||
else:
|
||||
raise Exception(_("Unknown auth strategy '%s'") % strategy)
|
||||
|
||||
|
||||
def get_endpoint(service_catalog, service_type='image', endpoint_region=None,
|
||||
endpoint_type='publicURL'):
|
||||
"""
|
||||
Select an endpoint from the service catalog
|
||||
|
||||
We search the full service catalog for services
|
||||
matching both type and region. If the client
|
||||
supplied no region then any 'image' endpoint
|
||||
is considered a match. There must be one -- and
|
||||
only one -- successful match in the catalog,
|
||||
otherwise we will raise an exception.
|
||||
"""
|
||||
endpoint = None
|
||||
for service in service_catalog:
|
||||
s_type = None
|
||||
try:
|
||||
s_type = service['type']
|
||||
except KeyError:
|
||||
msg = _('Encountered service with no "type": %s') % s_type
|
||||
LOG.warn(msg)
|
||||
continue
|
||||
|
||||
if s_type == service_type:
|
||||
for ep in service['endpoints']:
|
||||
if endpoint_region is None or endpoint_region == ep['region']:
|
||||
if endpoint is not None:
|
||||
# This is a second match, abort
|
||||
exc = exceptions.RegionAmbiguity
|
||||
raise exc(region=endpoint_region)
|
||||
endpoint = ep
|
||||
if endpoint and endpoint.get(endpoint_type):
|
||||
return endpoint[endpoint_type]
|
||||
else:
|
||||
raise exceptions.NoServiceEndpoint()
|
@ -37,7 +37,6 @@ from glance_store._drivers.swift import store as swift
|
||||
from glance_store import backend
|
||||
from glance_store import BackendException
|
||||
from glance_store import capabilities
|
||||
from glance_store.common import auth
|
||||
from glance_store.common import utils
|
||||
from glance_store import exceptions
|
||||
from glance_store import location
|
||||
@ -624,11 +623,11 @@ class SwiftTests(object):
|
||||
self.config(swift_store_container='container')
|
||||
self.config(swift_store_create_container_on_put=True)
|
||||
self.config(swift_store_multiple_containers_seed=2)
|
||||
fake_get_endpoint = FakeGetEndpoint('https://some_endpoint')
|
||||
self.stubs.Set(auth, 'get_endpoint', fake_get_endpoint)
|
||||
service_catalog = mock.MagicMock()
|
||||
service_catalog.url_for.return_value = 'https://some_endpoint'
|
||||
ctxt = mock.MagicMock(
|
||||
user='user', tenant='tenant', auth_token='123',
|
||||
service_catalog={})
|
||||
service_catalog=service_catalog)
|
||||
store = swift.MultiTenantStore(self.conf)
|
||||
store.configure()
|
||||
location, size, checksum, _ = store.add(expected_image_id, image_swift,
|
||||
@ -1298,15 +1297,8 @@ class TestMultiTenantStoreContext(base.StoreBaseTest):
|
||||
self.config(**conf)
|
||||
self.store.configure()
|
||||
self.register_store_schemes(self.store, 'swift')
|
||||
self.service_catalog = [{
|
||||
"name": "Object Storage",
|
||||
"type": "object-store",
|
||||
"endpoints": [{
|
||||
"publicURL": "http://127.0.0.1:0",
|
||||
"region": "region1",
|
||||
"versionId": "1.0",
|
||||
}]
|
||||
}]
|
||||
self.service_catalog = mock.MagicMock()
|
||||
self.service_catalog.url_for.return_value = "http://127.0.0.1:0"
|
||||
self.addCleanup(self.conf.reset)
|
||||
|
||||
@requests_mock.mock()
|
||||
@ -1411,11 +1403,11 @@ class TestCreatingLocations(base.StoreBaseTest):
|
||||
|
||||
def test_multi_tenant_location(self):
|
||||
self.config(swift_store_container='container')
|
||||
fake_get_endpoint = FakeGetEndpoint('https://some_endpoint')
|
||||
self.stubs.Set(auth, 'get_endpoint', fake_get_endpoint)
|
||||
service_catalog = mock.MagicMock()
|
||||
service_catalog.url_for.return_value = 'https://some_endpoint'
|
||||
ctxt = mock.MagicMock(
|
||||
user='user', tenant='tenant', auth_token='123',
|
||||
service_catalog={})
|
||||
service_catalog=service_catalog)
|
||||
store = swift.MultiTenantStore(self.conf)
|
||||
store.configure()
|
||||
location = store.create_location('image-id', context=ctxt)
|
||||
@ -1425,55 +1417,71 @@ class TestCreatingLocations(base.StoreBaseTest):
|
||||
self.assertEqual(location.obj, 'image-id')
|
||||
self.assertIsNone(location.user)
|
||||
self.assertIsNone(location.key)
|
||||
self.assertEqual(fake_get_endpoint.service_type, 'object-store')
|
||||
service_catalog.url_for.assert_called_once_with(
|
||||
service_type=store.service_type,
|
||||
region_name=store.region,
|
||||
endpoint_type=store.endpoint_type)
|
||||
|
||||
def test_multi_tenant_location_http(self):
|
||||
fake_get_endpoint = FakeGetEndpoint('http://some_endpoint')
|
||||
self.stubs.Set(auth, 'get_endpoint', fake_get_endpoint)
|
||||
service_catalog = mock.MagicMock()
|
||||
service_catalog.url_for.return_value = 'http://some_endpoint'
|
||||
ctxt = mock.MagicMock(
|
||||
user='user', tenant='tenant', auth_token='123',
|
||||
service_catalog={})
|
||||
service_catalog=service_catalog)
|
||||
store = swift.MultiTenantStore(self.conf)
|
||||
store.configure()
|
||||
location = store.create_location('image-id', context=ctxt)
|
||||
self.assertEqual(location.scheme, 'swift+http')
|
||||
self.assertEqual(location.swift_url, 'http://some_endpoint')
|
||||
service_catalog.url_for.assert_called_once_with(
|
||||
service_type=store.service_type,
|
||||
region_name=store.region,
|
||||
endpoint_type=store.endpoint_type)
|
||||
|
||||
def test_multi_tenant_location_with_region(self):
|
||||
self.config(swift_store_region='WestCarolina')
|
||||
fake_get_endpoint = FakeGetEndpoint('https://some_endpoint')
|
||||
self.stubs.Set(auth, 'get_endpoint', fake_get_endpoint)
|
||||
service_catalog = mock.MagicMock()
|
||||
service_catalog.url_for.return_value = 'https://some_endpoint'
|
||||
ctxt = mock.MagicMock(
|
||||
user='user', tenant='tenant', auth_token='123',
|
||||
service_catalog={})
|
||||
service_catalog=service_catalog)
|
||||
store = swift.MultiTenantStore(self.conf)
|
||||
store.configure()
|
||||
store._get_endpoint(ctxt)
|
||||
self.assertEqual(fake_get_endpoint.endpoint_region, 'WestCarolina')
|
||||
service_catalog.url_for.assert_called_once_with(
|
||||
service_type=store.service_type,
|
||||
region_name=store.region,
|
||||
endpoint_type=store.endpoint_type)
|
||||
|
||||
def test_multi_tenant_location_custom_service_type(self):
|
||||
self.config(swift_store_service_type='toy-store')
|
||||
fake_get_endpoint = FakeGetEndpoint('https://some_endpoint')
|
||||
self.stubs.Set(auth, 'get_endpoint', fake_get_endpoint)
|
||||
service_catalog = mock.MagicMock()
|
||||
service_catalog.url_for.return_value = 'https://some_endpoint'
|
||||
ctxt = mock.MagicMock(
|
||||
user='user', tenant='tenant', auth_token='123',
|
||||
service_catalog={})
|
||||
service_catalog=service_catalog)
|
||||
store = swift.MultiTenantStore(self.conf)
|
||||
store.configure()
|
||||
store._get_endpoint(ctxt)
|
||||
self.assertEqual(fake_get_endpoint.service_type, 'toy-store')
|
||||
service_catalog.url_for.assert_called_once_with(
|
||||
service_type=store.service_type,
|
||||
region_name=store.region,
|
||||
endpoint_type=store.endpoint_type)
|
||||
|
||||
def test_multi_tenant_location_custom_endpoint_type(self):
|
||||
self.config(swift_store_endpoint_type='InternalURL')
|
||||
fake_get_endpoint = FakeGetEndpoint('https://some_endpoint')
|
||||
self.stubs.Set(auth, 'get_endpoint', fake_get_endpoint)
|
||||
service_catalog = mock.MagicMock()
|
||||
service_catalog.url_for.return_value = 'https://some_endpoint'
|
||||
ctxt = mock.MagicMock(
|
||||
user='user', tenant='tenant', auth_token='123',
|
||||
service_catalog={})
|
||||
service_catalog=service_catalog)
|
||||
store = swift.MultiTenantStore(self.conf)
|
||||
store.configure()
|
||||
store._get_endpoint(ctxt)
|
||||
self.assertEqual(fake_get_endpoint.endpoint_type, 'InternalURL')
|
||||
service_catalog.url_for.assert_called_once_with(
|
||||
service_type=store.service_type,
|
||||
region_name=store.region,
|
||||
endpoint_type=store.endpoint_type)
|
||||
|
||||
|
||||
class TestChunkReader(base.StoreBaseTest):
|
||||
|
Loading…
Reference in New Issue
Block a user