Allow admin project to operate on all quotas
Currently when operating on quotas in a nested hierarchical environment, the calling project context must be a member of the same hierarchy as the target project in order to be authorized, even if it has the admin role. This is a change in behavior for users who are used to being able to view and modify any project quotas as admin without needing a token scoped to the target project's hierarchy. This patch allows projects with the cloud admin role to be able to operate on all project quotas regardless of its place in project hierarchy. Change-Id: Ifc0f1c5d06e7ca3aa7d9cf267a40f2a6b5cfc078 Closes-bug: #1597045
This commit is contained in:
parent
55cb9e8747
commit
a0a04f4332
@ -85,6 +85,10 @@ class QuotaSetsController(wsgi.Controller):
|
||||
:param parent_id: The parent id of the project in which the user
|
||||
want to perform an update or delete operation.
|
||||
"""
|
||||
if context_project.is_admin_project:
|
||||
# The calling project has admin privileges and should be able
|
||||
# to operate on all quotas.
|
||||
return
|
||||
if context_project.parent_id and parent_id != context_project.id:
|
||||
msg = _("Update and delete quota operations can only be made "
|
||||
"by an admin of immediate parent or by the CLOUD admin.")
|
||||
@ -105,15 +109,20 @@ class QuotaSetsController(wsgi.Controller):
|
||||
def _authorize_show(self, context_project, target_project):
|
||||
"""Checks if show is allowed in the current hierarchy.
|
||||
|
||||
With hierarchical projects, are allowed to perform quota show operation
|
||||
users with admin role in, at least, one of the following projects: the
|
||||
current project; the immediate parent project; or the root project.
|
||||
With hierarchical projects, users are allowed to perform a quota show
|
||||
operation if they have the cloud admin role or if they belong to at
|
||||
least one of the following projects: the target project, its immediate
|
||||
parent project, or the root project of its hierarchy.
|
||||
|
||||
:param context_project: The project in which the user
|
||||
is scoped to.
|
||||
:param target_project: The project in which the user wants
|
||||
to perform a show operation.
|
||||
"""
|
||||
if context_project.is_admin_project:
|
||||
# The calling project has admin privileges and should be able
|
||||
# to view all quotas.
|
||||
return
|
||||
if target_project.parent_id:
|
||||
if target_project.id != context_project.id:
|
||||
if not self._is_descendant(target_project.id,
|
||||
@ -170,7 +179,8 @@ class QuotaSetsController(wsgi.Controller):
|
||||
target_project = quota_utils.get_project_hierarchy(
|
||||
context, target_project_id)
|
||||
context_project = quota_utils.get_project_hierarchy(
|
||||
context, context.project_id, subtree_as_ids=True)
|
||||
context, context.project_id, subtree_as_ids=True,
|
||||
is_admin_project=context.is_admin)
|
||||
|
||||
self._authorize_show(context_project, target_project)
|
||||
|
||||
@ -238,7 +248,8 @@ class QuotaSetsController(wsgi.Controller):
|
||||
# Get the children of the project which the token is scoped to
|
||||
# in order to know if the target_project is in its hierarchy.
|
||||
context_project = quota_utils.get_project_hierarchy(
|
||||
context, context.project_id, subtree_as_ids=True)
|
||||
context, context.project_id, subtree_as_ids=True,
|
||||
is_admin_project=context.is_admin)
|
||||
self._authorize_update_or_delete(context_project,
|
||||
target_project.id,
|
||||
parent_id)
|
||||
|
@ -38,12 +38,14 @@ class GenericProjectInfo(object):
|
||||
def __init__(self, project_id, project_keystone_api_version,
|
||||
project_parent_id=None,
|
||||
project_subtree=None,
|
||||
project_parent_tree=None):
|
||||
project_parent_tree=None,
|
||||
is_admin_project=False):
|
||||
self.id = project_id
|
||||
self.keystone_api_version = project_keystone_api_version
|
||||
self.parent_id = project_parent_id
|
||||
self.subtree = project_subtree
|
||||
self.parents = project_parent_tree
|
||||
self.is_admin_project = is_admin_project
|
||||
|
||||
|
||||
def get_volume_type_reservation(ctxt, volume, type_id,
|
||||
@ -90,7 +92,7 @@ def _filter_domain_id_from_parents(domain_id, tree):
|
||||
|
||||
|
||||
def get_project_hierarchy(context, project_id, subtree_as_ids=False,
|
||||
parents_as_ids=False):
|
||||
parents_as_ids=False, is_admin_project=False):
|
||||
"""A Helper method to get the project hierarchy.
|
||||
|
||||
Along with hierarchical multitenancy in keystone API v3, projects can be
|
||||
@ -118,6 +120,8 @@ def get_project_hierarchy(context, project_id, subtree_as_ids=False,
|
||||
if parents_as_ids:
|
||||
generic_project.parents = _filter_domain_id_from_parents(
|
||||
project.domain_id, project.parents)
|
||||
|
||||
generic_project.is_admin_project = is_admin_project
|
||||
except exceptions.NotFound:
|
||||
msg = (_("Tenant ID: %s does not exist.") % project_id)
|
||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
|
@ -81,11 +81,13 @@ class QuotaSetsControllerTestBase(test.TestCase):
|
||||
|
||||
class FakeProject(object):
|
||||
|
||||
def __init__(self, id=fake.PROJECT_ID, parent_id=None):
|
||||
def __init__(self, id=fake.PROJECT_ID, parent_id=None,
|
||||
is_admin_project=False):
|
||||
self.id = id
|
||||
self.parent_id = parent_id
|
||||
self.subtree = None
|
||||
self.parents = None
|
||||
self.is_admin_project = is_admin_project
|
||||
|
||||
def setUp(self):
|
||||
super(QuotaSetsControllerTestBase, self).setUp()
|
||||
@ -149,7 +151,7 @@ class QuotaSetsControllerTestBase(test.TestCase):
|
||||
self.C.id: self.C, self.D.id: self.D}
|
||||
|
||||
def _get_project(self, context, id, subtree_as_ids=False,
|
||||
parents_as_ids=False):
|
||||
parents_as_ids=False, is_admin_project=False):
|
||||
return self.project_by_id.get(id, self.FakeProject())
|
||||
|
||||
def _create_fake_quota_usages(self, usage_map):
|
||||
@ -606,6 +608,15 @@ class QuotaSetsControllerNestedQuotasTest(QuotaSetsControllerTestBase):
|
||||
expected = make_subproject_body(tenant_id=self.D.id)
|
||||
self.assertDictMatch(expected, result)
|
||||
|
||||
def test_subproject_show_not_in_hierarchy_admin_context(self):
|
||||
E = self.FakeProject(id=uuid.uuid4().hex, parent_id=None,
|
||||
is_admin_project=True)
|
||||
self.project_by_id[E.id] = E
|
||||
self.req.environ['cinder.context'].project_id = E.id
|
||||
result = self.controller.show(self.req, self.B.id)
|
||||
expected = make_subproject_body(tenant_id=self.B.id)
|
||||
self.assertDictMatch(expected, result)
|
||||
|
||||
def test_subproject_show_target_project_equals_to_context_project(
|
||||
self):
|
||||
self.req.environ['cinder.context'].project_id = self.B.id
|
||||
@ -643,6 +654,27 @@ class QuotaSetsControllerNestedQuotasTest(QuotaSetsControllerTestBase):
|
||||
self.assertRaises(webob.exc.HTTPForbidden,
|
||||
self.controller.update, self.req, F.id, body)
|
||||
|
||||
def test_update_subproject_not_in_hierarchy_admin_context(self):
|
||||
E = self.FakeProject(id=uuid.uuid4().hex, parent_id=None,
|
||||
is_admin_project=True)
|
||||
self.project_by_id[E.id] = E
|
||||
self.req.environ['cinder.context'].project_id = E.id
|
||||
body = make_body(gigabytes=2000, snapshots=15,
|
||||
volumes=5, backups=5, tenant_id=None)
|
||||
# Update the project A quota, not in the project hierarchy
|
||||
# of E but it will be allowed because E is the cloud admin.
|
||||
result = self.controller.update(self.req, self.A.id, body)
|
||||
self.assertDictMatch(body, result)
|
||||
# Update the quota of B to be equal to its parent A.
|
||||
result = self.controller.update(self.req, self.B.id, body)
|
||||
self.assertDictMatch(body, result)
|
||||
# Remove the admin role from project E
|
||||
E.is_admin_project = False
|
||||
# Now updating the quota of B will fail, because it is not
|
||||
# a member of E's hierarchy and E is no longer a cloud admin.
|
||||
self.assertRaises(webob.exc.HTTPForbidden,
|
||||
self.controller.update, self.req, self.B.id, body)
|
||||
|
||||
def test_update_subproject(self):
|
||||
# Update the project A quota.
|
||||
self.req.environ['cinder.context'].project_id = self.A.id
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
fixes:
|
||||
- Projects with the admin role are now allowed to operate
|
||||
on the quotas of all other projects.
|
Loading…
Reference in New Issue
Block a user