Add is_domain field in Project Table
Provides the basic storage of 'is_domain' field in project table. This flag is not interpreted anywhere yet and is set as False by default in manager level. We currently do not allow the creation or seeing a project with this flag set to True via the REST API. We will allow these operations in subsequent patches. This attribute is only available in v3. Hence, it's filtered in v2. Co-Authored-By: Raildo Mascena <raildo@lsd.ufcg.edu.br> Co-Authored-By: Rodrigo Duarte <rodrigods@lsd.ufcg.edu.br> Change-Id: I190e1dd04b1474703804de1fb212340fe6626c13 Partially-Implements: bp reseller
This commit is contained in:
parent
0ca655f9c2
commit
0b8248364b
|
@ -252,6 +252,12 @@ class V2Controller(wsgi.Application):
|
|||
ref.pop('parent_id', None)
|
||||
return ref
|
||||
|
||||
@staticmethod
|
||||
def filter_is_domain(ref):
|
||||
"""Remove is_domain field since v2 calls are not domain-aware."""
|
||||
ref.pop('is_domain', None)
|
||||
return ref
|
||||
|
||||
@staticmethod
|
||||
def normalize_username_in_response(ref):
|
||||
"""Adds username to outgoing user refs to match the v2 spec.
|
||||
|
@ -340,6 +346,7 @@ class V2Controller(wsgi.Application):
|
|||
"""Run through the various filter methods."""
|
||||
V2Controller.filter_domain_id(ref)
|
||||
V2Controller.filter_project_parent_id(ref)
|
||||
V2Controller.filter_is_domain(ref)
|
||||
return ref
|
||||
|
||||
if isinstance(ref, dict):
|
||||
|
|
|
@ -130,11 +130,12 @@ class Project(Model):
|
|||
Optional Keys:
|
||||
description
|
||||
enabled (bool, default True)
|
||||
is_domain (bool, default False)
|
||||
|
||||
"""
|
||||
|
||||
required_keys = ('id', 'name', 'domain_id')
|
||||
optional_keys = ('description', 'enabled')
|
||||
optional_keys = ('description', 'enabled', 'is_domain')
|
||||
|
||||
|
||||
class Role(Model):
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import sqlalchemy as sql
|
||||
|
||||
|
||||
_PROJECT_TABLE_NAME = 'project'
|
||||
_IS_DOMAIN_COLUMN_NAME = 'is_domain'
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = sql.MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
project_table = sql.Table(_PROJECT_TABLE_NAME, meta, autoload=True)
|
||||
is_domain = sql.Column(_IS_DOMAIN_COLUMN_NAME, sql.Boolean, nullable=False,
|
||||
server_default='0', default=False)
|
||||
project_table.create_column(is_domain)
|
|
@ -60,6 +60,14 @@ class Resource(resource.Driver):
|
|||
else:
|
||||
raise ValueError(_('Expected dict or list: %s') % type(ref))
|
||||
|
||||
def _set_default_is_domain_project(self, ref):
|
||||
if isinstance(ref, dict):
|
||||
return dict(ref, is_domain=False)
|
||||
elif isinstance(ref, list):
|
||||
return [self._set_default_is_domain_project(x) for x in ref]
|
||||
else:
|
||||
raise ValueError(_('Expected dict or list: %s') % type(ref))
|
||||
|
||||
def _validate_parent_project_is_none(self, ref):
|
||||
"""If a parent_id different from None was given,
|
||||
raises InvalidProjectException.
|
||||
|
@ -69,8 +77,15 @@ class Resource(resource.Driver):
|
|||
if parent_id is not None:
|
||||
raise exception.InvalidParentProject(parent_id)
|
||||
|
||||
def _validate_is_domain_field_is_false(self, ref):
|
||||
is_domain = ref.pop('is_domain', None)
|
||||
if is_domain:
|
||||
raise exception.ValidationError(_('LDAP does not support projects '
|
||||
'with is_domain flag enabled'))
|
||||
|
||||
def _set_default_attributes(self, project_ref):
|
||||
project_ref = self._set_default_domain(project_ref)
|
||||
project_ref = self._set_default_is_domain_project(project_ref)
|
||||
return self._set_default_parent_project(project_ref)
|
||||
|
||||
def get_project(self, tenant_id):
|
||||
|
@ -117,6 +132,7 @@ class Resource(resource.Driver):
|
|||
def create_project(self, tenant_id, tenant):
|
||||
self.project.check_allow_create()
|
||||
self._validate_parent_project_is_none(tenant)
|
||||
self._validate_is_domain_field_is_false(tenant)
|
||||
tenant['name'] = clean.project_name(tenant['name'])
|
||||
data = tenant.copy()
|
||||
if 'id' not in data or data['id'] is None:
|
||||
|
@ -129,6 +145,7 @@ class Resource(resource.Driver):
|
|||
def update_project(self, tenant_id, tenant):
|
||||
self.project.check_allow_update()
|
||||
tenant = self._validate_default_domain(tenant)
|
||||
self._validate_is_domain_field_is_false(tenant)
|
||||
if 'name' in tenant:
|
||||
tenant['name'] = clean.project_name(tenant['name'])
|
||||
return self._set_default_attributes(
|
||||
|
|
|
@ -245,7 +245,7 @@ class Domain(sql.ModelBase, sql.DictBase):
|
|||
class Project(sql.ModelBase, sql.DictBase):
|
||||
__tablename__ = 'project'
|
||||
attributes = ['id', 'name', 'domain_id', 'description', 'enabled',
|
||||
'parent_id']
|
||||
'parent_id', 'is_domain']
|
||||
id = sql.Column(sql.String(64), primary_key=True)
|
||||
name = sql.Column(sql.String(64), nullable=False)
|
||||
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
|
||||
|
@ -254,6 +254,7 @@ class Project(sql.ModelBase, sql.DictBase):
|
|||
enabled = sql.Column(sql.Boolean)
|
||||
extra = sql.Column(sql.JsonBlob())
|
||||
parent_id = sql.Column(sql.String(64), sql.ForeignKey('project.id'))
|
||||
is_domain = sql.Column(sql.Boolean, default=False, nullable=False)
|
||||
# Unique constraint across two columns to create the separation
|
||||
# rather than just only 'name' being unique
|
||||
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
|
||||
|
|
|
@ -47,24 +47,34 @@ class Tenant(controller.V2Controller):
|
|||
self.assert_admin(context)
|
||||
tenant_refs = self.resource_api.list_projects_in_domain(
|
||||
CONF.identity.default_domain_id)
|
||||
for tenant_ref in tenant_refs:
|
||||
tenant_ref = self.v3_to_v2_project(tenant_ref)
|
||||
tenant_refs = [self.v3_to_v2_project(tenant_ref)
|
||||
for tenant_ref in tenant_refs
|
||||
if not tenant_ref.get('is_domain')]
|
||||
params = {
|
||||
'limit': context['query_string'].get('limit'),
|
||||
'marker': context['query_string'].get('marker'),
|
||||
}
|
||||
return self.format_project_list(tenant_refs, **params)
|
||||
|
||||
def _assert_not_is_domain_project(self, project_id, project_ref=None):
|
||||
# Projects acting as a domain should not be visible via v2
|
||||
if not project_ref:
|
||||
project_ref = self.resource_api.get_project(project_id)
|
||||
if project_ref.get('is_domain'):
|
||||
raise exception.ProjectNotFound(project_id)
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_project(self, context, tenant_id):
|
||||
# TODO(termie): this stuff should probably be moved to middleware
|
||||
self.assert_admin(context)
|
||||
ref = self.resource_api.get_project(tenant_id)
|
||||
self._assert_not_is_domain_project(tenant_id, ref)
|
||||
return {'tenant': self.v3_to_v2_project(ref)}
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_project_by_name(self, context, tenant_name):
|
||||
self.assert_admin(context)
|
||||
# Projects acting as a domain should not be visible via v2
|
||||
ref = self.resource_api.get_project_by_name(
|
||||
tenant_name, CONF.identity.default_domain_id)
|
||||
return {'tenant': self.v3_to_v2_project(ref)}
|
||||
|
@ -88,11 +98,12 @@ class Tenant(controller.V2Controller):
|
|||
@controller.v2_deprecated
|
||||
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
|
||||
self._assert_not_is_domain_project(tenant_id)
|
||||
# Remove domain_id and is_domain if specified - a v2 api caller
|
||||
# should not be specifying that
|
||||
clean_tenant = tenant.copy()
|
||||
clean_tenant.pop('domain_id', None)
|
||||
|
||||
clean_tenant.pop('is_domain', None)
|
||||
tenant_ref = self.resource_api.update_project(
|
||||
tenant_id, clean_tenant)
|
||||
return {'tenant': self.v3_to_v2_project(tenant_ref)}
|
||||
|
@ -100,6 +111,7 @@ class Tenant(controller.V2Controller):
|
|||
@controller.v2_deprecated
|
||||
def delete_project(self, context, tenant_id):
|
||||
self.assert_admin(context)
|
||||
self._assert_not_is_domain_project(tenant_id)
|
||||
self.resource_api.delete_project(tenant_id)
|
||||
|
||||
|
||||
|
@ -201,6 +213,12 @@ class ProjectV3(controller.V3Controller):
|
|||
def create_project(self, context, project):
|
||||
ref = self._assign_unique_id(self._normalize_dict(project))
|
||||
ref = self._normalize_domain_id(context, ref)
|
||||
|
||||
if ref.get('is_domain'):
|
||||
msg = _('The creation of projects acting as domains is not '
|
||||
'allowed yet.')
|
||||
raise exception.NotImplemented(msg)
|
||||
|
||||
initiator = notifications._get_request_audit_info(context)
|
||||
try:
|
||||
ref = self.resource_api.create_project(ref['id'], ref,
|
||||
|
|
|
@ -88,6 +88,7 @@ class Manager(manager.Manager):
|
|||
tenant['enabled'] = clean.project_enabled(tenant['enabled'])
|
||||
tenant.setdefault('description', '')
|
||||
tenant.setdefault('parent_id', None)
|
||||
tenant.setdefault('is_domain', False)
|
||||
|
||||
self.get_domain(tenant.get('domain_id'))
|
||||
if tenant.get('parent_id') is not None:
|
||||
|
@ -198,6 +199,11 @@ class Manager(manager.Manager):
|
|||
raise exception.ForbiddenAction(
|
||||
action=_('Update of `parent_id` is not allowed.'))
|
||||
|
||||
if ('is_domain' in tenant and
|
||||
tenant['is_domain'] != original_tenant['is_domain']):
|
||||
raise exception.ValidationError(
|
||||
message=_('Update of `is_domain` is not allowed.'))
|
||||
|
||||
if 'enabled' in tenant:
|
||||
tenant['enabled'] = clean.project_enabled(tenant['enabled'])
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ _project_properties = {
|
|||
# implementation.
|
||||
'domain_id': parameter_types.id_string,
|
||||
'enabled': parameter_types.boolean,
|
||||
'is_domain': parameter_types.boolean,
|
||||
'parent_id': validation.nullable(parameter_types.id_string),
|
||||
'name': {
|
||||
'type': 'string',
|
||||
|
|
|
@ -25,6 +25,7 @@ TENANTS = [
|
|||
'description': 'description',
|
||||
'enabled': True,
|
||||
'parent_id': None,
|
||||
'is_domain': False,
|
||||
}, {
|
||||
'id': 'baz',
|
||||
'name': 'BAZ',
|
||||
|
@ -32,6 +33,7 @@ TENANTS = [
|
|||
'description': 'description',
|
||||
'enabled': True,
|
||||
'parent_id': None,
|
||||
'is_domain': False,
|
||||
}, {
|
||||
'id': 'mtu',
|
||||
'name': 'MTU',
|
||||
|
@ -39,6 +41,7 @@ TENANTS = [
|
|||
'enabled': True,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'parent_id': None,
|
||||
'is_domain': False,
|
||||
}, {
|
||||
'id': 'service',
|
||||
'name': 'service',
|
||||
|
@ -46,6 +49,7 @@ TENANTS = [
|
|||
'enabled': True,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'parent_id': None,
|
||||
'is_domain': False,
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -2089,7 +2089,7 @@ class IdentityTests(object):
|
|||
# Create a project
|
||||
project = {'id': uuid.uuid4().hex, 'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'name': uuid.uuid4().hex, 'description': uuid.uuid4().hex,
|
||||
'enabled': True, 'parent_id': None}
|
||||
'enabled': True, 'parent_id': None, 'is_domain': False}
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
|
||||
# Build driver hints with the project's name and inexistent description
|
||||
|
@ -2157,7 +2157,8 @@ class IdentityTests(object):
|
|||
'domain_id': domain_id,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project_id, project)
|
||||
|
||||
projects = [project]
|
||||
|
@ -2167,13 +2168,38 @@ class IdentityTests(object):
|
|||
'domain_id': domain_id,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': project_id}
|
||||
'parent_id': project_id,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(new_project['id'], new_project)
|
||||
projects.append(new_project)
|
||||
project_id = new_project['id']
|
||||
|
||||
return projects
|
||||
|
||||
def test_create_project_without_is_domain_flag(self):
|
||||
project = {'id': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': None}
|
||||
|
||||
ref = self.resource_api.create_project(project['id'], project)
|
||||
# The is_domain flag should be False by default
|
||||
self.assertFalse(ref['is_domain'])
|
||||
|
||||
def test_create_is_domain_project(self):
|
||||
project = {'id': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': None,
|
||||
'is_domain': True}
|
||||
|
||||
ref = self.resource_api.create_project(project['id'], project)
|
||||
self.assertTrue(ref['is_domain'])
|
||||
|
||||
def test_check_leaf_projects(self):
|
||||
projects_hierarchy = self._create_projects_hierarchy()
|
||||
root_project = projects_hierarchy[0]
|
||||
|
@ -2201,7 +2227,8 @@ class IdentityTests(object):
|
|||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': project2['id']}
|
||||
'parent_id': project2['id'],
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project4['id'], project4)
|
||||
|
||||
subtree = self.resource_api.list_projects_in_subtree(project1['id'])
|
||||
|
@ -2270,7 +2297,8 @@ class IdentityTests(object):
|
|||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': project2['id']}
|
||||
'parent_id': project2['id'],
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project4['id'], project4)
|
||||
|
||||
parents1 = self.resource_api.list_project_parents(project3['id'])
|
||||
|
@ -2873,7 +2901,8 @@ class IdentityTests(object):
|
|||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'parent_id': 'fake'}
|
||||
'parent_id': 'fake',
|
||||
'is_domain': False}
|
||||
self.assertRaises(exception.ProjectNotFound,
|
||||
self.resource_api.create_project,
|
||||
project['id'],
|
||||
|
@ -2886,7 +2915,8 @@ class IdentityTests(object):
|
|||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(root_project['id'], root_project)
|
||||
|
||||
domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
|
@ -2897,7 +2927,8 @@ class IdentityTests(object):
|
|||
'description': '',
|
||||
'domain_id': domain['id'],
|
||||
'enabled': True,
|
||||
'parent_id': root_project['id']}
|
||||
'parent_id': root_project['id'],
|
||||
'is_domain': False}
|
||||
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.resource_api.create_project,
|
||||
|
@ -2948,13 +2979,15 @@ class IdentityTests(object):
|
|||
'name': uuid.uuid4().hex,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': False,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project1['id'], project1)
|
||||
|
||||
project2 = {'id': uuid.uuid4().hex,
|
||||
'name': uuid.uuid4().hex,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'parent_id': project1['id']}
|
||||
'parent_id': project1['id'],
|
||||
'is_domain': False}
|
||||
|
||||
# It's not possible to create a project under a disabled one in the
|
||||
# hierarchy
|
||||
|
@ -3020,7 +3053,8 @@ class IdentityTests(object):
|
|||
'id': project_id,
|
||||
'name': uuid.uuid4().hex,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'parent_id': leaf_project['id']}
|
||||
'parent_id': leaf_project['id'],
|
||||
'is_domain': False}
|
||||
self.assertRaises(exception.ForbiddenAction,
|
||||
self.resource_api.create_project,
|
||||
project_id,
|
||||
|
@ -3032,7 +3066,8 @@ class IdentityTests(object):
|
|||
'name': uuid.uuid4().hex,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
|
||||
# Add a description attribute.
|
||||
|
@ -3048,7 +3083,8 @@ class IdentityTests(object):
|
|||
'name': uuid.uuid4().hex,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
|
||||
# Add a description attribute.
|
||||
|
@ -3726,16 +3762,16 @@ class IdentityTests(object):
|
|||
domain2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||
self.resource_api.create_domain(domain2['id'], domain2)
|
||||
project1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
'domain_id': domain1['id']}
|
||||
'domain_id': domain1['id'], 'is_domain': False}
|
||||
project1 = self.resource_api.create_project(project1['id'], project1)
|
||||
project2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
'domain_id': domain1['id']}
|
||||
'domain_id': domain1['id'], 'is_domain': False}
|
||||
project2 = self.resource_api.create_project(project2['id'], project2)
|
||||
project3 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
'domain_id': domain1['id']}
|
||||
'domain_id': domain1['id'], 'is_domain': False}
|
||||
project3 = self.resource_api.create_project(project3['id'], project3)
|
||||
project4 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
'domain_id': domain2['id']}
|
||||
'domain_id': domain2['id'], 'is_domain': False}
|
||||
project4 = self.resource_api.create_project(project4['id'], project4)
|
||||
group_list = []
|
||||
role_list = []
|
||||
|
@ -5538,14 +5574,16 @@ class InheritanceTests(object):
|
|||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(root_project['id'], root_project)
|
||||
leaf_project = {'id': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': root_project['id']}
|
||||
'parent_id': root_project['id'],
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(leaf_project['id'], leaf_project)
|
||||
|
||||
user = {'name': uuid.uuid4().hex, 'password': uuid.uuid4().hex,
|
||||
|
@ -5659,14 +5697,16 @@ class InheritanceTests(object):
|
|||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(root_project['id'], root_project)
|
||||
leaf_project = {'id': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': root_project['id']}
|
||||
'parent_id': root_project['id'],
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(leaf_project['id'], leaf_project)
|
||||
|
||||
user = {'name': uuid.uuid4().hex, 'password': uuid.uuid4().hex,
|
||||
|
|
|
@ -1532,7 +1532,8 @@ class LDAPIdentity(BaseLDAPIdentity, tests.TestCase):
|
|||
'domain_id': CONF.identity.default_domain_id,
|
||||
'description': uuid.uuid4().hex,
|
||||
'enabled': True,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
project_ref = self.resource_api.get_project(project['id'])
|
||||
|
||||
|
@ -1610,7 +1611,8 @@ class LDAPIdentity(BaseLDAPIdentity, tests.TestCase):
|
|||
'description': '',
|
||||
'domain_id': domain['id'],
|
||||
'enabled': True,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project1['id'], project1)
|
||||
|
||||
# Creating project2 under project1. LDAP will not allow
|
||||
|
@ -1620,7 +1622,8 @@ class LDAPIdentity(BaseLDAPIdentity, tests.TestCase):
|
|||
'description': '',
|
||||
'domain_id': domain['id'],
|
||||
'enabled': True,
|
||||
'parent_id': project1['id']}
|
||||
'parent_id': project1['id'],
|
||||
'is_domain': False}
|
||||
|
||||
self.assertRaises(exception.InvalidParentProject,
|
||||
self.resource_api.create_project,
|
||||
|
@ -1634,6 +1637,37 @@ class LDAPIdentity(BaseLDAPIdentity, tests.TestCase):
|
|||
# Returning projects to be used across the tests
|
||||
return [project1, project2]
|
||||
|
||||
def test_create_is_domain_project(self):
|
||||
domain = self._get_domain_fixture()
|
||||
project = {'id': uuid.uuid4().hex,
|
||||
'name': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': domain['id'],
|
||||
'enabled': True,
|
||||
'parent_id': None,
|
||||
'is_domain': True}
|
||||
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.resource_api.create_project,
|
||||
project['id'], project)
|
||||
|
||||
def test_update_is_domain_field(self):
|
||||
domain = self._get_domain_fixture()
|
||||
project = {'id': uuid.uuid4().hex,
|
||||
'name': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': domain['id'],
|
||||
'enabled': True,
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
|
||||
# Try to update the is_domain field to True
|
||||
project['is_domain'] = True
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.resource_api.update_project,
|
||||
project['id'], project)
|
||||
|
||||
def test_check_leaf_projects(self):
|
||||
projects = self._assert_create_hierarchy_not_allowed()
|
||||
for project in projects:
|
||||
|
@ -1966,7 +2000,8 @@ class LDAPIdentityEnabledEmulation(LDAPIdentity):
|
|||
'name': uuid.uuid4().hex,
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'description': uuid.uuid4().hex,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
project_ref = self.resource_api.get_project(project['id'])
|
||||
|
@ -2603,7 +2638,8 @@ class MultiLDAPandSQLIdentity(BaseLDAPIdentity, tests.SQLDriverOverrides,
|
|||
'domain_id': domain['id'],
|
||||
'description': uuid.uuid4().hex,
|
||||
'parent_id': None,
|
||||
'enabled': True}
|
||||
'enabled': True,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_domain(domain['id'], domain)
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
project_ref = self.resource_api.get_project(project['id'])
|
||||
|
|
|
@ -156,7 +156,8 @@ class SqlModels(SqlTests):
|
|||
('domain_id', sql.String, 64),
|
||||
('enabled', sql.Boolean, None),
|
||||
('extra', sql.JsonBlob, None),
|
||||
('parent_id', sql.String, 64))
|
||||
('parent_id', sql.String, 64),
|
||||
('is_domain', sql.Boolean, False))
|
||||
self.assertExpectedSchema('project', cols)
|
||||
|
||||
def test_role_assignment_model(self):
|
||||
|
|
|
@ -629,6 +629,13 @@ class SqlUpgradeTests(SqlMigrateBase):
|
|||
self.assertFalse(self._does_index_exist('assignment',
|
||||
'assignment_role_id_fkey'))
|
||||
|
||||
def test_project_is_domain_upgrade(self):
|
||||
self.upgrade(74)
|
||||
self.assertTableColumns('project',
|
||||
['id', 'name', 'extra', 'description',
|
||||
'enabled', 'domain_id', 'parent_id',
|
||||
'is_domain'])
|
||||
|
||||
def populate_user_table(self, with_pass_enab=False,
|
||||
with_pass_enab_domain=False):
|
||||
# Populate the appropriate fields in the user
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import uuid
|
||||
|
||||
from keystone.assignment import controllers as assignment_controllers
|
||||
from keystone import exception
|
||||
from keystone.resource import controllers as resource_controllers
|
||||
from keystone.tests import unit as tests
|
||||
from keystone.tests.unit import default_fixtures
|
||||
|
@ -93,4 +94,50 @@ class TenantTestCase(tests.TestCase):
|
|||
tenant_copy = tenant.copy()
|
||||
tenant_copy.pop('domain_id')
|
||||
tenant_copy.pop('parent_id')
|
||||
tenant_copy.pop('is_domain')
|
||||
self.assertIn(tenant_copy, refs['tenants'])
|
||||
|
||||
def _create_is_domain_project(self):
|
||||
project = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
'domain_id': 'default', 'is_domain': True}
|
||||
project_ref = self.resource_api.create_project(project['id'], project)
|
||||
return self.tenant_controller.v3_to_v2_project(project_ref)
|
||||
|
||||
def test_update_is_domain_project_not_found(self):
|
||||
"""Test that update is_domain project is not allowed in v2."""
|
||||
project = self._create_is_domain_project()
|
||||
|
||||
project['name'] = uuid.uuid4().hex
|
||||
self.assertRaises(
|
||||
exception.ProjectNotFound,
|
||||
self.tenant_controller.update_project,
|
||||
_ADMIN_CONTEXT,
|
||||
project['id'],
|
||||
project
|
||||
)
|
||||
|
||||
def test_delete_is_domain_project_not_found(self):
|
||||
"""Test that delete is_domain project is not allowed in v2."""
|
||||
project = self._create_is_domain_project()
|
||||
|
||||
self.assertRaises(
|
||||
exception.ProjectNotFound,
|
||||
self.tenant_controller.delete_project,
|
||||
_ADMIN_CONTEXT,
|
||||
project['id']
|
||||
)
|
||||
|
||||
def test_list_is_domain_project_not_found(self):
|
||||
"""Test v2 get_all_projects having projects that act as a domain.
|
||||
|
||||
In v2 no project with the is_domain flag enabled should be
|
||||
returned.
|
||||
"""
|
||||
project1 = self._create_is_domain_project()
|
||||
project2 = self._create_is_domain_project()
|
||||
|
||||
refs = self.tenant_controller.get_all_projects(_ADMIN_CONTEXT)
|
||||
projects = refs.get('tenants')
|
||||
|
||||
self.assertNotIn(project1, projects)
|
||||
self.assertNotIn(project2, projects)
|
||||
|
|
|
@ -299,10 +299,11 @@ class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase,
|
|||
ref = self.new_ref()
|
||||
return ref
|
||||
|
||||
def new_project_ref(self, domain_id, parent_id=None):
|
||||
def new_project_ref(self, domain_id=None, parent_id=None, is_domain=False):
|
||||
ref = self.new_ref()
|
||||
ref['domain_id'] = domain_id
|
||||
ref['parent_id'] = parent_id
|
||||
ref['is_domain'] = is_domain
|
||||
return ref
|
||||
|
||||
def new_user_ref(self, domain_id, project_id=None):
|
||||
|
|
|
@ -527,6 +527,18 @@ class AssignmentTestCase(test_v3.RestfulTestCase,
|
|||
ref = self.new_project_ref(domain_id=uuid.uuid4().hex)
|
||||
self.post('/projects', body={'project': ref}, expected_status=400)
|
||||
|
||||
def test_create_project_is_domain_not_allowed(self):
|
||||
"""Call ``POST /projects``.
|
||||
|
||||
Setting is_domain=True is not supported yet and should raise
|
||||
NotImplemented.
|
||||
|
||||
"""
|
||||
ref = self.new_project_ref(domain_id=self.domain_id, is_domain=True)
|
||||
self.post('/projects',
|
||||
body={'project': ref},
|
||||
expected_status=501)
|
||||
|
||||
def _create_projects_hierarchy(self, hierarchy_size=1):
|
||||
"""Creates a single-branched project hierarchy with the specified size.
|
||||
|
||||
|
@ -942,6 +954,22 @@ class AssignmentTestCase(test_v3.RestfulTestCase,
|
|||
body={'project': leaf_project},
|
||||
expected_status=403)
|
||||
|
||||
def test_update_project_is_domain_not_allowed(self):
|
||||
"""Call ``PATCH /projects/{project_id}`` with is_domain.
|
||||
|
||||
The is_domain flag is immutable.
|
||||
"""
|
||||
project = self.new_project_ref(domain_id=self.domain['id'])
|
||||
resp = self.post('/projects',
|
||||
body={'project': project})
|
||||
self.assertFalse(resp.result['project']['is_domain'])
|
||||
|
||||
project['is_domain'] = True
|
||||
self.patch('/projects/%(project_id)s' % {
|
||||
'project_id': resp.result['project']['id']},
|
||||
body={'project': project},
|
||||
expected_status=400)
|
||||
|
||||
def test_disable_leaf_project(self):
|
||||
"""Call ``PATCH /projects/{project_id}``."""
|
||||
projects = self._create_projects_hierarchy()
|
||||
|
|
Loading…
Reference in New Issue