summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenry Nash <henryn@linux.vnet.ibm.com>2013-01-16 16:10:24 +0000
committerHenry Nash <henryn@linux.vnet.ibm.com>2013-02-12 16:09:15 +0000
commit8a89464d62e9c81a1ba15c0a3aa695456fc6fd33 (patch)
tree46c61fe10c2859fdeddd80379d15bc839d37cd3d
parentf1defe8f624e006a7562bc07cd471bdd176e303e (diff)
downloadkeystone-8a89464d62e9c81a1ba15c0a3aa695456fc6fd33.tar.gz
keystone-8a89464d62e9c81a1ba15c0a3aa695456fc6fd33.tar.xz
keystone-8a89464d62e9c81a1ba15c0a3aa695456fc6fd33.zip
Keystone backend preparation for domain-scoping
These changes lay the ground work for the implmentation of domain-scoping, but are benign in that they don't change the token. They include making domain_id a first-class attribute in the user and project entity (i.e. move it out of the 'extra' attribute), filling in domain grant and project support for the kvs backend and fixing a series of issues in the mirgation to make it work for both MySQL, Postgresql and sqlite. A further, separate, commit will actually provide the code to update the actual tokens once the v3 token support has been added. blueprint domain-scoping blueprint default-domain Change-Id: I55ab7947a6a1efbab003bd234856bd3805bb4a63
-rw-r--r--keystone/common/controller.py50
-rw-r--r--keystone/common/models.py13
-rw-r--r--keystone/common/sql/legacy.py9
-rw-r--r--keystone/common/sql/migrate_repo/versions/008_create_default_domain.py2
-rw-r--r--keystone/common/sql/migrate_repo/versions/010_normalize_identity_migration.py35
-rw-r--r--keystone/common/sql/migrate_repo/versions/012_populate_endpoint_type.py61
-rw-r--r--keystone/common/sql/migrate_repo/versions/014_add_group_tables.py3
-rw-r--r--keystone/common/sql/migrate_repo/versions/015_tenant_to_project.py1
-rw-r--r--keystone/common/sql/migrate_repo/versions/016_normalize_domain_ids.py414
-rw-r--r--keystone/common/sql/nova.py5
-rw-r--r--keystone/config.py3
-rw-r--r--keystone/identity/backends/kvs.py80
-rw-r--r--keystone/identity/backends/ldap/core.py23
-rw-r--r--keystone/identity/backends/pam.py8
-rw-r--r--keystone/identity/backends/sql.py53
-rw-r--r--keystone/identity/controllers.py83
-rw-r--r--keystone/identity/core.py19
-rw-r--r--keystone/token/controllers.py127
-rw-r--r--tests/default_fixtures.py11
-rw-r--r--tests/test_backend.py248
-rw-r--r--tests/test_backend_ldap.py42
-rw-r--r--tests/test_backend_pam.py7
-rw-r--r--tests/test_backend_sql.py18
-rw-r--r--tests/test_keystoneclient.py10
-rw-r--r--tests/test_migrate_nova_auth.py7
-rw-r--r--tests/test_sql_upgrade.py217
26 files changed, 1261 insertions, 288 deletions
diff --git a/keystone/common/controller.py b/keystone/common/controller.py
index 3112fc0c..83400d3e 100644
--- a/keystone/common/controller.py
+++ b/keystone/common/controller.py
@@ -10,6 +10,7 @@ from keystone import exception
LOG = logging.getLogger(__name__)
CONF = config.CONF
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
def protected(f):
@@ -68,6 +69,21 @@ class V2Controller(wsgi.Application):
msg = '%s field is required and cannot be empty' % attr
raise exception.ValidationError(message=msg)
+ def _normalize_domain_id(self, context, ref):
+ """Fill in domain_id since v2 calls are not domain-aware.
+
+ This will overwrite any domain_id that was inadvertently
+ specified in the v2 call.
+
+ """
+ ref['domain_id'] = DEFAULT_DOMAIN_ID
+ return ref
+
+ def _filter_domain_id(self, ref):
+ """Remove domain_id since v2 calls are not domain-aware."""
+ ref.pop('domain_id', None)
+ return ref
+
class V3Controller(V2Controller):
"""Base controller class for Identity API v3.
@@ -148,3 +164,37 @@ class V3Controller(V2Controller):
value = context['query_string'][attr]
return [r for r in refs if r[attr] == value]
return refs
+
+ def _normalize_domain_id(self, context, ref):
+ """Fill in domain_id if not specified in a v3 call."""
+
+ if 'domain_id' not in ref:
+ if context['is_admin']:
+ ref['domain_id'] = DEFAULT_DOMAIN_ID
+ else:
+ # Fish the domain_id out of the token
+ #
+ # We could make this more efficient by loading the domain_id
+ # into the context in the wrapper function above (since
+ # this version of normalize_domain will only be called inside
+ # a v3 protected call). However, given that we only use this
+ # for creating entities, this optimization is probably not
+ # worth the duplication of state
+ try:
+ token_ref = self.token_api.get_token(
+ context=context, token_id=context['token_id'])
+ except exception.TokenNotFound:
+ LOG.warning(_('Invalid token in normalize_domain_id'))
+ raise exception.Unauthorized()
+
+ if 'domain' in token_ref:
+ ref['domain_id'] = token_ref['domain']['id']
+ else:
+ # FIXME(henry-nash) Revisit this once v3 token scoping
+ # across domains has been hashed out
+ ref['domain_id'] = DEFAULT_DOMAIN_ID
+ return ref
+
+ def _filter_domain_id(self, ref):
+ """Override v2 filter to let domain_id out for v3 calls."""
+ return ref
diff --git a/keystone/common/models.py b/keystone/common/models.py
index 72818111..f572d382 100644
--- a/keystone/common/models.py
+++ b/keystone/common/models.py
@@ -87,6 +87,7 @@ class User(Model):
Required keys:
id
name
+ domain_id
Optional keys:
password
@@ -95,7 +96,7 @@ class User(Model):
enabled (bool, default True)
"""
- required_keys = ('id', 'name')
+ required_keys = ('id', 'name', 'domain_id')
optional_keys = ('password', 'description', 'email', 'enabled')
@@ -105,15 +106,16 @@ class Group(Model):
Required keys:
id
name
+ domain_id
Optional keys:
- domain_id
+
description
"""
- required_keys = ('id', 'name')
- optional_keys = ('domain_id', 'description')
+ required_keys = ('id', 'name', 'domain_id')
+ optional_keys = ('description')
class Project(Model):
@@ -122,6 +124,7 @@ class Project(Model):
Required keys:
id
name
+ domain_id
Optional Keys:
description
@@ -129,7 +132,7 @@ class Project(Model):
"""
- required_keys = ('id', 'name')
+ required_keys = ('id', 'name', 'domain_id')
optional_keys = ('description', 'enabled')
diff --git a/keystone/common/sql/legacy.py b/keystone/common/sql/legacy.py
index 4d742456..82dda2cf 100644
--- a/keystone/common/sql/legacy.py
+++ b/keystone/common/sql/legacy.py
@@ -22,9 +22,12 @@ from sqlalchemy import exc
from keystone.common import logging
from keystone.contrib.ec2.backends import sql as ec2_sql
from keystone.identity.backends import sql as identity_sql
+from keystone import config
LOG = logging.getLogger(__name__)
+CONF = config.CONF
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
def export_db(db):
@@ -103,7 +106,8 @@ class LegacyMigration(object):
# map
new_dict = {'description': x.get('desc', ''),
'id': x.get('uid', x.get('id')),
- 'enabled': x.get('enabled', True)}
+ 'enabled': x.get('enabled', True),
+ 'domain_id': x.get('domain_id', DEFAULT_DOMAIN_ID)}
new_dict['name'] = x.get('name', new_dict.get('id'))
# track internal ids
self._project_map[x.get('id')] = new_dict['id']
@@ -117,7 +121,8 @@ class LegacyMigration(object):
new_dict = {'email': x.get('email', ''),
'password': x.get('password', None),
'id': x.get('uid', x.get('id')),
- 'enabled': x.get('enabled', True)}
+ 'enabled': x.get('enabled', True),
+ 'domain_id': x.get('domain_id', DEFAULT_DOMAIN_ID)}
if x.get('tenant_id'):
new_dict['tenant_id'] = self._project_map.get(x['tenant_id'])
new_dict['name'] = x.get('name', new_dict.get('id'))
diff --git a/keystone/common/sql/migrate_repo/versions/008_create_default_domain.py b/keystone/common/sql/migrate_repo/versions/008_create_default_domain.py
index fb1da77b..3a88f897 100644
--- a/keystone/common/sql/migrate_repo/versions/008_create_default_domain.py
+++ b/keystone/common/sql/migrate_repo/versions/008_create_default_domain.py
@@ -22,7 +22,7 @@ from keystone import config
CONF = config.CONF
-DEFAULT_DOMAIN_ID = CONF['identity']['default_domain_id']
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
def upgrade(migrate_engine):
diff --git a/keystone/common/sql/migrate_repo/versions/010_normalize_identity_migration.py b/keystone/common/sql/migrate_repo/versions/010_normalize_identity_migration.py
index 01c153ce..7f0ee379 100644
--- a/keystone/common/sql/migrate_repo/versions/010_normalize_identity_migration.py
+++ b/keystone/common/sql/migrate_repo/versions/010_normalize_identity_migration.py
@@ -32,26 +32,21 @@ def is_enabled(enabled):
return bool(enabled)
-def downgrade_user_table(meta, migrate_engine):
+def downgrade_user_table(meta, migrate_engine, session):
user_table = Table('user', meta, autoload=True)
- maker = sessionmaker(bind=migrate_engine)
- session = maker()
for user in session.query(user_table).all():
extra = json.loads(user.extra)
extra['password'] = user.password
extra['enabled'] = '%r' % user.enabled
- values = {'extra': json.dumps(extra)}
+ values = {'extra': json.dumps(extra)}
update = user_table.update().\
where(user_table.c.id == user.id).\
values(values)
migrate_engine.execute(update)
- session.commit()
-def downgrade_tenant_table(meta, migrate_engine):
+def downgrade_tenant_table(meta, migrate_engine, session):
tenant_table = Table('tenant', meta, autoload=True)
- maker = sessionmaker(bind=migrate_engine)
- session = maker()
for tenant in session.query(tenant_table).all():
extra = json.loads(tenant.extra)
extra['description'] = tenant.description
@@ -61,13 +56,10 @@ def downgrade_tenant_table(meta, migrate_engine):
where(tenant_table.c.id == tenant.id).\
values(values)
migrate_engine.execute(update)
- session.commit()
-def upgrade_user_table(meta, migrate_engine):
+def upgrade_user_table(meta, migrate_engine, session):
user_table = Table('user', meta, autoload=True)
- maker = sessionmaker(bind=migrate_engine)
- session = maker()
for user in session.query(user_table).all():
extra = json.loads(user.extra)
values = {'password': extra.pop('password', None),
@@ -77,14 +69,10 @@ def upgrade_user_table(meta, migrate_engine):
where(user_table.c.id == user.id).\
values(values)
migrate_engine.execute(update)
- session.commit()
-def upgrade_tenant_table(meta, migrate_engine):
+def upgrade_tenant_table(meta, migrate_engine, session):
tenant_table = Table('tenant', meta, autoload=True)
-
- maker = sessionmaker(bind=migrate_engine)
- session = maker()
for tenant in session.query(tenant_table):
extra = json.loads(tenant.extra)
values = {'description': extra.pop('description', None),
@@ -94,18 +82,21 @@ def upgrade_tenant_table(meta, migrate_engine):
where(tenant_table.c.id == tenant.id).\
values(values)
migrate_engine.execute(update)
- session.commit()
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
- upgrade_user_table(meta, migrate_engine)
- upgrade_tenant_table(meta, migrate_engine)
+ session = sessionmaker(bind=migrate_engine)()
+ upgrade_user_table(meta, migrate_engine, session)
+ upgrade_tenant_table(meta, migrate_engine, session)
+ session.commit()
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
- downgrade_user_table(meta, migrate_engine)
- downgrade_tenant_table(meta, migrate_engine)
+ session = sessionmaker(bind=migrate_engine)()
+ downgrade_user_table(meta, migrate_engine, session)
+ downgrade_tenant_table(meta, migrate_engine, session)
+ session.commit()
diff --git a/keystone/common/sql/migrate_repo/versions/012_populate_endpoint_type.py b/keystone/common/sql/migrate_repo/versions/012_populate_endpoint_type.py
index abfe728a..cede906d 100644
--- a/keystone/common/sql/migrate_repo/versions/012_populate_endpoint_type.py
+++ b/keystone/common/sql/migrate_repo/versions/012_populate_endpoint_type.py
@@ -48,13 +48,10 @@ def upgrade(migrate_engine):
'url': urls[interface],
'extra': json.dumps(extra),
}
- session.execute(
- 'INSERT INTO `%s` (%s) VALUES (%s)' % (
- new_table.name,
- ', '.join('%s' % k for k in endpoint.keys()),
- ', '.join([':%s' % k for k in endpoint.keys()])),
- endpoint)
+ insert = new_table.insert().values(endpoint)
+ migrate_engine.execute(insert)
session.commit()
+ session.close()
def downgrade(migrate_engine):
@@ -67,31 +64,31 @@ def downgrade(migrate_engine):
session = orm.sessionmaker(bind=migrate_engine)()
for ref in session.query(new_table).all():
- extra = json.loads(ref.extra)
- extra['%surl' % ref.interface] = ref.url
- endpoint = {
- 'id': ref.legacy_endpoint_id,
- 'region': ref.region,
- 'service_id': ref.service_id,
- 'extra': json.dumps(extra),
- }
-
- try:
- session.execute(
- 'INSERT INTO `%s` (%s) VALUES (%s)' % (
- legacy_table.name,
- ', '.join('%s' % k for k in endpoint.keys()),
- ', '.join([':%s' % k for k in endpoint.keys()])),
- endpoint)
- except sql.exc.IntegrityError:
- q = session.query(legacy_table)
- q = q.filter_by(id=ref.legacy_endpoint_id)
- legacy_ref = q.one()
+ q = session.query(legacy_table)
+ q = q.filter_by(id=ref.legacy_endpoint_id)
+ legacy_ref = q.first()
+ if legacy_ref:
+ # We already have one, so just update the extra
+ # attribute with the urls.
extra = json.loads(legacy_ref.extra)
extra['%surl' % ref.interface] = ref.url
-
- session.execute(
- 'UPDATE `%s` SET extra=:extra WHERE id=:id' % (
- legacy_table.name),
- {'extra': json.dumps(extra), 'id': legacy_ref.id})
- session.commit()
+ values = {'extra': json.dumps(extra)}
+ update = legacy_table.update().\
+ where(legacy_table.c.id == legacy_ref.id).\
+ values(values)
+ migrate_engine.execute(update)
+ else:
+ # This is the first one of this legacy ID, so
+ # we can insert instead.
+ extra = json.loads(ref.extra)
+ extra['%surl' % ref.interface] = ref.url
+ endpoint = {
+ 'id': ref.legacy_endpoint_id,
+ 'region': ref.region,
+ 'service_id': ref.service_id,
+ 'extra': json.dumps(extra),
+ }
+ insert = legacy_table.insert().values(endpoint)
+ migrate_engine.execute(insert)
+ session.commit()
+ session.close()
diff --git a/keystone/common/sql/migrate_repo/versions/014_add_group_tables.py b/keystone/common/sql/migrate_repo/versions/014_add_group_tables.py
index a42b5772..668bca2d 100644
--- a/keystone/common/sql/migrate_repo/versions/014_add_group_tables.py
+++ b/keystone/common/sql/migrate_repo/versions/014_add_group_tables.py
@@ -26,7 +26,8 @@ def upgrade(migrate_engine):
'group',
meta,
sql.Column('id', sql.String(64), primary_key=True),
- sql.Column('domain_id', sql.String(64), sql.ForeignKey('domain.id')),
+ sql.Column('domain_id', sql.String(64), sql.ForeignKey('domain.id'),
+ nullable=False),
sql.Column('name', sql.String(64), unique=True, nullable=False),
sql.Column('description', sql.Text()),
sql.Column('extra', sql.Text()))
diff --git a/keystone/common/sql/migrate_repo/versions/015_tenant_to_project.py b/keystone/common/sql/migrate_repo/versions/015_tenant_to_project.py
index 9b413338..4ac0d612 100644
--- a/keystone/common/sql/migrate_repo/versions/015_tenant_to_project.py
+++ b/keystone/common/sql/migrate_repo/versions/015_tenant_to_project.py
@@ -11,7 +11,6 @@ def upgrade(migrate_engine):
def downgrade(migrate_engine):
- """Replace API-version specific endpoint tables with one based on v2."""
meta = sql.MetaData()
meta.bind = migrate_engine
upgrade_table = sql.Table('project', meta, autoload=True)
diff --git a/keystone/common/sql/migrate_repo/versions/016_normalize_domain_ids.py b/keystone/common/sql/migrate_repo/versions/016_normalize_domain_ids.py
new file mode 100644
index 00000000..4705daf0
--- /dev/null
+++ b/keystone/common/sql/migrate_repo/versions/016_normalize_domain_ids.py
@@ -0,0 +1,414 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC
+# Copyright 2013 IBM
+#
+# 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.
+
+"""
+Normalize for domain_id, i.e. ensure User and Project entities have the
+domain_id as a first class attribute.
+
+Both User and Project (as well as Group) entities are owned by a
+domain, which is implemented as each having a domain_id foreign key
+in their sql representation that points back to the respective
+domain in the domain table. This domain_id attribute should also
+be required (i.e. not nullable)
+
+Adding a non_nullable foreign key attribute to a table with existing
+data causes a few problems since not all DB engines support the
+ability to either control the triggering of integrity constraints
+or the ability to modify columns after they are created.
+
+To get round the above inconsistencies, two versions of the
+upgrade/downgrade functions are supplied, one for those engines
+that support dropping columns, and one for those that don't. For
+the latter we are forced to do table copy AND control the triggering
+of integrity constraints.
+"""
+
+import sqlalchemy as sql
+from sqlalchemy.orm import sessionmaker
+from keystone import config
+
+
+CONF = config.CONF
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
+
+
+def _disable_foreign_constraints(session, migrate_engine):
+ if migrate_engine.name == 'mysql':
+ session.execute('SET foreign_key_checks = 0;')
+
+
+def _enable_foreign_constraints(session, migrate_engine):
+ if migrate_engine.name == 'mysql':
+ session.execute('SET foreign_key_checks = 1;')
+
+
+def upgrade_user_table_with_copy(meta, migrate_engine, session):
+ # We want to add the domain_id attribute to the user table. Since
+ # it is non nullable and the table may have data, easiest way is
+ # a table copy. Further, in order to keep foreign key constraints
+ # pointing at the right table, we need to be able and do a table
+ # DROP then CREATE, rather than ALTERing the name of the table.
+
+ # First make a copy of the user table
+ temp_user_table = sql.Table(
+ 'temp_user',
+ 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()),
+ sql.Column("password", sql.String(128)),
+ sql.Column("enabled", sql.Boolean, default=True))
+ temp_user_table.create(migrate_engine, checkfirst=True)
+
+ user_table = sql.Table('user', meta, autoload=True)
+ for user in session.query(user_table):
+ session.execute("insert into temp_user (id, name, extra, "
+ "password, enabled) "
+ "values ( :id, :name, :extra, "
+ ":password, :enabled);",
+ {'id': user.id,
+ 'name': user.name,
+ 'extra': user.extra,
+ 'password': user.password,
+ 'enabled': user.enabled})
+
+ # Now switch off constraints while we drop and then re-create the
+ # user table, with the additional domain_id column
+ _disable_foreign_constraints(session, migrate_engine)
+ session.execute('drop table user;')
+ # Need to create a new metadata stream since we are going to load a
+ # different version of the user table
+ meta2 = sql.MetaData()
+ meta2.bind = migrate_engine
+ domain_table = sql.Table('domain', meta2, autoload=True)
+ user_table = sql.Table(
+ 'user',
+ meta2,
+ sql.Column('id', sql.String(64), primary_key=True),
+ sql.Column('name', sql.String(64), unique=True, nullable=False),
+ sql.Column('extra', sql.Text()),
+ sql.Column("password", sql.String(128)),
+ sql.Column("enabled", sql.Boolean, default=True),
+ sql.Column('domain_id', sql.String(64), sql.ForeignKey('domain.id'),
+ nullable=False))
+ user_table.create(migrate_engine, checkfirst=True)
+
+ # Finally copy in the data from our temp table and then clean
+ # up by deleting our temp table
+ for user in session.query(temp_user_table):
+ session.execute("insert into user (id, name, extra, "
+ "password, enabled, domain_id) "
+ "values ( :id, :name, :extra, "
+ ":password, :enabled, :domain_id);",
+ {'id': user.id,
+ 'name': user.name,
+ 'extra': user.extra,
+ 'password': user.password,
+ 'enabled': user.enabled,
+ 'domain_id': DEFAULT_DOMAIN_ID})
+ _enable_foreign_constraints(session, migrate_engine)
+ session.execute("drop table temp_user;")
+
+
+def upgrade_project_table_with_copy(meta, migrate_engine, session):
+ # We want to add the domain_id attribute to the project table. Since
+ # it is non nullable and the table may have data, easiest way is
+ # a table copy. Further, in order to keep foreign key constraints
+ # pointing at the right table, we need to be able and do a table
+ # DROP then CREATE, rather than ALTERing the name of the table.
+
+ # Fist make a copy of the project table
+ temp_project_table = sql.Table(
+ 'temp_project',
+ 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()),
+ sql.Column("description", sql.Text()),
+ sql.Column("enabled", sql.Boolean, default=True))
+ temp_project_table.create(migrate_engine, checkfirst=True)
+
+ project_table = sql.Table('project', meta, autoload=True)
+ for project in session.query(project_table):
+ session.execute("insert into temp_project (id, name, extra, "
+ "description, enabled) "
+ "values ( :id, :name, :extra, "
+ ":description, :enabled);",
+ {'id': project.id,
+ 'name': project.name,
+ 'extra': project.extra,
+ 'description': project.description,
+ 'enabled': project.enabled})
+
+ # Now switch off constraints while we drop and then re-create the
+ # project table, with the additional domain_id column
+ _disable_foreign_constraints(session, migrate_engine)
+ session.execute("drop table project;")
+ # Need to create a new metadata stream since we are going to load a
+ # different version of the project table
+ meta2 = sql.MetaData()
+ meta2.bind = migrate_engine
+ domain_table = sql.Table('domain', meta2, autoload=True)
+ project_table = sql.Table(
+ 'project',
+ meta2,
+ sql.Column('id', sql.String(64), primary_key=True),
+ sql.Column('name', sql.String(64), unique=True, nullable=False),
+ sql.Column('extra', sql.Text()),
+ sql.Column('description', sql.Text()),
+ sql.Column("enabled", sql.Boolean, default=True),
+ sql.Column('domain_id', sql.String(64), sql.ForeignKey('domain.id'),
+ nullable=False))
+ project_table.create(migrate_engine, checkfirst=True)
+
+ # Finally copy in the data from our temp table and then clean
+ # up by deleting our temp table
+ for project in session.query(temp_project_table):
+ session.execute("insert into project (id, name, extra, "
+ "description, enabled, domain_id) "
+ "values ( :id, :name, :extra, "
+ ":description, :enabled, :domain_id);",
+ {'id': project.id,
+ 'name': project.name,
+ 'extra': project.extra,
+ 'description': project.description,
+ 'enabled': project.enabled,
+ 'domain_id': DEFAULT_DOMAIN_ID})
+ _enable_foreign_constraints(session, migrate_engine)
+ session.execute("drop table temp_project;")
+
+
+def downgrade_user_table_with_copy(meta, migrate_engine, session):
+ # For engines that don't support dropping columns, we need to do this
+ # as a table copy. Further, in order to keep foreign key constraints
+ # pointing at the right table, we need to be able and do a table
+ # DROP then CREATE, rather than ALTERing the name of the table.
+
+ # Fist make a copy of the user table
+ temp_user_table = sql.Table(
+ 'temp_user',
+ meta,
+ sql.Column('id', sql.String(64), primary_key=True),
+ sql.Column('name', sql.String(64), unique=True, nullable=False),
+ # Temporary table, so no need to make it a foreign key
+ sql.Column('domain_id', sql.String(64), nullable=False),
+ sql.Column("password", sql.String(128)),
+ sql.Column("enabled", sql.Boolean, default=True),
+ sql.Column('extra', sql.Text()))
+ temp_user_table.create(migrate_engine, checkfirst=True)
+
+ user_table = sql.Table('user', meta, autoload=True)
+ for user in session.query(user_table):
+ session.execute("insert into temp_user (id, name, domain_id, "
+ "password, enabled, extra) "
+ "values ( :id, :name, :domain_id, "
+ ":password, :enabled, :extra);",
+ {'id': user.id,
+ 'name': user.name,
+ 'domain_id': user.domain_id,
+ 'password': user.password,
+ 'enabled': user.enabled,
+ 'extra': user.extra})
+
+ # Now switch off constraints while we drop and then re-create the
+ # user table, less the columns we wanted to drop
+ _disable_foreign_constraints(session, migrate_engine)
+ session.execute("drop table user;")
+ # Need to create a new metadata stream since we are going to load a
+ # different version of the user table
+ meta2 = sql.MetaData()
+ meta2.bind = migrate_engine
+ user_table = sql.Table(
+ 'user',
+ meta2,
+ sql.Column('id', sql.String(64), primary_key=True),
+ sql.Column('name', sql.String(64), unique=True, nullable=False),
+ sql.Column('extra', sql.Text()),
+ sql.Column("password", sql.String(128)),
+ sql.Column("enabled", sql.Boolean, default=True))
+ user_table.create(migrate_engine, checkfirst=True)
+ _enable_foreign_constraints(session, migrate_engine)
+
+ # Finally copy in the data from our temp table and then clean
+ # up by deleting our temp table
+ for user in session.query(temp_user_table):
+ session.execute("insert into user (id, name, extra, "
+ "password, enabled) "
+ "values ( :id, :name, :extra, "
+ ":password, :enabled);",
+ {'id': user.id,
+ 'name': user.name,
+ 'extra': user.extra,
+ 'password': user.password,
+ 'enabled': user.enabled})
+ session.execute("drop table temp_user;")
+
+
+def downgrade_project_table_with_copy(meta, migrate_engine, session):
+ # For engines that don't support dropping columns, we need to do this
+ # as a table copy. Further, in order to keep foreign key constraints
+ # pointing at the right table, we need to be able and do a table
+ # DROP then CREATE, rather than ALTERing the name of the table.
+
+ # Fist make a copy of the project table
+ temp_project_table = sql.Table(
+ 'temp_project',
+ meta,
+ sql.Column('id', sql.String(64), primary_key=True),
+ sql.Column('name', sql.String(64), unique=True, nullable=False),
+ # Temporary table, so no need to make it a foreign key
+ sql.Column('domain_id', sql.String(64), nullable=False),
+ sql.Column('description', sql.Text()),
+ sql.Column("enabled", sql.Boolean, default=True),
+ sql.Column('extra', sql.Text()))
+ temp_project_table.create(migrate_engine, checkfirst=True)
+
+ project_table = sql.Table('project', meta, autoload=True)
+ for project in session.query(project_table):
+ session.execute("insert into temp_project (id, name, domain_id, "
+ "description, enabled, extra) "
+ "values ( :id, :name, :domain_id, "
+ ":description, :enabled, :extra);",
+ {'id': project.id,
+ 'name': project.name,
+ 'domain_id': project.domain_id,
+ 'description': project.description,
+ 'enabled': project.enabled,
+ 'extra': project.extra})
+
+ # Now switch off constraints while we drop and then re-create the
+ # project table, less the columns we wanted to drop
+ _disable_foreign_constraints(session, migrate_engine)
+ session.execute("drop table project;")
+ # Need to create a new metadata stream since we are going to load a
+ # different version of the project table
+ meta2 = sql.MetaData()
+ meta2.bind = migrate_engine
+ project_table = sql.Table(
+ 'project',
+ meta2,
+ sql.Column('id', sql.String(64), primary_key=True),
+ sql.Column('name', sql.String(64), unique=True, nullable=False),
+ sql.Column('extra', sql.Text()),
+ sql.Column("description", sql.Text()),
+ sql.Column("enabled", sql.Boolean, default=True))
+ project_table.create(migrate_engine, checkfirst=True)
+ _enable_foreign_constraints(session, migrate_engine)
+
+ # Finally copy in the data from our temp table and then clean
+ # up by deleting our temp table
+ for project in session.query(temp_project_table):
+ session.execute("insert into project (id, name, extra, "
+ "description, enabled) "
+ "values ( :id, :name, :extra, "
+ ":description, :enabled);",
+ {'id': project.id,
+ 'name': project.name,
+ 'extra': project.extra,
+ 'description': project.description,
+ 'enabled': project.enabled})
+ session.execute("drop table temp_project;")
+
+
+def upgrade_user_table_with_col_create(meta, migrate_engine, session):
+ # Create the domain_id column. We want this to be not nullable
+ # but also a foreign key. We can't create this right off the
+ # bat since any existing rows would cause an Integrity Error.
+ # We therefore create it nullable, fill the column with the
+ # default data and then set it to non nullable.
+ domain_table = sql.Table('domain', meta, autoload=True)
+ user_table = sql.Table('user', meta, autoload=True)
+ user_table.create_column(
+ sql.Column('domain_id', sql.String(64),
+ sql.ForeignKey('domain.id'), nullable=True))
+ for user in session.query(user_table).all():
+ values = {'domain_id': DEFAULT_DOMAIN_ID}
+ update = user_table.update().\
+ where(user_table.c.id == user.id).\
+ values(values)
+ migrate_engine.execute(update)
+ # Need to commit this or setting nullable to False will fail
+ session.commit()
+ user_table.columns.domain_id.alter(nullable=False)
+
+
+def upgrade_project_table_with_col_create(meta, migrate_engine, session):
+ # Create the domain_id column. We want this to be not nullable
+ # but also a foreign key. We can't create this right off the
+ # bat since any existing rows would cause an Integrity Error.
+ # We therefore create it nullable, fill the column with the
+ # default data and then set it to non nullable.
+ domain_table = sql.Table('domain', meta, autoload=True)
+ project_table = sql.Table('project', meta, autoload=True)
+ project_table.create_column(
+ sql.Column('domain_id', sql.String(64),
+ sql.ForeignKey('domain.id'), nullable=True))
+ for project in session.query(project_table).all():
+ values = {'domain_id': DEFAULT_DOMAIN_ID}
+ update = project_table.update().\
+ where(project_table.c.id == project.id).\
+ values(values)
+ migrate_engine.execute(update)
+ # Need to commit this or setting nullable to False will fail
+ session.commit()
+ project_table.columns.domain_id.alter(nullable=False)
+
+
+def downgrade_user_table_with_col_drop(meta, migrate_engine):
+ domain_table = sql.Table('domain', meta, autoload=True)
+ user_table = sql.Table('user', meta, autoload=True)
+ column = sql.Column('domain_id', sql.String(64),
+ sql.ForeignKey('domain.id'), nullable=False)
+ column.drop(user_table)
+
+
+def downgrade_project_table_with_col_drop(meta, migrate_engine):
+ domain_table = sql.Table('domain', meta, autoload=True)
+ project_table = sql.Table('project', meta, autoload=True)
+ column = sql.Column('domain_id', sql.String(64),
+ sql.ForeignKey('domain.id'), nullable=False)
+ column.drop(project_table)
+
+
+def upgrade(migrate_engine):
+ meta = sql.MetaData()
+ meta.bind = migrate_engine
+ session = sessionmaker(bind=migrate_engine)()
+ if migrate_engine.name in ['sqlite', 'mysql']:
+ upgrade_user_table_with_copy(meta, migrate_engine, session)
+ upgrade_project_table_with_copy(meta, migrate_engine, session)
+ else:
+ upgrade_user_table_with_col_create(meta, migrate_engine, session)
+ upgrade_project_table_with_col_create(meta, migrate_engine, session)
+ session.commit()
+ session.close()
+
+
+def downgrade(migrate_engine):
+ meta = sql.MetaData()
+ meta.bind = migrate_engine
+ session = sessionmaker(bind=migrate_engine)()
+ if migrate_engine.name in ['sqlite', 'mysql']:
+ downgrade_user_table_with_copy(meta, migrate_engine, session)
+ downgrade_project_table_with_copy(meta, migrate_engine, session)
+ else:
+ # MySQL should in theory be able to use this path, but seems to
+ # have problems dropping columns which are foreign keys
+ downgrade_user_table_with_col_drop(meta, migrate_engine)
+ downgrade_project_table_with_col_drop(meta, migrate_engine)
+ session.commit()
+ session.close()
diff --git a/keystone/common/sql/nova.py b/keystone/common/sql/nova.py
index c7fc4725..968c542f 100644
--- a/keystone/common/sql/nova.py
+++ b/keystone/common/sql/nova.py
@@ -18,12 +18,15 @@
import uuid
+from keystone import config
from keystone.common import logging
from keystone.contrib.ec2.backends import sql as ec2_sql
from keystone.identity.backends import sql as identity_sql
LOG = logging.getLogger(__name__)
+CONF = config.CONF
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
def import_auth(data):
@@ -51,6 +54,7 @@ def _create_projects(api, tenants):
tenant_dict = {
'id': _generate_uuid(),
'name': tenant['id'],
+ 'domain_id': tenant.get('domain_id', DEFAULT_DOMAIN_ID),
'description': tenant['description'],
'enabled': True,
}
@@ -66,6 +70,7 @@ def _create_users(api, users):
user_dict = {
'id': _generate_uuid(),
'name': user['id'],
+ 'domain_id': user.get('domain_id', DEFAULT_DOMAIN_ID),
'email': '',
'password': user['password'],
'enabled': True,
diff --git a/keystone/config.py b/keystone/config.py
index acd70e69..a459264d 100644
--- a/keystone/config.py
+++ b/keystone/config.py
@@ -256,6 +256,7 @@ register_str('user_name_attribute', group='ldap', default='sn')
register_str('user_mail_attribute', group='ldap', default='email')
register_str('user_pass_attribute', group='ldap', default='userPassword')
register_str('user_enabled_attribute', group='ldap', default='enabled')
+register_str('user_domain_id_attribute', group='ldap', default='domain_id')
register_int('user_enabled_mask', group='ldap', default=0)
register_str('user_enabled_default', group='ldap', default='True')
register_list('user_attribute_ignore', group='ldap',
@@ -272,6 +273,7 @@ register_str('tenant_member_attribute', group='ldap', default='member')
register_str('tenant_name_attribute', group='ldap', default='ou')
register_str('tenant_desc_attribute', group='ldap', default='desc')
register_str('tenant_enabled_attribute', group='ldap', default='enabled')
+register_str('tenant_domain_id_attribute', group='ldap', default='domain_id')
register_list('tenant_attribute_ignore', group='ldap', default='')
register_bool('tenant_allow_create', group='ldap', default=True)
register_bool('tenant_allow_update', group='ldap', default=True)
@@ -295,6 +297,7 @@ register_str('group_id_attribute', group='ldap', default='cn')
register_str('group_name_attribute', group='ldap', default='ou')
register_str('group_member_attribute', group='ldap', default='member')
register_str('group_desc_attribute', group='ldap', default='desc')
+register_str('group_domain_id_attribute', group='ldap', default='domain_id')
register_list('group_attribute_ignore', group='ldap', default='')
register_bool('group_allow_create', group='ldap', default=True)
register_bool('group_allow_update', group='ldap', default=True)
diff --git a/keystone/identity/backends/kvs.py b/keystone/identity/backends/kvs.py
index 8eef7df5..6922a1c1 100644
--- a/keystone/identity/backends/kvs.py
+++ b/keystone/identity/backends/kvs.py
@@ -67,7 +67,7 @@ class Identity(kvs.Base, identity.Driver):
tenant_keys = filter(lambda x: x.startswith("tenant-"), self.db.keys())
return [self.db.get(key) for key in tenant_keys]
- def get_project_by_name(self, tenant_name):
+ def get_project_by_name(self, tenant_name, domain_id):
try:
return self.db.get('tenant_name-%s' % tenant_name)
except exception.NotFound:
@@ -85,7 +85,7 @@ class Identity(kvs.Base, identity.Driver):
except exception.NotFound:
raise exception.UserNotFound(user_id=user_id)
- def _get_user_by_name(self, user_name):
+ def _get_user_by_name(self, user_name, domain_id):
try:
return self.db.get('user_name-%s' % user_name)
except exception.NotFound:
@@ -94,16 +94,27 @@ class Identity(kvs.Base, identity.Driver):
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))
+ def get_user_by_name(self, user_name, domain_id):
+ return identity.filter_user(
+ self._get_user_by_name(user_name, domain_id))
def get_metadata(self, user_id=None, tenant_id=None,
domain_id=None, group_id=None):
try:
if user_id:
- return self.db.get('metadata-%s-%s' % (tenant_id, user_id))
+ if tenant_id:
+ return self.db.get('metadata-%s-%s' % (tenant_id,
+ user_id))
+ else:
+ return self.db.get('metadata-%s-%s' % (domain_id,
+ user_id))
else:
- return self.db.get('metadata-%s-%s' % (tenant_id, group_id))
+ if tenant_id:
+ return self.db.get('metadata-%s-%s' % (tenant_id,
+ group_id))
+ else:
+ return self.db.get('metadata-%s-%s' % (domain_id,
+ group_id))
except exception.NotFound:
raise exception.MetadataNotFound()
@@ -195,7 +206,7 @@ class Identity(kvs.Base, identity.Driver):
raise exception.Conflict(type='user', details=msg)
try:
- self.get_user_by_name(user['name'])
+ self.get_user_by_name(user['name'], user['domain_id'])
except exception.UserNotFound:
pass
else:
@@ -294,7 +305,7 @@ class Identity(kvs.Base, identity.Driver):
raise exception.Conflict(type='tenant', details=msg)
try:
- self.get_project_by_name(tenant['name'])
+ self.get_project_by_name(tenant['name'], tenant['domain_id'])
except exception.ProjectNotFound:
pass
else:
@@ -338,18 +349,22 @@ class Identity(kvs.Base, identity.Driver):
def create_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
- if user_id:
- self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata)
- else:
- self.db.set('metadata-%s-%s' % (tenant_id, group_id), metadata)
- return metadata
+
+ return self.update_metadata(user_id, tenant_id, metadata,
+ domain_id, group_id)
def update_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
if user_id:
- self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata)
+ if tenant_id:
+ self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata)
+ else:
+ self.db.set('metadata-%s-%s' % (domain_id, user_id), metadata)
else:
- self.db.set('metadata-%s-%s' % (tenant_id, group_id), metadata)
+ if tenant_id:
+ self.db.set('metadata-%s-%s' % (tenant_id, group_id), metadata)
+ else:
+ self.db.set('metadata-%s-%s' % (domain_id, group_id), metadata)
return metadata
def create_role(self, role_id, role):
@@ -500,7 +515,24 @@ class Identity(kvs.Base, identity.Driver):
# domain crud
def create_domain(self, domain_id, domain):
+ try:
+ self.get_domain(domain_id)
+ except exception.DomainNotFound:
+ pass
+ else:
+ msg = 'Duplicate ID, %s.' % domain_id
+ raise exception.Conflict(type='domain', details=msg)
+
+ try:
+ self.get_domain_by_name(domain['name'])
+ except exception.DomainNotFound:
+ pass
+ else:
+ msg = 'Duplicate name, %s.' % domain['name']
+ raise exception.Conflict(type='domain', details=msg)
+
self.db.set('domain-%s' % domain_id, domain)
+ self.db.set('domain_name-%s' % domain['name'], domain)
domain_list = set(self.db.get('domain_list', []))
domain_list.add(domain_id)
self.db.set('domain_list', list(domain_list))
@@ -510,14 +542,30 @@ class Identity(kvs.Base, identity.Driver):
return self.db.get('domain_list', [])
def get_domain(self, domain_id):
- return self.db.get('domain-%s' % domain_id)
+ try:
+ return self.db.get('domain-%s' % domain_id)
+ except exception.NotFound:
+ raise exception.DomainNotFound(domain_id=domain_id)
+
+ def get_domain_by_name(self, domain_name):
+ try:
+ return self.db.get('domain_name-%s' % domain_name)
+ except exception.NotFound:
+ raise exception.DomainNotFound(domain_id=domain_name)
def update_domain(self, domain_id, domain):
+ orig_domain = self.get_domain(domain_id)
+ domain['id'] = domain_id
self.db.set('domain-%s' % domain_id, domain)
+ self.db.set('domain_name-%s' % domain['name'], domain)
+ if domain['name'] != orig_domain['name']:
+ self.db.delete('domain_name-%s' % orig_domain['name'])
return domain
def delete_domain(self, domain_id):
+ domain = self.get_domain(domain_id)
self.db.delete('domain-%s' % domain_id)
+ self.db.delete('domain_name-%s' % domain['name'])
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/ldap/core.py b/keystone/identity/backends/ldap/core.py
index b403abff..177dd026 100644
--- a/keystone/identity/backends/ldap/core.py
+++ b/keystone/identity/backends/ldap/core.py
@@ -106,7 +106,9 @@ class Identity(identity.Driver):
def get_projects(self):
return self.project.get_all()
- def get_project_by_name(self, tenant_name):
+ def get_project_by_name(self, tenant_name, domain_id):
+ # TODO(henry-nash): Use domain_id once domains are implemented
+ # in LDAP backend
try:
return self.project.get_by_name(tenant_name)
except exception.NotFound:
@@ -124,7 +126,9 @@ class Identity(identity.Driver):
def list_users(self):
return self.user.get_all()
- def get_user_by_name(self, user_name):
+ def get_user_by_name(self, user_name, domain_id):
+ # TODO(henry-nash): Use domain_id once domains are implemented
+ # in LDAP backend
try:
return identity.filter_user(self.user.get_by_name(user_name))
except exception.NotFound:
@@ -353,7 +357,8 @@ class UserApi(common_ldap.BaseLdap, ApiShimMixin):
attribute_mapping = {'password': 'userPassword',
'email': 'mail',
'name': 'sn',
- 'enabled': 'enabled'}
+ 'enabled': 'enabled',
+ 'domain_id': 'domain_id'}
model = models.User
@@ -363,6 +368,8 @@ class UserApi(common_ldap.BaseLdap, ApiShimMixin):
self.attribute_mapping['email'] = conf.ldap.user_mail_attribute
self.attribute_mapping['password'] = conf.ldap.user_pass_attribute
self.attribute_mapping['enabled'] = conf.ldap.user_enabled_attribute
+ self.attribute_mapping['domain_id'] = (
+ conf.ldap.user_domain_id_attribute)
self.enabled_mask = conf.ldap.user_enabled_mask
self.enabled_default = conf.ldap.user_enabled_default
self.attribute_ignore = (getattr(conf.ldap, 'user_attribute_ignore')
@@ -510,7 +517,8 @@ class ProjectApi(common_ldap.BaseLdap, ApiShimMixin):
attribute_mapping = {'name': 'ou',
'description': 'desc',
'tenantId': 'cn',
- 'enabled': 'enabled'}
+ 'enabled': 'enabled',
+ 'domain_id': 'domain_id'}
model = models.Project
def __init__(self, conf):
@@ -519,6 +527,8 @@ class ProjectApi(common_ldap.BaseLdap, ApiShimMixin):
self.attribute_mapping['name'] = conf.ldap.tenant_name_attribute
self.attribute_mapping['description'] = conf.ldap.tenant_desc_attribute
self.attribute_mapping['enabled'] = conf.ldap.tenant_enabled_attribute
+ self.attribute_mapping['domain_id'] = (
+ conf.ldap.tenant_domain_id_attribute)
self.member_attribute = (getattr(conf.ldap, 'tenant_member_attribute')
or self.DEFAULT_MEMBER_ATTRIBUTE)
self.attribute_ignore = (getattr(conf.ldap, 'tenant_attribute_ignore')
@@ -1070,7 +1080,8 @@ class GroupApi(common_ldap.BaseLdap, ApiShimMixin):
options_name = 'group'
attribute_mapping = {'name': 'ou',
'description': 'desc',
- 'groupId': 'cn'}
+ 'groupId': 'cn',
+ 'domain_id': 'domain_id'}
model = models.Group
def __init__(self, conf):
@@ -1078,6 +1089,8 @@ class GroupApi(common_ldap.BaseLdap, ApiShimMixin):
self.api = ApiShim(conf)
self.attribute_mapping['name'] = conf.ldap.group_name_attribute
self.attribute_mapping['description'] = conf.ldap.group_desc_attribute
+ self.attribute_mapping['domain_id'] = (
+ conf.ldap.group_domain_id_attribute)
self.member_attribute = (getattr(conf.ldap, 'group_member_attribute')
or self.DEFAULT_MEMBER_ATTRIBUTE)
self.attribute_ignore = (getattr(conf.ldap, 'group_attribute_ignore')
diff --git a/keystone/identity/backends/pam.py b/keystone/identity/backends/pam.py
index bc345424..3aa87c40 100644
--- a/keystone/identity/backends/pam.py
+++ b/keystone/identity/backends/pam.py
@@ -74,13 +74,17 @@ class PamIdentity(identity.Driver):
def get_project(self, tenant_id):
return {'id': tenant_id, 'name': tenant_id}
- def get_project_by_name(self, tenant_name):
+ def get_project_by_name(self, tenant_name, domain_id):
+ # TODO(henry-nash): Used domain_id once domains are implemented
+ # in LDAP backend
return {'id': tenant_name, 'name': tenant_name}
def get_user(self, user_id):
return {'id': user_id, 'name': user_id}
- def get_user_by_name(self, user_name):
+ def get_user_by_name(self, user_name, domain_id):
+ # TODO(henry-nash): Used domain_id once domains are implemented
+ # in LDAP backend
return {'id': user_name, 'name': user_name}
def get_role(self, role_id):
diff --git a/keystone/identity/backends/sql.py b/keystone/identity/backends/sql.py
index a880995f..8004a416 100644
--- a/keystone/identity/backends/sql.py
+++ b/keystone/identity/backends/sql.py
@@ -39,9 +39,11 @@ def handle_conflicts(type='object'):
class User(sql.ModelBase, sql.DictBase):
__tablename__ = 'user'
- attributes = ['id', 'name', 'password', 'enabled']
+ attributes = ['id', 'name', 'domain_id', 'password', 'enabled']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(64), unique=True, nullable=False)
+ domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
+ nullable=False)
password = sql.Column(sql.String(128))
enabled = sql.Column(sql.Boolean)
extra = sql.Column(sql.JsonBlob())
@@ -52,7 +54,8 @@ class Group(sql.ModelBase, sql.DictBase):
attributes = ['id', 'name', 'domain_id']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(64), unique=True, nullable=False)
- domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'))
+ domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
+ nullable=False)
description = sql.Column(sql.Text())
extra = sql.Column(sql.JsonBlob())
@@ -82,9 +85,11 @@ class Domain(sql.ModelBase, sql.DictBase):
# TODO(dolph): rename to Project
class Project(sql.ModelBase, sql.DictBase):
__tablename__ = 'project'
- attributes = ['id', 'name']
+ attributes = ['id', 'name', 'domain_id']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(64), unique=True, nullable=False)
+ domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
+ nullable=False)
description = sql.Column(sql.Text())
enabled = sql.Column(sql.Boolean)
extra = sql.Column(sql.JsonBlob())
@@ -222,12 +227,16 @@ class Identity(sql.Base, identity.Driver):
raise exception.ProjectNotFound(project_id=tenant_id)
return tenant_ref.to_dict()
- def get_project_by_name(self, tenant_name):
+ def get_project_by_name(self, tenant_name, domain_id):
session = self.get_session()
- tenant_ref = session.query(Project).filter_by(name=tenant_name).first()
- if not tenant_ref:
+ query = session.query(Project)
+ query = query.filter_by(name=tenant_name)
+ query = query.filter_by(domain_id=domain_id)
+ try:
+ project_ref = query.one()
+ except sql.NotFound:
raise exception.ProjectNotFound(project_id=tenant_name)
- return tenant_ref.to_dict()
+ return project_ref.to_dict()
def get_project_users(self, tenant_id):
session = self.get_session()
@@ -484,7 +493,8 @@ class Identity(sql.Base, identity.Driver):
tenant_ref = session.query(Project).filter_by(id=tenant_id).one()
except sql.NotFound:
raise exception.ProjectNotFound(project_id=tenant_id)
-
+ # FIXME(henry-nash) Think about how we detect potential name clash
+ # when we move domains
with session.begin():
old_project_dict = tenant_ref.to_dict()
for k in tenant:
@@ -603,6 +613,14 @@ class Identity(sql.Base, identity.Driver):
raise exception.DomainNotFound(domain_id=domain_id)
return ref.to_dict()
+ def get_domain_by_name(self, domain_name):
+ session = self.get_session()
+ try:
+ ref = session.query(Domain).filter_by(name=domain_name).one()
+ except sql.NotFound:
+ raise exception.DomainNotFound(domain_id=domain_name)
+ return ref.to_dict()
+
@handle_conflicts(type='domain')
def update_domain(self, domain_id, domain):
session = self.get_session()
@@ -674,18 +692,23 @@ class Identity(sql.Base, identity.Driver):
raise exception.UserNotFound(user_id=user_id)
return user_ref.to_dict()
- def _get_user_by_name(self, user_name):
+ def _get_user_by_name(self, user_name, domain_id):
session = self.get_session()
- user_ref = session.query(User).filter_by(name=user_name).first()
- if not user_ref:
+ query = session.query(User)
+ query = query.filter_by(name=user_name)
+ query = query.filter_by(domain_id=domain_id)
+ try:
+ user_ref = query.one()
+ except sql.NotFound:
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))
+ def get_user_by_name(self, user_name, domain_id):
+ return identity.filter_user(
+ self._get_user_by_name(user_name, domain_id))
@handle_conflicts(type='user')
def update_user(self, user_id, user):
@@ -694,6 +717,8 @@ class Identity(sql.Base, identity.Driver):
session = self.get_session()
if 'id' in user and user_id != user['id']:
raise exception.ValidationError('Cannot change user ID')
+ # FIXME(henry-nash) Think about how we detect potential name clash
+ # when we move domains
with session.begin():
user_ref = session.query(User).filter_by(id=user_id).first()
if user_ref is None:
@@ -826,6 +851,8 @@ class Identity(sql.Base, identity.Driver):
@handle_conflicts(type='group')
def update_group(self, group_id, group):
session = self.get_session()
+ # FIXME(henry-nash) Think about how we detect potential name clash
+ # when we move domains
with session.begin():
ref = session.query(Group).filter_by(id=group_id).first()
if ref is None:
diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py
index 1b7180bb..c34a25b6 100644
--- a/keystone/identity/controllers.py
+++ b/keystone/identity/controllers.py
@@ -27,7 +27,7 @@ from keystone import exception
CONF = config.CONF
-DEFAULT_DOMAIN_ID = CONF['identity']['default_domain_id']
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
LOG = logging.getLogger(__name__)
@@ -40,6 +40,8 @@ class Tenant(controller.V2Controller):
self.assert_admin(context)
tenant_refs = self.identity_api.get_projects(context)
+ for tenant_ref in tenant_refs:
+ tenant_ref = self._filter_domain_id(tenant_ref)
params = {
'limit': context['query_string'].get('limit'),
'marker': context['query_string'].get('marker'),
@@ -67,9 +69,9 @@ class Tenant(controller.V2Controller):
context, user_ref['id'])
tenant_refs = []
for tenant_id in tenant_ids:
- tenant_refs.append(self.identity_api.get_project(
- context=context,
- tenant_id=tenant_id))
+ ref = self.identity_api.get_project(
+ context=context, tenant_id=tenant_id)
+ tenant_refs.append(self._filter_domain_id(ref))
params = {
'limit': context['query_string'].get('limit'),
'marker': context['query_string'].get('marker'),
@@ -79,12 +81,14 @@ class Tenant(controller.V2Controller):
def get_project(self, context, tenant_id):
# TODO(termie): this stuff should probably be moved to middleware
self.assert_admin(context)
- return {'tenant': self.identity_api.get_project(context, tenant_id)}
+ ref = self.identity_api.get_project(context, tenant_id)
+ return {'tenant': self._filter_domain_id(ref)}
def get_project_by_name(self, context, tenant_name):
self.assert_admin(context)
- return {'tenant': self.identity_api.get_project_by_name(
- context, tenant_name)}
+ ref = self.identity_api.get_project_by_name(
+ context, tenant_name, DEFAULT_DOMAIN_ID)
+ return {'tenant': self._filter_domain_id(ref)}
# CRUD Extension
def create_project(self, context, tenant):
@@ -97,13 +101,18 @@ class Tenant(controller.V2Controller):
self.assert_admin(context)
tenant_ref['id'] = tenant_ref.get('id', uuid.uuid4().hex)
tenant = self.identity_api.create_project(
- context, tenant_ref['id'], tenant_ref)
- return {'tenant': tenant}
+ context, tenant_ref['id'],
+ self._normalize_domain_id(context, tenant_ref))
+ return {'tenant': self._filter_domain_id(tenant)}
def update_project(self, context, tenant_id, tenant):
self.assert_admin(context)
+ # Remove domain_id if specified - a v2 api caller should not
+ # be specifying that
+ clean_tenant = tenant.copy()
+ clean_tenant.pop('domain_id', None)
tenant_ref = self.identity_api.update_project(
- context, tenant_id, tenant)
+ context, tenant_id, clean_tenant)
return {'tenant': tenant_ref}
def delete_project(self, context, tenant_id):
@@ -113,6 +122,8 @@ class Tenant(controller.V2Controller):
def get_project_users(self, context, tenant_id, **kw):
self.assert_admin(context)
user_refs = self.identity_api.get_project_users(context, tenant_id)
+ for user_ref in user_refs:
+ self._filter_domain_id(user_ref)
return {'users': user_refs}
def _format_project_list(self, tenant_refs, **kwargs):
@@ -153,7 +164,8 @@ class Tenant(controller.V2Controller):
class User(controller.V2Controller):
def get_user(self, context, user_id):
self.assert_admin(context)
- return {'user': self.identity_api.get_user(context, user_id)}
+ ref = self.identity_api.get_user(context, user_id)
+ return {'user': self._filter_domain_id(ref)}
def get_users(self, context):
# NOTE(termie): i can't imagine that this really wants all the data
@@ -163,11 +175,16 @@ class User(controller.V2Controller):
context, context['query_string'].get('name'))
self.assert_admin(context)
- return {'users': self.identity_api.list_users(context)}
+ user_list = self.identity_api.list_users(context)
+ for x in user_list:
+ self._filter_domain_id(x)
+ return {'users': user_list}
def get_user_by_name(self, context, user_name):
self.assert_admin(context)
- return {'user': self.identity_api.get_user_by_name(context, user_name)}
+ ref = self.identity_api.get_user_by_name(
+ context, user_name, DEFAULT_DOMAIN_ID)
+ return {'user': self._filter_domain_id(ref)}
# CRUD extension
def create_user(self, context, user):
@@ -178,18 +195,20 @@ class User(controller.V2Controller):
msg = 'Name field is required and cannot be empty'
raise exception.ValidationError(message=msg)
- tenant_id = user.get('tenantId', None)
- if (tenant_id is not None
- and self.identity_api.get_project(context, tenant_id) is None):
- raise exception.ProjectNotFound(project_id=tenant_id)
+ default_tenant_id = user.get('tenantId', None)
+ if (default_tenant_id is not None
+ and self.identity_api.get_project(context,
+ default_tenant_id) is None):
+ raise exception.ProjectNotFound(project_id=default_tenant_id)
user_id = uuid.uuid4().hex
- user_ref = user.copy()
+ user_ref = self._normalize_domain_id(context, user.copy())
user_ref['id'] = user_id
new_user_ref = self.identity_api.create_user(
context, user_id, user_ref)
- if tenant_id:
- self.identity_api.add_user_to_project(context, tenant_id, user_id)
- return {'user': new_user_ref}
+ if default_tenant_id:
+ self.identity_api.add_user_to_project(context,
+ default_tenant_id, user_id)
+ return {'user': self._filter_domain_id(new_user_ref)}
def update_user(self, context, user_id, user):
# NOTE(termie): this is really more of a patch than a put
@@ -206,7 +225,7 @@ class User(controller.V2Controller):
# backends that can't list tokens for users
LOG.warning('User %s status has changed, but existing tokens '
'remain valid' % user_id)
- return {'user': user_ref}
+ return {'user': self._filter_domain_id(user_ref)}
def delete_user(self, context, user_id):
self.assert_admin(context)
@@ -222,8 +241,9 @@ class User(controller.V2Controller):
"""Update the default tenant."""
self.assert_admin(context)
# ensure that we're a member of that tenant
- tenant_id = user.get('tenantId')
- self.identity_api.add_user_to_project(context, tenant_id, user_id)
+ default_tenant_id = user.get('tenantId')
+ self.identity_api.add_user_to_project(context,
+ default_tenant_id, user_id)
return self.update_user(context, user_id, user)
@@ -403,6 +423,7 @@ class DomainV3(controller.V3Controller):
@controller.protected
def list_domains(self, context):
refs = self.identity_api.list_domains(context)
+ refs = self._filter_by_attribute(context, refs, 'name')
return DomainV3.wrap_collection(context, refs)
@controller.protected
@@ -456,6 +477,17 @@ class DomainV3(controller.V3Controller):
return self.identity_api.delete_domain(context, domain_id)
+ def _get_domain_by_name(self, context, domain_name):
+ """Get the domain via its unique name.
+
+ For use by token authentication - not for hooking to the identity
+ router as a public api.
+
+ """
+ ref = self.identity_api.get_domain_by_name(
+ context, domain_name)
+ return {'domain': ref}
+
class ProjectV3(controller.V3Controller):
collection_name = 'projects'
@@ -464,6 +496,7 @@ class ProjectV3(controller.V3Controller):
@controller.protected
def create_project(self, context, project):
ref = self._assign_unique_id(self._normalize_dict(project))
+ ref = self._normalize_domain_id(context, ref)
ref = self.identity_api.create_project(context, ref['id'], ref)
return ProjectV3.wrap_member(context, ref)
@@ -501,6 +534,7 @@ class UserV3(controller.V3Controller):
@controller.protected
def create_user(self, context, user):
ref = self._assign_unique_id(self._normalize_dict(user))
+ ref = self._normalize_domain_id(context, ref)
ref = self.identity_api.create_user(context, ref['id'], ref)
return UserV3.wrap_member(context, ref)
@@ -560,6 +594,7 @@ class GroupV3(controller.V3Controller):
@controller.protected
def create_group(self, context, group):
ref = self._assign_unique_id(self._normalize_dict(group))
+ ref = self._normalize_domain_id(context, ref)
ref = self.identity_api.create_group(context, ref['id'], ref)
return GroupV3.wrap_member(context, ref)
diff --git a/keystone/identity/core.py b/keystone/identity/core.py
index 8c3c82d6..90587307 100644
--- a/keystone/identity/core.py
+++ b/keystone/identity/core.py
@@ -29,7 +29,9 @@ LOG = logging.getLogger(__name__)
def filter_user(user_ref):
- """Filter out private items in a user dict ('password' and 'tenants')
+ """Filter out private items in a user dict.
+
+ 'password', 'tenants' and 'groups' are never returned.
:returns: user_ref
@@ -81,7 +83,7 @@ class Driver(object):
"""
raise exception.NotImplemented()
- def get_project_by_name(self, tenant_name):
+ def get_project_by_name(self, tenant_name, domain_id):
"""Get a tenant by name.
:returns: tenant_ref
@@ -90,7 +92,7 @@ class Driver(object):
"""
raise exception.NotImplemented()
- def get_user_by_name(self, user_name):
+ def get_user_by_name(self, user_name, domain_id):
"""Get a user by name.
:returns: user_ref
@@ -252,7 +254,16 @@ class Driver(object):
def get_domain(self, domain_id):
"""Get a domain by ID.
- :returns: user_ref
+ :returns: domain_ref
+ :raises: keystone.exception.DomainNotFound
+
+ """
+ raise exception.NotImplemented()
+
+ def get_domain_by_name(self, domain_name):
+ """Get a domain by name.
+
+ :returns: domain_ref
:raises: keystone.exception.DomainNotFound
"""
diff --git a/keystone/token/controllers.py b/keystone/token/controllers.py
index 62134028..5dbfc0c3 100644
--- a/keystone/token/controllers.py
+++ b/keystone/token/controllers.py
@@ -13,6 +13,7 @@ from keystone.token import core
CONF = config.CONF
LOG = logging.getLogger(__name__)
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
class ExternalAuthNotApplicable(Exception):
@@ -64,19 +65,26 @@ class Auth(controller.V2Controller):
if "token" in auth:
# Try to authenticate using a token
- auth_token_data, auth_info = self._authenticate_token(
+ auth_info = self._authenticate_token(
context, auth)
else:
# Try external authentication
try:
- auth_token_data, auth_info = self._authenticate_external(
+ auth_info = self._authenticate_external(
context, auth)
except ExternalAuthNotApplicable:
# Try local authentication
- auth_token_data, auth_info = self._authenticate_local(
+ auth_info = self._authenticate_local(
context, auth)
- user_ref, tenant_ref, metadata_ref = auth_info
+ user_ref, tenant_ref, metadata_ref, expiry = auth_info
+ user_ref = self._filter_domain_id(user_ref)
+ if tenant_ref:
+ tenant_ref = self._filter_domain_id(tenant_ref)
+ auth_token_data = self._get_auth_token_data(user_ref,
+ tenant_ref,
+ metadata_ref,
+ expiry)
# If the user is disabled don't allow them to authenticate
if not user_ref.get('enabled', True):
@@ -202,21 +210,15 @@ class Auth(controller.V2Controller):
tenant_ref = self._get_project_ref(context, user_id, tenant_id)
metadata_ref = self._get_metadata_ref(context, user_id, tenant_id)
- self._append_roles(metadata_ref,
- self._get_group_metadata_ref(
- context, user_id, tenant_id))
+ # TODO (henry-nash) If no tenant was specified, instead check
+ # for a domain and find any related user/group roles
self._append_roles(metadata_ref,
- self._get_domain_metadata_ref(
+ self._get_group_metadata_ref(
context, user_id, tenant_id))
expiry = old_token_ref['expires']
- auth_token_data = self._get_auth_token_data(current_user_ref,
- tenant_ref,
- metadata_ref,
- expiry)
-
- return auth_token_data, (current_user_ref, tenant_ref, metadata_ref)
+ return (current_user_ref, tenant_ref, metadata_ref, expiry)
def _authenticate_local(self, context, auth):
"""Try to authenticate against the identity backend.
@@ -256,7 +258,8 @@ class Auth(controller.V2Controller):
if username:
try:
user_ref = self.identity_api.get_user_by_name(
- context=context, user_name=username)
+ context=context, user_name=username,
+ domain_id=DEFAULT_DOMAIN_ID)
user_id = user_ref['id']
except exception.UserNotFound as e:
raise exception.Unauthorized(e)
@@ -273,21 +276,19 @@ class Auth(controller.V2Controller):
raise exception.Unauthorized(e)
(user_ref, tenant_ref, metadata_ref) = auth_info
- self._append_roles(metadata_ref,
- self._get_group_metadata_ref(
- context, user_id, tenant_id))
+ # By now we will have authorized and if a tenant/project was
+ # specified, we will have obtained its metadata. In this case
+ # we just need to add in any group roles.
+ #
+ # TODO (henry-nash) If no tenant was specified, instead check
+ # for a domain and find any related user/group roles
self._append_roles(metadata_ref,
- self._get_domain_metadata_ref(
+ self._get_group_metadata_ref(
context, user_id, tenant_id))
expiry = core.default_expire_time()
- auth_token_data = self._get_auth_token_data(user_ref,
- tenant_ref,
- metadata_ref,
- expiry)
-
- return auth_token_data, (user_ref, tenant_ref, metadata_ref)
+ return (user_ref, tenant_ref, metadata_ref, expiry)
def _authenticate_external(self, context, auth):
"""Try to authenticate an external user via REMOTE_USER variable.
@@ -300,7 +301,8 @@ class Auth(controller.V2Controller):
username = context['REMOTE_USER']
try:
user_ref = self.identity_api.get_user_by_name(
- context=context, user_name=username)
+ context=context, user_name=username,
+ domain_id=DEFAULT_DOMAIN_ID)
user_id = user_ref['id']
except exception.UserNotFound as e:
raise exception.Unauthorized(e)
@@ -310,21 +312,15 @@ class Auth(controller.V2Controller):
tenant_ref = self._get_project_ref(context, user_id, tenant_id)
metadata_ref = self._get_metadata_ref(context, user_id, tenant_id)
- self._append_roles(metadata_ref,
- self._get_group_metadata_ref(
- context, user_id, tenant_id))
+ # TODO (henry-nash) If no tenant was specified, instead check
+ # for a domain and find any related user/group roles
self._append_roles(metadata_ref,
- self._get_domain_metadata_ref(
+ self._get_group_metadata_ref(
context, user_id, tenant_id))
expiry = core.default_expire_time()
- auth_token_data = self._get_auth_token_data(user_ref,
- tenant_ref,
- metadata_ref,
- expiry)
-
- return auth_token_data, (user_ref, tenant_ref, metadata_ref)
+ return (user_ref, tenant_ref, metadata_ref, expiry)
def _get_auth_token_data(self, user, tenant, metadata, expiry):
return dict(dict(user=user,
@@ -350,12 +346,32 @@ class Auth(controller.V2Controller):
if tenant_name:
try:
tenant_ref = self.identity_api.get_project_by_name(
- context=context, tenant_name=tenant_name)
+ context=context, tenant_name=tenant_name,
+ domain_id=DEFAULT_DOMAIN_ID)
tenant_id = tenant_ref['id']
except exception.ProjectNotFound as e:
raise exception.Unauthorized(e)
return tenant_id
+ def _get_domain_id_from_auth(self, context, auth):
+ """Extract domain information from v3 auth dict.
+
+ Returns a valid domain_id if it exists, or None if not specified.
+ """
+ # FIXME(henry-nash): This is a placeholder that needs to be
+ # only called in the v3 context, and the auth.get calls
+ # converted to the v3 format
+ domain_id = auth.get('domainId', None)
+ domain_name = auth.get('domainName', None)
+ if domain_name:
+ try:
+ domain_ref = self.identity_api._get_domain_by_name(
+ context=context, domain_name=domain_name)
+ domain_id = domain_ref['id']
+ except exception.DomainNotFound as e:
+ raise exception.Unauthorized(e)
+ return domain_id
+
def _get_project_ref(self, context, user_id, tenant_id):
"""Returns the tenant_ref for the user's tenant"""
tenant_ref = None
@@ -375,43 +391,32 @@ class Auth(controller.V2Controller):
return tenant_ref
def _get_metadata_ref(self, context, user_id=None, tenant_id=None,
- group_id=None):
- """Returns the metadata_ref for a user or group in a tenant"""
+ domain_id=None, group_id=None):
+ """Returns metadata_ref for a user or group in a tenant or domain"""
+
metadata_ref = {}
- if tenant_id:
+ if (user_id or group_id) and (tenant_id or domain_id):
try:
- if user_id:
- metadata_ref = self.identity_api.get_metadata(
- context=context,
- user_id=user_id,
- tenant_id=tenant_id)
- elif group_id:
- metadata_ref = self.identity_api.get_metadata(
- context=context,
- group_id=group_id,
- tenant_id=tenant_id)
+ metadata_ref = self.identity_api.get_metadata(
+ context=context, user_id=user_id, tenant_id=tenant_id,
+ domain_id=domain_id, group_id=group_id)
except exception.MetadataNotFound:
- metadata_ref = {}
-
+ pass
return metadata_ref
- def _get_group_metadata_ref(self, context, user_id, tenant_id):
- """Return any metadata for this project due to group grants"""
+ def _get_group_metadata_ref(self, context, user_id,
+ tenant_id=None, domain_id=None):
+ """Return any metadata for this project/domain due to group grants"""
group_refs = self.identity_api.list_groups_for_user(context=context,
user_id=user_id)
metadata_ref = {}
for x in group_refs:
metadata_ref.update(self._get_metadata_ref(context,
group_id=x['id'],
- tenant_id=tenant_id))
+ tenant_id=tenant_id,
+ domain_id=domain_id))
return metadata_ref
- def _get_domain_metadata_ref(self, context, user_id, tenant_id):
- """Return any metadata for this project due to domain grants"""
- # TODO (henry-nashe) Get the domain for this tenant...and then see if
- # any domain grants apply. Bug #1093248
- return {}
-
def _append_roles(self, metadata, additional_metadata):
"""
Update the roles in metadata to be the union of the roles from
diff --git a/tests/default_fixtures.py b/tests/default_fixtures.py
index 4a844a50..e20f9d7a 100644
--- a/tests/default_fixtures.py
+++ b/tests/default_fixtures.py
@@ -17,13 +17,21 @@
# NOTE(dolph): please try to avoid additional fixtures if possible; test suite
# performance may be negatively affected.
+from keystone import config
+
+
+DEFAULT_DOMAIN_ID = config.CONF.identity.default_domain_id
+
+
TENANTS = [
{
'id': 'bar',
'name': 'BAR',
+ 'domain_id': DEFAULT_DOMAIN_ID,
}, {
'id': 'baz',
'name': 'BAZ',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'description': 'description',
'enabled': True,
}
@@ -34,11 +42,13 @@ USERS = [
{
'id': 'foo',
'name': 'FOO',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'foo2',
'tenants': ['bar']
}, {
'id': 'two',
'name': 'TWO',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'two2',
'email': 'two@example.com',
'enabled': True,
@@ -47,6 +57,7 @@ USERS = [
}, {
'id': 'badguy',
'name': 'BadGuy',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'bad',
'email': 'bad@guy.com',
'enabled': False,
diff --git a/tests/test_backend.py b/tests/test_backend.py
index f8194a80..f7556004 100644
--- a/tests/test_backend.py
+++ b/tests/test_backend.py
@@ -21,9 +21,14 @@ import uuid
from keystone.catalog import core
from keystone import exception
from keystone.openstack.common import timeutils
+from keystone import config
from keystone import test
+CONF = config.CONF
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
+
+
class IdentityTests(object):
def test_authenticate_bad_user(self):
self.assertRaises(AssertionError,
@@ -85,6 +90,7 @@ class IdentityTests(object):
user = {
'id': 'no_meta',
'name': 'NO_META',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'no_meta2',
}
self.identity_api.create_user(user['id'], user)
@@ -118,13 +124,15 @@ class IdentityTests(object):
def test_get_project_by_name(self):
tenant_ref = self.identity_api.get_project_by_name(
- tenant_name=self.tenant_bar['name'])
+ tenant_name=self.tenant_bar['name'],
+ domain_id=DEFAULT_DOMAIN_ID)
self.assertDictEqual(tenant_ref, self.tenant_bar)
def test_get_project_by_name_404(self):
self.assertRaises(exception.ProjectNotFound,
- self.identity_api.get_project,
- tenant_id=uuid.uuid4().hex)
+ self.identity_api.get_project_by_name,
+ tenant_name=uuid.uuid4().hex,
+ domain_id=DEFAULT_DOMAIN_ID)
def test_get_project_users_404(self):
self.assertRaises(exception.ProjectNotFound,
@@ -146,7 +154,8 @@ class IdentityTests(object):
def test_get_user_by_name(self):
user_ref = self.identity_api.get_user_by_name(
- user_name=self.user_foo['name'])
+ user_name=self.user_foo['name'],
+ domain_id=DEFAULT_DOMAIN_ID)
# NOTE(termie): the password field is left in user_foo to make
# it easier to authenticate in tests, but should
# not be returned by the api
@@ -156,7 +165,8 @@ class IdentityTests(object):
def test_get_user_by_name_404(self):
self.assertRaises(exception.UserNotFound,
self.identity_api.get_user_by_name,
- user_name=uuid.uuid4().hex)
+ user_name=uuid.uuid4().hex,
+ domain_id=DEFAULT_DOMAIN_ID)
def test_get_metadata(self):
metadata_ref = self.identity_api.get_metadata(
@@ -217,6 +227,7 @@ class IdentityTests(object):
def test_create_duplicate_user_id_fails(self):
user = {'id': 'fake1',
'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'fakepass',
'tenants': ['bar']}
self.identity_api.create_user('fake1', user)
@@ -229,6 +240,7 @@ class IdentityTests(object):
def test_create_duplicate_user_name_fails(self):
user = {'id': 'fake1',
'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'fakepass',
'tenants': ['bar']}
self.identity_api.create_user('fake1', user)
@@ -241,10 +253,12 @@ class IdentityTests(object):
def test_rename_duplicate_user_name_fails(self):
user1 = {'id': 'fake1',
'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'fakepass',
'tenants': ['bar']}
user2 = {'id': 'fake2',
'name': 'fake2',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'fakepass',
'tenants': ['bar']}
self.identity_api.create_user('fake1', user1)
@@ -258,6 +272,7 @@ class IdentityTests(object):
def test_update_user_id_fails(self):
user = {'id': 'fake1',
'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'fakepass',
'tenants': ['bar']}
self.identity_api.create_user('fake1', user)
@@ -273,7 +288,8 @@ class IdentityTests(object):
'fake2')
def test_create_duplicate_project_id_fails(self):
- tenant = {'id': 'fake1', 'name': 'fake1'}
+ tenant = {'id': 'fake1', 'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_project('fake1', tenant)
tenant['name'] = 'fake2'
self.assertRaises(exception.Conflict,
@@ -282,7 +298,8 @@ class IdentityTests(object):
tenant)
def test_create_duplicate_project_name_fails(self):
- tenant = {'id': 'fake1', 'name': 'fake'}
+ tenant = {'id': 'fake1', 'name': 'fake',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_project('fake1', tenant)
tenant['id'] = 'fake2'
self.assertRaises(exception.Conflict,
@@ -291,8 +308,10 @@ class IdentityTests(object):
tenant)
def test_rename_duplicate_project_name_fails(self):
- tenant1 = {'id': 'fake1', 'name': 'fake1'}
- tenant2 = {'id': 'fake2', 'name': 'fake2'}
+ tenant1 = {'id': 'fake1', 'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID}
+ tenant2 = {'id': 'fake2', 'name': 'fake2',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_project('fake1', tenant1)
self.identity_api.create_project('fake2', tenant2)
tenant2['name'] = 'fake1'
@@ -302,7 +321,8 @@ class IdentityTests(object):
tenant2)
def test_update_project_id_does_nothing(self):
- tenant = {'id': 'fake1', 'name': 'fake1'}
+ tenant = {'id': 'fake1', 'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_project('fake1', tenant)
tenant['id'] = 'fake2'
self.identity_api.update_project('fake1', tenant)
@@ -465,11 +485,14 @@ class IdentityTests(object):
role_id='member')
def test_get_and_remove_role_grant_by_group_and_project(self):
+ new_domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
+ self.identity_api.create_domain(new_domain['id'], new_domain)
new_group = {'id': uuid.uuid4().hex, 'domain_id': uuid.uuid4().hex,
'name': uuid.uuid4().hex}
self.identity_api.create_group(new_group['id'], new_group)
new_user = {'id': uuid.uuid4().hex, 'name': 'new_user',
- 'password': 'secret', 'enabled': True}
+ 'password': 'secret', 'enabled': True,
+ 'domain_id': new_domain['id']}
self.identity_api.create_user(new_user['id'], new_user)
self.identity_api.add_user_to_group(new_user['id'],
new_group['id'])
@@ -505,17 +528,82 @@ class IdentityTests(object):
'name': uuid.uuid4().hex}
self.identity_api.create_group(new_group['id'], new_group)
new_user = {'id': uuid.uuid4().hex, 'name': 'new_user',
- 'password': 'secret', 'enabled': True}
+ 'password': uuid.uuid4().hex, 'enabled': True,
+ 'domain_id': new_domain['id']}
self.identity_api.create_user(new_user['id'], new_user)
self.identity_api.add_user_to_group(new_user['id'],
new_group['id'])
+
roles_ref = self.identity_api.list_grants(
group_id=new_group['id'],
domain_id=new_domain['id'])
self.assertEquals(len(roles_ref), 0)
+
self.identity_api.create_grant(group_id=new_group['id'],
domain_id=new_domain['id'],
role_id='member')
+
+ roles_ref = self.identity_api.list_grants(
+ group_id=new_group['id'],
+ domain_id=new_domain['id'])
+ self.assertDictEqual(roles_ref[0], self.role_member)
+
+ self.identity_api.delete_grant(group_id=new_group['id'],
+ domain_id=new_domain['id'],
+ role_id='member')
+ roles_ref = self.identity_api.list_grants(
+ group_id=new_group['id'],
+ domain_id=new_domain['id'])
+ self.assertEquals(len(roles_ref), 0)
+ self.assertRaises(exception.NotFound,
+ self.identity_api.delete_grant,
+ group_id=new_group['id'],
+ domain_id=new_domain['id'],
+ role_id='member')
+
+ def test_get_and_remove_correct_role_grant_from_a_mix(self):
+ new_domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
+ self.identity_api.create_domain(new_domain['id'], new_domain)
+ new_project = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
+ 'domain_id': new_domain['id']}
+ self.identity_api.create_project(new_project['id'], new_project)
+ new_group = {'id': uuid.uuid4().hex, 'domain_id': new_domain['id'],
+ 'name': uuid.uuid4().hex}
+ self.identity_api.create_group(new_group['id'], new_group)
+ new_group2 = {'id': uuid.uuid4().hex, 'domain_id': new_domain['id'],
+ 'name': uuid.uuid4().hex}
+ self.identity_api.create_group(new_group2['id'], new_group2)
+ new_user = {'id': uuid.uuid4().hex, 'name': 'new_user',
+ 'password': uuid.uuid4().hex, 'enabled': True,
+ 'domain_id': new_domain['id']}
+ self.identity_api.create_user(new_user['id'], new_user)
+ new_user2 = {'id': uuid.uuid4().hex, 'name': 'new_user2',
+ 'password': uuid.uuid4().hex, 'enabled': True,
+ 'domain_id': new_domain['id']}
+ self.identity_api.create_user(new_user2['id'], new_user2)
+ self.identity_api.add_user_to_group(new_user['id'],
+ new_group['id'])
+ # First check we have no grants
+ roles_ref = self.identity_api.list_grants(
+ group_id=new_group['id'],
+ domain_id=new_domain['id'])
+ self.assertEquals(len(roles_ref), 0)
+ # Now add the grant we are going to test for, and some others as
+ # well just to make sure we get back the right one
+ self.identity_api.create_grant(group_id=new_group['id'],
+ domain_id=new_domain['id'],
+ role_id='member')
+
+ self.identity_api.create_grant(group_id=new_group2['id'],
+ domain_id=new_domain['id'],
+ role_id='keystone_admin')
+ self.identity_api.create_grant(user_id=new_user2['id'],
+ domain_id=new_domain['id'],
+ role_id='keystone_admin')
+ self.identity_api.create_grant(group_id=new_group['id'],
+ project_id=new_project['id'],
+ role_id='keystone_admin')
+
roles_ref = self.identity_api.list_grants(
group_id=new_group['id'],
domain_id=new_domain['id'])
@@ -538,7 +626,8 @@ class IdentityTests(object):
new_domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
self.identity_api.create_domain(new_domain['id'], new_domain)
new_user = {'id': uuid.uuid4().hex, 'name': 'new_user',
- 'password': 'secret', 'enabled': True}
+ 'password': 'secret', 'enabled': True,
+ 'domain_id': new_domain['id']}
self.identity_api.create_user(new_user['id'], new_user)
roles_ref = self.identity_api.list_grants(
user_id=new_user['id'],
@@ -657,6 +746,7 @@ class IdentityTests(object):
def test_delete_user_with_project_association(self):
user = {'id': uuid.uuid4().hex,
'name': uuid.uuid4().hex,
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': uuid.uuid4().hex}
self.identity_api.create_user(user['id'], user)
self.identity_api.add_user_to_project(self.tenant_bar['id'],
@@ -669,6 +759,7 @@ class IdentityTests(object):
def test_delete_user_with_project_roles(self):
user = {'id': uuid.uuid4().hex,
'name': uuid.uuid4().hex,
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': uuid.uuid4().hex}
self.identity_api.create_user(user['id'], user)
self.identity_api.add_role_to_user_and_project(
@@ -691,33 +782,38 @@ class IdentityTests(object):
uuid.uuid4().hex)
def test_create_project_long_name_fails(self):
- tenant = {'id': 'fake1', 'name': 'a' * 65}
+ tenant = {'id': 'fake1', 'name': 'a' * 65,
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.assertRaises(exception.ValidationError,
self.identity_api.create_project,
tenant['id'],
tenant)
def test_create_project_blank_name_fails(self):
- tenant = {'id': 'fake1', 'name': ''}
+ tenant = {'id': 'fake1', 'name': '',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.assertRaises(exception.ValidationError,
self.identity_api.create_project,
tenant['id'],
tenant)
def test_create_project_invalid_name_fails(self):
- tenant = {'id': 'fake1', 'name': None}
+ tenant = {'id': 'fake1', 'name': None,
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.assertRaises(exception.ValidationError,
self.identity_api.create_project,
tenant['id'],
tenant)
- tenant = {'id': 'fake1', 'name': 123}
+ tenant = {'id': 'fake1', 'name': 123,
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.assertRaises(exception.ValidationError,
self.identity_api.create_project,
tenant['id'],
tenant)
def test_update_project_blank_name_fails(self):
- tenant = {'id': 'fake1', 'name': 'fake1'}
+ tenant = {'id': 'fake1', 'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_project('fake1', tenant)
tenant['name'] = ''
self.assertRaises(exception.ValidationError,
@@ -726,7 +822,8 @@ class IdentityTests(object):
tenant)
def test_update_project_long_name_fails(self):
- tenant = {'id': 'fake1', 'name': 'fake1'}
+ tenant = {'id': 'fake1', 'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_project('fake1', tenant)
tenant['name'] = 'a' * 65
self.assertRaises(exception.ValidationError,
@@ -735,7 +832,8 @@ class IdentityTests(object):
tenant)
def test_update_project_invalid_name_fails(self):
- tenant = {'id': 'fake1', 'name': 'fake1'}
+ tenant = {'id': 'fake1', 'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_project('fake1', tenant)
tenant['name'] = None
self.assertRaises(exception.ValidationError,
@@ -750,34 +848,39 @@ class IdentityTests(object):
tenant)
def test_create_user_long_name_fails(self):
- user = {'id': 'fake1', 'name': 'a' * 65}
+ user = {'id': 'fake1', 'name': 'a' * 65,
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.assertRaises(exception.ValidationError,
self.identity_api.create_user,
'fake1',
user)
def test_create_user_blank_name_fails(self):
- user = {'id': 'fake1', 'name': ''}
+ user = {'id': 'fake1', 'name': '',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.assertRaises(exception.ValidationError,
self.identity_api.create_user,
'fake1',
user)
def test_create_user_invalid_name_fails(self):
- user = {'id': 'fake1', 'name': None}
+ user = {'id': 'fake1', 'name': None,
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.assertRaises(exception.ValidationError,
self.identity_api.create_user,
'fake1',
user)
- user = {'id': 'fake1', 'name': 123}
+ user = {'id': 'fake1', 'name': 123,
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.assertRaises(exception.ValidationError,
self.identity_api.create_user,
'fake1',
user)
def test_update_user_long_name_fails(self):
- user = {'id': 'fake1', 'name': 'fake1'}
+ user = {'id': 'fake1', 'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_user('fake1', user)
user['name'] = 'a' * 65
self.assertRaises(exception.ValidationError,
@@ -786,7 +889,8 @@ class IdentityTests(object):
user)
def test_update_user_blank_name_fails(self):
- user = {'id': 'fake1', 'name': 'fake1'}
+ user = {'id': 'fake1', 'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_user('fake1', user)
user['name'] = ''
self.assertRaises(exception.ValidationError,
@@ -795,7 +899,8 @@ class IdentityTests(object):
user)
def test_update_user_invalid_name_fails(self):
- user = {'id': 'fake1', 'name': 'fake1'}
+ user = {'id': 'fake1', 'name': 'fake1',
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_user('fake1', user)
user['name'] = None
@@ -827,8 +932,9 @@ class IdentityTests(object):
if x['id'] == test_project['id'])
def test_delete_project_with_role_assignments(self):
- tenant = {'id': 'fake1', 'name': 'fake1'}
- self.identity_api.create_project('fake1', tenant)
+ tenant = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
+ 'domain_id': DEFAULT_DOMAIN_ID}
+ self.identity_api.create_project(tenant['id'], tenant)
self.identity_api.add_role_to_user_and_project(
self.user_foo['id'], tenant['id'], 'member')
self.identity_api.delete_project(tenant['id'])
@@ -852,20 +958,23 @@ class IdentityTests(object):
self.assertIn(alt_role['id'], roles_ref)
def test_create_project_doesnt_modify_passed_in_dict(self):
- new_project = {'id': 'tenant_id', 'name': 'new_project'}
+ new_project = {'id': 'tenant_id', 'name': uuid.uuid4().hex,
+ 'domain_id': DEFAULT_DOMAIN_ID}
original_project = new_project.copy()
self.identity_api.create_project('tenant_id', new_project)
self.assertDictEqual(original_project, new_project)
def test_create_user_doesnt_modify_passed_in_dict(self):
- new_user = {'id': 'user_id', 'name': 'new_user',
- 'password': 'secret', 'enabled': True}
+ new_user = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
+ 'password': uuid.uuid4().hex, 'enabled': True,
+ 'domain_id': DEFAULT_DOMAIN_ID}
original_user = new_user.copy()
self.identity_api.create_user('user_id', new_user)
self.assertDictEqual(original_user, new_user)
def test_update_user_enable(self):
- user = {'id': 'fake1', 'name': 'fake1', 'enabled': True}
+ user = {'id': 'fake1', 'name': 'fake1', 'enabled': True,
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_user('fake1', user)
user_ref = self.identity_api.get_user('fake1')
self.assertEqual(user_ref['enabled'], True)
@@ -881,7 +990,8 @@ class IdentityTests(object):
self.assertEqual(user_ref['enabled'], user['enabled'])
def test_update_project_enable(self):
- tenant = {'id': 'fake1', 'name': 'fake1', 'enabled': True}
+ tenant = {'id': 'fake1', 'name': 'fake1', 'enabled': True,
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_project('fake1', tenant)
tenant_ref = self.identity_api.get_project('fake1')
self.assertEqual(tenant_ref['enabled'], True)
@@ -897,11 +1007,14 @@ class IdentityTests(object):
self.assertEqual(tenant_ref['enabled'], tenant['enabled'])
def test_add_user_to_group(self):
+ domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
+ self.identity_api.create_domain(domain['id'], domain)
new_group = {'id': uuid.uuid4().hex, 'domain_id': uuid.uuid4().hex,
'name': uuid.uuid4().hex}
self.identity_api.create_group(new_group['id'], new_group)
new_user = {'id': uuid.uuid4().hex, 'name': 'new_user',
- 'password': 'secret', 'enabled': True}
+ 'password': uuid.uuid4().hex, 'enabled': True,
+ 'domain_id': domain['id']}
self.identity_api.create_user(new_user['id'], new_user)
self.identity_api.add_user_to_group(new_user['id'],
new_group['id'])
@@ -914,8 +1027,11 @@ class IdentityTests(object):
self.assertTrue(found)
def test_add_user_to_group_404(self):
+ domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
+ self.identity_api.create_domain(domain['id'], domain)
new_user = {'id': uuid.uuid4().hex, 'name': 'new_user',
- 'password': 'secret', 'enabled': True}
+ 'password': uuid.uuid4().hex, 'enabled': True,
+ 'domain_id': domain['id']}
self.identity_api.create_user(new_user['id'], new_user)
self.assertRaises(exception.GroupNotFound,
self.identity_api.add_user_to_group,
@@ -931,11 +1047,14 @@ class IdentityTests(object):
new_group['id'])
def test_check_user_in_group(self):
+ domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
+ self.identity_api.create_domain(domain['id'], domain)
new_group = {'id': uuid.uuid4().hex, 'domain_id': uuid.uuid4().hex,
'name': uuid.uuid4().hex}
self.identity_api.create_group(new_group['id'], new_group)
new_user = {'id': uuid.uuid4().hex, 'name': 'new_user',
- 'password': 'secret', 'enabled': True}
+ 'password': uuid.uuid4().hex, 'enabled': True,
+ 'domain_id': domain['id']}
self.identity_api.create_user(new_user['id'], new_user)
self.identity_api.add_user_to_group(new_user['id'],
new_group['id'])
@@ -951,11 +1070,14 @@ class IdentityTests(object):
new_group['id'])
def test_list_users_in_group(self):
+ domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
+ self.identity_api.create_domain(domain['id'], domain)
new_group = {'id': uuid.uuid4().hex, 'domain_id': uuid.uuid4().hex,
'name': uuid.uuid4().hex}
self.identity_api.create_group(new_group['id'], new_group)
new_user = {'id': uuid.uuid4().hex, 'name': 'new_user',
- 'password': 'secret', 'enabled': True}
+ 'password': uuid.uuid4().hex, 'enabled': True,
+ 'domain_id': domain['id']}
self.identity_api.create_user(new_user['id'], new_user)
self.identity_api.add_user_to_group(new_user['id'],
new_group['id'])
@@ -967,11 +1089,14 @@ class IdentityTests(object):
self.assertTrue(found)
def test_remove_user_from_group(self):
+ domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
+ self.identity_api.create_domain(domain['id'], domain)
new_group = {'id': uuid.uuid4().hex, 'domain_id': uuid.uuid4().hex,
'name': uuid.uuid4().hex}
self.identity_api.create_group(new_group['id'], new_group)
new_user = {'id': uuid.uuid4().hex, 'name': 'new_user',
- 'password': 'secret', 'enabled': True}
+ 'password': uuid.uuid4().hex, 'enabled': True,
+ 'domain_id': domain['id']}
self.identity_api.create_user(new_user['id'], new_user)
self.identity_api.add_user_to_group(new_user['id'],
new_group['id'])
@@ -983,8 +1108,11 @@ class IdentityTests(object):
self.assertFalse(x['id'] == new_group['id'])
def test_remove_user_from_group_404(self):
+ domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
+ self.identity_api.create_domain(domain['id'], domain)
new_user = {'id': uuid.uuid4().hex, 'name': 'new_user',
- 'password': 'secret', 'enabled': True}
+ 'password': uuid.uuid4().hex, 'enabled': True,
+ 'domain_id': domain['id']}
self.identity_api.create_user(new_user['id'], new_user)
new_group = {'id': uuid.uuid4().hex, 'domain_id': uuid.uuid4().hex,
'name': uuid.uuid4().hex}
@@ -1009,20 +1137,52 @@ class IdentityTests(object):
'name': uuid.uuid4().hex}
self.identity_api.create_group(group['id'], group)
group_ref = self.identity_api.get_group(group['id'])
- group_ref_dict = dict((x, group_ref[x]) for x in group_ref)
- self.assertDictEqual(group_ref_dict, group)
+ self.assertDictEqual(group_ref, group)
group['name'] = uuid.uuid4().hex
self.identity_api.update_group(group['id'], group)
group_ref = self.identity_api.get_group(group['id'])
- group_ref_dict = dict((x, group_ref[x]) for x in group_ref)
- self.assertDictEqual(group_ref_dict, group)
+ self.assertDictEqual(group_ref, group)
self.identity_api.delete_group(group['id'])
self.assertRaises(exception.GroupNotFound,
self.identity_api.get_group,
group['id'])
+ def test_project_crud(self):
+ project = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
+ 'domain_id': uuid.uuid4().hex}
+ self.identity_api.create_project(project['id'], project)
+ project_ref = self.identity_api.get_project(project['id'])
+ self.assertDictEqual(project_ref, project)
+
+ project['name'] = uuid.uuid4().hex
+ self.identity_api.update_project(project['id'], project)
+ project_ref = self.identity_api.get_project(project['id'])
+ self.assertDictEqual(project_ref, project)
+
+ self.identity_api.delete_project(project['id'])
+ self.assertRaises(exception.ProjectNotFound,
+ self.identity_api.get_project,
+ project['id'])
+
+ def test_domain_crud(self):
+ domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
+ 'enabled': True}
+ self.identity_api.create_domain(domain['id'], domain)
+ domain_ref = self.identity_api.get_domain(domain['id'])
+ self.assertDictEqual(domain_ref, domain)
+
+ domain['name'] = uuid.uuid4().hex
+ self.identity_api.update_domain(domain['id'], domain)
+ domain_ref = self.identity_api.get_domain(domain['id'])
+ self.assertDictEqual(domain_ref, domain)
+
+ self.identity_api.delete_domain(domain['id'])
+ self.assertRaises(exception.DomainNotFound,
+ self.identity_api.get_domain,
+ domain['id'])
+
class TokenTests(object):
def test_token_crud(self):
diff --git a/tests/test_backend_ldap.py b/tests/test_backend_ldap.py
index 3b6d1e13..cc92b02f 100644
--- a/tests/test_backend_ldap.py
+++ b/tests/test_backend_ldap.py
@@ -15,6 +15,7 @@
# under the License.
import uuid
+import nose.exc
from keystone.common.ldap import fakeldap
from keystone import config
@@ -413,48 +414,57 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
user_api.get_connection(user=None, password=None)
# TODO (henry-nash) These need to be removed when the full LDAP implementation
-# is submitted - see BugL #1092187
+# is submitted - see Bugs 1092187, 1101287, 1101276, 1101289
def test_group_crud(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1092187')
def test_add_user_to_group(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1092187')
def test_add_user_to_group_404(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1092187')
def test_check_user_in_group(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1092187')
def test_check_user_not_in_group(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1092187')
def test_list_users_in_group(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1092187')
def test_remove_user_from_group(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1092187')
def test_remove_user_from_group_404(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1092187')
def test_get_role_grant_by_user_and_project(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1101287')
def test_get_role_grants_for_user_and_project_404(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1101287')
def test_add_role_grant_to_user_and_project_404(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1101287')
def test_remove_role_grant_from_user_and_project(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1101287')
def test_get_and_remove_role_grant_by_group_and_project(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1101287')
def test_get_and_remove_role_grant_by_group_and_domain(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1101287')
def test_get_and_remove_role_grant_by_user_and_domain(self):
- pass
+ raise nose.exc.SkipTest('Blocked by bug 1101287')
+
+ def test_get_and_remove_correct_role_grant_from_a_mix(self):
+ raise nose.exc.SkipTest('Blocked by bug 1101287')
+
+ def test_domain_crud(self):
+ raise nose.exc.SkipTest('Blocked by bug 1101276')
+
+ def test_project_crud(self):
+ raise nose.exc.SkipTest('Blocked by bug 1101289')
diff --git a/tests/test_backend_pam.py b/tests/test_backend_pam.py
index a5384d43..a8f4e575 100644
--- a/tests/test_backend_pam.py
+++ b/tests/test_backend_pam.py
@@ -22,6 +22,7 @@ from keystone import test
CONF = config.CONF
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
class PamIdentity(test.TestCase):
@@ -41,7 +42,8 @@ class PamIdentity(test.TestCase):
def test_get_project_by_name(self):
tenant_in_name = self.tenant_in['name']
- tenant_out = self.identity_api.get_project_by_name(tenant_in_name)
+ tenant_out = self.identity_api.get_project_by_name(
+ tenant_in_name, DEFAULT_DOMAIN_ID)
self.assertDictEqual(self.tenant_in, tenant_out)
def test_get_user(self):
@@ -49,7 +51,8 @@ class PamIdentity(test.TestCase):
self.assertDictEqual(self.user_in, user_out)
def test_get_user_by_name(self):
- user_out = self.identity_api.get_user_by_name(self.user_in['name'])
+ user_out = self.identity_api.get_user_by_name(
+ self.user_in['name'], DEFAULT_DOMAIN_ID)
self.assertDictEqual(self.user_in, user_out)
def test_get_metadata_for_non_root(self):
diff --git a/tests/test_backend_sql.py b/tests/test_backend_sql.py
index 080668d0..a4be85e3 100644
--- a/tests/test_backend_sql.py
+++ b/tests/test_backend_sql.py
@@ -28,8 +28,8 @@ from keystone import token
import default_fixtures
import test_backend
-
CONF = config.CONF
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
class SqlTests(test.TestCase):
@@ -65,6 +65,7 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
def test_delete_user_with_project_association(self):
user = {'id': uuid.uuid4().hex,
'name': uuid.uuid4().hex,
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': uuid.uuid4().hex}
self.identity_api.create_user(user['id'], user)
self.identity_api.add_user_to_project(self.tenant_bar['id'],
@@ -77,6 +78,7 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
def test_create_null_user_name(self):
user = {'id': uuid.uuid4().hex,
'name': None,
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': uuid.uuid4().hex}
self.assertRaises(exception.ValidationError,
self.identity_api.create_user,
@@ -87,11 +89,13 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
user['id'])
self.assertRaises(exception.UserNotFound,
self.identity_api.get_user_by_name,
- user['name'])
+ user['name'],
+ DEFAULT_DOMAIN_ID)
def test_create_null_project_name(self):
tenant = {'id': uuid.uuid4().hex,
- 'name': None}
+ 'name': None,
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.assertRaises(exception.ValidationError,
self.identity_api.create_project,
tenant['id'],
@@ -101,7 +105,8 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
tenant['id'])
self.assertRaises(exception.ProjectNotFound,
self.identity_api.get_project_by_name,
- tenant['name'])
+ tenant['name'],
+ DEFAULT_DOMAIN_ID)
def test_create_null_role_name(self):
role = {'id': uuid.uuid4().hex,
@@ -117,6 +122,7 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
def test_delete_project_with_user_association(self):
user = {'id': 'fake',
'name': 'fakeuser',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'passwd'}
self.identity_api.create_user('fake', user)
self.identity_api.add_user_to_project(self.tenant_bar['id'],
@@ -128,6 +134,7 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
def test_delete_user_with_metadata(self):
user = {'id': 'fake',
'name': 'fakeuser',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'passwd'}
self.identity_api.create_user('fake', user)
self.identity_api.create_metadata(user['id'],
@@ -142,6 +149,7 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
def test_delete_project_with_metadata(self):
user = {'id': 'fake',
'name': 'fakeuser',
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': 'passwd'}
self.identity_api.create_user('fake', user)
self.identity_api.create_metadata(user['id'],
@@ -169,6 +177,7 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
tenant = {
'id': tenant_id,
'name': uuid.uuid4().hex,
+ 'domain_id': DEFAULT_DOMAIN_ID,
arbitrary_key: arbitrary_value}
ref = self.identity_api.create_project(tenant_id, tenant)
self.assertEqual(arbitrary_value, ref[arbitrary_key])
@@ -195,6 +204,7 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
user = {
'id': user_id,
'name': uuid.uuid4().hex,
+ 'domain_id': DEFAULT_DOMAIN_ID,
'password': uuid.uuid4().hex,
arbitrary_key: arbitrary_value}
ref = self.identity_api.create_user(user_id, user)
diff --git a/tests/test_keystoneclient.py b/tests/test_keystoneclient.py
index 213e3ddc..09b7a2f7 100644
--- a/tests/test_keystoneclient.py
+++ b/tests/test_keystoneclient.py
@@ -22,11 +22,13 @@ import nose.exc
from keystone.openstack.common import jsonutils
from keystone.openstack.common import timeutils
+from keystone import config
from keystone import test
-
import default_fixtures
+CONF = config.CONF
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
OPENSTACK_REPO = 'https://review.openstack.org/p/openstack'
KEYSTONECLIENT_REPO = '%s/python-keystoneclient.git' % OPENSTACK_REPO
@@ -862,7 +864,8 @@ class KcMasterTestCase(CompatTestCase, KeystoneClientTests):
# Add two arbitrary tenants to user for testing purposes
for i in range(2):
tenant_id = uuid.uuid4().hex
- tenant = {'name': 'tenant-%s' % tenant_id, 'id': tenant_id}
+ tenant = {'name': 'tenant-%s' % tenant_id, 'id': tenant_id,
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_project(tenant_id, tenant)
self.identity_api.add_user_to_project(tenant_id,
self.user_foo['id'])
@@ -888,7 +891,8 @@ class KcMasterTestCase(CompatTestCase, KeystoneClientTests):
# Add two arbitrary tenants to user for testing purposes
for i in range(2):
tenant_id = uuid.uuid4().hex
- tenant = {'name': 'tenant-%s' % tenant_id, 'id': tenant_id}
+ tenant = {'name': 'tenant-%s' % tenant_id, 'id': tenant_id,
+ 'domain_id': DEFAULT_DOMAIN_ID}
self.identity_api.create_project(tenant_id, tenant)
self.identity_api.add_user_to_project(tenant_id,
self.user_foo['id'])
diff --git a/tests/test_migrate_nova_auth.py b/tests/test_migrate_nova_auth.py
index 56e4c5ba..3a257dff 100644
--- a/tests/test_migrate_nova_auth.py
+++ b/tests/test_migrate_nova_auth.py
@@ -25,6 +25,7 @@ from keystone import test
CONF = config.CONF
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
FIXTURE = {
@@ -92,11 +93,13 @@ class MigrateNovaAuth(test.TestCase):
users = {}
for user in ['user1', 'user2', 'user3', 'user4']:
- users[user] = self.identity_api.get_user_by_name(user)
+ users[user] = self.identity_api.get_user_by_name(
+ user, DEFAULT_DOMAIN_ID)
tenants = {}
for tenant in ['proj1', 'proj2', 'proj4']:
- tenants[tenant] = self.identity_api.get_project_by_name(tenant)
+ tenants[tenant] = self.identity_api.get_project_by_name(
+ tenant, DEFAULT_DOMAIN_ID)
membership_map = {
'user1': ['proj1'],
diff --git a/tests/test_sql_upgrade.py b/tests/test_sql_upgrade.py
index f0b5fa5d..d204492c 100644
--- a/tests/test_sql_upgrade.py
+++ b/tests/test_sql_upgrade.py
@@ -35,12 +35,14 @@ import sqlalchemy
from keystone.common import sql
from keystone.common.sql import migration
from keystone import config
+from keystone import exception
from keystone import test
import default_fixtures
CONF = config.CONF
+DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
class SqlUpgradeTests(test.TestCase):
@@ -129,8 +131,8 @@ class SqlUpgradeTests(test.TestCase):
self.populate_tenant_table()
self.upgrade(10)
self.assertTableColumns("user",
- ["id", "name", "extra", "password",
- "enabled"])
+ ["id", "name", "extra",
+ "password", "enabled"])
self.assertTableColumns("tenant",
["id", "name", "extra", "description",
"enabled"])
@@ -152,17 +154,33 @@ class SqlUpgradeTests(test.TestCase):
a_tenant = session.query(tenant_table).filter("id='baz'").one()
self.assertEqual(a_tenant.description, 'description')
session.commit()
+ session.close()
def test_downgrade_10_to_8(self):
- self.upgrade(8)
- self.populate_user_table()
- self.populate_tenant_table()
self.upgrade(10)
+ self.populate_user_table(with_pass_enab=True)
+ self.populate_tenant_table(with_desc_enab=True)
self.downgrade(8)
+ self.assertTableColumns('user',
+ ['id', 'name', 'extra'])
+ self.assertTableColumns('tenant',
+ ['id', 'name', 'extra'])
+ session = self.Session()
+ user_table = sqlalchemy.Table("user",
+ self.metadata,
+ autoload=True)
+ a_user = session.query(user_table).filter("id='badguy'").one()
+ self.assertEqual(a_user.name, default_fixtures.USERS[2]['name'])
+ tenant_table = sqlalchemy.Table("tenant",
+ self.metadata,
+ autoload=True)
+ a_tenant = session.query(tenant_table).filter("id='baz'").one()
+ self.assertEqual(a_tenant.name, default_fixtures.TENANTS[1]['name'])
+ session.commit()
+ session.close()
def test_upgrade_10_to_13(self):
self.upgrade(10)
-
service_extra = {
'name': uuid.uuid4().hex,
}
@@ -187,9 +205,9 @@ class SqlUpgradeTests(test.TestCase):
self.insert_dict(session, 'service', service)
self.insert_dict(session, 'endpoint', endpoint)
session.commit()
+ session.close()
self.upgrade(13)
-
self.assertTableColumns(
'service',
['id', 'type', 'extra'])
@@ -215,6 +233,8 @@ class SqlUpgradeTests(test.TestCase):
self.assertEqual(ref.service_id, endpoint['service_id'])
self.assertEqual(ref.url, endpoint_extra['%surl' % interface])
self.assertEqual(ref.extra, '{}')
+ session.commit()
+ session.close()
def assertTenantTables(self):
self.assertTableExists('tenant')
@@ -235,6 +255,12 @@ class SqlUpgradeTests(test.TestCase):
self.assertProjectTables()
def test_downgrade_project_to_tenant(self):
+ # TODO(henry-nash): Debug why we need to re-load the tenant
+ # or user_tenant_membership ahead of upgrading to project
+ # in order for the assertProjectTables to work on sqlite
+ # (MySQL is fine without it)
+ self.upgrade(14)
+ self.assertTenantTables()
self.upgrade(15)
self.assertProjectTables()
self.downgrade(14)
@@ -248,6 +274,59 @@ class SqlUpgradeTests(test.TestCase):
self.assertTableExists('group_domain_metadata')
self.assertTableExists('user_group_membership')
+ def test_upgrade_14_to_16(self):
+ self.upgrade(14)
+ self.populate_user_table(with_pass_enab=True)
+ self.populate_tenant_table(with_desc_enab=True)
+ self.upgrade(16)
+ self.assertTableColumns("user",
+ ["id", "name", "extra",
+ "password", "enabled", "domain_id"])
+ session = self.Session()
+ user_table = sqlalchemy.Table("user",
+ self.metadata,
+ autoload=True)
+ a_user = session.query(user_table).filter("id='foo'").one()
+ self.assertTrue(a_user.enabled)
+ self.assertEqual(a_user.domain_id, DEFAULT_DOMAIN_ID)
+ a_user = session.query(user_table).filter("id='badguy'").one()
+ self.assertEqual(a_user.name, default_fixtures.USERS[2]['name'])
+ self.assertEqual(a_user.domain_id, DEFAULT_DOMAIN_ID)
+ project_table = sqlalchemy.Table("project",
+ self.metadata,
+ autoload=True)
+ a_project = session.query(project_table).filter("id='baz'").one()
+ self.assertEqual(a_project.description,
+ default_fixtures.TENANTS[1]['description'])
+ self.assertEqual(a_project.domain_id, DEFAULT_DOMAIN_ID)
+ session.commit()
+ session.close()
+
+ def test_downgrade_16_to_14(self):
+ self.upgrade(16)
+ self.populate_user_table(with_pass_enab_domain=True)
+ self.populate_tenant_table(with_desc_enab_domain=True)
+ self.downgrade(14)
+ self.assertTableColumns("user",
+ ["id", "name", "extra",
+ "password", "enabled"])
+ session = self.Session()
+ user_table = sqlalchemy.Table("user",
+ self.metadata,
+ autoload=True)
+ a_user = session.query(user_table).filter("id='foo'").one()
+ self.assertTrue(a_user.enabled)
+ a_user = session.query(user_table).filter("id='badguy'").one()
+ self.assertEqual(a_user.name, default_fixtures.USERS[2]['name'])
+ tenant_table = sqlalchemy.Table("tenant",
+ self.metadata,
+ autoload=True)
+ a_tenant = session.query(tenant_table).filter("id='baz'").one()
+ self.assertEqual(a_tenant.description,
+ default_fixtures.TENANTS[1]['description'])
+ session.commit()
+ session.close()
+
def test_downgrade_14_to_13(self):
self.upgrade(14)
self.downgrade(13)
@@ -298,6 +377,7 @@ class SqlUpgradeTests(test.TestCase):
endpoint.update(common_endpoint_attrs)
self.insert_dict(session, 'endpoint', endpoint)
session.commit()
+ session.close()
self.downgrade(9)
@@ -323,14 +403,15 @@ class SqlUpgradeTests(test.TestCase):
for interface in ['public', 'internal', 'admin']:
expected_url = endpoints[interface]['url']
self.assertEqual(extra['%surl' % interface], expected_url)
+ session.commit()
+ session.close()
def insert_dict(self, session, table_name, d):
"""Naively inserts key-value pairs into a table, given a dictionary."""
- session.execute(
- 'INSERT INTO `%s` (%s) VALUES (%s)' % (
- table_name,
- ', '.join('%s' % k for k in d.keys()),
- ', '.join("'%s'" % v for v in d.values())))
+ this_table = sqlalchemy.Table(table_name, self.metadata, autoload=True)
+ insert = this_table.insert()
+ insert.execute(d)
+ session.commit()
def test_downgrade_to_0(self):
self.upgrade(self.max_version)
@@ -355,28 +436,103 @@ class SqlUpgradeTests(test.TestCase):
self.assertTableColumns('user_domain_metadata',
['user_id', 'domain_id', 'data'])
- def populate_user_table(self):
- user_table = sqlalchemy.Table('user',
+ def populate_user_table(self, with_pass_enab=False,
+ with_pass_enab_domain=False):
+ # Populate the appropriate fields in the user
+ # table, depending on the parameters:
+ #
+ # Default: id, name, extra
+ # pass_enab: Add password, enabled as well
+ # pass_enab_domain: Add password, enabled and domain as well
+ #
+ this_table = sqlalchemy.Table("user",
self.metadata,
autoload=True)
- session = self.Session()
- insert = user_table.insert()
for user in default_fixtures.USERS:
extra = copy.deepcopy(user)
extra.pop('id')
extra.pop('name')
- user['extra'] = json.dumps(extra)
- insert.execute(user)
- def populate_tenant_table(self):
+ if with_pass_enab:
+ password = extra.pop('password', None)
+ enabled = extra.pop('enabled', True)
+ ins = this_table.insert().values(
+ {'id': user['id'],
+ 'name': user['name'],
+ 'password': password,
+ 'enabled': bool(enabled),
+ 'extra': json.dumps(extra)})
+ else:
+ if with_pass_enab_domain:
+ password = extra.pop('password', None)
+ enabled = extra.pop('enabled', True)
+ extra.pop('domain_id')
+ ins = this_table.insert().values(
+ {'id': user['id'],
+ 'name': user['name'],
+ 'domain_id': user['domain_id'],
+ 'password': password,
+ 'enabled': bool(enabled),
+ 'extra': json.dumps(extra)})
+ else:
+ ins = this_table.insert().values(
+ {'id': user['id'],
+ 'name': user['name'],
+ 'extra': json.dumps(extra)})
+ self.engine.execute(ins)
+
+ def populate_tenant_table(self, with_desc_enab=False,
+ with_desc_enab_domain=False):
+ # Populate the appropriate fields in the tenant or
+ # project table, depending on the parameters
+ #
+ # Default: id, name, extra
+ # desc_enab: Add description, enabled as well
+ # desc_enab_domain: Add description, enabled and domain as well,
+ # plus use project instead of tenant
+ #
+ if with_desc_enab_domain:
+ # By this time tenants are now projects
+ this_table = sqlalchemy.Table("project",
+ self.metadata,
+ autoload=True)
+ else:
+ this_table = sqlalchemy.Table("tenant",
+ self.metadata,
+ autoload=True)
+
for tenant in default_fixtures.TENANTS:
extra = copy.deepcopy(tenant)
extra.pop('id')
extra.pop('name')
- self.engine.execute("insert into tenant values ('%s', '%s', '%s')"
- % (tenant['id'],
- tenant['name'],
- json.dumps(extra)))
+
+ if with_desc_enab:
+ desc = extra.pop('description', None)
+ enabled = extra.pop('enabled', True)
+ ins = this_table.insert().values(
+ {'id': tenant['id'],
+ 'name': tenant['name'],
+ 'description': desc,
+ 'enabled': bool(enabled),
+ 'extra': json.dumps(extra)})
+ else:
+ if with_desc_enab_domain:
+ desc = extra.pop('description', None)
+ enabled = extra.pop('enabled', True)
+ extra.pop('domain_id')
+ ins = this_table.insert().values(
+ {'id': tenant['id'],
+ 'name': tenant['name'],
+ 'domain_id': tenant['domain_id'],
+ 'description': desc,
+ 'enabled': bool(enabled),
+ 'extra': json.dumps(extra)})
+ else:
+ ins = this_table.insert().values(
+ {'id': tenant['id'],
+ 'name': tenant['name'],
+ 'extra': json.dumps(extra)})
+ self.engine.execute(ins)
def select_table(self, name):
table = sqlalchemy.Table(name,
@@ -387,16 +543,21 @@ class SqlUpgradeTests(test.TestCase):
def assertTableExists(self, table_name):
try:
- #TODO ayoung: make quoting work for postgres
- self.engine.execute("select count(*) from '%s'" % table_name)
- except:
+ self.select_table(table_name)
+ except sqlalchemy.exc.NoSuchTableError:
raise AssertionError('Table "%s" does not exist' % table_name)
def assertTableDoesNotExist(self, table_name):
"""Asserts that a given table exists cannot be selected by name."""
+ # Switch to a different metadata otherwise you might still
+ # detect renamed or dropped tables
try:
- self.assertTableExists(table_name)
- except AssertionError:
+ temp_metadata = sqlalchemy.MetaData()
+ temp_metadata.bind = self.engine
+ table = sqlalchemy.Table(table_name,
+ temp_metadata,
+ autoload=True)
+ except sqlalchemy.exc.NoSuchTableError:
pass
else:
raise AssertionError('Table "%s" already exists' % table_name)