255 lines
8.3 KiB
Python
255 lines
8.3 KiB
Python
import json
|
|
import logging
|
|
import uuid
|
|
|
|
import routes
|
|
import webob.dec
|
|
import webob.exc
|
|
|
|
from keystone import identity
|
|
from keystone import token
|
|
from keystone import wsgi
|
|
|
|
|
|
HIGH_LEVEL_CALLS = {
|
|
'authenticate': ('POST', '/tokens'),
|
|
'get_tenants': ('GET', '/user/%(user_id)s/tenants'),
|
|
'get_user': ('GET', '/user/%(user_id)s'),
|
|
'get_tenant': ('GET', '/tenant/%(tenant_id)s'),
|
|
'get_tenant_by_name': ('GET', '/tenant_name/%(tenant_name)s'),
|
|
'get_metadata': ('GET', '/metadata/%(tenant_id)s-%(user_id)s'),
|
|
'get_token': ('GET', '/token/%(token_id)s'),
|
|
}
|
|
|
|
# NOTE(termie): creates are seperate from updates to allow duplication checks
|
|
LOW_LEVEL_CALLS = {
|
|
# tokens
|
|
'create_token': ('POST', '/token'),
|
|
'delete_token': ('DELETE', '/token/%(token_id)s'),
|
|
# users
|
|
'create_user': ('POST', '/user'),
|
|
'update_user': ('PUT', '/user/%(user_id)s'),
|
|
'delete_user': ('DELETE', '/user/%(user_id)s'),
|
|
# tenants
|
|
'create_tenant': ('POST', '/tenant'),
|
|
'update_tenant': ('PUT', '/tenant/%(tenant_id)s'),
|
|
'delete_tenant': ('DELETE', '/tenant/%(tenant_id)s'),
|
|
# metadata
|
|
# NOTE(termie): these separators are probably going to bite us eventually
|
|
'create_metadata': ('POST', '/metadata'),
|
|
'update_metadata': ('PUT', '/metadata/%(tenant_id)s-%(user_id)s'),
|
|
'delete_metadata': ('DELETE', '/metadata/%(tenant_id)s-%(user_id)s'),
|
|
}
|
|
|
|
|
|
URLMAP = HIGH_LEVEL_CALLS.copy()
|
|
URLMAP.update(LOW_LEVEL_CALLS)
|
|
|
|
|
|
class SmarterEncoder(json.JSONEncoder):
|
|
def default(self, obj):
|
|
if not isinstance(obj, dict) and hasattr(obj, 'iteritems'):
|
|
return dict(obj.iteritems())
|
|
return super(SmarterEncoder, self).default(obj)
|
|
|
|
|
|
class BaseApplication(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 = dict([(self._normalize_arg(k), v)
|
|
for (k, v) in params.iteritems()])
|
|
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=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 TokenController(BaseApplication):
|
|
"""Validate and pass through calls to TokenManager."""
|
|
|
|
def __init__(self):
|
|
self.token_api = token.Manager()
|
|
|
|
def validate_token(self, context, token_id):
|
|
token_info = self.token_api.validate_token(context, token_id)
|
|
if not token_info:
|
|
raise webob.exc.HTTPUnauthorized()
|
|
return token_info
|
|
|
|
|
|
class IdentityController(BaseApplication):
|
|
"""Validate and pass calls through to IdentityManager.
|
|
|
|
IdentityManager will also pretty much just pass calls through to
|
|
a specific driver.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.identity_api = identity.Manager()
|
|
self.token_api = token.Manager()
|
|
|
|
def noop(self, context, *args, **kw):
|
|
return ''
|
|
|
|
def authenticate(self, context, **kwargs):
|
|
user_ref, tenant_ref, metadata_ref = self.identity_api.authenticate(
|
|
context, **kwargs)
|
|
# TODO(termie): strip password from return values
|
|
token_ref = self.token_api.create_token(context,
|
|
dict(tenant=tenant_ref,
|
|
user=user_ref,
|
|
metadata=metadata_ref))
|
|
logging.debug('TOKEN: %s', token_ref)
|
|
return token_ref
|
|
|
|
def get_tenants(self, context, user_id=None):
|
|
token_id = context.get('token_id')
|
|
token_ref = self.token_api.get_token(context, token_id)
|
|
assert token_ref
|
|
assert token_ref['user']['id'] == user_id
|
|
tenants_ref = []
|
|
for tenant_id in token_ref['user']['tenants']:
|
|
tenants_ref.append(self.identity_api.get_tenant(context,
|
|
tenant_id))
|
|
|
|
return tenants_ref
|
|
|
|
# crud api
|
|
def get_user(self, context, user_id):
|
|
return self.identity_api.get_user(context, user_id=user_id)
|
|
|
|
def create_user(self, context, **kw):
|
|
user_id = kw.get('id') and kw.get('id') or uuid.uuid4().hex
|
|
kw['id'] = user_id
|
|
return self.identity_api.create_user(context, user_id=user_id, data=kw)
|
|
|
|
def update_user(self, context, user_id, **kw):
|
|
kw['id'] = user_id
|
|
kw.pop('user_id', None)
|
|
return self.identity_api.update_user(context, user_id=user_id, data=kw)
|
|
|
|
def delete_user(self, context, user_id):
|
|
return self.identity_api.delete_user(context, user_id=user_id)
|
|
|
|
def get_tenant(self, context, tenant_id):
|
|
return self.identity_api.get_tenant(context, tenant_id=tenant_id)
|
|
|
|
def get_tenant_by_name(self, context, tenant_name):
|
|
return self.identity_api.get_tenant_by_name(
|
|
context, tenant_name=tenant_name)
|
|
|
|
def create_tenant(self, context, **kw):
|
|
tenant_id = kw.get('id') and kw.get('id') or uuid.uuid4().hex
|
|
kw['id'] = tenant_id
|
|
return self.identity_api.create_tenant(
|
|
context, tenant_id=tenant_id, data=kw)
|
|
|
|
def update_tenant(self, context, tenant_id, **kw):
|
|
kw['id'] = tenant_id
|
|
kw.pop('tenant_id', None)
|
|
return self.identity_api.update_tenant(
|
|
context, tenant_id=tenant_id, data=kw)
|
|
|
|
def delete_tenant(self, context, tenant_id):
|
|
return self.identity_api.delete_tenant(context, tenant_id=tenant_id)
|
|
|
|
def get_metadata(self, context, user_id, tenant_id):
|
|
return self.identity_api.get_metadata(
|
|
context, user_id=user_id, tenant_id=tenant_id)
|
|
|
|
def create_metadata(self, context, **kw):
|
|
user_id = kw.pop('user_id')
|
|
tenant_id = kw.pop('tenant_id')
|
|
return self.identity_api.create_metadata(
|
|
context, user_id=user_id, tenant_id=tenant_id, data=kw)
|
|
|
|
def update_metadata(self, context, user_id, tenant_id, **kw):
|
|
kw.pop('user_id', None)
|
|
kw.pop('tenant_id', None)
|
|
return self.identity_api.update_metadata(
|
|
context, user_id=user_id, tenant_id=tenant_id, data=kw)
|
|
|
|
def delete_metadata(self, context, user_id, tenant_id):
|
|
return self.identity_api.delete_metadata(
|
|
context, user_id=user_id, tenant_id=tenant_id)
|
|
|
|
|
|
class Router(wsgi.Router):
|
|
def __init__(self):
|
|
self.identity_controller = IdentityController()
|
|
self.token_controller = TokenController()
|
|
|
|
mapper = self._build_map(URLMAP)
|
|
mapper.connect('/', controller=self.identity_controller, action='noop')
|
|
super(Router, self).__init__(mapper)
|
|
|
|
def _build_map(self, urlmap):
|
|
"""Build a routes.Mapper based on URLMAP."""
|
|
mapper = routes.Mapper()
|
|
for k, v in urlmap.iteritems():
|
|
# NOTE(termie): hack
|
|
if 'token' in k:
|
|
controller = self.token_controller
|
|
else:
|
|
controller = self.identity_controller
|
|
action = k
|
|
method, path = v
|
|
path = path.replace('%(', '{').replace(')s', '}')
|
|
|
|
mapper.connect(path,
|
|
controller=controller,
|
|
action=action,
|
|
conditions=dict(method=[method]))
|
|
|
|
return mapper
|
|
|
|
|
|
def app_factory(global_conf, **local_conf):
|
|
#conf = global_conf.copy()
|
|
#conf.update(local_conf)
|
|
return Router()
|