235 lines
11 KiB
Python
235 lines
11 KiB
Python
# Copyright 2011 Nebula, Inc.
|
|
# 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.
|
|
import logging
|
|
|
|
from keystoneclient import access
|
|
from keystoneclient import client
|
|
from keystoneclient import exceptions
|
|
from keystoneclient import service_catalog
|
|
from keystoneclient.v2_0 import ec2
|
|
from keystoneclient.v2_0 import endpoints
|
|
from keystoneclient.v2_0 import roles
|
|
from keystoneclient.v2_0 import services
|
|
from keystoneclient.v2_0 import tenants
|
|
from keystoneclient.v2_0 import tokens
|
|
from keystoneclient.v2_0 import users
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Client(client.HTTPClient):
|
|
"""Client for the OpenStack Keystone v2.0 API.
|
|
|
|
:param string username: Username for authentication. (optional)
|
|
:param string password: Password for authentication. (optional)
|
|
:param string token: Token for authentication. (optional)
|
|
:param string tenant_id: Tenant id. (optional)
|
|
:param string tenant_name: Tenant name. (optional)
|
|
:param string auth_url: Keystone service endpoint for authorization.
|
|
:param string region_name: Name of a region to select when choosing an
|
|
endpoint from the service catalog.
|
|
:param string endpoint: A user-supplied endpoint URL for the keystone
|
|
service. Lazy-authentication is possible for API
|
|
service calls if endpoint is set at
|
|
instantiation.(optional)
|
|
:param integer timeout: Allows customization of the timeout for client
|
|
http requests. (optional)
|
|
:param string original_ip: The original IP of the requesting user
|
|
which will be sent to Keystone in a
|
|
'Forwarded' header. (optional)
|
|
:param string cert: If provided, used as a local certificate to communicate
|
|
with the keystone endpoint. If provided, requires the
|
|
additional parameter key. (optional)
|
|
:param string key: The key associated with the certificate for secure
|
|
keystone communication. (optional)
|
|
:param string cacert: the ca-certs to verify the secure communications
|
|
with keystone. (optional)
|
|
:param boolean insecure: If using an SSL endpoint, allows for the certicate
|
|
to be unsigned - does not verify the certificate
|
|
chain. default: False (optional)
|
|
:param dict auth_ref: To allow for consumers of the client to manage their
|
|
own caching strategy, you may initialize a client
|
|
with a previously captured auth_reference (token)
|
|
:param boolean debug: Enables debug logging of all request and responses
|
|
to keystone. default False (option)
|
|
|
|
.. warning::
|
|
|
|
If debug is enabled, it may show passwords in plain text as a part of its
|
|
output.
|
|
|
|
|
|
The client can be created and used like a user or in a strictly
|
|
bootstrap mode. Normal operation expects a username, password, auth_url,
|
|
and tenant_name or id to be provided. Other values will be lazily loaded
|
|
as needed from the service catalog.
|
|
|
|
Example::
|
|
|
|
>>> from keystoneclient.v2_0 import client
|
|
>>> keystone = client.Client(username=USER,
|
|
password=PASS,
|
|
tenant_name=TENANT_NAME,
|
|
auth_url=KEYSTONE_URL)
|
|
>>> keystone.tenants.list()
|
|
...
|
|
>>> user = keystone.users.get(USER_ID)
|
|
>>> user.delete()
|
|
|
|
Once authenticated, you can store and attempt to re-use the
|
|
authenticated token. the auth_ref property on the client
|
|
returns as a dictionary-like-object so that you can export and
|
|
cache it, re-using it when initiating another client::
|
|
|
|
>>> from keystoneclient.v2_0 import client
|
|
>>> keystone = client.Client(username=USER,
|
|
password=PASS,
|
|
tenant_name=TENANT_NAME,
|
|
auth_url=KEYSTONE_URL)
|
|
>>> auth_ref = keystone.auth_ref
|
|
>>> # pickle or whatever you like here
|
|
>>> new_client = client.Client(auth_ref=auth_ref)
|
|
|
|
Alternatively, you can provide the administrative token configured in
|
|
keystone and an endpoint to communicate with directly. See
|
|
(``admin_token`` in ``keystone.conf``) In this case, authenticate()
|
|
is not needed, and no service catalog will be loaded.
|
|
|
|
Example::
|
|
|
|
>>> from keystoneclient.v2_0 import client
|
|
>>> admin_client = client.Client(
|
|
token='12345secret7890',
|
|
endpoint='http://localhost:35357/v2.0')
|
|
>>> keystone.tenants.list()
|
|
|
|
"""
|
|
|
|
def __init__(self, **kwargs):
|
|
""" Initialize a new client for the Keystone v2.0 API. """
|
|
super(Client, self).__init__(**kwargs)
|
|
self.endpoints = endpoints.EndpointManager(self)
|
|
self.roles = roles.RoleManager(self)
|
|
self.services = services.ServiceManager(self)
|
|
self.tenants = tenants.TenantManager(self)
|
|
self.tokens = tokens.TokenManager(self)
|
|
self.users = users.UserManager(self)
|
|
|
|
# extensions
|
|
self.ec2 = ec2.CredentialsManager(self)
|
|
|
|
if self.management_url is None:
|
|
self.authenticate()
|
|
|
|
#TODO(heckj): move to a method on auth_ref
|
|
def has_service_catalog(self):
|
|
"""Returns True if this client provides a service catalog."""
|
|
return hasattr(self, 'service_catalog')
|
|
|
|
def authenticate(self, username=None, password=None, tenant_name=None,
|
|
tenant_id=None, auth_url=None, token=None):
|
|
""" Authenticate against the Keystone API.
|
|
|
|
Uses the data provided at instantiation to authenticate against
|
|
the Keystone server. This may use either a username and password
|
|
or token for authentication. If a tenant name or id was provided
|
|
then the resulting authenticated client will be scoped to that
|
|
tenant and contain a service catalog of available endpoints.
|
|
|
|
With the v2.0 API, if a tenant name or ID is not provided, the
|
|
authenication token returned will be 'unscoped' and limited in
|
|
capabilities until a fully-scoped token is acquired.
|
|
|
|
If successful, sets the self.auth_ref and self.auth_token with
|
|
the returned token. If not already set, will also set
|
|
self.management_url from the details provided in the token.
|
|
|
|
:returns: ``True`` if authentication was successful.
|
|
:raises: AuthorizationFailure if unable to authenticate or validate
|
|
the existing authorization token
|
|
:raises: ValueError if insufficient parameters are used.
|
|
"""
|
|
auth_url = auth_url or self.auth_url
|
|
username = username or self.username
|
|
password = password or self.password
|
|
tenant_name = tenant_name or self.tenant_name
|
|
tenant_id = tenant_id or self.tenant_id
|
|
token = token or self.auth_token
|
|
|
|
try:
|
|
raw_token = self._base_authN(auth_url,
|
|
username=username,
|
|
tenant_id=tenant_id,
|
|
tenant_name=tenant_name,
|
|
password=password,
|
|
token=token)
|
|
self.auth_ref = access.AccessInfo(**raw_token)
|
|
# if we got a response without a service catalog, set the local
|
|
# list of tenants for introspection, and leave to client user
|
|
# to determine what to do. Otherwise, load up the service catalog
|
|
self.auth_token = self.auth_ref.auth_token
|
|
if self.auth_ref.scoped:
|
|
if self.management_url is None \
|
|
and self.auth_ref.management_url:
|
|
self.management_url = self.auth_ref.management_url[0]
|
|
self.tenant_name = self.auth_ref.tenant_name
|
|
self.tenant_id = self.auth_ref.tenant_id
|
|
self.user_id = self.auth_ref.user_id
|
|
self._extract_service_catalog(self.auth_url, self.auth_ref)
|
|
return True
|
|
except (exceptions.AuthorizationFailure, exceptions.Unauthorized):
|
|
_logger.debug("Authorization Failed.")
|
|
raise
|
|
except Exception as e:
|
|
raise exceptions.AuthorizationFailure("Authorization Failed: "
|
|
"%s" % e)
|
|
|
|
def _base_authN(self, auth_url, username=None, password=None,
|
|
tenant_name=None, tenant_id=None, token=None):
|
|
""" Takes a username, password, and optionally a tenant_id or
|
|
tenant_name to get an authentication token from keystone.
|
|
May also take a token and a tenant_id to re-scope a token
|
|
to a tenant."""
|
|
headers = {}
|
|
url = auth_url + "/tokens"
|
|
if token:
|
|
headers['X-Auth-Token'] = token
|
|
params = {"auth": {"token": {"id": token}}}
|
|
elif username and password:
|
|
params = {"auth": {"passwordCredentials": {"username": username,
|
|
"password": password}}}
|
|
else:
|
|
raise ValueError('A username and password or token is required.')
|
|
if tenant_id:
|
|
params['auth']['tenantId'] = tenant_id
|
|
elif tenant_name:
|
|
params['auth']['tenantName'] = tenant_name
|
|
resp, body = self.request(url, 'POST', body=params, headers=headers)
|
|
return body['access']
|
|
|
|
# TODO(heckj): remove entirely in favor of access.AccessInfo and
|
|
# associated methods
|
|
def _extract_service_catalog(self, url, body):
|
|
""" Set the client's service catalog from the response data. """
|
|
self.service_catalog = service_catalog.ServiceCatalog(body)
|
|
try:
|
|
sc = self.service_catalog.get_token()
|
|
# Save these since we have them and they'll be useful later
|
|
self.auth_tenant_id = sc.get('tenant_id')
|
|
self.auth_user_id = sc.get('user_id')
|
|
except KeyError:
|
|
raise exceptions.AuthorizationFailure()
|