Merge "Enable api v2 policy check"

This commit is contained in:
Zuul 2019-09-25 11:29:45 +00:00 committed by Gerrit Code Review
commit 8adf4b9955
6 changed files with 95 additions and 44 deletions

View File

@ -25,6 +25,7 @@ from cyborg.api.controllers import link
from cyborg.api.controllers import types from cyborg.api.controllers import types
from cyborg.api import expose from cyborg.api import expose
from cyborg.common import exception from cyborg.common import exception
from cyborg.common import policy
from cyborg import objects from cyborg import objects
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -107,7 +108,7 @@ class ARQsController(base.CyborgController):
except Exception: except Exception:
return None return None
# @policy.authorize_wsgi("cyborg:arq", "create", False) @policy.authorize_wsgi("cyborg:arq", "create", False)
@expose.expose(ARQCollection, body=types.jsontype, @expose.expose(ARQCollection, body=types.jsontype,
status_code=http_client.CREATED) status_code=http_client.CREATED)
def post(self, req): def post(self, req):
@ -159,7 +160,7 @@ class ARQsController(base.CyborgController):
LOG.info('[arqs] post returned: %s', ret) LOG.info('[arqs] post returned: %s', ret)
return ret return ret
# @policy.authorize_wsgi("cyborg:arq", "get_one") @policy.authorize_wsgi("cyborg:arq", "get_one")
@expose.expose(ARQ, wtypes.text) @expose.expose(ARQ, wtypes.text)
def get_one(self, uuid): def get_one(self, uuid):
"""Get a single ARQ by UUID.""" """Get a single ARQ by UUID."""
@ -167,7 +168,7 @@ class ARQsController(base.CyborgController):
extarq = objects.ExtARQ.get(context, uuid) extarq = objects.ExtARQ.get(context, uuid)
return ARQ.convert_with_links(extarq.arq) return ARQ.convert_with_links(extarq.arq)
# @policy.authorize_wsgi("cyborg:arq", "get_all") @policy.authorize_wsgi("cyborg:arq", "get_all", False)
@expose.expose(ARQCollection, wtypes.text, types.uuid) @expose.expose(ARQCollection, wtypes.text, types.uuid)
def get_all(self, bind_state=None, instance=None): def get_all(self, bind_state=None, instance=None):
"""Retrieve a list of arqs.""" """Retrieve a list of arqs."""
@ -203,7 +204,7 @@ class ARQsController(base.CyborgController):
LOG.info('[arqs:get_all] Returned: %s', ret) LOG.info('[arqs:get_all] Returned: %s', ret)
return ret return ret
# @policy.authorize_wsgi("cyborg:arq", "delete") @policy.authorize_wsgi("cyborg:arq", "delete")
@expose.expose(None, wtypes.text, wtypes.text, @expose.expose(None, wtypes.text, wtypes.text,
status_code=http_client.NO_CONTENT) status_code=http_client.NO_CONTENT)
def delete(self, arqs=None, instance=None): def delete(self, arqs=None, instance=None):
@ -260,7 +261,7 @@ class ARQsController(base.CyborgController):
raise exception.PatchError(reason=reason) raise exception.PatchError(reason=reason)
return valid_fields return valid_fields
# @policy.authorize_wsgi("cyborg:arq", "update") @policy.authorize_wsgi("cyborg:arq", "update", False)
@expose.expose(None, body=types.jsontype, @expose.expose(None, body=types.jsontype,
status_code=http_client.ACCEPTED) status_code=http_client.ACCEPTED)
def patch(self, patch_list): def patch(self, patch_list):

View File

@ -27,6 +27,7 @@ from cyborg.api.controllers import link
from cyborg.api.controllers import types from cyborg.api.controllers import types
from cyborg.api import expose from cyborg.api import expose
from cyborg.common import exception from cyborg.common import exception
from cyborg.common import policy
from cyborg import objects from cyborg import objects
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -84,7 +85,7 @@ class DeviceProfileCollection(object):
class DeviceProfilesController(base.CyborgController): class DeviceProfilesController(base.CyborgController):
"""REST controller for Device Profiles.""" """REST controller for Device Profiles."""
# @policy.authorize_wsgi("cyborg:device_profile", "create", False) @policy.authorize_wsgi("cyborg:device_profile", "create", False)
@expose.expose('json', body=types.jsontype, @expose.expose('json', body=types.jsontype,
status_code=http_client.CREATED) status_code=http_client.CREATED)
def post(self, req_devprof_list): def post(self, req_devprof_list):
@ -164,7 +165,7 @@ class DeviceProfilesController(base.CyborgController):
return api_obj_devprofs return api_obj_devprofs
# @policy.authorize_wsgi("cyborg:device_profile", "get_all") @policy.authorize_wsgi("cyborg:device_profile", "get_all", False)
@expose.expose('json', wtypes.text) @expose.expose('json', wtypes.text)
def get_all(self, name=None): def get_all(self, name=None):
"""Retrieve a list of device profiles.""" """Retrieve a list of device profiles."""
@ -181,7 +182,7 @@ class DeviceProfilesController(base.CyborgController):
return wsme.api.Response(ret, status_code=http_client.OK, return wsme.api.Response(ret, status_code=http_client.OK,
return_type=wsme.types.DictType) return_type=wsme.types.DictType)
# @policy.authorize_wsgi("cyborg:device_profile", "get_one") @policy.authorize_wsgi("cyborg:device_profile", "get_one")
@expose.expose('json', wtypes.text) @expose.expose('json', wtypes.text)
def get_one(self, uuid): def get_one(self, uuid):
"""Retrieve a single device profile by uuid.""" """Retrieve a single device profile by uuid."""
@ -202,14 +203,14 @@ class DeviceProfilesController(base.CyborgController):
return wsme.api.Response(ret, status_code=http_client.OK, return wsme.api.Response(ret, status_code=http_client.OK,
return_type=wsme.types.DictType) return_type=wsme.types.DictType)
# @policy.authorize_wsgi("cyborg:device_profile", "delete") @policy.authorize_wsgi("cyborg:device_profile", "delete")
@expose.expose(None, wtypes.text, status_code=http_client.NO_CONTENT) @expose.expose(None, wtypes.text, status_code=http_client.NO_CONTENT)
def delete(self, value): def delete(self, value):
"""Delete one or more device_profiles. """Delete one or more device_profiles.
URL: /device_profiles/{uuid} OR /device_profiles?value=foo,bar URL: /device_profiles/{uuid} OR /device_profiles?value=foo,bar
:param value: Tis should be one of these two: :param value: This should be one of these two:
- UUID of a device_profile. - UUID of a device_profile.
- Comma-delimited list of device profile names. - Comma-delimited list of device profile names.
""" """

View File

@ -73,40 +73,37 @@ default_policies = [
# All of these may be overridden by configuration, but we can # All of these may be overridden by configuration, but we can
# depend on their existence throughout the code. # depend on their existence throughout the code.
accelerator_policies = [ accelerator_request_policies = [
policy.RuleDefault('cyborg:accelerator:get', policy.RuleDefault('cyborg:arq:get_all',
'rule:default', 'rule:default',
description='Retrieve accelerator records'), description='Retrieve accelerator request records.'),
policy.RuleDefault('cyborg:accelerator:create', policy.RuleDefault('cyborg:arq:get_one',
'rule:default',
description='Get an accelerator request record.'),
policy.RuleDefault('cyborg:arq:create',
'rule:allow', 'rule:allow',
description='Create accelerator records'), description='Create accelerator request records.'),
policy.RuleDefault('cyborg:accelerator:delete', policy.RuleDefault('cyborg:arq:delete',
'rule:default', 'rule:default',
description='Delete accelerator records'), description='Delete accelerator request records.'),
policy.RuleDefault('cyborg:accelerator:update', policy.RuleDefault('cyborg:arq:update',
'rule:default', 'rule:default',
description='Update accelerator records'), description='Update accelerator request records.'),
] ]
deployable_policies = [ device_profile_policies = [
policy.RuleDefault('cyborg:deployable:get_one', policy.RuleDefault('cyborg:device_profile:get_all',
'rule:allow', 'rule:default',
description='Show deployable detail'), description='Retrieve device_profile records.'),
policy.RuleDefault('cyborg:deployable:get_all', policy.RuleDefault('cyborg:device_profile:get_one',
'rule:allow', 'rule:default',
description='Retrieve all deployable records'), description='Get a device_profile record.'),
policy.RuleDefault('cyborg:deployable:create', policy.RuleDefault('cyborg:device_profile:create',
'rule:admin_api', 'rule:is_admin',
description='Create deployable records'), description='Create device_profile records.'),
policy.RuleDefault('cyborg:deployable:delete', policy.RuleDefault('cyborg:device_profile:delete',
'rule:admin_api', 'rule:default',
description='Delete deployable records'), description='Delete device_profile records.'),
policy.RuleDefault('cyborg:deployable:update',
'rule:admin_api',
description='Update deployable records'),
policy.RuleDefault('cyborg:deployable:program',
'rule:allow',
description='Program deployable(FPGA) records'),
] ]
fpga_policies = [ fpga_policies = [
@ -124,9 +121,9 @@ fpga_policies = [
def list_policies(): def list_policies():
return default_policies \ return default_policies \
+ accelerator_policies \ + fpga_policies \
+ deployable_policies \ + accelerator_request_policies \
+ fpga_policies + device_profile_policies
@lockutils.synchronized('policy_enforcer', 'cyborg-') @lockutils.synchronized('policy_enforcer', 'cyborg-')

View File

@ -16,6 +16,7 @@
"""Base classes for API tests.""" """Base classes for API tests."""
from oslo_config import cfg from oslo_config import cfg
from oslo_context import context
import pecan import pecan
import pecan.testing import pecan.testing
@ -107,6 +108,10 @@ class BaseApiTest(base.DbTestCase):
headers=headers, extra_environ=extra_environ, headers=headers, extra_environ=extra_environ,
status=status, method="post") status=status, method="post")
def gen_context(self, value, **kwargs):
ct = context.RequestContext.from_dict(value, **kwargs)
return ct
def gen_headers(self, context, **kw): def gen_headers(self, context, **kw):
"""Generate a header for a simulated HTTP request to Pecan test app. """Generate a header for a simulated HTTP request to Pecan test app.
@ -119,6 +124,10 @@ class BaseApiTest(base.DbTestCase):
""" """
ct = context.to_dict() ct = context.to_dict()
ct.update(kw) ct.update(kw)
if ct.get("is_admin"):
role = "admin"
else:
role = "user"
headers = { headers = {
'X-User-Name': ct.get("user_name") or "user", 'X-User-Name': ct.get("user_name") or "user",
'X-User-Id': 'X-User-Id':
@ -131,9 +140,8 @@ class BaseApiTest(base.DbTestCase):
'X-User-Domain-Name': ct.get("domain_name") or "no_domain", 'X-User-Domain-Name': ct.get("domain_name") or "no_domain",
'X-Auth-Token': 'X-Auth-Token':
ct.get("auth_token") or "b9764005b8c145bf972634fb16a826e8", ct.get("auth_token") or "b9764005b8c145bf972634fb16a826e8",
'X-Roles': ct.get("roles") or "cyborg" 'X-Roles': ct.get("roles") or role
} }
return headers return headers
def get_json(self, path, expect_errors=False, headers=None, def get_json(self, path, expect_errors=False, headers=None,

View File

@ -111,3 +111,19 @@ class TestARQsController(v2_test.APITestV2):
args = '?' + "instance=" + instance args = '?' + "instance=" + instance
response = self.delete(url + args, headers=self.headers) response = self.delete(url + args, headers=self.headers)
self.assertEqual(http_client.NO_CONTENT, response.status_int) self.assertEqual(http_client.NO_CONTENT, response.status_int)
def test_delete_with_non_default(self):
value = {"is_admin": False, "roles": "user", "is_admin_project": False}
ct = self.gen_context(value)
headers = self.gen_headers(ct)
url = self.ARQ_URL
arq = self.fake_extarqs[0].arq
args = '?' + "arqs=" + str(arq['uuid'])
exc = None
try:
self.delete(url + args, headers=headers)
except Exception as e:
exc = e
# Cyborg does not raise different exception when policy check failed
# now, improve this case with assertRaises later.
self.assertIn("Bad response: 403 Forbidden", exc.args[0])

View File

@ -23,7 +23,6 @@ from cyborg.tests.unit import fake_device_profile
class TestDeviceProfileController(v2_test.APITestV2): class TestDeviceProfileController(v2_test.APITestV2):
DP_URL = '/device_profiles' DP_URL = '/device_profiles'
def setUp(self): def setUp(self):
@ -72,6 +71,20 @@ class TestDeviceProfileController(v2_test.APITestV2):
for in_dp, out_dp in zip(self.fake_dp_objs, out_dps): for in_dp, out_dp in zip(self.fake_dp_objs, out_dps):
self._validate_dp(in_dp, out_dp) self._validate_dp(in_dp, out_dp)
def test_create_with_non_admin(self):
value = {"is_admin": False, "roles": "user", "is_admin_project": False}
ct = self.gen_context(value)
headers = self.gen_headers(ct)
dp = [self.fake_dps[0]]
exc = None
try:
self.post_json(self.DP_URL, dp, headers=headers)
except Exception as e:
exc = e
# Cyborg does not raise different exception when policy check failed
# now, improve this case with assertRaises later.
self.assertIn("Bad response: 403 Forbidden", exc.args[0])
@mock.patch('cyborg.objects.DeviceProfile.create') @mock.patch('cyborg.objects.DeviceProfile.create')
def test_create(self, mock_obj_dp): def test_create(self, mock_obj_dp):
dp = [self.fake_dps[0]] dp = [self.fake_dps[0]]
@ -92,3 +105,18 @@ class TestDeviceProfileController(v2_test.APITestV2):
url = self.DP_URL + "?value=mydp" url = self.DP_URL + "?value=mydp"
response = self.delete(url, headers=self.headers) response = self.delete(url, headers=self.headers)
self.assertEqual(http_client.NO_CONTENT, response.status_int) self.assertEqual(http_client.NO_CONTENT, response.status_int)
def test_delete_with_non_default(self):
value = {"is_admin": False, "roles": "user", "is_admin_project": False}
ct = self.gen_context(value)
headers = self.gen_headers(ct)
dp = self.fake_dp_objs[0]
url = self.DP_URL + '/%s'
exc = None
try:
self.delete(url % dp['uuid'], headers=headers)
except Exception as e:
exc = e
# Cyborg does not raise different exception when policy check failed
# now, improve this case with assertRaises later.
self.assertIn("Bad response: 403 Forbidden", exc.args[0])