This commit is contained in:
termie 2011-06-20 17:56:20 -07:00
commit 419c2cb95f
10 changed files with 245 additions and 0 deletions

31
README.rst Normal file
View File

@ -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

View File

View File

View File

@ -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)

28
keystonelight/identity.py Normal file
View File

@ -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)

View File

@ -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']}}}

70
keystonelight/service.py Normal file
View File

@ -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']))

16
keystonelight/token.py Normal file
View File

@ -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

39
keystonelight/utils.py Normal file
View File

@ -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()

2
tools/pip-requires Normal file
View File

@ -0,0 +1,2 @@
hflags
pam==0.1.4