diff options
Diffstat (limited to 'troveclient/auth.py')
| -rw-r--r-- | troveclient/auth.py | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/troveclient/auth.py b/troveclient/auth.py new file mode 100644 index 0000000..909d45b --- /dev/null +++ b/troveclient/auth.py @@ -0,0 +1,269 @@ +# Copyright 2012 OpenStack LLC +# +# 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. + +from troveclient import exceptions + + +def get_authenticator_cls(cls_or_name): + """Factory method to retrieve Authenticator class.""" + if isinstance(cls_or_name, type): + return cls_or_name + elif isinstance(cls_or_name, basestring): + if cls_or_name == "keystone": + return KeyStoneV2Authenticator + elif cls_or_name == "rax": + return RaxAuthenticator + elif cls_or_name == "auth1.1": + return Auth1_1 + elif cls_or_name == "fake": + return FakeAuth + + raise ValueError("Could not determine authenticator class from the given " + "value %r." % cls_or_name) + + +class Authenticator(object): + """ + Helper class to perform Keystone or other miscellaneous authentication. + + The "authenticate" method returns a ServiceCatalog, which can be used + to obtain a token. + + """ + + URL_REQUIRED = True + + def __init__(self, client, type, url, username, password, tenant, + region=None, service_type=None, service_name=None, + service_url=None): + self.client = client + self.type = type + self.url = url + self.username = username + self.password = password + self.tenant = tenant + self.region = region + self.service_type = service_type + self.service_name = service_name + self.service_url = service_url + + def _authenticate(self, url, body, root_key='access'): + """Authenticate and extract the service catalog.""" + # Make sure we follow redirects when trying to reach Keystone + tmp_follow_all_redirects = self.client.follow_all_redirects + self.client.follow_all_redirects = True + + try: + resp, body = self.client._time_request(url, "POST", body=body) + finally: + self.client.follow_all_redirects = tmp_follow_all_redirects + + if resp.status == 200: # content must always present + try: + return ServiceCatalog(body, region=self.region, + service_type=self.service_type, + service_name=self.service_name, + service_url=self.service_url, + root_key=root_key) + except exceptions.AmbiguousEndpoints: + print "Found more than one valid endpoint. Use a more "\ + "restrictive filter" + raise + except KeyError: + raise exceptions.AuthorizationFailure() + except exceptions.EndpointNotFound: + print "Could not find any suitable endpoint. Correct region?" + raise + + elif resp.status == 305: + return resp['location'] + else: + raise exceptions.from_response(resp, body) + + def authenticate(self): + raise NotImplementedError("Missing authenticate method.") + + +class KeyStoneV2Authenticator(Authenticator): + + def authenticate(self): + if self.url is None: + raise exceptions.AuthUrlNotGiven() + return self._v2_auth(self.url) + + def _v2_auth(self, url): + """Authenticate against a v2.0 auth service.""" + body = {"auth": { + "passwordCredentials": { + "username": self.username, + "password": self.password} + } + } + + if self.tenant: + body['auth']['tenantName'] = self.tenant + + return self._authenticate(url, body) + + +class Auth1_1(Authenticator): + + def authenticate(self): + """Authenticate against a v2.0 auth service.""" + if self.url is None: + raise exceptions.AuthUrlNotGiven() + auth_url = self.url + body = {"credentials": {"username": self.username, + "key": self.password}} + return self._authenticate(auth_url, body, root_key='auth') + + try: + print(resp_body) + self.auth_token = resp_body['auth']['token']['id'] + except KeyError: + raise nova_exceptions.AuthorizationFailure() + + catalog = resp_body['auth']['serviceCatalog'] + if 'cloudDatabases' not in catalog: + raise nova_exceptions.EndpointNotFound() + endpoints = catalog['cloudDatabases'] + for endpoint in endpoints: + if self.region_name is None or \ + endpoint['region'] == self.region_name: + self.management_url = endpoint['publicURL'] + return + raise nova_exceptions.EndpointNotFound() + + +class RaxAuthenticator(Authenticator): + + def authenticate(self): + if self.url is None: + raise exceptions.AuthUrlNotGiven() + return self._rax_auth(self.url) + + def _rax_auth(self, url): + """Authenticate against the Rackspace auth service.""" + body = {'auth': { + 'RAX-KSKEY:apiKeyCredentials': { + 'username': self.username, + 'apiKey': self.password, + 'tenantName': self.tenant} + } + } + + return self._authenticate(self.url, body) + + +class FakeAuth(Authenticator): + """Useful for faking auth.""" + + def authenticate(self): + class FakeCatalog(object): + def __init__(self, auth): + self.auth = auth + + def get_public_url(self): + return "%s/%s" % ('http://localhost:8779/v1.0', + self.auth.tenant) + + def get_token(self): + return self.auth.tenant + + return FakeCatalog(self) + + +class ServiceCatalog(object): + """Represents a Keystone Service Catalog which describes a service. + + This class has methods to obtain a valid token as well as a public service + url and a management url. + + """ + + def __init__(self, resource_dict, region=None, service_type=None, + service_name=None, service_url=None, root_key='access'): + self.catalog = resource_dict + self.region = region + self.service_type = service_type + self.service_name = service_name + self.service_url = service_url + self.management_url = None + self.public_url = None + self.root_key = root_key + self._load() + + def _load(self): + if not self.service_url: + self.public_url = self._url_for(attr='region', + filter_value=self.region, + endpoint_type="publicURL") + self.management_url = self._url_for(attr='region', + filter_value=self.region, + endpoint_type="adminURL") + else: + self.public_url = self.service_url + self.management_url = self.service_url + + def get_token(self): + return self.catalog[self.root_key]['token']['id'] + + def get_management_url(self): + return self.management_url + + def get_public_url(self): + return self.public_url + + def _url_for(self, attr=None, filter_value=None, + endpoint_type='publicURL'): + """ + Fetch the public URL from the Trove service for a particular + endpoint attribute. If none given, return the first. + """ + matching_endpoints = [] + if 'endpoints' in self.catalog: + # We have a bastardized service catalog. Treat it special. :/ + for endpoint in self.catalog['endpoints']: + if not filter_value or endpoint[attr] == filter_value: + matching_endpoints.append(endpoint) + if not matching_endpoints: + raise exceptions.EndpointNotFound() + + # We don't always get a service catalog back ... + if not 'serviceCatalog' in self.catalog[self.root_key]: + raise exceptions.EndpointNotFound() + + # Full catalog ... + catalog = self.catalog[self.root_key]['serviceCatalog'] + + for service in catalog: + if service.get("type") != self.service_type: + continue + + if (self.service_name and self.service_type == 'database' and + service.get('name') != self.service_name): + continue + + endpoints = service['endpoints'] + for endpoint in endpoints: + if not filter_value or endpoint.get(attr) == filter_value: + endpoint["serviceName"] = service.get("name") + matching_endpoints.append(endpoint) + + if not matching_endpoints: + raise exceptions.EndpointNotFound() + elif len(matching_endpoints) > 1: + raise exceptions.AmbiguousEndpoints(endpoints=matching_endpoints) + else: + return matching_endpoints[0].get(endpoint_type, None) |
