initial
This commit is contained in:
commit
419c2cb95f
31
README.rst
Normal file
31
README.rst
Normal 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
|
||||
|
||||
|
||||
|
0
keystonelight/__init__.py
Normal file
0
keystonelight/__init__.py
Normal file
0
keystonelight/backends/__init__.py
Normal file
0
keystonelight/backends/__init__.py
Normal file
21
keystonelight/backends/pam.py
Normal file
21
keystonelight/backends/pam.py
Normal 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
28
keystonelight/identity.py
Normal 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)
|
38
keystonelight/keystone_compat.py
Normal file
38
keystonelight/keystone_compat.py
Normal 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
70
keystonelight/service.py
Normal 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
16
keystonelight/token.py
Normal 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
39
keystonelight/utils.py
Normal 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
2
tools/pip-requires
Normal file
@ -0,0 +1,2 @@
|
||||
hflags
|
||||
pam==0.1.4
|
Loading…
x
Reference in New Issue
Block a user