Merge "Using oslo.policy for monasca-api"

This commit is contained in:
Zuul 2018-06-07 10:52:26 +00:00 committed by Gerrit Code Review
commit 378408a490
33 changed files with 1058 additions and 201 deletions

View File

@ -5,3 +5,7 @@ config-generator
To generate sample configuration file execute::
tox -e genconfig
To generate the sample policies execute::
tox -e genpolicy

View File

@ -6,3 +6,4 @@ summarize = True
namespace = monasca_api
namespace = oslo.log
namespace = oslo.db
namespace = oslo.policy

View File

@ -0,0 +1,4 @@
[DEFAULT]
output_file = etc/api-policy.yaml.sample
format = yaml
namespace = monasca_api

View File

@ -1,4 +1,5 @@
# Copyright 2016 FUJITSU LIMITED
# Copyright 2018 OP5 AB
#
# 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
@ -14,11 +15,16 @@
import falcon
from oslo_context import context
from monasca_common.policy import policy_engine as policy
from monasca_api.api.core import request_context
from monasca_api.common.repositories import constants
from monasca_api import policies
from monasca_api.v2.common import exceptions
policy.POLICIES = policies
_TENANT_ID_PARAM = 'tenant_id'
"""Name of the query-param pointing at project-id (tenant-id)"""
@ -33,7 +39,7 @@ class Request(falcon.Request):
def __init__(self, env, options=None):
super(Request, self).__init__(env, options)
self.context = context.RequestContext.from_environ(self.env)
self.context = request_context.RequestContext.from_environ(self.env)
@property
def project_id(self):
@ -105,5 +111,8 @@ class Request(falcon.Request):
else:
return constants.PAGE_LIMIT
def can(self, action, target=None):
return self.context.can(action, target)
def __repr__(self):
return '%s, context=%s' % (self.path, self.context)

View File

@ -0,0 +1,36 @@
# Copyright 2017 FUJITSU LIMITED
# Copyright 2018 OP5 AB
#
# 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 monasca_common.policy import policy_engine as policy
from oslo_context import context
from monasca_api import policies
policy.POLICIES = policies
class RequestContext(context.RequestContext):
"""RequestContext.
RequestContext is customized version of
:py:class:oslo_context.context.RequestContext.
"""
def can(self, action, target=None):
if target is None:
target = {'project_id': self.project_id,
'user_id': self.user_id}
return policy.authorize(self, action=action, target=target)

View File

@ -17,6 +17,10 @@
from oslo_config import cfg
security_opts = [
cfg.ListOpt('healthcheck_roles', default=['@'],
help='Roles that are allowed to check the health'),
cfg.ListOpt('versions_roles', default=['@'],
help='Roles that are allowed to check the versions'),
cfg.ListOpt('default_authorized_roles', default=['monasca-user'],
help='''
Roles that are allowed full access to the API

View File

@ -1,4 +1,5 @@
# Copyright 2017 FUJITSU LIMITED
# Copyright 2018 OP5 AB
#
# 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
@ -16,6 +17,7 @@ import sys
from oslo_config import cfg
from oslo_log import log
from oslo_policy import opts as policy_opts
from monasca_api import conf
from monasca_api import version
@ -57,6 +59,7 @@ def parse_args(argv=None, config_file=None):
product_name='monasca-api',
version=version.version_str)
conf.register_opts()
policy_opts.set_defaults(CONF)
_CONF_LOADED = True

View File

@ -1,4 +1,5 @@
# Copyright 2017 FUJITSU LIMITED
# Copyright 2018 OP5 AB
#
# 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
@ -39,6 +40,7 @@ class HealthChecks(healthcheck_api.HealthCheckApi):
res.cache_control = self.CACHE_CONTROL
def on_get(self, req, res):
helpers.validate_authorization(req, ['api:healthcheck'])
kafka_result = self._kafka_check.health_check()
alarms_db_result = self._alarm_db_check.health_check()
metrics_db_result = self._metrics_db_check.health_check()

View File

@ -0,0 +1,78 @@
# Copyright 2017 FUJITSU LIMITED
# Copyright 2018 OP5 AB
#
# 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.
import os
import pkgutil
from oslo_config import cfg
from oslo_log import log
from oslo_utils import importutils
from monasca_api.conf import security
LOG = log.getLogger(__name__)
_BASE_MOD_PATH = 'monasca_api.policies.'
CONF = cfg.CONF
def roles_list_to_check_str(roles_list):
converted_roles_list = ["role:" + role if role != '@' else role for role in roles_list]
return ' or '.join(converted_roles_list)
security.register_opts(CONF)
HEALTHCHECK_ROLES = roles_list_to_check_str(cfg.CONF.security.healthcheck_roles)
VERSIONS_ROLES = roles_list_to_check_str(cfg.CONF.security.versions_roles)
DEFAULT_AUTHORIZED_ROLES = roles_list_to_check_str(cfg.CONF.security.default_authorized_roles)
READ_ONLY_AUTHORIZED_ROLES = roles_list_to_check_str(cfg.CONF.security.read_only_authorized_roles)
AGENT_AUTHORIZED_ROLES = roles_list_to_check_str(cfg.CONF.security.agent_authorized_roles)
DELEGATE_AUTHORIZED_ROLES = roles_list_to_check_str(cfg.CONF.security.delegate_authorized_roles)
def load_policy_modules():
"""Load all modules that contain policies.
Method iterates over modules of :py:mod:`monasca_events_api.policies`
and imports only those that contain following methods:
- list_rules
"""
for modname in _list_module_names():
mod = importutils.import_module(_BASE_MOD_PATH + modname)
if hasattr(mod, 'list_rules'):
yield mod
def _list_module_names():
package_path = os.path.dirname(os.path.abspath(__file__))
for _, modname, ispkg in pkgutil.iter_modules(path=[package_path]):
if not (modname == "opts" and ispkg):
yield modname
def list_rules():
"""List all policy modules rules.
Goes through all policy modules and yields their rules
"""
all_rules = []
for mod in load_policy_modules():
rules = mod.list_rules()
all_rules.extend(rules)
return all_rules

View File

@ -0,0 +1,158 @@
# Copyright 2018 OP5 AB
#
# 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 oslo_policy import policy
from monasca_api.policies import DEFAULT_AUTHORIZED_ROLES
from monasca_api.policies import READ_ONLY_AUTHORIZED_ROLES
rules = [
policy.DocumentedRuleDefault(
name='api:alarms:definition:post',
check_str=DEFAULT_AUTHORIZED_ROLES,
description='Post alarm definition role',
operations=[
{
'path': '/v2.0/alarm-definitions/',
'method': 'POST'
}
]
),
policy.DocumentedRuleDefault(
name='api:alarms:definition:get',
check_str=DEFAULT_AUTHORIZED_ROLES + ' or ' + READ_ONLY_AUTHORIZED_ROLES,
description='Get alarm definition role',
operations=[
{
'path': '/v2.0/alarm-definitions/{alarm_definition_id}',
'method': 'GET'
},
{
'path': '/v2.0/alarm-definitions',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name='api:alarms:definition:put',
check_str=DEFAULT_AUTHORIZED_ROLES,
description='Put alarm definition role',
operations=[
{
'path': '/v2.0/alarm-definitions/{alarm_definition_id}',
'method': 'PUT'
},
]
),
policy.DocumentedRuleDefault(
name='api:alarms:definition:patch',
check_str=DEFAULT_AUTHORIZED_ROLES,
description='Patch alarm definition role',
operations=[
{
'path': '/v2.0/alarm-definitions/{alarm_definition_id}',
'method': 'PATCH'
},
]
),
policy.DocumentedRuleDefault(
name='api:alarms:definition:delete',
check_str=DEFAULT_AUTHORIZED_ROLES,
description='Delete alarm definition role',
operations=[
{
'path': '/v2.0/alarm-definitions/{alarm_definition_id}',
'method': 'DELETE'
},
]
),
policy.DocumentedRuleDefault(
name='api:alarms:put',
check_str=DEFAULT_AUTHORIZED_ROLES,
description='Put alarm role',
operations=[
{
'path': '/v2.0/alarms/{alarm_id}',
'method': 'PUT'
},
]
),
policy.DocumentedRuleDefault(
name='api:alarms:patch',
check_str=DEFAULT_AUTHORIZED_ROLES,
description='Patch alarm role',
operations=[
{
'path': '/v2.0/alarms/{alarm_id}',
'method': 'PATCH'
},
]
),
policy.DocumentedRuleDefault(
name='api:alarms:delete',
check_str=DEFAULT_AUTHORIZED_ROLES,
description='Delete alarm role',
operations=[
{
'path': '/v2.0/alarms/{alarm_id}',
'method': 'DELETE'
},
]
),
policy.DocumentedRuleDefault(
name='api:alarms:get',
check_str=DEFAULT_AUTHORIZED_ROLES + ' or ' + READ_ONLY_AUTHORIZED_ROLES,
description='Get alarm role',
operations=[
{
'path': '/v2.0/alarms/',
'method': 'GET'
},
{
'path': '/v2.0/alarms/{alarm_id}',
'method': 'GET'
},
]
),
policy.DocumentedRuleDefault(
name='api:alarms:count',
check_str=DEFAULT_AUTHORIZED_ROLES + ' or ' + READ_ONLY_AUTHORIZED_ROLES,
description='Count alarm role',
operations=[
{
'path': '/v2.0/alarms/count/',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name='api:alarms:state_history',
check_str=DEFAULT_AUTHORIZED_ROLES + ' or ' + READ_ONLY_AUTHORIZED_ROLES,
description='Alarm state history role',
operations=[
{
'path': '/v2.0/alarms/state-history',
'method': 'GET'
},
{
'path': '/v2.0/alarms/{alarm_id}/state-history',
'method': 'GET'
}
]
)
]
def list_rules():
return rules

View File

@ -0,0 +1,31 @@
# Copyright 2018 OP5 AB
#
# 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 oslo_policy import policy
from monasca_api.policies import DELEGATE_AUTHORIZED_ROLES
rules = [
policy.RuleDefault(
name='api:delegate',
check_str=DELEGATE_AUTHORIZED_ROLES,
description='The rules that allowes to access the API on'
' behalf of another tenant role',
)
]
def list_rules():
return rules

View File

@ -0,0 +1,32 @@
# Copyright 2018 OP5 AB
#
# 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 oslo_policy import policy
from monasca_api.policies import HEALTHCHECK_ROLES
rules = [
policy.DocumentedRuleDefault(
name='api:healthcheck',
check_str=HEALTHCHECK_ROLES,
description='Healthcheck role',
operations=[
{'path': '/healthcheck', 'method': 'GET'}
]
),
]
def list_rules():
return rules

View File

@ -0,0 +1,62 @@
# Copyright 2018 OP5 AB
#
# 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 oslo_policy import policy
from monasca_api.policies import AGENT_AUTHORIZED_ROLES
from monasca_api.policies import DEFAULT_AUTHORIZED_ROLES
from monasca_api.policies import READ_ONLY_AUTHORIZED_ROLES
rules = [
policy.DocumentedRuleDefault(
name='api:metrics:get',
check_str=DEFAULT_AUTHORIZED_ROLES + ' or ' + READ_ONLY_AUTHORIZED_ROLES,
description='Get metrics role',
operations=[
{'path': '/v2.0/metrics', 'method': 'GET'},
{'path': '/v2.0/metrics/measurements', 'method': 'GET'},
{'path': '/v2.0/metrics/statistics', 'method': 'GET'},
{'path': '/v2.0/metrics/names', 'method': 'GET'}
]
),
policy.DocumentedRuleDefault(
name='api:metrics:post',
check_str=DEFAULT_AUTHORIZED_ROLES + ' or ' + AGENT_AUTHORIZED_ROLES,
description='Post metrics role',
operations=[
{'path': '/v2.0/metrics', 'method': 'POST'}
]
),
policy.DocumentedRuleDefault(
name='api:metrics:dimension:values',
check_str=DEFAULT_AUTHORIZED_ROLES + ' or ' + READ_ONLY_AUTHORIZED_ROLES,
description='Get metrics dimension values role',
operations=[
{'path': '/v2.0/metrics/dimensions/names/values', 'method': 'GET'}
]
),
policy.DocumentedRuleDefault(
name='api:metrics:dimension:names',
check_str=DEFAULT_AUTHORIZED_ROLES + ' or ' + READ_ONLY_AUTHORIZED_ROLES,
description='Get metrics dimension names role',
operations=[
{'path': '/v2.0/metrics/dimensions/names', 'method': 'GET'}
]
),
]
def list_rules():
return rules

View File

@ -0,0 +1,96 @@
# Copyright 2018 OP5 AB
#
# 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 oslo_policy import policy
from monasca_api.policies import DEFAULT_AUTHORIZED_ROLES
from monasca_api.policies import READ_ONLY_AUTHORIZED_ROLES
rules = [
policy.DocumentedRuleDefault(
name='api:notifications:put',
check_str=DEFAULT_AUTHORIZED_ROLES,
description='Put notifications role',
operations=[
{
'path': '/v2.0/notification-methods/{notification_method_id}',
'method': 'PUT'
},
]
),
policy.DocumentedRuleDefault(
name='api:notifications:patch',
check_str=DEFAULT_AUTHORIZED_ROLES,
description='Patch notifications role',
operations=[
{
'path': '/v2.0/notification-methods/{notification_method_id}',
'method': 'PATCH'
},
]
),
policy.DocumentedRuleDefault(
name='api:notifications:delete',
check_str=DEFAULT_AUTHORIZED_ROLES,
description='Delete notifications role',
operations=[
{
'path': '/v2.0/notification-methods/{notification_method_id}',
'method': 'DELETE'
},
]
),
policy.DocumentedRuleDefault(
name='api:notifications:get',
check_str=DEFAULT_AUTHORIZED_ROLES + ' or ' + READ_ONLY_AUTHORIZED_ROLES,
description='Get notifications role',
operations=[
{
'path': '/v2.0/notification-methods',
'method': 'GET'
},
{
'path': '/v2.0/notification-methods/{notification_method_id}',
'method': 'GET'
},
]
),
policy.DocumentedRuleDefault(
name='api:notifications:post',
check_str=DEFAULT_AUTHORIZED_ROLES,
description='Post notifications role',
operations=[
{
'path': '/v2.0/notification-methods',
'method': 'POST'
}
]
),
policy.DocumentedRuleDefault(
name='api:notifications:type',
check_str=DEFAULT_AUTHORIZED_ROLES + ' or ' + READ_ONLY_AUTHORIZED_ROLES,
description='Get notifications type role',
operations=[
{
'path': '/v2.0/notification-methods/types',
'method': 'GET'
}
]
)
]
def list_rules():
return rules

View File

@ -0,0 +1,33 @@
# Copyright 2018 OP5 AB
#
# 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 oslo_policy import policy
from monasca_api.policies import VERSIONS_ROLES
rules = [
policy.DocumentedRuleDefault(
name='api:versions',
check_str=VERSIONS_ROLES,
description='Get versions role',
operations=[
{'path': '/', 'method': 'GET'},
{'path': '/v2.0', 'method': 'GET'}
]
),
]
def list_rules():
return rules

View File

@ -1,5 +1,6 @@
# Copyright 2015 kornicameister@gmail.com
# Copyright 2015-2017 FUJITSU LIMITED
# Copyright 2018 OP5 AB
#
# 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
@ -12,17 +13,25 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import falcon
from falcon import testing
import fixtures
from monasca_common.policy import policy_engine as policy
from oslo_config import cfg
from oslo_config import fixture as oo_cfg
from oslo_context import fixture as oo_ctx
from oslo_serialization import jsonutils
from oslotest import base as oslotest_base
from monasca_api.api.core import request
from monasca_api import conf
from monasca_api import config
from monasca_api import policies
policy.POLICIES = policies
class MockedAPI(falcon.API):
@ -70,6 +79,7 @@ class BaseTestCase(oslotest_base.BaseTestCase):
super(BaseTestCase, self).setUp()
self.useFixture(ConfigFixture())
self.useFixture(oo_ctx.ClearRequestContext())
self.useFixture(PolicyFixture())
@staticmethod
def conf_override(**kw):
@ -95,3 +105,37 @@ class BaseApiTestCase(BaseTestCase, testing.TestBase):
*args,
**kwargs
)
class PolicyFixture(fixtures.Fixture):
"""Override the policy with a completely new policy file.
This overrides the policy with a completely fake and synthetic
policy file.
"""
def setUp(self):
super(PolicyFixture, self).setUp()
self._prepare_policy()
policy.reset()
policy.init()
def _prepare_policy(self):
policy_dir = self.useFixture(fixtures.TempDir())
policy_file = os.path.join(policy_dir.path, 'policy.yaml')
# load the fake_policy data and add the missing default rules.
policy_rules = jsonutils.loads('{}')
self.add_missing_default_rules(policy_rules)
with open(policy_file, 'w') as f:
jsonutils.dump(policy_rules, f)
BaseTestCase.conf_override(policy_file=policy_file,
group='oslo_policy')
BaseTestCase.conf_override(policy_dirs=[], group='oslo_policy')
@staticmethod
def add_missing_default_rules(rules):
for rule in policies.list_rules():
if rule.name not in rules:
rules[rule.name] = rule.check_str

View File

@ -193,7 +193,6 @@ class TestAlarmsStateHistory(AlarmTestBase):
'X-Roles': CONF.security.default_authorized_roles[0],
'X-Tenant-Id': TENANT_ID,
})
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertThat(response, RESTResponseEquals(expected_elements))

View File

@ -0,0 +1,98 @@
# Copyright 2018 OP5 AB
#
# 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 falcon import testing
from monasca_common.policy import policy_engine as policy
from oslo_policy import policy as os_policy
from monasca_api.api.core import request
from monasca_api.tests import base
import monasca_api.v2.reference.helpers as helpers
class TestGetXTenantOrTenantId(base.BaseApiTestCase):
def setUp(self):
super(TestGetXTenantOrTenantId, self).setUp()
rules = [
os_policy.RuleDefault("example:allowed", "@"),
os_policy.RuleDefault("example:denied", "!"),
os_policy.RuleDefault("example:authorized",
"role:role_1 or role:role_2")
]
policy.reset()
policy.init()
policy._ENFORCER.register_defaults(rules)
def test_return_tenant_id_on_authorized_roles(self):
for role in ['role_1', 'role_2']:
req_context = self._get_request_context(role)
self.assertEqual(
'fake_tenant_id',
helpers.get_x_tenant_or_tenant_id(
req_context, ['example:authorized']
)
)
def test_return_tenant_id_on_allowed_rules(self):
req_context = self._get_request_context()
self.assertEqual(
'fake_tenant_id',
helpers.get_x_tenant_or_tenant_id(
req_context,
['example:allowed']
)
)
def test_return_project_id_on_unauthorized_role(self):
req_context = self._get_request_context()
self.assertEqual('fake_project_id',
helpers.get_x_tenant_or_tenant_id(
req_context,
['example:authorized']))
def test_return_project_id_on_denied_rules(self):
req_context = self._get_request_context()
self.assertEqual(
'fake_project_id',
helpers.get_x_tenant_or_tenant_id(
req_context,
['example:denied']
)
)
def test_return_project_id_on_unavailable_tenant_id(self):
req_context = self._get_request_context()
req_context.query_string = ''
self.assertEqual(
'fake_project_id',
helpers.get_x_tenant_or_tenant_id(
req_context,
['example:allowed']
)
)
@staticmethod
def _get_request_context(role='fake_role'):
return request.Request(
testing.create_environ(
path="/",
query_string="tenant_id=fake_tenant_id",
headers={
"X_PROJECT_ID": "fake_project_id",
"X_ROLES": role
}
)
)

View File

@ -0,0 +1,254 @@
# Copyright 2016-2017 FUJITSU LIMITED
# Copyright 2018 OP5 AB
#
# 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 falcon import testing
from monasca_common.policy import policy_engine as policy
from oslo_context import context
from oslo_policy import policy as os_policy
from monasca_api.api.core import request
from monasca_api.policies import roles_list_to_check_str
from monasca_api.tests import base
class TestPolicyFileCase(base.BaseTestCase):
def setUp(self):
super(TestPolicyFileCase, self).setUp()
self.context = context.RequestContext(user='fake',
tenant='fake',
roles=['fake'])
self.target = {'tenant_id': 'fake'}
def test_modified_policy_reloads(self):
tmp_file = \
self.create_tempfiles(files=[('policies', '{}')], ext='.yaml')[0]
base.BaseTestCase.conf_override(policy_file=tmp_file,
group='oslo_policy')
policy.reset()
policy.init()
action = 'example:test'
rule = os_policy.RuleDefault(action, '')
policy._ENFORCER.register_defaults([rule])
with open(tmp_file, 'w') as policy_file:
policy_file.write('{"example:test": ""}')
policy.authorize(self.context, action, self.target)
with open(tmp_file, 'w') as policy_file:
policy_file.write('{"example:test": "!"}')
policy._ENFORCER.load_rules(True)
self.assertRaises(os_policy.PolicyNotAuthorized, policy.authorize,
self.context, action, self.target)
class TestPolicyCase(base.BaseTestCase):
def setUp(self):
super(TestPolicyCase, self).setUp()
rules = [
os_policy.RuleDefault("true", "@"),
os_policy.RuleDefault("example:allowed", "@"),
os_policy.RuleDefault("example:denied", "!"),
os_policy.RuleDefault("example:lowercase_monasca_user",
"role:monasca_user or role:sysadmin"),
os_policy.RuleDefault("example:uppercase_monasca_user",
"role:MONASCA_USER or role:sysadmin"),
]
policy.reset()
policy.init()
policy._ENFORCER.register_defaults(rules)
def test_authorize_nonexist_action_throws(self):
action = "example:noexist"
ctx = request.Request(
testing.create_environ(
path="/",
headers={
"X_USER_ID": "fake",
"X_PROJECT_ID": "fake",
"X_ROLES": "member"
}
)
)
self.assertRaises(os_policy.PolicyNotRegistered, policy.authorize,
ctx.context, action, {})
def test_authorize_bad_action_throws(self):
action = "example:denied"
ctx = request.Request(
testing.create_environ(
path="/",
headers={
"X_USER_ID": "fake",
"X_PROJECT_ID": "fake",
"X_ROLES": "member"
}
)
)
self.assertRaises(os_policy.PolicyNotAuthorized, policy.authorize,
ctx.context, action, {})
def test_authorize_bad_action_no_exception(self):
action = "example:denied"
ctx = request.Request(
testing.create_environ(
path="/",
headers={
"X_USER_ID": "fake",
"X_PROJECT_ID": "fake",
"X_ROLES": "member"
}
)
)
result = policy.authorize(ctx.context, action, {}, False)
self.assertFalse(result)
def test_authorize_good_action(self):
action = "example:allowed"
ctx = request.Request(
testing.create_environ(
path="/",
headers={
"X_USER_ID": "fake",
"X_PROJECT_ID": "fake",
"X_ROLES": "member"
}
)
)
result = policy.authorize(ctx.context, action, {}, False)
self.assertTrue(result)
def test_ignore_case_role_check(self):
lowercase_action = "example:lowercase_monasca_user"
uppercase_action = "example:uppercase_monasca_user"
monasca_user_context = request.Request(
testing.create_environ(
path="/",
headers={
"X_USER_ID": "monasca_user",
"X_PROJECT_ID": "fake",
"X_ROLES": "MONASCA_user"
}
)
)
self.assertTrue(policy.authorize(monasca_user_context.context,
lowercase_action,
{}))
self.assertTrue(policy.authorize(monasca_user_context.context,
uppercase_action,
{}))
class RegisteredPoliciesTestCase(base.BaseTestCase):
def __init__(self, *args, **kwds):
super(RegisteredPoliciesTestCase, self).__init__(*args, **kwds)
self.agent_roles = ['agent']
self.readonly_roles = ['monasca-read-only-user']
self.default_roles = ['monasca-user']
self.delegate_roles = ['admin']
def test_alarms_policies_roles(self):
alarms_policies = {
'api:alarms:definition:post': self.default_roles,
'api:alarms:definition:get':
self.default_roles + self.readonly_roles,
'api:alarms:definition:put': self.default_roles,
'api:alarms:definition:patch': self.default_roles,
'api:alarms:definition:delete': self.default_roles,
'api:alarms:put': self.default_roles,
'api:alarms:patch': self.default_roles,
'api:alarms:delete': self.default_roles,
'api:alarms:get': self.default_roles + self.readonly_roles,
'api:alarms:count': self.default_roles + self.readonly_roles,
'api:alarms:state_history': self.default_roles + self.readonly_roles
}
self._assert_rules(alarms_policies)
def test_metrics_policies_roles(self):
metrics_policies = {
'api:metrics:get': self.default_roles + self.readonly_roles,
'api:metrics:post': self.agent_roles + self.default_roles,
'api:metrics:dimension:values':
self.default_roles + self.readonly_roles,
'api:metrics:dimension:names':
self.default_roles + self.readonly_roles
}
self._assert_rules(metrics_policies)
def test_notifications_policies_roles(self):
notifications_policies = {
'api:notifications:put': self.default_roles,
'api:notifications:patch': self.default_roles,
'api:notifications:delete': self.default_roles,
'api:notifications:get': self.default_roles + self.readonly_roles,
'api:notifications:post': self.default_roles,
'api:notifications:type': self.default_roles + self.readonly_roles,
}
self._assert_rules(notifications_policies)
def test_versions_policies_roles(self):
versions_policies = {
'api:versions': ['any_rule!']
}
self._assert_rules(versions_policies)
def test_healthcheck_policies_roles(self):
healthcheck_policies = {
'api:healthcheck': ['any_rule!']
}
self._assert_rules(healthcheck_policies)
def test_delegate_policies_roles(self):
delegate_policies = {
'api:delegate': self.delegate_roles
}
self._assert_rules(delegate_policies)
def _assert_rules(self, policies_list):
for policy_name in policies_list:
registered_rule = policy.get_rules()[policy_name]
if hasattr(registered_rule, 'rules'):
self.assertEqual(len(registered_rule.rules),
len(policies_list[policy_name]))
for role in policies_list[policy_name]:
ctx = self._get_request_context(role)
self.assertTrue(policy.authorize(ctx.context,
policy_name,
{})
)
@staticmethod
def _get_request_context(role):
return request.Request(
testing.create_environ(
path='/',
headers={'X_ROLES': role}
)
)
class PolicyUtilsTestCase(base.BaseTestCase):
def test_roles_list_to_check_str(self):
self.assertEqual(roles_list_to_check_str(['test_role']), 'role:test_role')
self.assertEqual(roles_list_to_check_str(['role1', 'role2', 'role3']),
'role:role1 or role:role2 or role:role3')
self.assertEqual(roles_list_to_check_str(['@']), '@')
self.assertEqual(roles_list_to_check_str(['role1', '@', 'role2']),
'role:role1 or @ or role:role2')

View File

@ -1,4 +1,5 @@
# Copyright 2016-2017 FUJITSU LIMITED
# Copyright 2018 OP5 AB
#
# 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
@ -20,6 +21,8 @@ from monasca_api.v2.common import exceptions
class TestRequest(base.BaseApiTestCase):
def setUp(self):
super(TestRequest, self).setUp()
def test_use_context_from_request(self):
req = request.Request(

View File

@ -26,6 +26,11 @@ import monasca_api.v2.common.validation as validation
import monasca_api.v2.reference.helpers as helpers
def mock_req_can(authorised_rule):
if authorised_rule != 'authorized':
raise Exception
class TestStateValidation(base.BaseTestCase):
VALID_STATES = "OK", "ALARM", "UNDETERMINED"
@ -71,49 +76,20 @@ class TestSeverityValidation(base.BaseTestCase):
'|'.join([self.VALID_SEVERITIES[0], 'BOGUS']))
class TestRoleValidation(base.BaseTestCase):
def test_role_valid(self):
req_roles = 'role0', 'rOlE1'
authorized_roles = ['RolE1', 'Role2']
class TestRuleValidation(base.BaseApiTestCase):
def test_rule_valid(self):
req = mock.Mock()
req.roles = req_roles
helpers.validate_authorization(req, authorized_roles)
def test_role_invalid(self):
req_roles = 'role2', 'role3'
authorized_roles = ['role0', 'role1']
req.can = mock_req_can
test_rules = ['Rule1', 'authorized']
helpers.validate_authorization(req, test_rules)
def test_rule_invalid(self):
req = mock.Mock()
req.roles = req_roles
req.can = mock_req_can
test_rules = ['rule1', 'rule2']
self.assertRaises(
falcon.HTTPUnauthorized,
helpers.validate_authorization, req, authorized_roles)
def test_empty_role_header(self):
req_roles = []
authorized_roles = ['Role1', 'Role2']
req = mock.Mock()
req.roles = req_roles
self.assertRaises(
falcon.HTTPUnauthorized,
helpers.validate_authorization, req, authorized_roles)
def test_no_role_header(self):
req_roles = None
authorized_roles = ['Role1', 'Role2']
req = mock.Mock()
req.roles = req_roles
self.assertRaises(
falcon.HTTPUnauthorized,
helpers.validate_authorization, req, authorized_roles)
helpers.validate_authorization, req, test_rules)
class TestTimestampsValidation(base.BaseTestCase):

View File

@ -1,4 +1,5 @@
# (C) Copyright 2014-2017 Hewlett Packard Enterprise Development LP
# Copyright 2018 OP5 AB
#
# 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
@ -45,11 +46,6 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
try:
super(AlarmDefinitions, self).__init__()
self._region = cfg.CONF.region
self._default_authorized_roles = (
cfg.CONF.security.default_authorized_roles)
self._get_alarmdefs_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_authorized_roles)
self._alarm_definitions_repo = simport.load(
cfg.CONF.repositories.alarm_definitions_driver)()
@ -59,7 +55,7 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
@resource.resource_try_catch_block
def on_post(self, req, res):
helpers.validate_authorization(req, self._default_authorized_roles)
helpers.validate_authorization(req, ['api:alarms:definition:post'])
alarm_definition = helpers.from_json(req)
@ -88,8 +84,8 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
@resource.resource_try_catch_block
def on_get(self, req, res, alarm_definition_id=None):
helpers.validate_authorization(req, ['api:alarms:definition:get'])
if alarm_definition_id is None:
helpers.validate_authorization(req, self._get_alarmdefs_authorized_roles)
name = helpers.get_query_name(req)
dimensions = helpers.get_query_dimensions(req)
severity = helpers.get_query_param(req, "severity", default_val=None)
@ -119,20 +115,16 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
req.uri, sort_by,
offset, req.limit)
res.body = helpers.to_json(result)
res.status = falcon.HTTP_200
else:
helpers.validate_authorization(req, self._get_alarmdefs_authorized_roles)
result = self._alarm_definition_show(req.project_id,
alarm_definition_id)
helpers.add_links_to_resource(result,
re.sub('/' + alarm_definition_id, '',
req.uri))
res.body = helpers.to_json(result)
res.status = falcon.HTTP_200
res.body = helpers.to_json(result)
res.status = falcon.HTTP_200
@resource.resource_try_catch_block
def on_put(self, req, res, alarm_definition_id=None):
@ -140,7 +132,7 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
if not alarm_definition_id:
raise HTTPBadRequestError('Bad Request', 'Alarm definition ID not provided')
helpers.validate_authorization(req, self._default_authorized_roles)
helpers.validate_authorization(req, ['api:alarms:definition:put'])
alarm_definition = helpers.from_json(req)
@ -182,7 +174,7 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
if not alarm_definition_id:
raise HTTPBadRequestError('Bad Request', 'Alarm definition ID not provided')
helpers.validate_authorization(req, self._default_authorized_roles)
helpers.validate_authorization(req, ['api:alarms:definition:patch'])
alarm_definition = helpers.from_json(req)
@ -231,7 +223,7 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
if not alarm_definition_id:
raise HTTPBadRequestError('Bad Request', 'Alarm definition ID not provided')
helpers.validate_authorization(req, self._default_authorized_roles)
helpers.validate_authorization(req, ['api:alarms:definition:delete'])
self._alarm_definition_delete(req.project_id, alarm_definition_id)
res.status = falcon.HTTP_204

View File

@ -1,4 +1,5 @@
# Copyright 2014-2017 Hewlett Packard Enterprise Development LP
# Copyright 2018 OP5 AB
#
# 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
@ -38,11 +39,6 @@ class Alarms(alarms_api_v2.AlarmsV2API,
try:
super(Alarms, self).__init__()
self._region = cfg.CONF.region
self._default_authorized_roles = (
cfg.CONF.security.default_authorized_roles)
self._get_alarms_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_authorized_roles)
self._alarms_repo = simport.load(
cfg.CONF.repositories.alarms_driver)()
@ -53,7 +49,7 @@ class Alarms(alarms_api_v2.AlarmsV2API,
@resource.resource_try_catch_block
def on_put(self, req, res, alarm_id):
helpers.validate_authorization(req, self._default_authorized_roles)
helpers.validate_authorization(req, ['api:alarms:put'])
alarm = helpers.from_json(req)
schema_alarm.validate(alarm)
@ -80,7 +76,7 @@ class Alarms(alarms_api_v2.AlarmsV2API,
@resource.resource_try_catch_block
def on_patch(self, req, res, alarm_id):
helpers.validate_authorization(req, self._default_authorized_roles)
helpers.validate_authorization(req, ['api:alarms:patch'])
alarm = helpers.from_json(req)
schema_alarm.validate(alarm)
@ -106,7 +102,7 @@ class Alarms(alarms_api_v2.AlarmsV2API,
@resource.resource_try_catch_block
def on_delete(self, req, res, alarm_id):
helpers.validate_authorization(req, self._default_authorized_roles)
helpers.validate_authorization(req, ['api:alarms:delete'])
self._alarm_delete(req.project_id, alarm_id)
@ -114,7 +110,7 @@ class Alarms(alarms_api_v2.AlarmsV2API,
@resource.resource_try_catch_block
def on_get(self, req, res, alarm_id=None):
helpers.validate_authorization(req, self._get_alarms_authorized_roles)
helpers.validate_authorization(req, ['api:alarms:get'])
if alarm_id is None:
query_parms = falcon.uri.parse_query_string(req.query_string)
@ -359,9 +355,6 @@ class AlarmsCount(alarms_api_v2.AlarmsCountV2API, alarming.Alarming):
try:
super(AlarmsCount, self).__init__()
self._region = cfg.CONF.region
self._get_alarms_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_authorized_roles)
self._alarms_repo = simport.load(
cfg.CONF.repositories.alarms_driver)()
@ -371,7 +364,7 @@ class AlarmsCount(alarms_api_v2.AlarmsCountV2API, alarming.Alarming):
@resource.resource_try_catch_block
def on_get(self, req, res):
helpers.validate_authorization(req, self._get_alarms_authorized_roles)
helpers.validate_authorization(req, ['api:alarms:count'])
query_parms = falcon.uri.parse_query_string(req.query_string)
if 'state' in query_parms:
@ -464,9 +457,6 @@ class AlarmsStateHistory(alarms_api_v2.AlarmsStateHistoryV2API,
try:
super(AlarmsStateHistory, self).__init__()
self._region = cfg.CONF.region
self._get_alarms_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_authorized_roles)
self._alarms_repo = simport.load(
cfg.CONF.repositories.alarms_driver)()
self._metrics_repo = simport.load(
@ -478,12 +468,13 @@ class AlarmsStateHistory(alarms_api_v2.AlarmsStateHistoryV2API,
@resource.resource_try_catch_block
def on_get(self, req, res, alarm_id=None):
helpers.validate_authorization(req, ['api:alarms:state_history'])
offset = helpers.get_query_param(req, 'offset')
if alarm_id is None:
helpers.validate_authorization(req, self._get_alarms_authorized_roles)
start_timestamp = helpers.get_query_starttime_timestamp(req, False)
end_timestamp = helpers.get_query_endtime_timestamp(req, False)
offset = helpers.get_query_param(req, 'offset')
dimensions = helpers.get_query_dimensions(req)
helpers.validate_query_dimensions(dimensions)
@ -491,19 +482,13 @@ class AlarmsStateHistory(alarms_api_v2.AlarmsStateHistoryV2API,
end_timestamp, dimensions,
req.uri, offset, req.limit)
res.body = helpers.to_json(result)
res.status = falcon.HTTP_200
else:
helpers.validate_authorization(req, self._get_alarms_authorized_roles)
offset = helpers.get_query_param(req, 'offset')
result = self._alarm_history(req.project_id, alarm_id,
req.uri, offset,
req.limit)
res.body = helpers.to_json(result)
res.status = falcon.HTTP_200
res.body = helpers.to_json(result)
res.status = falcon.HTTP_200
def _alarm_history_list(self, tenant_id, start_timestamp,
end_timestamp, dimensions, req_uri, offset,

View File

@ -1,6 +1,7 @@
# Copyright 2015 Cray Inc. All Rights Reserved.
# (C) Copyright 2014,2016-2017 Hewlett Packard Enterprise Development LP
# (C) Copyright 2017 SUSE LLC
# Copyright 2018 OP5 AB
#
# 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
@ -69,53 +70,41 @@ def validate_json_content_type(req):
'application/json')
def validate_authorization(req, authorized_roles):
"""Validates whether one or more X-ROLES in the HTTP header is authorized.
def validate_authorization(http_request, authorized_rules_list):
"""Validates whether is authorized according to provided policy rules list.
If authorization fails, 401 is thrown with appropriate description.
Additionally response specifies 'WWW-Authenticate' header with 'Token'
value challenging the client to use different token (the one with
different set of roles).
:param req: HTTP request object. Must contain "X-ROLES" in the HTTP
request header.
:param authorized_roles: List of authorized roles to check against.
:raises falcon.HTTPUnauthorized
different set of roles which can access the service).
"""
roles = req.roles
challenge = 'Token'
if not roles:
raise falcon.HTTPUnauthorized('Forbidden',
'Tenant does not have any roles',
challenge)
roles = roles.split(',') if isinstance(roles, six.string_types) else roles
authorized_roles_lower = [r.lower() for r in authorized_roles]
for role in roles:
role = role.lower()
if role in authorized_roles_lower:
for rule in authorized_rules_list:
try:
http_request.can(rule)
return
except Exception as ex:
LOG.debug(ex)
raise falcon.HTTPUnauthorized('Forbidden',
'Tenant ID is missing a required role to '
'access this service',
'The request does not have access to this service',
challenge)
def get_x_tenant_or_tenant_id(req, delegate_authorized_roles):
"""Evaluates whether the tenant ID or cross tenant ID should be returned.
def get_x_tenant_or_tenant_id(http_request, delegate_authorized_rules_list):
params = falcon.uri.parse_query_string(http_request.query_string)
if 'tenant_id' in params:
tenant_id = params['tenant_id']
:param req: HTTP request object.
:param delegate_authorized_roles: List of authorized roles that have
delegate privileges.
for rule in delegate_authorized_rules_list:
try:
http_request.can(rule)
return tenant_id
except Exception as ex:
LOG.debug(ex)
:returns: Returns the cross tenant or tenant ID.
"""
if any(x in set(delegate_authorized_roles) for x in req.roles):
params = falcon.uri.parse_query_string(req.query_string)
if 'tenant_id' in params:
tenant_id = params['tenant_id']
return tenant_id
return req.project_id
return http_request.project_id
def get_query_param(req, param_name, required=False, default_val=None):

View File

@ -1,4 +1,5 @@
# (C) Copyright 2014-2017 Hewlett Packard Enterprise Development LP
# Copyright 2018 OP5 AB
#
# 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
@ -52,14 +53,6 @@ class Metrics(metrics_api_v2.MetricsV2API):
try:
super(Metrics, self).__init__()
self._region = cfg.CONF.region
self._delegate_authorized_roles = (
cfg.CONF.security.delegate_authorized_roles)
self._get_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_authorized_roles)
self._post_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.agent_authorized_roles)
self._message_queue = simport.load(cfg.CONF.messaging.driver)(
'metrics')
self._metrics_repo = simport.load(
@ -94,8 +87,7 @@ class Metrics(metrics_api_v2.MetricsV2API):
@resource.resource_try_catch_block
def on_post(self, req, res):
helpers.validate_json_content_type(req)
helpers.validate_authorization(req,
self._post_metrics_authorized_roles)
helpers.validate_authorization(req, ['api:metrics:post'])
metrics = helpers.from_json(req)
try:
metric_validation.validate(metrics)
@ -103,9 +95,7 @@ class Metrics(metrics_api_v2.MetricsV2API):
LOG.exception(ex)
raise HTTPUnprocessableEntityError("Unprocessable Entity", str(ex))
tenant_id = (
helpers.get_x_tenant_or_tenant_id(req,
self._delegate_authorized_roles))
tenant_id = helpers.get_x_tenant_or_tenant_id(req, ['api:delegate'])
transformed_metrics = metrics_message.transform(
metrics, tenant_id, self._region)
self._send_metrics(transformed_metrics)
@ -113,10 +103,8 @@ class Metrics(metrics_api_v2.MetricsV2API):
@resource.resource_try_catch_block
def on_get(self, req, res):
helpers.validate_authorization(req, self._get_metrics_authorized_roles)
tenant_id = (
helpers.get_x_tenant_or_tenant_id(req,
self._delegate_authorized_roles))
helpers.validate_authorization(req, ['api:metrics:get'])
tenant_id = helpers.get_x_tenant_or_tenant_id(req, ['api:delegate'])
name = helpers.get_query_name(req)
helpers.validate_query_name(name)
dimensions = helpers.get_query_dimensions(req)
@ -138,14 +126,6 @@ class MetricsMeasurements(metrics_api_v2.MetricsMeasurementsV2API):
try:
super(MetricsMeasurements, self).__init__()
self._region = cfg.CONF.region
self._delegate_authorized_roles = (
cfg.CONF.security.delegate_authorized_roles)
self._get_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_authorized_roles)
self._post_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.agent_authorized_roles)
self._metrics_repo = simport.load(
cfg.CONF.repositories.metrics_driver)()
@ -156,10 +136,8 @@ class MetricsMeasurements(metrics_api_v2.MetricsMeasurementsV2API):
@resource.resource_try_catch_block
def on_get(self, req, res):
helpers.validate_authorization(req, self._get_metrics_authorized_roles)
tenant_id = (
helpers.get_x_tenant_or_tenant_id(req,
self._delegate_authorized_roles))
helpers.validate_authorization(req, ['api:metrics:get'])
tenant_id = helpers.get_x_tenant_or_tenant_id(req, ['api:delegate'])
name = helpers.get_query_name(req, True)
helpers.validate_query_name(name)
dimensions = helpers.get_query_dimensions(req)
@ -203,11 +181,6 @@ class MetricsStatistics(metrics_api_v2.MetricsStatisticsV2API):
try:
super(MetricsStatistics, self).__init__()
self._region = cfg.CONF.region
self._delegate_authorized_roles = (
cfg.CONF.security.delegate_authorized_roles)
self._get_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_authorized_roles)
self._metrics_repo = simport.load(
cfg.CONF.repositories.metrics_driver)()
@ -218,10 +191,8 @@ class MetricsStatistics(metrics_api_v2.MetricsStatisticsV2API):
@resource.resource_try_catch_block
def on_get(self, req, res):
helpers.validate_authorization(req, self._get_metrics_authorized_roles)
tenant_id = (
helpers.get_x_tenant_or_tenant_id(req,
self._delegate_authorized_roles))
helpers.validate_authorization(req, ['api:metrics:get'])
tenant_id = helpers.get_x_tenant_or_tenant_id(req, ['api:delegate'])
name = helpers.get_query_name(req, True)
helpers.validate_query_name(name)
dimensions = helpers.get_query_dimensions(req)
@ -268,11 +239,6 @@ class MetricsNames(metrics_api_v2.MetricsNamesV2API):
try:
super(MetricsNames, self).__init__()
self._region = cfg.CONF.region
self._delegate_authorized_roles = (
cfg.CONF.security.delegate_authorized_roles)
self._get_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_authorized_roles)
self._metrics_repo = simport.load(
cfg.CONF.repositories.metrics_driver)()
@ -283,10 +249,8 @@ class MetricsNames(metrics_api_v2.MetricsNamesV2API):
@resource.resource_try_catch_block
def on_get(self, req, res):
helpers.validate_authorization(req, self._get_metrics_authorized_roles)
tenant_id = (
helpers.get_x_tenant_or_tenant_id(req,
self._delegate_authorized_roles))
helpers.validate_authorization(req, ['api:metrics:get'])
tenant_id = helpers.get_x_tenant_or_tenant_id(req, ['api:delegate'])
dimensions = helpers.get_query_dimensions(req)
helpers.validate_query_dimensions(dimensions)
offset = helpers.get_query_param(req, 'offset')
@ -310,11 +274,6 @@ class DimensionValues(metrics_api_v2.DimensionValuesV2API):
try:
super(DimensionValues, self).__init__()
self._region = cfg.CONF.region
self._delegate_authorized_roles = (
cfg.CONF.security.delegate_authorized_roles)
self._get_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_authorized_roles)
self._metrics_repo = simport.load(
cfg.CONF.repositories.metrics_driver)()
@ -324,10 +283,8 @@ class DimensionValues(metrics_api_v2.DimensionValuesV2API):
@resource.resource_try_catch_block
def on_get(self, req, res):
helpers.validate_authorization(req, self._get_metrics_authorized_roles)
tenant_id = (
helpers.get_x_tenant_or_tenant_id(req,
self._delegate_authorized_roles))
helpers.validate_authorization(req, ['api:metrics:dimension:values'])
tenant_id = helpers.get_x_tenant_or_tenant_id(req, ['api:delegate'])
metric_name = helpers.get_query_param(req, 'metric_name')
dimension_name = helpers.get_query_param(req, 'dimension_name',
required=True)
@ -353,11 +310,6 @@ class DimensionNames(metrics_api_v2.DimensionNamesV2API):
try:
super(DimensionNames, self).__init__()
self._region = cfg.CONF.region
self._delegate_authorized_roles = (
cfg.CONF.security.delegate_authorized_roles)
self._get_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_authorized_roles)
self._metrics_repo = simport.load(
cfg.CONF.repositories.metrics_driver)()
@ -368,10 +320,8 @@ class DimensionNames(metrics_api_v2.DimensionNamesV2API):
@resource.resource_try_catch_block
def on_get(self, req, res):
helpers.validate_authorization(req, self._get_metrics_authorized_roles)
tenant_id = (
helpers.get_x_tenant_or_tenant_id(req,
self._delegate_authorized_roles))
helpers.validate_authorization(req, ['api:metrics:dimension:names'])
tenant_id = helpers.get_x_tenant_or_tenant_id(req, ['api:delegate'])
metric_name = helpers.get_query_param(req, 'metric_name')
offset = helpers.get_query_param(req, 'offset')
result = self._dimension_names(tenant_id, req.uri, metric_name,

View File

@ -1,4 +1,5 @@
# (C) Copyright 2014-2017 Hewlett Packard Enterprise Development LP
# Copyright 2018 OP5 AB
#
# 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
@ -37,11 +38,6 @@ class Notifications(notifications_api_v2.NotificationsV2API):
super(Notifications, self).__init__()
self._region = cfg.CONF.region
self._default_authorized_roles = (
cfg.CONF.security.default_authorized_roles)
self._get_notifications_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_authorized_roles)
self._notifications_repo = simport.load(
cfg.CONF.repositories.notifications_driver)()
self._notification_method_type_repo = simport.load(
@ -205,7 +201,7 @@ class Notifications(notifications_api_v2.NotificationsV2API):
@resource.resource_try_catch_block
def on_post(self, req, res):
helpers.validate_json_content_type(req)
helpers.validate_authorization(req, self._default_authorized_roles)
helpers.validate_authorization(req, ['api:notifications:post'])
notification = helpers.from_json(req)
self._parse_and_validate_notification(notification)
result = self._create_notification(req.project_id, notification, req.uri)
@ -214,9 +210,8 @@ class Notifications(notifications_api_v2.NotificationsV2API):
@resource.resource_try_catch_block
def on_get(self, req, res, notification_method_id=None):
helpers.validate_authorization(req, ['api:notifications:get'])
if notification_method_id is None:
helpers.validate_authorization(req,
self._get_notifications_authorized_roles)
sort_by = helpers.get_query_param(req, 'sort_by', default_val=None)
if sort_by is not None:
if isinstance(sort_by, six.string_types):
@ -238,27 +233,26 @@ class Notifications(notifications_api_v2.NotificationsV2API):
result = self._list_notifications(req.project_id, req.uri, sort_by,
offset, req.limit)
res.body = helpers.to_json(result)
res.status = falcon.HTTP_200
else:
helpers.validate_authorization(req,
self._get_notifications_authorized_roles)
result = self._list_notification(req.project_id,
notification_method_id,
req.uri)
res.body = helpers.to_json(result)
res.status = falcon.HTTP_200
res.body = helpers.to_json(result)
res.status = falcon.HTTP_200
@resource.resource_try_catch_block
def on_delete(self, req, res, notification_method_id):
helpers.validate_authorization(req, self._default_authorized_roles)
helpers.validate_authorization(req, ['api:notifications:delete'])
self._delete_notification(req.project_id, notification_method_id)
res.status = falcon.HTTP_204
@resource.resource_try_catch_block
def on_put(self, req, res, notification_method_id):
helpers.validate_json_content_type(req)
helpers.validate_authorization(req, self._default_authorized_roles)
helpers.validate_authorization(req, ['api:notifications:put'])
notification = helpers.from_json(req)
self._parse_and_validate_notification(notification, require_all=True)
result = self._update_notification(notification_method_id, req.project_id,
@ -269,7 +263,7 @@ class Notifications(notifications_api_v2.NotificationsV2API):
@resource.resource_try_catch_block
def on_patch(self, req, res, notification_method_id):
helpers.validate_json_content_type(req)
helpers.validate_authorization(req, self._default_authorized_roles)
helpers.validate_authorization(req, ['api:notifications:patch'])
notification = helpers.from_json(req)
self._patch_get_notification(req.project_id, notification_method_id, notification)
self._parse_and_validate_notification(notification, require_all=True)

View File

@ -1,4 +1,5 @@
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
# Copyright 2018 OP5 AB
#
# 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
@ -34,7 +35,7 @@ class NotificationsType(notificationstype_api_v2.NotificationsTypeV2API):
@resource.resource_try_catch_block
def on_get(self, req, res):
helpers.validate_authorization(req, ['api:notifications:type'])
# This is to provide consistency. Pagination is not really supported here as there
# are not that many rows
result = self._list_notifications(req.uri, req.limit)

View File

@ -1,4 +1,5 @@
# Copyright 2016 Hewlett Packard Enterprise Development Company, L.P.
# Copyright 2018 OP5 AB
#
# 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
@ -20,6 +21,8 @@ class Version2(object):
super(Version2, self).__init__()
def on_get(self, req, res):
helpers.validate_authorization(req,
['api:versions'])
result = {
'id': 'v2.0',
'links': [{

View File

@ -1,4 +1,5 @@
# Copyright 2014 Hewlett-Packard
# Copyright 2018 OP5 AB
#
# 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
@ -38,6 +39,8 @@ class Versions(versions_api.VersionsAPI):
def on_get(self, req, res, version_id=None):
req_uri = req.uri.decode('utf8') if six.PY2 else req.uri
helpers.validate_authorization(req,
['api:versions'])
result = {
'links': [{
'rel': 'self',

View File

@ -0,0 +1,5 @@
---
features:
- |
Use of oslo mechanisms for defining and enforcing policy.
A command line entry point that allow the user to generate a sample policy file.

View File

@ -6,6 +6,7 @@ oslo.config>=5.2.0 # Apache-2.0
oslo.context>=2.19.2 # Apache-2.0
oslo.log>=3.36.0 # Apache-2.0
oslo.middleware>=3.31.0 # Apache-2.0
oslo.policy>=1.30.0 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0

View File

@ -39,6 +39,9 @@ console_scripts =
oslo.config.opts =
monasca_api = monasca_api.conf:list_opts
oslo.policy.policies =
monasca_api = monasca_api.policies:list_rules
[build_sphinx]
all_files = 1
build-dir = doc/build

View File

@ -117,6 +117,10 @@ commands =
description = Generates sample configuration file for monasca-api
commands = oslo-config-generator --config-file=config-generator/api-config.conf
[testenv:genpolicy]
description = Generates sample policy.json file for monasca-api
commands = oslopolicy-sample-generator --config-file=config-generator/policy.conf
[testenv:venv]
commands = {posargs}