Enforce a default policy
Provide a default policy which requires to be admin for every API call except rating/quote, report/total and storage/list_data_frames (which will require a bit more control to return only the current tenant data for non-admin users). Also provide a custom Context class to extract roles information from the API request. hashmap controllers now use a new base class which provides a custom _route method. This will avoid to define policies for core and external configuration APIs. Change-Id: Ie3feb4e926270b95ab813a5d24854d1df1758a5e
This commit is contained in:
parent
f072724991
commit
0f21a37d06
cloudkitty
api
common
rating
etc/cloudkitty
@ -15,9 +15,9 @@
|
||||
#
|
||||
# @author: Stéphane Albert
|
||||
#
|
||||
from oslo_context import context
|
||||
from pecan import hooks
|
||||
|
||||
from cloudkitty.common import context
|
||||
from cloudkitty.common import policy
|
||||
|
||||
|
||||
@ -49,6 +49,7 @@ class ContextHook(hooks.PecanHook):
|
||||
'tenant': headers.get('X-Tenant') or headers.get('X-Tenant-Id'),
|
||||
'auth_token': headers.get('X-Auth-Token'),
|
||||
'is_admin': is_admin,
|
||||
'roles': roles,
|
||||
}
|
||||
|
||||
state.request.context = context.RequestContext(**creds)
|
||||
|
@ -21,6 +21,7 @@ from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudkitty.api.v1.datamodels import collector as collector_models
|
||||
from cloudkitty.common import policy
|
||||
from cloudkitty.db import api as db_api
|
||||
|
||||
|
||||
@ -36,6 +37,7 @@ class MappingController(rest.RestController):
|
||||
|
||||
:return: List of every services mapped.
|
||||
"""
|
||||
policy.enforce(pecan.request.context, 'collector:list_mappings', {})
|
||||
return [mapping.service for mapping in self._db.list_services()]
|
||||
|
||||
@wsme_pecan.wsexpose(collector_models.ServiceToCollectorMapping,
|
||||
@ -45,6 +47,7 @@ class MappingController(rest.RestController):
|
||||
|
||||
:param service: Name of the service to filter on.
|
||||
"""
|
||||
policy.enforce(pecan.request.context, 'collector:get_mapping', {})
|
||||
try:
|
||||
return self._db.get_mapping(service)
|
||||
except db_api.NoSuchMapping as e:
|
||||
@ -70,6 +73,7 @@ class CollectorController(rest.RestController):
|
||||
:param collector: Name of the collector.
|
||||
:return: State of the collector.
|
||||
"""
|
||||
policy.enforce(pecan.request.context, 'collector:get_state', {})
|
||||
return self._db.get_state('collector_{}'.format(collector))
|
||||
|
||||
@wsme_pecan.wsexpose(bool, wtypes.text, body=bool)
|
||||
@ -80,4 +84,5 @@ class CollectorController(rest.RestController):
|
||||
:param state: New state for the collector.
|
||||
:return: State of the collector.
|
||||
"""
|
||||
policy.enforce(pecan.request.context, 'collector:update_state', {})
|
||||
return self._db.set_state('collector_{}'.format(collector), state)
|
||||
|
@ -22,6 +22,7 @@ from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudkitty.api.v1.datamodels import rating as rating_models
|
||||
from cloudkitty.common import policy
|
||||
from cloudkitty.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -40,12 +41,21 @@ class ModulesController(rest.RestController):
|
||||
invoke_on_load=True
|
||||
)
|
||||
|
||||
def route(self, *args):
|
||||
route = args[0]
|
||||
if route.startswith('/v1/module_config'):
|
||||
policy.enforce(pecan.request.context, 'rating:module_config', {})
|
||||
|
||||
super(ModulesController, self).route(*args)
|
||||
|
||||
@wsme_pecan.wsexpose(rating_models.CloudkittyModuleCollection)
|
||||
def get_all(self):
|
||||
"""return the list of loaded modules.
|
||||
|
||||
:return: name of every loaded modules.
|
||||
"""
|
||||
policy.enforce(pecan.request.context, 'rating:list_modules', {})
|
||||
|
||||
modules_list = []
|
||||
for module in self.extensions:
|
||||
infos = module.obj.module_info.copy()
|
||||
@ -61,6 +71,8 @@ class ModulesController(rest.RestController):
|
||||
|
||||
:return: CloudKittyModule
|
||||
"""
|
||||
policy.enforce(pecan.request.context, 'rating:get_module', {})
|
||||
|
||||
try:
|
||||
module = self.extensions[module_id]
|
||||
except KeyError:
|
||||
@ -79,6 +91,8 @@ class ModulesController(rest.RestController):
|
||||
:param module_id: name of the module to modify
|
||||
:param module: CloudKittyModule object describing the new desired state
|
||||
"""
|
||||
policy.enforce(pecan.request.context, 'rating:update_module', {})
|
||||
|
||||
try:
|
||||
ext = self.extensions[module_id].obj
|
||||
except KeyError:
|
||||
@ -153,6 +167,8 @@ class RatingController(rest.RestController):
|
||||
:param res_data: List of resource descriptions.
|
||||
:return: Total price for these descriptions.
|
||||
"""
|
||||
policy.enforce(pecan.request.context, 'rating:quote', {})
|
||||
|
||||
client = pecan.request.rpc_client.prepare(namespace='rating')
|
||||
res_dict = {}
|
||||
for res in res_data.resources:
|
||||
|
@ -23,6 +23,8 @@ from pecan import rest
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudkitty.common import policy
|
||||
|
||||
|
||||
class ReportController(rest.RestController):
|
||||
"""REST Controller managing the reporting.
|
||||
@ -41,6 +43,7 @@ class ReportController(rest.RestController):
|
||||
"""Return the list of rated tenants.
|
||||
|
||||
"""
|
||||
policy.enforce(pecan.request.context, 'report:list_tenants', {})
|
||||
storage = pecan.request.storage_backend
|
||||
tenants = storage.get_tenants(begin, end)
|
||||
return tenants
|
||||
@ -53,6 +56,7 @@ class ReportController(rest.RestController):
|
||||
"""Return the amount to pay for a given period.
|
||||
|
||||
"""
|
||||
policy.enforce(pecan.request.context, 'report:get_total', {})
|
||||
storage = pecan.request.storage_backend
|
||||
# FIXME(sheeprine): We should filter on user id.
|
||||
# Use keystone token information by default but make it overridable and
|
||||
|
@ -24,6 +24,7 @@ from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudkitty.api.v1.datamodels import storage as storage_models
|
||||
from cloudkitty.common import policy
|
||||
from cloudkitty import storage as ck_storage
|
||||
from cloudkitty import utils as ck_utils
|
||||
|
||||
@ -46,6 +47,8 @@ class DataFramesController(rest.RestController):
|
||||
:return: Collection of DataFrame objects.
|
||||
"""
|
||||
|
||||
policy.enforce(pecan.request.context, 'storage:list_data_frames', {})
|
||||
|
||||
begin_ts = ck_utils.dt2ts(begin)
|
||||
end_ts = ck_utils.dt2ts(end)
|
||||
backend = pecan.request.storage_backend
|
||||
|
53
cloudkitty/common/context.py
Normal file
53
cloudkitty/common/context.py
Normal file
@ -0,0 +1,53 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# 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_context import context
|
||||
|
||||
|
||||
class RequestContext(context.RequestContext):
|
||||
"""Extends security contexts from the OpenStack common library."""
|
||||
|
||||
def __init__(self, auth_token=None, user=None, tenant=None, domain=None,
|
||||
user_domain=None, project_domain=None, is_admin=False,
|
||||
read_only=False, show_deleted=False, request_id=None,
|
||||
resource_uuid=None, overwrite=True, roles=None):
|
||||
"""Extra parameter:
|
||||
|
||||
:param roles: List of user's roles if any.
|
||||
|
||||
"""
|
||||
self.roles = roles or []
|
||||
|
||||
super(RequestContext, self).__init__(auth_token=auth_token,
|
||||
user=user, tenant=tenant,
|
||||
domain=domain,
|
||||
user_domain=user_domain,
|
||||
project_domain=project_domain,
|
||||
is_admin=is_admin,
|
||||
read_only=read_only,
|
||||
show_deleted=show_deleted,
|
||||
request_id=request_id,
|
||||
resource_uuid=resource_uuid,
|
||||
overwrite=overwrite)
|
||||
|
||||
def to_dict(self):
|
||||
d = super(RequestContext, self).to_dict()
|
||||
d['roles'] = self.roles
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, values):
|
||||
values.pop('user', None)
|
||||
values.pop('tenant', None)
|
||||
return cls(**values)
|
@ -17,8 +17,11 @@
|
||||
#
|
||||
import abc
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import six
|
||||
|
||||
from cloudkitty.common import policy
|
||||
from cloudkitty.db import api as db_api
|
||||
from cloudkitty import rpc
|
||||
|
||||
@ -132,3 +135,14 @@ class RatingProcessorBase(object):
|
||||
def notify_reload(self):
|
||||
client = rpc.get_client().prepare(namespace='rating', fanout=True)
|
||||
client.cast({}, 'reload_module', name=self.module_name)
|
||||
|
||||
|
||||
class RatingRestControllerBase(rest.RestController):
|
||||
@pecan.expose()
|
||||
def _route(self, args, request):
|
||||
try:
|
||||
policy.enforce(request.context, 'rating:module_config', {})
|
||||
except policy.PolicyNotAuthorized as e:
|
||||
pecan.abort(403, str(e))
|
||||
|
||||
return super(RatingRestControllerBase, self)._route(args, request)
|
||||
|
@ -16,15 +16,15 @@
|
||||
# @author: Stéphane Albert
|
||||
#
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudkitty.api.v1 import types as ck_types
|
||||
from cloudkitty import rating
|
||||
from cloudkitty.rating.hash.datamodels import field as field_models
|
||||
from cloudkitty.rating.hash.db import api as db_api
|
||||
|
||||
|
||||
class HashMapFieldsController(rest.RestController):
|
||||
class HashMapFieldsController(rating.RatingRestControllerBase):
|
||||
"""Controller responsible of fields management.
|
||||
|
||||
"""
|
||||
|
@ -16,16 +16,16 @@
|
||||
# @author: Stéphane Albert
|
||||
#
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudkitty.api.v1 import types as ck_types
|
||||
from cloudkitty import rating
|
||||
from cloudkitty.rating.hash.datamodels import group as group_models
|
||||
from cloudkitty.rating.hash.datamodels import mapping as mapping_models
|
||||
from cloudkitty.rating.hash.db import api as db_api
|
||||
|
||||
|
||||
class HashMapGroupsController(rest.RestController):
|
||||
class HashMapGroupsController(rating.RatingRestControllerBase):
|
||||
"""Controller responsible of groups management.
|
||||
|
||||
"""
|
||||
|
@ -16,16 +16,16 @@
|
||||
# @author: Stéphane Albert
|
||||
#
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudkitty.api.v1 import types as ck_types
|
||||
from cloudkitty import rating
|
||||
from cloudkitty.rating.hash.datamodels import group as group_models
|
||||
from cloudkitty.rating.hash.datamodels import mapping as mapping_models
|
||||
from cloudkitty.rating.hash.db import api as db_api
|
||||
|
||||
|
||||
class HashMapMappingsController(rest.RestController):
|
||||
class HashMapMappingsController(rating.RatingRestControllerBase):
|
||||
"""Controller responsible of mappings management.
|
||||
|
||||
"""
|
||||
|
@ -15,10 +15,10 @@
|
||||
#
|
||||
# @author: Stéphane Albert
|
||||
#
|
||||
from pecan import rest
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudkitty import rating
|
||||
from cloudkitty.rating.hash.controllers import field as field_api
|
||||
from cloudkitty.rating.hash.controllers import group as group_api
|
||||
from cloudkitty.rating.hash.controllers import mapping as mapping_api
|
||||
@ -27,7 +27,7 @@ from cloudkitty.rating.hash.controllers import threshold as threshold_api
|
||||
from cloudkitty.rating.hash.datamodels import mapping as mapping_models
|
||||
|
||||
|
||||
class HashMapConfigController(rest.RestController):
|
||||
class HashMapConfigController(rating.RatingRestControllerBase):
|
||||
"""Controller exposing all management sub controllers.
|
||||
|
||||
"""
|
||||
|
@ -16,16 +16,16 @@
|
||||
# @author: Stéphane Albert
|
||||
#
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudkitty.api.v1 import types as ck_types
|
||||
from cloudkitty import rating
|
||||
from cloudkitty.rating.hash.controllers import field as field_api
|
||||
from cloudkitty.rating.hash.datamodels import service as service_models
|
||||
from cloudkitty.rating.hash.db import api as db_api
|
||||
|
||||
|
||||
class HashMapServicesController(rest.RestController):
|
||||
class HashMapServicesController(rating.RatingRestControllerBase):
|
||||
"""Controller responsible of services management.
|
||||
|
||||
"""
|
||||
|
@ -16,16 +16,16 @@
|
||||
# @author: Stéphane Albert
|
||||
#
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudkitty.api.v1 import types as ck_types
|
||||
from cloudkitty import rating
|
||||
from cloudkitty.rating.hash.datamodels import group as group_models
|
||||
from cloudkitty.rating.hash.datamodels import threshold as threshold_models
|
||||
from cloudkitty.rating.hash.db import api as db_api
|
||||
|
||||
|
||||
class HashMapThresholdsController(rest.RestController):
|
||||
class HashMapThresholdsController(rating.RatingRestControllerBase):
|
||||
"""Controller responsible of thresholds management.
|
||||
|
||||
"""
|
||||
|
@ -1,3 +1,21 @@
|
||||
{
|
||||
"context_is_admin": "role:admin"
|
||||
"context_is_admin": "role:admin",
|
||||
"default": "",
|
||||
|
||||
"rating:list_modules": "role:admin",
|
||||
"rating:get_module": "role:admin",
|
||||
"rating:update_module": "role:admin",
|
||||
"rating:quote": "",
|
||||
|
||||
"report:list_tenants": "role:admin",
|
||||
"report:get_total": "",
|
||||
|
||||
"collector:list_mappings": "role:admin",
|
||||
"collector:get_mapping": "role:admin",
|
||||
"collector:get_state": "role:admin",
|
||||
"collector:update_state": "role:admin",
|
||||
|
||||
"storage:list_data_frames": "",
|
||||
|
||||
"rating:module_config": "role:admin"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user