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:
parent
2e15fe428a
commit
d95adc1ac8
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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',
|
||||
|
@ -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."""
|
||||
|
||||
|
@ -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)
|
19
keystone/credential/__init__.py
Normal file
19
keystone/credential/__init__.py
Normal 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
|
0
keystone/credential/backends/__init__.py
Normal file
0
keystone/credential/backends/__init__.py
Normal file
96
keystone/credential/backends/sql.py
Normal file
96
keystone/credential/backends/sql.py
Normal 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()
|
55
keystone/credential/controllers.py
Normal file
55
keystone/credential/controllers.py
Normal 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)
|
87
keystone/credential/core.py
Normal file
87
keystone/credential/core.py
Normal 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()
|
26
keystone/credential/routers.py
Normal file
26
keystone/credential/routers.py
Normal 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'))
|
@ -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')
|
||||
|
@ -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'
|
||||
|
@ -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):
|
||||
|
@ -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}',
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
80
tests/test_v3_credential.py
Normal file
80
tests/test_v3_credential.py
Normal 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})
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user