Split the assignments controller
This is the final part of the more comprehensive split of assignments, which rationalizes both the backend and controllers. In order to make this change easier for reviewers, it is divided into a number of smaller patches. This patch divides up the assignment controller, giving resource its own controller. Previous patches have: - Moved role management into its own manager and drivers - Fixed incorrect doc strings for grant driver methods - Updated controllers to call the new role manager - Updated unit tests to call the new role manager - Refactored the assignment manager and drivers enabling projects/domains to be split out - Fixed incorrect comment about circular dependency between assignment and identity - Moved the logically separated project and domain functionality into their own manager/backend (called resource). - Removes unused pointer to assignment from identity driver - Uddated controllers and managers to call the new resource manager - Updated tests to call the new resource manager Partially implements: bp pluggable-assignments Change-Id: Ic7a4dbe9e39c1910ecc23b37d0b798955544fde4
This commit is contained in:
parent
8ba0c166e5
commit
f01cd89bd0
@ -36,14 +36,31 @@ Identity
|
||||
--------
|
||||
|
||||
The Identity service provides auth credential validation and data about Users,
|
||||
Groups, Projects, Domains and Roles, as well as any associated metadata.
|
||||
Groups.
|
||||
|
||||
In the basic case all this data is managed by the service, allowing the service
|
||||
to manage all the CRUD associated with the data.
|
||||
|
||||
In other cases, this data is pulled, by varying degrees, from an authoritative
|
||||
backend service. An example of this would be when backending on LDAP. See
|
||||
`LDAP Backend` below for more details.
|
||||
In other cases from an authoritative backend service. An example of this would
|
||||
be when backending on LDAP. See `LDAP Backend` below for more details.
|
||||
|
||||
|
||||
Resource
|
||||
--------
|
||||
|
||||
The Resource service provides data about Projects and Domains.
|
||||
|
||||
Like the Identity service, this data may either be managed directly by the
|
||||
service or be pulled from another authoritative backend service, such as LDAP.
|
||||
|
||||
|
||||
Assignment
|
||||
----------
|
||||
|
||||
The Assignment service provides data about Roles and Role assignments to the
|
||||
entities managed by the Identity and Resource services. Again, like these two
|
||||
services, this data may either be managed directly by the Assignment service
|
||||
or be pulled from another authoritative backend service, such as LDAP.
|
||||
|
||||
|
||||
Token
|
||||
@ -90,8 +107,12 @@ on the Keystone configuration.
|
||||
|
||||
* Assignment
|
||||
|
||||
* :mod:`keystone.assignment.controllers.DomainV3`
|
||||
* :mod:`keystone.assignment.controllers.ProjectV3`
|
||||
* :mod:`keystone.assignment.controllers.GrantAssignmentV3`
|
||||
* :mod:`keystone.assignment.controllers.ProjectAssignmentV3`
|
||||
* :mod:`keystone.assignment.controllers.TenantAssignment`
|
||||
* :mod:`keystone.assignment.controllers.Role`
|
||||
* :mod:`keystone.assignment.controllers.RoleAssignmentV2`
|
||||
* :mod:`keystone.assignment.controllers.RoleAssignmentV3`
|
||||
* :mod:`keystone.assignment.controllers.RoleV3`
|
||||
|
||||
* Authentication
|
||||
@ -113,6 +134,11 @@ on the Keystone configuration.
|
||||
|
||||
* :mod:`keystone.policy.controllers.PolicyV3`
|
||||
|
||||
* Resource
|
||||
|
||||
* :mod:`keystone.resource.controllers.DomainV3`
|
||||
* :mod:`keystone.resource.controllers.ProjectV3`
|
||||
|
||||
* Token
|
||||
|
||||
* :mod:`keystone.token.controllers.Auth`
|
||||
|
@ -36,27 +36,9 @@ CONF = config.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
@dependency.requires('assignment_api', 'identity_api', 'resource_api',
|
||||
'token_provider_api')
|
||||
class Tenant(controller.V2Controller):
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_all_projects(self, context, **kw):
|
||||
"""Gets a list of all tenants for an admin user."""
|
||||
if 'name' in context['query_string']:
|
||||
return self.get_project_by_name(
|
||||
context, context['query_string'].get('name'))
|
||||
|
||||
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.filter_domain_id(tenant_ref)
|
||||
params = {
|
||||
'limit': context['query_string'].get('limit'),
|
||||
'marker': context['query_string'].get('marker'),
|
||||
}
|
||||
return self._format_project_list(tenant_refs, **params)
|
||||
@dependency.requires('assignment_api', 'identity_api', 'token_provider_api')
|
||||
class TenantAssignment(controller.V2Controller):
|
||||
"""The V2 Project APIs that are processing assignments."""
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_projects_for_token(self, context, **kw):
|
||||
@ -85,54 +67,7 @@ class Tenant(controller.V2Controller):
|
||||
'limit': context['query_string'].get('limit'),
|
||||
'marker': context['query_string'].get('marker'),
|
||||
}
|
||||
return self._format_project_list(tenant_refs, **params)
|
||||
|
||||
@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)
|
||||
return {'tenant': self.filter_domain_id(ref)}
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_project_by_name(self, context, tenant_name):
|
||||
self.assert_admin(context)
|
||||
ref = self.resource_api.get_project_by_name(
|
||||
tenant_name, CONF.identity.default_domain_id)
|
||||
return {'tenant': self.filter_domain_id(ref)}
|
||||
|
||||
# CRUD Extension
|
||||
@controller.v2_deprecated
|
||||
def create_project(self, context, tenant):
|
||||
tenant_ref = self._normalize_dict(tenant)
|
||||
|
||||
if 'name' not in tenant_ref or not tenant_ref['name']:
|
||||
msg = _('Name field is required and cannot be empty')
|
||||
raise exception.ValidationError(message=msg)
|
||||
|
||||
self.assert_admin(context)
|
||||
tenant_ref['id'] = tenant_ref.get('id', uuid.uuid4().hex)
|
||||
tenant = self.resource_api.create_project(
|
||||
tenant_ref['id'],
|
||||
self._normalize_domain_id(context, tenant_ref))
|
||||
return {'tenant': self.filter_domain_id(tenant)}
|
||||
|
||||
@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
|
||||
clean_tenant = tenant.copy()
|
||||
clean_tenant.pop('domain_id', None)
|
||||
|
||||
tenant_ref = self.resource_api.update_project(
|
||||
tenant_id, clean_tenant)
|
||||
return {'tenant': tenant_ref}
|
||||
|
||||
@controller.v2_deprecated
|
||||
def delete_project(self, context, tenant_id):
|
||||
self.assert_admin(context)
|
||||
self.resource_api.delete_project(tenant_id)
|
||||
return self.format_project_list(tenant_refs, **params)
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_project_users(self, context, tenant_id, **kw):
|
||||
@ -152,60 +87,11 @@ class Tenant(controller.V2Controller):
|
||||
user_refs.append(self.v3_to_v2_user(user_ref))
|
||||
return {'users': user_refs}
|
||||
|
||||
def _format_project_list(self, tenant_refs, **kwargs):
|
||||
marker = kwargs.get('marker')
|
||||
first_index = 0
|
||||
if marker is not None:
|
||||
for (marker_index, tenant) in enumerate(tenant_refs):
|
||||
if tenant['id'] == marker:
|
||||
# we start pagination after the marker
|
||||
first_index = marker_index + 1
|
||||
break
|
||||
else:
|
||||
msg = _('Marker could not be found')
|
||||
raise exception.ValidationError(message=msg)
|
||||
|
||||
limit = kwargs.get('limit')
|
||||
last_index = None
|
||||
if limit is not None:
|
||||
try:
|
||||
limit = int(limit)
|
||||
if limit < 0:
|
||||
raise AssertionError()
|
||||
except (ValueError, AssertionError):
|
||||
msg = _('Invalid limit value')
|
||||
raise exception.ValidationError(message=msg)
|
||||
last_index = first_index + limit
|
||||
|
||||
tenant_refs = tenant_refs[first_index:last_index]
|
||||
|
||||
for x in tenant_refs:
|
||||
if 'enabled' not in x:
|
||||
x['enabled'] = True
|
||||
o = {'tenants': tenant_refs,
|
||||
'tenants_links': []}
|
||||
return o
|
||||
|
||||
|
||||
@dependency.requires('assignment_api', 'role_api')
|
||||
class Role(controller.V2Controller):
|
||||
"""The Role management APIs."""
|
||||
|
||||
# COMPAT(essex-3)
|
||||
@controller.v2_deprecated
|
||||
def get_user_roles(self, context, user_id, tenant_id):
|
||||
"""Get the roles for a user and tenant pair.
|
||||
|
||||
Since we're trying to ignore the idea of user-only roles we're
|
||||
not implementing them in hopes that the idea will die off.
|
||||
|
||||
"""
|
||||
self.assert_admin(context)
|
||||
roles = self.assignment_api.get_roles_for_user_and_project(
|
||||
user_id, tenant_id)
|
||||
return {'roles': [self.role_api.get_role(x)
|
||||
for x in roles]}
|
||||
|
||||
# CRUD extension
|
||||
@controller.v2_deprecated
|
||||
def get_role(self, context, role_id):
|
||||
self.assert_admin(context)
|
||||
@ -235,6 +121,26 @@ class Role(controller.V2Controller):
|
||||
self.assert_admin(context)
|
||||
return {'roles': self.role_api.list_roles()}
|
||||
|
||||
|
||||
@dependency.requires('assignment_api', 'resource_api', 'role_api')
|
||||
class RoleAssignmentV2(controller.V2Controller):
|
||||
"""The V2 Role APIs that are processing assignments."""
|
||||
|
||||
# COMPAT(essex-3)
|
||||
@controller.v2_deprecated
|
||||
def get_user_roles(self, context, user_id, tenant_id=None):
|
||||
"""Get the roles for a user and tenant pair.
|
||||
|
||||
Since we're trying to ignore the idea of user-only roles we're
|
||||
not implementing them in hopes that the idea will die off.
|
||||
|
||||
"""
|
||||
self.assert_admin(context)
|
||||
roles = self.assignment_api.get_roles_for_user_and_project(
|
||||
user_id, tenant_id)
|
||||
return {'roles': [self.role_api.get_role(x)
|
||||
for x in roles]}
|
||||
|
||||
@controller.v2_deprecated
|
||||
def add_role_to_user(self, context, user_id, role_id, tenant_id=None):
|
||||
"""Add a role to a user and tenant pair.
|
||||
@ -342,142 +248,29 @@ class Role(controller.V2Controller):
|
||||
user_id, tenant_id, role_id)
|
||||
|
||||
|
||||
@dependency.requires('resource_api')
|
||||
class DomainV3(controller.V3Controller):
|
||||
collection_name = 'domains'
|
||||
member_name = 'domain'
|
||||
|
||||
def __init__(self):
|
||||
super(DomainV3, self).__init__()
|
||||
self.get_member_from_driver = self.resource_api.get_domain
|
||||
|
||||
@controller.protected()
|
||||
@validation.validated(schema.domain_create, 'domain')
|
||||
def create_domain(self, context, domain):
|
||||
ref = self._assign_unique_id(self._normalize_dict(domain))
|
||||
ref = self.resource_api.create_domain(ref['id'], ref)
|
||||
return DomainV3.wrap_member(context, ref)
|
||||
|
||||
@controller.filterprotected('enabled', 'name')
|
||||
def list_domains(self, context, filters):
|
||||
hints = DomainV3.build_driver_hints(context, filters)
|
||||
refs = self.resource_api.list_domains(hints=hints)
|
||||
return DomainV3.wrap_collection(context, refs, hints=hints)
|
||||
|
||||
@controller.protected()
|
||||
def get_domain(self, context, domain_id):
|
||||
ref = self.resource_api.get_domain(domain_id)
|
||||
return DomainV3.wrap_member(context, ref)
|
||||
|
||||
@controller.protected()
|
||||
@validation.validated(schema.domain_update, 'domain')
|
||||
def update_domain(self, context, domain_id, domain):
|
||||
self._require_matching_id(domain_id, domain)
|
||||
ref = self.resource_api.update_domain(domain_id, domain)
|
||||
return DomainV3.wrap_member(context, ref)
|
||||
|
||||
@controller.protected()
|
||||
def delete_domain(self, context, domain_id):
|
||||
return self.resource_api.delete_domain(domain_id)
|
||||
|
||||
|
||||
@dependency.requires('assignment_api', 'resource_api')
|
||||
class ProjectV3(controller.V3Controller):
|
||||
class ProjectAssignmentV3(controller.V3Controller):
|
||||
"""The V3 Project APIs that are processing assignments."""
|
||||
|
||||
collection_name = 'projects'
|
||||
member_name = 'project'
|
||||
|
||||
def __init__(self):
|
||||
super(ProjectV3, self).__init__()
|
||||
super(ProjectAssignmentV3, self).__init__()
|
||||
self.get_member_from_driver = self.resource_api.get_project
|
||||
|
||||
@controller.protected()
|
||||
@validation.validated(schema.project_create, 'project')
|
||||
def create_project(self, context, project):
|
||||
ref = self._assign_unique_id(self._normalize_dict(project))
|
||||
ref = self._normalize_domain_id(context, ref)
|
||||
ref = self.resource_api.create_project(ref['id'], ref)
|
||||
return ProjectV3.wrap_member(context, ref)
|
||||
|
||||
@controller.filterprotected('domain_id', 'enabled', 'name',
|
||||
'parent_id')
|
||||
def list_projects(self, context, filters):
|
||||
hints = ProjectV3.build_driver_hints(context, filters)
|
||||
refs = self.resource_api.list_projects(hints=hints)
|
||||
return ProjectV3.wrap_collection(context, refs, hints=hints)
|
||||
|
||||
@controller.filterprotected('enabled', 'name')
|
||||
def list_user_projects(self, context, filters, user_id):
|
||||
hints = ProjectV3.build_driver_hints(context, filters)
|
||||
refs = self.assignment_api.list_projects_for_user(user_id, hints=hints)
|
||||
return ProjectV3.wrap_collection(context, refs, hints=hints)
|
||||
|
||||
def _expand_project_ref(self, context, ref):
|
||||
params = context['query_string']
|
||||
|
||||
parents_as_list = 'parents_as_list' in params and (
|
||||
self.query_filter_is_true(params['parents_as_list']))
|
||||
parents_as_ids = 'parents_as_ids' in params and (
|
||||
self.query_filter_is_true(params['parents_as_ids']))
|
||||
|
||||
subtree_as_list = 'subtree_as_list' in params and (
|
||||
self.query_filter_is_true(params['subtree_as_list']))
|
||||
subtree_as_ids = 'subtree_as_ids' in params and (
|
||||
self.query_filter_is_true(params['subtree_as_ids']))
|
||||
|
||||
# parents_as_list and parents_as_ids are mutually exclusive
|
||||
if parents_as_list and parents_as_ids:
|
||||
msg = _('Cannot use parents_as_list and parents_as_ids query '
|
||||
'params at the same time.')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
# subtree_as_list and subtree_as_ids are mutually exclusive
|
||||
if subtree_as_list and subtree_as_ids:
|
||||
msg = _('Cannot use subtree_as_list and subtree_as_ids query '
|
||||
'params at the same time.')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
user_id = self.get_auth_context(context).get('user_id')
|
||||
|
||||
if parents_as_list:
|
||||
parents = self.resource_api.list_project_parents(
|
||||
ref['id'], user_id)
|
||||
ref['parents'] = [ProjectV3.wrap_member(context, p)
|
||||
for p in parents]
|
||||
elif parents_as_ids:
|
||||
ref['parents'] = self.resource_api.get_project_parents_as_ids(ref)
|
||||
|
||||
if subtree_as_list:
|
||||
subtree = self.resource_api.list_projects_in_subtree(
|
||||
ref['id'], user_id)
|
||||
ref['subtree'] = [ProjectV3.wrap_member(context, p)
|
||||
for p in subtree]
|
||||
elif subtree_as_ids:
|
||||
ref['subtree'] = self.resource_api.get_projects_in_subtree_as_ids(
|
||||
ref['id'])
|
||||
|
||||
@controller.protected()
|
||||
def get_project(self, context, project_id):
|
||||
ref = self.resource_api.get_project(project_id)
|
||||
self._expand_project_ref(context, ref)
|
||||
return ProjectV3.wrap_member(context, ref)
|
||||
|
||||
@controller.protected()
|
||||
@validation.validated(schema.project_update, 'project')
|
||||
def update_project(self, context, project_id, project):
|
||||
self._require_matching_id(project_id, project)
|
||||
self._require_matching_domain_id(
|
||||
project_id, project, self.resource_api.get_project)
|
||||
ref = self.resource_api.update_project(project_id, project)
|
||||
return ProjectV3.wrap_member(context, ref)
|
||||
|
||||
@controller.protected()
|
||||
def delete_project(self, context, project_id):
|
||||
return self.resource_api.delete_project(project_id)
|
||||
hints = ProjectAssignmentV3.build_driver_hints(context, filters)
|
||||
refs = self.assignment_api.list_projects_for_user(user_id,
|
||||
hints=hints)
|
||||
return ProjectAssignmentV3.wrap_collection(context, refs, hints=hints)
|
||||
|
||||
|
||||
@dependency.requires('assignment_api', 'identity_api', 'resource_api',
|
||||
'role_api')
|
||||
@dependency.requires('role_api')
|
||||
class RoleV3(controller.V3Controller):
|
||||
"""The V3 Role CRUD APIs."""
|
||||
|
||||
collection_name = 'roles'
|
||||
member_name = 'role'
|
||||
|
||||
@ -516,6 +309,19 @@ class RoleV3(controller.V3Controller):
|
||||
def delete_role(self, context, role_id):
|
||||
self.role_api.delete_role(role_id)
|
||||
|
||||
|
||||
@dependency.requires('assignment_api', 'identity_api', 'resource_api',
|
||||
'role_api')
|
||||
class GrantAssignmentV3(controller.V3Controller):
|
||||
"""The V3 Grant Assignment APIs."""
|
||||
|
||||
collection_name = 'roles'
|
||||
member_name = 'role'
|
||||
|
||||
def __init__(self):
|
||||
super(GrantAssignmentV3, self).__init__()
|
||||
self.get_member_from_driver = self.role_api.get_role
|
||||
|
||||
def _require_domain_xor_project(self, domain_id, project_id):
|
||||
if (domain_id and project_id) or (not domain_id and not project_id):
|
||||
msg = _('Specify a domain or project, not both')
|
||||
@ -582,7 +388,7 @@ class RoleV3(controller.V3Controller):
|
||||
refs = self.assignment_api.list_grants(
|
||||
user_id, group_id, domain_id, project_id,
|
||||
self._check_if_inherited(context))
|
||||
return RoleV3.wrap_collection(context, refs)
|
||||
return GrantAssignmentV3.wrap_collection(context, refs)
|
||||
|
||||
@controller.protected(callback=_check_grant_protection)
|
||||
def check_grant(self, context, role_id, user_id=None,
|
||||
@ -613,6 +419,7 @@ class RoleV3(controller.V3Controller):
|
||||
|
||||
@dependency.requires('assignment_api', 'identity_api', 'resource_api')
|
||||
class RoleAssignmentV3(controller.V3Controller):
|
||||
"""The V3 Role Assignment APIs, really just list_role_assignment()."""
|
||||
|
||||
# TODO(henry-nash): The current implementation does not provide a full
|
||||
# first class entity for role-assignment. There is no role_assignment_id
|
||||
|
@ -31,7 +31,7 @@ build_os_inherit_relation = functools.partial(
|
||||
|
||||
class Public(wsgi.ComposableRouter):
|
||||
def add_routes(self, mapper):
|
||||
tenant_controller = controllers.Tenant()
|
||||
tenant_controller = controllers.TenantAssignment()
|
||||
mapper.connect('/tenants',
|
||||
controller=tenant_controller,
|
||||
action='get_projects_for_token',
|
||||
@ -40,19 +40,8 @@ class Public(wsgi.ComposableRouter):
|
||||
|
||||
class Admin(wsgi.ComposableRouter):
|
||||
def add_routes(self, mapper):
|
||||
# Tenant Operations
|
||||
tenant_controller = controllers.Tenant()
|
||||
mapper.connect('/tenants',
|
||||
controller=tenant_controller,
|
||||
action='get_all_projects',
|
||||
conditions=dict(method=['GET']))
|
||||
mapper.connect('/tenants/{tenant_id}',
|
||||
controller=tenant_controller,
|
||||
action='get_project',
|
||||
conditions=dict(method=['GET']))
|
||||
|
||||
# Role Operations
|
||||
roles_controller = controllers.Role()
|
||||
roles_controller = controllers.RoleAssignmentV2()
|
||||
mapper.connect('/tenants/{tenant_id}/users/{user_id}/roles',
|
||||
controller=roles_controller,
|
||||
action='get_user_roles',
|
||||
@ -66,17 +55,8 @@ class Admin(wsgi.ComposableRouter):
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
routers.append(
|
||||
router.Router(controllers.DomainV3(),
|
||||
'domains', 'domain',
|
||||
resource_descriptions=self.v3_resources))
|
||||
|
||||
project_controller = controllers.ProjectV3()
|
||||
routers.append(
|
||||
router.Router(project_controller,
|
||||
'projects', 'project',
|
||||
resource_descriptions=self.v3_resources))
|
||||
|
||||
project_controller = controllers.ProjectAssignmentV3()
|
||||
self._add_resource(
|
||||
mapper, project_controller,
|
||||
path='/users/{user_id}/projects',
|
||||
@ -86,13 +66,13 @@ class Routers(wsgi.RoutersBase):
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
|
||||
role_controller = controllers.RoleV3()
|
||||
routers.append(
|
||||
router.Router(role_controller, 'roles', 'role',
|
||||
router.Router(controllers.RoleV3(), 'roles', 'role',
|
||||
resource_descriptions=self.v3_resources))
|
||||
|
||||
grant_controller = controllers.GrantAssignmentV3()
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/projects/{project_id}/users/{user_id}/roles/{role_id}',
|
||||
get_head_action='check_grant',
|
||||
put_action='create_grant',
|
||||
@ -104,7 +84,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/projects/{project_id}/groups/{group_id}/roles/{role_id}',
|
||||
get_head_action='check_grant',
|
||||
put_action='create_grant',
|
||||
@ -116,7 +96,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/projects/{project_id}/users/{user_id}/roles',
|
||||
get_action='list_grants',
|
||||
rel=json_home.build_v3_resource_relation('project_user_roles'),
|
||||
@ -125,7 +105,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/projects/{project_id}/groups/{group_id}/roles',
|
||||
get_action='list_grants',
|
||||
rel=json_home.build_v3_resource_relation('project_group_roles'),
|
||||
@ -134,7 +114,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/domains/{domain_id}/users/{user_id}/roles/{role_id}',
|
||||
get_head_action='check_grant',
|
||||
put_action='create_grant',
|
||||
@ -146,7 +126,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/domains/{domain_id}/groups/{group_id}/roles/{role_id}',
|
||||
get_head_action='check_grant',
|
||||
put_action='create_grant',
|
||||
@ -158,7 +138,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/domains/{domain_id}/users/{user_id}/roles',
|
||||
get_action='list_grants',
|
||||
rel=json_home.build_v3_resource_relation('domain_user_roles'),
|
||||
@ -167,7 +147,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/domains/{domain_id}/groups/{group_id}/roles',
|
||||
get_action='list_grants',
|
||||
rel=json_home.build_v3_resource_relation('domain_group_roles'),
|
||||
@ -184,7 +164,7 @@ class Routers(wsgi.RoutersBase):
|
||||
|
||||
if config.CONF.os_inherit.enabled:
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/'
|
||||
'{role_id}/inherited_to_projects',
|
||||
get_head_action='check_grant',
|
||||
@ -198,7 +178,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/'
|
||||
'{role_id}/inherited_to_projects',
|
||||
get_head_action='check_grant',
|
||||
@ -212,7 +192,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/'
|
||||
'inherited_to_projects',
|
||||
get_action='list_grants',
|
||||
@ -223,7 +203,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/'
|
||||
'inherited_to_projects',
|
||||
get_action='list_grants',
|
||||
@ -234,7 +214,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/'
|
||||
'{role_id}/inherited_to_projects',
|
||||
get_head_action='check_grant',
|
||||
@ -248,7 +228,7 @@ class Routers(wsgi.RoutersBase):
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
mapper, grant_controller,
|
||||
path='/OS-INHERIT/projects/{project_id}/groups/{group_id}/'
|
||||
'roles/{role_id}/inherited_to_projects',
|
||||
get_head_action='check_grant',
|
||||
|
@ -10,70 +10,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystone.common import validation
|
||||
from keystone.common.validation import parameter_types
|
||||
|
||||
|
||||
_project_properties = {
|
||||
'description': validation.nullable(parameter_types.description),
|
||||
# NOTE(lbragstad): domain_id isn't nullable according to some backends.
|
||||
# The identity-api should be updated to be consistent with the
|
||||
# implementation.
|
||||
'domain_id': parameter_types.id_string,
|
||||
'enabled': parameter_types.boolean,
|
||||
'parent_id': validation.nullable(parameter_types.id_string),
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'minLength': 1,
|
||||
'maxLength': 64
|
||||
}
|
||||
}
|
||||
|
||||
project_create = {
|
||||
'type': 'object',
|
||||
'properties': _project_properties,
|
||||
# NOTE(lbragstad): A project name is the only parameter required for
|
||||
# project creation according to the Identity V3 API. We should think
|
||||
# about using the maxProperties validator here, and in update.
|
||||
'required': ['name'],
|
||||
'additionalProperties': True
|
||||
}
|
||||
|
||||
project_update = {
|
||||
'type': 'object',
|
||||
'properties': _project_properties,
|
||||
# NOTE(lbragstad) Make sure at least one property is being updated
|
||||
'minProperties': 1,
|
||||
'additionalProperties': True
|
||||
}
|
||||
|
||||
_domain_properties = {
|
||||
'description': validation.nullable(parameter_types.description),
|
||||
'enabled': parameter_types.boolean,
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'minLength': 1,
|
||||
'maxLength': 64
|
||||
}
|
||||
}
|
||||
|
||||
domain_create = {
|
||||
'type': 'object',
|
||||
'properties': _domain_properties,
|
||||
# TODO(lbragstad): According to the V3 API spec, name isn't required but
|
||||
# the current implementation in assignment.controller:DomainV3 requires a
|
||||
# name for the domain.
|
||||
'required': ['name'],
|
||||
'additionalProperties': True
|
||||
}
|
||||
|
||||
domain_update = {
|
||||
'type': 'object',
|
||||
'properties': _domain_properties,
|
||||
'minProperties': 1,
|
||||
'additionalProperties': True
|
||||
}
|
||||
|
||||
_role_properties = {
|
||||
'name': parameter_types.name
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ from oslo_utils import importutils
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
|
||||
from keystone.assignment import controllers as assignment_controllers
|
||||
from keystone.common import controller
|
||||
from keystone.common import dependency
|
||||
from keystone.common import wsgi
|
||||
@ -29,6 +28,7 @@ from keystone.contrib import federation
|
||||
from keystone import exception
|
||||
from keystone.i18n import _, _LI, _LW
|
||||
from keystone.openstack.common import log
|
||||
from keystone.resource import controllers as resource_controllers
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -582,7 +582,7 @@ class Auth(controller.V3Controller):
|
||||
grp_refs = self.assignment_api.list_projects_for_groups(group_ids)
|
||||
|
||||
refs = self._combine_lists_uniquely(user_refs, grp_refs)
|
||||
return assignment_controllers.ProjectV3.wrap_collection(context, refs)
|
||||
return resource_controllers.ProjectV3.wrap_collection(context, refs)
|
||||
|
||||
@controller.protected()
|
||||
def get_auth_domains(self, context):
|
||||
@ -603,7 +603,7 @@ class Auth(controller.V3Controller):
|
||||
grp_refs = self.assignment_api.list_domains_for_groups(group_ids)
|
||||
|
||||
refs = self._combine_lists_uniquely(user_refs, grp_refs)
|
||||
return assignment_controllers.DomainV3.wrap_collection(context, refs)
|
||||
return resource_controllers.DomainV3.wrap_collection(context, refs)
|
||||
|
||||
@controller.protected()
|
||||
def get_auth_catalog(self, context):
|
||||
|
@ -287,6 +287,41 @@ class V2Controller(wsgi.Application):
|
||||
else:
|
||||
raise ValueError(_('Expected dict or list: %s') % type(ref))
|
||||
|
||||
def format_project_list(self, tenant_refs, **kwargs):
|
||||
"""Format a v2 style project list, including marker/limits."""
|
||||
marker = kwargs.get('marker')
|
||||
first_index = 0
|
||||
if marker is not None:
|
||||
for (marker_index, tenant) in enumerate(tenant_refs):
|
||||
if tenant['id'] == marker:
|
||||
# we start pagination after the marker
|
||||
first_index = marker_index + 1
|
||||
break
|
||||
else:
|
||||
msg = _('Marker could not be found')
|
||||
raise exception.ValidationError(message=msg)
|
||||
|
||||
limit = kwargs.get('limit')
|
||||
last_index = None
|
||||
if limit is not None:
|
||||
try:
|
||||
limit = int(limit)
|
||||
if limit < 0:
|
||||
raise AssertionError()
|
||||
except (ValueError, AssertionError):
|
||||
msg = _('Invalid limit value')
|
||||
raise exception.ValidationError(message=msg)
|
||||
last_index = first_index + limit
|
||||
|
||||
tenant_refs = tenant_refs[first_index:last_index]
|
||||
|
||||
for x in tenant_refs:
|
||||
if 'enabled' not in x:
|
||||
x['enabled'] = True
|
||||
o = {'tenants': tenant_refs,
|
||||
'tenants_links': []}
|
||||
return o
|
||||
|
||||
|
||||
@dependency.requires('policy_api', 'token_provider_api')
|
||||
class V3Controller(wsgi.Application):
|
||||
|
@ -17,6 +17,7 @@ from keystone import catalog
|
||||
from keystone.common import extension
|
||||
from keystone.common import wsgi
|
||||
from keystone import identity
|
||||
from keystone import resource
|
||||
|
||||
|
||||
extension.register_admin_extension(
|
||||
@ -47,9 +48,12 @@ class CrudExtension(wsgi.ExtensionRouter):
|
||||
"""
|
||||
|
||||
def add_routes(self, mapper):
|
||||
tenant_controller = assignment.controllers.Tenant()
|
||||
tenant_controller = resource.controllers.Tenant()
|
||||
assignment_tenant_controller = (
|
||||
assignment.controllers.TenantAssignment())
|
||||
user_controller = identity.controllers.User()
|
||||
role_controller = assignment.controllers.Role()
|
||||
assignment_role_controller = assignment.controllers.RoleAssignmentV2()
|
||||
service_controller = catalog.controllers.Service()
|
||||
endpoint_controller = catalog.controllers.Endpoint()
|
||||
|
||||
@ -71,7 +75,7 @@ class CrudExtension(wsgi.ExtensionRouter):
|
||||
conditions=dict(method=['DELETE']))
|
||||
mapper.connect(
|
||||
'/tenants/{tenant_id}/users',
|
||||
controller=tenant_controller,
|
||||
controller=assignment_tenant_controller,
|
||||
action='get_project_users',
|
||||
conditions=dict(method=['GET']))
|
||||
|
||||
@ -137,41 +141,41 @@ class CrudExtension(wsgi.ExtensionRouter):
|
||||
# User Roles
|
||||
mapper.connect(
|
||||
'/users/{user_id}/roles/OS-KSADM/{role_id}',
|
||||
controller=role_controller,
|
||||
controller=assignment_role_controller,
|
||||
action='add_role_to_user',
|
||||
conditions=dict(method=['PUT']))
|
||||
mapper.connect(
|
||||
'/users/{user_id}/roles/OS-KSADM/{role_id}',
|
||||
controller=role_controller,
|
||||
controller=assignment_role_controller,
|
||||
action='remove_role_from_user',
|
||||
conditions=dict(method=['DELETE']))
|
||||
|
||||
# COMPAT(diablo): User Roles
|
||||
mapper.connect(
|
||||
'/users/{user_id}/roleRefs',
|
||||
controller=role_controller,
|
||||
controller=assignment_role_controller,
|
||||
action='get_role_refs',
|
||||
conditions=dict(method=['GET']))
|
||||
mapper.connect(
|
||||
'/users/{user_id}/roleRefs',
|
||||
controller=role_controller,
|
||||
controller=assignment_role_controller,
|
||||
action='create_role_ref',
|
||||
conditions=dict(method=['POST']))
|
||||
mapper.connect(
|
||||
'/users/{user_id}/roleRefs/{role_ref_id}',
|
||||
controller=role_controller,
|
||||
controller=assignment_role_controller,
|
||||
action='delete_role_ref',
|
||||
conditions=dict(method=['DELETE']))
|
||||
|
||||
# User-Tenant Roles
|
||||
mapper.connect(
|
||||
'/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}',
|
||||
controller=role_controller,
|
||||
controller=assignment_role_controller,
|
||||
action='add_role_to_user',
|
||||
conditions=dict(method=['PUT']))
|
||||
mapper.connect(
|
||||
'/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}',
|
||||
controller=role_controller,
|
||||
controller=assignment_role_controller,
|
||||
action='remove_role_from_user',
|
||||
conditions=dict(method=['DELETE']))
|
||||
|
||||
|
@ -14,12 +14,12 @@
|
||||
|
||||
import six
|
||||
|
||||
from keystone import assignment
|
||||
from keystone.catalog import controllers as catalog_controllers
|
||||
from keystone.common import controller
|
||||
from keystone.common import dependency
|
||||
from keystone import exception
|
||||
from keystone import notifications
|
||||
from keystone import resource
|
||||
|
||||
|
||||
@dependency.requires('catalog_api', 'endpoint_filter_api', 'resource_api')
|
||||
@ -135,8 +135,8 @@ class EndpointFilterV3Controller(_ControllerBase):
|
||||
|
||||
projects = [self.resource_api.get_project(
|
||||
ref['project_id']) for ref in refs]
|
||||
return assignment.controllers.ProjectV3.wrap_collection(context,
|
||||
projects)
|
||||
return resource.controllers.ProjectV3.wrap_collection(context,
|
||||
projects)
|
||||
|
||||
|
||||
class EndpointGroupV3Controller(_ControllerBase):
|
||||
@ -226,8 +226,8 @@ class EndpointGroupV3Controller(_ControllerBase):
|
||||
endpoint_group_ref['project_id'])
|
||||
if project:
|
||||
projects.append(project)
|
||||
return assignment.controllers.ProjectV3.wrap_collection(context,
|
||||
projects)
|
||||
return resource.controllers.ProjectV3.wrap_collection(context,
|
||||
projects)
|
||||
|
||||
@controller.protected()
|
||||
def list_endpoints_associated_with_endpoint_group(self,
|
||||
|
@ -321,12 +321,12 @@ class DomainV3(controller.V3Controller):
|
||||
|
||||
|
||||
@dependency.requires('assignment_api', 'resource_api')
|
||||
class ProjectV3(controller.V3Controller):
|
||||
class ProjectAssignmentV3(controller.V3Controller):
|
||||
collection_name = 'projects'
|
||||
member_name = 'project'
|
||||
|
||||
def __init__(self):
|
||||
super(ProjectV3, self).__init__()
|
||||
super(ProjectAssignmentV3, self).__init__()
|
||||
self.get_member_from_driver = self.resource_api.get_project
|
||||
|
||||
@controller.protected()
|
||||
@ -340,7 +340,7 @@ class ProjectV3(controller.V3Controller):
|
||||
auth_context = context['environment'][authorization.AUTH_CONTEXT_ENV]
|
||||
projects = self.assignment_api.list_projects_for_groups(
|
||||
auth_context['group_ids'])
|
||||
return ProjectV3.wrap_collection(context, projects)
|
||||
return ProjectAssignmentV3.wrap_collection(context, projects)
|
||||
|
||||
|
||||
@dependency.requires('federation_api')
|
||||
|
@ -89,7 +89,7 @@ class FederationExtension(wsgi.V3ExtensionRouter):
|
||||
idp_controller = controllers.IdentityProvider()
|
||||
protocol_controller = controllers.FederationProtocol()
|
||||
mapping_controller = controllers.MappingController()
|
||||
project_controller = controllers.ProjectV3()
|
||||
project_controller = controllers.ProjectAssignmentV3()
|
||||
domain_controller = controllers.DomainV3()
|
||||
saml_metadata_controller = controllers.SAMLMetadataV3()
|
||||
sp_controller = controllers.ServiceProvider()
|
||||
|
@ -10,4 +10,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystone.resource import controllers # noqa
|
||||
from keystone.resource.core import * # noqa
|
||||
from keystone.resource import routers # noqa
|
||||
|
227
keystone/resource/controllers.py
Normal file
227
keystone/resource/controllers.py
Normal file
@ -0,0 +1,227 @@
|
||||
# Copyright 2013 Metacloud, Inc.
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Workflow Logic the Resource service."""
|
||||
|
||||
import uuid
|
||||
|
||||
from keystone.common import controller
|
||||
from keystone.common import dependency
|
||||
from keystone.common import validation
|
||||
from keystone import config
|
||||
from keystone import exception
|
||||
from keystone.i18n import _
|
||||
from keystone.openstack.common import log
|
||||
from keystone.resource import schema
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
@dependency.requires('resource_api')
|
||||
class Tenant(controller.V2Controller):
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_all_projects(self, context, **kw):
|
||||
"""Gets a list of all tenants for an admin user."""
|
||||
if 'name' in context['query_string']:
|
||||
return self.get_project_by_name(
|
||||
context, context['query_string'].get('name'))
|
||||
|
||||
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.filter_domain_id(tenant_ref)
|
||||
params = {
|
||||
'limit': context['query_string'].get('limit'),
|
||||
'marker': context['query_string'].get('marker'),
|
||||
}
|
||||
return self.format_project_list(tenant_refs, **params)
|
||||
|
||||
@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)
|
||||
return {'tenant': self.filter_domain_id(ref)}
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_project_by_name(self, context, tenant_name):
|
||||
self.assert_admin(context)
|
||||
ref = self.resource_api.get_project_by_name(
|
||||
tenant_name, CONF.identity.default_domain_id)
|
||||
return {'tenant': self.filter_domain_id(ref)}
|
||||
|
||||
# CRUD Extension
|
||||
@controller.v2_deprecated
|
||||
def create_project(self, context, tenant):
|
||||
tenant_ref = self._normalize_dict(tenant)
|
||||
|
||||
if 'name' not in tenant_ref or not tenant_ref['name']:
|
||||
msg = _('Name field is required and cannot be empty')
|
||||
raise exception.ValidationError(message=msg)
|
||||
|
||||
self.assert_admin(context)
|
||||
tenant_ref['id'] = tenant_ref.get('id', uuid.uuid4().hex)
|
||||
tenant = self.resource_api.create_project(
|
||||
tenant_ref['id'],
|
||||
self._normalize_domain_id(context, tenant_ref))
|
||||
return {'tenant': self.filter_domain_id(tenant)}
|
||||
|
||||
@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
|
||||
clean_tenant = tenant.copy()
|
||||
clean_tenant.pop('domain_id', None)
|
||||
|
||||
tenant_ref = self.resource_api.update_project(
|
||||
tenant_id, clean_tenant)
|
||||
return {'tenant': tenant_ref}
|
||||
|
||||
@controller.v2_deprecated
|
||||
def delete_project(self, context, tenant_id):
|
||||
self.assert_admin(context)
|
||||
self.resource_api.delete_project(tenant_id)
|
||||
|
||||
|
||||
@dependency.requires('resource_api')
|
||||
class DomainV3(controller.V3Controller):
|
||||
collection_name = 'domains'
|
||||
member_name = 'domain'
|
||||
|
||||
def __init__(self):
|
||||
super(DomainV3, self).__init__()
|
||||
self.get_member_from_driver = self.resource_api.get_domain
|
||||
|
||||
@controller.protected()
|
||||
@validation.validated(schema.domain_create, 'domain')
|
||||
def create_domain(self, context, domain):
|
||||
ref = self._assign_unique_id(self._normalize_dict(domain))
|
||||
ref = self.resource_api.create_domain(ref['id'], ref)
|
||||
return DomainV3.wrap_member(context, ref)
|
||||
|
||||
@controller.filterprotected('enabled', 'name')
|
||||
def list_domains(self, context, filters):
|
||||
hints = DomainV3.build_driver_hints(context, filters)
|
||||
refs = self.resource_api.list_domains(hints=hints)
|
||||
return DomainV3.wrap_collection(context, refs, hints=hints)
|
||||
|
||||
@controller.protected()
|
||||
def get_domain(self, context, domain_id):
|
||||
ref = self.resource_api.get_domain(domain_id)
|
||||
return DomainV3.wrap_member(context, ref)
|
||||
|
||||
@controller.protected()
|
||||
@validation.validated(schema.domain_update, 'domain')
|
||||
def update_domain(self, context, domain_id, domain):
|
||||
self._require_matching_id(domain_id, domain)
|
||||
ref = self.resource_api.update_domain(domain_id, domain)
|
||||
return DomainV3.wrap_member(context, ref)
|
||||
|
||||
@controller.protected()
|
||||
def delete_domain(self, context, domain_id):
|
||||
return self.resource_api.delete_domain(domain_id)
|
||||
|
||||
|
||||
@dependency.requires('resource_api')
|
||||
class ProjectV3(controller.V3Controller):
|
||||
collection_name = 'projects'
|
||||
member_name = 'project'
|
||||
|
||||
def __init__(self):
|
||||
super(ProjectV3, self).__init__()
|
||||
self.get_member_from_driver = self.resource_api.get_project
|
||||
|
||||
@controller.protected()
|
||||
@validation.validated(schema.project_create, 'project')
|
||||
def create_project(self, context, project):
|
||||
ref = self._assign_unique_id(self._normalize_dict(project))
|
||||
ref = self._normalize_domain_id(context, ref)
|
||||
ref = self.resource_api.create_project(ref['id'], ref)
|
||||
return ProjectV3.wrap_member(context, ref)
|
||||
|
||||
@controller.filterprotected('domain_id', 'enabled', 'name',
|
||||
'parent_id')
|
||||
def list_projects(self, context, filters):
|
||||
hints = ProjectV3.build_driver_hints(context, filters)
|
||||
refs = self.resource_api.list_projects(hints=hints)
|
||||
return ProjectV3.wrap_collection(context, refs, hints=hints)
|
||||
|
||||
def _expand_project_ref(self, context, ref):
|
||||
params = context['query_string']
|
||||
|
||||
parents_as_list = 'parents_as_list' in params and (
|
||||
self.query_filter_is_true(params['parents_as_list']))
|
||||
parents_as_ids = 'parents_as_ids' in params and (
|
||||
self.query_filter_is_true(params['parents_as_ids']))
|
||||
|
||||
subtree_as_list = 'subtree_as_list' in params and (
|
||||
self.query_filter_is_true(params['subtree_as_list']))
|
||||
subtree_as_ids = 'subtree_as_ids' in params and (
|
||||
self.query_filter_is_true(params['subtree_as_ids']))
|
||||
|
||||
# parents_as_list and parents_as_ids are mutually exclusive
|
||||
if parents_as_list and parents_as_ids:
|
||||
msg = _('Cannot use parents_as_list and parents_as_ids query '
|
||||
'params at the same time.')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
# subtree_as_list and subtree_as_ids are mutually exclusive
|
||||
if subtree_as_list and subtree_as_ids:
|
||||
msg = _('Cannot use subtree_as_list and subtree_as_ids query '
|
||||
'params at the same time.')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
user_id = self.get_auth_context(context).get('user_id')
|
||||
|
||||
if parents_as_list:
|
||||
parents = self.resource_api.list_project_parents(
|
||||
ref['id'], user_id)
|
||||
ref['parents'] = [ProjectV3.wrap_member(context, p)
|
||||
for p in parents]
|
||||
elif parents_as_ids:
|
||||
ref['parents'] = self.resource_api.get_project_parents_as_ids(ref)
|
||||
|
||||
if subtree_as_list:
|
||||
subtree = self.resource_api.list_projects_in_subtree(
|
||||
ref['id'], user_id)
|
||||
ref['subtree'] = [ProjectV3.wrap_member(context, p)
|
||||
for p in subtree]
|
||||
elif subtree_as_ids:
|
||||
ref['subtree'] = self.resource_api.get_projects_in_subtree_as_ids(
|
||||
ref['id'])
|
||||
|
||||
@controller.protected()
|
||||
def get_project(self, context, project_id):
|
||||
ref = self.resource_api.get_project(project_id)
|
||||
self._expand_project_ref(context, ref)
|
||||
return ProjectV3.wrap_member(context, ref)
|
||||
|
||||
@controller.protected()
|
||||
@validation.validated(schema.project_update, 'project')
|
||||
def update_project(self, context, project_id, project):
|
||||
self._require_matching_id(project_id, project)
|
||||
self._require_matching_domain_id(
|
||||
project_id, project, self.resource_api.get_project)
|
||||
ref = self.resource_api.update_project(project_id, project)
|
||||
return ProjectV3.wrap_member(context, ref)
|
||||
|
||||
@controller.protected()
|
||||
def delete_project(self, context, project_id):
|
||||
return self.resource_api.delete_project(project_id)
|
48
keystone/resource/routers.py
Normal file
48
keystone/resource/routers.py
Normal file
@ -0,0 +1,48 @@
|
||||
# Copyright 2013 Metacloud, Inc.
|
||||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""WSGI Routers for the Resource service."""
|
||||
|
||||
from keystone.common import router
|
||||
from keystone.common import wsgi
|
||||
from keystone.resource import controllers
|
||||
|
||||
|
||||
class Admin(wsgi.ComposableRouter):
|
||||
def add_routes(self, mapper):
|
||||
# Tenant Operations
|
||||
tenant_controller = controllers.Tenant()
|
||||
mapper.connect('/tenants',
|
||||
controller=tenant_controller,
|
||||
action='get_all_projects',
|
||||
conditions=dict(method=['GET']))
|
||||
mapper.connect('/tenants/{tenant_id}',
|
||||
controller=tenant_controller,
|
||||
action='get_project',
|
||||
conditions=dict(method=['GET']))
|
||||
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
routers.append(
|
||||
router.Router(controllers.DomainV3(),
|
||||
'domains', 'domain',
|
||||
resource_descriptions=self.v3_resources))
|
||||
|
||||
routers.append(
|
||||
router.Router(controllers.ProjectV3(),
|
||||
'projects', 'project',
|
||||
resource_descriptions=self.v3_resources))
|
75
keystone/resource/schema.py
Normal file
75
keystone/resource/schema.py
Normal file
@ -0,0 +1,75 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystone.common import validation
|
||||
from keystone.common.validation import parameter_types
|
||||
|
||||
|
||||
_project_properties = {
|
||||
'description': validation.nullable(parameter_types.description),
|
||||
# NOTE(lbragstad): domain_id isn't nullable according to some backends.
|
||||
# The identity-api should be updated to be consistent with the
|
||||
# implementation.
|
||||
'domain_id': parameter_types.id_string,
|
||||
'enabled': parameter_types.boolean,
|
||||
'parent_id': validation.nullable(parameter_types.id_string),
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'minLength': 1,
|
||||
'maxLength': 64
|
||||
}
|
||||
}
|
||||
|
||||
project_create = {
|
||||
'type': 'object',
|
||||
'properties': _project_properties,
|
||||
# NOTE(lbragstad): A project name is the only parameter required for
|
||||
# project creation according to the Identity V3 API. We should think
|
||||
# about using the maxProperties validator here, and in update.
|
||||
'required': ['name'],
|
||||
'additionalProperties': True
|
||||
}
|
||||
|
||||
project_update = {
|
||||
'type': 'object',
|
||||
'properties': _project_properties,
|
||||
# NOTE(lbragstad) Make sure at least one property is being updated
|
||||
'minProperties': 1,
|
||||
'additionalProperties': True
|
||||
}
|
||||
|
||||
_domain_properties = {
|
||||
'description': validation.nullable(parameter_types.description),
|
||||
'enabled': parameter_types.boolean,
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'minLength': 1,
|
||||
'maxLength': 64
|
||||
}
|
||||
}
|
||||
|
||||
domain_create = {
|
||||
'type': 'object',
|
||||
'properties': _domain_properties,
|
||||
# TODO(lbragstad): According to the V3 API spec, name isn't required but
|
||||
# the current implementation in assignment.controller:DomainV3 requires a
|
||||
# name for the domain.
|
||||
'required': ['name'],
|
||||
'additionalProperties': True
|
||||
}
|
||||
|
||||
domain_update = {
|
||||
'type': 'object',
|
||||
'properties': _domain_properties,
|
||||
'minProperties': 1,
|
||||
'additionalProperties': True
|
||||
}
|
@ -28,6 +28,7 @@ from keystone import credential
|
||||
from keystone import identity
|
||||
from keystone.openstack.common import log
|
||||
from keystone import policy
|
||||
from keystone import resource
|
||||
from keystone import routers
|
||||
from keystone import token
|
||||
from keystone import trust
|
||||
@ -78,6 +79,7 @@ def admin_app_factory(global_conf, **local_conf):
|
||||
[identity.routers.Admin(),
|
||||
assignment.routers.Admin(),
|
||||
token.routers.Router(),
|
||||
resource.routers.Admin(),
|
||||
routers.VersionV2('admin'),
|
||||
routers.Extension()])
|
||||
|
||||
@ -101,7 +103,8 @@ def v3_app_factory(global_conf, **local_conf):
|
||||
sub_routers = []
|
||||
_routers = []
|
||||
|
||||
router_modules = [assignment, auth, catalog, credential, identity, policy]
|
||||
router_modules = [assignment, auth, catalog, credential, identity, policy,
|
||||
resource]
|
||||
if CONF.trust.enabled:
|
||||
router_modules.append(trust)
|
||||
|
||||
|
@ -15,7 +15,8 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from keystone.assignment import controllers
|
||||
from keystone.assignment import controllers as assignment_controllers
|
||||
from keystone.resource import controllers as resource_controllers
|
||||
from keystone import tests
|
||||
from keystone.tests import default_fixtures
|
||||
from keystone.tests.ksfixtures import database
|
||||
@ -35,8 +36,11 @@ class TenantTestCase(tests.TestCase):
|
||||
self.useFixture(database.Database())
|
||||
self.load_backends()
|
||||
self.load_fixtures(default_fixtures)
|
||||
self.tenant_controller = controllers.Tenant()
|
||||
self.role_controller = controllers.Role()
|
||||
self.tenant_controller = resource_controllers.Tenant()
|
||||
self.assignment_tenant_controller = (
|
||||
assignment_controllers.TenantAssignment())
|
||||
self.assignment_role_controller = (
|
||||
assignment_controllers.RoleAssignmentV2())
|
||||
|
||||
def test_get_project_users_no_user(self):
|
||||
"""get_project_users when user doesn't exist.
|
||||
@ -47,17 +51,19 @@ class TenantTestCase(tests.TestCase):
|
||||
"""
|
||||
project_id = self.tenant_bar['id']
|
||||
|
||||
orig_project_users = self.tenant_controller.get_project_users(
|
||||
_ADMIN_CONTEXT, project_id)
|
||||
orig_project_users = (
|
||||
self.assignment_tenant_controller.get_project_users(_ADMIN_CONTEXT,
|
||||
project_id))
|
||||
|
||||
# Assign a role to a user that doesn't exist to the `bar` project.
|
||||
|
||||
user_id = uuid.uuid4().hex
|
||||
self.role_controller.add_role_to_user(
|
||||
self.assignment_role_controller.add_role_to_user(
|
||||
_ADMIN_CONTEXT, user_id, self.role_other['id'], project_id)
|
||||
|
||||
new_project_users = self.tenant_controller.get_project_users(
|
||||
_ADMIN_CONTEXT, project_id)
|
||||
new_project_users = (
|
||||
self.assignment_tenant_controller.get_project_users(_ADMIN_CONTEXT,
|
||||
project_id))
|
||||
|
||||
# The new user isn't included in the result, so no change.
|
||||
# asserting that the expected values appear in the list,
|
||||
|
@ -23,6 +23,7 @@ from keystone.common.validation import validators
|
||||
from keystone.credential import schema as credential_schema
|
||||
from keystone import exception
|
||||
from keystone.policy import schema as policy_schema
|
||||
from keystone.resource import schema as resource_schema
|
||||
from keystone.trust import schema as trust_schema
|
||||
|
||||
"""Example model to validate create requests against. Assume that this is
|
||||
@ -298,8 +299,8 @@ class ProjectValidationTestCase(testtools.TestCase):
|
||||
|
||||
self.project_name = 'My Project'
|
||||
|
||||
create = assignment_schema.project_create
|
||||
update = assignment_schema.project_update
|
||||
create = resource_schema.project_create
|
||||
update = resource_schema.project_update
|
||||
self.create_project_validator = validators.SchemaValidator(create)
|
||||
self.update_project_validator = validators.SchemaValidator(update)
|
||||
|
||||
@ -425,8 +426,8 @@ class DomainValidationTestCase(testtools.TestCase):
|
||||
|
||||
self.domain_name = 'My Domain'
|
||||
|
||||
create = assignment_schema.domain_create
|
||||
update = assignment_schema.domain_update
|
||||
create = resource_schema.domain_create
|
||||
update = resource_schema.domain_update
|
||||
self.create_domain_validator = validators.SchemaValidator(create)
|
||||
self.update_domain_validator = validators.SchemaValidator(update)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user