summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/admin/policy-yaml-file.rst14
-rw-r--r--oslo_policy/_cache_handler.py29
-rw-r--r--oslo_policy/tests/test_cache_handler.py94
-rw-r--r--oslo_policy/tests/test_policy.py6
4 files changed, 139 insertions, 4 deletions
diff --git a/doc/source/admin/policy-yaml-file.rst b/doc/source/admin/policy-yaml-file.rst
index 5757dc7..9d9b85a 100644
--- a/doc/source/admin/policy-yaml-file.rst
+++ b/doc/source/admin/policy-yaml-file.rst
@@ -239,7 +239,19 @@ Target object attributes are fields from the object description in the
database. For example in the case of the ``"compute:start"`` API, the
object is the instance to be started. The policy for starting instances
could use the ``%(project_id)s`` attribute, that is the project that
-owns the instance. The trailing ``s`` indicates this is a string.
+owns the instance. The trailing ``s`` indicates this is a string. The same
+case would be valid for API attributes like ``%(user_id)s`` and
+``%(domain_id)s``.
+
+During a debug logging phase, it's common to have the target object
+attributes retrieved in the API calls. Comparing the API call on the logs
+with the policy enforced for the corresponding API, you can check which API
+attribute has been used as the target object. For example in the policy.yaml
+for the Nova project you can find ``"compute:start"`` API, the policy will show as
+``"rule:admin_or_owner"`` which will point for
+``"admin_or_owner": "is_admin:True or project_id:%(project_id)s"`` and in this
+way you can check that the target object in the debug logging it needs to be a
+``project_id`` attribute.
``is_admin`` indicates that administrative privileges are granted via
the admin token mechanism (the ``--os-token`` option of the ``keystone``
diff --git a/oslo_policy/_cache_handler.py b/oslo_policy/_cache_handler.py
index ab90684..fd34915 100644
--- a/oslo_policy/_cache_handler.py
+++ b/oslo_policy/_cache_handler.py
@@ -13,9 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+import errno
import logging
import os
+from oslo_config import cfg
+
LOG = logging.getLogger(__name__)
@@ -33,13 +36,33 @@ def read_cached_file(cache, filename, force_reload=False):
delete_cached_file(cache, filename)
reloaded = False
- mtime = os.path.getmtime(filename)
+ try:
+ mtime = os.path.getmtime(filename)
+ except OSError as err:
+ msg = err.strerror
+ LOG.error('Config file not found %(filename)s: %(msg)s',
+ {'filename': filename, 'msg': msg})
+ return True, {}
+
cache_info = cache.setdefault(filename, {})
if not cache_info or mtime > cache_info.get('mtime', 0):
LOG.debug("Reloading cached file %s", filename)
- with open(filename) as fap:
- cache_info['data'] = fap.read()
+ try:
+ with open(filename) as fap:
+ cache_info['data'] = fap.read()
+ except IOError as err:
+ msg = err.strerror
+ err_code = err.errno
+ LOG.error('IO error loading %(filename)s: %(msg)s',
+ {'filename': filename, 'msg': msg})
+ if err_code == errno.EACCES:
+ raise cfg.ConfigFilesPermissionDeniedError((filename,))
+ except OSError as err:
+ msg = err.strerror
+ LOG.error('Config file not found %(filename)s: %(msg)s',
+ {'filename': filename, 'msg': msg})
+ raise cfg.ConfigFilesNotFoundError((filename,))
cache_info['mtime'] = mtime
reloaded = True
return (reloaded, cache_info['data'])
diff --git a/oslo_policy/tests/test_cache_handler.py b/oslo_policy/tests/test_cache_handler.py
new file mode 100644
index 0000000..3251009
--- /dev/null
+++ b/oslo_policy/tests/test_cache_handler.py
@@ -0,0 +1,94 @@
+# Copyright (c) 2020 OpenStack Foundation.
+# 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.
+
+"""Test the cache handler module"""
+
+import os
+from unittest import mock
+
+import fixtures
+import oslo_config
+from oslotest import base as test_base
+
+from oslo_policy import _cache_handler as _ch
+
+
+class CacheHandlerTest(test_base.BaseTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.tmpdir = self.useFixture(fixtures.TempDir())
+
+ def test_read_cached_file(self):
+ file_cache = {}
+
+ path = os.path.join(self.tmpdir.path, 'tmpfile')
+ with open(path, 'w+') as fp:
+ fp.write('test')
+
+ reloaded, data = _ch.read_cached_file(file_cache, path)
+ self.assertEqual('test', data)
+ self.assertTrue(reloaded)
+
+ reloaded, data = _ch.read_cached_file(file_cache, path)
+ self.assertEqual('test', data)
+ self.assertFalse(reloaded)
+
+ reloaded, data = _ch.read_cached_file(
+ file_cache, path, force_reload=True)
+ self.assertEqual('test', data)
+ self.assertTrue(reloaded)
+
+ def test_read_cached_file_with_updates(self):
+ file_cache = {}
+
+ path = os.path.join(self.tmpdir.path, 'tmpfile')
+ with open(path, 'w+') as fp:
+ fp.write('test')
+
+ reloaded, data = _ch.read_cached_file(file_cache, path)
+
+ # update the timestamps
+ times = (os.stat(path).st_atime + 1, os.stat(path).st_mtime + 1)
+ os.utime(path, times)
+
+ reloaded, data = _ch.read_cached_file(file_cache, path)
+ self.assertTrue(reloaded)
+
+ @mock.patch.object(_ch, 'LOG')
+ def test_reloading_cache_with_permission_denied(self, mock_log):
+ file_cache = {}
+
+ path = os.path.join(self.tmpdir.path, 'tmpfile')
+ with open(path, 'w+') as fp:
+ fp.write('test')
+
+ os.chmod(path, 000)
+ self.assertRaises(
+ oslo_config.cfg.ConfigFilesPermissionDeniedError,
+ _ch.read_cached_file, file_cache, path)
+ mock_log.error.assert_called_once()
+
+ @mock.patch.object(_ch, 'LOG')
+ def test_reloading_on_removed_file(self, mock_log):
+ file_cache = {}
+
+ # don't actually create the file
+ path = os.path.join(self.tmpdir.path, 'tmpfile')
+
+ reloaded, data = _ch.read_cached_file(file_cache, path)
+ self.assertEqual({}, data)
+ self.assertTrue(reloaded)
+ mock_log.error.assert_called_once()
diff --git a/oslo_policy/tests/test_policy.py b/oslo_policy/tests/test_policy.py
index 0a8bab5..d5c86c3 100644
--- a/oslo_policy/tests/test_policy.py
+++ b/oslo_policy/tests/test_policy.py
@@ -1802,6 +1802,12 @@ class DocumentedRuleDefaultDeprecationTestCase(base.PolicyBaseTestCase):
self.assertEqual(
str(enforcer.rules['foo:create_bar']), str(expected_check))
self.assertEqual(check, rule.check)
+ # Hacky way to check whether _handle_deprecated_rule was called again.
+ # If a second call to load_rules doesn't overwrite our dummy rule then
+ # we know it didn't call the deprecated rule function again.
+ enforcer.rules['foo:create_bar'] = 'foo:bar'
+ enforcer.load_rules()
+ self.assertEqual('foo:bar', enforcer.rules['foo:create_bar'])
class DocumentedRuleDefaultTestCase(base.PolicyBaseTestCase):