Introduces UID's & domain models (bp portable-identifiers)

This is an initial redesign of a series where we're moving
away from the existing keystone backend/model architecture
towards a simpler design more in line with other OpenStack
projects.

- Implemented User & Tenant UID's
  - Migrations to introduce users.uid and tenants.uid
  - Migrations populate these columns with existing PK ID's
  - Populating them with UUID's moving forward
- Domain models
  - Allows us to keep backend models in the backend,
    and use business objects in keystone.logic
  - Starts to simplify de/serialization
- Added 'database' commands to keystone-manage
  - e.g. ./bin/keystone-manage database [sync|version|version_contro|upgrade|downgrade]
- Improved test coverage
  - Add database migration tests
  - Cleaner handling of migration errors
  - New attribute is commented out in sqlalchemy model (for illustration)
- Refactored service.py to be class-based (IdentityService)
  - Retains configuration in self.options on init
  - Added __init__ which inits ClassManagers
  - Moved backend initialization to IdentityService
- Pylint updates
  - added many @staticmethods where recommended
  - turned off checks where appropriate (or to be fixed)

Change-Id: I4ac00c190968b24640d25806f45b33c7aa2c5c92
This commit is contained in:
Ziad Sawalha 2011-12-08 15:45:19 -06:00
parent 1accb479fd
commit d1d3df0465
77 changed files with 3201 additions and 369 deletions

View File

@ -11,6 +11,8 @@ if os.path.exists(os.path.join(possible_topdir, 'keystone', '__init__.py')):
sys.path.insert(0, possible_topdir)
import keystone.manage
import keystone.tools.tracer # @UnusedImport # module runs on import
if __name__ == '__main__':
keystone.manage.main()

64
etc/ldap.conf Normal file
View File

@ -0,0 +1,64 @@
#
# SAMPLE CONFIG FILE TO TEST LDAP TOKEN STORE
#
# TO USE:
# in /bin, run:
# ./keystone -c ../etc/ldap.conf
# also run
# ./sampledata -c ../etc/ldap.conf
#
[DEFAULT]
verbose = False
debug = False
default_store = sqlite
log_file = keystone.ldap.log
log_dir = .
backends = keystone.backends.sqlalchemy,keystone.backends.ldap
extensions= osksadm,oskscatalog
service-header-mappings = {
'nova' : 'X-Server-Management-Url',
'swift' : 'X-Storage-Url',
'cdn' : 'X-CDN-Management-Url'}
service_host = 0.0.0.0
service_port = 5000
service_ssl = False
admin_host = 0.0.0.0
admin_port = 35357
admin_ssl = False
keystone-admin-role = Admin
keystone-service-admin-role = KeystoneServiceAdmin
hash-password = True
[keystone.backends.sqlalchemy]
sql_connection = sqlite://
sql_idle_timeout = 30
backend_entities = ['Endpoints', 'Credentials', 'EndpointTemplates', 'Token', 'Service']
[keystone.backends.ldap]
ldap_url = fake://memory
ldap_user = cn=Admin
ldap_password = password
backend_entities = ['Tenant', 'User', 'UserRoleAssociation', 'Role']
[pipeline:admin]
pipeline =
urlrewritefilter
admin_api
[pipeline:keystone-legacy-auth]
pipeline =
urlrewritefilter
legacy_auth
service_api
[app:service_api]
paste.app_factory = keystone.server:service_app_factory
[app:admin_api]
paste.app_factory = keystone.server:admin_app_factory
[filter:urlrewritefilter]
paste.filter_factory = keystone.middleware.url:filter_factory
[filter:legacy_auth]
paste.filter_factory = keystone.frontends.legacy_token_auth:filter_factory

View File

@ -14,6 +14,8 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# pylint: disable=W0603
import ast
import logging
from keystone import utils
@ -34,9 +36,10 @@ SHOULD_HASH_PASSWORD = None
def configure_backends(options):
'''Load backends given in the 'backends' option.'''
backend_names = options.get('backends', DEFAULT_BACKENDS)
for backend in backend_names.split(','):
backend_module = utils.import_module(backend)
backend_module.configure_backend(options[backend])
if backend_names:
for backend in backend_names.split(','):
backend_module = utils.import_module(backend)
backend_module.configure_backend(options[backend])
#Initialize common configs general to all backends.
global ADMIN_ROLE_NAME

View File

@ -16,9 +16,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=W0603, R0921
#Base APIs
class BaseUserAPI(object):
def __init__(self, *args, **kw):
pass
def get_all(self):
raise NotImplementedError
@ -85,6 +90,9 @@ class BaseUserAPI(object):
class BaseTokenAPI(object):
def __init__(self, *args, **kw):
pass
def create(self, values):
raise NotImplementedError
@ -105,6 +113,9 @@ class BaseTokenAPI(object):
class BaseTenantAPI(object):
def __init__(self, *args, **kw):
pass
def create(self, values):
raise NotImplementedError
@ -146,6 +157,9 @@ class BaseTenantAPI(object):
class BaseRoleAPI(object):
def __init__(self, *args, **kw):
pass
def create(self, values):
raise NotImplementedError
@ -196,6 +210,9 @@ class BaseRoleAPI(object):
class BaseEndpointTemplateAPI(object):
def __init__(self, *args, **kw):
pass
def create(self, values):
raise NotImplementedError
@ -249,7 +266,10 @@ class BaseEndpointTemplateAPI(object):
raise NotImplementedError
class BaseServiceAPI:
class BaseServiceAPI(object):
def __init__(self, *args, **kw):
pass
def create(self, values):
raise NotImplementedError
@ -276,6 +296,9 @@ class BaseServiceAPI:
class BaseCredentialsAPI(object):
def __init__(self, *args, **kw):
pass
def create(self, values):
raise NotImplementedError

View File

@ -1,11 +1,12 @@
from keystone.backends import models
import keystone.backends as backends
# pylint: disable=E0611
from passlib.hash import sha512_crypt as sc
def __get_hashed_password(password):
if password != None and len(password) > 0:
return __make_password(password)
return __make_password(password)
else:
return None
@ -44,6 +45,6 @@ def __make_password(raw_password):
#Refer http://packages.python.org/passlib/lib/passlib.hash.sha512_crypt.html
#Using the default properties as of now.Salt gets generated automatically.
#Using the default properties as of now.Salt gets generated automatically.
def __get_hexdigest(raw_password):
return sc.encrypt(raw_password)

View File

@ -4,6 +4,7 @@ from itertools import izip, count
def _get_redirect(cls, method):
# pylint: disable=W0613
def inner(self, *args):
return getattr(cls(), method)(*args)
return inner
@ -44,9 +45,11 @@ class BaseLdapAPI(object):
return '%s=%s,%s' % (self.id_attr, ldap.dn.escape_dn_chars(str(id)),
self.tree_dn)
def _dn_to_id(self, dn):
@staticmethod
def _dn_to_id(dn):
return ldap.dn.str2dn(dn)[0][0][1]
# pylint: disable=E1102
def _ldap_res_to_model(self, res):
obj = self.model(id=self._dn_to_id(res[0]))
obj['name'] = obj['id']
@ -64,6 +67,7 @@ class BaseLdapAPI(object):
obj[k] = None
return obj
# pylint: disable=E1102
def create(self, values):
conn = self.api.get_connection()
object_classes = self.structural_classes + [self.object_class]
@ -77,7 +81,7 @@ class BaseLdapAPI(object):
if 'groupOfNames' in object_classes and self.use_dumb_member:
attrs.append(('member', [self.DUMB_MEMBER_DN]))
conn.add_s(self._id_to_dn(values['id']), attrs)
return self.model(values)
return self.model(**values)
def _ldap_get(self, id, filter=None):
conn = self.api.get_connection()
@ -110,6 +114,7 @@ class BaseLdapAPI(object):
else:
return self._ldap_res_to_model(res)
# pylint: disable=W0141
def get_all(self, filter=None):
return map(self._ldap_res_to_model, self._ldap_get_all(filter))
@ -119,14 +124,17 @@ class BaseLdapAPI(object):
def get_page_markers(self, marker, limit):
return self._get_page_markers(marker, limit, self.get_all())
def _get_page(self, marker, limit, lst, key=lambda e: e.id):
# pylint: disable=W0141
@staticmethod
def _get_page(marker, limit, lst, key=lambda e: e.id):
lst.sort(key=key)
if not marker:
return lst[:limit]
else:
return filter(lambda e: key(e) > marker, lst)[:limit]
def _get_page_markers(self, marker, limit, lst, key=lambda e: e.id):
@staticmethod
def _get_page_markers(marker, limit, lst, key=lambda e: e.id):
if len(lst) < limit:
return (None, None)
lst.sort(key=key)
@ -136,11 +144,12 @@ class BaseLdapAPI(object):
else:
nxt = key(lst[limit])
return (None, nxt)
for i, item in izip(count(), lst):
k = key(item)
if k >= marker:
exact = k == marker
break
# pylint: disable=W0631
if i <= limit:
prv = None
else:

View File

@ -3,7 +3,7 @@ import ldap
from keystone.backends.api import BaseTenantAPI
from keystone.common import exception
from .. import models
from keystone import models
from .base import BaseLdapAPI
@ -13,7 +13,7 @@ class RoleAPI(BaseLdapAPI, BaseTenantAPI):
options_name = 'role'
object_class = 'keystoneRole'
model = models.Role
attribute_mapping = {'desc': 'description', 'service_id': 'serviceId'}
attribute_mapping = {'description': 'desc', 'serviceId': 'service_id'}
@staticmethod
def _create_ref(role_id, tenant_id, user_id):

View File

@ -4,7 +4,7 @@ import uuid
from keystone.backends.api import BaseTenantAPI
from keystone.backends.sqlalchemy.api.tenant import TenantAPI as SQLTenantAPI
from .. import models
from keystone import models
from .base import BaseLdapAPI, add_redirects
@ -14,7 +14,7 @@ class TenantAPI(BaseLdapAPI, BaseTenantAPI):
options_name = 'tenant'
object_class = 'keystoneTenant'
model = models.Tenant
attribute_mapping = {'desc': 'description', 'enabled': 'keystoneEnabled',
attribute_mapping = {'description': 'desc', 'enabled': 'keystoneEnabled',
'name': 'keystoneName'}
def get_by_name(self, name, filter=None):

View File

@ -6,7 +6,7 @@ import keystone.backends.backendutils as utils
from keystone.backends.api import BaseUserAPI
from keystone.backends.sqlalchemy.api.user import UserAPI as SQLUserAPI
from .. import models
from keystone import models
from .base import BaseLdapAPI, add_redirects

View File

@ -4,7 +4,7 @@ __all__ = ['UserRoleAssociation', 'Role', 'Tenant', 'User']
def create_model(name, attrs):
class C(Mapping):
class Cmapper(Mapping):
__slots__ = attrs
def __init__(self, arg=None, **kwargs):
@ -34,8 +34,8 @@ def create_model(name, attrs):
def __len__(self):
return len(attrs)
C.__name__ = name
return C
Cmapper.__name__ = name
return Cmapper
UserRoleAssociation = create_model(

View File

@ -15,4 +15,4 @@
# License for the specific language governing permissions and limitations
# under the License.
import token
from . import token

View File

@ -14,11 +14,15 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystone.backends.memcache import MEMCACHE_SERVER, models
from keystone.backends.memcache import MEMCACHE_SERVER
from keystone.backends.api import BaseTokenAPI
# pylint: disable=W0223
class TokenAPI(BaseTokenAPI):
def __init__(self, *args, **kw):
super(TokenAPI, self).__init__(*args, **kw)
def create(self, token):
if not hasattr(token, 'tenant_id'):
token.tenant_id = None
@ -36,6 +40,7 @@ class TokenAPI(BaseTokenAPI):
token.tenant_id = None
return token
# pylint: disable=E1103
def delete(self, id):
token = self.get(id)
if token is not None:

View File

@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=C0103,W0603
#Current Models
UserRoleAssociation = None
Endpoints = None

View File

@ -15,99 +15,107 @@
# License for the specific language governing permissions and limitations
# under the License.
# package import
from sqlalchemy.orm import joinedload, aliased, sessionmaker
import ast
import logging
import sys
from sqlalchemy import create_engine
from sqlalchemy.orm import joinedload, aliased, sessionmaker
from sqlalchemy.pool import StaticPool
from keystone.common import config
from keystone import utils
from keystone.backends.sqlalchemy import models
import keystone.utils as utils
import keystone.backends.api as top_api
import keystone.backends.models as top_models
_ENGINE = None
_MAKER = None
BASE = models.Base
_DRIVER = None
# TODO(dolph): these should be computed dynamically
MODEL_PREFIX = 'keystone.backends.sqlalchemy.models.'
API_PREFIX = 'keystone.backends.sqlalchemy.api.'
def configure_backend(options):
"""
Establish the database, create an engine if needed, and
register the models.
class Driver():
def __init__(self, options):
self.session = None
self._engine = None
connection_str = options['sql_connection']
model_list = ast.literal_eval(options["backend_entities"])
:param options: Mapping of configuration options
"""
global _ENGINE
if not _ENGINE:
debug = config.get_option(
options, 'debug', type='bool', default=False)
verbose = config.get_option(
options, 'verbose', type='bool', default=False)
timeout = config.get_option(
options, 'sql_idle_timeout', type='int', default=3600)
self._init_engine(connection_str)
self._init_models(model_list)
self._init_session_maker()
if options['sql_connection'] == "sqlite://":
_ENGINE = create_engine(options['sql_connection'],
connect_args={'check_same_thread': False},
poolclass=StaticPool)
def _init_engine(self, connection_str):
if connection_str == "sqlite://":
# in-memory sqlite
self._engine = create_engine(
connection_str,
connect_args={'check_same_thread': False},
poolclass=StaticPool)
else:
_ENGINE = create_engine(options['sql_connection'],
pool_recycle=timeout)
self._engine = create_engine(
connection_str,
pool_recycle=3600)
logger = logging.getLogger('sqlalchemy.engine')
if debug:
logger.setLevel(logging.DEBUG)
elif verbose:
logger.setLevel(logging.INFO)
def _init_models(self, model_list):
tables = []
register_models(options)
for model in model_list:
module = utils.import_module(MODEL_PREFIX + model)
tables.append(module.__table__)
top_models.set_value(model, module)
if module.__api__ is not None:
api_module = utils.import_module(API_PREFIX + module.__api__)
top_api.set_value(module.__api__, api_module.get())
tables_to_create = []
for table in reversed(models.Base.metadata.sorted_tables):
if table in tables:
tables_to_create.append(table)
models.Base.metadata.create_all(self._engine, tables=tables_to_create,
checkfirst=True)
def _init_session_maker(self):
self.session = sessionmaker(
bind=self._engine,
autocommit=True,
expire_on_commit=False)
def get_session(self):
"""Creates a pre-configured database session"""
return self.session()
def reset(self):
"""Unregister models and reset DB engine.
Useful clearing out data before testing
TODO(dolph)::
... but what does this *do*? Issue DROP TABLE statements?
TRUNCATE TABLE? Or is the scope of impact limited to python?
"""
if self._engine is not None:
models.Base.metadata.drop_all(self._engine)
self._engine = None
def get_session(autocommit=True, expire_on_commit=False):
"""Helper method to grab session"""
global _MAKER, _ENGINE
if not _MAKER:
assert _ENGINE
_MAKER = sessionmaker(bind=_ENGINE,
autocommit=autocommit,
expire_on_commit=expire_on_commit)
return _MAKER()
def configure_backend(options):
global _DRIVER
_DRIVER = Driver(options)
def register_models(options):
"""Register Models and create properties"""
global _ENGINE
assert _ENGINE
# Need to decide.Not This is missing
# and prevents foreign key reference checks.
# _ENGINE.execute('pragma foreign_keys=on')
supported_alchemy_models = ast.literal_eval(
options["backend_entities"])
supported_alchemy_tables = []
for supported_alchemy_model in supported_alchemy_models:
model = utils.import_module(MODEL_PREFIX + supported_alchemy_model)
supported_alchemy_tables.append(model.__table__)
top_models.set_value(supported_alchemy_model, model)
if model.__api__ is not None:
model_api = utils.import_module(API_PREFIX + model.__api__)
top_api.set_value(model.__api__, model_api.get())
creation_tables = []
for table in reversed(BASE.metadata.sorted_tables):
if table in supported_alchemy_tables:
creation_tables.append(table)
BASE.metadata.create_all(_ENGINE, tables=creation_tables, checkfirst=True)
def get_session():
global _DRIVER
return _DRIVER.get_session()
def unregister_models():
"""Unregister Models and reset _ENGINE,
useful clearing out data before testing"""
global _ENGINE
if _ENGINE:
BASE.metadata.drop_all(_ENGINE)
_ENGINE = None
global _DRIVER
if _DRIVER:
return _DRIVER.reset()

View File

@ -1 +0,0 @@
from . import endpoint_template, role, tenant, token, user

View File

@ -16,32 +16,91 @@
# under the License.
from keystone.backends.sqlalchemy import get_session, models
from keystone.backends.api import BaseCredentialsAPI
from keystone.backends import api
from keystone.models import Credentials
from keystone.logic.types import fault
class CredentialsAPI(BaseCredentialsAPI):
# pylint: disable=E1103,W0221
class CredentialsAPI(api.BaseCredentialsAPI):
def __init__(self, *args, **kw):
super(CredentialsAPI, self).__init__(*args, **kw)
@staticmethod
def transpose(ref):
""" Transposes field names from domain to sql model"""
if hasattr(api.TENANT, 'uid_to_id'):
if 'tenant_id' in ref:
ref['tenant_id'] = api.TENANT.uid_to_id(ref['tenant_id'])
elif hasattr(ref, 'tenant_id'):
ref.tenant_id = api.TENANT.uid_to_id(ref.tenant_id)
if hasattr(api.USER, 'uid_to_id'):
if 'user_id' in ref:
ref['user_id'] = api.USER.uid_to_id(ref['user_id'])
elif hasattr(ref, 'tenant_id'):
ref.user_id = api.USER.uid_to_id(ref.user_id)
@staticmethod
def to_model(ref):
""" Returns Keystone model object based on SQLAlchemy model"""
if ref:
if hasattr(api.TENANT, 'uid_to_id'):
if 'tenant_id' in ref:
ref['tenant_id'] = api.TENANT.id_to_uid(ref['tenant_id'])
elif hasattr(ref, 'tenant_id'):
ref.tenant_id = api.TENANT.id_to_uid(ref.tenant_id)
if hasattr(api.USER, 'uid_to_id'):
if 'user_id' in ref:
ref['user_id'] = api.USER.id_to_uid(ref['user_id'])
elif hasattr(ref, 'user_id'):
ref.user_id = api.USER.id_to_uid(ref.user_id)
return Credentials(id=ref.id, user_id=ref.user_id,
tenant_id=ref.tenant_id, type=ref.type, key=ref.key,
secret=ref.secret)
@staticmethod
def to_model_list(refs):
return [CredentialsAPI.to_model(ref) for ref in refs]
def create(self, values):
data = values.copy()
CredentialsAPI.transpose(data)
if 'tenant_id' in values:
if data['tenant_id'] is None and values['tenant_id'] is not None:
raise fault.ItemNotFoundFault('Invalid tenant id: %s' % \
values['tenant_id'])
credentials_ref = models.Credentials()
credentials_ref.update(values)
credentials_ref.update(data)
credentials_ref.save()
return credentials_ref
return CredentialsAPI.to_model(credentials_ref)
def get(self, id, session=None):
if not session:
session = get_session()
result = session.query(models.Credentials).filter_by(id=id).first()
return result
return CredentialsAPI.to_model(result)
def get_by_access(self, access, session=None):
if not session:
session = get_session()
result = session.query(models.Credentials).\
filter_by(type="EC2", key=access).first()
return result
return CredentialsAPI.to_model(result)
def delete(self, id, session=None):
if not session:
session = get_session()
with session.begin():
group_ref = self.get(id, session)
session.delete(group_ref)

View File

@ -16,10 +16,28 @@
# under the License.
from keystone.backends.sqlalchemy import get_session, models, aliased
from keystone.backends.api import BaseEndpointTemplateAPI
from keystone.backends import api
class EndpointTemplateAPI(BaseEndpointTemplateAPI):
# pylint: disable=E1103,W0221
class EndpointTemplateAPI(api.BaseEndpointTemplateAPI):
def __init__(self, *args, **kw):
super(EndpointTemplateAPI, self).__init__(*args, **kw)
@staticmethod
def transpose(values):
""" Transposes field names from domain to sql model"""
pass
@staticmethod
def to_model(ref):
""" Returns Keystone model object based on SQLAlchemy model"""
pass
@staticmethod
def to_model_list(refs):
return [EndpointTemplateAPI.to_model(ref) for ref in refs]
def create(self, values):
endpoint_template = models.EndpointTemplates()
endpoint_template.update(values)
@ -45,13 +63,16 @@ class EndpointTemplateAPI(BaseEndpointTemplateAPI):
def get(self, id, session=None):
if not session:
session = get_session()
result = session.query(models.EndpointTemplates).\
filter_by(id=id).first()
return result
def get_all(self, session=None):
if not session:
session = get_session()
return session.query(models.EndpointTemplates).all()
def get_by_service(self, service_id, session=None):
@ -76,7 +97,8 @@ class EndpointTemplateAPI(BaseEndpointTemplateAPI):
models.EndpointTemplates.id.desc()).\
limit(limit).all()
def get_by_service_get_page_markers(self, service_id, marker,\
# pylint: disable=R0912
def get_by_service_get_page_markers(self, service_id, marker, \
limit, session=None):
if not session:
session = get_session()
@ -138,6 +160,7 @@ class EndpointTemplateAPI(BaseEndpointTemplateAPI):
models.EndpointTemplates.id.desc()).\
limit(limit).all()
# pylint: disable=R0912
def get_page_markers(self, marker, limit, session=None):
if not session:
session = get_session()
@ -182,40 +205,55 @@ class EndpointTemplateAPI(BaseEndpointTemplateAPI):
return (prev_page, next_page)
def endpoint_get_by_tenant_get_page(self, tenant_id, marker, limit,
session=None):
session=None):
if not session:
session = get_session()
if isinstance(api.TENANT, models.Tenant):
tenant_id = api.TENANT.uid_to_id(tenant_id)
if marker:
return session.query(models.Endpoints).\
results = session.query(models.Endpoints).\
filter(models.Endpoints.tenant_id == tenant_id).\
filter("id >= :marker").params(
marker='%s' % marker).order_by(
models.Endpoints.id).limit(limit).all()
else:
return session.query(models.Endpoints).\
results = session.query(models.Endpoints).\
filter(models.Endpoints.tenant_id == tenant_id).\
order_by(models.Endpoints.id).limit(limit).all()
if isinstance(api.TENANT, models.Tenant):
for result in results:
result.tenant_id = api.TENANT.id_to_uid(result.tenant_id)
return results
# pylint: disable=R0912
def endpoint_get_by_tenant_get_page_markers(self, tenant_id, marker, limit,
session=None):
session=None):
if not session:
session = get_session()
if isinstance(api.TENANT, models.Tenant):
tenant_id = api.TENANT.uid_to_id(tenant_id)
tba = aliased(models.Endpoints)
first = session.query(tba).\
filter(tba.tenant_id == tenant_id).\
order_by(tba.id).first()
filter(tba.tenant_id == tenant_id).\
order_by(tba.id).first()
last = session.query(tba).\
filter(tba.tenant_id == tenant_id).\
order_by(tba.id.desc()).first()
filter(tba.tenant_id == tenant_id).\
order_by(tba.id.desc()).first()
if first is None:
return (None, None)
if marker is None:
marker = first.id
next_page = session.query(tba).\
filter(tba.tenant_id == tenant_id).\
filter("id>=:marker").params(
marker='%s' % marker).order_by(
tba.id).limit(int(limit)).all()
filter(tba.tenant_id == tenant_id).\
filter("id>=:marker").params(
marker='%s' % marker).order_by(
tba.id).limit(int(limit)).all()
prev_page = session.query(tba).\
filter(tba.tenant_id == tenant_id).\
@ -246,36 +284,61 @@ class EndpointTemplateAPI(BaseEndpointTemplateAPI):
return (prev_page, next_page)
def endpoint_add(self, values):
if isinstance(api.TENANT, models.Tenant):
values.tenant_id = api.TENANT.uid_to_id(values.tenant_id)
endpoints = models.Endpoints()
endpoints.update(values)
endpoints.save()
if isinstance(api.TENANT, models.Tenant):
endpoints.tenant_id = api.TENANT.id_to_uid(endpoints.tenant_id)
return endpoints
def endpoint_get(self, id, session=None):
if not session:
session = get_session()
result = session.query(models.Endpoints).\
filter_by(id=id).first()
filter_by(id=id).first()
if isinstance(api.TENANT, models.Tenant):
if result:
result.tenant_id = api.TENANT.id_to_uid(result.tenant_id)
return result
def endpoint_get_by_tenant(self, tenant_id, session=None):
if not session:
session = get_session()
if isinstance(api.TENANT, models.Tenant):
tenant_id = api.TENANT.uid_to_id(tenant_id)
result = session.query(models.Endpoints).\
filter_by(tenant_id=tenant_id).first()
if isinstance(api.TENANT, models.Tenant):
if result:
result.tenant_id = api.TENANT.id_to_uid(result.tenant_id)
return result
def endpoint_get_by_endpoint_template(
self, endpoint_template_id, session=None):
if not session:
session = get_session()
result = session.query(models.Endpoints).\
filter_by(endpoint_template_id=endpoint_template_id).all()
return result
def endpoint_delete(self, id, session=None):
if not session:
session = get_session()
with session.begin():
endpoints = self.endpoint_get(id, session)
if endpoints:

View File

@ -16,10 +16,14 @@
# under the License.
from keystone.backends.sqlalchemy import get_session, models
from keystone.backends.api import BaseRoleAPI
from keystone.backends import api
class RoleAPI(BaseRoleAPI):
# pylint: disable=E1103,W0221
class RoleAPI(api.BaseRoleAPI):
def __init__(self, *args, **kw):
super(RoleAPI, self).__init__(*args, **kw)
# pylint: disable=W0221
def create(self, values):
role = models.Role()
@ -71,6 +75,12 @@ class RoleAPI(BaseRoleAPI):
def ref_get_page(self, marker, limit, user_id, tenant_id, session=None):
if not session:
session = get_session()
if hasattr(api.USER, 'uid_to_id'):
user_id = api.USER.uid_to_id(user_id)
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = api.TENANT.uid_to_id(tenant_id)
query = session.query(models.UserRoleAssociation).\
filter_by(user_id=user_id)
if tenant_id:
@ -78,39 +88,83 @@ class RoleAPI(BaseRoleAPI):
else:
query = query.filter("tenant_id is null")
if marker:
return query.filter("id>:marker").params(\
results = query.filter("id>:marker").params(\
marker='%s' % marker).order_by(\
models.UserRoleAssociation.id.desc()).limit(limit).all()
else:
return query.order_by(\
results = query.order_by(\
models.UserRoleAssociation.id.desc()).limit(limit).all()
for result in results:
if hasattr(api.USER, 'uid_to_id'):
result.user_id = api.USER.id_to_uid(result.user_id)
if hasattr(api.TENANT, 'uid_to_id'):
result.tenant_id = api.TENANT.id_to_uid(result.tenant_id)
return results
def ref_get_all_global_roles(self, user_id, session=None):
if not session:
session = get_session()
return session.query(models.UserRoleAssociation).\
if hasattr(api.USER, 'uid_to_id'):
user_id = api.USER.uid_to_id(user_id)
results = session.query(models.UserRoleAssociation).\
filter_by(user_id=user_id).filter("tenant_id is null").all()
for result in results:
if hasattr(api.USER, 'uid_to_id'):
result.user_id = api.USER.id_to_uid(result.user_id)
if hasattr(api.TENANT, 'uid_to_id'):
result.tenant_id = api.TENANT.id_to_uid(result.tenant_id)
return results
def ref_get_all_tenant_roles(self, user_id, tenant_id, session=None):
if not session:
session = get_session()
return session.query(models.UserRoleAssociation).\
if hasattr(api.USER, 'uid_to_id'):
user_id = api.USER.uid_to_id(user_id)
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = api.TENANT.uid_to_id(tenant_id)
results = session.query(models.UserRoleAssociation).\
filter_by(user_id=user_id).filter_by(tenant_id=tenant_id).all()
for result in results:
if hasattr(api.USER, 'uid_to_id'):
result.user_id = api.USER.id_to_uid(result.user_id)
if hasattr(api.TENANT, 'uid_to_id'):
result.tenant_id = api.TENANT.id_to_uid(result.tenant_id)
return results
def ref_get(self, id, session=None):
if not session:
session = get_session()
result = session.query(models.UserRoleAssociation).filter_by(id=id).\
first()
if result:
if hasattr(api.USER, 'uid_to_id'):
result.user_id = api.USER.id_to_uid(result.user_id)
if hasattr(api.TENANT, 'uid_to_id'):
result.tenant_id = api.TENANT.id_to_uid(result.tenant_id)
return result
def ref_delete(self, id, session=None):
if not session:
session = get_session()
with session.begin():
role_ref = self.ref_get(id, session)
session.delete(role_ref)
# pylint: disable=R0912
def get_page_markers(self, marker, limit, session=None):
if not session:
session = get_session()
@ -148,10 +202,17 @@ class RoleAPI(BaseRoleAPI):
next_page = next_page.id
return (prev_page, next_page)
# pylint: disable=R0912
def ref_get_page_markers(self, user_id, tenant_id, marker,
limit, session=None):
limit, session=None):
if not session:
session = get_session()
if hasattr(api.USER, 'uid_to_id'):
user_id = api.USER.uid_to_id(user_id)
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = api.TENANT.uid_to_id(tenant_id)
query = session.query(models.UserRoleAssociation).filter_by(\
user_id=user_id)
if tenant_id:
@ -202,13 +263,27 @@ class RoleAPI(BaseRoleAPI):
def ref_get_by_role(self, role_id, session=None):
if not session:
session = get_session()
result = session.query(models.UserRoleAssociation).\
results = session.query(models.UserRoleAssociation).\
filter_by(role_id=role_id).all()
return result
for result in results:
if hasattr(api.USER, 'uid_to_id'):
result.user_id = api.USER.id_to_uid(result.user_id)
if hasattr(api.TENANT, 'uid_to_id'):
result.tenant_id = api.TENANT.id_to_uid(result.tenant_id)
return results
def ref_get_by_user(self, user_id, role_id, tenant_id, session=None):
if not session:
session = get_session()
if hasattr(api.USER, 'uid_to_id'):
user_id = api.USER.uid_to_id(user_id)
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = api.TENANT.uid_to_id(tenant_id)
if tenant_id is None:
result = session.query(models.UserRoleAssociation).\
filter_by(user_id=user_id).filter("tenant_id is null").\
@ -217,6 +292,13 @@ class RoleAPI(BaseRoleAPI):
result = session.query(models.UserRoleAssociation).\
filter_by(user_id=user_id).filter_by(tenant_id=tenant_id).\
filter_by(role_id=role_id).first()
if result:
if hasattr(api.USER, 'uid_to_id'):
result.user_id = api.USER.id_to_uid(result.user_id)
if hasattr(api.TENANT, 'uid_to_id'):
result.tenant_id = api.TENANT.id_to_uid(result.tenant_id)
return result

View File

@ -16,12 +16,13 @@
# under the License.
from keystone.backends.sqlalchemy import get_session, models
from keystone.backends.api import BaseServiceAPI
from keystone.backends import api
class ServiceAPI(BaseServiceAPI):
def __init__(self):
pass
# pylint: disable=E1103,W0221
class ServiceAPI(api.BaseServiceAPI):
def __init__(self, *args, **kw):
super(ServiceAPI, self).__init__(*args, **kw)
# pylint: disable=W0221
def create(self, values):

View File

@ -15,34 +15,128 @@
# License for the specific language governing permissions and limitations
# under the License.
import uuid
from keystone.backends.sqlalchemy import get_session, models, aliased
from keystone.backends.api import BaseTenantAPI
from keystone.backends import api
from keystone.models import Tenant
class TenantAPI(BaseTenantAPI):
# pylint: disable=E1103,W0221
class TenantAPI(api.BaseTenantAPI):
def __init__(self, *args, **kw):
super(TenantAPI, self).__init__(*args, **kw)
# pylint: disable=W0221
@staticmethod
def transpose(values):
""" Handles transposing field names from Keystone model to
sqlalchemy mode
Differences:
desc <-> description
id <-> uid (coming soon)
"""
if 'id' in values:
values['uid'] = values['id']
del values['id']
if 'description' in values:
values['desc'] = values['description']
del values['description']
if 'enabled' in values:
if values['enabled'] in [1, 'true', 'True', True]:
values['enabled'] = 1
else:
values['enabled'] = 0
@staticmethod
def to_model(ref):
""" Returns Keystone model object based on SQLAlchemy model"""
if ref:
return Tenant(id=ref.uid, name=ref.name, description=ref.desc,
enabled=bool(ref.enabled))
@staticmethod
def to_model_list(refs):
return [TenantAPI.to_model(ref) for ref in refs]
def create(self, values):
values = values.copy()
TenantAPI.transpose(values)
tenant_ref = models.Tenant()
tenant_ref.update(values)
if tenant_ref.uid is None:
tenant_ref.uid = uuid.uuid4().hex
tenant_ref.save()
return tenant_ref
return TenantAPI.to_model(tenant_ref)
def get(self, id, session=None):
"""Returns a tenant by ID.
.warning::
Internally, the provided ID is matched against the ``tenants.UID``,
not the PK (``tenants.id``) column.
For PK lookups from within the sqlalchemy backend,
use ``_get_by_id()`` instead.
"""
session = session or get_session()
result = session.query(models.Tenant).filter_by(uid=id).first()
return TenantAPI.to_model(result)
@staticmethod
def _get_by_id(id, session=None):
"""Returns a tenant by ID (PK).
.warning::
The provided ID is matched against the PK (``tenants.ID``).
This is **only** for use within the sqlalchemy backend.
"""
session = session or get_session()
return session.query(models.Tenant).filter_by(id=id).first()
@staticmethod
def id_to_uid(id, session=None):
session = session or get_session()
tenant = session.query(models.Tenant).filter_by(id=id).first()
return tenant.uid if tenant else None
@staticmethod
def uid_to_id(uid, session=None):
session = session or get_session()
tenant = session.query(models.Tenant).filter_by(uid=uid).first()
return tenant.id if tenant else None
def get_by_name(self, name, session=None):
session = session or get_session()
return session.query(models.Tenant).filter_by(name=name).first()
result = session.query(models.Tenant).filter_by(name=name).first()
return TenantAPI.to_model(result)
def get_all(self, session=None):
if not session:
session = get_session()
return session.query(models.Tenant).all()
results = session.query(models.Tenant).all()
return TenantAPI.to_model_list(results)
def tenants_for_user_get_page(self, user, marker, limit, session=None):
if not session:
session = get_session()
if hasattr(api.USER, 'uid_to_id'):
user.id = api.USER.uid_to_id(user.id)
if hasattr(api.TENANT, 'uid_to_id'):
user.tenant_id = api.TENANT.uid_to_id(user.tenant_id)
ura = aliased(models.UserRoleAssociation)
tenant = aliased(models.Tenant)
q1 = session.query(tenant).join((ura, ura.tenant_id == tenant.id)).\
@ -50,16 +144,25 @@ class TenantAPI(BaseTenantAPI):
q2 = session.query(tenant).filter(tenant.id == user.tenant_id)
q3 = q1.union(q2)
if marker:
return q3.filter("tenant.id>:marker").params(\
results = q3.filter("tenant.id>:marker").params(\
marker='%s' % marker).order_by(\
tenant.id.desc()).limit(limit).all()
else:
return q3.order_by(tenant.id.desc()).limit(limit).all()
results = q3.order_by(tenant.id.desc()).limit(limit).all()
return TenantAPI.to_model_list(results)
# pylint: disable=R0912
def tenants_for_user_get_page_markers(self, user, marker, limit,
session=None):
if not session:
session = get_session()
if hasattr(api.USER, 'uid_to_id'):
user.id = api.USER.uid_to_id(user.id)
if hasattr(api.TENANT, 'uid_to_id'):
user.tenant_id = api.TENANT.uid_to_id(user.tenant_id)
ura = aliased(models.UserRoleAssociation)
tenant = aliased(models.Tenant)
q1 = session.query(tenant).join((ura, ura.tenant_id == tenant.id)).\
@ -111,6 +214,7 @@ class TenantAPI(BaseTenantAPI):
return session.query(models.Tenant).order_by(\
models.Tenant.id.desc()).limit(limit).all()
# pylint: disable=R0912
def get_page_markers(self, marker, limit, session=None):
if not session:
session = get_session()
@ -157,6 +261,10 @@ class TenantAPI(BaseTenantAPI):
def is_empty(self, id, session=None):
if not session:
session = get_session()
if hasattr(api.TENANT, 'uid_to_id'):
id = self.uid_to_id(id)
a_user = session.query(models.UserRoleAssociation).filter_by(\
tenant_id=id).first()
if a_user != None:
@ -169,28 +277,44 @@ class TenantAPI(BaseTenantAPI):
def update(self, id, values, session=None):
if not session:
session = get_session()
if hasattr(api.TENANT, 'uid_to_id'):
id = self.uid_to_id(id)
data = values.copy()
TenantAPI.transpose(data)
with session.begin():
tenant_ref = self.get(id, session)
tenant_ref.update(values)
tenant_ref = self._get_by_id(id, session)
tenant_ref.update(data)
tenant_ref.save(session=session)
return self.get(id, session)
def delete(self, id, session=None):
if not session:
session = get_session()
if hasattr(api.TENANT, 'uid_to_id'):
id = self.uid_to_id(id)
with session.begin():
tenant_ref = self.get(id, session)
tenant_ref = self._get_by_id(id, session)
session.delete(tenant_ref)
def get_all_endpoints(self, tenant_id, session=None):
if not session:
session = get_session()
endpointTemplates = aliased(models.EndpointTemplates)
q = session.query(endpointTemplates).\
filter(endpointTemplates.is_global == True)
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = self.uid_to_id(tenant_id)
endpoint_templates = aliased(models.EndpointTemplates)
q = session.query(endpoint_templates).\
filter(endpoint_templates.is_global == True)
if tenant_id:
ep = aliased(models.Endpoints)
q1 = session.query(endpointTemplates).join((ep,
ep.endpoint_template_id == endpointTemplates.id)).\
q1 = session.query(endpoint_templates).join((ep,
ep.endpoint_template_id == endpoint_templates.id)).\
filter(ep.tenant_id == tenant_id)
q = q.union(q1)
return q.all()
@ -198,9 +322,21 @@ class TenantAPI(BaseTenantAPI):
def get_role_assignments(self, tenant_id, session=None):
if not session:
session = get_session()
return session.query(models.UserRoleAssociation).\
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = TenantAPI.uid_to_id(tenant_id)
results = session.query(models.UserRoleAssociation).\
filter_by(tenant_id=tenant_id)
for result in results:
if hasattr(api.USER, 'uid_to_id'):
result.user_id = api.USER.id_to_uid(result.user_id)
if hasattr(api.TENANT, 'uid_to_id'):
result.tenant_id = api.TENANT.id_to_uid(result.tenant_id)
return results
def get():
return TenantAPI()

View File

@ -16,25 +16,73 @@
# under the License.
from keystone.backends.sqlalchemy import get_session, models
from keystone.backends.api import BaseTokenAPI
from keystone.backends import api
from keystone.models import Token
class TokenAPI(BaseTokenAPI):
# pylint: disable=E1103,W0221
class TokenAPI(api.BaseTokenAPI):
def __init__(self, *args, **kw):
super(TokenAPI, self).__init__(*args, **kw)
@staticmethod
def transpose(ref):
""" Transposes field names from domain to sql model"""
if hasattr(api.TENANT, 'uid_to_id'):
if 'tenant_id' in ref:
ref['tenant_id'] = api.TENANT.uid_to_id(ref['tenant_id'])
elif hasattr(ref, 'tenant_id'):
ref.tenant_id = api.TENANT.uid_to_id(ref.tenant_id)
if hasattr(api.USER, 'uid_to_id'):
if 'user_id' in ref:
ref['user_id'] = api.USER.uid_to_id(ref['user_id'])
elif hasattr(ref, 'tenant_id'):
ref.user_id = api.USER.uid_to_id(ref.user_id)
@staticmethod
def to_model(ref):
""" Returns Keystone model object based on SQLAlchemy model"""
if ref:
if hasattr(api.TENANT, 'uid_to_id'):
if 'tenant_id' in ref:
ref['tenant_id'] = api.TENANT.id_to_uid(ref['tenant_id'])
elif hasattr(ref, 'tenant_id'):
ref.tenant_id = api.TENANT.id_to_uid(ref.tenant_id)
if hasattr(api.USER, 'uid_to_id'):
if 'user_id' in ref:
ref['user_id'] = api.USER.id_to_uid(ref['user_id'])
elif hasattr(ref, 'user_id'):
ref.user_id = api.USER.id_to_uid(ref.user_id)
return Token(id=ref.id, user_id=ref.user_id, expires=ref.expires,
tenant_id=ref.tenant_id)
@staticmethod
def to_model_list(refs):
return [TokenAPI.to_model(ref) for ref in refs]
def create(self, values):
data = values.copy()
TokenAPI.transpose(data)
token_ref = models.Token()
token_ref.update(values)
token_ref.update(data)
token_ref.save()
return token_ref
return TokenAPI.to_model(token_ref)
def get(self, id, session=None):
if not session:
session = get_session()
result = session.query(models.Token).filter_by(id=id).first()
return result
return TokenAPI.to_model(result)
def delete(self, id, session=None):
if not session:
session = get_session()
with session.begin():
token_ref = self.get(id, session)
session.delete(token_ref)
@ -42,23 +90,36 @@ class TokenAPI(BaseTokenAPI):
def get_for_user(self, user_id, session=None):
if not session:
session = get_session()
if hasattr(api.USER, 'uid_to_id'):
user_id = api.USER.uid_to_id(user_id)
result = session.query(models.Token).filter_by(
user_id=user_id, tenant_id=None).order_by("expires desc").first()
return result
return TokenAPI.to_model(result)
def get_for_user_by_tenant(self, user_id, tenant_id, session=None):
if not session:
session = get_session()
if hasattr(api.USER, 'uid_to_id'):
user_id = api.USER.uid_to_id(user_id)
result = session.query(models.Token).\
filter_by(user_id=user_id, tenant_id=tenant_id).\
order_by("expires desc").\
first()
return result
return TokenAPI.to_model(result)
def get_all(self, session=None):
if not session:
session = get_session()
return session.query(models.Token).all()
results = session.query(models.Token).all()
return TokenAPI.to_model_list(results)
def get():

View File

@ -15,58 +15,143 @@
# License for the specific language governing permissions and limitations
# under the License.
import uuid
import keystone.backends.backendutils as utils
from keystone.backends.sqlalchemy import get_session, models, aliased, \
joinedload
from keystone.backends.api import BaseUserAPI
from keystone.backends import api
from keystone.models import User
class UserAPI(BaseUserAPI):
# pylint: disable=E1103,W0221,W0223
class UserAPI(api.BaseUserAPI):
def __init__(self, *args, **kw):
super(UserAPI, self).__init__(*args, **kw)
@staticmethod
def transpose(ref):
""" Transposes field names from domain to sql model"""
if 'id' in ref:
ref['uid'] = ref['id']
del ref['id']
if hasattr(api.TENANT, 'uid_to_id'):
if 'tenant_id' in ref:
ref['tenant_id'] = api.TENANT.uid_to_id(ref['tenant_id'])
elif hasattr(ref, 'tenant_id'):
ref.tenant_id = api.TENANT.uid_to_id(ref.tenant_id)
if hasattr(ref, 'enabled'):
if ref.enabled in [1, 'true', 'True', True]:
ref.enabled = 1
else:
ref.enabled = 0
@staticmethod
def to_model(ref):
""" Returns Keystone model object based on SQLAlchemy model"""
if ref:
if hasattr(api.TENANT, 'uid_to_id'):
if 'tenant_id' in ref:
ref['tenant_id'] = api.TENANT.id_to_uid(ref['tenant_id'])
elif hasattr(ref, 'tenant_id'):
ref.tenant_id = api.TENANT.id_to_uid(ref.tenant_id)
return User(id=ref.uid, password=ref.password, name=ref.name,
tenant_id=ref.tenant_id, email=ref.email,
enabled=bool(ref.enabled))
@staticmethod
def to_model_list(refs):
return [UserAPI.to_model(ref) for ref in refs]
# pylint: disable=W0221
def get_all(self, session=None):
if not session:
session = get_session()
return session.query(models.User)
results = session.query(models.User)
return UserAPI.to_model_list(results)
def create(self, values):
user_ref = models.User()
UserAPI.transpose(values)
utils.set_hashed_password(values)
user_ref.update(values)
if user_ref.uid is None:
user_ref.uid = uuid.uuid4().hex
user_ref.save()
return user_ref
return UserAPI.to_model(user_ref)
def get(self, id, session=None):
if not session:
session = get_session()
user = session.query(models.User).filter_by(id=id).first()
return user or self.get_by_name(id, session)
result = session.query(models.User).filter_by(uid=id).first()
return UserAPI.to_model(result)
@staticmethod
def _get_by_id(id, session=None):
"""Only for use by the sql backends
- Queries by PK ID
- Doesn't wrap result with domain layer models
"""
if not session:
session = get_session()
return session.query(models.User).filter_by(id=id).first()
@staticmethod
def id_to_uid(id, session=None):
session = session or get_session()
user = session.query(models.User).filter_by(id=id).first()
return user.uid if user else None
@staticmethod
def uid_to_id(uid, session=None):
session = session or get_session()
user = session.query(models.User).filter_by(uid=uid).first()
return user.id if user else None
def get_by_name(self, name, session=None):
if not session:
session = get_session()
return session.query(models.User).filter_by(name=name).first()
result = session.query(models.User).filter_by(name=name).first()
return UserAPI.to_model(result)
def get_by_email(self, email, session=None):
if not session:
session = get_session()
return session.query(models.User).filter_by(email=email).first()
result = session.query(models.User).filter_by(email=email).first()
return UserAPI.to_model(result)
def get_page(self, marker, limit, session=None):
if not session:
session = get_session()
if marker:
return session.query(models.User).filter("id>:marker").params(\
results = session.query(models.User).filter("id>:marker").params(\
marker='%s' % marker).order_by(\
models.User.id.desc()).limit(limit).all()
else:
return session.query(models.User).order_by(\
results = session.query(models.User).order_by(\
models.User.id.desc()).limit(limit).all()
return UserAPI.to_model_list(results)
# pylint: disable=R0912
def get_page_markers(self, marker, limit, session=None):
if not session:
session = get_session()
first = session.query(models.User).order_by(\
models.User.id).first()
last = session.query(models.User).order_by(\
@ -104,16 +189,32 @@ class UserAPI(BaseUserAPI):
def user_roles_by_tenant(self, user_id, tenant_id, session=None):
if not session:
session = get_session()
result = session.query(models.UserRoleAssociation).\
if hasattr(api.USER, 'uid_to_id'):
user_id = api.USER.uid_to_id(user_id)
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = api.TENANT.uid_to_id(tenant_id)
results = session.query(models.UserRoleAssociation).\
filter_by(user_id=user_id, tenant_id=tenant_id).\
options(joinedload('roles'))
return result
for result in results:
if hasattr(api.USER, 'id_to_uid'):
result.user_id = api.USER.id_to_uid(result.user_id)
if hasattr(api.TENANT, 'id_to_uid'):
result.tenant_id = api.TENANT.id_to_uid(result.tenant_id)
return results
def update(self, id, values, session=None):
if not session:
session = get_session()
UserAPI.transpose(values)
with session.begin():
user_ref = self.get(id, session)
user_ref = session.query(models.User).filter_by(uid=id).first()
utils.set_hashed_password(values)
user_ref.update(values)
user_ref.save(session=session)
@ -121,32 +222,51 @@ class UserAPI(BaseUserAPI):
def delete(self, id, session=None):
if not session:
session = get_session()
with session.begin():
user_ref = self.get(id, session)
user_ref = session.query(models.User).filter_by(uid=id).first()
session.delete(user_ref)
def get_by_tenant(self, id, tenant_id, session=None):
if not session:
session = get_session()
uid = id
if hasattr(api.USER, 'uid_to_id'):
id = api.USER.uid_to_id(uid)
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = api.TENANT.uid_to_id(tenant_id)
# Most common use case: user lives in tenant
user = session.query(models.User).\
filter_by(id=id, tenant_id=tenant_id).first()
if user:
return user
return UserAPI.to_model(user)
# Find user through grants to this tenant
result = session.query(models.UserRoleAssociation).\
filter_by(tenant_id=tenant_id, user_id=id).first()
if result:
return self.get(id, session)
return self.get(uid, session)
else:
return None
def delete_tenant_user(self, id, tenant_id, session=None):
if not session:
session = get_session()
uid = id
tenant_uid = tenant_id
if hasattr(api.USER, 'uid_to_id'):
id = api.USER.uid_to_id(uid)
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = api.TENANT.uid_to_id(tenant_id)
with session.begin():
users_tenant_ref = self.users_get_by_tenant(id, tenant_id, session)
users_tenant_ref = self.users_get_by_tenant(uid, tenant_uid,
session)
if users_tenant_ref is not None:
for user_tenant_ref in users_tenant_ref:
session.delete(user_tenant_ref)
@ -154,38 +274,63 @@ class UserAPI(BaseUserAPI):
def users_get_by_tenant(self, user_id, tenant_id, session=None):
if not session:
session = get_session()
result = session.query(models.User).filter_by(id=user_id,
if hasattr(api.USER, 'uid_to_id'):
user_id = api.USER.uid_to_id(user_id)
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = api.TENANT.uid_to_id(tenant_id)
results = session.query(models.User).filter_by(id=user_id,
tenant_id=tenant_id)
return result
return UserAPI.to_model_list(results)
def user_role_add(self, values):
if hasattr(api.USER, 'uid_to_id'):
values['user_id'] = api.USER.uid_to_id(values['user_id'])
if hasattr(api.TENANT, 'uid_to_id'):
values['tenant_id'] = api.TENANT.uid_to_id(values['tenant_id'])
user_role_ref = models.UserRoleAssociation()
user_role_ref.update(values)
user_role_ref.save()
if hasattr(api.USER, 'uid_to_id'):
user_role_ref.user_id = api.USER.uid_to_id(user_role_ref.user_id)
if hasattr(api.TENANT, 'uid_to_id'):
user_role_ref.tenant_id = api.TENANT.uid_to_id(
user_role_ref.tenant_id)
return user_role_ref
def user_get_update(self, id, session=None):
if not session:
session = get_session()
return session.query(models.User).filter_by(id=id).first()
result = session.query(models.User).filter_by(uid=id).first()
return UserAPI.to_model(result)
def users_get_page(self, marker, limit, session=None):
if not session:
session = get_session()
user = aliased(models.User)
if marker:
return session.query(user).\
results = session.query(user).\
filter("id>=:marker").params(
marker='%s' % marker).order_by(
"id").limit(limit).all()
else:
return session.query(user).\
results = session.query(user).\
order_by("id").limit(limit).all()
def users_get_page_markers(self, marker, limit, \
session=None):
return UserAPI.to_model_list(results)
# pylint: disable=R0912
def users_get_page_markers(self, marker, limit, session=None):
if not session:
session = get_session()
user = aliased(models.User)
first = session.query(user).\
order_by(user.id).first()
@ -233,6 +378,10 @@ class UserAPI(BaseUserAPI):
# Also the user lookup is nasty and potentially injectiable.
if not session:
session = get_session()
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = api.TENANT.uid_to_id(tenant_id)
user = aliased(models.UserRoleAssociation)
query = session.query(user).\
filter("tenant_id = :tenant_id").\
@ -258,17 +407,24 @@ class UserAPI(BaseUserAPI):
users = session.query(models.User).\
filter("id in ('%s')" % "','".join(user_ids)).\
all()
for usr in users:
usr.tenant_roles = set()
for role in usr.roles:
if role.tenant_id == tenant_id:
usr.tenant_roles.add(role.role_id)
return users
return UserAPI.to_model_list(users)
# pylint: disable=R0912
def users_get_by_tenant_get_page_markers(self, tenant_id, \
role_id, marker, limit, session=None):
if not session:
session = get_session()
if hasattr(api.TENANT, 'uid_to_id'):
tenant_id = api.TENANT.uid_to_id(tenant_id)
user = aliased(models.UserRoleAssociation)
query = session.query(user).\
filter(user.tenant_id == tenant_id)

View File

@ -16,9 +16,11 @@ meta = sqlalchemy.MetaData()
def upgrade(migrate_engine):
meta.bind = migrate_engine
# pylint: disable=E1101
sqlalchemy.Table('token', meta).rename('tokens')
def downgrade(migrate_engine):
meta.bind = migrate_engine
# pylint: disable=E1101
sqlalchemy.Table('tokens', meta).rename('token')

View File

@ -0,0 +1,38 @@
# pylint: disable=C0103
import sqlalchemy
import migrate
meta = sqlalchemy.MetaData()
# define the previous state of tenants
tenant = {}
tenant['id'] = sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True,
autoincrement=True)
tenant['name'] = sqlalchemy.Column('name', sqlalchemy.String(255), unique=True)
tenant['desc'] = sqlalchemy.Column('desc', sqlalchemy.String(255))
tenant['enabled'] = sqlalchemy.Column('enabled', sqlalchemy.Integer)
tenants = sqlalchemy.Table('tenants', meta, *tenant.values())
# this column will become unique/non-nullable after populating it
tenant_uid = sqlalchemy.Column('uid', sqlalchemy.String(255),
unique=False, nullable=True)
def upgrade(migrate_engine):
meta.bind = migrate_engine
migrate.create_column(tenant_uid, tenants)
assert tenants.c.uid is tenant_uid
def downgrade(migrate_engine):
meta.bind = migrate_engine
migrate.drop_column(tenant_uid, tenants)
assert not hasattr(tenants.c, 'uid')

View File

@ -0,0 +1,40 @@
"""
Data migration to populate tenants.uid with existing tenants.id values.
"""
# pylint: disable=C0103
import sqlalchemy
meta = sqlalchemy.MetaData()
# define the previous state of tenants
tenant = {}
tenant['id'] = sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True,
autoincrement=True)
tenant['uid'] = sqlalchemy.Column('uid', sqlalchemy.String(255), unique=False,
nullable=True)
tenant['name'] = sqlalchemy.Column('name', sqlalchemy.String(255), unique=True)
tenant['desc'] = sqlalchemy.Column('desc', sqlalchemy.String(255))
tenant['enabled'] = sqlalchemy.Column('enabled', sqlalchemy.Integer)
tenants = sqlalchemy.Table('tenants', meta, *tenant.values())
def upgrade(migrate_engine):
meta.bind = migrate_engine
dtenants = tenants.select().execute()
for dtenant in dtenants:
whereclause = "`id`='%s'" % (dtenant.id)
values = {'uid': str(dtenant.id)}
tenants.update(whereclause=whereclause, values=values).execute()
def downgrade(migrate_engine):
meta.bind = migrate_engine
tenants.update(values={'uid': None}).execute()

View File

@ -0,0 +1,59 @@
"""
Schema migration to enforce uniqueness on tenants.uid
"""
# pylint: disable=C0103
import sqlalchemy
import migrate
from migrate.changeset import constraint
meta = sqlalchemy.MetaData()
# define the previous state of tenants
tenant = {}
tenant['id'] = sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True,
autoincrement=True)
tenant['uid'] = sqlalchemy.Column('uid', sqlalchemy.String(255), unique=False,
nullable=True)
tenant['name'] = sqlalchemy.Column('name', sqlalchemy.String(255), unique=True)
tenant['desc'] = sqlalchemy.Column('desc', sqlalchemy.String(255))
tenant['enabled'] = sqlalchemy.Column('enabled', sqlalchemy.Integer)
tenants = sqlalchemy.Table('tenants', meta, *tenant.values())
unique_constraint = constraint.UniqueConstraint(tenant['uid'])
def upgrade(migrate_engine):
meta.bind = migrate_engine
tenant['uid'].alter(nullable=False)
assert not tenants.c.uid.nullable
unique_constraint.create()
def downgrade(migrate_engine):
meta.bind = migrate_engine
try:
# this is NOT supported in sqlite!
# but let's try anyway, in case it is
unique_constraint.drop()
except migrate.exceptions.NotSupportedError, e:
if migrate_engine.name == 'sqlite':
# skipping the constraint drop doesn't seem to cause any issues
# *in sqlite*
# as constraints are only checked on row insert/update,
# and don't apply to nulls.
print 'WARNING: Skipping dropping unique constraint ' \
'from `tenants`, UNIQUE (uid)'
else:
raise e
tenant['uid'].alter(nullable=True)
assert tenants.c.uid.nullable

View File

@ -0,0 +1,40 @@
# pylint: disable=C0103
import sqlalchemy
import migrate
meta = sqlalchemy.MetaData()
# define the previous state of users
user = {}
user['id'] = sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True,
autoincrement=True)
user['name'] = sqlalchemy.Column('name', sqlalchemy.String(255), unique=True)
user['password'] = sqlalchemy.Column('password', sqlalchemy.String(255))
user['email'] = sqlalchemy.Column('email', sqlalchemy.String(255))
user['enabled'] = sqlalchemy.Column('enabled', sqlalchemy.Integer)
user['tenant_id'] = sqlalchemy.Column('tenant_id', sqlalchemy.Integer)
users = sqlalchemy.Table('users', meta, *user.values())
# this column will become unique/non-nullable after populating it
user_uid = sqlalchemy.Column('uid', sqlalchemy.String(255),
unique=False, nullable=True)
def upgrade(migrate_engine):
meta.bind = migrate_engine
migrate.create_column(user_uid, users)
assert users.c.uid is user_uid
def downgrade(migrate_engine):
meta.bind = migrate_engine
migrate.drop_column(user_uid, users)
assert not hasattr(users.c, 'uid')

View File

@ -0,0 +1,42 @@
"""
Data migration to populate users.uid with existing users.id values.
"""
# pylint: disable=C0103
import sqlalchemy
meta = sqlalchemy.MetaData()
# define the previous state of users
user = {}
user['id'] = sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True,
autoincrement=True)
user['uid'] = sqlalchemy.Column('uid', sqlalchemy.String(255), unique=False,
nullable=True)
user['name'] = sqlalchemy.Column('name', sqlalchemy.String(255), unique=True)
user['password'] = sqlalchemy.Column('password', sqlalchemy.String(255))
user['email'] = sqlalchemy.Column('email', sqlalchemy.String(255))
user['enabled'] = sqlalchemy.Column('enabled', sqlalchemy.Integer)
user['tenant_id'] = sqlalchemy.Column('tenant_id', sqlalchemy.Integer)
users = sqlalchemy.Table('users', meta, *user.values())
def upgrade(migrate_engine):
meta.bind = migrate_engine
dusers = users.select().execute()
for duser in dusers:
whereclause = "`id`='%s'" % (duser.id)
values = {'uid': str(duser.id)}
users.update(whereclause=whereclause, values=values).execute()
def downgrade(migrate_engine):
meta.bind = migrate_engine
users.update(values={'uid': None}).execute()

View File

@ -0,0 +1,60 @@
"""
Schema migration to enforce uniqueness on users.uid
"""
# pylint: disable=C0103
import sqlalchemy
import migrate
from migrate.changeset import constraint
meta = sqlalchemy.MetaData()
# define the previous state of users
user = {}
user['id'] = sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True,
autoincrement=True)
user['uid'] = sqlalchemy.Column('uid', sqlalchemy.String(255), unique=False,
nullable=True)
user['name'] = sqlalchemy.Column('name', sqlalchemy.String(255), unique=True)
user['password'] = sqlalchemy.Column('password', sqlalchemy.String(255))
user['email'] = sqlalchemy.Column('email', sqlalchemy.String(255))
user['enabled'] = sqlalchemy.Column('enabled', sqlalchemy.Integer)
user['tenant_id'] = sqlalchemy.Column('tenant_id', sqlalchemy.Integer)
users = sqlalchemy.Table('users', meta, *user.values())
unique_constraint = constraint.UniqueConstraint(user['uid'])
def upgrade(migrate_engine):
meta.bind = migrate_engine
user['uid'].alter(nullable=False)
assert not users.c.uid.nullable
unique_constraint.create()
def downgrade(migrate_engine):
meta.bind = migrate_engine
try:
# this is NOT supported in sqlite!
# but let's try anyway, in case it is
unique_constraint.drop()
except migrate.exceptions.NotSupportedError, e:
if migrate_engine.name == 'sqlite':
# skipping the constraint drop doesn't seem to cause any issues
# *in sqlite*
# as constraints are only checked on row insert/update,
# and don't apply to nulls.
print 'WARNING: Skipping dropping unique constraint ' \
'from `users`, UNIQUE (uid)'
else:
raise e
user['uid'].alter(nullable=True)
assert users.c.uid.nullable

View File

@ -0,0 +1,132 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# 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.
import logging
import os
from migrate.versioning import api as versioning_api
# See LP bug #719834. sqlalchemy-migrate changed location of
# exceptions.py after 0.6.0.
try:
from migrate.versioning import exceptions as versioning_exceptions
except ImportError:
from migrate import exceptions as versioning_exceptions
from keystone.logic.types import fault
logger = logging.getLogger('keystone.backends.sqlalchemy.migration')
def db_version(options):
"""
Return the database's current migration number
:param options: options dict
:retval version number
"""
repo_path = get_migrate_repo_path()
sql_connection = options['sql_connection']
print repo_path, sql_connection
try:
return versioning_api.db_version(sql_connection, repo_path)
except versioning_exceptions.DatabaseNotControlledError:
msg = (_("database '%(sql_connection)s' is not under "
"migration control") % locals())
raise fault.DatabaseMigrationError(msg)
def upgrade(options, version=None):
"""
Upgrade the database's current migration level
:param options: options dict
:param version: version to upgrade (defaults to latest)
:retval version number
"""
db_version(options) # Ensure db is under migration control
repo_path = get_migrate_repo_path()
sql_connection = options['sql_connection']
version_str = version or 'latest'
logger.info(_("Upgrading %(sql_connection)s to version %(version_str)s") %
locals())
return versioning_api.upgrade(sql_connection, repo_path, version)
def downgrade(options, version):
"""
Downgrade the database's current migration level
:param options: options dict
:param version: version to downgrade to
:retval version number
"""
db_version(options) # Ensure db is under migration control
repo_path = get_migrate_repo_path()
sql_connection = options['sql_connection']
logger.info(_("Downgrading %(sql_connection)s to version %(version)s") %
locals())
return versioning_api.downgrade(sql_connection, repo_path, version)
def version_control(options):
"""
Place a database under migration control
:param options: options dict
"""
sql_connection = options['sql_connection']
try:
_version_control(options)
except versioning_exceptions.DatabaseAlreadyControlledError:
msg = (_("database '%(sql_connection)s' is already under migration "
"control") % locals())
raise fault.DatabaseMigrationError(msg)
def _version_control(options):
"""
Place a database under migration control
:param options: options dict
"""
repo_path = get_migrate_repo_path()
sql_connection = options['sql_connection']
return versioning_api.version_control(sql_connection, repo_path)
def db_sync(options, version=None):
"""
Place a database under migration control and perform an upgrade
:param options: options dict
:retval version number
"""
try:
_version_control(options)
except versioning_exceptions.DatabaseAlreadyControlledError, e:
pass
upgrade(options, version=version)
def get_migrate_repo_path():
"""Get the path for the migrate repository."""
path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'migrate_repo')
assert os.path.exists(path)
return path

View File

@ -20,12 +20,14 @@ from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, object_mapper
# pylint: disable=C0103
Base = declarative_base()
class KeystoneBase(object):
"""Base class for Keystone Models."""
__api__ = None
# pylint: disable=C0103
_i = None
def save(self, session=None):
@ -125,6 +127,7 @@ class Tenant(Base, KeystoneBase):
__tablename__ = 'tenants'
__api__ = 'tenant'
id = Column(Integer, primary_key=True, autoincrement=True)
uid = Column(String(255), unique=True, nullable=False)
name = Column(String(255), unique=True)
desc = Column(String(255))
enabled = Column(Integer)
@ -134,6 +137,7 @@ class User(Base, KeystoneBase):
__tablename__ = 'users'
__api__ = 'user'
id = Column(Integer, primary_key=True, autoincrement=True)
uid = Column(String(255), unique=True, nullable=False)
name = Column(String(255), unique=True)
password = Column(String(255))
email = Column(String(255))

View File

@ -30,15 +30,18 @@ from urllib import quote
import logging
import time
# pylint: disable=E0611
from eventlet.green.httplib import CONTINUE, HTTPConnection, HTTPMessage, \
HTTPResponse, HTTPSConnection, _UNKNOWN
DEFAULT_TIMEOUT = 30
# pylint: disable=R0902
class BufferedHTTPResponse(HTTPResponse):
"""HTTPResponse class that buffers reading of headers"""
# pylint: disable=C0103
def __init__(self, sock, debuglevel=0, strict=0,
method=None): # pragma: no cover
self.sock = sock
@ -59,6 +62,7 @@ class BufferedHTTPResponse(HTTPResponse):
self.length = _UNKNOWN # number of bytes left in response
self.will_close = _UNKNOWN # conn will close at end of response
# pylint: disable=E1101,E0203,W0201
def expect_response(self):
self.fp = self.sock.makefile('rb', 0)
version, status, reason = self._read_status()
@ -73,20 +77,24 @@ class BufferedHTTPResponse(HTTPResponse):
self.msg.fp = None
# pylint: disable=W0232
class BufferedHTTPConnection(HTTPConnection):
"""HTTPConnection class that uses BufferedHTTPResponse"""
response_class = BufferedHTTPResponse
# pylint: disable=W0201
def connect(self):
self._connected_time = time.time()
return HTTPConnection.connect(self)
# pylint: disable=W0201
def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0):
self._method = method
self._path = url
return HTTPConnection.putrequest(self, method, url, skip_host,
skip_accept_encoding)
# pylint: disable=E1101
def getexpect(self):
response = BufferedHTTPResponse(self.sock, strict=self.strict,
method=self._method)
@ -102,6 +110,7 @@ class BufferedHTTPConnection(HTTPConnection):
return response
# pylint: disable=R0913
def http_connect(ipaddr, port, device, partition, method, path,
headers=None, query_string=None, ssl=False, key_file=None,
cert_file=None, timeout=None):
@ -124,11 +133,13 @@ def http_connect(ipaddr, port, device, partition, method, path,
:returns: HTTPConnection object
"""
path = quote('/' + device + '/' + str(partition) + path)
# pylint: disable=E1121, E1124
return http_connect_raw(ipaddr, port, device, partition, method, path,
headers, query_string, ssl, key_file, cert_file,
timeout=timeout)
# pylint: disable=W0201
def http_connect_raw(ipaddr, port, method, path, headers=None,
query_string=None, ssl=False, key_file=None,
cert_file=None, timeout=None):
@ -161,7 +172,9 @@ def http_connect_raw(ipaddr, port, method, path, headers=None,
conn.path = path
conn.putrequest(method, path)
if headers:
# pylint: disable=E1103
for header, value in headers.iteritems():
conn.putheader(header, value)
# pylint: disable=E1103
conn.endheaders()
return conn

View File

@ -1,6 +1,6 @@
from keystone import utils
from keystone.common import wsgi
from keystone.logic.service import IdentityService
from keystone.logic import service
from keystone.logic.types.credential import PasswordCredentials
from . import get_marker_limit_and_url
@ -9,31 +9,32 @@ class CredentialsController(wsgi.Controller):
"""Controller for Credentials related operations"""
def __init__(self, options):
self.options = options
self.identity_service = service.IdentityService(options)
@utils.wrap_error
def get_credentials(self, req, user_id):
marker, limit, url = get_marker_limit_and_url(req)
credentials = IdentityService.get_credentials(
credentials = self.identity_service.get_credentials(
utils.get_auth_token(req), user_id, marker, limit, url)
return utils.send_result(200, req, credentials)
@utils.wrap_error
def get_password_credential(self, req, user_id):
credentials = IdentityService.get_password_credentials(
credentials = self.identity_service.get_password_credentials(
utils.get_auth_token(req), user_id)
return utils.send_result(200, req, credentials)
@utils.wrap_error
def delete_password_credential(self, req, user_id):
IdentityService.delete_password_credentials(utils.get_auth_token(req),
user_id)
self.identity_service.delete_password_credentials(
utils.get_auth_token(req), user_id)
return utils.send_result(204, None)
@utils.wrap_error
def add_credential(self, req, user_id):
credential = utils.get_normalized_request_content(
PasswordCredentials, req)
credential = IdentityService.create_password_credentials(
credential = self.identity_service.create_password_credentials(
utils.get_auth_token(req), user_id, credential)
return utils.send_result(201, req, credential)
@ -41,6 +42,6 @@ class CredentialsController(wsgi.Controller):
def update_password_credential(self, req, user_id):
credential = utils.get_normalized_request_content(
PasswordCredentials, req)
credential = IdentityService.update_password_credentials(
credential = self.identity_service.update_password_credentials(
utils.get_auth_token(req), user_id, credential)
return utils.send_result(200, req, credential)

View File

@ -1,6 +1,6 @@
from keystone import utils
from keystone.common import wsgi
from keystone.logic.service import IdentityService
from keystone.logic import service
from keystone.logic.types.endpoint import EndpointTemplate
from . import get_marker_limit_and_url
@ -10,17 +10,18 @@ class EndpointTemplatesController(wsgi.Controller):
def __init__(self, options):
self.options = options
self.identity_service = service.IdentityService(options)
@utils.wrap_error
def get_endpoint_templates(self, req):
marker, limit, url = get_marker_limit_and_url(req)
service_id = req.GET["serviceId"] if "serviceId" in req.GET else None
if service_id:
endpoint_templates = IdentityService.\
endpoint_templates = self.identity_service.\
get_endpoint_templates_by_service(
utils.get_auth_token(req), service_id, marker, limit, url)
else:
endpoint_templates = IdentityService.get_endpoint_templates(
endpoint_templates = self.identity_service.get_endpoint_templates(
utils.get_auth_token(req), marker, limit, url)
return utils.send_result(200, req, endpoint_templates)
@ -29,34 +30,34 @@ class EndpointTemplatesController(wsgi.Controller):
endpoint_template = utils.get_normalized_request_content(
EndpointTemplate, req)
return utils.send_result(201, req,
IdentityService.add_endpoint_template(utils.get_auth_token(req),
endpoint_template))
self.identity_service.add_endpoint_template(
utils.get_auth_token(req), endpoint_template))
@utils.wrap_error
def modify_endpoint_template(self, req, endpoint_template_id):
endpoint_template = utils.\
get_normalized_request_content(EndpointTemplate, req)
return utils.send_result(201, req,
IdentityService.modify_endpoint_template(\
self.identity_service.modify_endpoint_template(\
utils.get_auth_token(req),
endpoint_template_id, endpoint_template))
@utils.wrap_error
def delete_endpoint_template(self, req, endpoint_template_id):
rval = IdentityService.delete_endpoint_template(
rval = self.identity_service.delete_endpoint_template(
utils.get_auth_token(req), endpoint_template_id)
return utils.send_result(204, req, rval)
@utils.wrap_error
def get_endpoint_template(self, req, endpoint_template_id):
endpoint_template = IdentityService.get_endpoint_template(
endpoint_template = self.identity_service.get_endpoint_template(
utils.get_auth_token(req), endpoint_template_id)
return utils.send_result(200, req, endpoint_template)
@utils.wrap_error
def get_endpoints_for_tenant(self, req, tenant_id):
marker, limit, url = get_marker_limit_and_url(req)
endpoints = IdentityService.get_tenant_endpoints(
endpoints = self.identity_service.get_tenant_endpoints(
utils.get_auth_token(req), marker, limit, url, tenant_id)
return utils.send_result(200, req, endpoints)
@ -64,11 +65,11 @@ class EndpointTemplatesController(wsgi.Controller):
def add_endpoint_to_tenant(self, req, tenant_id):
endpoint = utils.get_normalized_request_content(EndpointTemplate, req)
return utils.send_result(201, req,
IdentityService.create_endpoint_for_tenant(
self.identity_service.create_endpoint_for_tenant(
utils.get_auth_token(req), tenant_id, endpoint))
@utils.wrap_error
def remove_endpoint_from_tenant(self, req, tenant_id, endpoint_id):
rval = IdentityService.delete_endpoint(utils.get_auth_token(req),
rval = self.identity_service.delete_endpoint(utils.get_auth_token(req),
endpoint_id)
return utils.send_result(204, req, rval)

View File

@ -20,5 +20,5 @@ class ExtensionsController(wsgi.Controller):
@utils.wrap_error
def get_extensions_info(self, req):
return utils.send_result(200, req,
self.extension_reader.get_extensions())
return utils.send_result(200, req,
self.extension_reader.get_extensions())

View File

@ -1,7 +1,7 @@
from keystone import utils
from keystone.common import wsgi
from keystone.logic.types.role import Role
from keystone.logic.service import IdentityService
from keystone.logic import service
from . import get_marker_limit_and_url
@ -10,52 +10,55 @@ class RolesController(wsgi.Controller):
def __init__(self, options):
self.options = options
self.identity_service = service.IdentityService(options)
# Not exposed yet.
@utils.wrap_error
def create_role(self, req):
role = utils.get_normalized_request_content(Role, req)
return utils.send_result(201, req,
IdentityService.create_role(utils.get_auth_token(req), role))
self.identity_service.create_role(utils.get_auth_token(req), role))
@utils.wrap_error
def delete_role(self, req, role_id):
rval = IdentityService.delete_role(utils.get_auth_token(req), role_id)
rval = self.identity_service.delete_role(
utils.get_auth_token(req), role_id)
return utils.send_result(204, req, rval)
@utils.wrap_error
def get_roles(self, req):
role_name = req.GET["name"] if "name" in req.GET else None
if role_name:
tenant = IdentityService.get_role_by_name(
tenant = self.identity_service.get_role_by_name(
utils.get_auth_token(req), role_name)
return utils.send_result(200, req, tenant)
else:
marker, limit, url = get_marker_limit_and_url(req)
roles = IdentityService.get_roles(
roles = self.identity_service.get_roles(
utils.get_auth_token(req), marker, limit, url)
return utils.send_result(200, req, roles)
@utils.wrap_error
def get_role(self, req, role_id):
role = IdentityService.get_role(utils.get_auth_token(req), role_id)
role = self.identity_service.get_role(utils.get_auth_token(req),
role_id)
return utils.send_result(200, req, role)
@utils.wrap_error
def add_role_to_user(self, req, user_id, role_id, tenant_id=None):
IdentityService.add_role_to_user(utils.get_auth_token(req),
self.identity_service.add_role_to_user(utils.get_auth_token(req),
user_id, role_id, tenant_id)
return utils.send_result(201, None)
@utils.wrap_error
def delete_role_from_user(self, req, user_id, role_id, tenant_id=None):
IdentityService.remove_role_from_user(utils.get_auth_token(req),
self.identity_service.remove_role_from_user(utils.get_auth_token(req),
user_id, role_id, tenant_id)
return utils.send_result(204, req, None)
@utils.wrap_error
def get_user_roles(self, req, user_id, tenant_id=None):
marker, limit, url = get_marker_limit_and_url(req)
roles = IdentityService.get_user_roles(
roles = self.identity_service.get_user_roles(
utils.get_auth_token(req), marker, limit, url, user_id, tenant_id)
return utils.send_result(200, req, roles)

View File

@ -1,7 +1,7 @@
from keystone import utils
from keystone.common import wsgi
from keystone.logic.types.service import Service
from keystone.logic.service import IdentityService
from keystone.logic import service
from . import get_marker_limit_and_url
@ -10,34 +10,36 @@ class ServicesController(wsgi.Controller):
def __init__(self, options):
self.options = options
self.identity_service = service.IdentityService(options)
@utils.wrap_error
def create_service(self, req):
service = utils.get_normalized_request_content(Service, req)
return utils.send_result(201, req,
IdentityService.create_service(utils.get_auth_token(req), service))
self.identity_service.create_service(utils.get_auth_token(req),
service))
@utils.wrap_error
def get_services(self, req):
service_name = req.GET["name"] if "name" in req.GET else None
if service_name:
tenant = IdentityService.get_service_by_name(
tenant = self.identity_service.get_service_by_name(
utils.get_auth_token(req), service_name)
return utils.send_result(200, req, tenant)
else:
marker, limit, url = get_marker_limit_and_url(req)
services = IdentityService.get_services(
services = self.identity_service.get_services(
utils.get_auth_token(req), marker, limit, url)
return utils.send_result(200, req, services)
@utils.wrap_error
def get_service(self, req, service_id):
service = IdentityService.get_service(
service = self.identity_service.get_service(
utils.get_auth_token(req), service_id)
return utils.send_result(200, req, service)
@utils.wrap_error
def delete_service(self, req, service_id):
rval = IdentityService.delete_service(utils.get_auth_token(req),
rval = self.identity_service.delete_service(utils.get_auth_token(req),
service_id)
return utils.send_result(204, req, rval)

View File

@ -1,7 +1,7 @@
from keystone import utils
from keystone.common import wsgi
from keystone.logic.service import IdentityService
from keystone.logic.types.tenant import Tenant
from keystone.logic import service
from keystone.models import Tenant
from . import get_marker_limit_and_url
@ -10,43 +10,47 @@ class TenantController(wsgi.Controller):
def __init__(self, options, is_service_operation=None):
self.options = options
self.identity_service = service.IdentityService(options)
self.is_service_operation = is_service_operation
@utils.wrap_error
def create_tenant(self, req):
tenant = utils.get_normalized_request_content(Tenant, req)
return utils.send_result(201, req,
IdentityService.create_tenant(utils.get_auth_token(req), tenant))
self.identity_service.create_tenant(utils.get_auth_token(req),
tenant))
@utils.wrap_error
def get_tenants(self, req):
tenant_name = req.GET["name"] if "name" in req.GET else None
if tenant_name:
tenant = IdentityService.get_tenant_by_name(
tenant = self.identity_service.get_tenant_by_name(
utils.get_auth_token(req),
tenant_name)
return utils.send_result(200, req, tenant)
else:
marker, limit, url = get_marker_limit_and_url(req)
tenants = IdentityService.get_tenants(utils.get_auth_token(req),
marker, limit, url, self.is_service_operation)
tenants = self.identity_service.get_tenants(
utils.get_auth_token(req), marker, limit, url,
self.is_service_operation)
return utils.send_result(200, req, tenants)
@utils.wrap_error
def get_tenant(self, req, tenant_id):
tenant = IdentityService.get_tenant(utils.get_auth_token(req),
tenant = self.identity_service.get_tenant(utils.get_auth_token(req),
tenant_id)
return utils.send_result(200, req, tenant)
@utils.wrap_error
def update_tenant(self, req, tenant_id):
tenant = utils.get_normalized_request_content(Tenant, req)
rval = IdentityService.update_tenant(utils.get_auth_token(req),
rval = self.identity_service.update_tenant(utils.get_auth_token(req),
tenant_id, tenant)
return utils.send_result(200, req, rval)
@utils.wrap_error
def delete_tenant(self, req, tenant_id):
rval = IdentityService.delete_tenant(utils.get_auth_token(req),
rval = self.identity_service.delete_tenant(utils.get_auth_token(req),
tenant_id)
return utils.send_result(204, req, rval)

View File

@ -28,7 +28,7 @@ from keystone import utils
from keystone.common import wsgi
from keystone.logic.types import auth
from keystone.logic.types import fault
from keystone.logic.service import IdentityService
from keystone.logic import service
from . import get_marker_limit_and_url
@ -37,19 +37,20 @@ class TokenController(wsgi.Controller):
def __init__(self, options):
self.options = options
self.identity_service = service.IdentityService(options)
@utils.wrap_error
def authenticate(self, req):
try:
auth_with_credentials = utils.get_normalized_request_content(
auth.AuthWithPasswordCredentials, req)
result = IdentityService.authenticate(auth_with_credentials)
result = self.identity_service.authenticate(auth_with_credentials)
except fault.BadRequestFault as e1:
try:
unscoped = utils.get_normalized_request_content(
auth.AuthWithUnscopedToken, req)
result = IdentityService.authenticate_with_unscoped_token(
unscoped)
result = self.identity_service.\
authenticate_with_unscoped_token(unscoped)
except fault.BadRequestFault as e2:
if e1.msg == e2.msg:
raise e1
@ -62,12 +63,12 @@ class TokenController(wsgi.Controller):
def authenticate_ec2(self, req):
creds = utils.get_normalized_request_content(auth.Ec2Credentials, req)
return utils.send_result(200, req,
IdentityService.authenticate_ec2(creds))
self.identity_service.authenticate_ec2(creds))
def _validate_token(self, req, token_id):
"""Validates the token, and that it belongs to the specified tenant"""
belongs_to = req.GET.get('belongsTo')
return IdentityService.validate_token(
return self.identity_service.validate_token(
utils.get_auth_token(req), token_id, belongs_to)
@utils.wrap_error
@ -84,12 +85,13 @@ class TokenController(wsgi.Controller):
@utils.wrap_error
def delete_token(self, req, token_id):
return utils.send_result(204, req,
IdentityService.revoke_token(utils.get_auth_token(req), token_id))
self.identity_service.revoke_token(utils.get_auth_token(req),
token_id))
@utils.wrap_error
def endpoints(self, req, token_id):
marker, limit, url = get_marker_limit_and_url(req)
return utils.send_result(200, req,
IdentityService.get_endpoints_for_token(
self.identity_service.get_endpoints_for_token(
utils.get_auth_token(req),
token_id, marker, limit, url))

View File

@ -1,6 +1,6 @@
from keystone import utils
from keystone.common import wsgi
from keystone.logic.service import IdentityService
from keystone.logic import service
from keystone.logic.types.user import User, User_Update
from . import get_marker_limit_and_url
@ -10,62 +10,65 @@ class UserController(wsgi.Controller):
def __init__(self, options):
self.options = options
self.identity_service = service.IdentityService(options)
@utils.wrap_error
def create_user(self, req):
u = utils.get_normalized_request_content(User, req)
return utils.send_result(201, req, IdentityService.create_user(
return utils.send_result(201, req, self.identity_service.create_user(
utils.get_auth_token(req), u))
@utils.wrap_error
def get_users(self, req):
user_name = req.GET["name"] if "name" in req.GET else None
if user_name:
tenant = IdentityService.get_user_by_name(
tenant = self.identity_service.get_user_by_name(
utils.get_auth_token(req),
user_name)
return utils.send_result(200, req, tenant)
else:
marker, limit, url = get_marker_limit_and_url(req)
users = IdentityService.get_users(utils.get_auth_token(req),
users = self.identity_service.get_users(utils.get_auth_token(req),
marker, limit, url)
return utils.send_result(200, req, users)
@utils.wrap_error
def get_user(self, req, user_id):
user = IdentityService.get_user(utils.get_auth_token(req), user_id)
user = self.identity_service.get_user(utils.get_auth_token(req),
user_id)
return utils.send_result(200, req, user)
@utils.wrap_error
def update_user(self, req, user_id):
user = utils.get_normalized_request_content(User_Update, req)
rval = IdentityService.update_user(utils.get_auth_token(req), user_id,
user)
rval = self.identity_service.update_user(utils.get_auth_token(req),
user_id, user)
return utils.send_result(200, req, rval)
@utils.wrap_error
def delete_user(self, req, user_id):
rval = IdentityService.delete_user(utils.get_auth_token(req), user_id)
rval = self.identity_service.delete_user(utils.get_auth_token(req),
user_id)
return utils.send_result(204, req, rval)
@utils.wrap_error
def set_user_password(self, req, user_id):
user = utils.get_normalized_request_content(User_Update, req)
rval = IdentityService.set_user_password(utils.get_auth_token(req),
user_id, user)
rval = self.identity_service.set_user_password(
utils.get_auth_token(req), user_id, user)
return utils.send_result(200, req, rval)
@utils.wrap_error
def set_user_enabled(self, req, user_id):
user = utils.get_normalized_request_content(User_Update, req)
rval = IdentityService.enable_disable_user(utils.get_auth_token(req),
user_id, user)
rval = self.identity_service.enable_disable_user(
utils.get_auth_token(req), user_id, user)
return utils.send_result(200, req, rval)
@utils.wrap_error
def update_user_tenant(self, req, user_id):
user = utils.get_normalized_request_content(User_Update, req)
rval = IdentityService.set_user_tenant(utils.get_auth_token(req),
rval = self.identity_service.set_user_tenant(utils.get_auth_token(req),
user_id, user)
return utils.send_result(200, req, rval)
@ -73,6 +76,6 @@ class UserController(wsgi.Controller):
def get_tenant_users(self, req, tenant_id):
marker, limit, url = get_marker_limit_and_url(req)
role_id = req.GET["roleId"] if "roleId" in req.GET else None
users = IdentityService.get_tenant_users(utils.get_auth_token(req),
tenant_id, role_id, marker, limit, url)
users = self.identity_service.get_tenant_users(
utils.get_auth_token(req), tenant_id, role_id, marker, limit, url)
return utils.send_result(200, req, users)

View File

@ -12,6 +12,8 @@
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# pylint: disable=C0302
from datetime import datetime, timedelta
import uuid
@ -23,15 +25,18 @@ import keystone.backends as backends
import keystone.backends.api as api
import keystone.backends.models as models
from keystone.logic.types import fault
from keystone.logic.types.tenant import Tenant, Tenants
from keystone.logic.types.tenant import Tenants
from keystone.logic.types.role import Role, Roles
from keystone.logic.types.service import Service, Services
from keystone.logic.types.user import User, User_Update, Users
from keystone.logic.types.endpoint import Endpoint, Endpoints, \
EndpointTemplate, EndpointTemplates
from keystone.logic.types.credential import Credentials, PasswordCredentials
from keystone.common import wsgi
from keystone import utils
# New imports as we refactor old backend design and models
from keystone.models import Tenant, Token
from keystone.token import Manager as TokenManager
from keystone.tenant import Manager as TenantManager
LOG = logging.getLogger('keystone.logic.service')
@ -48,7 +53,7 @@ def has_admin_role(token_id):
is defined in the config file using the keystone-admin-role setting
"""
(token, user) = validate_token(token_id)
initialize_admin_role_identifiers()
init_admin_role_identifiers()
if has_role({"api": api}, user, backends.ADMIN_ROLE_ID):
return (token, user)
else:
@ -66,7 +71,7 @@ def has_service_admin_role(token_id):
is defined in the config file using the keystone-admin-role setting
"""
(token, user) = validate_token(token_id)
initialize_admin_role_identifiers()
init_admin_role_identifiers()
if has_role({"api": api}, user,
backends.SERVICE_ADMIN_ROLE_ID):
return (token, user)
@ -112,7 +117,7 @@ def validate_service_admin_token(token_id):
"You are not authorized to make this call")
def initialize_admin_role_identifiers():
def init_admin_role_identifiers():
if backends.SERVICE_ADMIN_ROLE_ID is None:
role = api.ROLE.get_by_name(backends.SERVICE_ADMIN_ROLE_NAME)
if role:
@ -142,6 +147,7 @@ def has_role(env, user, role):
return False
# pylint: disable=W0613
def is_owner(env, user, object):
"""Checks if a user is the owner of an object.
@ -276,7 +282,8 @@ def validate_tenant(dtenant):
if not dtenant:
raise fault.UnauthorizedFault("Tenant not found")
if not dtenant.enabled:
if dtenant.enabled is None or \
str(dtenant.enabled).lower() not in ['1', 'true']:
raise fault.TenantDisabledFault("Tenant %s has been disabled!"
% dtenant.id)
@ -328,13 +335,25 @@ def validate_token(token_id, belongs_to=None, is_check_token=None):
class IdentityService(object):
"""Implements Identity service"""
"""Implements the Identity service
This class handles all logic of routing requests to the correct
backend as well as validating incoming/outgoing data
"""
def __init__(self, options):
""" Initialize
Loads all necessary backends to handle incoming requests.
"""
backends.configure_backends(options)
self.token_manager = TokenManager(options)
self.tenant_manager = TenantManager(options)
#
# Token Operations
#
@staticmethod
def authenticate(auth_request):
def authenticate(self, auth_request):
# Check auth_with_password_credentials
if not isinstance(auth_request, auth.AuthWithPasswordCredentials):
raise fault.BadRequestFault(
@ -353,11 +372,10 @@ class IdentityService(object):
if not user:
raise fault.UnauthorizedFault("Unauthorized")
return IdentityService._authenticate(
return self._authenticate(
validate, user.id, auth_request.tenant_id)
@staticmethod
def authenticate_with_unscoped_token(auth_request):
def authenticate_with_unscoped_token(self, auth_request):
# Check auth_with_unscoped_token
if not isinstance(auth_request, auth.AuthWithUnscopedToken):
raise fault.BadRequestFault("Expecting auth_with_unscoped_token!")
@ -374,14 +392,14 @@ class IdentityService(object):
elif auth_request.tenant_id:
dtenant = validate_tenant_by_id(auth_request.tenant_id)
# pylint: disable=W0613
def validate(duser):
# The user is already authenticated
return True
return IdentityService._authenticate(validate, user.id,
return self._authenticate(validate, user.id,
auth_request.tenant_id)
@staticmethod
def authenticate_ec2(credentials):
def authenticate_ec2(self, credentials):
# Check credentials
if not isinstance(credentials, auth.Ec2Credentials):
raise fault.BadRequestFault("Expecting Ec2 Credentials!")
@ -391,6 +409,7 @@ class IdentityService(object):
raise fault.UnauthorizedFault("No credentials found for %s"
% credentials.access)
# pylint: disable=W0613
def validate(duser):
signer = Signer(creds.secret)
signature = signer.generate(credentials)
@ -404,7 +423,7 @@ class IdentityService(object):
signature = signer.generate(credentials)
return signature == credentials.signature
return False
return IdentityService._authenticate(validate, creds.user_id,
return self._authenticate(validate, creds.user_id,
creds.tenant_id)
@staticmethod
@ -432,12 +451,12 @@ class IdentityService(object):
if not dtoken or dtoken.expires < datetime.now():
# Create new token
dtoken = models.Token()
dtoken = Token()
dtoken.id = str(uuid.uuid4())
dtoken.user_id = duser.id
dtoken.tenant_id = tenant_id
dtoken.expires = datetime.now() + timedelta(days=1)
api.TOKEN.create(dtoken)
dtoken = api.TOKEN.create(dtoken)
return get_auth_data(dtoken)
@staticmethod
@ -456,8 +475,7 @@ class IdentityService(object):
api.TOKEN.delete(token_id)
@staticmethod
def get_endpoints_for_token(admin_token,
def get_endpoints_for_token(self, admin_token,
token_id, marker, limit, url,):
validate_service_admin_token(admin_token)
dtoken = api.TOKEN.get(token_id)
@ -465,13 +483,12 @@ class IdentityService(object):
raise fault.ItemNotFoundFault("Token not found")
if not dtoken.tenant_id:
raise fault.ItemNotFoundFault("Token not mapped to any tenant.")
return IdentityService.fetch_tenant_endpoints(
return self.fetch_tenant_endpoints(
marker, limit, url, dtoken.tenant_id)
#
# Tenant Operations
#
@staticmethod
def create_tenant(admin_token, tenant):
validate_admin_token(admin_token)
@ -480,22 +497,17 @@ class IdentityService(object):
raise fault.BadRequestFault("Expecting a Tenant")
utils.check_empty_string(tenant.name, "Expecting a unique Tenant Name")
if api.TENANT.get_by_name(tenant.name) != None:
if api.TENANT.get_by_name(tenant.name) is not None:
raise fault.TenantConflictFault(
"A tenant with that name already exists")
dtenant = models.Tenant()
dtenant = Tenant()
dtenant.name = tenant.name
dtenant.desc = tenant.description
dtenant.description = tenant.description
dtenant.enabled = tenant.enabled
return api.TENANT.create(dtenant)
dtenant = api.TENANT.create(dtenant)
tenant.id = dtenant.id
return tenant
@staticmethod
def get_tenants(admin_token, marker, limit, url,
def get_tenants(self, admin_token, marker, limit, url,
is_service_operation=False):
"""Fetch tenants for either an admin or service operation."""
ts = []
@ -517,10 +529,11 @@ class IdentityService(object):
prev_page, next_page = api.TENANT.get_page_markers(marker, limit)
for dtenant in dtenants:
ts.append(Tenant(id=dtenant.id, name=dtenant.name,
description=dtenant.desc, enabled=dtenant.enabled))
t = Tenant(id=dtenant.id, name=dtenant.name,
description=dtenant.desc, enabled=dtenant.enabled)
ts.append(t)
links = IdentityService.get_links(url, prev_page, next_page, limit)
links = self.get_links(url, prev_page, next_page, limit)
return Tenants(ts, links)
@staticmethod
@ -561,7 +574,8 @@ class IdentityService(object):
'name': tenant.name}
api.TENANT.update(tenant_id, values)
dtenant = api.TENANT.get(tenant_id)
return Tenant(dtenant.id, dtenant.name, dtenant.desc, dtenant.enabled)
return Tenant(id=dtenant.id, name=dtenant.name,
description=dtenant.desc, enabled=dtenant.enabled)
@staticmethod
def delete_tenant(admin_token, tenant_id):
@ -581,11 +595,10 @@ class IdentityService(object):
#
# User Operations
#
@staticmethod
def create_user(admin_token, user):
def create_user(self, admin_token, user):
validate_admin_token(admin_token)
IdentityService.validate_and_fetch_user_tenant(user.tenant_id)
self.validate_and_fetch_user_tenant(user.tenant_id)
if not isinstance(user, User):
raise fault.BadRequestFault("Expecting a User")
@ -622,8 +635,8 @@ class IdentityService(object):
"Your account has been disabled")
return dtenant
@staticmethod
def get_tenant_users(admin_token, tenant_id,
# pylint: disable=R0913
def get_tenant_users(self, admin_token, tenant_id,
role_id, marker, limit, url):
validate_admin_token(admin_token)
@ -649,11 +662,10 @@ class IdentityService(object):
if ts.__len__():
prev, next = api.USER.users_get_by_tenant_get_page_markers(
tenant_id, role_id, marker, limit)
links = IdentityService.get_links(url, prev, next, limit)
links = self.get_links(url, prev, next, limit)
return Users(ts, links)
@staticmethod
def get_users(admin_token, marker, limit, url):
def get_users(self, admin_token, marker, limit, url):
validate_admin_token(admin_token)
ts = []
dusers = api.USER.users_get_page(marker, limit)
@ -663,7 +675,7 @@ class IdentityService(object):
links = []
if ts.__len__():
prev, next = api.USER.users_get_page_markers(marker, limit)
links = IdentityService.get_links(url, prev, next, limit)
links = self.get_links(url, prev, next, limit)
return Users(ts, links)
@staticmethod
@ -752,20 +764,16 @@ class IdentityService(object):
return User_Update(enabled=user.enabled)
@staticmethod
def set_user_tenant(admin_token, user_id, user):
def set_user_tenant(self, admin_token, user_id, user):
validate_admin_token(admin_token)
duser = api.USER.get(user_id)
if not duser:
raise fault.ItemNotFoundFault("The user could not be found")
if not isinstance(user, User):
raise fault.BadRequestFault("Expecting a User")
duser = api.USER.get(user_id)
if duser is None:
raise fault.ItemNotFoundFault("The user could not be found")
IdentityService.validate_and_fetch_user_tenant(user.tenant_id)
self.validate_and_fetch_user_tenant(user.tenant_id)
values = {'tenant_id': user.tenant_id}
api.USER.update(user_id, values)
return User_Update(tenant_id=user.tenant_id)
@ -834,8 +842,7 @@ class IdentityService(object):
role.id = drole.id
return role
@staticmethod
def get_roles(admin_token, marker, limit, url):
def get_roles(self, admin_token, marker, limit, url):
validate_service_admin_token(admin_token)
ts = []
@ -843,7 +850,7 @@ class IdentityService(object):
for drole in droles:
ts.append(Role(drole.id, drole.name, drole.desc, drole.service_id))
prev, next = api.ROLE.get_page_markers(marker, limit)
links = IdentityService.get_links(url, prev, next, limit)
links = self.get_links(url, prev, next, limit)
return Roles(ts, links)
@staticmethod
@ -889,8 +896,7 @@ class IdentityService(object):
api.ROLE.delete(role_id)
@staticmethod
def add_role_to_user(admin_token,
user_id, role_id, tenant_id=None):
def add_role_to_user(admin_token, user_id, role_id, tenant_id=None):
validate_service_admin_token(admin_token)
duser = api.USER.get(user_id)
if not duser:
@ -917,17 +923,17 @@ class IdentityService(object):
api.USER.user_role_add(drole_ref)
@staticmethod
def remove_role_from_user(admin_token,
user_id, role_id, tenant_id=None):
def remove_role_from_user(admin_token, user_id, role_id, tenant_id=None):
validate_service_admin_token(admin_token)
print user_id, role_id, tenant_id
drole_ref = api.ROLE.ref_get_by_user(user_id, role_id, tenant_id)
if drole_ref is None:
raise fault.ItemNotFoundFault(
"This role is not mapped to the user.")
api.ROLE.ref_delete(drole_ref.id)
@staticmethod
def get_user_roles(admin_token, marker,
# pylint: disable=R0913, R0914
def get_user_roles(self, admin_token, marker,
limit, url, user_id, tenant_id):
validate_service_admin_token(admin_token)
duser = api.USER.get(user_id)
@ -947,7 +953,7 @@ class IdentityService(object):
drole.desc, drole.service_id))
prev, next = api.ROLE.ref_get_page_markers(
user_id, tenant_id, marker, limit)
links = IdentityService.get_links(url, prev, next, limit)
links = self.get_links(url, prev, next, limit)
return Roles(ts, links)
@staticmethod
@ -1079,17 +1085,15 @@ class IdentityService(object):
api.ENDPOINT_TEMPLATE.endpoint_delete(endpoint.id)
api.ENDPOINT_TEMPLATE.delete(endpoint_template_id)
@staticmethod
def get_endpoint_templates(admin_token, marker, limit, url):
def get_endpoint_templates(self, admin_token, marker, limit, url):
validate_service_admin_token(admin_token)
dendpoint_templates = api.ENDPOINT_TEMPLATE.get_page(marker, limit)
ts = IdentityService.transform_endpoint_templates(dendpoint_templates)
ts = self.transform_endpoint_templates(dendpoint_templates)
prev, next = api.ENDPOINT_TEMPLATE.get_page_markers(marker, limit)
links = IdentityService.get_links(url, prev, next, limit)
links = self.get_links(url, prev, next, limit)
return EndpointTemplates(ts, links)
@staticmethod
def get_endpoint_templates_by_service(admin_token,
def get_endpoint_templates_by_service(self, admin_token,
service_id, marker, limit, url):
validate_service_admin_token(admin_token)
dservice = api.SERVICE.get(service_id)
@ -1098,10 +1102,10 @@ class IdentityService(object):
"No service with the id %s found." % service_id)
dendpoint_templates = api.ENDPOINT_TEMPLATE.\
get_by_service_get_page(service_id, marker, limit)
ts = IdentityService.transform_endpoint_templates(dendpoint_templates)
ts = self.transform_endpoint_templates(dendpoint_templates)
prev, next = api.ENDPOINT_TEMPLATE.\
get_by_service_get_page_markers(service_id, marker, limit)
links = IdentityService.get_links(url, prev, next, limit)
links = self.get_links(url, prev, next, limit)
return EndpointTemplates(ts, links)
@staticmethod
@ -1149,14 +1153,12 @@ class IdentityService(object):
dendpoint_template.version_info
)
@staticmethod
def get_tenant_endpoints(admin_token, marker, limit, url, tenant_id):
def get_tenant_endpoints(self, admin_token, marker, limit, url, tenant_id):
validate_service_admin_token(admin_token)
return IdentityService.fetch_tenant_endpoints(marker, limit,
return self.fetch_tenant_endpoints(marker, limit,
url, tenant_id)
@staticmethod
def fetch_tenant_endpoints(marker, limit, url, tenant_id):
def fetch_tenant_endpoints(self, marker, limit, url, tenant_id):
if tenant_id is None:
raise fault.BadRequestFault("Expecting a Tenant Id")
@ -1191,12 +1193,11 @@ class IdentityService(object):
prev, next = \
api.ENDPOINT_TEMPLATE.endpoint_get_by_tenant_get_page_markers(
tenant_id, marker, limit)
links = IdentityService.get_links(url, prev, next, limit)
links = self.get_links(url, prev, next, limit)
return Endpoints(ts, links)
@staticmethod
def create_endpoint_for_tenant(admin_token,
tenant_id, endpoint_template):
def create_endpoint_for_tenant(admin_token, tenant_id, endpoint_template):
validate_service_admin_token(admin_token)
utils.check_empty_string(tenant_id, "Expecting a Tenant Id.")
if api.TENANT.get(tenant_id) is None:
@ -1258,8 +1259,7 @@ class IdentityService(object):
return service
@staticmethod
def get_services(admin_token, marker, limit, url):
def get_services(self, admin_token, marker, limit, url):
validate_service_admin_token(admin_token)
ts = []
@ -1268,7 +1268,7 @@ class IdentityService(object):
ts.append(Service(dservice.id, dservice.name, dservice.type,
dservice.desc))
prev, next = api.SERVICE.get_page_markers(marker, limit)
links = IdentityService.get_links(url, prev, next, limit)
links = self.get_links(url, prev, next, limit)
return Services(ts, links)
@staticmethod
@ -1352,8 +1352,8 @@ class IdentityService(object):
return
@staticmethod
def update_password_credentials(admin_token,
user_id, password_credentials):
def update_password_credentials(admin_token, user_id,
password_credentials):
validate_admin_token(admin_token)
duser = api.USER.get(user_id)
if not duser:
@ -1373,8 +1373,8 @@ class IdentityService(object):
return PasswordCredentials(duser.name, duser.password)
@staticmethod
def create_password_credentials(admin_token, user_id, \
password_credentials):
def create_password_credentials(admin_token, user_id,
password_credentials):
validate_admin_token(admin_token)
duser = api.USER.get(user_id)
if not duser:

View File

@ -56,6 +56,7 @@ LOG = logging.getLogger('keystone.signer')
class Signer(object):
"""Hacked up code from boto/connection.py"""
# pylint: disable=E1101
def __init__(self, secret_key):
secret_key = secret_key.encode()
self.hmac = hmac.new(secret_key, digestmod=hashlib.sha1)
@ -131,6 +132,7 @@ class Signer(object):
if __name__ == '__main__':
# pylint: disable=E1121
print Signer('foo').generate({'SignatureMethod': 'HmacSHA256',
'SignatureVersion': '2'},
'get', 'server', '/foo')

View File

@ -150,7 +150,7 @@ class AuthWithPasswordCredentials(AuthBase):
obj = json.loads(json_str)
auth = AuthBase._validate_auth(obj, 'tenantId', 'tenantName',
'passwordCredentials')
'passwordCredentials', 'token')
cred = AuthBase._validate_key(auth, 'passwordCredentials',
'username', 'password')

View File

@ -159,3 +159,7 @@ class ServiceConflictFault(IdentityFault):
def __init__(self, msg, details=None, code=409):
super(ServiceConflictFault, self).__init__(msg, details, code)
self.key = "serviceConflict"
class DatabaseMigrationError(IdentityFault):
message = _("There was an error migrating the database.")

View File

@ -15,7 +15,9 @@
import json
from lxml import etree
from keystone.logic.types import fault
from keystone import models
class Tenant(object):
@ -33,6 +35,7 @@ class Tenant(object):
self.enabled = bool(enabled)
else:
self.enabled = None
print self.enabled
@staticmethod
def from_xml(xml_str):
@ -56,7 +59,7 @@ class Tenant(object):
"description")
if desc is None:
raise fault.BadRequestFault("Expecting Tenant Description")
return Tenant(id=id, name=name, description=desc.text,
return models.Tenant(id=id, name=name, description=desc.text,
enabled=set_enabled)
except etree.LxmlError as e:
raise fault.BadRequestFault("Cannot parse Tenant", str(e))
@ -93,8 +96,8 @@ class Tenant(object):
def to_dom(self):
dom = etree.Element("tenant",
xmlns="http://docs.openstack.org/identity/api/v2.0",
enabled=str(self.enabled).lower())
xmlns="http://docs.openstack.org/identity/api/v2.0",
enabled=str(self.enabled).lower())
if self.id:
dom.set("id", unicode(self.id))
if self.name:

View File

@ -27,16 +27,19 @@ import logging
import optparse # deprecated in 2.7, in favor of argparse
from keystone import version
from keystone.common import config
from keystone.manage import api
import keystone.backends as db
from keystone.backends.sqlalchemy import migration
from keystone.common import config
from keystone.logic.types import fault
from keystone.manage import api
# CLI feature set
OBJECTS = ['user', 'tenant', 'role', 'service',
'endpointTemplates', 'token', 'endpoint', 'credentials']
'endpointTemplates', 'token', 'endpoint', 'credentials', 'database']
ACTIONS = ['add', 'list', 'disable', 'delete', 'grant',
'revoke']
'revoke',
'sync', 'downgrade', 'upgrade', 'version_control', 'version']
# Messages
@ -65,6 +68,7 @@ def parse_args(args=None):
tokens : user, tenant, expiration
role list [tenant] will list roles granted on that tenant
database [sync | downgrade | upgrade | version_control | version]
options
-c | --config-file : config file to use
@ -90,6 +94,21 @@ def parse_args(args=None):
return args
def get_options(args=None):
# Initialize a parser for our configuration paramaters
parser = RaisingOptionParser()
_common_group = config.add_common_options(parser)
config.add_log_options(parser)
# Parse command-line and load config
(options, args) = config.parse_options(parser, list(args))
_config_file, conf = config.load_paste_config('admin', options, args)
conf.global_conf.update(conf.local_conf)
return conf.global_conf
def process(*args):
# Check arguments
if len(args) == 0:
@ -106,7 +125,7 @@ def process(*args):
if action not in ACTIONS:
raise optparse.OptParseError(SUPPORTED_ACTIONS)
if action not in ['list']:
if action not in ['list', 'sync', 'version_control', 'version']:
if len(args) == 2:
raise optparse.OptParseError(ID_NOT_SPECIFIED)
else:
@ -270,15 +289,116 @@ def process(*args):
elif object_type == 'credentials':
raise optparse.OptParseError(ACTION_NOT_SUPPORTED % ('credentials'))
elif (object_type, action) == ('database', 'sync'):
require_args(args, 1, 'Syncing database requires a version #')
options = get_options(args)
options = get_options(args)
backend_names = options.get('backends', None)
if backend_names:
if 'keystone.backends.sqlalchemy' in backend_names.split(','):
do_db_sync(options['keystone.backends.sqlalchemy'],
args)
else:
raise optparse.OptParseError(
'SQL alchemy backend not specified in config')
elif (object_type, action) == ('database', 'upgrade'):
require_args(args, 1, 'Upgrading database requires a version #')
options = get_options(args)
backend_names = options.get('backends', None)
if backend_names:
if 'keystone.backends.sqlalchemy' in backend_names.split(','):
do_db_upgrade(options['keystone.backends.sqlalchemy'],
args)
else:
raise optparse.OptParseError(
'SQL alchemy backend not specified in config')
elif (object_type, action) == ('database', 'downgrade'):
require_args(args, 1, 'Downgrading database requires a version #')
options = get_options(args)
backend_names = options.get('backends', None)
if backend_names:
if 'keystone.backends.sqlalchemy' in backend_names.split(','):
do_db_downgrade(options['keystone.backends.sqlalchemy'],
args)
else:
raise optparse.OptParseError(
'SQL alchemy backend not specified in config')
elif (object_type, action) == ('database', 'version_control'):
options = get_options(args)
backend_names = options.get('backends', None)
if backend_names:
if 'keystone.backends.sqlalchemy' in backend_names.split(','):
do_db_version_control(options['keystone.backends.sqlalchemy'])
else:
raise optparse.OptParseError(
'SQL alchemy backend not specified in config')
elif (object_type, action) == ('database', 'version'):
options = get_options(args)
backend_names = options.get('backends', None)
if backend_names:
if 'keystone.backends.sqlalchemy' in backend_names.split(','):
do_db_version(options['keystone.backends.sqlalchemy'])
else:
raise optparse.OptParseError(
'SQL alchemy backend not specified in config')
else:
# Command recognized but not handled: should never reach this
raise NotImplementedError()
#
# Database Migration commands (from Glance-manage)
#
def do_db_version(options):
"""Print database's current migration level"""
print migration.db_version(options)
def do_db_upgrade(options, args):
"""Upgrade the database's migration level"""
try:
db_version = args[2]
except IndexError:
db_version = None
print "Upgrading database to version %s" % db_version
migration.upgrade(options, version=db_version)
def do_db_downgrade(options, args):
"""Downgrade the database's migration level"""
try:
db_version = args[2]
except IndexError:
raise Exception("downgrade requires a version argument")
migration.downgrade(options, version=db_version)
def do_db_version_control(options):
"""Place a database under migration control"""
migration.version_control(options)
print "Database now under version control"
def do_db_sync(options, args):
"""Place a database under migration control and upgrade"""
try:
db_version = args[2]
except IndexError:
db_version = None
migration.db_sync(options, version=db_version)
def main(args=None):
try:
process(*parse_args(args))
except optparse.OptParseError as exc:
except (optparse.OptParseError, fault.DatabaseMigrationError) as exc:
print >> sys.stderr, exc
sys.exit(2)
except Exception as exc:

View File

@ -2,6 +2,7 @@ import datetime
import keystone.backends.api as db_api
import keystone.backends.models as db_models
import keystone.models as models
def add_user(name, password, tenant=None):
@ -32,10 +33,10 @@ def list_users():
def add_tenant(name):
obj = db_models.Tenant()
obj = models.Tenant()
obj.name = name
obj.enabled = True
return db_api.TENANT.create(obj)
db_api.TENANT.create(obj)
def list_tenants():
@ -56,8 +57,7 @@ def disable_tenant(name):
def add_role(name):
obj = db_models.Role()
obj.name = name
role = db_api.ROLE.create(obj)
return role
return db_api.ROLE.create(obj)
def list_role_assignments(tenant):
@ -147,7 +147,7 @@ def add_token(token, user, tenant, expires):
user = db_api.USER.get_by_name(name=user).id
tenant = db_api.TENANT.get_by_name(name=tenant).id
obj = db_models.Token()
obj = models.Token()
obj.id = token
obj.user_id = user
obj.tenant_id = tenant
@ -192,7 +192,7 @@ def add_credentials(user, type, key, secrete, tenant=None):
if tenant:
tenant = db_api.TENANT.get_by_name(tenant).id
obj = db_models.Credentials()
obj = models.Credentials()
obj.user_id = user
obj.type = type
obj.key = key

View File

@ -32,6 +32,7 @@ from nova import context
from nova import flags
from nova import utils
from nova import wsgi
# pylint: disable=W0611
from nova import exception
import webob.dec
import webob.exc
@ -43,13 +44,16 @@ FLAGS = flags.FLAGS
class KeystoneAuthShim(wsgi.Middleware):
"""Lazy provisioning nova project/users from keystone tenant/user"""
# pylint: disable=E1002
def __init__(self, application, db_driver=None):
if not db_driver:
db_driver = FLAGS.db_driver
# pylint: disable=C0103
self.db = utils.import_object(db_driver)
self.auth = auth.manager.AuthManager()
super(KeystoneAuthShim, self).__init__(application)
# pylint: disable=W0702
@webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
# find or create user
@ -81,6 +85,7 @@ class KeystoneAuthShim(wsgi.Middleware):
# This is for legacy compatibility
project_id = req.headers['X_TENANT']
# pylint: disable=W0612
if project_id:
try:
project_ref = self.auth.get_project(project_id)
@ -102,4 +107,5 @@ class KeystoneAuthShim(wsgi.Middleware):
auth_token=auth_token)
req.environ['nova.context'] = ctx
# pylint: disable=E1101
return self.application

View File

@ -80,6 +80,7 @@ PROTOCOL_NAME = "Token Authentication"
LOG = logging.getLogger('quantum.common.authentication')
# pylint: disable=R0902
class AuthProtocol(object):
"""Auth Middleware that handles authenticating client calls"""
@ -149,15 +150,26 @@ class AuthProtocol(object):
self.auth_uri = None
self.auth_port = None
self.auth_protocol = None
self.auth_timeout = None
self.cert_file = None
self.key_file = None
self.service_host = None
self.service_port = None
self.service_protocol = None
self.service_url = None
self.proxy_headers = None
self.start_response = None
self.app = None
self.conf = None
self.env = None
self.delay_auth_decision = None
self.expanded = None
self.claims = None
self._init_protocol_common(app, conf) # Applies to all protocols
self._init_protocol(app, conf) # Specific to this protocol
# pylint: disable=R0912
def __call__(self, env, start_response):
""" Handle incoming request. Authenticate. And send downstream. """
LOG.debug("entering AuthProtocol.__call__")
@ -273,7 +285,8 @@ class AuthProtocol(object):
data = response.read()
return data
def _get_claims(self, env):
@staticmethod
def _get_claims(env):
"""Get claims from request"""
claims = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
return claims
@ -315,6 +328,7 @@ class AuthProtocol(object):
key_file=self.key_file, cert_file=self.cert_file,
timeout=self.auth_timeout)
resp = conn.getresponse()
# pylint: disable=E1103
conn.close()
if not str(resp.status).startswith('20'):
@ -352,6 +366,7 @@ class AuthProtocol(object):
timeout=self.auth_timeout)
resp = conn.getresponse()
data = resp.read()
# pylint: disable=E1103
conn.close()
if not str(resp.status).startswith('20'):
@ -410,6 +425,7 @@ class AuthProtocol(object):
else:
# We are forwarding to a remote service (no downstream WSGI app)
req = Request(self.proxy_headers)
# pylint: disable=E1101
parsed = urlparse(req.url)
conn = http_connect(self.service_host,
self.service_port,

531
keystone/models.py Normal file
View File

@ -0,0 +1,531 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2011 OpenStack LLC.
#
# 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.
""" Module that contains all object models
The models are used to hold Keystone 'business' objects and their validation,
serialization, and backend interaction code.
The models are based off of python's dict.
The uses supported are:
# can be initialized with static properties
tenant = Tenant(name='A1000')
# handles writing to correct backend
tenant.save()
# static properties
id = tenant.id
tenant = None
# Acts as a dict
tenant is a dict
tenant.dict points to the data dict (i.e. tenant["tenant"])
# can be retrieved by static property
tenant_by_name = Tenant.get(name='A1000')
# can be retrieved by id default, so name not needed
tenant_by_id = Tenant.get(id)
assertIsEquals(tenant_by_id, tenant_by_name)
# handles serialization
print tenant_by_id
print tenant_by_id.to_json() # Keystone latest contract
print tenant_by_id.to_json_20() # Keystone 2.0 contract
Serialization routines can take hints in this format:
{
"contract_attributes": ["id", "name", ...],
"types": [("id", int), (...)]
}
attribute/value can be:
contract_attributes: list of contract attributes (see initializer)
format is a list of attributes names (ex ['id', 'name'])
types: list of attribute/type mappings
format is a list of name/type tuples (ex [('id', int)])
tags: list of attributes that go into XML tags
format is a list of attribute names(ex ['description'])
"""
import json
from lxml import etree
from keystone.utils import fault
class Resource(dict):
""" Base class for models
Provides basic functionality that can be overridden """
def __init__(self, *args, **kw):
""" Initialize object
kwargs contain static properties
"""
super(Resource, self).__init__(*args, **kw)
# attributes that can be used as attributes. Example:
# tenant.id - here id is a contract attribute
# initialize dynamically (to prevent recursion on __setattr__)
super(Resource, self).__setattr__("contract_attributes", [])
# set statically for references
self.contract_attributes = []
if kw:
self.contract_attributes.extend(kw.keys())
for name, value in kw.iteritems():
self[name] = value
#
# model properties
#
# Override built-in classes to allow for user.id (as well as user["id"])
# for attributes defined in the Keystone contract
#
def __getattr__(self, name):
""" Supports reading contract attributes (ex. tenant.id)
This should only be called if the original call did not match
an attribute (Python's rules)"""
if name in self.contract_attributes:
if name in self:
return self[name]
return None
elif name == 'desc':
# We need to maintain this compatibility with this nasty attribute
# until we're done refactoring
return self.description
else:
raise AttributeError("'%s' not found on object of class '%s'" % \
(name, self.__class__.__name__))
def __setattr__(self, name, value):
""" Supports setting contract attributes (ex. tenant.name = 'A1')
This should only be called if the original call did not match
an attribute (Python's rules)."""
if name in self.contract_attributes:
if value is not None:
self[name] = value
elif name == 'contract_attributes':
# Allow someone to set that
super(Resource, self).__setattr__(name, value)
else:
raise AttributeError("'%s' not found on object of class '%s'" % \
(name, self.__class__.__name__))
def __getitem__(self, name):
if name in self.contract_attributes:
if super(Resource, self).__contains__(name):
return super(Resource, self).__getitem__(name)
return None
elif name == 'desc':
# We need to maintain this compatibility with this nasty attribute
# until we're done refactoring
return self.description
else:
return super(Resource, self).__getitem__(name)
def __setitem__(self, name, value):
super(Resource, self).__setitem__(name, value)
def __contains__(self, key):
if key in self.contract_attributes:
return True
return super(Resource, self).__contains__(key)
#
# Validation calls
#
def validate(self):
""" Validates object attributes. Raises error if object not valid
This calls inspect() in fail_fast mode, so it gets back the first
validation error and raises it. It is up to the code in inspect()
to determine what validations take precedence and are returned
first
:returns: True if no validation errors raise"""
errors = self.inspect(fail_fast=True)
if errors:
raise errors[0][0](errors[0][1])
return errors is None
# pylint: disable=W0613, R0201
def inspect(self, fail_fast=None):
""" Validates and retuns validation results without raising any errors
:param fail_fast" return after first validation failure
:returns: [(faultClass, message), ...], ordered by relevance
- if None, then no errors found
"""
return None
#
# Serialization Functions - may be moved to a different class
#
def __str__(self):
return str(self.to_dict())
def to_dict(self):
""" For compatibility with logic.types """
root_name = self.__class__.__name__.lower()
return {root_name: self.strip_null_fields(self.copy())}
@staticmethod
def strip_null_fields(dict_object):
""" Strips null fields from dict"""
for k, v in dict_object.items():
if v is None:
del dict_object[k]
return dict_object
@staticmethod
def write_dict_to_xml(dict_object, xml, tags=None):
""" Attempts to convert a dict into XML as best as possible.
Converts named keys and attributes and recursively calls for
any values are are embedded dicts
:param tags: accepts a list of attribute names that should go into XML
tags instead of attributes
"""
if tags is None:
tags = []
for name, value in dict_object.iteritems():
if isinstance(value, dict):
element = etree.SubElement(xml, name)
Resource.write_dict_to_xml(value, element)
elif name in tags:
element = xml.find(name)
if element is None:
element = etree.SubElement(xml, name)
if isinstance(value, dict):
Resource.write_dict_to_xml(value, element)
else:
element.text = str(value)
else:
if value is not None:
if isinstance(value, dict):
Resource.write_dict_to_xml(value, xml)
elif isinstance(value, bool):
xml.set(name, str(value).lower())
else:
xml.set(name, str(value))
else:
if name in xml:
del xml.attrib[name]
@staticmethod
def write_xml_to_dict(xml, dict_object):
""" Attempts to update a dict with XML as best as possible."""
for key, value in xml.items():
dict_object[key] = value
for element in xml.iterdescendants():
name = element.tag
if "}" in name:
#trim away namespace if it is there
name = name[name.rfind("}") + 1:]
if element.attrib == {}:
dict_object[name] = element.text
else:
dict_object[name] = {}
Resource.write_xml_to_dict(element, dict_object[element.tag])
def apply_type_mappings(self, type_mappings):
""" Applies type mappings to dict values
Right now only handles integer mappings"""
if type_mappings:
for name, type in type_mappings:
if type is int:
self[name] = int(self[name])
elif type is str:
# Move sub to string
if name in self and self[name] is dict:
self[name] = self[name][0]
else:
raise NotImplementedError("Model type mappings cannot \
handle '%s' types" % type.__class__.__name__)
def to_json(self, hints=None):
""" Serializes object to json - implies latest Keystone contract """
d = self.to_dict()
if hints:
if "types" in hints:
Resource.apply_type_mappings(d, hints["types"])
return json.dumps(d)
def to_xml(self, hints=None):
""" Serializes object to XML string
- implies latest Keystone contract
:param hints: see introduction on format"""
tags = None
if hints:
if 'tags' in hints:
tags = hints['tags']
dom = self.to_dom(tags=tags)
Resource.write_dict_to_xml(self, dom, tags=tags)
return etree.tostring(dom)
def to_dom(self, xmlns=None, tags=None):
""" Serializes object to XML objec
- implies latest Keystone contract
:param xmlns: accepts an optional namespace for XML
:param tags: accepts a list of attribute names that should go into XML
tags instead of attributes
"""
if tags is None:
tags = []
if xmlns:
dom = etree.Element(self.__class__.__name__.lower(), xmlns=xmlns)
else:
dom = etree.Element(self.__class__.__name__.lower())
Resource.write_dict_to_xml(self, dom, tags)
return dom
@classmethod
def from_json(cls, json_str, hints=None):
""" Deserializes object from json - assumes latest Keystone
contract
"""
try:
object = json.loads(json_str)
model_name = cls.__name__.lower()
if model_name in object:
# Ignore class name if it is there
object = object[model_name]
model_object = None
type_mappings = None
if hints:
if 'types' in hints:
type_mappings = hints['types']
if 'contract_attributes' in hints:
# build mapping and instantiate object with
# contract_attributes provided
params = {}
for name in hints['contract_attributes']:
if name in object:
params[name] = object[name]
else:
params[name] = None
model_object = cls(**params)
if model_object is None:
model_object = cls()
model_object.update(object)
if type_mappings:
model_object.apply_type_mappings(type_mappings)
return model_object
except (ValueError, TypeError) as e:
raise fault.BadRequestFault("Cannot parse '%s' json" % \
cls.__name__, str(e))
@classmethod
def from_xml(cls, xml_str, hints=None):
""" Deserializes object from XML - assumes latest Keystone
contract """
try:
object = etree.fromstring(xml_str)
model_object = None
type_mappings = None
if hints:
if 'types' in hints:
type_mappings = hints['types']
if 'contract_attributes' in hints:
# build mapping and instantiate object with
# contract_attributes provided
params = {}
for name in hints['contract_attributes']:
params[name] = object.get(name, None)
model_object = cls(**params)
if model_object is None:
model_object = cls()
cls.write_xml_to_dict(object, model_object)
if type_mappings:
model_object.apply_type_mappings(type_mappings)
return model_object
except etree.LxmlError as e:
raise fault.BadRequestFault("Cannot parse '%s' xml" % cls.__name__,
str(e))
#
# Backend management
#
def save(self):
""" Handles finding correct backend and writing to it
Supports both saving new object (create) and updating an existing one
"""
#if self.status == 'new':
# #backends[find the class].create(self)
#elif self.status == 'existing':
# #backends[find the class].update(self)
pass
def delete(self):
""" Handles finding correct backend and deleting object from it """
pass
@classmethod
def get(cls, id=None, *args, **kw):
# backends[find the class].get(id, *args, **kw)
return cls(*args, **kw)
class Service(Resource):
""" Service model """
def __init__(self, id=None, type=None, name=None, description=None,
*args, **kw):
super(Service, self).__init__(id=id, type=type, name=name,
description=description, *args, **kw)
def to_json_20(self):
return super(Service, self).to_json_20()
def inspect(self, fail_fast=None):
result = super(Service, self).inspect(fail_fast)
if fail_fast and result:
return result
class Tenant(Resource):
""" Tenant model """
# pylint: disable=E0203,C0103
def __init__(self, id=None, name=None, description=None, enabled=None,
*args, **kw):
super(Tenant, self).__init__(id=id, name=name,
description=description, enabled=enabled,
*args, **kw)
if isinstance(self.id, int):
self.id = str(self.id)
@classmethod
def from_xml(cls, xml_str, hints=None):
if hints is None:
hints = {}
if 'contract_attributes' not in hints:
hints['contract_attributes'] = ['id', 'name', 'description',
'enabled']
if 'tags' not in hints:
hints['tags'] = ["description"]
return super(Tenant, cls).from_xml(xml_str, hints=hints)
def to_dom(self, xmlns=None, tags=None):
if tags is None:
tags = ["description"]
if xmlns is None:
xmlns = "http://docs.openstack.org/identity/api/v2.0"
return super(Tenant, self).to_dom(xmlns=xmlns, tags=tags)
def to_xml(self, hints=None):
if hints is None:
hints = {}
if 'tags' not in hints:
hints['tags'] = ["description"]
return super(Tenant, self).to_xml(hints=hints)
def to_json(self, hints=None):
if hints is None:
hints = {}
return super(Tenant, self).to_json(hints=hints)
class User(Resource):
""" User model
Attribute Notes:
default_tenant_id (formerly tenant_id): this attribute can be enabled or
disabled by configuration. When enabled, any authentication call
without a tenant gets authenticated to this tenant.
"""
# pylint: disable=R0913
def __init__(self, id=None, password=None, name=None,
tenant_id=None,
email=None, enabled=None,
*args, **kw):
super(User, self).__init__(id=id, password=password, name=name,
tenant_id=tenant_id, email=email,
enabled=enabled, *args, **kw)
class EndpointTemplate(Resource):
""" EndpointTemplate model """
# pylint: disable=R0913
def __init__(self, id=None, region=None, name=None, type=None,
public_url=None, admin_url=None,
internal_url=None, enabled=None, is_global=None,
version_id=None, version_list=None, version_info=None,
*args, **kw):
super(EndpointTemplate, self).__init__(id=id, region=region, name=name,
type=type, public_url=public_url, admin_url=admin_url,
internal_url=internal_url, enabled=enabled,
is_global=is_global, version_id=version_id,
version_list=version_list, version_info=version_info,
*args, **kw)
class Endpoint(Resource):
""" Endpoint model """
# pylint: disable=R0913
def __init__(self, id=None, tenant_id=None, region=None, name=None,
type=None, public_url=None, admin_url=None,
internal_url=None, version_id=None, version_list=None,
version_info=None,
*args, **kw):
super(Endpoint, self).__init__(id=id, tenant_id=tenant_id,
region=region, name=name, type=type, public_url=public_url,
admin_url=admin_url, internal_url=internal_url,
version_id=version_id, version_list=version_list,
version_info=version_info,
*args, **kw)
class Role(Resource):
""" Role model """
def __init__(self, id=None, name=None, description=None, service_id=None,
tenant_id=None, *args, **kw):
super(Role, self).__init__(id=id, name=name, description=description,
service_id=service_id, tenant_id=tenant_id,
*args, **kw)
class Token(Resource):
""" Token model """
def __init__(self, id=None, user_id=None, expires=None, tenant_id=None,
*args, **kw):
super(Token, self).__init__(id=id, user_id=user_id, expires=expires,
tenant_id=tenant_id, *args, **kw)
class UserRoleAssociation(Resource):
""" Role Grant model """
def __init__(self, user_id=None, role_id=None, tenant_id=None,
*args, **kw):
super(UserRoleAssociation, self).__init__(user_id=user_id,
role_id=role_id, tenant_id=tenant_id,
*args, **kw)
class Credentials(Resource):
# pylint: disable=R0913
def __init__(self, id=None, user_id=None, tenant_id=None, type=None,
key=None, secret=None, *args, **kw):
super(Credentials, self).__init__(id=id, user_id=user_id,
tenant_id=tenant_id, type=type, key=key, secret=secret, *args,
**kw)

View File

@ -18,7 +18,6 @@
import routes
from keystone.common import wsgi
import keystone.backends as db
from keystone.controllers.token import TokenController
from keystone.controllers.roles import RolesController
from keystone.controllers.staticfiles import StaticFilesController
@ -35,7 +34,6 @@ class AdminApi(wsgi.Router):
def __init__(self, options):
self.options = options
mapper = routes.Mapper()
db.configure_backends(options)
# Token Operations
auth_controller = TokenController(options)

View File

@ -18,7 +18,6 @@
import routes
from keystone.common import wsgi
import keystone.backends as db
from keystone.controllers.token import TokenController
from keystone.controllers.tenant import TenantController
from keystone.controllers.version import VersionController
@ -33,8 +32,6 @@ class ServiceApi(wsgi.Router):
self.options = options
mapper = routes.Mapper()
db.configure_backends(options)
# Token Operations
auth_controller = TokenController(options)
mapper.connect("/tokens", controller=auth_controller,

View File

@ -53,10 +53,10 @@ HTTP_X_AUTHORIZATION
import sys
import optparse
from keystone import version
from keystone.common import config, wsgi
from keystone.routers.service import ServiceApi
from keystone.routers.admin import AdminApi
from keystone.common import config, wsgi
from keystone import version
def service_app_factory(global_conf, **local_conf):
@ -105,7 +105,7 @@ class Server():
# Initialize a parser for our configuration paramaters
parser = optparse.OptionParser(version='%%prog %s' %
version.version())
common_group = config.add_common_options(parser)
config.add_common_options(parser)
config.add_log_options(parser)
# Parse arguments and load config
@ -123,6 +123,8 @@ class Server():
self.name = name
self.config = config_name or self.name
self.key = None
self.server = None
def start(self, host=None, port=None, wait=True):
"""Starts the Keystone server

32
keystone/tenant.py Normal file
View File

@ -0,0 +1,32 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2011 OpenStack LLC.
#
# 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.
""" Tenant manager module
TODO: move functionality into here. Ex:
def get_tenant(self, context, tenant_id):
'''Return info for a tenant if it is valid.'''
return self.driver.get(tenant_id)
"""
import keystone.backends.api as api
class Manager(object):
def __init__(self, options):
self.options = options
self.driver = api.TENANT

View File

@ -487,8 +487,6 @@ class KeystoneTest(object):
return not core.run(config=c, testRunner=runner,
argv=argv + ['-P'])
except Exception as e:
print 'Error %s' % e
finally:
self.tearDown()

View File

@ -661,10 +661,11 @@ class FunctionalTestCase(ApiTestCase):
data = {
"auth": {
"token": {
"id": token,
}}}
"id": token}}}
if tenant_id:
data["auth"]["tenantId"] = tenant_id
return self.post_token(as_json=data, **kwargs)
def validate_token(self, token_id=None, tenant_id=None, **kwargs):

View File

@ -27,10 +27,14 @@ class TestAdminAuthentication(common.FunctionalTestCase):
class TestAdminAuthenticationNegative(common.FunctionalTestCase):
"""Negative test admin-side user authentication"""
def test_admin_user_trying_to_scope_to_tenant_with_established_role(self):
def test_admin_user_scoping_to_tenant_with_role(self):
"""A Keystone Admin SHOULD be able to retrieve a scoped token...
But only if the Admin has some Role on the Tenant other than Admin."""
But only if the Admin has some Role on the Tenant other than Admin.
Formerly:
test_admin_user_trying_to_scope_to_tenant_with_established_role
"""
tenant = self.create_tenant().json['tenant']
role = self.create_role().json['role']

View File

@ -205,8 +205,8 @@ class UnScopedAuthenticationTest(common.FunctionalTestCase):
assert_status=200)
self.assertIsNotNone(r.json['access']['token'])
service_catalog = r.json['access']['serviceCatalog']
self.assertIsNotNone(service_catalog)
service_catalog = r.json['access'].get('serviceCatalog')
self.assertIsNotNone(service_catalog, r.json)
self.check_urls_for_regular_user(service_catalog)
def test_authenticate_xml(self):
@ -226,8 +226,8 @@ class UnScopedAuthenticationTest(common.FunctionalTestCase):
r = self.authenticate(self.user['name'], self.user['password'],
assert_status=200, request_type='admin')
self.assertIsNotNone(r.json['access']['token'])
self.assertIsNotNone(r.json['access']['serviceCatalog'])
self.assertIsNotNone(r.json['access'].get('token'), r.json)
self.assertIsNotNone(r.json['access'].get('serviceCatalog'), r.json)
service_catalog = r.json['access']['serviceCatalog']
self.check_urls_for_regular_user(service_catalog)

View File

@ -19,6 +19,7 @@ class TestIssue85(common.FunctionalTestCase):
tenant['id']).json['access']['token']['id']
# Validate and check that token belongs to tenant
print self.get_token(user_token).body
tenant_id = self.get_token(user_token).\
json['access']['token']['tenant']['id']
self.assertEqual(tenant_id, tenant['id'])
@ -31,7 +32,7 @@ class TestIssue85(common.FunctionalTestCase):
'name': tenant['name'],
'description': 'description',
'enabled': False}})
self.assertFalse(r.json['tenant']['enabled'])
self.assertEquals(r.json['tenant']['enabled'], False)
# Assert that token belonging to disabled tenant is invalid
r = self.admin_request(path='/tokens/%s?belongsTo=%s' %

View File

@ -81,7 +81,8 @@ class CreateRolesTest(RolesTest):
self.assertEqual(role['description'], description)
self.assertEqual(role['serviceId'], service['id'])
def test_create_role_mapped_to_a_service_using_incorrect_role_name(self):
def test_create__service_role_using_incorrect_role_name(self):
""" test_create_role_mapped_to_a_service_using_incorrect_role_name """
self.create_role(common.unique_str(), service_id=common.unique_str(),
assert_status=400)
@ -143,7 +144,9 @@ class DeleteRoleTest(RolesTest):
self.assertEqual(role['id'], role_id)
self.assertEqual(role['serviceId'], service['id'])
def test_create_role_mapped_to_a_service_using_incorrect_role_name(self):
def test_create__service_role_using_incorrect_role_name(self):
""" Formerly:
test_create_role_mapped_to_a_service_using_incorrect_role_name"""
self.create_role(common.unique_str(), service_id=common.unique_str(),
assert_status=400)

View File

@ -314,11 +314,12 @@ class UpdateTenantTest(TenantTest):
new_tenant_name = common.unique_str()
new_description = common.unique_str()
updated_tenant = self.update_tenant(self.tenant['id'],
tenant_name=new_tenant_name,
tenant_name=new_tenant_name, tenant_enabled=False,
tenant_description=new_description, assert_status=200).\
json['tenant']
self.assertEqual(updated_tenant['name'], new_tenant_name)
self.assertEqual(updated_tenant['description'], new_description)
self.assertEqual(updated_tenant['enabled'], False)
def test_update_tenant_xml(self):
new_tenant_name = common.unique_str()

View File

@ -97,7 +97,7 @@ class CheckToken(common.FunctionalTestCase):
self.user = self.create_user_with_known_password(
tenant_id=self.tenant['id']).json['user']
self.token = self.authenticate(self.user['name'],
self.user['password'], self.tenant['id']).json['access']['token']
self.user['password'], self.tenant['id']).json['access']['token']
def test_validate_token_true(self):
self.check_token_belongs_to(self.token['id'], self.tenant['id'],

View File

@ -0,0 +1,93 @@
import unittest2 as unittest
import uuid
from keystone import backends
import keystone.backends.api as api
from keystone import models
from keystone import utils
class BackendTestCase(unittest.TestCase):
"""
Base class to run tests for Keystone backends
"""
def setUp(self, backend_name=None, settings=None):
super(BackendTestCase, self).setUp()
if backend_name is None:
backend_name = 'keystone.backends.sqlalchemy'
if settings is None:
settings = {
"sql_connection": "sqlite:///",
"backend_entities": "['UserRoleAssociation', 'Endpoints',\
'Role', 'Tenant', 'User',\
'Credentials', 'EndpointTemplates',\
'Token', 'Service']",
"sql_idle_timeout": "30"
}
# Init backends moddule constants
backends.configure_backends({
'backends': None,
"keystone-service-admin-role": "KeystoneServiceAdmin",
"keystone-admin-role": "KeystoneAdmin",
"hash-password": "False"
})
# Init instance of backend
self.backend = utils.import_module(backend_name)
self.backend.configure_backend(settings)
def test_registration(self):
self.assertIsNotNone(backends.api.CREDENTIALS)
self.assertIsNotNone(backends.api.ENDPOINT_TEMPLATE)
self.assertIsNotNone(backends.api.ROLE)
self.assertIsNotNone(backends.api.SERVICE)
self.assertIsNotNone(backends.api.TENANT)
self.assertIsNotNone(backends.api.TOKEN)
self.assertIsNotNone(backends.api.USER)
def test_basic_tenant_create(self):
tenant = models.Tenant(name="Tee One", description="This is T1",
enabled=True)
original_tenant = tenant.copy()
new_tenant = api.TENANT.create(tenant)
self.assertIsInstance(new_tenant, models.Tenant)
for k, v in original_tenant.items():
if k not in ['id'] and k in new_tenant:
self.assertEquals(new_tenant[k], v)
self.assertEqual(original_tenant, tenant, "Backend modified provided \
tenant")
def test_tenant_create_with_id(self):
tenant = models.Tenant(id="T2", name="Tee Two", description="This is \
T2", enabled=True)
original_tenant = tenant.copy()
new_tenant = api.TENANT.create(tenant)
self.assertIsInstance(new_tenant, models.Tenant)
for k, v in original_tenant.items():
if k in new_tenant:
self.assertEquals(new_tenant[k], v)
self.assertEqual(original_tenant, tenant, "Backend modified provided \
tenant")
def test_tenant_update(self):
tenant = models.Tenant(id="T3", name="Tee Three",
description="This is T3", enabled=True)
new_tenant = api.TENANT.create(tenant)
new_tenant.enabled = False
new_tenant.description = "This is UPDATED T3"
api.TENANT.update("T3", new_tenant)
updated_tenant = api.TENANT.get("T3")
self.assertEqual(new_tenant, updated_tenant)
if __name__ == '__main__':
unittest.main()

View File

@ -125,6 +125,8 @@ class EC2AuthnMethods(base.ServiceAPITest):
"""
Test that bad credentials returns a 401
"""
# Create dummy tenant (or adding creds will fail)
self.fixture_create_tenant(id='bad', name='bad')
access = "xpd285.access"
secret = "345fgi.secret"
kwargs = {
@ -135,6 +137,9 @@ class EC2AuthnMethods(base.ServiceAPITest):
"secret": secret,
}
self.fixture_create_credentials(**kwargs)
# Delete the 'bad' tenant, orphaning the creds
self.get_request('DELETE', '/tenants/bad')
url = "/ec2tokens"
req = self.get_request('POST', url)
params = {

View File

@ -0,0 +1,138 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010-2011 OpenStack, LLC
# 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.
"""
Tests for database migrations. The test case runs a series of test cases to
ensure that migrations work properly both upgrading and downgrading, and that
no data loss occurs if possible.
"""
import os
import unittest2 as unittest
import urlparse
from migrate.versioning.repository import Repository
from sqlalchemy import *
from sqlalchemy.pool import NullPool
import keystone.backends.sqlalchemy.migration as migration_api
from keystone.logic.types import fault
class TestMigrations(unittest.TestCase):
"""Test sqlalchemy-migrate migrations"""
TEST_DATABASES = {'sqlite': 'sqlite:///migration.db'}
REPOSITORY_PATH = os.path.abspath(os.path.join(os.path.abspath(__file__),
os.pardir, os.pardir, os.pardir, 'backends',
'sqlalchemy', 'migrate_repo'))
REPOSITORY = Repository(REPOSITORY_PATH)
def __init__(self, *args, **kwargs):
super(TestMigrations, self).__init__(*args, **kwargs)
def setUp(self):
# Load test databases
self.engines = {}
for key, value in TestMigrations.TEST_DATABASES.items():
self.engines[key] = create_engine(value, poolclass=NullPool)
# We start each test case with a completely blank slate.
self._reset_databases()
def tearDown(self):
# We destroy the test data store between each test case,
# and recreate it, which ensures that we have no side-effects
# from the tests
self._reset_databases()
def _reset_databases(self):
for key, engine in self.engines.items():
conn_string = TestMigrations.TEST_DATABASES[key]
conn_pieces = urlparse.urlparse(conn_string)
if conn_string.startswith('sqlite'):
# We can just delete the SQLite database, which is
# the easiest and cleanest solution
db_path = conn_pieces.path.strip('/')
if os.path.exists(db_path):
os.unlink(db_path)
# No need to recreate the SQLite DB. SQLite will
# create it for us if it's not there...
elif conn_string.startswith('mysql'):
# We can execute the MySQL client to destroy and re-create
# the MYSQL database, which is easier and less error-prone
# than using SQLAlchemy to do this via MetaData...trust me.
database = conn_pieces.path.strip('/')
loc_pieces = conn_pieces.netloc.split('@')
host = loc_pieces[1]
auth_pieces = loc_pieces[0].split(':')
user = auth_pieces[0]
password = ""
if len(auth_pieces) > 1:
if auth_pieces[1].strip():
password = "-p%s" % auth_pieces[1]
sql = ("drop database if exists %(database)s; "
"create database %(database)s;") % locals()
cmd = ("mysql -u%(user)s %(password)s -h%(host)s "
"-e\"%(sql)s\"") % locals()
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
def test_walk_versions(self):
"""
Walks all version scripts for each tested database, ensuring
that there are no errors in the version scripts for each engine
"""
for key, engine in self.engines.items():
options = {'sql_connection': TestMigrations.TEST_DATABASES[key]}
self._walk_versions(options)
def _walk_versions(self, options):
# Determine latest version script from the repo, then
# upgrade from 1 through to the latest, with no data
# in the databases. This just checks that the schema itself
# upgrades successfully.
# Assert we are not under version control...
self.assertRaises(fault.DatabaseMigrationError,
migration_api.db_version,
options)
# Place the database under version control
print options
print migration_api.version_control(options)
cur_version = migration_api.db_version(options)
self.assertEqual(0, cur_version)
for version in xrange(1, TestMigrations.REPOSITORY.latest + 1):
migration_api.upgrade(options, version)
cur_version = migration_api.db_version(options)
self.assertEqual(cur_version, version)
# Now walk it back down to 0 from the latest, testing
# the downgrade paths.
for version in reversed(
xrange(0, TestMigrations.REPOSITORY.latest)):
migration_api.downgrade(options, version)
cur_version = migration_api.db_version(options)
self.assertEqual(cur_version, version)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,94 @@
import json
from lxml import etree
import unittest2 as unittest
from keystone.models import Resource
from keystone.test import utils as testutils
class TestModels(unittest.TestCase):
'''Unit tests for keystone/models.py.'''
def test_resource(self):
resource = Resource()
self.assertEquals(str(resource.__class__),
"<class 'keystone.models.Resource'>",
"Resource should be of instance "
"class keystone.models.Resource but instead "
"was '%s'" % str(resource.__class__))
self.assertIsInstance(resource, dict, "")
def test_resource_static_properties(self):
resource = Resource(id=1, name="the resource", blank=None)
self.assertEquals(resource.id, 1)
self.assertEquals(resource.name, "the resource")
try:
x = resource.some_bad_property
except AttributeError:
pass
except:
self.assert_(False, "Invalid attribute on resource should fail")
def test_resource_keys(self):
resource = Resource(id=1, name="the resource", blank=None)
self.assertEquals(resource.id, 1)
self.assertEquals(resource['id'], 1)
self.assertTrue('id' in resource)
self.assertTrue(hasattr(resource, 'id'))
resource['dynamic'] = '1'
self.assertEquals(resource['dynamic'], '1')
self.assertTrue('dynamic' in resource)
def test_resource_dynamic_properties(self):
resource = Resource(id=1, name="the resource", blank=None)
resource["dynamic"] = "test"
self.assertEquals(resource["dynamic"], "test")
self.assertEquals(resource["name"], "the resource")
def test_resource_json_serialization(self):
resource = Resource(id=1, name="the resource", blank=None)
json_str = resource.to_json()
d1 = json.loads(json_str)
d2 = json.loads('{"resource": {"name": "the resource", "id": 1}}')
self.assertEquals(d1, d2)
def test_resource_xml_serialization(self):
resource = Resource(id=1, name="the resource", blank=None)
xml_str = resource.to_xml()
self.assertTrue(testutils.XMLTools.xmlEqual(xml_str,
'<resource id="1" name="the resource"/>'))
def test_resource_xml_deserialization(self):
resource = Resource.from_xml('<Resource blank="" id="1" \
name="the resource"/>',
hints={
"contract_attributes": ['id', 'name'],
"types": [("id", int)]})
self.assertIsInstance(resource, Resource)
self.assertEquals(resource.id, 1)
self.assertEquals(resource.name, "the resource")
def test_resource_json_deserialization(self):
resource = Resource.from_json('{"resource": {"name": "the resource", \
"id": 1}}',
hints={
"contract_attributes": ['id', 'name'],
"types": [("id", int)]})
self.assertIsInstance(resource, Resource)
self.assertEquals(resource.id, 1)
self.assertEquals(resource.name, "the resource")
def test_resource_inspection(self):
resource = Resource(id=1, name="the resource", blank=None)
self.assertIsNone(resource.inspect())
def test_resource_validation(self):
resource = Resource(id=1, name="the resource", blank=None)
self.assertTrue(resource.validate())
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,84 @@
import json
from lxml import etree
import unittest2 as unittest
from keystone.models import Endpoint
from keystone.test import utils as testutils
class TestModelsEndpoint(unittest.TestCase):
'''Unit tests for keystone/models.py:Endpoint class.'''
def test_endpoint(self):
endpoint = Endpoint()
self.assertEquals(str(endpoint.__class__),
"<class 'keystone.models.Endpoint'>",
"endpoint should be of instance "
"class keystone.models.Endpoint but instead "
"was '%s'" % str(endpoint.__class__))
self.assertIsInstance(endpoint, dict, "")
def test_endpoint_static_properties(self):
endpoint = Endpoint(id=1, name="the endpoint", enabled=True,
blank=None)
self.assertEquals(endpoint.id, 1)
self.assertEquals(endpoint.name, "the endpoint")
self.assertTrue(endpoint.enabled)
self.assertEquals(endpoint.admin_url, None)
try:
x = endpoint.some_bad_property
except AttributeError:
pass
except:
self.assert_(False, "Invalid attribute on endpoint should fail")
def test_endpoint_properties(self):
endpoint = Endpoint(id=2, name="the endpoint", blank=None)
endpoint["dynamic"] = "test"
self.assertEquals(endpoint["dynamic"], "test")
def test_endpoint_json_serialization(self):
endpoint = Endpoint(id=3, name="the endpoint", blank=None)
endpoint["dynamic"] = "test"
json_str = endpoint.to_json()
d1 = json.loads(json_str)
d2 = json.loads('{"endpoint": {"name": "the endpoint", \
"id": 3, "dynamic": "test"}}')
self.assertEquals(d1, d2)
def test_endpoint_xml_serialization(self):
endpoint = Endpoint(id=4, name="the endpoint", blank=None)
xml_str = endpoint.to_xml()
self.assertTrue(testutils.XMLTools.xmlEqual(xml_str,
'<endpoint name="the endpoint" id="4"/>'))
def test_endpoint_json_deserialization(self):
endpoint = Endpoint.from_json('{"name": "the endpoint", "id": 5}',
hints={"contract_attributes": ['id', 'name']})
self.assertIsInstance(endpoint, Endpoint)
self.assertEquals(endpoint.id, 5)
self.assertEquals(endpoint.name, "the endpoint")
def test_endpoint_json_deserialization_rootless(self):
endpoint = Endpoint.from_json('{"endpoint": {"name": "the endpoint", \
"id": 6}}',
hints={"contract_attributes": ['id', 'name']})
self.assertIsInstance(endpoint, Endpoint)
self.assertEquals(endpoint.id, 6)
self.assertEquals(endpoint.name, "the endpoint")
def test_endpoint_xml_deserialization(self):
endpoint = Endpoint(id=7, name="the endpoint", blank=None)
self.assertIsInstance(endpoint, Endpoint)
def test_endpoint_inspection(self):
endpoint = Endpoint(id=8, name="the endpoint", blank=None)
self.assertIsNone(endpoint.inspect())
def test_endpoint_validation(self):
endpoint = Endpoint(id=9, name="the endpoint", blank=None)
self.assertTrue(endpoint.validate())
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,85 @@
import json
from lxml import etree
import unittest2 as unittest
from keystone.models import EndpointTemplate
from keystone.test import utils as testutils
class TestModelsEndpointTemplate(unittest.TestCase):
'''Unit tests for keystone/models.py:EndpointTemplate class.'''
def test_endpointtemplate(self):
endpointtemplate = EndpointTemplate()
self.assertEquals(str(endpointtemplate.__class__),
"<class 'keystone.models.EndpointTemplate'>",
"endpointtemplate should be of instance "
"class keystone.models.EndpointTemplate but instead "
"was '%s'" % str(endpointtemplate.__class__))
self.assertIsInstance(endpointtemplate, dict, "")
def test_endpointtemplate_static_properties(self):
endpointtemplate = EndpointTemplate(id=1, name="the endpointtemplate",
enabled=True, blank=None)
self.assertEquals(endpointtemplate.id, 1)
self.assertEquals(endpointtemplate.name, "the endpointtemplate")
self.assertTrue(endpointtemplate.enabled)
self.assertEquals(endpointtemplate.admin_url, None)
try:
x = endpointtemplate.some_bad_property
except AttributeError:
pass
except:
self.assert_(False, "Invalid attribute on endpointtemplate \
should fail")
def test_endpointtemplate_properties(self):
endpointtemplate = EndpointTemplate(id=1, name="the endpointtemplate",
blank=None)
endpointtemplate["dynamic"] = "test"
self.assertEquals(endpointtemplate["dynamic"], "test")
def test_endpointtemplate_json_serialization(self):
endpointtemplate = EndpointTemplate(id=1, name="the endpointtemplate",
blank=None)
endpointtemplate["dynamic"] = "test"
json_str = endpointtemplate.to_json()
d1 = json.loads(json_str)
d2 = json.loads('{"endpointtemplate": {"name": "the endpointtemplate",\
"id": 1, "dynamic": "test"}}')
self.assertEquals(d1, d2)
def test_endpointtemplate_xml_serialization(self):
endpointtemplate = EndpointTemplate(id=1, name="the endpointtemplate",
blank=None)
xml_str = endpointtemplate.to_xml()
self.assertTrue(testutils.XMLTools.xmlEqual(xml_str,
'<endpointtemplate \
name="the endpointtemplate" id="1"/>'))
def test_endpointtemplate_json_deserialization(self):
endpointtemplate = EndpointTemplate.from_json('{"name": \
"the endpointtemplate", "id": 1}',
hints={"contract_attributes": ['id', 'name']})
self.assertIsInstance(endpointtemplate, EndpointTemplate)
self.assertEquals(endpointtemplate.id, 1)
self.assertEquals(endpointtemplate.name, "the endpointtemplate")
def test_endpointtemplate_xml_deserialization(self):
endpointtemplate = EndpointTemplate(id=1, name="the endpointtemplate",
blank=None)
self.assertIsInstance(endpointtemplate, EndpointTemplate)
def test_endpointtemplate_inspection(self):
endpointtemplate = EndpointTemplate(id=1, name="the endpointtemplate",
blank=None)
self.assertIsNone(endpointtemplate.inspect())
def test_endpointtemplate_validation(self):
endpointtemplate = EndpointTemplate(id=1, name="the endpointtemplate",
blank=None)
self.assertTrue(endpointtemplate.validate())
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,75 @@
import json
from lxml import etree
import unittest2 as unittest
from keystone.models import Role
from keystone.test import utils as testutils
class TestModelsRole(unittest.TestCase):
'''Unit tests for keystone/models.py:Role class.'''
def test_role(self):
role = Role()
self.assertEquals(str(role.__class__),
"<class 'keystone.models.Role'>",
"role should be of instance "
"class keystone.models.Role but instead "
"was '%s'" % str(role.__class__))
self.assertIsInstance(role, dict, "")
def test_role_static_properties(self):
role = Role(id=1, name="the role", enabled=True, blank=None)
self.assertEquals(role.id, 1)
self.assertEquals(role.name, "the role")
self.assertTrue(role.enabled)
self.assertEquals(role.description, None)
try:
x = role.some_bad_property
except AttributeError:
pass
except:
self.assert_(False, "Invalid attribute on role should fail")
def test_role_properties(self):
role = Role(id=1, name="the role", blank=None)
role["dynamic"] = "test"
self.assertEquals(role["dynamic"], "test")
def test_role_json_serialization(self):
role = Role(id=1, name="the role", blank=None)
role["dynamic"] = "test"
json_str = role.to_json()
d1 = json.loads(json_str)
d2 = json.loads('{"role": {"name": "the role", \
"id": 1, "dynamic": "test"}}')
self.assertEquals(d1, d2)
def test_role_xml_serialization(self):
role = Role(id=1, name="the role", blank=None)
xml_str = role.to_xml()
self.assertTrue(testutils.XMLTools.xmlEqual(xml_str,
'<role id="1" name="the role"/>'))
def test_role_json_deserialization(self):
role = Role.from_json('{"name": "the role", "id": 1}',
hints={"contract_attributes": ['id', 'name']})
self.assertIsInstance(role, Role)
self.assertEquals(role.id, 1)
self.assertEquals(role.name, "the role")
def test_role_xml_deserialization(self):
role = Role(id=1, name="the role", blank=None)
self.assertIsInstance(role, Role)
def test_role_inspection(self):
role = Role(id=1, name="the role", blank=None)
self.assertIsNone(role.inspect())
def test_role_validation(self):
role = Role(id=1, name="the role", blank=None)
self.assertTrue(role.validate())
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,75 @@
import json
from lxml import etree
import unittest2 as unittest
from keystone.models import Service
from keystone.test import utils as testutils
class TestModelsService(unittest.TestCase):
'''Unit tests for keystone/models.py:Service class.'''
def test_service(self):
service = Service()
self.assertEquals(str(service.__class__),
"<class 'keystone.models.Service'>",
"service should be of instance "
"class keystone.models.Service but instead "
"was '%s'" % str(service.__class__))
self.assertIsInstance(service, dict, "")
def test_service_static_properties(self):
service = Service(id=1, name="the service", blank=None)
self.assertEquals(service.id, 1)
self.assertEquals(service.name, "the service")
try:
x = service.some_bad_property
except AttributeError:
pass
except:
self.assert_(False, "Invalid attribute on service should fail")
def test_service_properties(self):
service = Service(id=1, name="the service", blank=None)
service["dynamic"] = "test"
self.assertEquals(service["dynamic"], "test")
def test_service_json_serialization(self):
service = Service(id=1, name="the service", blank=None)
service["dynamic"] = "test"
json_str = service.to_json()
d1 = json.loads(json_str)
d2 = json.loads('{"service": {"name": "the service", \
"id": 1, "dynamic": "test"}}')
self.assertEquals(d1, d2)
def test_service_xml_serialization(self):
service = Service(id=1, name="the service", blank=None)
xml_str = service.to_xml()
self.assertTrue(testutils.XMLTools.xmlEqual(xml_str,
'<service id="1" name="the service" />'))
def test_service_json_deserialization(self):
service = Service.from_json('{"name": "the service", "id": 1}',
hints={
"contract_attributes": ['id', 'name'],
"types": [("id", int)]})
self.assertIsInstance(service, Service)
self.assertEquals(service.id, 1)
self.assertEquals(service.name, "the service")
def test_service_xml_deserialization(self):
service = Service(id=1, name="the service", blank=None)
self.assertIsInstance(service, Service)
def test_service_inspection(self):
service = Service(id=1, name="the service", blank=None)
self.assertIsNone(service.inspect())
def test_service_validation(self):
service = Service(id=1, name="the service", blank=None)
self.assertTrue(service.validate())
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,116 @@
import json
from lxml import etree
import unittest2 as unittest
from keystone.models import Tenant
from keystone.test import utils as testutils
class TestModelsTenant(unittest.TestCase):
'''Unit tests for keystone/models.py:Tenant class.'''
def test_tenant(self):
tenant = Tenant()
self.assertEquals(str(tenant.__class__),
"<class 'keystone.models.Tenant'>",
"tenant should be of instance "
"class keystone.models.Tenant but instead "
"was '%s'" % str(tenant.__class__))
self.assertIsInstance(tenant, dict, "")
def test_tenant_static_properties(self):
tenant = Tenant(id=1, name="the tenant", enabled=True, blank=None)
self.assertEquals(tenant.id, "1")
self.assertEquals(tenant.name, "the tenant")
self.assertTrue(tenant.enabled)
self.assertEquals(tenant.description, None)
try:
x = tenant.some_bad_property
except AttributeError:
pass
except:
self.assert_(False, "Invalid attribute on tenant should fail")
def test_tenant_properties(self):
tenant = Tenant(id=2, name="the tenant", blank=None)
tenant["dynamic"] = "test"
self.assertEquals(tenant["dynamic"], "test")
def test_tenant_initialization(self):
tenant = Tenant(id=3, name="the tenant", enabled=True, blank=None)
self.assertTrue(tenant.enabled)
tenant = Tenant(id=35, name="the tenant", enabled=0, blank=None)
self.assertEquals(tenant.enabled, False)
json_str = tenant.to_json()
d1 = json.loads(json_str)
self.assertIn('tenant', d1)
self.assertIn('enabled', d1['tenant'])
self.assertEquals(d1['tenant']['enabled'], False)
tenant = Tenant(id=36, name="the tenant", enabled=False, blank=None)
self.assertEquals(tenant.enabled, False)
def test_tenant_json_serialization(self):
tenant = Tenant(id=3, name="the tenant", enabled=True, blank=None)
tenant["dynamic"] = "test"
json_str = tenant.to_json()
d1 = json.loads(json_str)
d2 = json.loads('{"tenant": {"name": "the tenant", \
"id": "3", "enabled": true, "dynamic": "test"}}')
self.assertEquals(d1, d2)
def test_tenant_xml_serialization(self):
tenant = Tenant(id=4, name="the tenant", description="X", blank=None)
xml_str = tenant.to_xml()
self.assertTrue(testutils.XMLTools.xmlEqual(xml_str,
'<tenant \
xmlns="http://docs.openstack.org/identity/api/v2.0" \
id="4" name="the tenant">\
<description>X</description></tenant>'))
def test_tenant_json_deserialization(self):
tenant = Tenant.from_json('{"tenant": {"name": "the tenant",\
"id": 5, "extra": "some data"}}',
hints={"contract_attributes": ['id', 'name']})
self.assertIsInstance(tenant, Tenant)
self.assertEquals(tenant.id, 5)
self.assertEquals(tenant.name, "the tenant")
def test_tenant_xml_deserialization(self):
tenant = Tenant.from_xml('<tenant \
xmlns="http://docs.openstack.org/identity/api/v2.0" \
enabled="true" id="6" name="the tenant">\
<description>qwerty text</description></tenant>',
hints={
"contract_attributes": ['id', 'name'],
"types": [("id", int),
("description", str)]})
self.assertIsInstance(tenant, Tenant)
self.assertEquals(tenant.id, 6)
self.assertEquals(tenant.name, "the tenant")
self.assertEquals(tenant.description, "qwerty text")
def test_tenant_xml_deserialization_hintless(self):
tenant = Tenant.from_xml('<tenant \
xmlns="http://docs.openstack.org/identity/api/v2.0" \
enabled="none" id="7" name="the tenant">\
<description>qwerty text</description></tenant>')
self.assertIsInstance(tenant, Tenant)
self.assertEquals(tenant.id, "7")
self.assertEquals(tenant.name, "the tenant")
self.assertEquals(tenant.description, "qwerty text")
def test_tenant_inspection(self):
tenant = Tenant(id=8, name="the tenant", blank=None)
self.assertIsNone(tenant.inspect())
def test_tenant_validation(self):
tenant = Tenant(id=9, name="the tenant", blank=None)
self.assertTrue(tenant.validate())
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,74 @@
import json
from lxml import etree
import unittest2 as unittest
from keystone.models import Token
from keystone.test import utils as testutils
class TestModelsToken(unittest.TestCase):
'''Unit tests for keystone/models.py:Token class.'''
def test_token(self):
token = Token()
self.assertEquals(str(token.__class__),
"<class 'keystone.models.Token'>",
"token should be of instance "
"class keystone.models.Token but instead "
"was '%s'" % str(token.__class__))
self.assertIsInstance(token, dict, "")
def test_token_static_properties(self):
token = Token(id=1, name="the token", enabled=True, blank=None)
self.assertEquals(token.id, 1)
self.assertEquals(token.name, "the token")
self.assertTrue(token.enabled)
try:
x = token.some_bad_property
except AttributeError:
pass
except:
self.assert_(False, "Invalid attribute on token should fail")
def test_token_properties(self):
token = Token(id=1, name="the token", blank=None)
token["dynamic"] = "test"
self.assertEquals(token["dynamic"], "test")
def test_token_json_serialization(self):
token = Token(id=1, name="the token", blank=None)
token["dynamic"] = "test"
json_str = token.to_json()
d1 = json.loads(json_str)
d2 = json.loads('{"token": {"name": "the token", \
"id": 1, "dynamic": "test"}}')
self.assertEquals(d1, d2)
def test_token_xml_serialization(self):
token = Token(id=1, name="the token", blank=None)
xml_str = token.to_xml()
self.assertTrue(testutils.XMLTools.xmlEqual(xml_str,
'<token id="1" name="the token"/>'))
def test_token_json_deserialization(self):
token = Token.from_json('{"name": "the token", "id": 1}',
hints={"contract_attributes": ['id', 'name']})
self.assertIsInstance(token, Token)
self.assertEquals(token.id, 1)
self.assertEquals(token.name, "the token")
def test_token_xml_deserialization(self):
token = Token(id=1, name="the token", blank=None)
self.assertIsInstance(token, Token)
def test_token_inspection(self):
token = Token(id=1, name="the token", blank=None)
self.assertIsNone(token.inspect())
def test_token_validation(self):
token = Token(id=1, name="the token", blank=None)
self.assertTrue(token.validate())
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,73 @@
import json
from lxml import etree
import unittest2 as unittest
from keystone.models import User
from keystone.test import utils as testutils
class TestModelsUser(unittest.TestCase):
'''Unit tests for keystone/models.py:User class.'''
def test_user(self):
user = User()
self.assertEquals(str(user.__class__),
"<class 'keystone.models.User'>",
"user should be of instance "
"class keystone.models.User but instead "
"was '%s'" % str(user.__class__))
self.assertIsInstance(user, dict, "")
def test_user_static_properties(self):
user = User(id=1, name="the user", blank=None)
self.assertEquals(user.id, 1)
self.assertEquals(user.name, "the user")
try:
x = user.some_bad_property
except AttributeError:
pass
except:
self.assert_(False, "Invalid attribute on user should fail")
def test_user_properties(self):
user = User(id=1, name="the user", blank=None)
user["dynamic"] = "test"
self.assertEquals(user["dynamic"], "test")
def test_user_json_serialization(self):
user = User(id=1, name="the user", blank=None)
user["dynamic"] = "test"
json_str = user.to_json()
d1 = json.loads(json_str)
d2 = json.loads('{"user": {"name": "the user", \
"id": 1, "dynamic": "test"}}')
self.assertEquals(d1, d2)
def test_user_xml_serialization(self):
user = User(id=1, name="the user", blank=None)
xml_str = user.to_xml()
self.assertTrue(testutils.XMLTools.xmlEqual(xml_str,
'<user name="the user" id="1"/>'))
def test_user_json_deserialization(self):
user = User.from_json('{"name": "the user", "id": 1}',
hints={"contract_attributes": ['id', 'name']})
self.assertIsInstance(user, User)
self.assertEquals(user.id, 1)
self.assertEquals(user.name, "the user")
def test_user_xml_deserialization(self):
user = User(id=1, name="the user", blank=None)
self.assertIsInstance(user, User)
def test_user_inspection(self):
user = User(id=1, name="the user", blank=None)
self.assertIsNone(user.inspect())
def test_user_validation(self):
user = User(id=1, name="the user", blank=None)
self.assertTrue(user.validate())
if __name__ == '__main__':
unittest.main()

View File

@ -1,4 +1,5 @@
import unittest2 as unittest
from keystone import utils

48
keystone/test/utils.py Normal file
View File

@ -0,0 +1,48 @@
import re
import string
from lxml import etree
class XMLTools():
@staticmethod
def xmlEqual(xmlStr1, xmlStr2):
et1 = etree.XML(xmlStr1)
et2 = etree.XML(xmlStr2)
let1 = [x for x in et1.iter()]
let2 = [x for x in et2.iter()]
if len(let1) != len(let2):
return False
while let1:
el = let1.pop(0)
foundEl = XMLTools.findMatchingElem(el, let2)
if foundEl is None:
return False
let2.remove(foundEl)
return True
@staticmethod
def findMatchingElem(el, eList):
for elem in eList:
if XMLTools.elemsEqual(el, elem):
return elem
return None
@staticmethod
def elemsEqual(el1, el2):
if el1.tag != el2.tag or el1.attrib != el2.attrib:
return False
# no requirement for text checking for now
#if el1.text != el2.text or el1.tail != el2.tail:
#return False
path1 = el1.getroottree().getpath(el1)
path2 = el2.getroottree().getpath(el2)
idxRE = re.compile(r"(\[\d*\])")
path1 = idxRE.sub("", path1)
path2 = idxRE.sub("", path2)
if path1 != path2:
return False
return True

32
keystone/token.py Normal file
View File

@ -0,0 +1,32 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2011 OpenStack LLC.
#
# 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.
""" Token manager module
TODO: move functionality into here. Ex:
def get_token(self, context, token_id):
'''Return info for a token if it is valid.'''
return self.driver.get(token_id)
"""
import keystone.backends.api as api
class Manager(object):
def __init__(self, options):
self.options = options
self.driver = api.TOKEN

View File

@ -54,6 +54,7 @@ def get_auth_key(req):
def wrap_error(func):
# pylint: disable=W0703
@functools.wraps(func)
def check_error(*args, **kwargs):
try: