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
This commit is contained in:
Henry Nash 2013-01-16 16:10:24 +00:00
parent f1defe8f62
commit 8a89464d62
26 changed files with 1263 additions and 290 deletions

View File

@ -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

View File

@ -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')

View File

@ -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'))

View File

@ -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):

View File

@ -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()

View File

@ -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()

View File

@ -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()))

View File

@ -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)

View File

@ -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()

View File

@ -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,

View File

@ -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)

View File

@ -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))

View File

@ -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')

View File

@ -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):

View File

@ -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:

View File

@ -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)

View File

@ -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
"""

View File

@ -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)
# 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_group_metadata_ref(
context, user_id, tenant_id))
self._append_roles(metadata_ref,
self._get_domain_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
# 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_group_metadata_ref(
context, user_id, tenant_id))
self._append_roles(metadata_ref,
self._get_domain_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)
# 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_group_metadata_ref(
context, user_id, tenant_id))
self._append_roles(metadata_ref,
self._get_domain_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"""
metadata_ref = {}
if tenant_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)
except exception.MetadataNotFound:
metadata_ref = {}
domain_id=None, group_id=None):
"""Returns metadata_ref for a user or group in a tenant or domain"""
metadata_ref = {}
if (user_id or group_id) and (tenant_id or domain_id):
try:
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:
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

View File

@ -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,

View File

@ -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):

View File

@ -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')

View File

@ -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):

View File

@ -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)

View File

@ -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'])

View File

@ -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'],

View File

@ -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)