extracting credentials

Moves the credentials API into its own backend.

LDAP was not going to be able to support credentials.

Even with a custom schema, many people are using LDAP in read only
mode, which means that they would not be able to use the  credentials
API at all.  By splitting it out, we have a workable solution for
both SQL and LDAP Identity backends.

Drops the Foreign Key constraints off the Credentials table, as there
is now no guaranttee that users are stored in the same backend.

Blueprint extract-credentials-id

Change-Id: I10ad4b36c6f03d1712621eaffcfefa48a5453aff
This commit is contained in:
Adam Young 2013-05-06 14:09:07 -04:00
parent 2e15fe428a
commit d95adc1ac8
19 changed files with 473 additions and 217 deletions

View File

@ -91,6 +91,9 @@
# exist to order to maintain support for your v2 clients.
# default_domain_id = default
[credential]
# driver = keystone.credential.backends.sql.Credential
[trust]
# driver = keystone.trust.backends.sql.Trust

View File

@ -48,7 +48,7 @@ class DbSync(BaseApp):
@staticmethod
def main():
for k in ['identity', 'catalog', 'policy', 'token']:
for k in ['identity', 'catalog', 'policy', 'token', 'credential']:
driver = importutils.import_object(getattr(CONF, k).driver)
if hasattr(driver, 'db_sync'):
driver.db_sync()

View File

@ -268,6 +268,10 @@ def configure():
'driver',
group='identity',
default='keystone.identity.backends.sql.Identity')
register_str(
'driver',
group='credential',
default='keystone.credential.backends.sql.Credential')
register_str(
'driver',
group='policy',

View File

@ -149,7 +149,7 @@ def filterprotected(*filters):
@dependency.requires('identity_api', 'policy_api', 'token_api',
'trust_api', 'catalog_api')
'trust_api', 'catalog_api', 'credential_api')
class V2Controller(wsgi.Application):
"""Base controller class for Identity API v2."""

View File

@ -0,0 +1,78 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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.
import sqlalchemy
from sqlalchemy.orm import sessionmaker
from migrate import ForeignKeyConstraint
MYSQL_FKEY_QUERY = ("select CONSTRAINT_NAME from "
"INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS "
"where table_name = 'credential'")
def drop_constraint_mysql(migrate_engine):
session = sessionmaker(bind=migrate_engine)()
#http://bugs.mysql.com/bug.php?id=10333
#MySQL varies from the SQL norm in naming
#Foreign Keys. The mapping from the column name
#to the actual foreign key is stored in
#INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
#SQLAlchemy expects the constraint name to be
# the column name.
for constraint in session.execute(MYSQL_FKEY_QUERY):
session.execute('ALTER TABLE credential DROP FOREIGN KEY %s;'
% constraint[0])
session.commit()
def remove_constraints(migrate_engine):
if migrate_engine.name == 'sqlite':
return
if migrate_engine.name == 'mysql':
drop_constraint_mysql(migrate_engine)
return
meta = sqlalchemy.MetaData()
meta.bind = migrate_engine
user_table = sqlalchemy.Table('user', meta, autoload=True)
proj_table = sqlalchemy.Table('project', meta, autoload=True)
cred_table = sqlalchemy.Table('credential', meta, autoload=True)
ForeignKeyConstraint(columns=[cred_table.c.user_id],
refcolumns=[user_table.c.id]).drop()
ForeignKeyConstraint(columns=[cred_table.c.project_id],
refcolumns=[proj_table.c.id]).drop()
def add_constraints(migrate_engine):
if migrate_engine.name == 'sqlite':
return
meta = sqlalchemy.MetaData()
meta.bind = migrate_engine
user_table = sqlalchemy.Table('user', meta, autoload=True)
proj_table = sqlalchemy.Table('project', meta, autoload=True)
cred_table = sqlalchemy.Table('credential', meta, autoload=True)
ForeignKeyConstraint(columns=[cred_table.c.user_id],
refcolumns=[user_table.c.id]).create()
ForeignKeyConstraint(columns=[cred_table.c.project_id],
refcolumns=[proj_table.c.id]).create()
def upgrade(migrate_engine):
remove_constraints(migrate_engine)
def downgrade(migrate_engine):
add_constraints(migrate_engine)

View File

@ -0,0 +1,19 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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.
from keystone.credential import controllers
from keystone.credential.core import *
from keystone.credential import routers

View File

View File

@ -0,0 +1,96 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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.
from keystone import clean
from keystone.common import sql
from keystone.common.sql import migration
from keystone.common import utils
from keystone import credential
from keystone import exception
class CredentialModel(sql.ModelBase, sql.DictBase):
__tablename__ = 'credential'
attributes = ['id', 'user_id', 'project_id', 'blob', 'type']
id = sql.Column(sql.String(64), primary_key=True)
user_id = sql.Column(sql.String(64),
nullable=False)
project_id = sql.Column(sql.String(64))
blob = sql.Column(sql.JsonBlob(), nullable=False)
type = sql.Column(sql.String(255), nullable=False)
extra = sql.Column(sql.JsonBlob())
class Credential(sql.Base, credential.Driver):
# Internal interface to manage the database
def db_sync(self):
migration.db_sync()
# credential crud
@sql.handle_conflicts(type='credential')
def create_credential(self, credential_id, credential):
session = self.get_session()
with session.begin():
ref = CredentialModel.from_dict(credential)
session.add(ref)
session.flush()
return ref.to_dict()
def list_credentials(self):
session = self.get_session()
refs = session.query(CredentialModel).all()
return [ref.to_dict() for ref in refs]
def get_credential(self, credential_id):
session = self.get_session()
ref = (session.query(CredentialModel)
.filter_by(id=credential_id).first())
if ref is None:
raise exception.CredentialNotFound(credential_id=credential_id)
return ref.to_dict()
@sql.handle_conflicts(type='credential')
def update_credential(self, credential_id, credential):
session = self.get_session()
with session.begin():
ref = (session.query(CredentialModel)
.filter_by(id=credential_id).first())
if ref is None:
raise exception.CredentialNotFound(credential_id=credential_id)
old_dict = ref.to_dict()
for k in credential:
old_dict[k] = credential[k]
new_credential = CredentialModel.from_dict(old_dict)
for attr in CredentialModel.attributes:
if attr != 'id':
setattr(ref, attr, getattr(new_credential, attr))
ref.extra = new_credential.extra
session.flush()
return ref.to_dict()
def delete_credential(self, credential_id):
session = self.get_session()
try:
ref = (session.query(CredentialModel)
.filter_by(id=credential_id).one())
except sql.NotFound:
raise exception.CredentialNotFound(credential_id=credential_id)
with session.begin():
session.delete(ref)
session.flush()

View File

@ -0,0 +1,55 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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.
from keystone.common import controller
class CredentialV3(controller.V3Controller):
collection_name = 'credentials'
member_name = 'credential'
@controller.protected
def create_credential(self, context, credential):
ref = self._assign_unique_id(self._normalize_dict(credential))
ref = self.credential_api.create_credential(context, ref['id'], ref)
return CredentialV3.wrap_member(context, ref)
@controller.protected
def list_credentials(self, context):
refs = self.credential_api.list_credentials(context)
return CredentialV3.wrap_collection(context, refs)
@controller.protected
def get_credential(self, context, credential_id):
ref = self.credential_api.get_credential(context, credential_id)
return CredentialV3.wrap_member(context, ref)
@controller.protected
def update_credential(self, context, credential_id, credential):
self._require_matching_id(credential_id, credential)
ref = self.credential_api.update_credential(
context,
credential_id,
credential)
return CredentialV3.wrap_member(context, ref)
def _delete_credential(self, context, credential_id):
return self.credential_api.delete_credential(context, credential_id)
@controller.protected
def delete_credential(self, context, credential_id):
return self._delete_credential(context, credential_id)

View File

@ -0,0 +1,87 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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.
"""Main entry point into the Credentials service."""
from keystone.common import dependency
from keystone.common import logging
from keystone.common import manager
from keystone import config
from keystone import exception
CONF = config.CONF
LOG = logging.getLogger(__name__)
@dependency.provider('credential_api')
class Manager(manager.Manager):
"""Default pivot point for the Credential backend.
See :mod:`keystone.common.manager.Manager` for more details on how this
dynamically calls the backend.
"""
def __init__(self):
super(Manager, self).__init__(CONF.credential.driver)
class Driver(object):
# credential crud
def create_credential(self, credential_id, credential):
"""Creates a new credential.
:raises: keystone.exception.Conflict
"""
raise exception.NotImplemented()
def list_credentials(self):
"""List all credentials in the system.
:returns: a list of credential_refs or an empty list.
"""
raise exception.NotImplemented()
def get_credential(self, credential_id):
"""Get a credential by ID.
:returns: credential_ref
:raises: keystone.exception.CredentialNotFound
"""
raise exception.NotImplemented()
def update_credential(self, credential_id, credential):
"""Updates an existing credential.
:raises: keystone.exception.CredentialNotFound,
keystone.exception.Conflict
"""
raise exception.NotImplemented()
def delete_credential(self, credential_id):
"""Deletes an existing credential.
:raises: keystone.exception.CredentialNotFound
"""
raise exception.NotImplemented()

View File

@ -0,0 +1,26 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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.
"""WSGI Routers for the Credentials service."""
from keystone.common import router
from keystone.credential import controllers
def append_v3_routers(mapper, routers):
routers.append(
router.Router(controllers.CredentialV3(),
'credentials', 'credential'))

View File

@ -51,19 +51,6 @@ class Group(sql.ModelBase, sql.DictBase):
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
class Credential(sql.ModelBase, sql.DictBase):
__tablename__ = 'credential'
attributes = ['id', 'user_id', 'project_id', 'blob', 'type']
id = sql.Column(sql.String(64), primary_key=True)
user_id = sql.Column(sql.String(64),
sql.ForeignKey('user.id'),
nullable=False)
project_id = sql.Column(sql.String(64), sql.ForeignKey('project.id'))
blob = sql.Column(sql.JsonBlob(), nullable=False)
type = sql.Column(sql.String(255), nullable=False)
extra = sql.Column(sql.JsonBlob())
class Domain(sql.ModelBase, sql.DictBase):
__tablename__ = 'domain'
attributes = ['id', 'name', 'enabled']
@ -857,59 +844,6 @@ class Identity(sql.Base, identity.Driver):
session.delete(ref)
session.flush()
# credential crud
@sql.handle_conflicts(type='credential')
def create_credential(self, credential_id, credential):
session = self.get_session()
with session.begin():
ref = Credential.from_dict(credential)
session.add(ref)
session.flush()
return ref.to_dict()
def list_credentials(self):
session = self.get_session()
refs = session.query(Credential).all()
return [ref.to_dict() for ref in refs]
def get_credential(self, credential_id):
session = self.get_session()
ref = session.query(Credential).filter_by(id=credential_id).first()
if ref is None:
raise exception.CredentialNotFound(credential_id=credential_id)
return ref.to_dict()
@sql.handle_conflicts(type='credential')
def update_credential(self, credential_id, credential):
session = self.get_session()
with session.begin():
ref = session.query(Credential).filter_by(id=credential_id).first()
if ref is None:
raise exception.CredentialNotFound(credential_id=credential_id)
old_dict = ref.to_dict()
for k in credential:
old_dict[k] = credential[k]
new_credential = Credential.from_dict(old_dict)
for attr in Credential.attributes:
if attr != 'id':
setattr(ref, attr, getattr(new_credential, attr))
ref.extra = new_credential.extra
session.flush()
return ref.to_dict()
def delete_credential(self, credential_id):
session = self.get_session()
try:
ref = session.query(Credential).filter_by(id=credential_id).one()
except sql.NotFound:
raise exception.CredentialNotFound(credential_id=credential_id)
with session.begin():
session.delete(ref)
session.flush()
# role crud
@sql.handle_conflicts(type='role')

View File

@ -572,9 +572,9 @@ class ProjectV3(controller.V3Controller):
def _delete_project(self, context, project_id):
# Delete any credentials that reference this project
for cred in self.identity_api.list_credentials(context):
for cred in self.credential_api.list_credentials(context):
if cred['project_id'] == project_id:
self.identity_api.delete_credential(context, cred['id'])
self.credential_api.delete_credential(context, cred['id'])
# Finally delete the project itself - the backend is
# responsible for deleting any role assignments related
# to this project
@ -643,9 +643,9 @@ class UserV3(controller.V3Controller):
def _delete_user(self, context, user_id):
# Delete any credentials that reference this user
for cred in self.identity_api.list_credentials(context):
for cred in self.credential_api.list_credentials(context):
if cred['user_id'] == user_id:
self.identity_api.delete_credential(context, cred['id'])
self.credential_api.delete_credential(context, cred['id'])
# Make sure any tokens are marked as deleted
self._delete_tokens_for_user(context, user_id)
@ -709,44 +709,6 @@ class GroupV3(controller.V3Controller):
return self._delete_group(context, group_id)
class CredentialV3(controller.V3Controller):
collection_name = 'credentials'
member_name = 'credential'
@controller.protected
def create_credential(self, context, credential):
ref = self._assign_unique_id(self._normalize_dict(credential))
ref = self.identity_api.create_credential(context, ref['id'], ref)
return CredentialV3.wrap_member(context, ref)
@controller.protected
def list_credentials(self, context):
refs = self.identity_api.list_credentials(context)
return CredentialV3.wrap_collection(context, refs)
@controller.protected
def get_credential(self, context, credential_id):
ref = self.identity_api.get_credential(context, credential_id)
return CredentialV3.wrap_member(context, ref)
@controller.protected
def update_credential(self, context, credential_id, credential):
self._require_matching_id(credential_id, credential)
ref = self.identity_api.update_credential(
context,
credential_id,
credential)
return CredentialV3.wrap_member(context, ref)
def _delete_credential(self, context, credential_id):
return self.identity_api.delete_credential(context, credential_id)
@controller.protected
def delete_credential(self, context, credential_id):
return self._delete_credential(context, credential_id)
class RoleV3(controller.V3Controller):
collection_name = 'roles'
member_name = 'role'

View File

@ -432,50 +432,6 @@ class Driver(object):
"""
raise exception.NotImplemented()
# credential crud
def create_credential(self, credential_id, credential):
"""Creates a new credential.
:raises: keystone.exception.Conflict
"""
raise exception.NotImplemented()
def list_credentials(self):
"""List all credentials in the system.
:returns: a list of credential_refs or an empty list.
"""
raise exception.NotImplemented()
def get_credential(self, credential_id):
"""Get a credential by ID.
:returns: credential_ref
:raises: keystone.exception.CredentialNotFound
"""
raise exception.NotImplemented()
def update_credential(self, credential_id, credential):
"""Updates an existing credential.
:raises: keystone.exception.CredentialNotFound,
keystone.exception.Conflict
"""
raise exception.NotImplemented()
def delete_credential(self, credential_id):
"""Deletes an existing credential.
:raises: keystone.exception.CredentialNotFound
"""
raise exception.NotImplemented()
# role crud
def create_role(self, role_id, role):

View File

@ -107,10 +107,6 @@ def append_v3_routers(mapper, routers):
action='list_groups_for_user',
conditions=dict(method=['GET']))
routers.append(
router.Router(controllers.CredentialV3(),
'credentials', 'credential'))
role_controller = controllers.RoleV3()
routers.append(router.Router(role_controller, 'roles', 'role'))
mapper.connect('/projects/{project_id}/users/{user_id}/roles/{role_id}',

View File

@ -23,6 +23,7 @@ from keystone import controllers
from keystone.common import logging
from keystone.common import wsgi
from keystone.contrib import ec2
from keystone import credential
from keystone import identity
from keystone import policy
from keystone import routers
@ -35,6 +36,7 @@ LOG = logging.getLogger(__name__)
DRIVERS = dict(
catalog_api=catalog.Manager(),
credentials_api=credential.Manager(),
ec2_api=ec2.Manager(),
identity_api=identity.Manager(),
policy_api=policy.Manager(),
@ -88,7 +90,7 @@ def v3_app_factory(global_conf, **local_conf):
conf.update(local_conf)
mapper = routes.Mapper()
v3routers = []
for module in [auth, catalog, identity, policy]:
for module in [auth, catalog, credential, identity, policy]:
module.routers.append_v3_routers(mapper, v3routers)
if CONF.trust.enabled:

View File

@ -35,6 +35,7 @@ from keystone.common import logging
from keystone.common import utils
from keystone.common import wsgi
from keystone import config
from keystone import credential
from keystone import exception
from keystone import identity
from keystone.openstack.common import timeutils
@ -76,6 +77,7 @@ def testsdir(*p):
def initialize_drivers():
DRIVERS['catalog_api'] = catalog.Manager()
DRIVERS['credential_api'] = credential.Manager()
DRIVERS['identity_api'] = identity.Manager()
DRIVERS['policy_api'] = policy.Manager()
DRIVERS['token_api'] = token.Manager()

View File

@ -0,0 +1,80 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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.
import uuid
from keystone import exception
import test_v3
class CredentialTestCase(test_v3.RestfulTestCase):
"""Test credential CRUD"""
def setUp(self):
super(CredentialTestCase, self).setUp()
self.credential_id = uuid.uuid4().hex
self.credential = self.new_credential_ref(
user_id=self.user['id'],
project_id=self.project_id)
self.credential['id'] = self.credential_id
self.credential_api.create_credential(
self.credential_id,
self.credential)
def test_list_credentials(self):
"""GET /credentials"""
r = self.get('/credentials')
self.assertValidCredentialListResponse(r, ref=self.credential)
def test_list_credentials_xml(self):
"""GET /credentials (xml data)"""
r = self.get('/credentials', content_type='xml')
self.assertValidCredentialListResponse(r, ref=self.credential)
def test_create_credential(self):
"""POST /credentials"""
ref = self.new_credential_ref(user_id=self.user['id'])
r = self.post(
'/credentials',
body={'credential': ref})
self.assertValidCredentialResponse(r, ref)
def test_get_credential(self):
"""GET /credentials/{credential_id}"""
r = self.get(
'/credentials/%(credential_id)s' % {
'credential_id': self.credential_id})
self.assertValidCredentialResponse(r, self.credential)
def test_update_credential(self):
"""PATCH /credentials/{credential_id}"""
ref = self.new_credential_ref(
user_id=self.user['id'],
project_id=self.project_id)
del ref['id']
r = self.patch(
'/credentials/%(credential_id)s' % {
'credential_id': self.credential_id},
body={'credential': ref})
self.assertValidCredentialResponse(r, ref)
def test_delete_credential(self):
"""DELETE /credentials/{credential_id}"""
self.delete(
'/credentials/%(credential_id)s' % {
'credential_id': self.credential_id})

View File

@ -22,7 +22,7 @@ import test_v3
class IdentityTestCase(test_v3.RestfulTestCase):
"""Test domains, projects, users, groups, credential & role CRUD"""
"""Test domains, projects, users, groups, & role CRUD"""
def setUp(self):
super(IdentityTestCase, self).setUp()
@ -38,7 +38,7 @@ class IdentityTestCase(test_v3.RestfulTestCase):
user_id=self.user['id'],
project_id=self.project_id)
self.credential['id'] = self.credential_id
self.identity_api.create_credential(
self.credential_api.create_credential(
self.credential_id,
self.credential)
@ -182,6 +182,7 @@ class IdentityTestCase(test_v3.RestfulTestCase):
- Check entities in self.domain are unaffected
"""
# Create a 2nd set of entities in a 2nd domain
self.domain2 = self.new_domain_ref()
self.identity_api.create_domain(self.domain2['id'], self.domain2)
@ -202,7 +203,7 @@ class IdentityTestCase(test_v3.RestfulTestCase):
self.credential2 = self.new_credential_ref(
user_id=self.user2['id'],
project_id=self.project2['id'])
self.identity_api.create_credential(
self.credential_api.create_credential(
self.credential2['id'],
self.credential2)
@ -229,7 +230,7 @@ class IdentityTestCase(test_v3.RestfulTestCase):
self.identity_api.get_user,
user_id=self.user2['id'])
self.assertRaises(exception.CredentialNotFound,
self.identity_api.get_credential,
self.credential_api.get_credential,
credential_id=self.credential2['id'])
# ...and that all self.domain entities are still here
@ -242,7 +243,7 @@ class IdentityTestCase(test_v3.RestfulTestCase):
r = self.identity_api.get_user(self.user['id'])
self.user.pop('password')
self.assertDictEqual(r, self.user)
r = self.identity_api.get_credential(self.credential['id'])
r = self.credential_api.get_credential(self.credential['id'])
self.assertDictEqual(r, self.credential)
# project crud tests
@ -291,7 +292,7 @@ class IdentityTestCase(test_v3.RestfulTestCase):
"""
# First check the credential for this project is present
r = self.identity_api.get_credential(self.credential['id'])
r = self.credential_api.get_credential(self.credential['id'])
self.assertDictEqual(r, self.credential)
# Create a second credential with a different project
self.project2 = self.new_project_ref(
@ -300,7 +301,7 @@ class IdentityTestCase(test_v3.RestfulTestCase):
self.credential2 = self.new_credential_ref(
user_id=self.user['id'],
project_id=self.project2['id'])
self.identity_api.create_credential(
self.credential_api.create_credential(
self.credential2['id'],
self.credential2)
@ -312,10 +313,10 @@ class IdentityTestCase(test_v3.RestfulTestCase):
# Deleting the project should have deleted any credentials
# that reference this project
self.assertRaises(exception.CredentialNotFound,
self.identity_api.get_credential,
self.credential_api.get_credential,
credential_id=self.credential['id'])
# But the credential for project2 is unaffected
r = self.identity_api.get_credential(self.credential2['id'])
r = self.credential_api.get_credential(self.credential2['id'])
self.assertDictEqual(r, self.credential2)
# user crud tests
@ -429,7 +430,7 @@ class IdentityTestCase(test_v3.RestfulTestCase):
"""
# First check the credential for this user is present
r = self.identity_api.get_credential(self.credential['id'])
r = self.credential_api.get_credential(self.credential['id'])
self.assertDictEqual(r, self.credential)
# Create a second credential with a different user
self.user2 = self.new_user_ref(
@ -439,7 +440,7 @@ class IdentityTestCase(test_v3.RestfulTestCase):
self.credential2 = self.new_credential_ref(
user_id=self.user2['id'],
project_id=self.project['id'])
self.identity_api.create_credential(
self.credential_api.create_credential(
self.credential2['id'],
self.credential2)
# Create a token for this user which we can check later
@ -462,13 +463,13 @@ class IdentityTestCase(test_v3.RestfulTestCase):
# Deleting the user should have deleted any credentials
# that reference this project
self.assertRaises(exception.CredentialNotFound,
self.identity_api.get_credential,
self.credential_api.get_credential,
credential_id=self.credential['id'])
# And the no tokens we remain valid
tokens = self.token_api.list_tokens(self.user['id'])
self.assertEquals(len(tokens), 0)
# But the credential for user2 is unaffected
r = self.identity_api.get_credential(self.credential2['id'])
r = self.credential_api.get_credential(self.credential2['id'])
self.assertDictEqual(r, self.credential2)
# group crud tests
@ -511,51 +512,6 @@ class IdentityTestCase(test_v3.RestfulTestCase):
self.delete('/groups/%(group_id)s' % {
'group_id': self.group_id})
# credential crud tests
def test_list_credentials(self):
"""GET /credentials"""
r = self.get('/credentials')
self.assertValidCredentialListResponse(r, ref=self.credential)
def test_list_credentials_xml(self):
"""GET /credentials (xml data)"""
r = self.get('/credentials', content_type='xml')
self.assertValidCredentialListResponse(r, ref=self.credential)
def test_create_credential(self):
"""POST /credentials"""
ref = self.new_credential_ref(user_id=self.user['id'])
r = self.post(
'/credentials',
body={'credential': ref})
self.assertValidCredentialResponse(r, ref)
def test_get_credential(self):
"""GET /credentials/{credential_id}"""
r = self.get(
'/credentials/%(credential_id)s' % {
'credential_id': self.credential_id})
self.assertValidCredentialResponse(r, self.credential)
def test_update_credential(self):
"""PATCH /credentials/{credential_id}"""
ref = self.new_credential_ref(
user_id=self.user['id'],
project_id=self.project_id)
del ref['id']
r = self.patch(
'/credentials/%(credential_id)s' % {
'credential_id': self.credential_id},
body={'credential': ref})
self.assertValidCredentialResponse(r, ref)
def test_delete_credential(self):
"""DELETE /credentials/{credential_id}"""
self.delete(
'/credentials/%(credential_id)s' % {
'credential_id': self.credential_id})
# role crud tests
def test_create_role(self):