From ec82e9b1c9bc4da3631454b2077bbcec42003623 Mon Sep 17 00:00:00 2001 From: termie Date: Mon, 9 Jan 2012 15:03:29 -0800 Subject: keystone_compat -> service --- keystone/client.py | 101 ----- keystone/keystone_compat.py | 860 -------------------------------------- keystone/service.py | 859 +++++++++++++++++++++++++++++++++++++ run_tests.sh | 2 +- tests/default.conf | 12 +- tests/keystone_compat_diablo.conf | 14 +- tests/test_identity_api.py | 162 ------- 7 files changed, 870 insertions(+), 1140 deletions(-) delete mode 100644 keystone/client.py delete mode 100644 keystone/keystone_compat.py create mode 100644 keystone/service.py delete mode 100644 tests/test_identity_api.py diff --git a/keystone/client.py b/keystone/client.py deleted file mode 100644 index 899e8d16..00000000 --- a/keystone/client.py +++ /dev/null @@ -1,101 +0,0 @@ - -"""Client library for KeystoneLight API.""" - -import json - -import httplib2 -import webob - -from keystone import service -from keystone import wsgi - - -URLMAP = service.URLMAP - - -class Client(object): - def __init__(self, token=None): - self.token = token - - def request(self, method, path, headers, body): - raise NotImplemented - - def get(self, path, headers=None): - return self.request('GET', path=path, headers=headers) - - def post(self, path, headers=None, body=None): - return self.request('POST', path=path, headers=headers, body=body) - - def put(self, path, headers=None, body=None): - return self.request('PUT', path=path, headers=headers, body=body) - - def _build_headers(self, headers=None): - if headers is None: - headers = {} - - if self.token: - headers.setdefault('X-Auth-Token', self.token) - - return headers - - def __getattr__(self, key): - """Lazy way to define a bunch of dynamic urls based on URLMAP. - - Turns something like - - c.authenticate(user_id='foo', password='bar') - - into - - c.request('POST', '/token', body={'user_id': 'foo', 'password': 'bar'}) - - """ - if key not in URLMAP: - raise AttributeError(key) - - method, path = URLMAP[key] - - def _internal(method_=method, path_=path, **kw): - path_ = path_ % kw - params = {'method': method_, - 'path': path_} - if method.lower() in ('put', 'post'): - params['body'] = kw - return self.request(**params) - - setattr(self, key, _internal) - - return getattr(self, key) - - -class HttpClient(Client): - def __init__(self, endpoint=None, token=None): - self.endpoint = endpoint - super(HttpClient, self).__init__(token=token) - - def request(self, method, path, headers=None, body=None): - if type(body) is type({}): - body = json.dumps(body) - headers = self._build_headers(headers) - h = httplib2.Http() - url = '%s%s' % (self.endpoint, path) - resp, content = h.request(url, method=method, headers=headers, body=body) - return webob.Response(content, status=resp.status, headerlist=resp.items()) - - -class TestClient(Client): - def __init__(self, app=None, token=None): - self.app = app - super(TestClient, self).__init__(token=token) - - def request(self, method, path, headers=None, body=None): - if type(body) is type({}): - body = json.dumps(body) - headers = self._build_headers(headers) - req = wsgi.Request.blank(path) - req.method = method - for k, v in headers.iteritems(): - req.headers[k] = v - if body: - req.body = body - return req.get_response(self.app) diff --git a/keystone/keystone_compat.py b/keystone/keystone_compat.py deleted file mode 100644 index 95e05400..00000000 --- a/keystone/keystone_compat.py +++ /dev/null @@ -1,860 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# this is the web service frontend that emulates keystone -import json -import urllib -import urlparse -import uuid - -import routes -import webob.dec -import webob.exc - -from keystone import catalog -from keystone import identity -from keystone import logging -from keystone import policy -from keystone import service -from keystone import token -from keystone import utils -from keystone import wsgi - - -class Application(wsgi.Application): - @webob.dec.wsgify - def __call__(self, req): - arg_dict = req.environ['wsgiorg.routing_args'][1] - action = arg_dict['action'] - del arg_dict['action'] - del arg_dict['controller'] - logging.debug('arg_dict: %s', arg_dict) - - context = req.environ.get('openstack.context', {}) - # allow middleware up the stack to override the params - params = {} - if 'openstack.params' in req.environ: - params = req.environ['openstack.params'] - params.update(arg_dict) - - # TODO(termie): do some basic normalization on methods - method = getattr(self, action) - - # NOTE(vish): make sure we have no unicode keys for py2.6. - params = self._normalize_dict(params) - result = method(context, **params) - - if result is None or type(result) is str or type(result) is unicode: - return result - elif isinstance(result, webob.exc.WSGIHTTPException): - return result - - return self._serialize(result) - - def _serialize(self, result): - return json.dumps(result, cls=utils.SmarterEncoder) - - def _normalize_arg(self, arg): - return str(arg).replace(':', '_').replace('-', '_') - - def _normalize_dict(self, d): - return dict([(self._normalize_arg(k), v) - for (k, v) in d.iteritems()]) - - def assert_admin(self, context): - if not context['is_admin']: - user_token_ref = self.token_api.get_token( - context=context, token_id=context['token_id']) - creds = user_token_ref['metadata'].copy() - creds['user_id'] = user_token_ref['user'].get('id') - creds['tenant_id'] = user_token_ref['tenant'].get('id') - print creds - # Accept either is_admin or the admin role - assert self.policy_api.can_haz(context, - ('is_admin:1', 'roles:admin'), - creds) - - -class AdminRouter(wsgi.Router): - def __init__(self): - mapper = routes.Mapper() - - # Token Operations - auth_controller = TokenController() - mapper.connect('/tokens', - controller=auth_controller, - action='authenticate', - conditions=dict(method=['POST'])) - mapper.connect('/tokens/{token_id}', - controller=auth_controller, - action='validate_token', - conditions=dict(method=['GET'])) - mapper.connect('/tokens/{token_id}/endpoints', - controller=auth_controller, - action='endpoints', - conditions=dict(method=['GET'])) - - # Tenant Operations - tenant_controller = TenantController() - mapper.connect('/tenants', - controller=tenant_controller, - action='get_tenants_for_token', - conditions=dict(method=['GET'])) - mapper.connect('/tenants/{tenant_id}', - controller=tenant_controller, - action='get_tenant', - conditions=dict(method=['GET'])) - - # User Operations - user_controller = UserController() - mapper.connect('/users/{user_id}', - controller=user_controller, - action='get_user', - conditions=dict(method=['GET'])) - - # Role Operations - roles_controller = RoleController() - mapper.connect('/tenants/{tenant_id}/users/{user_id}/roles', - controller=roles_controller, - action='get_user_roles', - conditions=dict(method=['GET'])) - mapper.connect('/users/{user_id}/roles', - controller=user_controller, - action='get_user_roles', - conditions=dict(method=['GET'])) - - # Miscellaneous Operations - version_controller = VersionController() - mapper.connect('/', - controller=version_controller, - action='get_version_info', module='admin/version', - conditions=dict(method=['GET'])) - - extensions_controller = ExtensionsController() - mapper.connect('/extensions', - controller=extensions_controller, - action='get_extensions_info', - conditions=dict(method=['GET'])) - - super(AdminRouter, self).__init__(mapper) - - -class ServiceRouter(wsgi.Router): - def __init__(self): - mapper = routes.Mapper() - - # Token Operations - auth_controller = TokenController() - mapper.connect('/tokens', - controller=auth_controller, - action='authenticate', - conditions=dict(method=['POST'])) - mapper.connect('/ec2tokens', - controller=auth_controller, - action='authenticate_ec2', - conditions=dict(methods=['POST'])) - - # Tenant Operations - tenant_controller = TenantController() - mapper.connect('/tenants', - controller=tenant_controller, - action='get_tenants_for_token', - conditions=dict(methods=['GET'])) - - # Miscellaneous - version_controller = VersionController() - mapper.connect('/', - controller=version_controller, - action='get_version_info', - module='service/version', - conditions=dict(method=['GET'])) - - extensions_controller = ExtensionsController() - mapper.connect('/extensions', - controller=extensions_controller, - action='get_extensions_info', - conditions=dict(method=['GET'])) - - super(ServiceRouter, self).__init__(mapper) - - -class AdminCrudExtension(wsgi.ExtensionRouter): - """Previously known as the OS-KSADM extension. - - Provides a bunch of CRUD operations for internal data types. - - """ - - def __init__(self, application): - mapper = routes.Mapper() - tenant_controller = TenantController() - user_controller = UserController() - role_controller = RoleController() - service_controller = ServiceController() - - # Tenant Operations - mapper.connect("/tenants", controller=tenant_controller, - action="create_tenant", - conditions=dict(method=["POST"])) - mapper.connect("/tenants/{tenant_id}", - controller=tenant_controller, - action="update_tenant", - conditions=dict(method=["PUT"])) - mapper.connect("/tenants/{tenant_id}", - controller=tenant_controller, - action="delete_tenant", - conditions=dict(method=["DELETE"])) - mapper.connect("/tenants/{tenant_id}/users", - controller=user_controller, - action="get_tenant_users", - conditions=dict(method=["GET"])) - - # User Operations - mapper.connect("/users", - controller=user_controller, - action="get_users", - conditions=dict(method=["GET"])) - mapper.connect("/users", - controller=user_controller, - action="create_user", - conditions=dict(method=["POST"])) - # NOTE(termie): not in diablo - mapper.connect("/users/{user_id}", - controller=user_controller, - action="update_user", - conditions=dict(method=["PUT"])) - mapper.connect("/users/{user_id}", - controller=user_controller, - action="delete_user", - conditions=dict(method=["DELETE"])) - - # COMPAT(diablo): the copy with no OS-KSADM is from diablo - mapper.connect("/users/{user_id}/password", - controller=user_controller, - action="set_user_password", - conditions=dict(method=["PUT"])) - mapper.connect("/users/{user_id}/OS-KSADM/password", - controller=user_controller, - action="set_user_password", - conditions=dict(method=["PUT"])) - - # COMPAT(diablo): the copy with no OS-KSADM is from diablo - mapper.connect("/users/{user_id}/tenant", - controller=user_controller, - action="update_user_tenant", - conditions=dict(method=["PUT"])) - mapper.connect("/users/{user_id}/OS-KSADM/tenant", - controller=user_controller, - action="update_user_tenant", - conditions=dict(method=["PUT"])) - - # COMPAT(diablo): the copy with no OS-KSADM is from diablo - mapper.connect("/users/{user_id}/enabled", - controller=user_controller, - action="set_user_enabled", - conditions=dict(method=["PUT"])) - mapper.connect("/users/{user_id}/OS-KSADM/enabled", - controller=user_controller, - action="set_user_enabled", - conditions=dict(method=["PUT"])) - - # User Roles - mapper.connect("/users/{user_id}/roles/OS-KSADM/{role_id}", - controller=role_controller, action="add_role_to_user", - conditions=dict(method=["PUT"])) - mapper.connect("/users/{user_id}/roles/OS-KSADM/{role_id}", - controller=role_controller, action="delete_role_from_user", - conditions=dict(method=["DELETE"])) - - # COMPAT(diablo): User Roles - mapper.connect("/users/{user_id}/roleRefs", - controller=role_controller, action="get_role_refs", - conditions=dict(method=["GET"])) - mapper.connect("/users/{user_id}/roleRefs", - controller=role_controller, action="create_role_ref", - conditions=dict(method=["POST"])) - mapper.connect("/users/{user_id}/roleRefs/{role_ref_id}", - controller=role_controller, action="delete_role_ref", - conditions=dict(method=["DELETE"])) - - # User-Tenant Roles - mapper.connect( - "/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}", - controller=role_controller, action="add_role_to_user", - conditions=dict(method=["PUT"])) - mapper.connect( - "/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}", - controller=role_controller, action="delete_role_from_user", - conditions=dict(method=["DELETE"])) - - # Service Operations - mapper.connect("/OS-KSADM/services", - controller=service_controller, - action="get_services", - conditions=dict(method=["GET"])) - mapper.connect("/OS-KSADM/services", - controller=service_controller, - action="create_service", - conditions=dict(method=["POST"])) - mapper.connect("/OS-KSADM/services/{service_id}", - controller=service_controller, - action="delete_service", - conditions=dict(method=["DELETE"])) - mapper.connect("/OS-KSADM/services/{service_id}", - controller=service_controller, - action="get_service", - conditions=dict(method=["GET"])) - - # Role Operations - mapper.connect("/OS-KSADM/roles", - controller=role_controller, - action="create_role", - conditions=dict(method=["POST"])) - mapper.connect("/OS-KSADM/roles", - controller=role_controller, - action="get_roles", - conditions=dict(method=["GET"])) - mapper.connect("/OS-KSADM/roles/{role_id}", - controller=role_controller, - action="get_role", - conditions=dict(method=["GET"])) - mapper.connect("/OS-KSADM/roles/{role_id}", - controller=role_controller, - action="delete_role", - conditions=dict(method=["DELETE"])) - - super(AdminCrudExtension, self).__init__( - application, mapper) - - -class TokenController(Application): - def __init__(self): - self.catalog_api = catalog.Manager() - self.identity_api = identity.Manager() - self.token_api = token.Manager() - self.policy_api = policy.Manager() - super(TokenController, self).__init__() - - def authenticate(self, context, auth=None): - """Authenticate credentials and return a token. - - Accept auth as a dict that looks like: - - { - "auth":{ - "passwordCredentials":{ - "username":"test_user", - "password":"mypass" - }, - "tenantName":"customer-x" - } - } - - In this case, tenant is optional, if not provided the token will be - considered "unscoped" and can later be used to get a scoped token. - - Alternatively, this call accepts auth with only a token and tenant - that will return a token that is scoped to that tenant. - """ - - if 'passwordCredentials' in auth: - username = auth['passwordCredentials'].get('username', '') - password = auth['passwordCredentials'].get('password', '') - tenant_name = auth.get('tenantName', None) - - if username: - user_ref = self.identity_api.get_user_by_name( - context=context, user_name=username) - user_id = user_ref['id'] - else: - user_id = auth['passwordCredentials'].get('userId', None) - - # more compat - if tenant_name: - tenant_ref = self.identity_api.get_tenant_by_name( - context=context, tenant_name=tenant_name) - tenant_id = tenant_ref['id'] - else: - tenant_id = auth.get('tenantId', None) - - (user_ref, tenant_ref, metadata_ref) = \ - self.identity_api.authenticate(context=context, - user_id=user_id, - password=password, - tenant_id=tenant_id) - token_ref = self.token_api.create_token( - context, dict(expires='', - user=user_ref, - tenant=tenant_ref, - metadata=metadata_ref)) - if tenant_ref: - catalog_ref = self.catalog_api.get_catalog( - context=context, - user_id=user_ref['id'], - tenant_id=tenant_ref['id'], - metadata=metadata_ref) - else: - catalog_ref = {} - - elif 'token' in auth: - token = auth['token'].get('id', None) - - tenant_name = auth.get('tenantName') - - # more compat - if tenant_name: - tenant_ref = self.identity_api.get_tenant_by_name( - context=context, tenant_name=tenant_name) - tenant_id = tenant_ref['id'] - else: - tenant_id = auth.get('tenantId', None) - - old_token_ref = self.token_api.get_token(context=context, - token_id=token) - user_ref = old_token_ref['user'] - - assert tenant_id in user_ref['tenants'] - - tenant_ref = self.identity_api.get_tenant(context=context, - tenant_id=tenant_id) - metadata_ref = self.identity_api.get_metadata( - context=context, - user_id=user_ref['id'], - tenant_id=tenant_ref['id']) - token_ref = self.token_api.create_token( - context, dict(expires='', - user=user_ref, - tenant=tenant_ref, - metadata=metadata_ref)) - catalog_ref = self.catalog_api.get_catalog( - context=context, - user_id=user_ref['id'], - tenant_id=tenant_ref['id'], - metadata=metadata_ref) - - # TODO(termie): optimize this call at some point and put it into the - # the return for metadata - # fill out the roles in the metadata - roles_ref = [] - for role_id in metadata_ref.get('roles', []): - roles_ref.append(self.identity_api.get_role(context, role_id)) - logging.debug('TOKEN_REF %s', token_ref) - return self._format_authenticate(token_ref, roles_ref, catalog_ref) - - def authenticate_ec2(self, context): - raise NotImplemented() - - # admin only - def validate_token(self, context, token_id, belongs_to=None): - """Check that a token is valid. - - Optionally, also ensure that it is owned by a specific tenant. - - """ - # TODO(termie): this stuff should probably be moved to middleware - if not context['is_admin']: - user_token_ref = self.token_api.get_token( - context=context, token_id=context['token_id']) - creds = user_token_ref['metadata'].copy() - creds['user_id'] = user_token_ref['user'].get('id') - creds['tenant_id'] = user_token_ref['tenant'].get('id') - # Accept either is_admin or the admin role - assert self.policy_api.can_haz(context, - ('is_admin:1', 'roles:admin'), - creds) - - token_ref = self.token_api.get_token(context=context, - token_id=token_id) - if belongs_to: - assert token_ref['tenant']['id'] == belongs_to - return self._format_token(token_ref) - - def endpoints(self, context, token_id): - """Return service catalog endpoints.""" - token_ref = self.token_api.get_token(context=context, - token_id=token_id) - catalog_ref = self.catalog_api.get_catalog(context, - token_ref['user']['id'], - token_ref['tenant']['id']) - return {'token': {'serviceCatalog': self._format_catalog(catalog_ref)}} - - def _format_authenticate(self, token_ref, roles_ref, catalog_ref): - o = self._format_token(token_ref, roles_ref) - o['access']['serviceCatalog'] = self._format_catalog(catalog_ref) - return o - - def _format_token(self, token_ref, roles_ref): - user_ref = token_ref['user'] - metadata_ref = token_ref['metadata'] - o = {'access': {'token': {'id': token_ref['id'], - 'expires': token_ref['expires'] - }, - 'user': {'id': user_ref['id'], - 'name': user_ref['name'], - 'username': user_ref['name'], - 'roles': roles_ref, - 'roles_links': metadata_ref.get('roles_links', - []) - } - } - } - if 'tenant' in token_ref and token_ref['tenant']: - token_ref['tenant']['enabled'] = True - o['access']['token']['tenant'] = token_ref['tenant'] - return o - - def _format_catalog(self, catalog_ref): - """Munge catalogs from internal to output format - Internal catalogs look like: - - {$REGION: { - {$SERVICE: { - $key1: $value1, - ... - } - } - } - - The legacy api wants them to look like - - [{'name': $SERVICE[name], - 'type': $SERVICE, - 'endpoints': [{ - 'tenantId': $tenant_id, - ... - 'region': $REGION, - }], - 'endpoints_links': [], - }] - - """ - if not catalog_ref: - return {} - - services = {} - for region, region_ref in catalog_ref.iteritems(): - for service, service_ref in region_ref.iteritems(): - new_service_ref = services.get(service, {}) - new_service_ref['name'] = service_ref.pop('name') - new_service_ref['type'] = service - new_service_ref['endpoints_links'] = [] - service_ref['region'] = region - - endpoints_ref = new_service_ref.get('endpoints', []) - endpoints_ref.append(service_ref) - - new_service_ref['endpoints'] = endpoints_ref - services[service] = new_service_ref - - return services.values() - - -class TenantController(Application): - def __init__(self): - self.identity_api = identity.Manager() - self.policy_api = policy.Manager() - self.token_api = token.Manager() - super(TenantController, self).__init__() - - def get_tenants_for_token(self, context, **kw): - """Get valid tenants for token based on token used to authenticate. - - Pulls the token from the context, validates it and gets the valid - tenants for the user in the token. - - Doesn't care about token scopedness. - - """ - token_ref = self.token_api.get_token(context=context, - token_id=context['token_id']) - assert token_ref is not None - - user_ref = token_ref['user'] - tenant_ids = self.identity_api.get_tenants_for_user( - context, user_ref['id']) - tenant_refs = [] - for tenant_id in tenant_ids: - tenant_refs.append(self.identity_api.get_tenant( - context=context, - tenant_id=tenant_id)) - return self._format_tenants_for_token(tenant_refs) - - def get_tenant(self, context, tenant_id): - # TODO(termie): this stuff should probably be moved to middleware - if not context['is_admin']: - user_token_ref = self.token_api.get_token( - context=context, token_id=context['token_id']) - creds = user_token_ref['metadata'].copy() - creds['user_id'] = user_token_ref['user'].get('id') - creds['tenant_id'] = user_token_ref['tenant'].get('id') - # Accept either is_admin or the admin role - assert self.policy_api.can_haz(context, - ('is_admin:1', 'roles:admin'), - creds) - - tenant = self.identity_api.get_tenant(context, tenant_id) - if not tenant: - return webob.exc.HTTPNotFound() - return {'tenant': tenant} - - # CRUD Extension - def create_tenant(self, context, tenant): - tenant_ref = self._normalize_dict(tenant) - self.assert_admin(context) - tenant_id = (tenant_ref.get('id') - and tenant_ref.get('id') - or uuid.uuid4().hex) - tenant_ref['id'] = tenant_id - - tenant = self.identity_api.create_tenant( - context, tenant_id=tenant_id, data=tenant_ref) - return {'tenant': tenant} - - def update_tenant(self, context, tenant_id, tenant): - self.assert_admin(context) - tenant_ref = self.identity_api.update_tenant( - context, tenant_id=tenant_id, data=tenant) - return {'tenant': tenant_ref} - - def delete_tenant(self, context, tenant_id, **kw): - self.assert_admin(context) - self.identity_api.delete_tenant(context, tenant_id=tenant_id) - - def get_tenant_users(self, context, **kw): - self.assert_admin(context) - raise NotImplementedError() - - def _format_tenants_for_token(self, tenant_refs): - for x in tenant_refs: - x['enabled'] = True - o = {'tenants': tenant_refs, - 'tenants_links': []} - return o - - -class UserController(Application): - def __init__(self): - self.catalog_api = catalog.Manager() - self.identity_api = identity.Manager() - self.policy_api = policy.Manager() - self.token_api = token.Manager() - super(UserController, self).__init__() - - def get_user(self, context, user_id): - self.assert_admin(context) - user_ref = self.identity_api.get_user(context, user_id) - if not user_ref: - raise webob.exc.HTTPNotFound() - return {'user': user_ref} - - def get_users(self, context): - # NOTE(termie): i can't imagine that this really wants all the data - # about every single user in the system... - self.assert_admin(context) - user_refs = self.identity_api.list_users(context) - return {'users': user_refs} - - # CRUD extension - def create_user(self, context, user): - user = self._normalize_dict(user) - self.assert_admin(context) - tenant_id = user.get('tenantId', None) - user_id = uuid.uuid4().hex - user_ref = user.copy() - user_ref['id'] = user_id - new_user_ref = self.identity_api.create_user( - context, user_id, user_ref) - if tenant_id: - self.identity_api.add_user_to_tenant(tenant_id, user_id) - return {'user': new_user_ref} - - # NOTE(termie): this is really more of a patch than a put - def update_user(self, context, user_id, user): - self.assert_admin(context) - user_ref = self.identity_api.get_user(context, user_id) - del user['id'] - user_ref.update(user) - self.identity_api.update_user(context, user_id, user_ref) - return {'user': user_ref} - - def delete_user(self, context, user_id): - self.assert_admin(context) - self.identity_api.delete_user(context, user_id) - - def set_user_enabled(self, context, user_id, user): - return self.update_user(context, user_id, user) - - def set_user_password(self, context, user_id, user): - return self.update_user(context, user_id, user) - - def update_user_tenant(self, context, user_id, user): - """Update the default tenant.""" - # ensure that we're a member of that tenant - tenant_id = user.get('tenantId') - self.identity_api.add_user_to_tenant(context, tenant_id, user_id) - return self.update_user(context, user_id, user) - - -class RoleController(Application): - def __init__(self): - self.catalog_api = catalog.Manager() - self.identity_api = identity.Manager() - self.token_api = token.Manager() - self.policy_api = policy.Manager() - super(RoleController, self).__init__() - - def get_user_roles(self, context, user_id, tenant_id=None): - raise NotImplemented() - - # CRUD extension - def get_role(self, context, role_id): - self.assert_admin(context) - role_ref = self.identity_api.get_role(context, role_id) - if not role_ref: - raise webob.exc.HTTPNotFound() - return {'role': role_ref} - - def create_role(self, context, role): - role = self._normalize_dict(role) - self.assert_admin(context) - role_id = uuid.uuid4().hex - role['id'] = role_id - role_ref = self.identity_api.create_role(context, role_id, role) - return {'role': role_ref} - - def delete_role(self, context, role_id): - self.assert_admin(context) - role_ref = self.identity_api.delete_role(context, role_id) - - def get_roles(self, context): - self.assert_admin(context) - roles = self.identity_api.list_roles(context) - # TODO(termie): probably inefficient at some point - return {'roles': roles} - - # COMPAT(diablo): CRUD extension - def get_role_refs(self, context, user_id): - """Ultimate hack to get around having to make role_refs first-class. - - This will basically iterate over the various roles the user has in - all tenants the user is a member of and create fake role_refs where - the id encodes the user-tenant-role information so we can look - up the appropriate data when we need to delete them. - - """ - self.assert_admin(context) - user_ref = self.identity_api.get_user(context, user_id) - tenant_ids = self.identity_api.get_tenants_for_user(context, user_id) - o = [] - for tenant_id in tenant_ids: - role_ids = self.identity_api.get_roles_for_user_and_tenant( - context, user_id, tenant_id) - for role_id in role_ids: - ref = {'roleId': role_id, - 'tenantId': tenant_id, - 'userId': user_id} - ref['id'] = urllib.urlencode(ref) - o.append(ref) - return {'roles': o} - - def create_role_ref(self, context, user_id, role): - """This is actually used for adding a user to a tenant. - - In the legacy data model adding a user to a tenant required setting - a role. - - """ - self.assert_admin(context) - # TODO(termie): for now we're ignoring the actual role - tenant_id = role.get('tenantId') - role_id = role.get('roleId') - self.identity_api.add_user_to_tenant(context, tenant_id, user_id) - self.identity_api.add_role_to_user_and_tenant( - context, user_id, tenant_id, role_id) - role_ref = self.identity_api.get_role(context, role_id) - return {'role': role_ref} - - def delete_role_ref(self, context, user_id, role_ref_id): - """This is actually used for deleting a user from a tenant. - - In the legacy data model removing a user from a tenant required - deleting a role. - - To emulate this, we encode the tenant and role in the role_ref_id, - and if this happens to be the last role for the user-tenant pair, - we remove the user from the tenant. - - """ - self.assert_admin(context) - # TODO(termie): for now we're ignoring the actual role - role_ref_ref = urlparse.parse_qs(role_ref_id) - tenant_id = role_ref_ref.get('tenantId')[0] - role_id = role_ref_ref.get('roleId')[0] - self.identity_api.remove_role_from_user_and_tenant( - context, user_id, tenant_id, role_id) - roles = self.identity_api.get_roles_for_user_and_tenant( - context, user_id, tenant_id) - if not roles: - self.identity_api.remove_user_from_tenant( - context, tenant_id, user_id) - - -class ServiceController(Application): - def __init__(self): - self.catalog_api = catalog.Manager() - self.identity_api = identity.Manager() - self.token_api = token.Manager() - self.policy_api = policy.Manager() - super(ServiceController, self).__init__() - - # CRUD extensions - # NOTE(termie): this OS-KSADM stuff is about the lamest ever - def get_services(self, context): - service_list = self.catalog_api.list_services(context) - service_refs = [self.catalog_api.get_service(context, x) - for x in service_list] - return {'OS-KSADM:services': service_refs} - - def get_service(self, context, service_id): - service_ref = self.catalog_api.get_service(context, service_id) - if not service_ref: - raise webob.exc.HTTPNotFound() - return {'OS-KSADM:service': service_ref} - - def delete_service(self, context, service_id): - service_ref = self.catalog_api.delete_service(context, service_id) - - def create_service(self, context, OS_KSADM_service): - service_id = uuid.uuid4().hex - service_ref = OS_KSADM_service.copy() - service_ref['id'] = service_id - new_service_ref = self.catalog_api.create_service( - context, service_id, service_ref) - return {'OS-KSADM:service': new_service_ref} - - -class VersionController(Application): - def __init__(self): - super(VersionController, self).__init__() - - def get_version_info(self, context, module='version'): - raise NotImplemented() - - -class ExtensionsController(Application): - def __init__(self): - super(ExtensionsController, self).__init__() - - def get_extensions_info(self, context): - raise NotImplemented() - - -def service_app_factory(global_conf, **local_conf): - conf = global_conf.copy() - conf.update(local_conf) - return ServiceRouter() - - -def admin_app_factory(global_conf, **local_conf): - conf = global_conf.copy() - conf.update(local_conf) - return AdminRouter() diff --git a/keystone/service.py b/keystone/service.py new file mode 100644 index 00000000..7bdd5d26 --- /dev/null +++ b/keystone/service.py @@ -0,0 +1,859 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# this is the web service frontend that emulates keystone +import json +import urllib +import urlparse +import uuid + +import routes +import webob.dec +import webob.exc + +from keystone import catalog +from keystone import identity +from keystone import logging +from keystone import policy +from keystone import token +from keystone import utils +from keystone import wsgi + + +class Application(wsgi.Application): + @webob.dec.wsgify + def __call__(self, req): + arg_dict = req.environ['wsgiorg.routing_args'][1] + action = arg_dict['action'] + del arg_dict['action'] + del arg_dict['controller'] + logging.debug('arg_dict: %s', arg_dict) + + context = req.environ.get('openstack.context', {}) + # allow middleware up the stack to override the params + params = {} + if 'openstack.params' in req.environ: + params = req.environ['openstack.params'] + params.update(arg_dict) + + # TODO(termie): do some basic normalization on methods + method = getattr(self, action) + + # NOTE(vish): make sure we have no unicode keys for py2.6. + params = self._normalize_dict(params) + result = method(context, **params) + + if result is None or type(result) is str or type(result) is unicode: + return result + elif isinstance(result, webob.exc.WSGIHTTPException): + return result + + return self._serialize(result) + + def _serialize(self, result): + return json.dumps(result, cls=utils.SmarterEncoder) + + def _normalize_arg(self, arg): + return str(arg).replace(':', '_').replace('-', '_') + + def _normalize_dict(self, d): + return dict([(self._normalize_arg(k), v) + for (k, v) in d.iteritems()]) + + def assert_admin(self, context): + if not context['is_admin']: + user_token_ref = self.token_api.get_token( + context=context, token_id=context['token_id']) + creds = user_token_ref['metadata'].copy() + creds['user_id'] = user_token_ref['user'].get('id') + creds['tenant_id'] = user_token_ref['tenant'].get('id') + print creds + # Accept either is_admin or the admin role + assert self.policy_api.can_haz(context, + ('is_admin:1', 'roles:admin'), + creds) + + +class AdminRouter(wsgi.Router): + def __init__(self): + mapper = routes.Mapper() + + # Token Operations + auth_controller = TokenController() + mapper.connect('/tokens', + controller=auth_controller, + action='authenticate', + conditions=dict(method=['POST'])) + mapper.connect('/tokens/{token_id}', + controller=auth_controller, + action='validate_token', + conditions=dict(method=['GET'])) + mapper.connect('/tokens/{token_id}/endpoints', + controller=auth_controller, + action='endpoints', + conditions=dict(method=['GET'])) + + # Tenant Operations + tenant_controller = TenantController() + mapper.connect('/tenants', + controller=tenant_controller, + action='get_tenants_for_token', + conditions=dict(method=['GET'])) + mapper.connect('/tenants/{tenant_id}', + controller=tenant_controller, + action='get_tenant', + conditions=dict(method=['GET'])) + + # User Operations + user_controller = UserController() + mapper.connect('/users/{user_id}', + controller=user_controller, + action='get_user', + conditions=dict(method=['GET'])) + + # Role Operations + roles_controller = RoleController() + mapper.connect('/tenants/{tenant_id}/users/{user_id}/roles', + controller=roles_controller, + action='get_user_roles', + conditions=dict(method=['GET'])) + mapper.connect('/users/{user_id}/roles', + controller=user_controller, + action='get_user_roles', + conditions=dict(method=['GET'])) + + # Miscellaneous Operations + version_controller = VersionController() + mapper.connect('/', + controller=version_controller, + action='get_version_info', module='admin/version', + conditions=dict(method=['GET'])) + + extensions_controller = ExtensionsController() + mapper.connect('/extensions', + controller=extensions_controller, + action='get_extensions_info', + conditions=dict(method=['GET'])) + + super(AdminRouter, self).__init__(mapper) + + +class ServiceRouter(wsgi.Router): + def __init__(self): + mapper = routes.Mapper() + + # Token Operations + auth_controller = TokenController() + mapper.connect('/tokens', + controller=auth_controller, + action='authenticate', + conditions=dict(method=['POST'])) + mapper.connect('/ec2tokens', + controller=auth_controller, + action='authenticate_ec2', + conditions=dict(methods=['POST'])) + + # Tenant Operations + tenant_controller = TenantController() + mapper.connect('/tenants', + controller=tenant_controller, + action='get_tenants_for_token', + conditions=dict(methods=['GET'])) + + # Miscellaneous + version_controller = VersionController() + mapper.connect('/', + controller=version_controller, + action='get_version_info', + module='service/version', + conditions=dict(method=['GET'])) + + extensions_controller = ExtensionsController() + mapper.connect('/extensions', + controller=extensions_controller, + action='get_extensions_info', + conditions=dict(method=['GET'])) + + super(ServiceRouter, self).__init__(mapper) + + +class AdminCrudExtension(wsgi.ExtensionRouter): + """Previously known as the OS-KSADM extension. + + Provides a bunch of CRUD operations for internal data types. + + """ + + def __init__(self, application): + mapper = routes.Mapper() + tenant_controller = TenantController() + user_controller = UserController() + role_controller = RoleController() + service_controller = ServiceController() + + # Tenant Operations + mapper.connect("/tenants", controller=tenant_controller, + action="create_tenant", + conditions=dict(method=["POST"])) + mapper.connect("/tenants/{tenant_id}", + controller=tenant_controller, + action="update_tenant", + conditions=dict(method=["PUT"])) + mapper.connect("/tenants/{tenant_id}", + controller=tenant_controller, + action="delete_tenant", + conditions=dict(method=["DELETE"])) + mapper.connect("/tenants/{tenant_id}/users", + controller=user_controller, + action="get_tenant_users", + conditions=dict(method=["GET"])) + + # User Operations + mapper.connect("/users", + controller=user_controller, + action="get_users", + conditions=dict(method=["GET"])) + mapper.connect("/users", + controller=user_controller, + action="create_user", + conditions=dict(method=["POST"])) + # NOTE(termie): not in diablo + mapper.connect("/users/{user_id}", + controller=user_controller, + action="update_user", + conditions=dict(method=["PUT"])) + mapper.connect("/users/{user_id}", + controller=user_controller, + action="delete_user", + conditions=dict(method=["DELETE"])) + + # COMPAT(diablo): the copy with no OS-KSADM is from diablo + mapper.connect("/users/{user_id}/password", + controller=user_controller, + action="set_user_password", + conditions=dict(method=["PUT"])) + mapper.connect("/users/{user_id}/OS-KSADM/password", + controller=user_controller, + action="set_user_password", + conditions=dict(method=["PUT"])) + + # COMPAT(diablo): the copy with no OS-KSADM is from diablo + mapper.connect("/users/{user_id}/tenant", + controller=user_controller, + action="update_user_tenant", + conditions=dict(method=["PUT"])) + mapper.connect("/users/{user_id}/OS-KSADM/tenant", + controller=user_controller, + action="update_user_tenant", + conditions=dict(method=["PUT"])) + + # COMPAT(diablo): the copy with no OS-KSADM is from diablo + mapper.connect("/users/{user_id}/enabled", + controller=user_controller, + action="set_user_enabled", + conditions=dict(method=["PUT"])) + mapper.connect("/users/{user_id}/OS-KSADM/enabled", + controller=user_controller, + action="set_user_enabled", + conditions=dict(method=["PUT"])) + + # User Roles + mapper.connect("/users/{user_id}/roles/OS-KSADM/{role_id}", + controller=role_controller, action="add_role_to_user", + conditions=dict(method=["PUT"])) + mapper.connect("/users/{user_id}/roles/OS-KSADM/{role_id}", + controller=role_controller, action="delete_role_from_user", + conditions=dict(method=["DELETE"])) + + # COMPAT(diablo): User Roles + mapper.connect("/users/{user_id}/roleRefs", + controller=role_controller, action="get_role_refs", + conditions=dict(method=["GET"])) + mapper.connect("/users/{user_id}/roleRefs", + controller=role_controller, action="create_role_ref", + conditions=dict(method=["POST"])) + mapper.connect("/users/{user_id}/roleRefs/{role_ref_id}", + controller=role_controller, action="delete_role_ref", + conditions=dict(method=["DELETE"])) + + # User-Tenant Roles + mapper.connect( + "/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}", + controller=role_controller, action="add_role_to_user", + conditions=dict(method=["PUT"])) + mapper.connect( + "/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}", + controller=role_controller, action="delete_role_from_user", + conditions=dict(method=["DELETE"])) + + # Service Operations + mapper.connect("/OS-KSADM/services", + controller=service_controller, + action="get_services", + conditions=dict(method=["GET"])) + mapper.connect("/OS-KSADM/services", + controller=service_controller, + action="create_service", + conditions=dict(method=["POST"])) + mapper.connect("/OS-KSADM/services/{service_id}", + controller=service_controller, + action="delete_service", + conditions=dict(method=["DELETE"])) + mapper.connect("/OS-KSADM/services/{service_id}", + controller=service_controller, + action="get_service", + conditions=dict(method=["GET"])) + + # Role Operations + mapper.connect("/OS-KSADM/roles", + controller=role_controller, + action="create_role", + conditions=dict(method=["POST"])) + mapper.connect("/OS-KSADM/roles", + controller=role_controller, + action="get_roles", + conditions=dict(method=["GET"])) + mapper.connect("/OS-KSADM/roles/{role_id}", + controller=role_controller, + action="get_role", + conditions=dict(method=["GET"])) + mapper.connect("/OS-KSADM/roles/{role_id}", + controller=role_controller, + action="delete_role", + conditions=dict(method=["DELETE"])) + + super(AdminCrudExtension, self).__init__( + application, mapper) + + +class TokenController(Application): + def __init__(self): + self.catalog_api = catalog.Manager() + self.identity_api = identity.Manager() + self.token_api = token.Manager() + self.policy_api = policy.Manager() + super(TokenController, self).__init__() + + def authenticate(self, context, auth=None): + """Authenticate credentials and return a token. + + Accept auth as a dict that looks like: + + { + "auth":{ + "passwordCredentials":{ + "username":"test_user", + "password":"mypass" + }, + "tenantName":"customer-x" + } + } + + In this case, tenant is optional, if not provided the token will be + considered "unscoped" and can later be used to get a scoped token. + + Alternatively, this call accepts auth with only a token and tenant + that will return a token that is scoped to that tenant. + """ + + if 'passwordCredentials' in auth: + username = auth['passwordCredentials'].get('username', '') + password = auth['passwordCredentials'].get('password', '') + tenant_name = auth.get('tenantName', None) + + if username: + user_ref = self.identity_api.get_user_by_name( + context=context, user_name=username) + user_id = user_ref['id'] + else: + user_id = auth['passwordCredentials'].get('userId', None) + + # more compat + if tenant_name: + tenant_ref = self.identity_api.get_tenant_by_name( + context=context, tenant_name=tenant_name) + tenant_id = tenant_ref['id'] + else: + tenant_id = auth.get('tenantId', None) + + (user_ref, tenant_ref, metadata_ref) = \ + self.identity_api.authenticate(context=context, + user_id=user_id, + password=password, + tenant_id=tenant_id) + token_ref = self.token_api.create_token( + context, dict(expires='', + user=user_ref, + tenant=tenant_ref, + metadata=metadata_ref)) + if tenant_ref: + catalog_ref = self.catalog_api.get_catalog( + context=context, + user_id=user_ref['id'], + tenant_id=tenant_ref['id'], + metadata=metadata_ref) + else: + catalog_ref = {} + + elif 'token' in auth: + token = auth['token'].get('id', None) + + tenant_name = auth.get('tenantName') + + # more compat + if tenant_name: + tenant_ref = self.identity_api.get_tenant_by_name( + context=context, tenant_name=tenant_name) + tenant_id = tenant_ref['id'] + else: + tenant_id = auth.get('tenantId', None) + + old_token_ref = self.token_api.get_token(context=context, + token_id=token) + user_ref = old_token_ref['user'] + + assert tenant_id in user_ref['tenants'] + + tenant_ref = self.identity_api.get_tenant(context=context, + tenant_id=tenant_id) + metadata_ref = self.identity_api.get_metadata( + context=context, + user_id=user_ref['id'], + tenant_id=tenant_ref['id']) + token_ref = self.token_api.create_token( + context, dict(expires='', + user=user_ref, + tenant=tenant_ref, + metadata=metadata_ref)) + catalog_ref = self.catalog_api.get_catalog( + context=context, + user_id=user_ref['id'], + tenant_id=tenant_ref['id'], + metadata=metadata_ref) + + # TODO(termie): optimize this call at some point and put it into the + # the return for metadata + # fill out the roles in the metadata + roles_ref = [] + for role_id in metadata_ref.get('roles', []): + roles_ref.append(self.identity_api.get_role(context, role_id)) + logging.debug('TOKEN_REF %s', token_ref) + return self._format_authenticate(token_ref, roles_ref, catalog_ref) + + def authenticate_ec2(self, context): + raise NotImplemented() + + # admin only + def validate_token(self, context, token_id, belongs_to=None): + """Check that a token is valid. + + Optionally, also ensure that it is owned by a specific tenant. + + """ + # TODO(termie): this stuff should probably be moved to middleware + if not context['is_admin']: + user_token_ref = self.token_api.get_token( + context=context, token_id=context['token_id']) + creds = user_token_ref['metadata'].copy() + creds['user_id'] = user_token_ref['user'].get('id') + creds['tenant_id'] = user_token_ref['tenant'].get('id') + # Accept either is_admin or the admin role + assert self.policy_api.can_haz(context, + ('is_admin:1', 'roles:admin'), + creds) + + token_ref = self.token_api.get_token(context=context, + token_id=token_id) + if belongs_to: + assert token_ref['tenant']['id'] == belongs_to + return self._format_token(token_ref) + + def endpoints(self, context, token_id): + """Return service catalog endpoints.""" + token_ref = self.token_api.get_token(context=context, + token_id=token_id) + catalog_ref = self.catalog_api.get_catalog(context, + token_ref['user']['id'], + token_ref['tenant']['id']) + return {'token': {'serviceCatalog': self._format_catalog(catalog_ref)}} + + def _format_authenticate(self, token_ref, roles_ref, catalog_ref): + o = self._format_token(token_ref, roles_ref) + o['access']['serviceCatalog'] = self._format_catalog(catalog_ref) + return o + + def _format_token(self, token_ref, roles_ref): + user_ref = token_ref['user'] + metadata_ref = token_ref['metadata'] + o = {'access': {'token': {'id': token_ref['id'], + 'expires': token_ref['expires'] + }, + 'user': {'id': user_ref['id'], + 'name': user_ref['name'], + 'username': user_ref['name'], + 'roles': roles_ref, + 'roles_links': metadata_ref.get('roles_links', + []) + } + } + } + if 'tenant' in token_ref and token_ref['tenant']: + token_ref['tenant']['enabled'] = True + o['access']['token']['tenant'] = token_ref['tenant'] + return o + + def _format_catalog(self, catalog_ref): + """Munge catalogs from internal to output format + Internal catalogs look like: + + {$REGION: { + {$SERVICE: { + $key1: $value1, + ... + } + } + } + + The legacy api wants them to look like + + [{'name': $SERVICE[name], + 'type': $SERVICE, + 'endpoints': [{ + 'tenantId': $tenant_id, + ... + 'region': $REGION, + }], + 'endpoints_links': [], + }] + + """ + if not catalog_ref: + return {} + + services = {} + for region, region_ref in catalog_ref.iteritems(): + for service, service_ref in region_ref.iteritems(): + new_service_ref = services.get(service, {}) + new_service_ref['name'] = service_ref.pop('name') + new_service_ref['type'] = service + new_service_ref['endpoints_links'] = [] + service_ref['region'] = region + + endpoints_ref = new_service_ref.get('endpoints', []) + endpoints_ref.append(service_ref) + + new_service_ref['endpoints'] = endpoints_ref + services[service] = new_service_ref + + return services.values() + + +class TenantController(Application): + def __init__(self): + self.identity_api = identity.Manager() + self.policy_api = policy.Manager() + self.token_api = token.Manager() + super(TenantController, self).__init__() + + def get_tenants_for_token(self, context, **kw): + """Get valid tenants for token based on token used to authenticate. + + Pulls the token from the context, validates it and gets the valid + tenants for the user in the token. + + Doesn't care about token scopedness. + + """ + token_ref = self.token_api.get_token(context=context, + token_id=context['token_id']) + assert token_ref is not None + + user_ref = token_ref['user'] + tenant_ids = self.identity_api.get_tenants_for_user( + context, user_ref['id']) + tenant_refs = [] + for tenant_id in tenant_ids: + tenant_refs.append(self.identity_api.get_tenant( + context=context, + tenant_id=tenant_id)) + return self._format_tenants_for_token(tenant_refs) + + def get_tenant(self, context, tenant_id): + # TODO(termie): this stuff should probably be moved to middleware + if not context['is_admin']: + user_token_ref = self.token_api.get_token( + context=context, token_id=context['token_id']) + creds = user_token_ref['metadata'].copy() + creds['user_id'] = user_token_ref['user'].get('id') + creds['tenant_id'] = user_token_ref['tenant'].get('id') + # Accept either is_admin or the admin role + assert self.policy_api.can_haz(context, + ('is_admin:1', 'roles:admin'), + creds) + + tenant = self.identity_api.get_tenant(context, tenant_id) + if not tenant: + return webob.exc.HTTPNotFound() + return {'tenant': tenant} + + # CRUD Extension + def create_tenant(self, context, tenant): + tenant_ref = self._normalize_dict(tenant) + self.assert_admin(context) + tenant_id = (tenant_ref.get('id') + and tenant_ref.get('id') + or uuid.uuid4().hex) + tenant_ref['id'] = tenant_id + + tenant = self.identity_api.create_tenant( + context, tenant_id=tenant_id, data=tenant_ref) + return {'tenant': tenant} + + def update_tenant(self, context, tenant_id, tenant): + self.assert_admin(context) + tenant_ref = self.identity_api.update_tenant( + context, tenant_id=tenant_id, data=tenant) + return {'tenant': tenant_ref} + + def delete_tenant(self, context, tenant_id, **kw): + self.assert_admin(context) + self.identity_api.delete_tenant(context, tenant_id=tenant_id) + + def get_tenant_users(self, context, **kw): + self.assert_admin(context) + raise NotImplementedError() + + def _format_tenants_for_token(self, tenant_refs): + for x in tenant_refs: + x['enabled'] = True + o = {'tenants': tenant_refs, + 'tenants_links': []} + return o + + +class UserController(Application): + def __init__(self): + self.catalog_api = catalog.Manager() + self.identity_api = identity.Manager() + self.policy_api = policy.Manager() + self.token_api = token.Manager() + super(UserController, self).__init__() + + def get_user(self, context, user_id): + self.assert_admin(context) + user_ref = self.identity_api.get_user(context, user_id) + if not user_ref: + raise webob.exc.HTTPNotFound() + return {'user': user_ref} + + def get_users(self, context): + # NOTE(termie): i can't imagine that this really wants all the data + # about every single user in the system... + self.assert_admin(context) + user_refs = self.identity_api.list_users(context) + return {'users': user_refs} + + # CRUD extension + def create_user(self, context, user): + user = self._normalize_dict(user) + self.assert_admin(context) + tenant_id = user.get('tenantId', None) + user_id = uuid.uuid4().hex + user_ref = user.copy() + user_ref['id'] = user_id + new_user_ref = self.identity_api.create_user( + context, user_id, user_ref) + if tenant_id: + self.identity_api.add_user_to_tenant(tenant_id, user_id) + return {'user': new_user_ref} + + # NOTE(termie): this is really more of a patch than a put + def update_user(self, context, user_id, user): + self.assert_admin(context) + user_ref = self.identity_api.get_user(context, user_id) + del user['id'] + user_ref.update(user) + self.identity_api.update_user(context, user_id, user_ref) + return {'user': user_ref} + + def delete_user(self, context, user_id): + self.assert_admin(context) + self.identity_api.delete_user(context, user_id) + + def set_user_enabled(self, context, user_id, user): + return self.update_user(context, user_id, user) + + def set_user_password(self, context, user_id, user): + return self.update_user(context, user_id, user) + + def update_user_tenant(self, context, user_id, user): + """Update the default tenant.""" + # ensure that we're a member of that tenant + tenant_id = user.get('tenantId') + self.identity_api.add_user_to_tenant(context, tenant_id, user_id) + return self.update_user(context, user_id, user) + + +class RoleController(Application): + def __init__(self): + self.catalog_api = catalog.Manager() + self.identity_api = identity.Manager() + self.token_api = token.Manager() + self.policy_api = policy.Manager() + super(RoleController, self).__init__() + + def get_user_roles(self, context, user_id, tenant_id=None): + raise NotImplemented() + + # CRUD extension + def get_role(self, context, role_id): + self.assert_admin(context) + role_ref = self.identity_api.get_role(context, role_id) + if not role_ref: + raise webob.exc.HTTPNotFound() + return {'role': role_ref} + + def create_role(self, context, role): + role = self._normalize_dict(role) + self.assert_admin(context) + role_id = uuid.uuid4().hex + role['id'] = role_id + role_ref = self.identity_api.create_role(context, role_id, role) + return {'role': role_ref} + + def delete_role(self, context, role_id): + self.assert_admin(context) + role_ref = self.identity_api.delete_role(context, role_id) + + def get_roles(self, context): + self.assert_admin(context) + roles = self.identity_api.list_roles(context) + # TODO(termie): probably inefficient at some point + return {'roles': roles} + + # COMPAT(diablo): CRUD extension + def get_role_refs(self, context, user_id): + """Ultimate hack to get around having to make role_refs first-class. + + This will basically iterate over the various roles the user has in + all tenants the user is a member of and create fake role_refs where + the id encodes the user-tenant-role information so we can look + up the appropriate data when we need to delete them. + + """ + self.assert_admin(context) + user_ref = self.identity_api.get_user(context, user_id) + tenant_ids = self.identity_api.get_tenants_for_user(context, user_id) + o = [] + for tenant_id in tenant_ids: + role_ids = self.identity_api.get_roles_for_user_and_tenant( + context, user_id, tenant_id) + for role_id in role_ids: + ref = {'roleId': role_id, + 'tenantId': tenant_id, + 'userId': user_id} + ref['id'] = urllib.urlencode(ref) + o.append(ref) + return {'roles': o} + + def create_role_ref(self, context, user_id, role): + """This is actually used for adding a user to a tenant. + + In the legacy data model adding a user to a tenant required setting + a role. + + """ + self.assert_admin(context) + # TODO(termie): for now we're ignoring the actual role + tenant_id = role.get('tenantId') + role_id = role.get('roleId') + self.identity_api.add_user_to_tenant(context, tenant_id, user_id) + self.identity_api.add_role_to_user_and_tenant( + context, user_id, tenant_id, role_id) + role_ref = self.identity_api.get_role(context, role_id) + return {'role': role_ref} + + def delete_role_ref(self, context, user_id, role_ref_id): + """This is actually used for deleting a user from a tenant. + + In the legacy data model removing a user from a tenant required + deleting a role. + + To emulate this, we encode the tenant and role in the role_ref_id, + and if this happens to be the last role for the user-tenant pair, + we remove the user from the tenant. + + """ + self.assert_admin(context) + # TODO(termie): for now we're ignoring the actual role + role_ref_ref = urlparse.parse_qs(role_ref_id) + tenant_id = role_ref_ref.get('tenantId')[0] + role_id = role_ref_ref.get('roleId')[0] + self.identity_api.remove_role_from_user_and_tenant( + context, user_id, tenant_id, role_id) + roles = self.identity_api.get_roles_for_user_and_tenant( + context, user_id, tenant_id) + if not roles: + self.identity_api.remove_user_from_tenant( + context, tenant_id, user_id) + + +class ServiceController(Application): + def __init__(self): + self.catalog_api = catalog.Manager() + self.identity_api = identity.Manager() + self.token_api = token.Manager() + self.policy_api = policy.Manager() + super(ServiceController, self).__init__() + + # CRUD extensions + # NOTE(termie): this OS-KSADM stuff is about the lamest ever + def get_services(self, context): + service_list = self.catalog_api.list_services(context) + service_refs = [self.catalog_api.get_service(context, x) + for x in service_list] + return {'OS-KSADM:services': service_refs} + + def get_service(self, context, service_id): + service_ref = self.catalog_api.get_service(context, service_id) + if not service_ref: + raise webob.exc.HTTPNotFound() + return {'OS-KSADM:service': service_ref} + + def delete_service(self, context, service_id): + service_ref = self.catalog_api.delete_service(context, service_id) + + def create_service(self, context, OS_KSADM_service): + service_id = uuid.uuid4().hex + service_ref = OS_KSADM_service.copy() + service_ref['id'] = service_id + new_service_ref = self.catalog_api.create_service( + context, service_id, service_ref) + return {'OS-KSADM:service': new_service_ref} + + +class VersionController(Application): + def __init__(self): + super(VersionController, self).__init__() + + def get_version_info(self, context, module='version'): + raise NotImplemented() + + +class ExtensionsController(Application): + def __init__(self): + super(ExtensionsController, self).__init__() + + def get_extensions_info(self, context): + raise NotImplemented() + + +def service_app_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + return ServiceRouter() + + +def admin_app_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + return AdminRouter() diff --git a/run_tests.sh b/run_tests.sh index be245a9a..825d9501 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -4,7 +4,7 @@ set -eu function usage { echo "Usage: $0 [OPTION]..." - echo "Run KeystoneLight's test suite(s)" + echo "Run Keystone's test suite(s)" echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" diff --git a/tests/default.conf b/tests/default.conf index a92b3943..f8771b97 100644 --- a/tests/default.conf +++ b/tests/default.conf @@ -38,19 +38,14 @@ paste.filter_factory = keystone.middleware:AdminTokenAuthMiddleware.factory paste.filter_factory = keystone.middleware:JsonBodyMiddleware.factory [filter:crud_extension] -paste.filter_factory = keystone.keystone_compat:AdminCrudExtension.factory +paste.filter_factory = keystone.service:AdminCrudExtension.factory -[app:keystone] -paste.app_factory = keystone.service:app_factory [app:keystone_service] -paste.app_factory = keystone.keystone_compat:service_app_factory +paste.app_factory = keystone.service:service_app_factory [app:keystone_admin] -paste.app_factory = keystone.keystone_compat:admin_app_factory - -[pipeline:keystone_api] -pipeline = token_auth admin_token_auth json_body debug keystone +paste.app_factory = keystone.service:admin_app_factory [pipeline:keystone_service_api] pipeline = token_auth admin_token_auth json_body debug keystone_service @@ -60,7 +55,6 @@ pipeline = token_auth admin_token_auth json_body debug crud_extension keystone_a [composite:main] use = egg:Paste#urlmap -/ = keystone_api /v2.0 = keystone_service_api [composite:admin] diff --git a/tests/keystone_compat_diablo.conf b/tests/keystone_compat_diablo.conf index 842bb810..5db43f6f 100644 --- a/tests/keystone_compat_diablo.conf +++ b/tests/keystone_compat_diablo.conf @@ -28,17 +28,13 @@ paste.filter_factory = keystone.middleware:AdminTokenAuthMiddleware.factory [filter:json_body] paste.filter_factory = keystone.middleware:JsonBodyMiddleware.factory -[app:keystone] -paste.app_factory = keystone.service:app_factory [app:keystone_service] -paste.app_factory = keystone.keystone_compat:service_app_factory +paste.app_factory = keystone.service:service_app_factory [app:keystone_admin] -paste.app_factory = keystone.keystone_compat:admin_app_factory +paste.app_factory = keystone.service:admin_app_factory -[pipeline:keystone_api] -pipeline = token_auth admin_token_auth json_body debug keystone [pipeline:keystone_service_api] pipeline = token_auth admin_token_auth json_body debug keystone_service @@ -46,7 +42,11 @@ pipeline = token_auth admin_token_auth json_body debug keystone_service [pipeline:keystone_admin_api] pipeline = token_auth admin_token_auth json_body debug keystone_admin + [composite:main] use = egg:Paste#urlmap -/ = keystone_api /v2.0 = keystone_service_api + +[composite:admin] +use = egg:Paste#urlmap +/v2.0 = keystone_admin_api diff --git a/tests/test_identity_api.py b/tests/test_identity_api.py deleted file mode 100644 index 50d813cf..00000000 --- a/tests/test_identity_api.py +++ /dev/null @@ -1,162 +0,0 @@ -import json - -from keystone import client -from keystone import config -from keystone import models -from keystone import test - -import default_fixtures - - -CONF = config.CONF - - -class IdentityApi(test.TestCase): - def setUp(self): - super(IdentityApi, self).setUp() - CONF(config_files=['default.conf']) - self.app = self.loadapp('default') - - self.load_backends() - self.load_fixtures(default_fixtures) - - def _login(self): - c = client.TestClient(self.app) - post_data = {'user_id': self.user_foo['id'], - 'tenant_id': self.tenant_bar['id'], - 'password': self.user_foo['password']} - resp = c.post('/tokens', body=post_data) - token = json.loads(resp.body) - return token - - def test_authenticate(self): - c = client.TestClient(self.app) - post_data = {'user_id': self.user_foo['id'], - 'tenant_id': self.tenant_bar['id'], - 'password': self.user_foo['password']} - resp = c.authenticate(**post_data) - data = json.loads(resp.body) - self.assertEquals(self.user_foo['id'], data['user']['id']) - self.assertEquals(self.tenant_bar['id'], data['tenant']['id']) - self.assertDictEquals(self.metadata_foobar, data['metadata']) - - def test_authenticate_no_tenant(self): - c = client.TestClient(self.app) - post_data = {'user_id': self.user_foo['id'], - 'password': self.user_foo['password']} - resp = c.authenticate(**post_data) - data = json.loads(resp.body) - self.assertEquals(self.user_foo['id'], data['user']['id']) - self.assertEquals(None, data['tenant']) - self.assertEquals({}, data['metadata']) - - def test_get_tenants(self): - token = self._login() - c = client.TestClient(self.app, token['id']) - resp = c.get_tenants(user_id=self.user_foo['id']) - data = json.loads(resp.body) - self.assertDictEquals(self.tenant_bar, data[0]) - - def test_crud_user(self): - token_id = CONF.admin_token - c = client.TestClient(self.app, token=token_id) - user_ref = models.User(name='FOO') - resp = c.create_user(**user_ref) - data = json.loads(resp.body) - self.assert_(data['id']) - - get_resp = c.get_user(user_id=data['id']) - get_data = json.loads(get_resp.body) - - self.assertDictEquals(data, get_data) - - update_resp = c.update_user(user_id=data['id'], - name='FOO', - id=data['id'], - password='foo') - update_data = json.loads(update_resp.body) - - self.assertEquals(data['id'], update_data['id']) - self.assertEquals('foo', update_data['password']) - - del_resp = c.delete_user(user_id=data['id']) - self.assertEquals(del_resp.body, '') - - delget_resp = c.get_user(user_id=data['id']) - self.assertEquals(delget_resp.body, '') - # TODO(termie): we should probably return not founds instead of None - #self.assertEquals(delget_resp.status, '404 Not Found') - - def test_crud_tenant(self): - token_id = CONF.admin_token - c = client.TestClient(self.app, token=token_id) - tenant_ref = models.Tenant(name='BAZ') - resp = c.create_tenant(**tenant_ref) - data = json.loads(resp.body) - self.assert_(data['id']) - - get_resp = c.get_tenant(tenant_id=data['id']) - get_data = json.loads(get_resp.body) - self.assertDictEquals(data, get_data) - - getname_resp = c.get_tenant_by_name(tenant_name=data['name']) - getname_data = json.loads(getname_resp.body) - self.assertDictEquals(data, getname_data) - - update_resp = c.update_tenant(tenant_id=data['id'], - id=data['id'], - name='NEWBAZ') - update_data = json.loads(update_resp.body) - - self.assertEquals(data['id'], update_data['id']) - self.assertEquals('NEWBAZ', update_data['name']) - - # make sure we can't get the old name - getname_resp = c.get_tenant_by_name(tenant_name=data['name']) - self.assertEquals(getname_resp.body, '') - - # but can get the new name - getname_resp = c.get_tenant_by_name(tenant_name=update_data['name']) - getname_data = json.loads(getname_resp.body) - self.assertDictEquals(update_data, getname_data) - - del_resp = c.delete_tenant(tenant_id=data['id']) - self.assertEquals(del_resp.body, '') - - delget_resp = c.get_tenant(tenant_id=data['id']) - self.assertEquals(delget_resp.body, '') - - delgetname_resp = c.get_tenant_by_name(tenant_name=update_data['name']) - self.assertEquals(delgetname_resp.body, '') - # TODO(termie): we should probably return not founds instead of None - #self.assertEquals(delget_resp.status, '404 Not Found') - - def test_crud_metadata(self): - token_id = CONF.admin_token - user_id = 'foo' - tenant_id = 'bar' - c = client.TestClient(self.app, token=token_id) - metadata_ref = dict(baz='qaz') - resp = c.create_metadata(user_id=user_id, tenant_id=tenant_id, **metadata_ref) - data = json.loads(resp.body) - self.assertEquals(data['baz'], 'qaz') - - get_resp = c.get_metadata(user_id=user_id, tenant_id=tenant_id) - get_data = json.loads(get_resp.body) - - self.assertDictEquals(data, get_data) - - update_resp = c.update_metadata(user_id=user_id, - tenant_id=tenant_id, - baz='WAZ') - update_data = json.loads(update_resp.body) - - self.assertEquals('WAZ', update_data['baz']) - - del_resp = c.delete_metadata(user_id=user_id, tenant_id=tenant_id) - self.assertEquals(del_resp.body, '') - - delget_resp = c.get_metadata(user_id=user_id, tenant_id=tenant_id) - self.assertEquals(delget_resp.body, '') - # TODO(termie): we should probably return not founds instead of None - #self.assertEquals(delget_resp.status, '404 Not Found') -- cgit