# All Rights Reserved.
#
#    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 imp
import inspect
import os
import sys

import mock
from oslo_utils import uuidutils

from ironic.tests import base as test_base


class TestExposedAPIMethodsCheckPolicy(test_base.TestCase):
    """Ensure that all exposed HTTP endpoints call authorize."""

    def setUp(self):
        super(TestExposedAPIMethodsCheckPolicy, self).setUp()
        self.original_method = sys.modules['ironic.api.expose'].expose
        self.exposed_methods = set()

        def expose_and_track(*args, **kwargs):
            def wrap(f):
                if f not in self.exposed_methods:
                    self.exposed_methods.add(f)
                e = self.original_method(*args, **kwargs)
                return e(f)
            return wrap

        p = mock.patch('ironic.api.expose.expose', expose_and_track)
        p.start()
        self.addCleanup(p.stop)

    def _test(self, module):
        module_path = os.path.abspath(sys.modules[module].__file__)
        # NOTE(vdrok): coverage runs on compiled .pyc files, which breaks
        # load_source. Strip c and o letters from the end of the module path,
        # just in case someone tries to use .pyo or .pyc for whatever reason
        imp.load_source(uuidutils.generate_uuid(), module_path.rstrip('co'))

        for func in self.exposed_methods:
            src = inspect.getsource(func)
            self.assertTrue('policy.authorize' in src,
                            'policy.authorize call not found in exposed '
                            'method %s' % func)
            self.assertTrue('context.to_policy_values' in src,
                            'context.to_policy_values call not found in '
                            'exposed method %s' % func)

    def test_chasis_api_policy(self):
        self._test('ironic.api.controllers.v1.chassis')

    def test_driver_api_policy(self):
        self._test('ironic.api.controllers.v1.driver')

    def test_node_api_policy(self):
        self._test('ironic.api.controllers.v1.node')

    def test_port_api_policy(self):
        self._test('ironic.api.controllers.v1.port')

    def test_portgroup_api_policy(self):
        self._test('ironic.api.controllers.v1.portgroup')

    def test_ramdisk_api_policy(self):
        self._test('ironic.api.controllers.v1.ramdisk')

Add test to ensure policy is always authorized
Add test to ensure policy is always authorized

This adds new unit tests to ensure that all API methods decorated
with @expose.expose are also calling policy.authorize() and
context.to_policy_values() within the method body.

This is done by patching sys.modules to replace the @expose decorator
with a wrapper that records a reference to the exposed method. Then,
all API modules' (chassis, node, port, etc.) sources are reloaded
within the unit test, causing the test classes own decorator to be
called, and references to every @expose'd method to be recorded in the
test class instance. The test case then iterates over that list of
function references, and inspects the python source of each one to
determine if it contains a call to the policy.authorize and
context.to_policy_values methods. An error is raised if it does not.

This approach is rather brutish, but without being able to invoke
@authorize as a decorator (*) I have not found a better way than
inspecting the source.

(*) we can't invoke @authorize as a decorator because decorators are
evaluated at module compile time, but @authorize requires run-time
context available only from the Pecan run-time magic accessor
pecan.request.context.

Co-Authored-By: Vladyslav Drok <488302dd246f7368b4b820d34501759efce48f70@mirantis.com>
Change-Id: Iebfd9183dcbd49dbef78398c07327a347a41976e
