split up the services and kvs backends

This commit is contained in:
termie 2012-01-18 20:57:44 -08:00
parent 909012a63e
commit 308a766b5b
22 changed files with 996 additions and 957 deletions

View File

@ -0,0 +1 @@
from keystone.catalog.core import *

View File

@ -0,0 +1,42 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from keystone.common import kvs
class KvsCatalog(kvs.Base):
# Public interface
def get_catalog(self, user_id, tenant_id, metadata=None):
return self.db.get('catalog-%s-%s' % (tenant_id, user_id))
def get_service(self, service_id):
return self.db.get('service-%s' % service_id)
def list_services(self):
return self.db.get('service_list', [])
def create_service(self, service_id, service):
self.db.set('service-%s' % service_id, service)
service_list = set(self.db.get('service_list', []))
service_list.add(service_id)
self.db.set('service_list', list(service_list))
return service
def update_service(self, service_id, service):
self.db.set('service-%s' % service_id, service)
return service
def delete_service(self, service_id):
self.db.delete('service-%s' % service_id)
service_list = set(self.db.get('service_list', []))
service_list.remove(service_id)
self.db.set('service_list', list(service_list))
return None
# Private interface
def _create_catalog(self, user_id, tenant_id, data):
self.db.set('catalog-%s-%s' % (tenant_id, user_id), data)
return data

View File

@ -1,15 +1,15 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from keystone import config
from keystone import logging
from keystone.backends import kvs
from keystone.common import logging
from keystone.catalog.backends import kvs
CONF = config.CONF
config.register_str('template_file', group='catalog')
class TemplatedCatalog(kvs.KvsCatalog):
class TemplatedCatalog(kvs.Catalog):
"""A backend that generates endpoints for the Catalog based on templates.
It is usually configured via config entries that look like:

View File

@ -3,7 +3,11 @@
"""Main entry point into the Catalog service."""
from keystone import config
from keystone import manager
from keystone import identity
from keystone import token
from keystone import policy
from keystone.common import manager
from keystone.common import wsgi
CONF = config.CONF
@ -22,3 +26,38 @@ class Manager(manager.Manager):
def __init__(self):
super(Manager, self).__init__(CONF.catalog.driver)
class ServiceController(wsgi.Application):
def __init__(self):
self.catalog_api = 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 not very consistent
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}

View File

@ -12,291 +12,10 @@ class DictKvs(dict):
INMEMDB = DictKvs()
class KvsIdentity(object):
class Base(object):
def __init__(self, db=None):
if db is None:
db = INMEMDB
elif type(db) is type({}):
db = DictKvs(db)
self.db = db
# Public interface
def authenticate(self, user_id=None, tenant_id=None, password=None):
"""Authenticate based on a user, tenant and password.
Expects the user object to have a password field and the tenant to be
in the list of tenants on the user.
"""
user_ref = self.get_user(user_id)
tenant_ref = None
metadata_ref = None
if not user_ref or user_ref.get('password') != password:
raise AssertionError('Invalid user / password')
if tenant_id and tenant_id not in user_ref['tenants']:
raise AssertionError('Invalid tenant')
tenant_ref = self.get_tenant(tenant_id)
if tenant_ref:
metadata_ref = self.get_metadata(user_id, tenant_id)
else:
metadata_ref = {}
return (user_ref, tenant_ref, metadata_ref)
def get_tenant(self, tenant_id):
tenant_ref = self.db.get('tenant-%s' % tenant_id)
return tenant_ref
def get_tenant_by_name(self, tenant_name):
tenant_ref = self.db.get('tenant_name-%s' % tenant_name)
return tenant_ref
def get_user(self, user_id):
user_ref = self.db.get('user-%s' % user_id)
return user_ref
def get_user_by_name(self, user_name):
user_ref = self.db.get('user_name-%s' % user_name)
return user_ref
def get_metadata(self, user_id, tenant_id):
return self.db.get('metadata-%s-%s' % (tenant_id, user_id))
def get_role(self, role_id):
role_ref = self.db.get('role-%s' % role_id)
return role_ref
def list_users(self):
user_ids = self.db.get('user_list', [])
return [self.get_user(x) for x in user_ids]
def list_roles(self):
role_ids = self.db.get('role_list', [])
return [self.get_role(x) for x in role_ids]
# These should probably be part of the high-level API
def add_user_to_tenant(self, tenant_id, user_id):
user_ref = self.get_user(user_id)
tenants = set(user_ref.get('tenants', []))
tenants.add(tenant_id)
user_ref['tenants'] = list(tenants)
self.update_user(user_id, user_ref)
def remove_user_from_tenant(self, tenant_id, user_id):
user_ref = self.get_user(user_id)
tenants = set(user_ref.get('tenants', []))
tenants.remove(tenant_id)
user_ref['tenants'] = list(tenants)
self.update_user(user_id, user_ref)
def get_tenants_for_user(self, user_id):
user_ref = self.get_user(user_id)
return user_ref.get('tenants', [])
def get_roles_for_user_and_tenant(self, user_id, tenant_id):
metadata_ref = self.get_metadata(user_id, tenant_id)
if not metadata_ref:
metadata_ref = {}
return metadata_ref.get('roles', [])
def add_role_to_user_and_tenant(self, user_id, tenant_id, role_id):
metadata_ref = self.get_metadata(user_id, tenant_id)
if not metadata_ref:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
roles.add(role_id)
metadata_ref['roles'] = list(roles)
self.update_metadata(user_id, tenant_id, metadata_ref)
def remove_role_from_user_and_tenant(self, user_id, tenant_id, role_id):
metadata_ref = self.get_metadata(user_id, tenant_id)
if not metadata_ref:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
roles.remove(role_id)
metadata_ref['roles'] = list(roles)
self.update_metadata(user_id, tenant_id, metadata_ref)
# CRUD
def create_user(self, user_id, user):
self.db.set('user-%s' % user_id, user)
self.db.set('user_name-%s' % user['name'], user)
user_list = set(self.db.get('user_list', []))
user_list.add(user_id)
self.db.set('user_list', list(user_list))
return user
def update_user(self, user_id, user):
# get the old name and delete it too
old_user = self.db.get('user-%s' % user_id)
self.db.delete('user_name-%s' % old_user['name'])
self.db.set('user-%s' % user_id, user)
self.db.set('user_name-%s' % user['name'], user)
return user
def delete_user(self, user_id):
old_user = self.db.get('user-%s' % user_id)
self.db.delete('user_name-%s' % old_user['name'])
self.db.delete('user-%s' % user_id)
user_list = set(self.db.get('user_list', []))
user_list.remove(user_id)
self.db.set('user_list', list(user_list))
return None
def create_tenant(self, tenant_id, tenant):
self.db.set('tenant-%s' % tenant_id, tenant)
self.db.set('tenant_name-%s' % tenant['name'], tenant)
return tenant
def update_tenant(self, tenant_id, tenant):
# get the old name and delete it too
old_tenant = self.db.get('tenant-%s' % tenant_id)
self.db.delete('tenant_name-%s' % old_tenant['name'])
self.db.set('tenant-%s' % tenant_id, tenant)
self.db.set('tenant_name-%s' % tenant['name'], tenant)
return tenant
def delete_tenant(self, tenant_id):
old_tenant = self.db.get('tenant-%s' % tenant_id)
self.db.delete('tenant_name-%s' % old_tenant['name'])
self.db.delete('tenant-%s' % tenant_id)
return None
def create_metadata(self, user_id, tenant_id, metadata):
self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata)
return metadata
def update_metadata(self, user_id, tenant_id, metadata):
self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata)
return metadata
def delete_metadata(self, user_id, tenant_id):
self.db.delete('metadata-%s-%s' % (tenant_id, user_id))
return None
def create_role(self, role_id, role):
self.db.set('role-%s' % role_id, role)
role_list = set(self.db.get('role_list', []))
role_list.add(role_id)
self.db.set('role_list', list(role_list))
return role
def update_role(self, role_id, role):
self.db.set('role-%s' % role_id, role)
return role
def delete_role(self, role_id):
self.db.delete('role-%s' % role_id)
role_list = set(self.db.get('role_list', []))
role_list.remove(role_id)
self.db.set('role_list', list(role_list))
return None
class KvsToken(object):
def __init__(self, db=None):
if db is None:
db = INMEMDB
elif type(db) is type({}):
db = DictKvs(db)
self.db = db
# Public interface
def get_token(self, token_id):
return self.db.get('token-%s' % token_id)
def create_token(self, token_id, data):
self.db.set('token-%s' % token_id, data)
return data
def delete_token(self, token_id):
return self.db.delete('token-%s' % token_id)
class KvsCatalog(object):
def __init__(self, db=None):
if db is None:
db = INMEMDB
elif type(db) is type({}):
db = DictKvs(db)
self.db = db
# Public interface
def get_catalog(self, user_id, tenant_id, metadata=None):
return self.db.get('catalog-%s-%s' % (tenant_id, user_id))
def get_service(self, service_id):
return self.db.get('service-%s' % service_id)
def list_services(self):
return self.db.get('service_list', [])
def create_service(self, service_id, service):
self.db.set('service-%s' % service_id, service)
service_list = set(self.db.get('service_list', []))
service_list.add(service_id)
self.db.set('service_list', list(service_list))
return service
def update_service(self, service_id, service):
self.db.set('service-%s' % service_id, service)
return service
def delete_service(self, service_id):
self.db.delete('service-%s' % service_id)
service_list = set(self.db.get('service_list', []))
service_list.remove(service_id)
self.db.set('service_list', list(service_list))
return None
# Private interface
def _create_catalog(self, user_id, tenant_id, data):
self.db.set('catalog-%s-%s' % (tenant_id, user_id), data)
return data
class KvsPolicy(object):
def __init__(self, db=None):
if db is None:
db = INMEMDB
elif type(db) is type({}):
db = DictKvs(db)
self.db = db
def can_haz(self, target, action, credentials):
pass
class KvsEc2(object):
def __init__(self, db=None):
if db is None:
db = INMEMDB
elif type(db) is type({}):
db = DictKvs(db)
self.db = db
# Public interface
def get_credential(self, credential_id):
credential_ref = self.db.get('credential-%s' % credential_id)
return credential_ref
def list_credentials(self, user_id):
credential_ids = self.db.get('credential_list', [])
rv = [self.get_credential(x) for x in credential_ids]
return [x for x in rv if x['user_id'] == user_id]
# CRUD
def create_credential(self, credential_id, credential):
self.db.set('credential-%s' % credential_id, credential)
credential_list = set(self.db.get('credential_list', []))
credential_list.add(credential_id)
self.db.set('credential_list', list(credential_list))
return credential
def delete_credential(self, credential_id):
old_credential = self.db.get('credential-%s' % credential_id)
self.db.delete('credential-%s' % credential_id)
credential_list = set(self.db.get('credential_list', []))
credential_list.remove(credential_id)
self.db.set('credential_list', list(credential_list))
return None

View File

@ -3,7 +3,7 @@
import functools
from keystone import config
from keystone import utils
from keystone.common import utils
class Manager(object):

View File

@ -25,7 +25,7 @@ import subprocess
import sys
import urllib
from keystone import logging
from keystone.common import logging
def import_class(import_str):

View File

@ -81,7 +81,7 @@ class Request(webob.Request):
pass
class Application(object):
class BaseApplication(object):
"""Base WSGI application wrapper. Subclasses need to implement __call__."""
@classmethod
@ -146,6 +146,60 @@ class Application(object):
raise NotImplementedError('You must implement __call__')
class Application(wsgi.BaseApplication):
@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 Middleware(Application):
"""Base WSGI middleware.
@ -310,6 +364,31 @@ class Router(object):
return app
class ComposingRouter(Router):
def __init__(self, mapper=None, routers=None):
if mapper is None:
mapper = routes.Mapper()
if routers is None:
routers = []
for router in routers:
router.add_routes(mapper)
super(ComposingRouter, self).__init__(mapper)
class ComposableRouter(object):
"""Router that supports use by ComposingRouter."""
def __init__(self, mapper=None):
if mapper is None:
mapper = routes.Mapper()
self.add_routes(mapper)
super(ComposableRouter, self).__init__(mapper)
def add_routes(self, mapper):
"""Add routes to given mapper."""
pass
class ExtensionRouter(Router):
"""A router that allows extensions to supplement or overwrite routes.
@ -317,10 +396,13 @@ class ExtensionRouter(Router):
"""
def __init__(self, application, mapper):
self.application = application
self.add_routes(mapper)
mapper.connect('{path_info:.*}', controller=self.application)
super(ExtensionRouter, self).__init__(mapper)
def add_routes(self, mapper):
pass
@classmethod
def factory(cls, global_config, **local_config):
"""Used for paste app factories in paste.deploy config files.

View File

@ -0,0 +1 @@
from keystone.contrib.admin_crud.core import *

View File

@ -0,0 +1,150 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from keystone import catalog
from keystone import identity
from keystone.common import wsgi
class CrudExtension(wsgi.ExtensionRouter):
"""Previously known as the OS-KSADM extension.
Provides a bunch of CRUD operations for internal data types.
"""
def add_routes(self, mapper):
tenant_controller = identity.TenantController()
user_controller = identity.UserController()
role_controller = identity.RoleController()
service_controller = catalog.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"]))

View File

@ -0,0 +1 @@
from keystone.contrib.ec2.core import *

View File

@ -0,0 +1,32 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from keystone.common import kvs
class Ec2(kvs.Base):
# Public interface
def get_credential(self, credential_id):
credential_ref = self.db.get('credential-%s' % credential_id)
return credential_ref
def list_credentials(self, user_id):
credential_ids = self.db.get('credential_list', [])
rv = [self.get_credential(x) for x in credential_ids]
return [x for x in rv if x['user_id'] == user_id]
# CRUD
def create_credential(self, credential_id, credential):
self.db.set('credential-%s' % credential_id, credential)
credential_list = set(self.db.get('credential_list', []))
credential_list.add(credential_id)
self.db.set('credential_list', list(credential_list))
return credential
def delete_credential(self, credential_id):
old_credential = self.db.get('credential-%s' % credential_id)
self.db.delete('credential-%s' % credential_id)
credential_list = set(self.db.get('credential_list', []))
credential_list.remove(credential_id)
self.db.set('credential_list', list(credential_list))
return None

View File

@ -2,8 +2,13 @@
"""Main entry point into the EC2 Credentials service."""
from keystone import catalog
from keystone import config
from keystone import manager
from keystone import identity
from keystone import policy
from keystone import token
from keystone.common import manager
from keystone.common import wsgi
CONF = config.CONF
@ -22,3 +27,131 @@ class Manager(manager.Manager):
def __init__(self):
super(Manager, self).__init__(CONF.ec2.driver)
class Ec2Extension(wsgi.ExtensionRouter):
def add_routes(self, mapper)
ec2_controller = Ec2Controller()
# validation
mapper.connect('/ec2tokens',
controller=ec2_controller,
action='authenticate_ec2',
conditions=dict(method=['POST']))
# crud
mapper.connect('/users/{user_id}/credentials/OS-EC2',
controller=ec2_controller,
action='create_credential',
conditions=dict(method=['POST']))
mapper.connect('/users/{user_id}/credentials/OS-EC2',
controller=ec2_controller,
action='get_credentials',
conditions=dict(method=['GET']))
mapper.connect('/users/{user_id}/credentials/OS-EC2/{credential_id}',
controller=ec2_controller,
action='get_credential',
conditions=dict(method=['GET']))
mapper.connect('/users/{user_id}/credentials/OS-EC2/{credential_id}',
controller=ec2_controller,
action='delete_credential',
conditions=dict(method=['DELETE']))
class Ec2Controller(wsgi.Application):
def __init__(self):
self.catalog_api = catalog.Manager()
self.identity_api = identity.Manager()
self.token_api = token.Manager()
self.policy_api = policy.Manager()
self.ec2_api = Manager()
super(Ec2Controller, self).__init__()
def authenticate_ec2(self, context, credentials=None,
ec2Credentials=None):
"""Validate a signed EC2 request and provide a token."""
# NOTE(termie): backwards compat hack
if not credentials and ec2Credentials:
credentials = ec2Credentials
creds_ref = self.ec2_api.get_credential(context,
credentials['access'])
signer = utils.Signer(creds_ref['secret'])
signature = signer.generate(credentials)
if signature == credentials['signature']:
pass
# NOTE(vish): Some libraries don't use the port when signing
# requests, so try again without port.
elif ':' in credentials['signature']:
hostname, _port = credentials['host'].split(":")
credentials['host'] = hostname
signature = signer.generate(credentials)
if signature != credentials.signature:
# TODO(termie): proper exception
raise Exception("Not Authorized")
else:
raise Exception("Not Authorized")
# TODO(termie): don't create new tokens every time
# TODO(termie): this is copied from TokenController.authenticate
token_id = uuid.uuid4().hex
tenant_ref = self.identity_api.get_tenant(creds_ref['tenant_id'])
user_ref = self.identity_api.get_user(creds_ref['user_id'])
metadata_ref = self.identity_api.get_metadata(
context=context,
user_id=user_ref['id'],
tenant_id=tenant_ref['id'])
catalog_ref = self.catalog_api.get_catalog(
context=context,
user_id=user_ref['id'],
tenant_id=tenant_ref['id'],
metadata=metadata_ref)
token_ref = self.token_api.create_token(
context, token_id, dict(expires='',
id=token_id,
user=user_ref,
tenant=tenant_ref,
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))
# TODO(termie): make this a util function or something
# TODO(termie): i don't think the ec2 middleware currently expects a
# full return, but it contains a note saying that it
# would be better to expect a full return
return TokenController._format_authenticate(
self, token_ref, roles_ref, catalog_ref)
def create_credential(self, context, user_id, tenant_id):
# TODO(termie): validate that this request is valid for given user
# tenant
cred_ref = {'user_id': user_id,
'tenant_id': tenant_id,
'access': uuid.uuid4().hex,
'secret': uuid.uuid4().hex}
self.ec2_api.create_credential(context, cred_ref['access'], cred_ref)
return {'credential': cred_ref}
def get_credentials(self, context, user_id):
"""List credentials for the given user_id."""
# TODO(termie): validate that this request is valid for given user
# tenant
return {'credentials': self.ec2_api.list_credentials(context, user_id)}
def get_credential(self, context, user_id, credential_id):
# TODO(termie): validate that this request is valid for given user
# tenant
return {'credential': self.ec2_api.get_credential(context,
credential_id)}
def delete_credential(self, context, user_id, credential_id):
# TODO(termie): validate that this request is valid for given user
# tenant
return self.ec2_api.delete_credential(context, credential_id)

View File

@ -0,0 +1 @@
from keystone.identity.core import *

View File

@ -0,0 +1,178 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from keystone.common import kvs
class Identity(kvs.Base):
# Public interface
def authenticate(self, user_id=None, tenant_id=None, password=None):
"""Authenticate based on a user, tenant and password.
Expects the user object to have a password field and the tenant to be
in the list of tenants on the user.
"""
user_ref = self.get_user(user_id)
tenant_ref = None
metadata_ref = None
if not user_ref or user_ref.get('password') != password:
raise AssertionError('Invalid user / password')
if tenant_id and tenant_id not in user_ref['tenants']:
raise AssertionError('Invalid tenant')
tenant_ref = self.get_tenant(tenant_id)
if tenant_ref:
metadata_ref = self.get_metadata(user_id, tenant_id)
else:
metadata_ref = {}
return (user_ref, tenant_ref, metadata_ref)
def get_tenant(self, tenant_id):
tenant_ref = self.db.get('tenant-%s' % tenant_id)
return tenant_ref
def get_tenant_by_name(self, tenant_name):
tenant_ref = self.db.get('tenant_name-%s' % tenant_name)
return tenant_ref
def get_user(self, user_id):
user_ref = self.db.get('user-%s' % user_id)
return user_ref
def get_user_by_name(self, user_name):
user_ref = self.db.get('user_name-%s' % user_name)
return user_ref
def get_metadata(self, user_id, tenant_id):
return self.db.get('metadata-%s-%s' % (tenant_id, user_id))
def get_role(self, role_id):
role_ref = self.db.get('role-%s' % role_id)
return role_ref
def list_users(self):
user_ids = self.db.get('user_list', [])
return [self.get_user(x) for x in user_ids]
def list_roles(self):
role_ids = self.db.get('role_list', [])
return [self.get_role(x) for x in role_ids]
# These should probably be part of the high-level API
def add_user_to_tenant(self, tenant_id, user_id):
user_ref = self.get_user(user_id)
tenants = set(user_ref.get('tenants', []))
tenants.add(tenant_id)
user_ref['tenants'] = list(tenants)
self.update_user(user_id, user_ref)
def remove_user_from_tenant(self, tenant_id, user_id):
user_ref = self.get_user(user_id)
tenants = set(user_ref.get('tenants', []))
tenants.remove(tenant_id)
user_ref['tenants'] = list(tenants)
self.update_user(user_id, user_ref)
def get_tenants_for_user(self, user_id):
user_ref = self.get_user(user_id)
return user_ref.get('tenants', [])
def get_roles_for_user_and_tenant(self, user_id, tenant_id):
metadata_ref = self.get_metadata(user_id, tenant_id)
if not metadata_ref:
metadata_ref = {}
return metadata_ref.get('roles', [])
def add_role_to_user_and_tenant(self, user_id, tenant_id, role_id):
metadata_ref = self.get_metadata(user_id, tenant_id)
if not metadata_ref:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
roles.add(role_id)
metadata_ref['roles'] = list(roles)
self.update_metadata(user_id, tenant_id, metadata_ref)
def remove_role_from_user_and_tenant(self, user_id, tenant_id, role_id):
metadata_ref = self.get_metadata(user_id, tenant_id)
if not metadata_ref:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
roles.remove(role_id)
metadata_ref['roles'] = list(roles)
self.update_metadata(user_id, tenant_id, metadata_ref)
# CRUD
def create_user(self, user_id, user):
self.db.set('user-%s' % user_id, user)
self.db.set('user_name-%s' % user['name'], user)
user_list = set(self.db.get('user_list', []))
user_list.add(user_id)
self.db.set('user_list', list(user_list))
return user
def update_user(self, user_id, user):
# get the old name and delete it too
old_user = self.db.get('user-%s' % user_id)
self.db.delete('user_name-%s' % old_user['name'])
self.db.set('user-%s' % user_id, user)
self.db.set('user_name-%s' % user['name'], user)
return user
def delete_user(self, user_id):
old_user = self.db.get('user-%s' % user_id)
self.db.delete('user_name-%s' % old_user['name'])
self.db.delete('user-%s' % user_id)
user_list = set(self.db.get('user_list', []))
user_list.remove(user_id)
self.db.set('user_list', list(user_list))
return None
def create_tenant(self, tenant_id, tenant):
self.db.set('tenant-%s' % tenant_id, tenant)
self.db.set('tenant_name-%s' % tenant['name'], tenant)
return tenant
def update_tenant(self, tenant_id, tenant):
# get the old name and delete it too
old_tenant = self.db.get('tenant-%s' % tenant_id)
self.db.delete('tenant_name-%s' % old_tenant['name'])
self.db.set('tenant-%s' % tenant_id, tenant)
self.db.set('tenant_name-%s' % tenant['name'], tenant)
return tenant
def delete_tenant(self, tenant_id):
old_tenant = self.db.get('tenant-%s' % tenant_id)
self.db.delete('tenant_name-%s' % old_tenant['name'])
self.db.delete('tenant-%s' % tenant_id)
return None
def create_metadata(self, user_id, tenant_id, metadata):
self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata)
return metadata
def update_metadata(self, user_id, tenant_id, metadata):
self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata)
return metadata
def delete_metadata(self, user_id, tenant_id):
self.db.delete('metadata-%s-%s' % (tenant_id, user_id))
return None
def create_role(self, role_id, role):
self.db.set('role-%s' % role_id, role)
role_list = set(self.db.get('role_list', []))
role_list.add(role_id)
self.db.set('role_list', list(role_list))
return role
def update_role(self, role_id, role):
self.db.set('role-%s' % role_id, role)
return role
def delete_role(self, role_id):
self.db.delete('role-%s' % role_id)
role_list = set(self.db.get('role_list', []))
role_list.remove(role_id)
self.db.set('role_list', list(role_list))
return None

View File

@ -22,3 +22,296 @@ class Manager(manager.Manager):
def __init__(self):
super(Manager, self).__init__(CONF.identity.driver)
class PublicRouter(wsgi.ComposableRouter):
def add_routes(self, mapper):
tenant_controller = TenantController()
mapper.connect('/tenants',
controller=tenant_controller,
action='get_tenants_for_token',
conditions=dict(methods=['GET']))
class AdminRouter(wsgi.ComposableRouter):
def add_routes(self, mapper):
# 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']))
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_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)
return {'tenant': tenant_ref}
def delete_tenant(self, context, tenant_id, **kw):
self.assert_admin(context)
self.identity_api.delete_tenant(context, 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)

View File

@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
import logging
from keystone.common import logging
class TrivialTrue(object):

View File

@ -10,70 +10,15 @@ import webob.dec
import webob.exc
from keystone import catalog
from keystone import ec2
from keystone import identity
from keystone import logging
from keystone import policy
from keystone import token
from keystone import utils
from keystone import wsgi
from keystone.common import logging
from keystone.common import utils
from keystone.common 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):
class AdminRouter(wsgi.ComposingRouter):
def __init__(self):
mapper = routes.Mapper()
@ -92,34 +37,6 @@ class AdminRouter(wsgi.Router):
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()
@ -133,11 +50,12 @@ class AdminRouter(wsgi.Router):
controller=extensions_controller,
action='get_extensions_info',
conditions=dict(method=['GET']))
super(AdminRouter, self).__init__(mapper)
identity_router = identity.AdminRouter()
routers = [identity_router]
super(AdminRouter, self).__init__(mapper, identity_router)
class PublicRouter(wsgi.Router):
class PublicRouter(wsgi.ComposingRouter):
def __init__(self):
mapper = routes.Mapper()
@ -153,13 +71,6 @@ class PublicRouter(wsgi.Router):
action='authenticate',
conditions=dict(method=['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('/',
@ -174,289 +85,13 @@ class PublicRouter(wsgi.Router):
action='get_extensions_info',
conditions=dict(method=['GET']))
super(PublicRouter, self).__init__(mapper)
identity_router = identity.PublicRouter()
routers = [identity_router]
super(PublicRouter, self).__init__(mapper, routers)
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 Ec2Extension(wsgi.ExtensionRouter):
def __init__(self, application):
mapper = routes.Mapper()
ec2_controller = Ec2Controller()
# validation
mapper.connect('/ec2tokens',
controller=ec2_controller,
action='authenticate_ec2',
conditions=dict(method=['POST']))
# crud
mapper.connect('/users/{user_id}/credentials/OS-EC2',
controller=ec2_controller,
action='create_credential',
conditions=dict(method=['POST']))
mapper.connect('/users/{user_id}/credentials/OS-EC2',
controller=ec2_controller,
action='get_credentials',
conditions=dict(method=['GET']))
mapper.connect('/users/{user_id}/credentials/OS-EC2/{credential_id}',
controller=ec2_controller,
action='get_credential',
conditions=dict(method=['GET']))
mapper.connect('/users/{user_id}/credentials/OS-EC2/{credential_id}',
controller=ec2_controller,
action='delete_credential',
conditions=dict(method=['DELETE']))
super(Ec2Extension, self).__init__(application, mapper)
class Ec2Controller(Application):
def __init__(self):
self.catalog_api = catalog.Manager()
self.identity_api = identity.Manager()
self.token_api = token.Manager()
self.policy_api = policy.Manager()
self.ec2_api = ec2.Manager()
super(Ec2Controller, self).__init__()
def authenticate_ec2(self, context, credentials=None,
ec2Credentials=None):
"""Validate a signed EC2 request and provide a token."""
# NOTE(termie): backwards compat hack
if not credentials and ec2Credentials:
credentials = ec2Credentials
creds_ref = self.ec2_api.get_credential(context,
credentials['access'])
signer = utils.Signer(creds_ref['secret'])
signature = signer.generate(credentials)
if signature == credentials['signature']:
pass
# NOTE(vish): Some libraries don't use the port when signing
# requests, so try again without port.
elif ':' in credentials['signature']:
hostname, _port = credentials['host'].split(":")
credentials['host'] = hostname
signature = signer.generate(credentials)
if signature != credentials.signature:
# TODO(termie): proper exception
raise Exception("Not Authorized")
else:
raise Exception("Not Authorized")
# TODO(termie): don't create new tokens every time
# TODO(termie): this is copied from TokenController.authenticate
token_id = uuid.uuid4().hex
tenant_ref = self.identity_api.get_tenant(creds_ref['tenant_id'])
user_ref = self.identity_api.get_user(creds_ref['user_id'])
metadata_ref = self.identity_api.get_metadata(
context=context,
user_id=user_ref['id'],
tenant_id=tenant_ref['id'])
catalog_ref = self.catalog_api.get_catalog(
context=context,
user_id=user_ref['id'],
tenant_id=tenant_ref['id'],
metadata=metadata_ref)
token_ref = self.token_api.create_token(
context, token_id, dict(expires='',
id=token_id,
user=user_ref,
tenant=tenant_ref,
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))
# TODO(termie): make this a util function or something
# TODO(termie): i don't think the ec2 middleware currently expects a
# full return, but it contains a note saying that it
# would be better to expect a full return
return TokenController._format_authenticate(
self, token_ref, roles_ref, catalog_ref)
def create_credential(self, context, user_id, tenant_id):
# TODO(termie): validate that this request is valid for given user
# tenant
cred_ref = {'user_id': user_id,
'tenant_id': tenant_id,
'access': uuid.uuid4().hex,
'secret': uuid.uuid4().hex}
self.ec2_api.create_credential(context, cred_ref['access'], cred_ref)
return {'credential': cred_ref}
def get_credentials(self, context, user_id):
"""List credentials for the given user_id."""
# TODO(termie): validate that this request is valid for given user
# tenant
return {'credentials': self.ec2_api.list_credentials(context, user_id)}
def get_credential(self, context, user_id, credential_id):
# TODO(termie): validate that this request is valid for given user
# tenant
return {'credential': self.ec2_api.get_credential(context,
credential_id)}
def delete_credential(self, context, user_id, credential_id):
# TODO(termie): validate that this request is valid for given user
# tenant
return self.ec2_api.delete_credential(context, credential_id)
class NoopController(Application):
class NoopController(wsgi.Application):
def __init__(self):
super(NoopController, self).__init__()
@ -464,7 +99,7 @@ class NoopController(Application):
return {}
class TokenController(Application):
class TokenController(wsgi.Application):
def __init__(self):
self.catalog_api = catalog.Manager()
self.identity_api = identity.Manager()
@ -693,291 +328,7 @@ class TokenController(Application):
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_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)
return {'tenant': tenant_ref}
def delete_tenant(self, context, tenant_id, **kw):
self.assert_admin(context)
self.identity_api.delete_tenant(context, 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 not very consistent
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):
class VersionController(wsgi.Application):
def __init__(self):
super(VersionController, self).__init__()
@ -985,7 +336,7 @@ class VersionController(Application):
raise NotImplemented()
class ExtensionsController(Application):
class ExtensionsController(wsgi.Application):
def __init__(self):
super(ExtensionsController, self).__init__()

View File

@ -0,0 +1 @@
from keystone.token.core import *

View File

View File

@ -0,0 +1,15 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from keystone.common import kvs
class KvsToken(kvs.Base):
# Public interface
def get_token(self, token_id):
return self.db.get('token-%s' % token_id)
def create_token(self, token_id, data):
self.db.set('token-%s' % token_id, data)
return data
def delete_token(self, token_id):
return self.db.delete('token-%s' % token_id)