diff --git a/cloudkitty/api/v1/controllers/storage.py b/cloudkitty/api/v1/controllers/storage.py index d1122df2..7618fe0e 100644 --- a/cloudkitty/api/v1/controllers/storage.py +++ b/cloudkitty/api/v1/controllers/storage.py @@ -59,8 +59,10 @@ class DataFramesController(rest.RestController): scope_key = CONF.collect.scope_key backend = pecan.request.storage_backend dataframes = [] - filters = {scope_key: tenant_id} if tenant_id else None - + if pecan.request.context.is_admin: + filters = {scope_key: tenant_id} if tenant_id else None + else: + filters = {scope_key: project_id} try: resp = backend.retrieve( begin, end, diff --git a/cloudkitty/api/v2/dataframes/dataframes.py b/cloudkitty/api/v2/dataframes/dataframes.py index d27e2fd8..e33aeef3 100644 --- a/cloudkitty/api/v2/dataframes/dataframes.py +++ b/cloudkitty/api/v2/dataframes/dataframes.py @@ -13,6 +13,7 @@ # under the License. # import flask +from oslo_config import cfg import voluptuous from werkzeug import exceptions as http_exceptions @@ -23,6 +24,11 @@ from cloudkitty import dataframe from cloudkitty import tzutils +CONF = cfg.CONF + +CONF.import_opt('scope_key', 'cloudkitty.collector', 'collect') + + class DataFrameList(base.BaseResource): @api_utils.add_input_schema('body', { voluptuous.Required('dataframes'): [dataframe.DataFrame.from_dict], @@ -61,7 +67,7 @@ class DataFrameList(base.BaseResource): limit=100, begin=None, end=None, - filters={}): + filters=None): policy.authorize( flask.request.context, @@ -72,7 +78,18 @@ class DataFrameList(base.BaseResource): begin = begin or tzutils.get_month_start() end = end or tzutils.get_next_month() - metric_types = [filters.pop('type')] if 'type' in filters else None + if filters and 'type' in filters: + metric_types = [filters.pop('type')] + else: + metric_types = None + + if not flask.request.context.is_admin: + scope_key = CONF.collect.scope_key + if filters: + filters[scope_key] = flask.request.context.project_id + else: + filters = {scope_key: flask.request.context.project_id} + results = self._storage.retrieve( begin=begin, end=end, filters=filters, diff --git a/cloudkitty/tests/api/v2/dataframes/__init__.py b/cloudkitty/tests/api/v2/dataframes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cloudkitty/tests/api/v2/dataframes/test_dataframes.py b/cloudkitty/tests/api/v2/dataframes/test_dataframes.py new file mode 100644 index 00000000..3a4658d1 --- /dev/null +++ b/cloudkitty/tests/api/v2/dataframes/test_dataframes.py @@ -0,0 +1,45 @@ +# Copyright 2019 Objectif Libre +# +# 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 unittest + +import mock + +from cloudkitty.api.v2.dataframes import dataframes +from cloudkitty import tzutils + + +class TestDataframeListEndpoint(unittest.TestCase): + + def setUp(self): + super(TestDataframeListEndpoint, self).setUp() + self.endpoint = dataframes.DataFrameList() + + def test_non_admin_request_is_filtered_on_project_id(self): + policy_mock = mock.patch('cloudkitty.common.policy.authorize') + with mock.patch.object(self.endpoint._storage, 'retrieve') as ret_mock: + with policy_mock, mock.patch('flask.request') as fmock: + ret_mock.return_value = {'total': 42, 'dataframes': []} + fmock.args.lists.return_value = [] + fmock.context.is_admin = False + fmock.context.project_id = 'test-project' + self.endpoint.get() + ret_mock.assert_called_once_with( + begin=tzutils.get_month_start(), + end=tzutils.get_next_month(), + metric_types=None, + filters={'project_id': 'test-project'}, + offset=0, + limit=100, + ) diff --git a/releasenotes/notes/fix-dataframe-filtering-282cae643457bb8b.yaml b/releasenotes/notes/fix-dataframe-filtering-282cae643457bb8b.yaml new file mode 100644 index 00000000..09f2b72f --- /dev/null +++ b/releasenotes/notes/fix-dataframe-filtering-282cae643457bb8b.yaml @@ -0,0 +1,6 @@ +--- +security: + - | + Data filtering on the ``GET /v1/dataframes`` and `` GET /v2/dataframes`` + has been fixed. It was previously possible for users to retrieve data + from other scopes through these endpoints.