initial
This commit is contained in:
commit
419c2cb95f
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
|
||||||
|
General expected data model:
|
||||||
|
|
||||||
|
( tenants >--< users ) --< roles
|
||||||
|
|
||||||
|
Tenants and Users have a many-to-many relationship.
|
||||||
|
A given Tenant-User pair can have many Roles.
|
||||||
|
|
||||||
|
|
||||||
|
Tenant Schema:
|
||||||
|
id: something big and unique
|
||||||
|
name: something displayable
|
||||||
|
.. created_at: datetime
|
||||||
|
.. deleted_at: datetime
|
||||||
|
|
||||||
|
User Schema:
|
||||||
|
id: something big and unique
|
||||||
|
name: something displayable
|
||||||
|
.. created_at: datetime
|
||||||
|
.. deleted_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
General service model:
|
||||||
|
|
||||||
|
(1) a web service with an API
|
||||||
|
(2) a variety of backend storage schemes for tenant-user pairs.
|
||||||
|
(3) a simple token datastore
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
from __future__ import absolute_imports
|
||||||
|
|
||||||
|
import pam
|
||||||
|
|
||||||
|
|
||||||
|
class PamIdentity(object):
|
||||||
|
"""Very basic identity based on PAM.
|
||||||
|
|
||||||
|
Tenant is always the same as User, root user has admin role.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def authenticate(self, username, password):
|
||||||
|
if pam.authenticate(username, password):
|
||||||
|
extras = {}
|
||||||
|
if username == 'root':
|
||||||
|
extras['is_admin'] == True
|
||||||
|
# NOTE(termie): (tenant, user, extras)
|
||||||
|
return (username, username, extras)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# these will be the basic data types for tenants and users
|
||||||
|
# backends will make use of them to return something that conforms to their apis
|
||||||
|
|
||||||
|
|
||||||
|
import hflags as flags
|
||||||
|
|
||||||
|
from keystonelight import utils
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
flags.DEFINE_string('identity_driver',
|
||||||
|
'keystonelight.backends.dummy.DummyIdentity',
|
||||||
|
'identity driver to handle identity requests')
|
||||||
|
|
||||||
|
|
||||||
|
class IdentityManager(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.driver = utils.import_object(FLAGS.identity_driver)
|
||||||
|
|
||||||
|
|
||||||
|
def authenticate(self, context, **kwargs):
|
||||||
|
"""Passthru authentication to the identity driver.
|
||||||
|
|
||||||
|
This call will basically just result in getting a token.
|
||||||
|
"""
|
||||||
|
return self.driver.authenticate(**kwargs)
|
|
@ -0,0 +1,38 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# this is the web service frontend that emulates keystone
|
||||||
|
from keystonelight import service
|
||||||
|
|
||||||
|
def _token_to_keystone(token):
|
||||||
|
return {'id': token['id'],
|
||||||
|
'expires': token.get('expires', '')
|
||||||
|
|
||||||
|
|
||||||
|
class KeystoneIdentityController(service.IdentityController):
|
||||||
|
def authenticate(self, context, **kwargs):
|
||||||
|
token = super(KeystoneIdentityController, self).authenticate(
|
||||||
|
context, **kwargs)
|
||||||
|
return {'auth': {'token': _token_to_keystone(token),
|
||||||
|
'serviceCatalog': SERVICE_CATALOG}}
|
||||||
|
|
||||||
|
|
||||||
|
class KeystoneTokenController(service.TokenController):
|
||||||
|
def validate_token(self, context, token_id):
|
||||||
|
token = super(KeystoneTokenController, self).validate_token(
|
||||||
|
context, token_id)
|
||||||
|
# TODO(termie): munge into keystone format
|
||||||
|
|
||||||
|
tenants = [{'tenantId': token['tenant']['id'],
|
||||||
|
'name': token['tenant']['name']}]
|
||||||
|
roles = []
|
||||||
|
if token['extras'].get('is_admin'):
|
||||||
|
roles.append({
|
||||||
|
'id': 1,
|
||||||
|
'href': 'https://.openstack.org/identity/v2.0/roles/admin',
|
||||||
|
'tenantId': token['tenant']['id']})
|
||||||
|
|
||||||
|
return {'auth': {'token': _token_to_keystone(token),
|
||||||
|
'user': {'groups': {'group': tenants},
|
||||||
|
'roleRefs': {'roleRef': roles}
|
||||||
|
'username': token['user']['name'],
|
||||||
|
'tenantId': token['tenant']['id']}}}
|
|
@ -0,0 +1,70 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# this is the web service frontend
|
||||||
|
|
||||||
|
import hflags as flags
|
||||||
|
|
||||||
|
from keystonelight import wsgi
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
class TokenController(wsgi.Controller):
|
||||||
|
"""Validate and pass through calls to TokenManager."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.token_api = token.Manager()
|
||||||
|
|
||||||
|
def validate_token(self, context, token_id):
|
||||||
|
token = self.validate_token(context, token_id)
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
class IdentityController(wsgi.Controller):
|
||||||
|
"""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 authenticate(self, context, **kwargs):
|
||||||
|
tenant, user, extras = self.identity_api.authenticate(context, **kwargs)
|
||||||
|
token = self.token_api.create_token(context,
|
||||||
|
tenant=tenant,
|
||||||
|
user=user,
|
||||||
|
extras=extras)
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Router(object):
|
||||||
|
def __init__(self):
|
||||||
|
token_controller = TokenController()
|
||||||
|
identity_controller = IdentityController()
|
||||||
|
|
||||||
|
mapper.connect('/v2.0/token', controller=identity_controller,
|
||||||
|
action='authenticate')
|
||||||
|
mapper.connect('/v2.0/token/{token_id}', controller=token_controller,
|
||||||
|
action='revoke_token',
|
||||||
|
conditions=dict(method=['DELETE']))
|
||||||
|
|
||||||
|
|
||||||
|
class AdminRouter(object):
|
||||||
|
def __init__(self):
|
||||||
|
token_controller = TokenController()
|
||||||
|
identity_controller = IdentityController()
|
||||||
|
|
||||||
|
mapper.connect('/v2.0/token', controller=identity_controller,
|
||||||
|
action='authenticate')
|
||||||
|
mapper.connect('/v2.0/token/{token_id}', controller=token_controller,
|
||||||
|
action='validate_token',
|
||||||
|
conditions=dict(method=['GET']))
|
||||||
|
mapper.connect('/v2.0/token/{token_id}', controller=token_controller,
|
||||||
|
action='revoke_token',
|
||||||
|
conditions=dict(method=['DELETE']))
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# the token interfaces
|
||||||
|
|
||||||
|
from keystonelight import identity
|
||||||
|
|
||||||
|
class TokenManager(object):
|
||||||
|
def create_token(self, context, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def validate_token(self, context, token_id):
|
||||||
|
"""Return info for a token if it is valid."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def revoke_token(self, context, token_id):
|
||||||
|
pass
|
|
@ -0,0 +1,39 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2010 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# Copyright 2011 Justin Santa Barbara
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
def import_class(import_str):
|
||||||
|
"""Returns a class from a string including module and class."""
|
||||||
|
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||||
|
try:
|
||||||
|
__import__(mod_str)
|
||||||
|
return getattr(sys.modules[mod_str], class_str)
|
||||||
|
except (ImportError, ValueError, AttributeError), exc:
|
||||||
|
LOG.debug(_('Inner Exception: %s'), exc)
|
||||||
|
raise exception.ClassNotFound(class_name=class_str)
|
||||||
|
|
||||||
|
|
||||||
|
def import_object(import_str):
|
||||||
|
"""Returns an object including a module or module and class."""
|
||||||
|
try:
|
||||||
|
__import__(import_str)
|
||||||
|
return sys.modules[import_str]
|
||||||
|
except ImportError:
|
||||||
|
cls = import_class(import_str)
|
||||||
|
return cls()
|
|
@ -0,0 +1,2 @@
|
||||||
|
hflags
|
||||||
|
pam==0.1.4
|
Loading…
Reference in New Issue