Merge "Split the assignments controller"
This commit is contained in:
commit
22f1238583
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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))
|
|
@ -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…
Reference in New Issue