Merge "Allow admin project to operate on all quotas"
This commit is contained in:
commit
438b2552b4
@ -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)
|
||||
|
@ -80,11 +80,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()
|
||||
@ -148,7 +150,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):
|
||||
@ -616,6 +618,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
|
||||
@ -653,6 +664,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