diff options
| author | Dolph Mathews <dolph.mathews@gmail.com> | 2012-11-09 08:32:18 -0600 |
|---|---|---|
| committer | Gerrit Code Review <review@openstack.org> | 2012-11-20 17:49:00 +0000 |
| commit | ddc8c833684ff0db65553b09b87eed7b80c7075d (patch) | |
| tree | b7c52bf9ca7edbc52020c0177315cd1a0a8ce755 | |
| parent | ff669f0da9cbf5250d8bb3e904608677f9164b6c (diff) | |
| download | keystone-ddc8c833684ff0db65553b09b87eed7b80c7075d.tar.gz keystone-ddc8c833684ff0db65553b09b87eed7b80c7075d.tar.xz keystone-ddc8c833684ff0db65553b09b87eed7b80c7075d.zip | |
v3 Identity
- v3 identity tests (bug 1023930)
- v3 identity implementation (bug 1023937)
Change-Id: Ic46575afe9760d9da85e262d0cf063ea002d9dcd
| -rw-r--r-- | keystone/clean.py | 19 | ||||
| -rw-r--r-- | keystone/common/sql/migrate_repo/versions/007_add_domain_tables.py | 79 | ||||
| -rw-r--r-- | keystone/exception.py | 8 | ||||
| -rw-r--r-- | keystone/identity/backends/kvs.py | 25 | ||||
| -rw-r--r-- | keystone/identity/backends/sql.py | 525 | ||||
| -rw-r--r-- | tests/test_v3_identity.py | 349 |
6 files changed, 886 insertions, 119 deletions
diff --git a/keystone/clean.py b/keystone/clean.py index 8eb63b0d..89d56762 100644 --- a/keystone/clean.py +++ b/keystone/clean.py @@ -37,15 +37,20 @@ def check_type(property_name, value, expected_type, display_expected_type): raise exception.ValidationError(msg) -def tenant_name(name): - check_type("Tenant name", name, basestring, "string or unicode") +def check_name(property_name, name): + check_type('%s name' % property_name, name, basestring, 'str or unicode') name = name.strip() - check_length("Tenant name", name) + check_length('%s name' % property_name, name) return name +def domain_name(name): + return check_name('Domain', name) + + +def tenant_name(name): + return check_name('Tenant', name) + + def user_name(name): - check_type("User name", name, basestring, "string or unicode") - name = name.strip() - check_length("User name", name) - return name + return check_name('User', name) diff --git a/keystone/common/sql/migrate_repo/versions/007_add_domain_tables.py b/keystone/common/sql/migrate_repo/versions/007_add_domain_tables.py new file mode 100644 index 00000000..cb35f26d --- /dev/null +++ b/keystone/common/sql/migrate_repo/versions/007_add_domain_tables.py @@ -0,0 +1,79 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 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 migrate +import sqlalchemy as sql + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; bind + # migrate_engine to your metadata + meta = sql.MetaData() + meta.bind = migrate_engine + + domain_table = sql.Table( + 'domain', + meta, + sql.Column('id', sql.String(64), primary_key=True), + sql.Column('name', sql.String(64), unique=True, nullable=False), + sql.Column('extra', sql.Text())) + domain_table.create(migrate_engine, checkfirst=True) + + sql.Table('user', meta, autoload=True) + user_domain_metadata_table = sql.Table( + 'user_domain_metadata', + meta, + sql.Column( + 'user_id', + sql.String(64), + sql.ForeignKey('user.id'), + primary_key=True), + sql.Column( + 'domain_id', + sql.String(64), + sql.ForeignKey('domain.id'), + primary_key=True), + sql.Column('data', sql.Text())) + user_domain_metadata_table.create(migrate_engine, checkfirst=True) + + sql.Table('tenant', meta, autoload=True) + credential_table = sql.Table( + 'credential', + meta, + sql.Column('id', sql.String(64), primary_key=True), + sql.Column('user_id', + sql.String(64), + sql.ForeignKey('user.id'), + nullable=False), + sql.Column('project_id', + sql.String(64), + sql.ForeignKey('tenant.id')), + sql.Column('blob', sql.Text(), nullable=False), + sql.Column('type', sql.String(255), nullable=False), + sql.Column('extra', sql.Text())) + credential_table.create(migrate_engine, checkfirst=True) + + role = sql.Table('role', meta, autoload=True) + extra = sql.Column('extra', sql.Text()) + role.create_column(extra) + + +def downgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + + role = sql.Table('role', meta, autoload=True) + role.drop_column('extra') diff --git a/keystone/exception.py b/keystone/exception.py index cc61a632..4b9721cf 100644 --- a/keystone/exception.py +++ b/keystone/exception.py @@ -95,10 +95,18 @@ class ServiceNotFound(NotFound): """Could not find service: %(service_id)s""" +class DomainNotFound(NotFound): + """Could not find domain: %(domain_id)s""" + + class TenantNotFound(NotFound): """Could not find tenant: %(tenant_id)s""" +class ProjectNotFound(TenantNotFound): + """Could not find project: %(project_id)s""" + + class TokenNotFound(NotFound): """Could not find token: %(token_id)s""" diff --git a/keystone/identity/backends/kvs.py b/keystone/identity/backends/kvs.py index b64baa38..65917181 100644 --- a/keystone/identity/backends/kvs.py +++ b/keystone/identity/backends/kvs.py @@ -357,3 +357,28 @@ class Identity(kvs.Base, identity.Driver): role_list = set(self.db.get('role_list', [])) role_list.remove(role_id) self.db.set('role_list', list(role_list)) + + # domain crud + + def create_domain(self, domain_id, domain): + self.db.set('domain-%s' % domain_id, domain) + domain_list = set(self.db.get('domain_list', [])) + domain_list.add(domain_id) + self.db.set('domain_list', list(domain_list)) + return domain + + def list_domains(self): + return self.db.get('domain_list', []) + + def get_domain(self, domain_id): + return self.db.get('domain-%s' % domain_id) + + def update_domain(self, domain_id, domain): + self.db.set('domain-%s' % domain_id, domain) + return domain + + def delete_domain(self, domain_id): + self.db.delete('domain-%s' % domain_id) + domain_list = set(self.db.get('domain_list', [])) + domain_list.remove(domain_id) + self.db.set('domain_list', list(domain_list)) diff --git a/keystone/identity/backends/sql.py b/keystone/identity/backends/sql.py index 753a30e2..209ec2f0 100644 --- a/keystone/identity/backends/sql.py +++ b/keystone/identity/backends/sql.py @@ -45,7 +45,30 @@ class User(sql.ModelBase, sql.DictBase): extra = sql.Column(sql.JsonBlob()) +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('tenant.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'] + id = sql.Column(sql.String(64), primary_key=True) + name = sql.Column(sql.String(64), unique=True, nullable=False) + extra = sql.Column(sql.JsonBlob()) + + +# TODO(dolph): rename to Project class Tenant(sql.ModelBase, sql.DictBase): + # TODO(dolph): rename to project __tablename__ = 'tenant' attributes = ['id', 'name'] id = sql.Column(sql.String(64), primary_key=True) @@ -58,11 +81,14 @@ class Role(sql.ModelBase, sql.DictBase): attributes = ['id', 'name'] id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), unique=True, nullable=False) + extra = sql.Column(sql.JsonBlob()) -class Metadata(sql.ModelBase, sql.DictBase): +class UserProjectMetadata(sql.ModelBase, sql.DictBase): + # TODO(dolph): rename to user_project_metadata (needs a migration) __tablename__ = 'metadata' user_id = sql.Column(sql.String(64), primary_key=True) + # TODO(dolph): rename to project_id (needs a migration) tenant_id = sql.Column(sql.String(64), primary_key=True) data = sql.Column(sql.JsonBlob()) @@ -75,6 +101,14 @@ class Metadata(sql.ModelBase, sql.DictBase): return dict(self.iteritems()) +class UserDomainMetadata(sql.ModelBase, sql.DictBase): + __tablename__ = 'user_domain_metadata' + user_id = sql.Column(sql.String(64), primary_key=True) + domain_id = sql.Column(sql.String(64), primary_key=True) + data = sql.Column(sql.JsonBlob()) + + +# TODO(dolph): ... do we need this table? class UserTenantMembership(sql.ModelBase, sql.DictBase): """Tenant membership join table.""" __tablename__ = 'user_tenant_membership' @@ -164,52 +198,79 @@ class Identity(sql.Base, identity.Driver): return [identity.filter_user(user_ref.to_dict()) for user_ref in user_refs] - def _get_user(self, user_id): + def get_metadata(self, user_id, tenant_id=None, domain_id=None): session = self.get_session() - user_ref = session.query(User).filter_by(id=user_id).first() - if not user_ref: - raise exception.UserNotFound(user_id=user_id) - return user_ref.to_dict() - def _get_user_by_name(self, user_name): - session = self.get_session() - user_ref = session.query(User).filter_by(name=user_name).first() - if not user_ref: - raise exception.UserNotFound(user_id=user_name) - return user_ref.to_dict() + if tenant_id: + q = session.query(UserProjectMetadata) + q = q.filter_by(tenant_id=tenant_id) + elif domain_id: + q = session.query(UserDomainMetadata) + q = q.filter_by(domain_id=domain_id) + q = q.filter_by(user_id=user_id) - def get_user(self, user_id): - return identity.filter_user(self._get_user(user_id)) + try: + return q.one().data + except sql.NotFound: + raise exception.MetadataNotFound() - def get_user_by_name(self, user_name): - return identity.filter_user(self._get_user_by_name(user_name)) + def create_grant(self, role_id, user_id, domain_id, project_id): + self.get_role(role_id) + self.get_user(user_id) + if domain_id: + self.get_domain(domain_id) + if project_id: + self.get_tenant(project_id) - def get_metadata(self, user_id, tenant_id): - session = self.get_session() - query = session.query(Metadata) - query = query.filter_by(user_id=user_id) - query = query.filter_by(tenant_id=tenant_id) - metadata_ref = query.first() - if metadata_ref is None: - raise exception.MetadataNotFound() - return metadata_ref.data + try: + metadata_ref = self.get_metadata(user_id, project_id, domain_id) + is_new = False + except exception.MetadataNotFound: + metadata_ref = {} + is_new = True + roles = set(metadata_ref.get('roles', [])) + roles.add(role_id) + metadata_ref['roles'] = list(roles) + if is_new: + self.create_metadata(user_id, project_id, metadata_ref, domain_id) + else: + self.update_metadata(user_id, project_id, metadata_ref, domain_id) - def get_role(self, role_id): - session = self.get_session() - role_ref = session.query(Role).filter_by(id=role_id).first() - if role_ref is None: + def list_grants(self, user_id, domain_id, project_id): + metadata_ref = self.get_metadata(user_id, project_id, domain_id) + return [self.get_role(x) for x in metadata_ref.get('roles', [])] + + def get_grant(self, role_id, user_id, domain_id, project_id): + metadata_ref = self.get_metadata(user_id, project_id, domain_id) + role_ids = set(metadata_ref.get('roles', [])) + if role_id not in role_ids: raise exception.RoleNotFound(role_id=role_id) - return role_ref + return self.get_role(role_id) - def list_users(self): - session = self.get_session() - user_refs = session.query(User) - return [identity.filter_user(x.to_dict()) for x in user_refs] + def delete_grant(self, role_id, user_id, domain_id, project_id): + self.get_role(role_id) + self.get_user(user_id) + if domain_id: + self.get_domain(domain_id) + if project_id: + self.get_tenant(project_id) - def list_roles(self): - session = self.get_session() - role_refs = session.query(Role) - return list(role_refs) + try: + metadata_ref = self.get_metadata(user_id, project_id, domain_id) + is_new = False + except exception.MetadataNotFound: + metadata_ref = {} + is_new = True + roles = set(metadata_ref.get('roles', [])) + try: + roles.remove(role_id) + except KeyError: + raise exception.RoleNotFound(role_id=role_id) + metadata_ref['roles'] = list(roles) + if is_new: + self.create_metadata(user_id, project_id, metadata_ref, domain_id) + else: + self.update_metadata(user_id, project_id, metadata_ref, domain_id) # These should probably be part of the high-level API def add_user_to_tenant(self, tenant_id, user_id): @@ -306,6 +367,198 @@ class Identity(sql.Base, identity.Driver): self.update_metadata(user_id, tenant_id, metadata_ref) # CRUD + @handle_conflicts(type='tenant') + def create_tenant(self, tenant_id, tenant): + tenant['name'] = clean.tenant_name(tenant['name']) + session = self.get_session() + with session.begin(): + tenant_ref = Tenant.from_dict(tenant) + session.add(tenant_ref) + session.flush() + return tenant_ref.to_dict() + + @handle_conflicts(type='tenant') + def update_tenant(self, tenant_id, tenant): + session = self.get_session() + + if 'name' in tenant: + tenant['name'] = clean.tenant_name(tenant['name']) + + try: + tenant_ref = session.query(Tenant).filter_by(id=tenant_id).one() + except sql.NotFound: + raise exception.TenantNotFound(tenant_id=tenant_id) + + with session.begin(): + old_tenant_dict = tenant_ref.to_dict() + for k in tenant: + old_tenant_dict[k] = tenant[k] + new_tenant = Tenant.from_dict(old_tenant_dict) + tenant_ref.name = new_tenant.name + tenant_ref.extra = new_tenant.extra + session.flush() + return tenant_ref.to_dict(include_extra_dict=True) + + def delete_tenant(self, tenant_id): + session = self.get_session() + + try: + tenant_ref = session.query(Tenant).filter_by(id=tenant_id).one() + except sql.NotFound: + raise exception.TenantNotFound(tenant_id=tenant_id) + + with session.begin(): + q = session.query(UserTenantMembership) + q = q.filter_by(tenant_id=tenant_id) + q.delete(False) + + q = session.query(UserProjectMetadata) + q = q.filter_by(tenant_id=tenant_id) + q.delete(False) + + if not session.query(Tenant).filter_by(id=tenant_id).delete(False): + raise exception.TenantNotFound(tenant_id=tenant_id) + + session.delete(tenant_ref) + session.flush() + + @handle_conflicts(type='metadata') + def create_metadata(self, user_id, tenant_id, metadata, domain_id=None): + session = self.get_session() + with session.begin(): + if tenant_id: + session.add(UserProjectMetadata(user_id=user_id, + tenant_id=tenant_id, + data=metadata)) + elif domain_id: + session.add(UserDomainMetadata(user_id=user_id, + domain_id=domain_id, + data=metadata)) + session.flush() + return metadata + + @handle_conflicts(type='metadata') + def update_metadata(self, user_id, tenant_id, metadata, domain_id=None): + session = self.get_session() + with session.begin(): + if tenant_id: + metadata_ref = session.query(UserProjectMetadata)\ + .filter_by(user_id=user_id)\ + .filter_by(tenant_id=tenant_id)\ + .first() + elif domain_id: + metadata_ref = session.query(UserDomainMetadata)\ + .filter_by(user_id=user_id)\ + .filter_by(domain_id=domain_id)\ + .first() + data = metadata_ref.data.copy() + data.update(metadata) + metadata_ref.data = data + session.flush() + return metadata_ref + + # domain crud + + @handle_conflicts(type='domain') + def create_domain(self, domain_id, domain): + session = self.get_session() + with session.begin(): + ref = Domain.from_dict(domain) + session.add(ref) + session.flush() + return ref.to_dict() + + def list_domains(self): + session = self.get_session() + refs = session.query(Domain).all() + return [ref.to_dict() for ref in refs] + + def get_domain(self, domain_id): + session = self.get_session() + ref = session.query(Domain).filter_by(id=domain_id).first() + if ref is None: + raise exception.DomainNotFound(domain_id=domain_id) + return ref.to_dict() + + @handle_conflicts(type='domain') + def update_domain(self, domain_id, domain): + session = self.get_session() + with session.begin(): + ref = session.query(Domain).filter_by(id=domain_id).first() + if ref is None: + raise exception.DomainNotFound(domain_id=domain_id) + old_dict = ref.to_dict() + for k in domain: + old_dict[k] = domain[k] + new_domain = Domain.from_dict(old_dict) + for attr in Domain.attributes: + if attr != 'id': + setattr(ref, attr, getattr(new_domain, attr)) + ref.extra = new_domain.extra + session.flush() + return ref.to_dict() + + def delete_domain(self, domain_id): + session = self.get_session() + ref = session.query(Domain).filter_by(id=domain_id).first() + if not ref: + raise exception.DomainNotFound(domain_id=domain_id) + with session.begin(): + session.delete(ref) + session.flush() + + # project crud + + @handle_conflicts(type='project') + def create_project(self, project_id, project): + return self.create_tenant(project_id, project) + + def get_project(self, project_id): + return self.get_tenant(project_id) + + def list_projects(self): + return self.get_tenants() + + @handle_conflicts(type='project') + def update_project(self, project_id, project): + session = self.get_session() + with session.begin(): + ref = session.query(Tenant).filter_by(id=project_id).first() + if ref is None: + raise exception.TenantNotFound(project_id=project_id) + old_dict = ref.to_dict() + for k in project: + old_dict[k] = project[k] + new_project = Tenant.from_dict(old_dict) + for attr in Tenant.attributes: + if attr != 'id': + setattr(ref, attr, getattr(new_project, attr)) + ref.extra = new_project.extra + session.flush() + return ref.to_dict() + + def delete_project(self, project_id): + return self.delete_tenant(project_id) + + def list_user_projects(self, user_id): + session = self.get_session() + user = self.get_user(user_id) + metadata_refs = session\ + .query(UserProjectMetadata)\ + .filter_by(user_id=user_id) + project_ids = set([x.tenant_id for x in metadata_refs + if x.data.get('roles')]) + if user.get('project_id'): + project_ids.add(user['project_id']) + + # FIXME(dolph): this should be removed with proper migrations + if user.get('tenant_id'): + project_ids.add(user['tenant_id']) + + return [self.get_project(x) for x in project_ids] + + # user crud + @handle_conflicts(type='user') def create_user(self, user_id, user): user['name'] = clean.user_name(user['name']) @@ -317,6 +570,31 @@ class Identity(sql.Base, identity.Driver): session.flush() return identity.filter_user(user_ref.to_dict()) + def list_users(self): + session = self.get_session() + user_refs = session.query(User) + return [identity.filter_user(x.to_dict()) for x in user_refs] + + def _get_user(self, user_id): + session = self.get_session() + user_ref = session.query(User).filter_by(id=user_id).first() + if not user_ref: + raise exception.UserNotFound(user_id=user_id) + return user_ref.to_dict() + + def _get_user_by_name(self, user_name): + session = self.get_session() + user_ref = session.query(User).filter_by(name=user_name).first() + if not user_ref: + raise exception.UserNotFound(user_id=user_name) + return user_ref.to_dict() + + def get_user(self, user_id): + return identity.filter_user(self._get_user(user_id)) + + def get_user_by_name(self, user_name): + return identity.filter_user(self._get_user_by_name(user_name)) + @handle_conflicts(type='user') def update_user(self, user_id, user): if 'name' in user: @@ -333,128 +611,151 @@ class Identity(sql.Base, identity.Driver): for k in user: old_user_dict[k] = user[k] new_user = User.from_dict(old_user_dict) - - user_ref.name = new_user.name + for attr in User.attributes: + if attr != 'id': + setattr(user_ref, attr, getattr(new_user, attr)) user_ref.extra = new_user.extra session.flush() return identity.filter_user(user_ref.to_dict(include_extra_dict=True)) def delete_user(self, user_id): session = self.get_session() + + try: + ref = session.query(User).filter_by(id=user_id).one() + except sql.NotFound: + raise exception.UserNotFound(user_id=user_id) + with session.begin(): - query = session.query(UserTenantMembership) - query = query.filter_by(user_id=user_id) - query.delete(False) - query = session.query(Metadata) - query = query.filter_by(user_id=user_id) - query.delete(False) + q = session.query(UserTenantMembership) + q = q.filter_by(user_id=user_id) + q.delete(False) + + q = session.query(UserProjectMetadata) + q = q.filter_by(user_id=user_id) + q.delete(False) + if not session.query(User).filter_by(id=user_id).delete(False): raise exception.UserNotFound(user_id=user_id) - @handle_conflicts(type='tenant') - def create_tenant(self, tenant_id, tenant): - tenant['name'] = clean.tenant_name(tenant['name']) - session = self.get_session() - with session.begin(): - tenant_ref = Tenant.from_dict(tenant) - session.add(tenant_ref) + session.delete(ref) session.flush() - return tenant_ref.to_dict() - @handle_conflicts(type='tenant') - def update_tenant(self, tenant_id, tenant): - if 'name' in tenant: - tenant['name'] = clean.tenant_name(tenant['name']) + # credential crud + + @handle_conflicts(type='credential') + def create_credential(self, credential_id, credential): session = self.get_session() with session.begin(): - tenant_ref = session.query(Tenant).filter_by(id=tenant_id).first() - if tenant_ref is None: - raise exception.TenantNotFound(tenant_id=tenant_id) - old_tenant_dict = tenant_ref.to_dict() - for k in tenant: - old_tenant_dict[k] = tenant[k] - new_tenant = Tenant.from_dict(old_tenant_dict) - - tenant_ref.name = new_tenant.name - tenant_ref.extra = new_tenant.extra + ref = Credential.from_dict(credential) + session.add(ref) session.flush() - return tenant_ref.to_dict(include_extra_dict=True) + return ref.to_dict() - def delete_tenant(self, tenant_id): + def list_credentials(self): session = self.get_session() - with session.begin(): - query = session.query(UserTenantMembership) - query = query.filter_by(tenant_id=tenant_id) - query.delete(False) - query = session.query(Metadata) - query = query.filter_by(tenant_id=tenant_id) - query.delete(False) - if not session.query(Tenant).filter_by(id=tenant_id).delete(False): - raise exception.TenantNotFound(tenant_id=tenant_id) + refs = session.query(Credential).all() + return [ref.to_dict() for ref in refs] - @handle_conflicts(type='metadata') - def create_metadata(self, user_id, tenant_id, metadata): + 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() + + @handle_conflicts(type='credential') + def update_credential(self, credential_id, credential): session = self.get_session() with session.begin(): - session.add(Metadata(user_id=user_id, - tenant_id=tenant_id, - data=metadata)) + 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 metadata + return ref.to_dict() - @handle_conflicts(type='metadata') - def update_metadata(self, user_id, tenant_id, metadata): + 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(): - query = session.query(Metadata) - query = query.filter_by(user_id=user_id) - query = query.filter_by(tenant_id=tenant_id) - metadata_ref = query.first() - data = metadata_ref.data.copy() - for k in metadata: - data[k] = metadata[k] - metadata_ref.data = data + session.delete(ref) session.flush() - return metadata_ref - def delete_metadata(self, user_id, tenant_id): - self.db.delete('metadata-%s-%s' % (tenant_id, user_id)) - return None + # role crud @handle_conflicts(type='role') def create_role(self, role_id, role): session = self.get_session() with session.begin(): - session.add(Role(**role)) + ref = Role.from_dict(role) + session.add(ref) session.flush() - return role + return ref.to_dict() + + def list_roles(self): + session = self.get_session() + refs = session.query(Role).all() + return [ref.to_dict() for ref in refs] + + def get_role(self, role_id): + session = self.get_session() + ref = session.query(Role).filter_by(id=role_id).first() + if ref is None: + raise exception.RoleNotFound(role_id=role_id) + return ref.to_dict() @handle_conflicts(type='role') def update_role(self, role_id, role): session = self.get_session() with session.begin(): - role_ref = session.query(Role).filter_by(id=role_id).first() - if role_ref is None: + ref = session.query(Role).filter_by(id=role_id).first() + if ref is None: raise exception.RoleNotFound(role_id=role_id) + old_dict = ref.to_dict() for k in role: - role_ref[k] = role[k] + old_dict[k] = role[k] + new_role = Role.from_dict(old_dict) + for attr in Role.attributes: + if attr != 'id': + setattr(ref, attr, getattr(new_role, attr)) + ref.extra = new_role.extra session.flush() - return role_ref + return ref.to_dict() def delete_role(self, role_id): session = self.get_session() + + try: + ref = session.query(Role).filter_by(id=role_id).one() + except sql.NotFound: + raise exception.RoleNotFound(role_id=role_id) + with session.begin(): - metadata_refs = session.query(Metadata) - for metadata_ref in metadata_refs: + for metadata_ref in session.query(UserProjectMetadata): metadata = metadata_ref.to_dict() - user_id = metadata['user_id'] - tenant_id = metadata['tenant_id'] try: - self.remove_role_from_user_and_tenant(user_id, - tenant_id, - role_id) + self.remove_role_from_user_and_tenant( + metadata['user_id'], metadata['tenant_id'], role_id) except exception.RoleNotFound: pass + + # FIXME(dolph): user-domain metadata needs to be updated + if not session.query(Role).filter_by(id=role_id).delete(): raise exception.RoleNotFound(role_id=role_id) + + session.delete(ref) session.flush() diff --git a/tests/test_v3_identity.py b/tests/test_v3_identity.py new file mode 100644 index 00000000..43bbf293 --- /dev/null +++ b/tests/test_v3_identity.py @@ -0,0 +1,349 @@ +import uuid + +import test_v3 + + +class IdentityTestCase(test_v3.RestfulTestCase): + """Test domains, projects, users, credential & role CRUD""" + + def setUp(self): + super(IdentityTestCase, self).setUp() + + self.domain_id = uuid.uuid4().hex + self.domain = self.new_domain_ref() + self.domain['id'] = self.domain_id + self.identity_api.create_domain( + self.domain_id, + self.domain.copy()) + + self.project_id = uuid.uuid4().hex + self.project = self.new_project_ref( + domain_id=self.domain_id) + self.project['id'] = self.project_id + self.identity_api.create_project( + self.project_id, + self.project.copy()) + + self.user_id = uuid.uuid4().hex + self.user = self.new_user_ref( + domain_id=self.domain_id, + project_id=self.project_id) + self.user['id'] = self.user_id + self.identity_api.create_user( + self.user_id, + self.user.copy()) + + 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.identity_api.create_credential( + self.credential_id, + self.credential.copy()) + + self.role_id = uuid.uuid4().hex + self.role = self.new_role_ref() + self.role['id'] = self.role_id + self.identity_api.create_role( + self.role_id, + self.role.copy()) + + # domain validation + + def assertValidDomainListResponse(self, resp, ref): + return self.assertValidListResponse( + resp, + 'domains', + self.assertValidDomain, + ref) + + def assertValidDomainResponse(self, resp, ref): + return self.assertValidResponse( + resp, + 'domain', + self.assertValidDomain, + ref) + + def assertValidDomain(self, entity, ref=None): + if ref: + pass + return entity + + # project validation + + def assertValidProjectListResponse(self, resp, ref): + return self.assertValidListResponse( + resp, + 'projects', + self.assertValidProject, + ref) + + def assertValidProjectResponse(self, resp, ref): + return self.assertValidResponse( + resp, + 'project', + self.assertValidProject, + ref) + + def assertValidProject(self, entity, ref=None): + self.assertIsNotNone(entity.get('domain_id')) + if ref: + self.assertEqual(ref['domain_id'], entity['domain_id']) + return entity + + # user validation + + def assertValidUserListResponse(self, resp, ref): + return self.assertValidListResponse( + resp, + 'users', + self.assertValidUser, + ref) + + def assertValidUserResponse(self, resp, ref): + return self.assertValidResponse( + resp, + 'user', + self.assertValidUser, + ref) + + def assertValidUser(self, entity, ref=None): + self.assertIsNotNone(entity.get('domain_id')) + self.assertIsNotNone(entity.get('email')) + self.assertIsNone(entity.get('password')) + if ref: + self.assertEqual(ref['domain_id'], entity['domain_id']) + self.assertEqual(ref['email'], entity['email']) + return entity + + # credential validation + + def assertValidCredentialListResponse(self, resp, ref): + return self.assertValidListResponse( + resp, + 'credentials', + self.assertValidCredential, + ref) + + def assertValidCredentialResponse(self, resp, ref): + return self.assertValidResponse( + resp, + 'credential', + self.assertValidCredential, + ref) + + def assertValidCredential(self, entity, ref=None): + self.assertIsNotNone(entity.get('user_id')) + self.assertIsNotNone(entity.get('blob')) + self.assertIsNotNone(entity.get('type')) + if ref: + self.assertEqual(ref['user_id'], entity['user_id']) + self.assertEqual(ref['blob'], entity['blob']) + self.assertEqual(ref['type'], entity['type']) + self.assertEqual(ref.get('project_id'), entity.get('project_id')) + return entity + + # role validation + + def assertValidRoleListResponse(self, resp, ref): + return self.assertValidListResponse( + resp, + 'roles', + self.assertValidRole, + ref) + + def assertValidRoleResponse(self, resp, ref): + return self.assertValidResponse( + resp, + 'role', + self.assertValidRole, + ref) + + def assertValidRole(self, entity, ref=None): + if ref: + pass + return entity + + # domain crud tests + + def test_create_domain(self): + """POST /domains""" + ref = self.new_domain_ref() + r = self.post( + '/domains', + body={'domain': ref}) + return self.assertValidDomainResponse(r, ref) + + def test_list_domains(self): + """GET /domains""" + r = self.get('/domains') + self.assertValidDomainListResponse(r, self.domain) + + def test_get_domain(self): + """GET /domains/{domain_id}""" + r = self.get('/domains/%(domain_id)s' % { + 'domain_id': self.domain_id}) + self.assertValidDomainResponse(r, self.domain) + + def test_update_domain(self): + """PATCH /domains/{domain_id}""" + ref = self.new_domain_ref() + del ref['id'] + r = self.patch('/domains/%(domain_id)s' % { + 'domain_id': self.domain_id}, + body={'domain': ref}) + self.assertValidDomainResponse(r, ref) + + def test_delete_domain(self): + """DELETE /domains/{domain_id}""" + self.delete('/domains/%(domain_id)s' % { + 'domain_id': self.domain_id}) + + # project crud tests + + def test_list_projects(self): + """GET /projects""" + r = self.get('/projects') + self.assertValidProjectListResponse(r, self.project) + + def test_create_project(self): + """POST /projects""" + ref = self.new_project_ref(domain_id=self.domain_id) + r = self.post( + '/projects', + body={'project': ref}) + self.assertValidProjectResponse(r, ref) + + def test_get_project(self): + """GET /projects/{project_id}""" + r = self.get( + '/projects/%(project_id)s' % { + 'project_id': self.project_id}) + self.assertValidProjectResponse(r, self.project) + + def test_update_project(self): + """PATCH /projects/{project_id}""" + ref = self.new_project_ref(domain_id=self.domain_id) + del ref['id'] + r = self.patch( + '/projects/%(project_id)s' % { + 'project_id': self.project_id}, + body={'project': ref}) + self.assertValidProjectResponse(r, ref) + + def test_delete_project(self): + """DELETE /projects/{project_id}""" + self.delete( + '/projects/%(project_id)s' % { + 'project_id': self.project_id}) + + # user crud tests + + def test_create_user(self): + """POST /users""" + ref = self.new_user_ref(domain_id=self.domain_id) + r = self.post( + '/users', + body={'user': ref}) + return self.assertValidUserResponse(r, ref) + + def test_list_users(self): + """GET /users""" + r = self.get('/users') + self.assertValidUserListResponse(r, self.user) + + def test_get_user(self): + """GET /users/{user_id}""" + r = self.get('/users/%(user_id)s' % { + 'user_id': self.user_id}) + self.assertValidUserResponse(r, self.user) + + def test_update_user(self): + """PATCH /users/{user_id}""" + user = self.new_user_ref(domain_id=self.domain_id) + del user['id'] + r = self.patch('/users/%(user_id)s' % { + 'user_id': self.user_id}, + body={'user': user}) + self.assertValidUserResponse(r, user) + + def test_delete_user(self): + """DELETE /users/{user_id}""" + self.delete('/users/%(user_id)s' % { + 'user_id': self.user_id}) + + # credential crud tests + + def test_list_credentials(self): + """GET /credentials""" + r = self.get('/credentials') + self.assertValidCredentialListResponse(r, 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): + """POST /roles""" + ref = self.new_role_ref() + r = self.post( + '/roles', + body={'role': ref}) + return self.assertValidRoleResponse(r, ref) + + def test_list_roles(self): + """GET /roles""" + r = self.get('/roles') + self.assertValidRoleListResponse(r, self.role) + + def test_get_role(self): + """GET /roles/{role_id}""" + r = self.get('/roles/%(role_id)s' % { + 'role_id': self.role_id}) + self.assertValidRoleResponse(r, self.role) + + def test_update_role(self): + """PATCH /roles/{role_id}""" + ref = self.new_role_ref() + del ref['id'] + r = self.patch('/roles/%(role_id)s' % { + 'role_id': self.role_id}, + body={'role': ref}) + self.assertValidRoleResponse(r, ref) + + def test_delete_role(self): + """DELETE /roles/{role_id}""" + self.delete('/roles/%(role_id)s' % { + 'role_id': self.role_id}) |
