diff --git a/keystoneclient/middleware/__init__.py b/keystoneclient/middleware/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/keystoneclient/middleware/auth_token.py b/keystoneclient/middleware/auth_token.py deleted file mode 100644 index b6c2be0ce..000000000 --- a/keystoneclient/middleware/auth_token.py +++ /dev/null @@ -1,864 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010-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. - -""" -TOKEN-BASED AUTH MIDDLEWARE - -This WSGI component: - -* Verifies that incoming client requests have valid tokens by validating - tokens with the auth service. -* Rejects unauthenticated requests UNLESS it is in 'delay_auth_decision' - mode, which means the final decision is delegated to the downstream WSGI - component (usually the OpenStack service) -* Collects and forwards identity information based on a valid token - such as user name, tenant, etc - -Refer to: http://keystone.openstack.org/middlewarearchitecture.html - -HEADERS -------- - -* Headers starting with HTTP\_ is a standard http header -* Headers starting with HTTP_X is an extended http header - -Coming in from initial call from client or customer -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -HTTP_X_AUTH_TOKEN - The client token being passed in. - -HTTP_X_STORAGE_TOKEN - The client token being passed in (legacy Rackspace use) to support - swift/cloud files - -Used for communication between components -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -WWW-Authenticate - HTTP header returned to a user indicating which endpoint to use - to retrieve a new token - -What we add to the request for use by the OpenStack service -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -HTTP_X_IDENTITY_STATUS - 'Confirmed' or 'Invalid' - The underlying service will only see a value of 'Invalid' if the Middleware - is configured to run in 'delay_auth_decision' mode - -HTTP_X_TENANT_ID - Identity service managed unique identifier, string - -HTTP_X_TENANT_NAME - Unique tenant identifier, string - -HTTP_X_USER_ID - Identity-service managed unique identifier, string - -HTTP_X_USER_NAME - Unique user identifier, string - -HTTP_X_ROLES - Comma delimited list of case-sensitive Roles - -HTTP_X_SERVICE_CATALOG - json encoded keystone service catalog (optional). - -HTTP_X_TENANT - *Deprecated* in favor of HTTP_X_TENANT_ID and HTTP_X_TENANT_NAME - Keystone-assigned unique identifier, deprecated - -HTTP_X_USER - *Deprecated* in favor of HTTP_X_USER_ID and HTTP_X_USER_NAME - Unique user name, string - -HTTP_X_ROLE - *Deprecated* in favor of HTTP_X_ROLES - This is being renamed, and the new header contains the same data. - -OTHER ENVIRONMENT VARIABLES ---------------------------- - -keystone.token_info - Information about the token discovered in the process of - validation. This may include extended information returned by the - Keystone token validation call, as well as basic information about - the tenant and user. - -""" - -import datetime -import httplib -import json -import logging -import os -import stat -import time -import webob -import webob.exc - -from keystoneclient.openstack.common import jsonutils -from keystoneclient.common import cms -from keystoneclient import utils -from keystoneclient.openstack.common import timeutils - -CONF = None -try: - from openstack.common import cfg - CONF = cfg.CONF -except ImportError: - # cfg is not a library yet, try application copies - for app in 'nova', 'glance', 'quantum', 'cinder': - try: - cfg = __import__('%s.openstack.common.cfg' % app, - fromlist=['%s.openstack.common' % app]) - # test which application middleware is running in - if hasattr(cfg, 'CONF') and 'config_file' in cfg.CONF: - CONF = cfg.CONF - break - except ImportError: - pass -if not CONF: - from keystoneclient.openstack.common import cfg - CONF = cfg.CONF -LOG = logging.getLogger(__name__) - -# alternative middleware configuration in the main application's -# configuration file e.g. in nova.conf -# [keystone_authtoken] -# auth_host = 127.0.0.1 -# auth_port = 35357 -# auth_protocol = http -# admin_tenant_name = admin -# admin_user = admin -# admin_password = badpassword -opts = [ - cfg.StrOpt('auth_admin_prefix', default=''), - cfg.StrOpt('auth_host', default='127.0.0.1'), - cfg.IntOpt('auth_port', default=35357), - cfg.StrOpt('auth_protocol', default='https'), - cfg.StrOpt('auth_uri', default=None), - cfg.BoolOpt('delay_auth_decision', default=False), - cfg.StrOpt('admin_token'), - cfg.StrOpt('admin_user'), - cfg.StrOpt('admin_password'), - cfg.StrOpt('admin_tenant_name', default='admin'), - cfg.StrOpt('certfile'), - cfg.StrOpt('keyfile'), - cfg.StrOpt('signing_dir'), - cfg.ListOpt('memcache_servers'), - cfg.IntOpt('token_cache_time', default=300), -] -CONF.register_opts(opts, group='keystone_authtoken') - - -def will_expire_soon(expiry): - """ Determines if expiration is about to occur. - - :param expiry: a datetime of the expected expiration - :returns: boolean : true if expiration is within 30 seconds - """ - soon = (timeutils.utcnow() + datetime.timedelta(seconds=30)) - return expiry < soon - - -class InvalidUserToken(Exception): - pass - - -class ServiceError(Exception): - pass - - -class ConfigurationError(Exception): - pass - - -class AuthProtocol(object): - """Auth Middleware that handles authenticating client calls.""" - - def __init__(self, app, conf): - LOG.info('Starting keystone auth_token middleware') - self.conf = conf - self.app = app - - # delay_auth_decision means we still allow unauthenticated requests - # through and we let the downstream service make the final decision - self.delay_auth_decision = (self._conf_get('delay_auth_decision') in - (True, 'true', 't', '1', 'on', 'yes', 'y')) - - # where to find the auth service (we use this to validate tokens) - self.auth_host = self._conf_get('auth_host') - self.auth_port = int(self._conf_get('auth_port')) - self.auth_protocol = self._conf_get('auth_protocol') - if self.auth_protocol == 'http': - self.http_client_class = httplib.HTTPConnection - else: - self.http_client_class = httplib.HTTPSConnection - - self.auth_admin_prefix = self._conf_get('auth_admin_prefix') - self.auth_uri = self._conf_get('auth_uri') - if self.auth_uri is None: - self.auth_uri = '%s://%s:%s' % (self.auth_protocol, - self.auth_host, - self.auth_port) - - # SSL - self.cert_file = self._conf_get('certfile') - self.key_file = self._conf_get('keyfile') - - #signing - self.signing_dirname = self._conf_get('signing_dir') - if self.signing_dirname is None: - self.signing_dirname = '%s/keystone-signing' % os.environ['HOME'] - LOG.info('Using %s as cache directory for signing certificate' % - self.signing_dirname) - if (os.path.exists(self.signing_dirname) and - not os.access(self.signing_dirname, os.W_OK)): - raise ConfigurationError("unable to access signing dir %s" % - self.signing_dirname) - - if not os.path.exists(self.signing_dirname): - os.makedirs(self.signing_dirname) - #will throw IOError if it cannot change permissions - os.chmod(self.signing_dirname, stat.S_IRWXU) - - val = '%s/signing_cert.pem' % self.signing_dirname - self.signing_cert_file_name = val - val = '%s/cacert.pem' % self.signing_dirname - self.ca_file_name = val - val = '%s/revoked.pem' % self.signing_dirname - self.revoked_file_name = val - - # Credentials used to verify this component with the Auth service since - # validating tokens is a privileged call - self.admin_token = self._conf_get('admin_token') - self.admin_token_expiry = None - self.admin_user = self._conf_get('admin_user') - self.admin_password = self._conf_get('admin_password') - self.admin_tenant_name = self._conf_get('admin_tenant_name') - - # Token caching via memcache - self._cache = None - self._iso8601 = None - memcache_servers = self._conf_get('memcache_servers') - # By default the token will be cached for 5 minutes - self.token_cache_time = int(self._conf_get('token_cache_time')) - self._token_revocation_list = None - self._token_revocation_list_fetched_time = None - cache_timeout = datetime.timedelta(seconds=0) - self.token_revocation_list_cache_timeout = cache_timeout - if memcache_servers: - try: - import memcache - import iso8601 - LOG.info('Using memcache for caching token') - self._cache = memcache.Client(memcache_servers.split(',')) - self._iso8601 = iso8601 - except ImportError as e: - LOG.warn('disabled caching due to missing libraries %s', e) - - def _conf_get(self, name): - # try config from paste-deploy first - if name in self.conf: - return self.conf[name] - else: - return CONF.keystone_authtoken[name] - - def __call__(self, env, start_response): - """Handle incoming request. - - Authenticate send downstream on success. Reject request if - we can't authenticate. - - """ - LOG.debug('Authenticating user token') - try: - self._remove_auth_headers(env) - user_token = self._get_user_token_from_header(env) - token_info = self._validate_user_token(user_token) - env['keystone.token_info'] = token_info - user_headers = self._build_user_headers(token_info) - self._add_headers(env, user_headers) - return self.app(env, start_response) - - except InvalidUserToken: - if self.delay_auth_decision: - LOG.info('Invalid user token - deferring reject downstream') - self._add_headers(env, {'X-Identity-Status': 'Invalid'}) - return self.app(env, start_response) - else: - LOG.info('Invalid user token - rejecting request') - return self._reject_request(env, start_response) - - except ServiceError as e: - LOG.critical('Unable to obtain admin token: %s' % e) - resp = webob.exc.HTTPServiceUnavailable() - return resp(env, start_response) - - def _remove_auth_headers(self, env): - """Remove headers so a user can't fake authentication. - - :param env: wsgi request environment - - """ - auth_headers = ( - 'X-Identity-Status', - 'X-Tenant-Id', - 'X-Tenant-Name', - 'X-User-Id', - 'X-User-Name', - 'X-Roles', - 'X-Service-Catalog', - # Deprecated - 'X-User', - 'X-Tenant', - 'X-Role', - ) - LOG.debug('Removing headers from request environment: %s' % - ','.join(auth_headers)) - self._remove_headers(env, auth_headers) - - def _get_user_token_from_header(self, env): - """Get token id from request. - - :param env: wsgi request environment - :return token id - :raises InvalidUserToken if no token is provided in request - - """ - token = self._get_header(env, 'X-Auth-Token', - self._get_header(env, 'X-Storage-Token')) - if token: - return token - else: - LOG.warn("Unable to find authentication token in headers: %s", env) - raise InvalidUserToken('Unable to find token in headers') - - def _reject_request(self, env, start_response): - """Redirect client to auth server. - - :param env: wsgi request environment - :param start_response: wsgi response callback - :returns HTTPUnauthorized http response - - """ - headers = [('WWW-Authenticate', 'Keystone uri=\'%s\'' % self.auth_uri)] - resp = webob.exc.HTTPUnauthorized('Authentication required', headers) - return resp(env, start_response) - - def get_admin_token(self): - """Return admin token, possibly fetching a new one. - - if self.admin_token_expiry is set from fetching an admin token, check - it for expiration, and request a new token is the existing token - is about to expire. - - :return admin token id - :raise ServiceError when unable to retrieve token from keystone - - """ - if self.admin_token_expiry: - if will_expire_soon(self.admin_token_expiry): - self.admin_token = None - - if not self.admin_token: - (self.admin_token, - self.admin_token_expiry) = self._request_admin_token() - - return self.admin_token - - def _get_http_connection(self): - if self.auth_protocol == 'http': - return self.http_client_class(self.auth_host, self.auth_port) - else: - return self.http_client_class(self.auth_host, - self.auth_port, - self.key_file, - self.cert_file) - - def _http_request(self, method, path): - """HTTP request helper used to make unspecified content type requests. - - :param method: http method - :param path: relative request url - :return (http response object) - :raise ServerError when unable to communicate with keystone - - """ - conn = self._get_http_connection() - - try: - conn.request(method, path) - response = conn.getresponse() - body = response.read() - except Exception as e: - LOG.error('HTTP connection exception: %s' % e) - raise ServiceError('Unable to communicate with keystone') - finally: - conn.close() - - return response, body - - def _json_request(self, method, path, body=None, additional_headers=None): - """HTTP request helper used to make json requests. - - :param method: http method - :param path: relative request url - :param body: dict to encode to json as request body. Optional. - :param additional_headers: dict of additional headers to send with - http request. Optional. - :return (http response object, response body parsed as json) - :raise ServerError when unable to communicate with keystone - - """ - conn = self._get_http_connection() - - kwargs = { - 'headers': { - 'Content-type': 'application/json', - 'Accept': 'application/json', - }, - } - - if additional_headers: - kwargs['headers'].update(additional_headers) - - if body: - kwargs['body'] = jsonutils.dumps(body) - - full_path = self.auth_admin_prefix + path - try: - conn.request(method, full_path, **kwargs) - response = conn.getresponse() - body = response.read() - except Exception as e: - LOG.error('HTTP connection exception: %s' % e) - raise ServiceError('Unable to communicate with keystone') - finally: - conn.close() - - try: - data = jsonutils.loads(body) - except ValueError: - LOG.debug('Keystone did not return json-encoded body') - data = {} - - return response, data - - def _request_admin_token(self): - """Retrieve new token as admin user from keystone. - - :return token id upon success - :raises ServerError when unable to communicate with keystone - - """ - params = { - 'auth': { - 'passwordCredentials': { - 'username': self.admin_user, - 'password': self.admin_password, - }, - 'tenantName': self.admin_tenant_name, - } - } - - response, data = self._json_request('POST', - '/v2.0/tokens', - body=params) - - try: - token = data['access']['token']['id'] - expiry = data['access']['token']['expires'] - assert token - assert expiry - datetime_expiry = timeutils.parse_isotime(expiry) - return (token, timeutils.normalize_time(datetime_expiry)) - except (AssertionError, KeyError): - LOG.warn("Unexpected response from keystone service: %s", data) - raise ServiceError('invalid json response') - except (ValueError): - LOG.warn("Unable to parse expiration time from token: %s", data) - raise ServiceError('invalid json response') - - def _validate_user_token(self, user_token, retry=True): - """Authenticate user using PKI - - :param user_token: user's token id - :param retry: Ignored, as it is not longer relevant - :return uncrypted body of the token if the token is valid - :raise InvalidUserToken if token is rejected - :no longer raises ServiceError since it no longer makes RPC - - """ - try: - token_id = cms.cms_hash_token(user_token) - cached = self._cache_get(token_id) - if cached: - return cached - if cms.is_ans1_token(user_token): - verified = self.verify_signed_token(user_token) - data = json.loads(verified) - else: - data = self.verify_uuid_token(user_token, retry) - self._cache_put(token_id, data) - return data - except Exception as e: - LOG.debug('Token validation failure.', exc_info=True) - self._cache_store_invalid(user_token) - LOG.warn("Authorization failed for token %s", user_token) - raise InvalidUserToken('Token authorization failed') - - def _build_user_headers(self, token_info): - """Convert token object into headers. - - Build headers that represent authenticated user: - * X_IDENTITY_STATUS: Confirmed or Invalid - * X_TENANT_ID: id of tenant if tenant is present - * X_TENANT_NAME: name of tenant if tenant is present - * X_USER_ID: id of user - * X_USER_NAME: name of user - * X_ROLES: list of roles - * X_SERVICE_CATALOG: service catalog - - Additional (deprecated) headers include: - * X_USER: name of user - * X_TENANT: For legacy compatibility before we had ID and Name - * X_ROLE: list of roles - - :param token_info: token object returned by keystone on authentication - :raise InvalidUserToken when unable to parse token object - - """ - user = token_info['access']['user'] - token = token_info['access']['token'] - roles = ','.join([role['name'] for role in user.get('roles', [])]) - - def get_tenant_info(): - """Returns a (tenant_id, tenant_name) tuple from context.""" - def essex(): - """Essex puts the tenant ID and name on the token.""" - return (token['tenant']['id'], token['tenant']['name']) - - def pre_diablo(): - """Pre-diablo, Keystone only provided tenantId.""" - return (token['tenantId'], token['tenantId']) - - def default_tenant(): - """Assume the user's default tenant.""" - return (user['tenantId'], user['tenantName']) - - for method in [essex, pre_diablo, default_tenant]: - try: - return method() - except KeyError: - pass - - raise InvalidUserToken('Unable to determine tenancy.') - - tenant_id, tenant_name = get_tenant_info() - - user_id = user['id'] - user_name = user['name'] - - rval = { - 'X-Identity-Status': 'Confirmed', - 'X-Tenant-Id': tenant_id, - 'X-Tenant-Name': tenant_name, - 'X-User-Id': user_id, - 'X-User-Name': user_name, - 'X-Roles': roles, - # Deprecated - 'X-User': user_name, - 'X-Tenant': tenant_name, - 'X-Role': roles, - } - - try: - catalog = token_info['access']['serviceCatalog'] - rval['X-Service-Catalog'] = jsonutils.dumps(catalog) - except KeyError: - pass - - return rval - - def _header_to_env_var(self, key): - """Convert header to wsgi env variable. - - :param key: http header name (ex. 'X-Auth-Token') - :return wsgi env variable name (ex. 'HTTP_X_AUTH_TOKEN') - - """ - return 'HTTP_%s' % key.replace('-', '_').upper() - - def _add_headers(self, env, headers): - """Add http headers to environment.""" - for (k, v) in headers.iteritems(): - env_key = self._header_to_env_var(k) - env[env_key] = v - - def _remove_headers(self, env, keys): - """Remove http headers from environment.""" - for k in keys: - env_key = self._header_to_env_var(k) - try: - del env[env_key] - except KeyError: - pass - - def _get_header(self, env, key, default=None): - """Get http header from environment.""" - env_key = self._header_to_env_var(key) - return env.get(env_key, default) - - def _cache_get(self, token): - """Return token information from cache. - - If token is invalid raise InvalidUserToken - return token only if fresh (not expired). - """ - if self._cache and token: - key = 'tokens/%s' % token - cached = self._cache.get(key) - if cached == 'invalid': - LOG.debug('Cached Token %s is marked unauthorized', token) - raise InvalidUserToken('Token authorization failed') - if cached: - data, expires = cached - if time.time() < float(expires): - LOG.debug('Returning cached token %s', token) - return data - else: - LOG.debug('Cached Token %s seems expired', token) - - def _cache_put(self, token, data): - """Put token data into the cache. - - Stores the parsed expire date in cache allowing - quick check of token freshness on retrieval. - """ - if self._cache and data: - key = 'tokens/%s' % token - if 'token' in data.get('access', {}): - timestamp = data['access']['token']['expires'] - expires = self._iso8601.parse_date(timestamp).strftime('%s') - else: - LOG.error('invalid token format') - return - LOG.debug('Storing %s token in memcache', token) - self._cache.set(key, - (data, expires), - time=self.token_cache_time) - - def _cache_store_invalid(self, token): - """Store invalid token in cache.""" - if self._cache: - key = 'tokens/%s' % token - LOG.debug('Marking token %s as unauthorized in memcache', token) - self._cache.set(key, - 'invalid', - time=self.token_cache_time) - - def cert_file_missing(self, called_proc_err, file_name): - return (called_proc_err.output.find(file_name) - and not os.path.exists(file_name)) - - def verify_uuid_token(self, user_token, retry=True): - """Authenticate user token with keystone. - - :param user_token: user's token id - :param retry: flag that forces the middleware to retry - user authentication when an indeterminate - response is received. Optional. - :return token object received from keystone on success - :raise InvalidUserToken if token is rejected - :raise ServiceError if unable to authenticate token - - """ - - headers = {'X-Auth-Token': self.get_admin_token()} - response, data = self._json_request('GET', - '/v2.0/tokens/%s' % user_token, - additional_headers=headers) - - if response.status == 200: - self._cache_put(user_token, data) - return data - if response.status == 404: - # FIXME(ja): I'm assuming the 404 status means that user_token is - # invalid - not that the admin_token is invalid - self._cache_store_invalid(user_token) - LOG.warn("Authorization failed for token %s", user_token) - raise InvalidUserToken('Token authorization failed') - if response.status == 401: - LOG.info('Keystone rejected admin token %s, resetting', headers) - self.admin_token = None - else: - LOG.error('Bad response code while validating token: %s' % - response.status) - if retry: - LOG.info('Retrying validation') - return self._validate_user_token(user_token, False) - else: - LOG.warn("Invalid user token: %s. Keystone response: %s.", - user_token, data) - - raise InvalidUserToken() - - def is_signed_token_revoked(self, signed_text): - """Indicate whether the token appears in the revocation list.""" - revocation_list = self.token_revocation_list - revoked_tokens = revocation_list.get('revoked', []) - if not revoked_tokens: - return - revoked_ids = (x['id'] for x in revoked_tokens) - token_id = utils.hash_signed_token(signed_text) - for revoked_id in revoked_ids: - if token_id == revoked_id: - LOG.debug('Token %s is marked as having been revoked', - token_id) - return True - return False - - def cms_verify(self, data): - """Verifies the signature of the provided data's IAW CMS syntax. - - If either of the certificate files are missing, fetch them and - retry. - """ - while True: - try: - output = cms.cms_verify(data, self.signing_cert_file_name, - self.ca_file_name) - except cms.subprocess.CalledProcessError as err: - if self.cert_file_missing(err, self.signing_cert_file_name): - self.fetch_signing_cert() - continue - if self.cert_file_missing(err, self.ca_file_name): - self.fetch_ca_cert() - continue - raise err - return output - - def verify_signed_token(self, signed_text): - """Check that the token is unrevoked and has a valid signature.""" - if self.is_signed_token_revoked(signed_text): - raise InvalidUserToken('Token has been revoked') - - formatted = cms.token_to_cms(signed_text) - return self.cms_verify(formatted) - - @property - def token_revocation_list_fetched_time(self): - if not self._token_revocation_list_fetched_time: - # If the fetched list has been written to disk, use its - # modification time. - if os.path.exists(self.revoked_file_name): - mtime = os.path.getmtime(self.revoked_file_name) - fetched_time = datetime.datetime.fromtimestamp(mtime) - # Otherwise the list will need to be fetched. - else: - fetched_time = datetime.datetime.min - self._token_revocation_list_fetched_time = fetched_time - return self._token_revocation_list_fetched_time - - @token_revocation_list_fetched_time.setter - def token_revocation_list_fetched_time(self, value): - self._token_revocation_list_fetched_time = value - - @property - def token_revocation_list(self): - timeout = (self.token_revocation_list_fetched_time + - self.token_revocation_list_cache_timeout) - list_is_current = timeutils.utcnow() < timeout - if list_is_current: - # Load the list from disk if required - if not self._token_revocation_list: - with open(self.revoked_file_name, 'r') as f: - self._token_revocation_list = jsonutils.loads(f.read()) - else: - self.token_revocation_list = self.fetch_revocation_list() - return self._token_revocation_list - - @token_revocation_list.setter - def token_revocation_list(self, value): - """Save a revocation list to memory and to disk. - - :param value: A json-encoded revocation list - - """ - self._token_revocation_list = jsonutils.loads(value) - self.token_revocation_list_fetched_time = timeutils.utcnow() - with open(self.revoked_file_name, 'w') as f: - f.write(value) - - def fetch_revocation_list(self, retry=True): - headers = {'X-Auth-Token': self.get_admin_token()} - response, data = self._json_request('GET', '/v2.0/tokens/revoked', - additional_headers=headers) - if response.status == 401: - if retry: - LOG.info('Keystone rejected admin token %s, resetting admin ' - 'token', headers) - self.admin_token = None - return self.fetch_revocation_list(retry=False) - if response.status != 200: - raise ServiceError('Unable to fetch token revocation list.') - if (not 'signed' in data): - raise ServiceError('Revocation list inmproperly formatted.') - return self.cms_verify(data['signed']) - - def fetch_signing_cert(self): - response, data = self._http_request('GET', - '/v2.0/certificates/signing') - try: - #todo check response - certfile = open(self.signing_cert_file_name, 'w') - certfile.write(data) - certfile.close() - except (AssertionError, KeyError): - LOG.warn("Unexpected response from keystone service: %s", data) - raise ServiceError('invalid json response') - - def fetch_ca_cert(self): - response, data = self._http_request('GET', - '/v2.0/certificates/ca') - try: - #todo check response - certfile = open(self.ca_file_name, 'w') - certfile.write(data) - certfile.close() - except (AssertionError, KeyError): - LOG.warn("Unexpected response from keystone service: %s", data) - raise ServiceError('invalid json response') - - -def filter_factory(global_conf, **local_conf): - """Returns a WSGI filter app for use with paste.deploy.""" - conf = global_conf.copy() - conf.update(local_conf) - - def auth_filter(app): - return AuthProtocol(app, conf) - return auth_filter - - -def app_factory(global_conf, **local_conf): - conf = global_conf.copy() - conf.update(local_conf) - return AuthProtocol(None, conf) diff --git a/keystoneclient/middleware/test.py b/keystoneclient/middleware/test.py deleted file mode 100644 index e5c117120..000000000 --- a/keystoneclient/middleware/test.py +++ /dev/null @@ -1,67 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# 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. - -# -# Test support for middleware authentication -# - -import os -import sys - - -ROOTDIR = os.path.dirname(os.path.abspath(os.curdir)) - - -def rootdir(*p): - return os.path.join(ROOTDIR, *p) - - -class NoModule(object): - """A mixin class to provide support for unloading/disabling modules.""" - - def __init__(self, *args, **kw): - super(NoModule, self).__init__(*args, **kw) - self._finders = [] - self._cleared_modules = {} - - def tearDown(self): - super(NoModule, self).tearDown() - for finder in self._finders: - sys.meta_path.remove(finder) - sys.modules.update(self._cleared_modules) - - def clear_module(self, module): - cleared_modules = {} - for fullname in sys.modules.keys(): - if fullname == module or fullname.startswith(module + '.'): - cleared_modules[fullname] = sys.modules.pop(fullname) - return cleared_modules - - def disable_module(self, module): - """Ensure ImportError for the specified module.""" - - # Clear 'module' references in sys.modules - self._cleared_modules.update(self.clear_module(module)) - - # Disallow further imports of 'module' - class NoModule(object): - def find_module(self, fullname, path): - if fullname == module or fullname.startswith(module + '.'): - raise ImportError - - finder = NoModule() - self._finders.append(finder) - sys.meta_path.insert(0, finder) diff --git a/keystoneclient/utils.py b/keystoneclient/utils.py index 225afe798..24431638b 100644 --- a/keystoneclient/utils.py +++ b/keystoneclient/utils.py @@ -1,5 +1,7 @@ -import uuid +import getpass import hashlib +import sys +import uuid import prettytable @@ -121,3 +123,22 @@ def hash_signed_token(signed_text): hash_ = hashlib.md5() hash_.update(signed_text) return hash_.hexdigest() + + +def prompt_for_password(): + """ + Prompt user for password if not provided so the password + doesn't show up in the bash history. + """ + if not (hasattr(sys.stdin, 'isatty') and sys.stdin.isatty()): + # nothing to do + return + + while True: + try: + new_passwd = getpass.getpass('New Password: ') + rep_passwd = getpass.getpass('Repeat New Password: ') + if new_passwd == rep_passwd: + return new_passwd + except EOFError: + return diff --git a/keystoneclient/v2_0/shell.py b/keystoneclient/v2_0/shell.py index a976134da..f5a373bc2 100755 --- a/keystoneclient/v2_0/shell.py +++ b/keystoneclient/v2_0/shell.py @@ -16,6 +16,7 @@ # under the License. import argparse +import sys from keystoneclient.v2_0 import client from keystoneclient import utils @@ -100,12 +101,18 @@ def do_user_update(kc, args): print 'Unable to update user: %s' % e -@utils.arg('--pass', metavar='', dest='passwd', required=True, +@utils.arg('--pass', metavar='', dest='passwd', required=False, help='Desired new password') @utils.arg('id', metavar='', help='User ID to update') def do_user_password_update(kc, args): """Update user password""" - kc.users.update_password(args.id, args.passwd) + user = args.id + new_passwd = args.passwd or utils.prompt_for_password() + if new_passwd is None: + msg = ("\nPlease specify password using the --pass option " + "or using the prompt") + sys.exit(msg) + kc.users.update_password(user, new_passwd) @utils.arg('id', metavar='', help='User ID to delete') diff --git a/keystoneclient/v3/__init__.py b/keystoneclient/v3/__init__.py deleted file mode 100644 index feb2536b8..000000000 --- a/keystoneclient/v3/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from keystoneclient.v3.client import Client diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py deleted file mode 100644 index 51672a738..000000000 --- a/keystoneclient/v3/client.py +++ /dev/null @@ -1,85 +0,0 @@ -# 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 json -import logging - -from keystoneclient.v2_0 import client -from keystoneclient.v3 import credentials -from keystoneclient.v3 import endpoints -from keystoneclient.v3 import domains -from keystoneclient.v3 import policies -from keystoneclient.v3 import projects -from keystoneclient.v3 import roles -from keystoneclient.v3 import services -from keystoneclient.v3 import users - - -_logger = logging.getLogger(__name__) - - -class Client(client.Client): - """Client for the OpenStack Identity API v3. - - :param string username: Username for authentication. (optional) - :param string password: Password for authentication. (optional) - :param string token: Token for authentication. (optional) - :param string tenant_name: Tenant id. (optional) - :param string tenant_id: 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) - - Example:: - - >>> from keystoneclient.v3 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() - - """ - - def __init__(self, endpoint=None, **kwargs): - """ Initialize a new client for the Keystone v2.0 API. """ - super(Client, self).__init__(endpoint=endpoint, **kwargs) - - self.credentials = credentials.CredentialManager(self) - self.endpoints = endpoints.EndpointManager(self) - self.domains = domains.DomainManager(self) - self.policies = policies.PolicyManager(self) - self.projects = projects.ProjectManager(self) - self.roles = roles.RoleManager(self) - self.services = services.ServiceManager(self) - self.users = users.UserManager(self) - - # NOTE(gabriel): If we have a pre-defined endpoint then we can - # get away with lazy auth. Otherwise auth immediately. - if endpoint: - self.management_url = endpoint - else: - self.authenticate() - - def serialize(self, entity): - return json.dumps(entity, sort_keys=True) diff --git a/keystoneclient/v3/credentials.py b/keystoneclient/v3/credentials.py deleted file mode 100644 index 264c367e7..000000000 --- a/keystoneclient/v3/credentials.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# 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. - -from keystoneclient import base - - -class Credential(base.Resource): - """Represents an Identity credential. - - Attributes: - * id: a uuid that identifies the credential - - """ - pass - - -class CredentialManager(base.CrudManager): - """Manager class for manipulating Identity credentials.""" - resource_class = Credential - collection_key = 'credentials' - key = 'credential' - - def create(self, user, type, data, project=None): - return super(CredentialManager, self).create( - user_id=base.getid(user), - type=type, - data=data, - project_id=base.getid(project)) - - def get(self, credential): - return super(CredentialManager, self).get( - credential_id=base.getid(credential)) - - def update(self, credential, user, type=None, data=None, project=None): - return super(CredentialManager, self).update( - credential_id=base.getid(credential), - user_id=base.getid(user), - type=type, - data=data, - project_id=base.getid(project)) - - def delete(self, credential): - return super(CredentialManager, self).delete( - credential_id=base.getid(credential)) diff --git a/keystoneclient/v3/domains.py b/keystoneclient/v3/domains.py deleted file mode 100644 index 2d27db225..000000000 --- a/keystoneclient/v3/domains.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# 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. - -from keystoneclient import base - - -class Domain(base.Resource): - """Represents an Identity domain. - - Attributes: - * id: a uuid that identifies the domain - - """ - pass - - -class DomainManager(base.CrudManager): - """Manager class for manipulating Identity domains.""" - resource_class = Domain - collection_key = 'domains' - key = 'domain' - - def create(self, name, description=None, enabled=True): - return super(DomainManager, self).create( - name=name, - description=description, - enabled=enabled) - - def get(self, domain): - return super(DomainManager, self).get( - domain_id=base.getid(domain)) - - def update(self, domain, name=None, description=None, enabled=None): - return super(DomainManager, self).update( - domain_id=base.getid(domain), - name=name, - description=description, - enabled=enabled) - - def delete(self, domain): - return super(DomainManager, self).delete( - domain_id=base.getid(domain)) diff --git a/keystoneclient/v3/endpoints.py b/keystoneclient/v3/endpoints.py deleted file mode 100644 index c13313d83..000000000 --- a/keystoneclient/v3/endpoints.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# 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. - -from keystoneclient import base - - -VALID_INTERFACES = ['public', 'admin', 'internal'] - - -class Endpoint(base.Resource): - """Represents an Identity endpoint. - - Attributes: - * id: a uuid that identifies the endpoint - * interface: 'public', 'admin' or 'internal' network interface - * region: geographic location of the endpoint - * service_id: service to which the endpoint belongs - * url: fully qualified service endpoint - * enabled: determines whether the endpoint appears in the catalog - - """ - pass - - -class EndpointManager(base.CrudManager): - """Manager class for manipulating Identity endpoints.""" - resource_class = Endpoint - collection_key = 'endpoints' - key = 'endpoint' - - def _validate_interface(self, interface): - if interface is not None and interface not in VALID_INTERFACES: - msg = '"interface" must be one of: %s' - msg = msg % ', '.join(VALID_INTERFACES) - raise Exception(msg) - - def create(self, service, url, name=None, interface=None, region=None, - enabled=True): - self._validate_interface(interface) - return super(EndpointManager, self).create( - service_id=base.getid(service), - interface=interface, - url=url, - region=region, - enabled=enabled) - - def get(self, endpoint): - return super(EndpointManager, self).get( - endpoint_id=base.getid(endpoint)) - - def list(self, service=None, name=None, interface=None, region=None, - enabled=None): - self._validate_interface(interface) - return super(EndpointManager, self).list( - service_id=base.getid(service), - interface=interface, - region=region, - enabled=enabled) - - def update(self, endpoint, service=None, url=None, name=None, - interface=None, region=None, enabled=None): - self._validate_interface(interface) - return super(EndpointManager, self).update( - endpoint_id=base.getid(endpoint), - service_id=base.getid(service), - interface=interface, - url=url, - region=region, - enabled=enabled) - - def delete(self, endpoint): - return super(EndpointManager, self).delete( - endpoint_id=base.getid(endpoint)) diff --git a/keystoneclient/v3/policies.py b/keystoneclient/v3/policies.py deleted file mode 100644 index 6f3f6a872..000000000 --- a/keystoneclient/v3/policies.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# 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. - -from keystoneclient import base - - -class Policy(base.Resource): - """Represents an Identity policy. - - Attributes: - * id: a uuid that identifies the policy - * endpoint_id: references the endpoint the policy applies to - * blob: a policy document (blob) - * type: the mime type of the policy blob - - """ - def update(self, endpoint=None, blob=None, type=None): - kwargs = { - 'endpoint_id': (base.getid(endpoint) - if endpoint is not None - else self.endpoint_id), - 'blob': blob if blob is not None else self.blob, - 'type': type if type is not None else self.type, - } - - try: - retval = self.manager.update(self.id, **kwargs) - self = retval - except Exception: - retval = None - - return retval - - -class PolicyManager(base.CrudManager): - """Manager class for manipulating Identity policies.""" - resource_class = Policy - collection_key = 'policies' - key = 'policy' - - def create(self, endpoint, blob, type='application/json'): - return super(PolicyManager, self).create( - endpoint_id=base.getid(endpoint), - blob=blob, - type=type) - - def get(self, policy): - return super(PolicyManager, self).get( - policy_id=base.getid(policy)) - - def list(self, endpoint=None): - return super(PolicyManager, self).list( - endpoint_id=base.getid(endpoint)) - - def update(self, entity, endpoint=None, blob=None, type=None): - return super(PolicyManager, self).update( - policy_id=base.getid(entity), - endpoint_id=base.getid(endpoint), - blob=blob, - type=type) - - def delete(self, policy): - return super(PolicyManager, self).delete( - policy_id=base.getid(policy)) diff --git a/keystoneclient/v3/projects.py b/keystoneclient/v3/projects.py deleted file mode 100644 index bcbc4fd57..000000000 --- a/keystoneclient/v3/projects.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# 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. - -from keystoneclient import base - - -class Project(base.Resource): - """Represents an Identity project. - - Attributes: - * id: a uuid that identifies the project - * name: project name - * description: project description - * enabled: boolean to indicate if project is enabled - - """ - def update(self, name=None, description=None, enabled=None): - kwargs = { - 'name': name if name is not None else self.name, - 'description': (description - if description is not None - else self.description), - 'enabled': enabled if enabled is not None else self.enabled, - } - - try: - retval = self.manager.update(self.id, **kwargs) - self = retval - except Exception: - retval = None - - return retval - - -class ProjectManager(base.CrudManager): - """Manager class for manipulating Identity projects.""" - resource_class = Project - collection_key = 'projects' - key = 'project' - - def create(self, name, domain, description=None, enabled=True): - return super(ProjectManager, self).create( - domain_id=base.getid(domain), - name=name, - description=description, - enabled=enabled) - - def list(self, domain=None, user=None): - base_url = '/users/%s' % base.getid(user) if user else None - return super(ProjectManager, self).list( - base_url=base_url, - domain_id=base.getid(domain)) - - def get(self, project): - return super(ProjectManager, self).get( - project_id=base.getid(project)) - - def update(self, project, name=None, domain=None, description=None, - enabled=None): - return super(ProjectManager, self).update( - project_id=base.getid(project), - domain_id=base.getid(domain), - name=name, - description=description, - enabled=enabled) - - def delete(self, project): - return super(ProjectManager, self).delete( - project_id=base.getid(project)) diff --git a/keystoneclient/v3/roles.py b/keystoneclient/v3/roles.py deleted file mode 100644 index 690602575..000000000 --- a/keystoneclient/v3/roles.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# 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. - -from keystoneclient import base -from keystoneclient import exceptions - - -class Role(base.Resource): - """Represents an Identity role. - - Attributes: - * id: a uuid that identifies the role - * name: user-facing identifier - - """ - pass - - -class RoleManager(base.CrudManager): - """Manager class for manipulating Identity roles.""" - resource_class = Role - collection_key = 'roles' - key = 'role' - - def _role_grants_base_url(self, user, domain, project): - params = {'user_id': base.getid(user)} - - if domain: - params['domain_id'] = base.getid(domain) - base_url = '/domains/%(domain_id)s/users/%(user_id)s' - elif project: - params['project_id'] = base.getid(project) - base_url = '/projects/%(project_id)s/users/%(user_id)s' - - return base_url % params - - def _require_domain_or_project(self, domain, project): - if (domain and project) or (not domain and not project): - msg = 'Specify either a domain or project, not both' - raise exceptions.ValidationError(msg) - - def create(self, name): - return super(RoleManager, self).create( - name=name) - - def get(self, role): - return super(RoleManager, self).get( - role_id=base.getid(role)) - - def list(self, user=None, domain=None, project=None): - """Lists roles and role grants. - - If no arguments are provided, all roles in the system will be listed. - - If a user is specified, you must also specify either a domain or - project to list role grants on that pair. - """ - - if user: - self._require_domain_or_project(domain, project) - return super(RoleManager, self).list( - base_url=self._role_grants_base_url(user, domain, project)) - - return super(RoleManager, self).list() - - def update(self, role, name=None): - return super(RoleManager, self).update( - role_id=base.getid(role), - name=name) - - def delete(self, role): - return super(RoleManager, self).delete( - role_id=base.getid(role)) - - def grant(self, role, user, domain=None, project=None): - """Grants a role to a user on either a domain or project.""" - self._require_domain_or_project(domain, project) - - return super(RoleManager, self).put( - base_url=self._role_grants_base_url(user, domain, project), - role_id=base.getid(role)) - - def check(self, role, user, domain=None, project=None): - """Grants a role to a user on either a domain or project.""" - self._require_domain_or_project(domain, project) - - return super(RoleManager, self).head( - base_url=self._role_grants_base_url(user, domain, project), - role_id=base.getid(role)) - - def revoke(self, role, user, domain=None, project=None): - """Revokes a role from a user on either a domain or project.""" - self._require_domain_or_project(domain, project) - - return super(RoleManager, self).delete( - base_url=self._role_grants_base_url(user, domain, project), - role_id=base.getid(role)) diff --git a/keystoneclient/v3/services.py b/keystoneclient/v3/services.py deleted file mode 100644 index d2134d43d..000000000 --- a/keystoneclient/v3/services.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# 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. - -from keystoneclient import base - - -class Service(base.Resource): - """Represents an Identity service. - - Attributes: - * id: a uuid that identifies the service - * name: user-facing name of the service (e.g. Keystone) - * type: 'compute', 'identity', etc - * enabled: determines whether the service appears in the catalog - - """ - pass - - -class ServiceManager(base.CrudManager): - """Manager class for manipulating Identity services.""" - resource_class = Service - collection_key = 'services' - key = 'service' - - def create(self, name, type, enabled=True, **kwargs): - return super(ServiceManager, self).create( - name=name, - type=type, - enabled=enabled, - **kwargs) - - def get(self, service): - return super(ServiceManager, self).get( - service_id=base.getid(service)) - - def update(self, service, name=None, type=None, enabled=None, **kwargs): - return super(ServiceManager, self).update( - service_id=base.getid(service), - name=name, - type=type, - enabled=enabled, - **kwargs) - - def delete(self, service): - return super(ServiceManager, self).delete( - service_id=base.getid(service)) diff --git a/keystoneclient/v3/users.py b/keystoneclient/v3/users.py deleted file mode 100644 index a8c1c4b38..000000000 --- a/keystoneclient/v3/users.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# 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. - -from keystoneclient import base - - -class User(base.Resource): - """Represents an Identity user. - - Attributes: - * id: a uuid that identifies the user - - """ - pass - - -class UserManager(base.CrudManager): - """Manager class for manipulating Identity users.""" - resource_class = User - collection_key = 'users' - key = 'user' - - def create(self, name, domain=None, project=None, password=None, - email=None, description=None, enabled=True): - return super(UserManager, self).create( - name=name, - domain_id=base.getid(domain), - project_id=base.getid(project), - password=password, - email=email, - description=description, - enabled=enabled) - - def list(self, project=None, domain=None): - return super(UserManager, self).list( - domain_id=base.getid(domain), - project_id=base.getid(project)) - - def get(self, user): - return super(UserManager, self).get( - user_id=base.getid(user)) - - def update(self, user, name=None, domain=None, project=None, password=None, - email=None, description=None, enabled=None): - return super(UserManager, self).update( - user_id=base.getid(user), - name=name, - domain_id=base.getid(domain), - project_id=base.getid(project), - password=password, - email=email, - description=description, - enabled=enabled) - - def delete(self, user): - return super(UserManager, self).delete( - user_id=base.getid(user)) diff --git a/tests/v3/__init__.py b/tests/v3/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/v3/test_credentials.py b/tests/v3/test_credentials.py deleted file mode 100644 index 180f680dd..000000000 --- a/tests/v3/test_credentials.py +++ /dev/null @@ -1,22 +0,0 @@ -import uuid - -from keystoneclient.v3 import credentials -from tests.v3 import utils - - -class CredentialTests(utils.TestCase, utils.CrudTests): - def setUp(self): - super(CredentialTests, self).setUp() - self.additionalSetUp() - self.key = 'credential' - self.collection_key = 'credentials' - self.model = credentials.Credential - self.manager = self.client.credentials - - def new_ref(self, **kwargs): - kwargs = super(CredentialTests, self).new_ref(**kwargs) - kwargs.setdefault('data', uuid.uuid4().hex) - kwargs.setdefault('project_id', uuid.uuid4().hex) - kwargs.setdefault('type', uuid.uuid4().hex) - kwargs.setdefault('user_id', uuid.uuid4().hex) - return kwargs diff --git a/tests/v3/test_domains.py b/tests/v3/test_domains.py deleted file mode 100644 index 8cf0ea075..000000000 --- a/tests/v3/test_domains.py +++ /dev/null @@ -1,20 +0,0 @@ -import uuid - -from keystoneclient.v3 import domains -from tests.v3 import utils - - -class DomainTests(utils.TestCase, utils.CrudTests): - def setUp(self): - super(DomainTests, self).setUp() - self.additionalSetUp() - self.key = 'domain' - self.collection_key = 'domains' - self.model = domains.Domain - self.manager = self.client.domains - - def new_ref(self, **kwargs): - kwargs = super(DomainTests, self).new_ref(**kwargs) - kwargs.setdefault('enabled', True) - kwargs.setdefault('name', uuid.uuid4().hex) - return kwargs diff --git a/tests/v3/test_endpoints.py b/tests/v3/test_endpoints.py deleted file mode 100644 index d428b9148..000000000 --- a/tests/v3/test_endpoints.py +++ /dev/null @@ -1,78 +0,0 @@ -import uuid - -from keystoneclient.v3 import endpoints -from tests.v3 import utils - - -class EndpointTests(utils.TestCase, utils.CrudTests): - def setUp(self): - super(EndpointTests, self).setUp() - self.additionalSetUp() - self.key = 'endpoint' - self.collection_key = 'endpoints' - self.model = endpoints.Endpoint - self.manager = self.client.endpoints - - def new_ref(self, **kwargs): - kwargs = super(EndpointTests, self).new_ref(**kwargs) - kwargs.setdefault('interface', 'public') - kwargs.setdefault('region', uuid.uuid4().hex) - kwargs.setdefault('service_id', uuid.uuid4().hex) - kwargs.setdefault('url', uuid.uuid4().hex) - kwargs.setdefault('enabled', True) - return kwargs - - def test_create_public_interface(self): - ref = self.new_ref(interface='public') - self.test_create(ref) - - def test_create_admin_interface(self): - ref = self.new_ref(interface='admin') - self.test_create(ref) - - def test_create_internal_interface(self): - ref = self.new_ref(interface='internal') - self.test_create(ref) - - def test_create_invalid_interface(self): - ref = self.new_ref(interface=uuid.uuid4().hex) - with self.assertRaises(Exception): - self.manager.create(**utils.parameterize(ref)) - - def test_update_public_interface(self): - ref = self.new_ref(interface='public') - self.test_update(ref) - - def test_update_admin_interface(self): - ref = self.new_ref(interface='admin') - self.test_update(ref) - - def test_update_internal_interface(self): - ref = self.new_ref(interface='internal') - self.test_update(ref) - - def test_update_invalid_interface(self): - ref = self.new_ref(interface=uuid.uuid4().hex) - with self.assertRaises(Exception): - self.manager.update(**utils.parameterize(ref)) - - def test_list_public_interface(self): - interface = 'public' - expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) - self.test_list(expected_path=expected_path, interface=interface) - - def test_list_admin_interface(self): - interface = 'admin' - expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) - self.test_list(expected_path=expected_path, interface=interface) - - def test_list_internal_interface(self): - interface = 'admin' - expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) - self.test_list(expected_path=expected_path, interface=interface) - - def test_list_invalid_interface(self): - interface = uuid.uuid4().hex - expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface) - with self.assertRaises(Exception): - self.manager.list(expected_path=expected_path, interface=interface) diff --git a/tests/v3/test_policies.py b/tests/v3/test_policies.py deleted file mode 100644 index fd3c74ee7..000000000 --- a/tests/v3/test_policies.py +++ /dev/null @@ -1,21 +0,0 @@ -import uuid - -from keystoneclient.v3 import policies -from tests.v3 import utils - - -class PolicyTests(utils.TestCase, utils.CrudTests): - def setUp(self): - super(PolicyTests, self).setUp() - self.additionalSetUp() - self.key = 'policy' - self.collection_key = 'policies' - self.model = policies.Policy - self.manager = self.client.policies - - def new_ref(self, **kwargs): - kwargs = super(PolicyTests, self).new_ref(**kwargs) - kwargs.setdefault('endpoint_id', uuid.uuid4().hex) - kwargs.setdefault('type', uuid.uuid4().hex) - kwargs.setdefault('blob', uuid.uuid4().hex) - return kwargs diff --git a/tests/v3/test_projects.py b/tests/v3/test_projects.py deleted file mode 100644 index 8a4ef4090..000000000 --- a/tests/v3/test_projects.py +++ /dev/null @@ -1,69 +0,0 @@ -import httplib2 -import urlparse -import uuid - -from keystoneclient.v3 import projects -from tests.v3 import utils - - -class ProjectTests(utils.TestCase, utils.CrudTests): - def setUp(self): - super(ProjectTests, self).setUp() - self.additionalSetUp() - self.key = 'project' - self.collection_key = 'projects' - self.model = projects.Project - self.manager = self.client.projects - - def new_ref(self, **kwargs): - kwargs = super(ProjectTests, self).new_ref(**kwargs) - kwargs.setdefault('domain_id', uuid.uuid4().hex) - kwargs.setdefault('enabled', True) - kwargs.setdefault('name', uuid.uuid4().hex) - return kwargs - - def test_list_projects_for_user(self): - ref_list = [self.new_ref(), self.new_ref()] - - user_id = uuid.uuid4().hex - resp = httplib2.Response({ - 'status': 200, - 'body': self.serialize(ref_list), - }) - - method = 'GET' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/users/%s/%s' % (user_id, self.collection_key)), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - returned_list = self.manager.list(user=user_id) - self.assertTrue(len(returned_list)) - [self.assertTrue(isinstance(r, self.model)) for r in returned_list] - - def test_list_projects_for_domain(self): - ref_list = [self.new_ref(), self.new_ref()] - - domain_id = uuid.uuid4().hex - resp = httplib2.Response({ - 'status': 200, - 'body': self.serialize(ref_list), - }) - - method = 'GET' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/%s?domain_id=%s' % (self.collection_key, domain_id)), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - returned_list = self.manager.list(domain=domain_id) - self.assertTrue(len(returned_list)) - [self.assertTrue(isinstance(r, self.model)) for r in returned_list] diff --git a/tests/v3/test_roles.py b/tests/v3/test_roles.py deleted file mode 100644 index e3fe35314..000000000 --- a/tests/v3/test_roles.py +++ /dev/null @@ -1,252 +0,0 @@ -import httplib2 -import urlparse -import uuid - -from keystoneclient import exceptions -from keystoneclient.v3 import roles -from tests.v3 import utils - - -class RoleTests(utils.TestCase, utils.CrudTests): - def setUp(self): - super(RoleTests, self).setUp() - self.additionalSetUp() - self.key = 'role' - self.collection_key = 'roles' - self.model = roles.Role - self.manager = self.client.roles - - def new_ref(self, **kwargs): - kwargs = super(RoleTests, self).new_ref(**kwargs) - kwargs.setdefault('name', uuid.uuid4().hex) - return kwargs - - def test_domain_role_grant(self): - user_id = uuid.uuid4().hex - domain_id = uuid.uuid4().hex - ref = self.new_ref() - resp = httplib2.Response({ - 'status': 201, - 'body': '', - }) - - method = 'PUT' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/domains/%s/users/%s/%s/%s' % ( - domain_id, user_id, self.collection_key, ref['id'])), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - self.manager.grant(role=ref['id'], domain=domain_id, user=user_id) - - def test_domain_role_list(self): - user_id = uuid.uuid4().hex - domain_id = uuid.uuid4().hex - ref_list = [self.new_ref(), self.new_ref()] - resp = httplib2.Response({ - 'status': 200, - 'body': self.serialize(ref_list), - }) - - method = 'GET' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/domains/%s/users/%s/%s' % ( - domain_id, user_id, self.collection_key)), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - self.manager.list(domain=domain_id, user=user_id) - - def test_domain_role_check(self): - user_id = uuid.uuid4().hex - domain_id = uuid.uuid4().hex - ref = self.new_ref() - resp = httplib2.Response({ - 'status': 200, - 'body': '', - }) - - method = 'HEAD' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/domains/%s/users/%s/%s/%s' % ( - domain_id, user_id, self.collection_key, ref['id'])), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - self.manager.check(role=ref['id'], domain=domain_id, user=user_id) - - def test_domain_role_revoke(self): - user_id = uuid.uuid4().hex - domain_id = uuid.uuid4().hex - ref = self.new_ref() - resp = httplib2.Response({ - 'status': 204, - 'body': '', - }) - - method = 'DELETE' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/domains/%s/users/%s/%s/%s' % ( - domain_id, user_id, self.collection_key, ref['id'])), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - self.manager.revoke(role=ref['id'], domain=domain_id, user=user_id) - - def test_project_role_grant(self): - user_id = uuid.uuid4().hex - project_id = uuid.uuid4().hex - ref = self.new_ref() - resp = httplib2.Response({ - 'status': 201, - 'body': '', - }) - - method = 'PUT' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/projects/%s/users/%s/%s/%s' % ( - project_id, user_id, self.collection_key, ref['id'])), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - self.manager.grant(role=ref['id'], project=project_id, user=user_id) - - def test_project_role_list(self): - user_id = uuid.uuid4().hex - project_id = uuid.uuid4().hex - ref_list = [self.new_ref(), self.new_ref()] - resp = httplib2.Response({ - 'status': 200, - 'body': self.serialize(ref_list), - }) - - method = 'GET' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/projects/%s/users/%s/%s' % ( - project_id, user_id, self.collection_key)), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - self.manager.list(project=project_id, user=user_id) - - def test_project_role_check(self): - user_id = uuid.uuid4().hex - project_id = uuid.uuid4().hex - ref = self.new_ref() - resp = httplib2.Response({ - 'status': 200, - 'body': '', - }) - - method = 'HEAD' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/projects/%s/users/%s/%s/%s' % ( - project_id, user_id, self.collection_key, ref['id'])), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - self.manager.check(role=ref['id'], project=project_id, user=user_id) - - def test_project_role_revoke(self): - user_id = uuid.uuid4().hex - project_id = uuid.uuid4().hex - ref = self.new_ref() - resp = httplib2.Response({ - 'status': 204, - 'body': '', - }) - - method = 'DELETE' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/projects/%s/users/%s/%s/%s' % ( - project_id, user_id, self.collection_key, ref['id'])), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - self.manager.revoke(role=ref['id'], project=project_id, user=user_id) - - def test_domain_project_role_grant_fails(self): - user_id = uuid.uuid4().hex - project_id = uuid.uuid4().hex - domain_id = uuid.uuid4().hex - ref = self.new_ref() - - self.assertRaises( - exceptions.ValidationError, - self.manager.grant, - role=ref['id'], - domain=domain_id, - project=project_id, - user=user_id) - - def test_domain_project_role_list_fails(self): - user_id = uuid.uuid4().hex - project_id = uuid.uuid4().hex - domain_id = uuid.uuid4().hex - - self.assertRaises( - exceptions.ValidationError, - self.manager.list, - domain=domain_id, - project=project_id, - user=user_id) - - def test_domain_project_role_check_fails(self): - user_id = uuid.uuid4().hex - project_id = uuid.uuid4().hex - domain_id = uuid.uuid4().hex - ref = self.new_ref() - - self.assertRaises( - exceptions.ValidationError, - self.manager.check, - role=ref['id'], - domain=domain_id, - project=project_id, - user=user_id) - - def test_domain_project_role_revoke_fails(self): - user_id = uuid.uuid4().hex - project_id = uuid.uuid4().hex - domain_id = uuid.uuid4().hex - ref = self.new_ref() - - self.assertRaises( - exceptions.ValidationError, - self.manager.revoke, - role=ref['id'], - domain=domain_id, - project=project_id, - user=user_id) diff --git a/tests/v3/test_services.py b/tests/v3/test_services.py deleted file mode 100644 index 545e84e1a..000000000 --- a/tests/v3/test_services.py +++ /dev/null @@ -1,21 +0,0 @@ -import uuid - -from keystoneclient.v3 import services -from tests.v3 import utils - - -class ServiceTests(utils.TestCase, utils.CrudTests): - def setUp(self): - super(ServiceTests, self).setUp() - self.additionalSetUp() - self.key = 'service' - self.collection_key = 'services' - self.model = services.Service - self.manager = self.client.services - - def new_ref(self, **kwargs): - kwargs = super(ServiceTests, self).new_ref(**kwargs) - kwargs.setdefault('name', uuid.uuid4().hex) - kwargs.setdefault('type', uuid.uuid4().hex) - kwargs.setdefault('enabled', True) - return kwargs diff --git a/tests/v3/test_users.py b/tests/v3/test_users.py deleted file mode 100644 index ee9f9d875..000000000 --- a/tests/v3/test_users.py +++ /dev/null @@ -1,23 +0,0 @@ -import uuid - -from keystoneclient.v3 import users -from tests.v3 import utils - - -class UserTests(utils.TestCase, utils.CrudTests): - def setUp(self): - super(UserTests, self).setUp() - self.additionalSetUp() - self.key = 'user' - self.collection_key = 'users' - self.model = users.User - self.manager = self.client.users - - def new_ref(self, **kwargs): - kwargs = super(UserTests, self).new_ref(**kwargs) - kwargs.setdefault('description', uuid.uuid4().hex) - kwargs.setdefault('domain_id', uuid.uuid4().hex) - kwargs.setdefault('enabled', True) - kwargs.setdefault('name', uuid.uuid4().hex) - kwargs.setdefault('project_id', uuid.uuid4().hex) - return kwargs diff --git a/tests/v3/utils.py b/tests/v3/utils.py deleted file mode 100644 index d45a07cba..000000000 --- a/tests/v3/utils.py +++ /dev/null @@ -1,227 +0,0 @@ -import json -import uuid -import time -import urlparse - -import httplib2 -import mox -import unittest2 as unittest - -from keystoneclient.v3 import client - - -def parameterize(ref): - """Rewrites attributes to match the kwarg naming convention in client. - - >>> paramterize({'project_id': 0}) - {'project': 0} - - """ - params = ref.copy() - for key in ref: - if key[-3:] == '_id': - params.setdefault(key[:-3], params.pop(key)) - return params - - -class TestCase(unittest.TestCase): - TEST_TENANT_NAME = 'aTenant' - TEST_TOKEN = 'aToken' - TEST_USER = 'test' - TEST_ROOT_URL = 'http://127.0.0.1:5000/' - TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3') - TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' - TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v3') - - def setUp(self): - super(TestCase, self).setUp() - self.mox = mox.Mox() - self._original_time = time.time - time.time = lambda: 1234 - httplib2.Http.request = self.mox.CreateMockAnything() - self.client = client.Client(username=self.TEST_USER, - token=self.TEST_TOKEN, - tenant_name=self.TEST_TENANT_NAME, - auth_url=self.TEST_URL, - endpoint=self.TEST_URL) - - def tearDown(self): - time.time = self._original_time - super(TestCase, self).tearDown() - self.mox.UnsetStubs() - self.mox.VerifyAll() - - -class UnauthenticatedTestCase(unittest.TestCase): - """ Class used as base for unauthenticated calls """ - TEST_ROOT_URL = 'http://127.0.0.1:5000/' - TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3') - TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' - TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v3') - - def setUp(self): - super(UnauthenticatedTestCase, self).setUp() - self.mox = mox.Mox() - self._original_time = time.time - time.time = lambda: 1234 - httplib2.Http.request = self.mox.CreateMockAnything() - - def tearDown(self): - time.time = self._original_time - super(UnauthenticatedTestCase, self).tearDown() - self.mox.UnsetStubs() - self.mox.VerifyAll() - - -class CrudTests(object): - key = None - collection_key = None - model = None - manager = None - - def new_ref(self, **kwargs): - kwargs.setdefault('id', uuid.uuid4().hex) - return kwargs - - def additionalSetUp(self): - self.headers = { - 'GET': { - 'X-Auth-Token': 'aToken', - 'User-Agent': 'python-keystoneclient', - } - } - - self.headers['HEAD'] = self.headers['GET'].copy() - self.headers['DELETE'] = self.headers['GET'].copy() - self.headers['PUT'] = self.headers['GET'].copy() - self.headers['POST'] = self.headers['GET'].copy() - self.headers['POST']['Content-Type'] = 'application/json' - self.headers['PATCH'] = self.headers['POST'].copy() - - def serialize(self, entity): - if isinstance(entity, dict): - return json.dumps({self.key: entity}, sort_keys=True) - if isinstance(entity, list): - return json.dumps({self.collection_key: entity}, sort_keys=True) - raise NotImplementedError('Are you sure you want to serialize that?') - - def test_create(self, ref=None): - ref = ref or self.new_ref() - resp = httplib2.Response({ - 'status': 201, - 'body': self.serialize(ref), - }) - - method = 'POST' - req_ref = ref.copy() - req_ref.pop('id') - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/%s' % self.collection_key), - method, - body=self.serialize(req_ref), - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - returned = self.manager.create(**parameterize(req_ref)) - self.assertTrue(isinstance(returned, self.model)) - for attr in ref: - self.assertEqual( - getattr(returned, attr), - ref[attr], - 'Expected different %s' % attr) - - def test_get(self, ref=None): - ref = ref or self.new_ref() - resp = httplib2.Response({ - 'status': 200, - 'body': self.serialize(ref), - }) - method = 'GET' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/%s/%s' % (self.collection_key, ref['id'])), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - returned = self.manager.get(ref['id']) - self.assertTrue(isinstance(returned, self.model)) - for attr in ref: - self.assertEqual( - getattr(returned, attr), - ref[attr], - 'Expected different %s' % attr) - - def test_list(self, ref_list=None, expected_path=None, **filter_kwargs): - ref_list = ref_list or [self.new_ref(), self.new_ref()] - - resp = httplib2.Response({ - 'status': 200, - 'body': self.serialize(ref_list), - }) - - method = 'GET' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - expected_path or 'v3/%s' % self.collection_key), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - returned_list = self.manager.list(**filter_kwargs) - self.assertTrue(len(returned_list)) - [self.assertTrue(isinstance(r, self.model)) for r in returned_list] - - def test_update(self, ref=None): - ref = ref or self.new_ref() - req_ref = ref.copy() - del req_ref['id'] - - resp = httplib2.Response({ - 'status': 200, - 'body': self.serialize(ref), - }) - - method = 'PATCH' - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/%s/%s' % (self.collection_key, ref['id'])), - method, - body=self.serialize(req_ref), - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - returned = self.manager.update(ref['id'], **parameterize(req_ref)) - self.assertTrue(isinstance(returned, self.model)) - for attr in ref: - self.assertEqual( - getattr(returned, attr), - ref[attr], - 'Expected different %s' % attr) - - def test_delete(self, ref=None): - ref = ref or self.new_ref() - method = 'DELETE' - resp = httplib2.Response({ - 'status': 204, - 'body': '', - }) - httplib2.Http.request( - urlparse.urljoin( - self.TEST_URL, - 'v3/%s/%s' % (self.collection_key, ref['id'])), - method, - headers=self.headers[method]) \ - .AndReturn((resp, resp['body'])) - self.mox.ReplayAll() - - self.manager.delete(ref['id'])