oslo.policy-1.33.1/0000775000175100017510000000000013224676252014106 5ustar zuulzuul00000000000000oslo.policy-1.33.1/sample_data/0000775000175100017510000000000013224676252016360 5ustar zuulzuul00000000000000oslo.policy-1.33.1/sample_data/auth_v3_token_admin.json0000666000175100017510000001162013224676023023172 0ustar zuulzuul00000000000000{ "token": { "methods": [ "password" ], "roles": [ { "name": "admin", "id":"41b1af9bb39241e8b8b79fae5906abcc" } ], "expires_at": "2038-01-18T21:14:07Z", "issued_at": "2000-01-18T21:14:07Z", "project": { "id": "tenant_id1", "domain": { "id": "domain_id1", "name": "domain_name1" }, "enabled": true, "description": null, "name": "tenant_name1" }, "catalog": [ { "endpoints": [ { "id": "f84e070735e54914b41e2b5cfa94dcf7", "interface": "admin", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "8220bba1d2844e0b81b171c6ede1155f", "interface": "internal", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "719b92ea82a04e7a9ff1107c62da10da", "interface": "public", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "type": "volume", "name": "volume", "id":"547e9195d1914b5eb087bedbc98fccc3" }, { "endpoints": [ { "id": "44752324c0d44375bc854168ea22f1fc", "interface": "admin", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "a59b3734f57449078f1637c10f96c8e8", "interface": "internal", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "16c3ab1a4df640569812e432c98b2a48", "interface": "public", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" } ], "type": "image", "name": "glance", "id": "22c15d232e55419eb4aeb3ebbd12aac2" }, { "endpoints": [ { "id": "9c2fdc2d45bb45c5a7f973e235e0f998", "interface": "admin", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "88ccfa8cbb7743998b38b998f4e6a720", "interface": "internal", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "113ee928c6934c92b9a12bd4e456c804", "interface": "public", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "type": "compute", "name": "nova", "id": "fbf2afcdeb10473392636df9785d3fb5" }, { "endpoints": [ { "id": "c10a5cda00784049953296d18464aa38", "interface": "admin", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "334650263e064428bb2f0b7c3c7a743c", "interface": "internal", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "52ff54addc38430d9b656c7164e2caf8", "interface": "public", "url": "http://127.0.0.1:5000/v3", "region": "RegionOne" } ], "type": "identity", "name": "keystone", "id": "a0d9913a4bca4d5699e151804e0b5172" } ], "user": { "domain": { "id": "domain_id1", "name": "domain_name1" }, "name": "user_name1", "id": "user_id1" } } } oslo.policy-1.33.1/sample_data/auth_v3_token_member.json0000666000175100017510000001162613224676023023357 0ustar zuulzuul00000000000000{ "token": { "methods": [ "password" ], "roles": [ { "name": "member", "id": "bb8d0f54-a1c9-444f-81da-231aef4d7e03" } ], "expires_at": "2038-01-18T21:14:07Z", "issued_at": "2000-01-18T21:14:07Z", "project": { "id": "tenant_id1", "domain": { "id": "domain_id1", "name": "domain_name1" }, "enabled": true, "description": null, "name": "tenant_name1" }, "catalog": [ { "endpoints": [ { "id": "f84e070735e54914b41e2b5cfa94dcf7", "interface": "admin", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "8220bba1d2844e0b81b171c6ede1155f", "interface": "internal", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "719b92ea82a04e7a9ff1107c62da10da", "interface": "public", "url": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "type": "volume", "name": "volume", "id":"547e9195d1914b5eb087bedbc98fccc3" }, { "endpoints": [ { "id": "44752324c0d44375bc854168ea22f1fc", "interface": "admin", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "a59b3734f57449078f1637c10f96c8e8", "interface": "internal", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "16c3ab1a4df640569812e432c98b2a48", "interface": "public", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" } ], "type": "image", "name": "glance", "id": "22c15d232e55419eb4aeb3ebbd12aac2" }, { "endpoints": [ { "id": "9c2fdc2d45bb45c5a7f973e235e0f998", "interface": "admin", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "88ccfa8cbb7743998b38b998f4e6a720", "interface": "internal", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "113ee928c6934c92b9a12bd4e456c804", "interface": "public", "url": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "type": "compute", "name": "nova", "id": "fbf2afcdeb10473392636df9785d3fb5" }, { "endpoints": [ { "id": "c10a5cda00784049953296d18464aa38", "interface": "admin", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "334650263e064428bb2f0b7c3c7a743c", "interface": "internal", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "52ff54addc38430d9b656c7164e2caf8", "interface": "public", "url": "http://127.0.0.1:5000/v3", "region": "RegionOne" } ], "type": "identity", "name": "keystone", "id": "a0d9913a4bca4d5699e151804e0b5172" } ], "user": { "domain": { "id": "domain_id1", "name": "domain_name1" }, "name": "user_name1", "id": "user_id1" } } } oslo.policy-1.33.1/HACKING.rst0000666000175100017510000000044713224676023015707 0ustar zuulzuul00000000000000Style Commandments ================== - Step 1: Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ - Step 2: Read on oslo.policy Specific Commandments --------------------------------- - Avoid using "double quotes" where you can reasonably use 'single quotes' oslo.policy-1.33.1/test-requirements.txt0000666000175100017510000000107613224676023020351 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 requests-mock>=1.1.0 # Apache-2.0 # computes code coverage percentages coverage!=4.4,>=4.0 # Apache-2.0 # These are needed for docs generation openstackdocstheme>=1.17.0 # Apache-2.0 sphinx>=1.6.2 # BSD reno>=2.5.0 # Apache-2.0 # Bandit security code scanner bandit>=1.1.0 # Apache-2.0 oslo.policy-1.33.1/CONTRIBUTING.rst0000666000175100017510000000104013224676023016540 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: https://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/oslo.policy oslo.policy-1.33.1/ChangeLog0000664000175100017510000003413513224676251015665 0ustar zuulzuul00000000000000CHANGES ======= 1.33.1 ------ * Only log deprecation warnings when they are overridden 1.33.0 ------ * Add a release note for enforce\_scope * Add configuration option for enforcing scope 1.32.2 ------ * Fix string injection for InvalidScope 1.32.1 ------ * Imported Translations from Zanata 1.32.0 ------ * Add scope\_types to RuleDefault objects 1.31.0 ------ * Remove -U from pip install * Avoid tox\_install.sh for constraints support * add bandit to pep8 job * Updated from global requirements * Handle deprecation of inspect.getargspec * Remove setting of version/release from releasenotes * Updated from global requirements 1.30.0 ------ * Imported Translations from Zanata * Add functionality to deprecate policies * Pass creds as a dict in tests 1.29.0 ------ * Documentation and release notes for plugins * expand type documentation for Enforcer arguments * Imported Translations from Zanata * http/https check rules as stevedore extensions * External Policy hook should support SSL 1.28.1 ------ * Modification to add additional information in the HTTPCheck request 1.28.0 ------ * Updated from global requirements * rewrite HttpCheckFixture to not mock out entire HttpCheck class 1.27.0 ------ * Updated from global requirements * Add JSON output option to sample generator 1.26.0 ------ * Imported Translations from Zanata * Updated from global requirements * throw an exception when sphinxext cannot find the config file * Update reno for stable/pike * fix formatting for empty defaults * Updated from global requirements 1.25.0 ------ * Updated from global requirements * Update URLs in documents according to document migration * Fix parsing bug when config file is empty 1.24.1 ------ * import configuration guide content from openstack-manuals repo * sphinxext: Use field lists in output * sphinxext: Format definition lists correctly * switch from oslosphinx to openstackdocstheme * move existing documentation into new standard layout 1.24.0 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements 1.23.0 ------ * Updated from global requirements * Simplify message of exception PolicyNotAuthorized * Updated from global requirements 1.22.1 ------ * Updated from global requirements * Add Sphinx extension to pretty-print modules * Optimize the link address * Check reStructuredText documents for common style issues 1.22.0 ------ * Update usage documentation * Add release note for DocumentedRuleDefault * Remove log translations * oslopolicy-sample-generator description support * Use Sphinx 1.5 warning-is-error 1.21.0 ------ * Comment out the rule from generated sample-policy file * Modify tests in test\_generator * Add additional param to policy.RuleDefault * Updated from global requirements * Seperate each policy rule with new line 1.20.0 ------ * Allow multiline descriptions for RuleDefaults 1.19.0 ------ * Updated from global requirements * [Fix gate]Update test requirement * Updated from global requirements * Updated from global requirements * Remove support for py34 * pbr.version.VersionInfo needs package name (oslo.xyz and not oslo\_xyz) * Delete the unnecessary word in policy.py * Update reno for stable/ocata * Add optional exception for check\_rules 1.18.0 ------ * Remove references to Python 3.4 * Remove dead code and use default value of argparse * Add Constraints support * Updated from global requirements 1.17.0 ------ * Improved performance of parse\_file\_contents() method * Show team and repo badges on README * Remove wrong parameter type for class NotCheck from docstring * Fix a code logic while doing cyclical reference check to the policy * Updated from global requirements * Add missing parameter description in module \_cache\_handler * Fix typo in oslo.policy * Updated from global requirements * Add stevedore to requirements * Imported Translations from Zanata * Updated from global requirements * Make exception PolicyNotAuthorized more readable 1.16.0 ------ * Change assertTrue(isinstance()) by optimal assert * Perform basic checks on policy definitions * Enable release notes translation * Changed the home-page link * Change assertTrue(isinstance()) by optimal assert 1.15.0 ------ * Updated from global requirements * Update docs on policy sample generator * Updated from global requirements * doc: Add introduction to index page * Add sphinx extension to build sample policy * Updated from global requirements * Updated from global requirements * Doc: declare YAML/JSON support * Remove oslo.utils from requirements * Update reno for stable/newton 1.14.0 ------ * Revert "Adds debug logging for policy file validation" * Updated from global requirements * Delete H803 in flake8 ignore 1.13.0 ------ * Updated from global requirements * Add note about not all APIs support policy enforcement by user\_id * Allow policy file to not exist * Adds debug logging for policy file validation * Fixed unit tests running on Windows * Add Python 3.5 classifier and venv 1.12.0 ------ * Updated from global requirements * Updated from global requirements * Fix mispelled method name in setup.cfg * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata 1.11.0 ------ * Updated from global requirements 1.10.0 ------ * Imported Translations from Zanata * Improve policy sample generation testing * Add helper scripts for generating policy info 1.9.0 ----- * Add sample file generation script and helper methods * Add equality operator to policy.RuleDefault * Imported Translations from Zanata * Updated from global requirements * Fix typo: 'olso' to 'oslo' * Updated from global requirements * Updated from global requirements * Add reno for release notes management * Add policy registration and authorize method * Updated from global requirements * doc: Fix wrong import statement in usage 1.8.0 ----- * Trivial: ignore openstack/common in flake8 exclude list 1.7.0 ----- * Updated from global requirements * Imported Translations from Zanata * Imported Translations from Zanata * Updated from global requirements * Updated from global requirements * Deprecate load\_json() in favor of load() * Support policy file in YAML 1.5.0 ----- * Updated from global requirements 1.4.0 ----- * Update translation setup * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Revert "Pass environment variables of proxy to tox" * Run docs testenv by default with tox * Add oslopolicy-checker command-line tool 1.3.0 ----- * Updated from global requirements * Don't crash on RoleCheck when roles not present * assertIsNone(val) instead of assertEqual(None,val) 1.2.0 ----- * Updated from global requirements * Add string format rendering to RoleCheck.\_\_call\_\_() * Pass environment variables of proxy to tox * Fixes combined "and" and "or" rule handling * Make sure item of policy\_dirs is directory * Updated from global requirements * Use dict comprehension * Don't generate doc from test 1.1.0 ----- * Trival: Remove 'MANIFEST.in' * Updated from global requirements * Updated from global requirements * Updated from global requirements * Use requests-mock instead of httpretty in tests * Clarify usage docs * Correct invalid doc references 1.0.0 ----- * Updated from global requirements * Remove Python 2.6 classifier * Remove python 2.6 and cleanup tox.ini 0.13.0 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements * Use JSON generator * Add test for enforce with rule doesn't exist * Add test for raising default exception * Add test for invalid JSON * Add cover test requirement * Fix a typo in policy.py 0.12.0 ------ * Updated from global requirements * Custom fixture to avoid external call in HttpCheck * Fix coverage configuration and execution * add auto-generated docs for config options * Add shields.io version/downloads links/badges into README.rst * Updated from global requirements * Use requests in http check instead of urllib * Change ignore-errors to ignore\_errors * Updated from global requirements * remove deprecation text for policy\_dirs option 0.11.0 ------ * Updated from global requirements 0.10.0 ------ * Pass reference then actual to assertEqual * Overwrite option should not cause policy file reloading * Updated from global requirements * Setup translations * Have the enforcer have its own file cache * Updated from global requirements 0.9.0 ----- * Updated from global requirements 0.8.0 ----- * Updated from global requirements * Fix typo of 'available' in token\_fixture.py * Fixes up the API docs and module index 0.7.0 ----- * Remove oslo-incubator specific code * Move fileutils functions to oslo.policy * Add six and oslo.utils to requirements * Add tox target to find missing requirements * Updated from global requirements * Updated from global requirements 0.6.0 ----- * Fix Enforcer docstring 0.5.0 ----- * Expose base check classes as part of public API * Cleanup logging to conform to guidelines * Cleanup logging to conform to guidelines * Remove support for Python 3.3 * Updated from global requirements 0.4.0 ----- * Uncap library requirements for liberty * Fix invalid indentation in \_load\_policy\_file method * Cleanup README.rst and setup.cfg * Avoid reloading policy files in policy.d for every call * Lists for Generic Checks * Updated from global requirements 0.3.1 ----- * Switch to non-namespaced module imports 0.3.0 ----- * deprecate policy\_dirs option * Updated from global requirements * Expose register and Check as part of public API * provide more descriptive exception 0.2.0 ----- * Add missing space to help message * Add Rules.from\_dict classmethod * Use assertTrue or assertFalse where appropriate * Fix the order of args to assertEqual in tests 0.1.0 ----- * Clean up configuration option management * Update comments about tox configuration * Fix i18n imports * Change default set of tox environments * Create the temporary files needed for tests * Fix minor spelling issues in oslo.policy * Use single quotes consistently * Do not log on missing or empty policy\_dirs * Remove symlinked file from tests * document the migration process and update the docs a bit * Use standard logging in oslo.policy * Updated from global requirements * Remove globals that were introduced for compatibility * Upgrade hacking to >=0.10.0 * Remove oslo.concurrency from requirements * Stop shouting test attribute names * Do not use global enforcer for tests * Make use of private modules * Privatize parsing classes * Add entry points for option discovery * Add pep8 import exception for oslo\_policy.\_i18n * Use oslo\_i18n * Perform an oslo-sync * General docstring cleanup * Drop use of oslo namespace for oslo libraries * Update .gitignore * Drop usage of namespaced packages * Remove use of graduated modules * Add docstrings for check classes * Correct docstring references * Improve policy documentation * Explicit configuration object * Fix project metadata * Add API documentation * Move project imports after 3rd party imports * Fix tests * Add openstack.common and requirements fixes * exported from oslo-incubator by graduate.sh * Improving docstrings for policy API * Don't log missing policy.d as a warning * Add rule overwrite flag to Enforcer class * Fixed a problem with neutron http policy check * Expanding the help text for policy\_dirs * policy: add a missing staticmethod declaration * Fixes nits in module policy * add list\_opts to all modules with configuration options * Correct default rule name for policy.Enforcer * Minor fixes in policy module * Delete graduated serialization files * Remove code that moved to oslo.i18n * Allow dictionary lookup in credentials with dot notation * Remove graduated test and fixtures libraries * Fix typo to show correct log message * Use MultiStrOpt for policy\_dirs * Add support for policy configration directories * Fix deletion of cached file for policy enforcer * Make policy debug logging less verbose * Improve help strings * Use oslotest instead of common test module * policy: rename Exception to avoid nose regression * Adds a flag to determine whether to reload the rules in policy * Documenting policy.json syntax * Update oslo log messages with translation domains * Fix policy tests for parallel testing * Allow policy.json resource vs constant check * Replaces use of urlutils with six in policy module * Utilizes assertIsNone and assertIsNotNone * Use hacking import\_exceptions for gettextutils.\_ * Use urlutils functions instead of urllib/urllib2 * Remove vim header * Use six.string\_type instead of basestring * Apply six for metaclass * ConfigFileNotFoundError with proper argument * Keystone user can't perform revoke\_token * Remove useless unit test codes in test\_policy * Replace using tests.utils part2 * Bump hacking to 0.7.0 * Fix wrong argument in openstack common policy * Fix missing argument bug in oslo common policy * Fix policy default\_rule issue * Allow use of hacking 0.6.0 and enable new checks * Fix missing argument bug in oslo common policy * Enable H302 hacking check * Enable hacking H404 test * Enable H306 hacking check * python3: python3 binary/text data compatbility * Reduce duplicated code related to policies * Removes len() on empty sequence evaluation * Convert unicode for python3 portability * Replaces standard logging with common logging * Update to use flake8 * Removes unused imports in the tests module * update OpenStack, LLC to OpenStack Foundation * Replace direct use of testtools BaseTestCase * Use testtools as test base class * Fix pep8 E125 errors * Revert "Add support for finer-grained policy decisions" * Remove an unneeded 'global' * Add support for finer-grained policy decisions * Add a 'not' operator to the policy langage * Add a new policy language * Remove deprecated policy engine APIs * Rewrite the policy engine from scratch * Use pep8 v1.3.3 * Allow non-string items in the creds dict * Use function registration for policy checks * Fix missing gettextutils in several modules * Switch common files to using jsonutils * Update common code to support pep 1.3. bug 1014216 * Common-ize policies * initial commit * Initial skeleton project oslo.policy-1.33.1/oslo_policy/0000775000175100017510000000000013224676252016441 5ustar zuulzuul00000000000000oslo.policy-1.33.1/oslo_policy/tests/0000775000175100017510000000000013224676252017603 5ustar zuulzuul00000000000000oslo.policy-1.33.1/oslo_policy/tests/test_checks.py0000666000175100017510000003713513224676023022463 0ustar zuulzuul00000000000000# Copyright (c) 2015 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. import mock from oslotest import base as test_base from oslo_policy import _checks from oslo_policy.tests import base from oslo_policy.tests import token_fixture class CheckRegisterTestCase(test_base.BaseTestCase): @mock.patch.object(_checks, 'registered_checks', {}) def test_register_check(self): class TestCheck(_checks.Check): pass _checks.register('spam', TestCheck) self.assertEqual(dict(spam=TestCheck), _checks.registered_checks) @mock.patch.object(_checks, 'registered_checks', {}) def test_register_check_decorator(self): @_checks.register('spam') class TestCheck(_checks.Check): pass self.assertEqual(dict(spam=TestCheck), _checks.registered_checks) class RuleCheckTestCase(base.PolicyBaseTestCase): def test_rule_missing(self): self.enforcer.rules = {} check = _checks.RuleCheck('rule', 'spam') self.assertFalse(check('target', 'creds', self.enforcer)) def test_rule_false(self): self.enforcer.rules = dict(spam=_BoolCheck(False)) check = _checks.RuleCheck('rule', 'spam') self.assertFalse(check('target', 'creds', self.enforcer)) def test_rule_true(self): self.enforcer.rules = dict(spam=_BoolCheck(True)) check = _checks.RuleCheck('rule', 'spam') self.assertTrue(check('target', 'creds', self.enforcer)) class RoleCheckTestCase(base.PolicyBaseTestCase): def test_accept(self): check = _checks.RoleCheck('role', 'sPaM') self.assertTrue(check({}, dict(roles=['SpAm']), self.enforcer)) def test_reject(self): check = _checks.RoleCheck('role', 'spam') self.assertFalse(check({}, dict(roles=[]), self.enforcer)) def test_format_value(self): check = _checks.RoleCheck('role', '%(target.role.name)s') target_dict = {'target.role.name': 'a'} cred_dict = dict(user='user', roles=['a', 'b', 'c']) self.assertTrue(check(target_dict, cred_dict, self.enforcer)) target_dict = {'target.role.name': 'd'} self.assertFalse(check(target_dict, cred_dict, self.enforcer)) target_dict = dict(target=dict(role=dict())) self.assertFalse(check(target_dict, cred_dict, self.enforcer)) def test_no_roles_case(self): check = _checks.RoleCheck('role', 'spam') self.assertFalse(check({}, {}, self.enforcer)) class GenericCheckTestCase(base.PolicyBaseTestCase): def test_no_cred(self): check = _checks.GenericCheck('name', '%(name)s') self.assertFalse(check(dict(name='spam'), {}, self.enforcer)) def test_cred_mismatch(self): check = _checks.GenericCheck('name', '%(name)s') self.assertFalse(check(dict(name='spam'), dict(name='ham'), self.enforcer)) def test_accept(self): check = _checks.GenericCheck('name', '%(name)s') self.assertTrue(check(dict(name='spam'), dict(name='spam'), self.enforcer)) def test_no_key_match_in_target(self): check = _checks.GenericCheck('name', '%(name)s') self.assertFalse(check(dict(name1='spam'), dict(name='spam'), self.enforcer)) def test_constant_string_mismatch(self): check = _checks.GenericCheck("'spam'", '%(name)s') self.assertFalse(check(dict(name='ham'), {}, self.enforcer)) def test_constant_string_accept(self): check = _checks.GenericCheck("'spam'", '%(name)s') self.assertTrue(check(dict(name='spam'), {}, self.enforcer)) def test_constant_literal_mismatch(self): check = _checks.GenericCheck('True', '%(enabled)s') self.assertFalse(check(dict(enabled=False), {}, self.enforcer)) def test_constant_literal_accept(self): check = _checks.GenericCheck('True', '%(enabled)s') self.assertTrue(check(dict(enabled=True), {}, self.enforcer)) def test_deep_credentials_dictionary_lookup(self): check = _checks.GenericCheck('a.b.c.d', 'APPLES') credentials = {'a': {'b': {'c': {'d': 'APPLES'}}}} self.assertTrue(check({}, credentials, self.enforcer)) def test_missing_credentials_dictionary_lookup(self): credentials = {'a': 'APPLES', 'o': {'t': 'ORANGES'}} # First a valid check - rest of case is expecting failures # Should prove the basic credentials structure before we test # for failure cases. check = _checks.GenericCheck('o.t', 'ORANGES') self.assertTrue(check({}, credentials, self.enforcer)) # Case where final key is missing check = _checks.GenericCheck('o.v', 'ORANGES') self.assertFalse(check({}, credentials, self.enforcer)) # Attempt to access key under a missing dictionary check = _checks.GenericCheck('q.v', 'APPLES') self.assertFalse(check({}, credentials, self.enforcer)) def test_single_entry_in_list_accepted(self): check = _checks.GenericCheck('a.b.c.d', 'APPLES') credentials = {'a': {'b': {'c': {'d': ['APPLES']}}}} self.assertTrue(check({}, credentials, self.enforcer)) def test_multiple_entry_in_list_accepted(self): check = _checks.GenericCheck('a.b.c.d', 'APPLES') credentials = {'a': {'b': {'c': {'d': ['Bananas', 'APPLES', 'Grapes']}}}} self.assertTrue(check({}, credentials, self.enforcer)) def test_multiple_entry_in_nested_list_accepted(self): check = _checks.GenericCheck('a.b.c.d', 'APPLES') credentials = {'a': {'b': [{'c': {'d': ['BANANAS', 'APPLES', 'GRAPES']}}, {}]}} self.assertTrue(check({}, credentials, self.enforcer)) def test_multiple_entries_one_matches(self): check = _checks.GenericCheck( 'token.catalog.endpoints.id', token_fixture.REGION_ONE_PUBLIC_KEYSTONE_ENDPOINT_ID) credentials = token_fixture.SCOPED_TOKEN_FIXTURE self.assertTrue(check({}, credentials, self.enforcer)) def test_generic_role_check_matches(self): check = _checks.GenericCheck( 'token.roles.name', 'role1') credentials = token_fixture.SCOPED_TOKEN_FIXTURE self.assertTrue(check({}, credentials, self.enforcer)) def test_generic_missing_role_does_not_matches(self): check = _checks.GenericCheck( 'token.roles.name', 'missing') credentials = token_fixture.SCOPED_TOKEN_FIXTURE self.assertFalse(check({}, credentials, self.enforcer)) def test_multiple_nested_lists_accepted(self): check = _checks.GenericCheck('a.b.c.d', 'APPLES') credentials = {'a': {'b': [{'a': ''}, {'c': {'d': ['BANANAS', 'APPLES', 'GRAPES']}}, {}]}} self.assertTrue(check({}, credentials, self.enforcer)) def test_entry_not_in_list_rejected(self): check = _checks.GenericCheck('a.b.c.d', 'APPLES') credentials = {'a': {'b': {'c': {'d': ['PEACHES', 'PEARS']}}}} self.assertFalse(check({}, credentials, self.enforcer)) class FalseCheckTestCase(test_base.BaseTestCase): def test_str(self): check = _checks.FalseCheck() self.assertEqual('!', str(check)) def test_call(self): check = _checks.FalseCheck() self.assertFalse(check('target', 'creds', None)) class TrueCheckTestCase(test_base.BaseTestCase): def test_str(self): check = _checks.TrueCheck() self.assertEqual('@', str(check)) def test_call(self): check = _checks.TrueCheck() self.assertTrue(check('target', 'creds', None)) class CheckForTest(_checks.Check): def __call__(self, target, creds, enforcer): pass class CheckTestCase(test_base.BaseTestCase): def test_init(self): check = CheckForTest('kind', 'match') self.assertEqual('kind', check.kind) self.assertEqual('match', check.match) def test_str(self): check = CheckForTest('kind', 'match') self.assertEqual('kind:match', str(check)) class NotCheckTestCase(test_base.BaseTestCase): def test_init(self): check = _checks.NotCheck('rule') self.assertEqual('rule', check.rule) def test_str(self): check = _checks.NotCheck('rule') self.assertEqual('not rule', str(check)) def test_call_true(self): rule = _checks.TrueCheck() check = _checks.NotCheck(rule) self.assertFalse(check('target', 'cred', None)) def test_call_false(self): rule = _checks.FalseCheck() check = _checks.NotCheck(rule) self.assertTrue(check('target', 'cred', None)) def test_rule_takes_current_rule(self): results = [] class TestCheck(object): def __call__(self, target, cred, enforcer, current_rule=None): results.append((target, cred, enforcer, current_rule)) return True check = _checks.NotCheck(TestCheck()) self.assertFalse(check('target', 'cred', None, current_rule="a_rule")) self.assertEqual( [('target', 'cred', None, 'a_rule')], results, ) def test_rule_does_not_take_current_rule(self): results = [] class TestCheck(object): def __call__(self, target, cred, enforcer): results.append((target, cred, enforcer)) return True check = _checks.NotCheck(TestCheck()) self.assertFalse(check('target', 'cred', None, current_rule="a_rule")) self.assertEqual( [('target', 'cred', None)], results, ) class _BoolCheck(_checks.BaseCheck): def __init__(self, result): self.called = False self.result = result def __str__(self): return str(self.result) def __call__(self, target, creds, enforcer, current_rule=None): self.called = True return self.result class AndCheckTestCase(test_base.BaseTestCase): def test_init(self): check = _checks.AndCheck(['rule1', 'rule2']) self.assertEqual(['rule1', 'rule2'], check.rules) def test_add_check(self): check = _checks.AndCheck(['rule1', 'rule2']) check.add_check('rule3') self.assertEqual(['rule1', 'rule2', 'rule3'], check.rules) def test_str(self): check = _checks.AndCheck(['rule1', 'rule2']) self.assertEqual('(rule1 and rule2)', str(check)) def test_call_all_false(self): rules = [ _BoolCheck(False), _BoolCheck(False), ] check = _checks.AndCheck(rules) self.assertFalse(check('target', 'cred', None)) self.assertTrue(rules[0].called) self.assertFalse(rules[1].called) def test_call_first_true(self): rules = [ _BoolCheck(True), _BoolCheck(False), ] check = _checks.AndCheck(rules) self.assertFalse(check('target', 'cred', None)) self.assertTrue(rules[0].called) self.assertTrue(rules[1].called) def test_call_second_true(self): rules = [ _BoolCheck(False), _BoolCheck(True), ] check = _checks.AndCheck(rules) self.assertFalse(check('target', 'cred', None)) self.assertTrue(rules[0].called) self.assertFalse(rules[1].called) def test_rule_takes_current_rule(self): results = [] class TestCheck(object): def __call__(self, target, cred, enforcer, current_rule=None): results.append((target, cred, enforcer, current_rule)) return False check = _checks.AndCheck([TestCheck()]) self.assertFalse(check('target', 'cred', None, current_rule="a_rule")) self.assertEqual( [('target', 'cred', None, 'a_rule')], results, ) def test_rule_does_not_take_current_rule(self): results = [] class TestCheck(object): def __call__(self, target, cred, enforcer): results.append((target, cred, enforcer)) return False check = _checks.AndCheck([TestCheck()]) self.assertFalse(check('target', 'cred', None, current_rule="a_rule")) self.assertEqual( [('target', 'cred', None)], results, ) class OrCheckTestCase(test_base.BaseTestCase): def test_init(self): check = _checks.OrCheck(['rule1', 'rule2']) self.assertEqual(['rule1', 'rule2'], check.rules) def test_add_check(self): check = _checks.OrCheck(['rule1', 'rule2']) check.add_check('rule3') self.assertEqual(['rule1', 'rule2', 'rule3'], check.rules) def test_pop_check(self): check = _checks.OrCheck(['rule1', 'rule2', 'rule3']) rules, check1 = check.pop_check() self.assertEqual(['rule1', 'rule2'], check.rules) self.assertEqual('rule3', check1) def test_str(self): check = _checks.OrCheck(['rule1', 'rule2']) self.assertEqual('(rule1 or rule2)', str(check)) def test_call_all_false(self): rules = [ _BoolCheck(False), _BoolCheck(False), ] check = _checks.OrCheck(rules) self.assertFalse(check('target', 'cred', None)) self.assertTrue(rules[0].called) self.assertTrue(rules[1].called) def test_call_first_true(self): rules = [ _BoolCheck(True), _BoolCheck(False), ] check = _checks.OrCheck(rules) self.assertTrue(check('target', 'cred', None)) self.assertTrue(rules[0].called) self.assertFalse(rules[1].called) def test_call_second_true(self): rules = [ _BoolCheck(False), _BoolCheck(True), ] check = _checks.OrCheck(rules) self.assertTrue(check('target', 'cred', None)) self.assertTrue(rules[0].called) self.assertTrue(rules[1].called) def test_rule_takes_current_rule(self): results = [] class TestCheck(object): def __call__(self, target, cred, enforcer, current_rule=None): results.append((target, cred, enforcer, current_rule)) return False check = _checks.OrCheck([TestCheck()]) self.assertFalse(check('target', 'cred', None, current_rule="a_rule")) self.assertEqual( [('target', 'cred', None, 'a_rule')], results, ) def test_rule_does_not_take_current_rule(self): results = [] class TestCheck(object): def __call__(self, target, cred, enforcer): results.append((target, cred, enforcer)) return False check = _checks.OrCheck([TestCheck()]) self.assertFalse(check('target', 'cred', None, current_rule="a_rule")) self.assertEqual( [('target', 'cred', None)], results, ) oslo.policy-1.33.1/oslo_policy/tests/test_parser.py0000666000175100017510000005023113224676023022507 0ustar zuulzuul00000000000000# Copyright (c) 2015 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. import mock from oslotest import base as test_base import six from oslo_policy import _checks from oslo_policy import _parser from oslo_policy.tests import base class ParseCheckTestCase(test_base.BaseTestCase): def test_false(self): result = _parser._parse_check('!') self.assertIsInstance(result, _checks.FalseCheck) def test_true(self): result = _parser._parse_check('@') self.assertIsInstance(result, _checks.TrueCheck) def test_bad_rule(self): result = _parser._parse_check('foobar') self.assertIsInstance(result, _checks.FalseCheck) @mock.patch.object(_checks, 'registered_checks', {}) def test_no_handler(self): result = _parser._parse_check('no:handler') self.assertIsInstance(result, _checks.FalseCheck) @mock.patch.object(_checks, 'registered_checks', { 'spam': mock.Mock(return_value='spam_check'), None: mock.Mock(return_value='none_check'), }) def test_check(self): result = _parser._parse_check('spam:handler') self.assertEqual('spam_check', result) _checks.registered_checks['spam'].assert_called_once_with('spam', 'handler') self.assertFalse(_checks.registered_checks[None].called) @mock.patch.object(_checks, 'registered_checks', { None: mock.Mock(return_value='none_check'), }) def test_check_default(self): result = _parser._parse_check('spam:handler') self.assertEqual('none_check', result) _checks.registered_checks[None].assert_called_once_with('spam', 'handler') class ParseListRuleTestCase(test_base.BaseTestCase): def test_empty(self): result = _parser._parse_list_rule([]) self.assertIsInstance(result, _checks.TrueCheck) self.assertEqual('@', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_oneele_zeroele(self): result = _parser._parse_list_rule([[]]) self.assertIsInstance(result, _checks.FalseCheck) self.assertEqual('!', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_oneele_bare(self): result = _parser._parse_list_rule(['rule']) self.assertIsInstance(result, base.FakeCheck) self.assertEqual('rule', result.result) self.assertEqual('rule', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_oneele_oneele(self): result = _parser._parse_list_rule([['rule']]) self.assertIsInstance(result, base.FakeCheck) self.assertEqual('rule', result.result) self.assertEqual('rule', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_oneele_multi(self): result = _parser._parse_list_rule([['rule1', 'rule2']]) self.assertIsInstance(result, _checks.AndCheck) self.assertEqual(2, len(result.rules)) for i, value in enumerate(['rule1', 'rule2']): self.assertIsInstance(result.rules[i], base.FakeCheck) self.assertEqual(value, result.rules[i].result) self.assertEqual('(rule1 and rule2)', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_multi_oneele(self): result = _parser._parse_list_rule([['rule1'], ['rule2']]) self.assertIsInstance(result, _checks.OrCheck) self.assertEqual(2, len(result.rules)) for i, value in enumerate(['rule1', 'rule2']): self.assertIsInstance(result.rules[i], base.FakeCheck) self.assertEqual(value, result.rules[i].result) self.assertEqual('(rule1 or rule2)', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_multi_multi(self): result = _parser._parse_list_rule([['rule1', 'rule2'], ['rule3', 'rule4']]) self.assertIsInstance(result, _checks.OrCheck) self.assertEqual(2, len(result.rules)) for i, values in enumerate([['rule1', 'rule2'], ['rule3', 'rule4']]): self.assertIsInstance(result.rules[i], _checks.AndCheck) self.assertEqual(2, len(result.rules[i].rules)) for j, value in enumerate(values): self.assertIsInstance(result.rules[i].rules[j], base.FakeCheck) self.assertEqual(value, result.rules[i].rules[j].result) self.assertEqual('((rule1 and rule2) or (rule3 and rule4))', str(result)) class ParseTokenizeTestCase(test_base.BaseTestCase): @mock.patch.object(_parser, '_parse_check', lambda x: x) def test_tokenize(self): exemplar = ("(( ( ((() And)) or ) (check:%(miss)s) not)) " "'a-string' \"another-string\"") expected = [ ('(', '('), ('(', '('), ('(', '('), ('(', '('), ('(', '('), ('(', '('), (')', ')'), ('and', 'And'), (')', ')'), (')', ')'), ('or', 'or'), (')', ')'), ('(', '('), ('check', 'check:%(miss)s'), (')', ')'), ('not', 'not'), (')', ')'), (')', ')'), ('string', 'a-string'), ('string', 'another-string'), ] result = list(_parser._parse_tokenize(exemplar)) self.assertEqual(expected, result) class ParseStateMetaTestCase(test_base.BaseTestCase): def test_reducer(self): @_parser.reducer('a', 'b', 'c') @_parser.reducer('d', 'e', 'f') def spam(): pass self.assertTrue(hasattr(spam, 'reducers')) self.assertEqual([['d', 'e', 'f'], ['a', 'b', 'c']], spam.reducers) def test_parse_state_meta(self): @six.add_metaclass(_parser.ParseStateMeta) class FakeState(object): @_parser.reducer('a', 'b', 'c') @_parser.reducer('d', 'e', 'f') def reduce1(self): pass @_parser.reducer('g', 'h', 'i') def reduce2(self): pass self.assertTrue(hasattr(FakeState, 'reducers')) for reduction, reducer in FakeState.reducers: if (reduction == ['a', 'b', 'c'] or reduction == ['d', 'e', 'f']): self.assertEqual('reduce1', reducer) elif reduction == ['g', 'h', 'i']: self.assertEqual('reduce2', reducer) else: self.fail('Unrecognized reducer discovered') class ParseStateTestCase(test_base.BaseTestCase): def test_init(self): state = _parser.ParseState() self.assertEqual([], state.tokens) self.assertEqual([], state.values) @mock.patch.object(_parser.ParseState, 'reducers', [(['tok1'], 'meth')]) @mock.patch.object(_parser.ParseState, 'meth', create=True) def test_reduce_none(self, mock_meth): state = _parser.ParseState() state.tokens = ['tok2'] state.values = ['val2'] state.reduce() self.assertEqual(['tok2'], state.tokens) self.assertEqual(['val2'], state.values) self.assertFalse(mock_meth.called) @mock.patch.object(_parser.ParseState, 'reducers', [(['tok1', 'tok2'], 'meth')]) @mock.patch.object(_parser.ParseState, 'meth', create=True) def test_reduce_short(self, mock_meth): state = _parser.ParseState() state.tokens = ['tok1'] state.values = ['val1'] state.reduce() self.assertEqual(['tok1'], state.tokens) self.assertEqual(['val1'], state.values) self.assertFalse(mock_meth.called) @mock.patch.object(_parser.ParseState, 'reducers', [(['tok1', 'tok2'], 'meth')]) @mock.patch.object(_parser.ParseState, 'meth', create=True, return_value=[('tok3', 'val3')]) def test_reduce_one(self, mock_meth): state = _parser.ParseState() state.tokens = ['tok1', 'tok2'] state.values = ['val1', 'val2'] state.reduce() self.assertEqual(['tok3'], state.tokens) self.assertEqual(['val3'], state.values) mock_meth.assert_called_once_with('val1', 'val2') @mock.patch.object(_parser.ParseState, 'reducers', [ (['tok1', 'tok4'], 'meth2'), (['tok2', 'tok3'], 'meth1'), ]) @mock.patch.object(_parser.ParseState, 'meth1', create=True, return_value=[('tok4', 'val4')]) @mock.patch.object(_parser.ParseState, 'meth2', create=True, return_value=[('tok5', 'val5')]) def test_reduce_two(self, mock_meth2, mock_meth1): state = _parser.ParseState() state.tokens = ['tok1', 'tok2', 'tok3'] state.values = ['val1', 'val2', 'val3'] state.reduce() self.assertEqual(['tok5'], state.tokens) self.assertEqual(['val5'], state.values) mock_meth1.assert_called_once_with('val2', 'val3') mock_meth2.assert_called_once_with('val1', 'val4') @mock.patch.object(_parser.ParseState, 'reducers', [(['tok1', 'tok2'], 'meth')]) @mock.patch.object(_parser.ParseState, 'meth', create=True, return_value=[('tok3', 'val3'), ('tok4', 'val4')]) def test_reduce_multi(self, mock_meth): state = _parser.ParseState() state.tokens = ['tok1', 'tok2'] state.values = ['val1', 'val2'] state.reduce() self.assertEqual(['tok3', 'tok4'], state.tokens) self.assertEqual(['val3', 'val4'], state.values) mock_meth.assert_called_once_with('val1', 'val2') def test_shift(self): state = _parser.ParseState() with mock.patch.object(_parser.ParseState, 'reduce') as mock_reduce: state.shift('token', 'value') self.assertEqual(['token'], state.tokens) self.assertEqual(['value'], state.values) mock_reduce.assert_called_once_with() def test_result_empty(self): state = _parser.ParseState() self.assertRaises(ValueError, lambda: state.result) def test_result_unreduced(self): state = _parser.ParseState() state.tokens = ['tok1', 'tok2'] state.values = ['val1', 'val2'] self.assertRaises(ValueError, lambda: state.result) def test_result(self): state = _parser.ParseState() state.tokens = ['token'] state.values = ['value'] self.assertEqual('value', state.result) def test_wrap_check(self): state = _parser.ParseState() result = state._wrap_check('(', 'the_check', ')') self.assertEqual([('check', 'the_check')], result) @mock.patch.object(_checks, 'AndCheck', lambda x: x) def test_make_and_expr(self): state = _parser.ParseState() result = state._make_and_expr('check1', 'and', 'check2') self.assertEqual([('and_expr', ['check1', 'check2'])], result) def test_extend_and_expr(self): state = _parser.ParseState() mock_expr = mock.Mock() mock_expr.add_check.return_value = 'newcheck' result = state._extend_and_expr(mock_expr, 'and', 'check') self.assertEqual([('and_expr', 'newcheck')], result) mock_expr.add_check.assert_called_once_with('check') @mock.patch.object(_checks, 'OrCheck', lambda x: x) def test_make_or_expr(self): state = _parser.ParseState() result = state._make_or_expr('check1', 'or', 'check2') self.assertEqual([('or_expr', ['check1', 'check2'])], result) def test_extend_or_expr(self): state = _parser.ParseState() mock_expr = mock.Mock() mock_expr.add_check.return_value = 'newcheck' result = state._extend_or_expr(mock_expr, 'or', 'check') self.assertEqual([('or_expr', 'newcheck')], result) mock_expr.add_check.assert_called_once_with('check') @mock.patch.object(_checks, 'NotCheck', lambda x: 'not %s' % x) def test_make_not_expr(self): state = _parser.ParseState() result = state._make_not_expr('not', 'check') self.assertEqual([('check', 'not check')], result) class ParseTextRuleTestCase(test_base.BaseTestCase): def test_empty(self): result = _parser._parse_text_rule('') self.assertIsInstance(result, _checks.TrueCheck) @mock.patch.object(_parser, '_parse_tokenize', return_value=[('tok1', 'val1'), ('tok2', 'val2')]) @mock.patch.object(_parser.ParseState, 'shift') @mock.patch.object(_parser.ParseState, 'result', 'result') def test_shifts(self, mock_shift, mock_parse_tokenize): result = _parser._parse_text_rule('test rule') self.assertEqual('result', result) mock_parse_tokenize.assert_called_once_with('test rule') mock_shift.assert_has_calls( [mock.call('tok1', 'val1'), mock.call('tok2', 'val2')]) @mock.patch.object(_parser, '_parse_tokenize', return_value=[]) def test_fail(self, mock_parse_tokenize): result = _parser._parse_text_rule('test rule') self.assertIsInstance(result, _checks.FalseCheck) mock_parse_tokenize.assert_called_once_with('test rule') def test_A_or_B_or_C(self): result = _parser._parse_text_rule('@ or ! or @') self.assertEqual('(@ or ! or @)', str(result)) def test_A_or_B_and_C(self): result = _parser._parse_text_rule('@ or ! and @') self.assertEqual('(@ or (! and @))', str(result)) def test_A_and_B_or_C(self): result = _parser._parse_text_rule('@ and ! or @') self.assertEqual('((@ and !) or @)', str(result)) def test_A_and_B_and_C(self): result = _parser._parse_text_rule('@ and ! and @') self.assertEqual('(@ and ! and @)', str(result)) def test_A_or_B_or_C_or_D(self): result = _parser._parse_text_rule('@ or ! or @ or !') self.assertEqual('(@ or ! or @ or !)', str(result)) def test_A_or_B_or_C_and_D(self): result = _parser._parse_text_rule('@ or ! or @ and !') self.assertEqual('(@ or ! or (@ and !))', str(result)) def test_A_or_B_and_C_or_D(self): result = _parser._parse_text_rule('@ or ! and @ or !') self.assertEqual('(@ or (! and @) or !)', str(result)) def test_A_or_B_and_C_and_D(self): result = _parser._parse_text_rule('@ or ! and @ and !') self.assertEqual('(@ or (! and @ and !))', str(result)) def test_A_and_B_or_C_or_D(self): result = _parser._parse_text_rule('@ and ! or @ or !') self.assertEqual('((@ and !) or @ or !)', str(result)) def test_A_and_B_or_C_and_D(self): result = _parser._parse_text_rule('@ and ! or @ and !') self.assertEqual('((@ and !) or (@ and !))', str(result)) def test_A_and_B_and_C_or_D(self): result = _parser._parse_text_rule('@ and ! and @ or !') self.assertEqual('((@ and ! and @) or !)', str(result)) def test_A_and_B_and_C_and_D(self): result = _parser._parse_text_rule('@ and ! and @ and !') self.assertEqual('(@ and ! and @ and !)', str(result)) def test_A_and_B_or_C_with_not_1(self): result = _parser._parse_text_rule('not @ and ! or @') self.assertEqual('((not @ and !) or @)', str(result)) def test_A_and_B_or_C_with_not_2(self): result = _parser._parse_text_rule('@ and not ! or @') self.assertEqual('((@ and not !) or @)', str(result)) def test_A_and_B_or_C_with_not_3(self): result = _parser._parse_text_rule('@ and ! or not @') self.assertEqual('((@ and !) or not @)', str(result)) def test_A_and_B_or_C_with_group_1(self): for expression in ['( @ ) and ! or @', '@ and ( ! ) or @', '@ and ! or ( @ )', '( @ ) and ! or ( @ )', '@ and ( ! ) or ( @ )', '( @ ) and ( ! ) or ( @ )', '( @ and ! ) or @', '( ( @ ) and ! ) or @', '( @ and ( ! ) ) or @', '( ( @ and ! ) ) or @', '( @ and ! or @ )']: result = _parser._parse_text_rule(expression) self.assertEqual('((@ and !) or @)', str(result)) def test_A_and_B_or_C_with_group_2(self): result = _parser._parse_text_rule('@ and ( ! or @ )') self.assertEqual('(@ and (! or @))', str(result)) def test_A_and_B_or_C_with_group_and_not_1(self): for expression in ['not ( @ ) and ! or @', 'not @ and ( ! ) or @', 'not @ and ! or ( @ )', '( not @ ) and ! or @', '( not @ and ! ) or @', '( not @ and ! or @ )']: result = _parser._parse_text_rule(expression) self.assertEqual('((not @ and !) or @)', str(result)) def test_A_and_B_or_C_with_group_and_not_2(self): result = _parser._parse_text_rule('not @ and ( ! or @ )') self.assertEqual('(not @ and (! or @))', str(result)) def test_A_and_B_or_C_with_group_and_not_3(self): result = _parser._parse_text_rule('not ( @ and ! or @ )') self.assertEqual('not ((@ and !) or @)', str(result)) def test_A_and_B_or_C_with_group_and_not_4(self): for expression in ['( @ ) and not ! or @', '@ and ( not ! ) or @', '@ and not ( ! ) or @', '@ and not ! or ( @ )', '( @ and not ! ) or @', '( @ and not ! or @ )']: result = _parser._parse_text_rule(expression) self.assertEqual('((@ and not !) or @)', str(result)) def test_A_and_B_or_C_with_group_and_not_5(self): result = _parser._parse_text_rule('@ and ( not ! or @ )') self.assertEqual('(@ and (not ! or @))', str(result)) def test_A_and_B_or_C_with_group_and_not_6(self): result = _parser._parse_text_rule('@ and not ( ! or @ )') self.assertEqual('(@ and not (! or @))', str(result)) def test_A_and_B_or_C_with_group_and_not_7(self): for expression in ['( @ ) and ! or not @', '@ and ( ! ) or not @', '@ and ! or not ( @ )', '@ and ! or ( not @ )', '( @ and ! ) or not @', '( @ and ! or not @ )']: result = _parser._parse_text_rule(expression) self.assertEqual('((@ and !) or not @)', str(result)) def test_A_and_B_or_C_with_group_and_not_8(self): result = _parser._parse_text_rule('@ and ( ! or not @ )') self.assertEqual('(@ and (! or not @))', str(result)) class ParseRuleTestCase(test_base.BaseTestCase): @mock.patch.object(_parser, '_parse_text_rule', return_value='text rule') @mock.patch.object(_parser, '_parse_list_rule', return_value='list rule') def test_parse_rule_string(self, mock_parse_list_rule, mock_parse_text_rule): result = _parser.parse_rule('a string') self.assertEqual('text rule', result) self.assertFalse(mock_parse_list_rule.called) mock_parse_text_rule.assert_called_once_with('a string') @mock.patch.object(_parser, '_parse_text_rule', return_value='text rule') @mock.patch.object(_parser, '_parse_list_rule', return_value='list rule') def test_parse_rule_list(self, mock_parse_list_rule, mock_parse_text_rule): result = _parser.parse_rule([['a'], ['list']]) self.assertEqual('list rule', result) self.assertFalse(mock_parse_text_rule.called) mock_parse_list_rule.assert_called_once_with([['a'], ['list']]) oslo.policy-1.33.1/oslo_policy/tests/token_fixture.py0000666000175100017510000001371613224676023023051 0ustar zuulzuul00000000000000# Copyright (c) 2015 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. REGION_ONE_PUBLIC_KEYSTONE_ENDPOINT_ID = '8cd4b957090f4ca5842a22e9a74099cd' SCOPED_TOKEN_FIXTURE = { "token": { "methods": [ "password" ], "roles": [ { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role1" }, { "id": "f03fda8f8a3249b2a70fb1f176a7b631", "name": "role2" } ], "issued_at": "2002-01-18T21:14:07Z", "expires_at": "2038-01-18T21:14:07Z", "project": { "id": "tenant_id1", "domain": { "id": "domain_id1", "name": "domain_name1" }, "enabled": True, "description": "no description available", "name": "tenant_name1" }, "catalog": [ { "endpoints": [ { "id": "3b5e554bcf114f2483e8a1be7a0506d1", "interface": "admin", "url": "http://127.0.0.1:8776/v1/" + "64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "54abd2dc463c4ba4a72915498f8ecad1", "interface": "internal", "url": "http://127.0.0.1:8776/v1/" + "64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "70a7efa4b1b941968357cc43ae1419ee", "interface": "public", "url": "http://127.0.0.1:8776/v1/" + "64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "id": "5707c3fc0a294703a3c638e9cf6a6c3a", "type": "volume", "name": "volume" }, { "endpoints": [ { "id": "92217a3b95394492859bc49fd474382f", "interface": "admin", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "f20563bdf66f4efa8a1f11d99b672be1", "interface": "internal", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" }, { "id": "375f9ba459a447738fb60fe5fc26e9aa", "interface": "public", "url": "http://127.0.0.1:9292/v1", "region": "regionOne" } ], "id": "15c21aae6b274a8da52e0a068e908aac", "type": "image", "name": "glance" }, { "endpoints": [ { "id": "edbd9f50f66746ae9ed11dc3b1ae35da", "interface": "admin", "url": "http://127.0.0.1:8774/v1.1/" + "64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "9e03c46c80a34a159cb39f5cb0498b92", "interface": "internal", "url": "http://127.0.0.1:8774/v1.1/" + "64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" }, { "id": "1df0b44d92634d59bd0e0d60cf7ce432", "interface": "public", "url": "http://127.0.0.1:8774/v1.1/" + "64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne" } ], "id": "2f404fdb89154c589efbc10726b029ec", "type": "compute", "name": "nova" }, { "endpoints": [ { "id": "a4501e141a4b4e14bf282e7bffd81dc5", "interface": "admin", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": "3d17e3227bfc4483b58de5eaa584e360", "interface": "internal", "url": "http://127.0.0.1:35357/v3", "region": "RegionOne" }, { "id": REGION_ONE_PUBLIC_KEYSTONE_ENDPOINT_ID, "interface": "public", "url": "http://127.0.0.1:5000/v3", "region": "RegionOne" } ], "id": "c5d926d566424e4fba4f80c37916cde5", "type": "identity", "name": "keystone" } ], "user": { "domain": { "id": "domain_id1", "name": "domain_name1" }, "name": "user_name1", "id": "user_id1" } } } oslo.policy-1.33.1/oslo_policy/tests/test_policy.py0000666000175100017510000013704513224676041022523 0ustar zuulzuul00000000000000# Copyright (c) 2012 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 of Policy Engine""" import os import mock from oslo_config import cfg from oslo_serialization import jsonutils from oslotest import base as test_base import six from oslo_policy import _cache_handler from oslo_policy import _checks from oslo_policy import _parser from oslo_policy import policy from oslo_policy.tests import base POLICY_A_CONTENTS = jsonutils.dumps({"default": "role:fakeA"}) POLICY_B_CONTENTS = jsonutils.dumps({"default": "role:fakeB"}) POLICY_FAKE_CONTENTS = jsonutils.dumps({"default": "role:fakeC"}) POLICY_JSON_CONTENTS = jsonutils.dumps({ "default": "rule:admin", "admin": "is_admin:True" }) @_checks.register('field') class FieldCheck(_checks.Check): """A non reversible check. All oslo.policy defined checks have a __str__ method with the property that rule == str(_parser.parse_rule(rule)). Consumers of oslo.policy may have defined checks for which that does not hold true. This FieldCheck is not reversible so we can use it for testing to ensure that this type of check does not break anything. """ def __init__(self, kind, match): # Process the match resource, field_value = match.split(':', 1) field, value = field_value.split('=', 1) super(FieldCheck, self).__init__(kind, '%s:%s:%s' % (resource, field, value)) self.field = field self.value = value def __call__(self, target_dict, cred_dict, enforcer): return True class MyException(Exception): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs class RulesTestCase(test_base.BaseTestCase): def test_init_basic(self): rules = policy.Rules() self.assertEqual({}, rules) self.assertIsNone(rules.default_rule) def test_init(self): rules = policy.Rules(dict(a=1, b=2, c=3), 'a') self.assertEqual(dict(a=1, b=2, c=3), rules) self.assertEqual('a', rules.default_rule) def test_no_default(self): rules = policy.Rules(dict(a=1, b=2, c=3)) self.assertRaises(KeyError, lambda: rules['d']) def test_missing_default(self): rules = policy.Rules(dict(a=1, c=3), 'b') self.assertRaises(KeyError, lambda: rules['d']) def test_with_default(self): rules = policy.Rules(dict(a=1, b=2, c=3), 'b') self.assertEqual(2, rules['d']) def test_retrieval(self): rules = policy.Rules(dict(a=1, b=2, c=3), 'b') self.assertEqual(1, rules['a']) self.assertEqual(2, rules['b']) self.assertEqual(3, rules['c']) @mock.patch.object(_parser, 'parse_rule', lambda x: x) def test_load_json(self): exemplar = jsonutils.dumps({ "admin_or_owner": [["role:admin"], ["project_id:%(project_id)s"]], "default": [] }) rules = policy.Rules.load(exemplar, 'default') self.assertEqual('default', rules.default_rule) self.assertEqual(dict( admin_or_owner=[['role:admin'], ['project_id:%(project_id)s']], default=[], ), rules) @mock.patch.object(_parser, 'parse_rule', lambda x: x) def test_load_json_invalid_exc(self): # When the JSON isn't valid, ValueError is raised on load_json. # Note the trailing , in the exemplar is invalid JSON. exemplar = """{ "admin_or_owner": [["role:admin"], ["project_id:%(project_id)s"]], "default": [ }""" self.assertRaises(ValueError, policy.Rules.load, exemplar, 'default') @mock.patch.object(_parser, 'parse_rule', lambda x: x) def test_load_empty_data(self): result = policy.Rules.load('', 'default') self.assertEqual(result, {}) @mock.patch.object(_parser, 'parse_rule', lambda x: x) def test_load_yaml(self): # Test that simplified YAML can be used with load(). # Show that YAML allows useful comments. exemplar = """ # Define a custom rule. admin_or_owner: role:admin or project_id:%(project_id)s # The default rule is used when there's no action defined. default: [] """ rules = policy.Rules.load(exemplar, 'default') self.assertEqual('default', rules.default_rule) self.assertEqual(dict( admin_or_owner='role:admin or project_id:%(project_id)s', default=[], ), rules) @mock.patch.object(_parser, 'parse_rule', lambda x: x) def test_load_yaml_invalid_exc(self): # When the JSON isn't valid, ValueError is raised on load(). # Note the trailing , in the exemplar is invalid JSON. exemplar = """{ # Define a custom rule. admin_or_owner: role:admin or project_id:%(project_id)s # The default rule is used when there's no action defined. default: [ }""" self.assertRaises(ValueError, policy.Rules.load, exemplar, 'default') @mock.patch.object(_parser, 'parse_rule', lambda x: x) def test_from_dict(self): expected = {'admin_or_owner': 'role:admin', 'default': '@'} rules = policy.Rules.from_dict(expected, 'default') self.assertEqual('default', rules.default_rule) self.assertEqual(expected, rules) def test_str(self): exemplar = jsonutils.dumps({ "admin_or_owner": "role:admin or project_id:%(project_id)s" }, indent=4) rules = policy.Rules(dict( admin_or_owner='role:admin or project_id:%(project_id)s', )) self.assertEqual(exemplar, str(rules)) def test_str_true(self): exemplar = jsonutils.dumps({ "admin_or_owner": "" }, indent=4) rules = policy.Rules(dict( admin_or_owner=_checks.TrueCheck(), )) self.assertEqual(exemplar, str(rules)) def test_load_json_deprecated(self): with self.assertWarnsRegex(DeprecationWarning, r'load_json\(\).*load\(\)'): policy.Rules.load_json(jsonutils.dumps({'default': ''}, 'default')) class EnforcerTest(base.PolicyBaseTestCase): def setUp(self): super(EnforcerTest, self).setUp() self.create_config_file('policy.json', POLICY_JSON_CONTENTS) def check_loaded_files(self, filenames): self.assertEqual( [self.get_config_file_fullname(n) for n in filenames], self.enforcer._loaded_files ) def _test_scenario_with_opts_registered(self, scenario, *args, **kwargs): # This test registers some rules, calls the scenario and then checks # the registered rules. The scenario should be a method which loads # policy files containing POLICY_*_CONTENTS defined above. They should # be loaded on the self.enforcer object. # This should be overridden by the policy file self.enforcer.register_default(policy.RuleDefault(name='admin', check_str='is_admin:False')) # This is not in the policy file, only registered self.enforcer.register_default(policy.RuleDefault(name='owner', check_str='role:owner')) scenario(*args, **kwargs) self.assertIn('owner', self.enforcer.rules) self.assertEqual('role:owner', str(self.enforcer.rules['owner'])) self.assertEqual('is_admin:True', str(self.enforcer.rules['admin'])) self.assertIn('owner', self.enforcer.registered_rules) self.assertIn('admin', self.enforcer.registered_rules) self.assertNotIn('default', self.enforcer.registered_rules) self.assertNotIn('owner', self.enforcer.file_rules) self.assertIn('admin', self.enforcer.file_rules) self.assertIn('default', self.enforcer.file_rules) def test_load_file(self): self.conf.set_override('policy_dirs', [], group='oslo_policy') self.enforcer.load_rules(True) self.assertIsNotNone(self.enforcer.rules) self.assertIn('default', self.enforcer.rules) self.assertIn('admin', self.enforcer.rules) self.assertEqual('is_admin:True', str(self.enforcer.rules['admin'])) def test_load_file_opts_registered(self): self._test_scenario_with_opts_registered(self.test_load_file) def test_load_directory(self): self.create_config_file( os.path.join('policy.d', 'a.conf'), POLICY_A_CONTENTS) self.create_config_file( os.path.join('policy.d', 'b.conf'), POLICY_B_CONTENTS) self.enforcer.load_rules(True) self.assertIsNotNone(self.enforcer.rules) loaded_rules = jsonutils.loads(str(self.enforcer.rules)) self.assertEqual('role:fakeB', loaded_rules['default']) self.assertEqual('is_admin:True', loaded_rules['admin']) self.check_loaded_files([ 'policy.json', os.path.join('policy.d', 'a.conf'), os.path.join('policy.d', 'b.conf'), ]) def test_load_directory_opts_registered(self): self._test_scenario_with_opts_registered(self.test_load_directory) def test_load_directory_caching_with_files_updated(self): self.create_config_file( os.path.join('policy.d', 'a.conf'), POLICY_A_CONTENTS) self.enforcer.load_rules(False) self.assertIsNotNone(self.enforcer.rules) old = six.next(six.itervalues( self.enforcer._policy_dir_mtimes)) self.assertEqual(1, len(self.enforcer._policy_dir_mtimes)) # Touch the file conf_path = os.path.join(self.config_dir, os.path.join( 'policy.d', 'a.conf')) stinfo = os.stat(conf_path) os.utime(conf_path, (stinfo.st_atime + 10, stinfo.st_mtime + 10)) self.enforcer.load_rules(False) self.assertEqual(1, len(self.enforcer._policy_dir_mtimes)) self.assertEqual(old, six.next(six.itervalues( self.enforcer._policy_dir_mtimes))) loaded_rules = jsonutils.loads(str(self.enforcer.rules)) self.assertEqual('is_admin:True', loaded_rules['admin']) self.check_loaded_files([ 'policy.json', os.path.join('policy.d', 'a.conf'), os.path.join('policy.d', 'a.conf'), ]) def test_load_directory_caching_with_files_updated_opts_registered(self): self._test_scenario_with_opts_registered( self.test_load_directory_caching_with_files_updated) def test_load_directory_caching_with_files_same(self, overwrite=True): self.enforcer.overwrite = overwrite self.create_config_file( os.path.join('policy.d', 'a.conf'), POLICY_A_CONTENTS) self.enforcer.load_rules(False) self.assertIsNotNone(self.enforcer.rules) old = six.next(six.itervalues( self.enforcer._policy_dir_mtimes)) self.assertEqual(1, len(self.enforcer._policy_dir_mtimes)) self.enforcer.load_rules(False) self.assertEqual(1, len(self.enforcer._policy_dir_mtimes)) self.assertEqual(old, six.next(six.itervalues( self.enforcer._policy_dir_mtimes))) loaded_rules = jsonutils.loads(str(self.enforcer.rules)) self.assertEqual('is_admin:True', loaded_rules['admin']) self.check_loaded_files([ 'policy.json', os.path.join('policy.d', 'a.conf'), ]) def test_load_directory_caching_with_files_same_but_overwrite_false(self): self.test_load_directory_caching_with_files_same(overwrite=False) def test_load_directory_caching_with_files_same_opts_registered(self): self._test_scenario_with_opts_registered( self.test_load_directory_caching_with_files_same) def test_load_dir_caching_with_files_same_overwrite_false_opts_reg(self): # Very long test name makes this difficult test = getattr(self, 'test_load_directory_caching_with_files_same_but_overwrite_false') # NOQA self._test_scenario_with_opts_registered(test) def test_load_multiple_directories(self): self.create_config_file( os.path.join('policy.d', 'a.conf'), POLICY_A_CONTENTS) self.create_config_file( os.path.join('policy.d', 'b.conf'), POLICY_B_CONTENTS) self.create_config_file( os.path.join('policy.2.d', 'fake.conf'), POLICY_FAKE_CONTENTS) self.conf.set_override('policy_dirs', ['policy.d', 'policy.2.d'], group='oslo_policy') self.enforcer.load_rules(True) self.assertIsNotNone(self.enforcer.rules) loaded_rules = jsonutils.loads(str(self.enforcer.rules)) self.assertEqual('role:fakeC', loaded_rules['default']) self.assertEqual('is_admin:True', loaded_rules['admin']) self.check_loaded_files([ 'policy.json', os.path.join('policy.d', 'a.conf'), os.path.join('policy.d', 'b.conf'), os.path.join('policy.2.d', 'fake.conf'), ]) def test_load_multiple_directories_opts_registered(self): self._test_scenario_with_opts_registered( self.test_load_multiple_directories) def test_load_non_existed_directory(self): self.create_config_file( os.path.join('policy.d', 'a.conf'), POLICY_A_CONTENTS) self.conf.set_override('policy_dirs', ['policy.d', 'policy.x.d'], group='oslo_policy') self.enforcer.load_rules(True) self.assertIsNotNone(self.enforcer.rules) self.assertIn('default', self.enforcer.rules) self.assertIn('admin', self.enforcer.rules) self.check_loaded_files( ['policy.json', os.path.join('policy.d', 'a.conf')]) def test_load_non_existed_directory_opts_registered(self): self._test_scenario_with_opts_registered( self.test_load_non_existed_directory) def test_load_policy_dirs_with_non_directory(self): self.create_config_file( os.path.join('policy.d', 'a.conf'), POLICY_A_CONTENTS) self.conf.set_override('policy_dirs', [os.path.join('policy.d', 'a.conf')], group='oslo_policy') self.assertRaises(ValueError, self.enforcer.load_rules, True) def test_set_rules_type(self): self.assertRaises(TypeError, self.enforcer.set_rules, 'dummy') @mock.patch.object(_cache_handler, 'delete_cached_file', mock.Mock()) def test_clear(self): # Make sure the rules are reset self.enforcer.rules = 'spam' self.enforcer.clear() self.assertEqual({}, self.enforcer.rules) self.assertIsNone(self.enforcer.default_rule) self.assertIsNone(self.enforcer.policy_path) def test_clear_opts_registered(self): # This should be overridden by the policy file self.enforcer.register_default(policy.RuleDefault(name='admin', check_str='is_admin:False')) # This is not in the policy file, only registered self.enforcer.register_default(policy.RuleDefault(name='owner', check_str='role:owner')) self.test_clear() self.assertEqual({}, self.enforcer.registered_rules) def test_rule_with_check(self): rules_json = jsonutils.dumps({ "deny_stack_user": "not role:stack_user", "cloudwatch:PutMetricData": "" }) rules = policy.Rules.load(rules_json) self.enforcer.set_rules(rules) action = 'cloudwatch:PutMetricData' creds = {'roles': ''} self.assertTrue(self.enforcer.enforce(action, {}, creds)) def test_enforcer_with_default_rule(self): rules_json = jsonutils.dumps({ "deny_stack_user": "not role:stack_user", "cloudwatch:PutMetricData": "" }) rules = policy.Rules.load(rules_json) default_rule = _checks.TrueCheck() enforcer = policy.Enforcer(self.conf, default_rule=default_rule) enforcer.set_rules(rules) action = 'cloudwatch:PutMetricData' creds = {'roles': ''} self.assertTrue(enforcer.enforce(action, {}, creds)) def test_enforcer_force_reload_with_overwrite(self, opts_registered=0): self.create_config_file( os.path.join('policy.d', 'a.conf'), POLICY_A_CONTENTS) self.create_config_file( os.path.join('policy.d', 'b.conf'), POLICY_B_CONTENTS) # Prepare in memory fake policies. self.enforcer.set_rules({'test': _parser.parse_rule('role:test')}, use_conf=True) self.enforcer.set_rules({'default': _parser.parse_rule('role:fakeZ')}, overwrite=False, # Keeps 'test' role. use_conf=True) self.enforcer.overwrite = True # Call enforce(), it will load rules from # policy configuration files, to overwrite # existing fake ones. self.assertFalse(self.enforcer.enforce('test', {}, {'roles': ['test']})) self.assertTrue(self.enforcer.enforce('default', {}, {'roles': ['fakeB']})) # Check against rule dict again from # enforcer object directly. self.assertNotIn('test', self.enforcer.rules) self.assertIn('default', self.enforcer.rules) self.assertIn('admin', self.enforcer.rules) loaded_rules = jsonutils.loads(str(self.enforcer.rules)) self.assertEqual(2 + opts_registered, len(loaded_rules)) self.assertIn('role:fakeB', loaded_rules['default']) self.assertIn('is_admin:True', loaded_rules['admin']) def test_enforcer_force_reload_with_overwrite_opts_registered(self): self._test_scenario_with_opts_registered( self.test_enforcer_force_reload_with_overwrite, opts_registered=1) def test_enforcer_force_reload_without_overwrite(self, opts_registered=0): self.create_config_file( os.path.join('policy.d', 'a.conf'), POLICY_A_CONTENTS) self.create_config_file( os.path.join('policy.d', 'b.conf'), POLICY_B_CONTENTS) # Prepare in memory fake policies. self.enforcer.set_rules({'test': _parser.parse_rule('role:test')}, use_conf=True) self.enforcer.set_rules({'default': _parser.parse_rule('role:fakeZ')}, overwrite=False, # Keeps 'test' role. use_conf=True) self.enforcer.overwrite = False self.enforcer._is_directory_updated = lambda x, y: True # Call enforce(), it will load rules from # policy configuration files, to merge with # existing fake ones. self.assertTrue(self.enforcer.enforce('test', {}, {'roles': ['test']})) # The existing rules have a same key with # new loaded ones will be overwrote. self.assertFalse(self.enforcer.enforce('default', {}, {'roles': ['fakeZ']})) # Check against rule dict again from # enforcer object directly. self.assertIn('test', self.enforcer.rules) self.assertIn('default', self.enforcer.rules) self.assertIn('admin', self.enforcer.rules) loaded_rules = jsonutils.loads(str(self.enforcer.rules)) self.assertEqual(3 + opts_registered, len(loaded_rules)) self.assertIn('role:test', loaded_rules['test']) self.assertIn('role:fakeB', loaded_rules['default']) self.assertIn('is_admin:True', loaded_rules['admin']) def test_enforcer_force_reload_without_overwrite_opts_registered(self): self._test_scenario_with_opts_registered( self.test_enforcer_force_reload_without_overwrite, opts_registered=1) def test_enforcer_keep_use_conf_flag_after_reload(self): self.create_config_file( os.path.join('policy.d', 'a.conf'), POLICY_A_CONTENTS) self.create_config_file( os.path.join('policy.d', 'b.conf'), POLICY_B_CONTENTS) self.assertTrue(self.enforcer.use_conf) self.assertTrue(self.enforcer.enforce('default', {}, {'roles': ['fakeB']})) self.assertFalse(self.enforcer.enforce('test', {}, {'roles': ['test']})) # After enforcement the flag should # be remained there. self.assertTrue(self.enforcer.use_conf) self.assertFalse(self.enforcer.enforce('_dynamic_test_rule', {}, {'roles': ['test']})) # Then if configure file got changed, # reloading will be triggered when calling # enforcer(), this case could happen only # when use_conf flag equals True. rules = jsonutils.loads(str(self.enforcer.rules)) rules['_dynamic_test_rule'] = 'role:test' with open(self.enforcer.policy_path, 'w') as f: f.write(jsonutils.dumps(rules)) self.enforcer.load_rules(force_reload=True) self.assertTrue(self.enforcer.enforce('_dynamic_test_rule', {}, {'roles': ['test']})) def test_enforcer_keep_use_conf_flag_after_reload_opts_registered(self): # This test does not use _test_scenario_with_opts_registered because # it loads all rules and then dumps them to a policy file and reloads. # That breaks the ability to differentiate between registered and file # loaded policies. # This should be overridden by the policy file self.enforcer.register_default(policy.RuleDefault(name='admin', check_str='is_admin:False')) # This is not in the policy file, only registered self.enforcer.register_default(policy.RuleDefault(name='owner', check_str='role:owner')) self.test_enforcer_keep_use_conf_flag_after_reload() self.assertIn('owner', self.enforcer.rules) self.assertEqual('role:owner', str(self.enforcer.rules['owner'])) self.assertEqual('is_admin:True', str(self.enforcer.rules['admin'])) def test_enforcer_force_reload_false(self): self.enforcer.set_rules({'test': 'test'}) self.enforcer.load_rules(force_reload=False) self.assertIn('test', self.enforcer.rules) self.assertNotIn('default', self.enforcer.rules) self.assertNotIn('admin', self.enforcer.rules) def test_enforcer_overwrite_rules(self): self.enforcer.set_rules({'test': 'test'}) self.enforcer.set_rules({'test': 'test1'}, overwrite=True) self.assertEqual({'test': 'test1'}, self.enforcer.rules) def test_enforcer_update_rules(self): self.enforcer.set_rules({'test': 'test'}) self.enforcer.set_rules({'test1': 'test1'}, overwrite=False) self.assertEqual({'test': 'test', 'test1': 'test1'}, self.enforcer.rules) def test_enforcer_with_default_policy_file(self): enforcer = policy.Enforcer(self.conf) self.assertEqual(self.conf.oslo_policy.policy_file, enforcer.policy_file) def test_enforcer_with_policy_file(self): enforcer = policy.Enforcer(self.conf, policy_file='non-default.json') self.assertEqual('non-default.json', enforcer.policy_file) def test_get_policy_path_raises_exc(self): enforcer = policy.Enforcer(self.conf, policy_file='raise_error.json') e = self.assertRaises(cfg.ConfigFilesNotFoundError, enforcer._get_policy_path, enforcer.policy_file) self.assertEqual(('raise_error.json', ), e.config_files) def test_enforcer_set_rules(self): self.enforcer.load_rules() self.enforcer.set_rules({'test': 'test1'}) self.enforcer.load_rules() self.assertEqual({'test': 'test1'}, self.enforcer.rules) def test_enforcer_default_rule_name(self): enforcer = policy.Enforcer(self.conf, default_rule='foo_rule') self.assertEqual('foo_rule', enforcer.rules.default_rule) self.conf.set_override('policy_default_rule', 'bar_rule', group='oslo_policy') enforcer = policy.Enforcer(self.conf, default_rule='foo_rule') self.assertEqual('foo_rule', enforcer.rules.default_rule) enforcer = policy.Enforcer(self.conf, ) self.assertEqual('bar_rule', enforcer.rules.default_rule) def test_enforcer_register_twice_raises(self): self.enforcer.register_default(policy.RuleDefault(name='owner', check_str='role:owner')) self.assertRaises(policy.DuplicatePolicyError, self.enforcer.register_default, policy.RuleDefault(name='owner', check_str='role:owner')) def test_non_reversible_check(self): self.create_config_file('policy.json', jsonutils.dumps( {'shared': 'field:networks:shared=True'})) # load_rules succeeding without error is the focus of this test self.enforcer.load_rules(True) self.assertIsNotNone(self.enforcer.rules) loaded_rules = jsonutils.loads(str(self.enforcer.rules)) self.assertNotEqual('field:networks:shared=True', loaded_rules['shared']) def test_authorize_opt_registered(self): self.enforcer.register_default(policy.RuleDefault(name='test', check_str='role:test')) self.assertTrue(self.enforcer.authorize('test', {}, {'roles': ['test']})) def test_authorize_opt_not_registered(self): self.assertRaises(policy.PolicyNotRegistered, self.enforcer.authorize, 'test', {}, {'roles': ['test']}) class EnforcerNoPolicyFileTest(base.PolicyBaseTestCase): def setUp(self): super(EnforcerNoPolicyFileTest, self).setUp() def check_loaded_files(self, filenames): self.assertEqual( [self.get_config_file_fullname(n) for n in filenames], self.enforcer._loaded_files ) def test_load_rules(self): # Check that loading rules with no policy file does not error self.enforcer.load_rules(True) self.assertIsNotNone(self.enforcer.rules) self.assertEqual(0, len(self.enforcer.rules)) def test_opts_registered(self): self.enforcer.register_default(policy.RuleDefault(name='admin', check_str='is_admin:False')) self.enforcer.register_default(policy.RuleDefault(name='owner', check_str='role:owner')) self.enforcer.load_rules(True) self.assertEqual({}, self.enforcer.file_rules) self.assertEqual('role:owner', str(self.enforcer.rules['owner'])) self.assertEqual('is_admin:False', str(self.enforcer.rules['admin'])) def test_load_directory(self): self.create_config_file('policy.d/a.conf', POLICY_JSON_CONTENTS) self.create_config_file('policy.d/b.conf', POLICY_B_CONTENTS) self.enforcer.load_rules(True) self.assertIsNotNone(self.enforcer.rules) loaded_rules = jsonutils.loads(str(self.enforcer.rules)) self.assertEqual('role:fakeB', loaded_rules['default']) self.assertEqual('is_admin:True', loaded_rules['admin']) self.check_loaded_files([ 'policy.d/a.conf', 'policy.d/b.conf', ]) class CheckFunctionTestCase(base.PolicyBaseTestCase): def setUp(self): super(CheckFunctionTestCase, self).setUp() self.create_config_file('policy.json', POLICY_JSON_CONTENTS) def test_check_explicit(self): rule = base.FakeCheck() result = self.enforcer.enforce(rule, 'target', 'creds') self.assertEqual(('target', 'creds', self.enforcer), result) def test_check_no_rules(self): # Clear the policy.json file created in setUp() self.create_config_file('policy.json', "{}") self.enforcer.default_rule = None self.enforcer.load_rules() result = self.enforcer.enforce('rule', 'target', 'creds') self.assertFalse(result) def test_check_with_rule(self): self.enforcer.set_rules(dict(default=base.FakeCheck())) creds = {} result = self.enforcer.enforce('default', 'target', creds) self.assertEqual(('target', creds, self.enforcer), result) def test_check_rule_not_exist_not_empty_policy_file(self): # If the rule doesn't exist, then enforce() fails rather than KeyError. # This test needs a non-empty file otherwise the code short-circuits. self.create_config_file('policy.json', jsonutils.dumps({"a_rule": []})) self.enforcer.default_rule = None self.enforcer.load_rules() result = self.enforcer.enforce('rule', 'target', 'creds') self.assertFalse(result) def test_check_raise_default(self): # When do_raise=True and exc is not used then PolicyNotAuthorized is # raised. self.enforcer.set_rules(dict(default=_checks.FalseCheck())) creds = {} self.assertRaisesRegex(policy.PolicyNotAuthorized, " is disallowed by policy", self.enforcer.enforce, 'rule', 'target', creds, True) def test_check_raise_custom_exception(self): self.enforcer.set_rules(dict(default=_checks.FalseCheck())) creds = {} exc = self.assertRaises( MyException, self.enforcer.enforce, 'rule', 'target', creds, True, MyException, 'arg1', 'arg2', kw1='kwarg1', kw2='kwarg2') self.assertEqual(('arg1', 'arg2'), exc.args) self.assertEqual(dict(kw1='kwarg1', kw2='kwarg2'), exc.kwargs) class RegisterCheckTestCase(base.PolicyBaseTestCase): @mock.patch.object(_checks, 'registered_checks', {}) def test_register_check(self): class TestCheck(policy.Check): pass policy.register('spam', TestCheck) self.assertEqual(dict(spam=TestCheck), _checks.registered_checks) class BaseCheckTypesTestCase(base.PolicyBaseTestCase): @mock.patch.object(_checks, 'registered_checks', {}) def test_base_check_types_are_public(self): '''Check that those check types are part of public API. They are blessed to be used by library consumers. ''' for check_type in (policy.AndCheck, policy.NotCheck, policy.OrCheck, policy.RuleCheck): class TestCheck(check_type): pass check_str = str(check_type) policy.register(check_str, TestCheck) self.assertEqual( TestCheck, _checks.registered_checks[check_str], message='%s check type is not public.' % check_str) class RuleDefaultTestCase(base.PolicyBaseTestCase): def test_rule_is_parsed(self): opt = policy.RuleDefault(name='foo', check_str='rule:foo') self.assertIsInstance(opt.check, _checks.BaseCheck) self.assertEqual('rule:foo', str(opt.check)) def test_str(self): opt = policy.RuleDefault(name='foo', check_str='rule:foo') self.assertEqual('"foo": "rule:foo"', str(opt)) def test_equality_obvious(self): opt1 = policy.RuleDefault(name='foo', check_str='rule:foo', description='foo') opt2 = policy.RuleDefault(name='foo', check_str='rule:foo', description='bar') self.assertEqual(opt1, opt2) def test_equality_less_obvious(self): opt1 = policy.RuleDefault(name='foo', check_str='', description='foo') opt2 = policy.RuleDefault(name='foo', check_str='@', description='bar') self.assertEqual(opt1, opt2) def test_not_equal_check(self): opt1 = policy.RuleDefault(name='foo', check_str='rule:foo', description='foo') opt2 = policy.RuleDefault(name='foo', check_str='rule:bar', description='bar') self.assertNotEqual(opt1, opt2) def test_not_equal_name(self): opt1 = policy.RuleDefault(name='foo', check_str='rule:foo', description='foo') opt2 = policy.RuleDefault(name='bar', check_str='rule:foo', description='bar') self.assertNotEqual(opt1, opt2) def test_not_equal_class(self): class NotRuleDefault(object): def __init__(self, name, check_str): self.name = name self.check = _parser.parse_rule(check_str) opt1 = policy.RuleDefault(name='foo', check_str='rule:foo') opt2 = NotRuleDefault(name='foo', check_str='rule:foo') self.assertNotEqual(opt1, opt2) def test_equal_subclass(self): class RuleDefaultSub(policy.RuleDefault): pass opt1 = policy.RuleDefault(name='foo', check_str='rule:foo') opt2 = RuleDefaultSub(name='foo', check_str='rule:foo') self.assertEqual(opt1, opt2) def test_not_equal_subclass(self): class RuleDefaultSub(policy.RuleDefault): pass opt1 = policy.RuleDefault(name='foo', check_str='rule:foo') opt2 = RuleDefaultSub(name='bar', check_str='rule:foo') self.assertNotEqual(opt1, opt2) def test_create_opt_with_scope_types(self): scope_types = ['project'] opt = policy.RuleDefault( name='foo', check_str='role:bar', scope_types=scope_types ) self.assertEqual(opt.scope_types, scope_types) def test_create_opt_with_scope_type_strings_fails(self): self.assertRaises( ValueError, policy.RuleDefault, name='foo', check_str='role:bar', scope_types='project' ) def test_create_opt_with_multiple_scope_types(self): opt = policy.RuleDefault( name='foo', check_str='role:bar', scope_types=['project', 'system'] ) self.assertEqual(opt.scope_types, ['project', 'system']) def test_ensure_scope_types_are_unique(self): self.assertRaises( ValueError, policy.RuleDefault, name='foo', check_str='role:bar', scope_types=['project', 'project'] ) class DocumentedRuleDefaultDeprecationTestCase(base.PolicyBaseTestCase): def test_deprecate_a_policy_check_string(self): deprecated_rule = policy.DeprecatedRule( name='foo:create_bar', check_str='role:fizz' ) rule_list = [policy.DocumentedRuleDefault( name='foo:create_bar', check_str='role:bang', description='Create a bar.', operations=[{'path': '/v1/bars', 'method': 'POST'}], deprecated_rule=deprecated_rule, deprecated_reason='"role:bang" is a better default', deprecated_since='N' )] enforcer = policy.Enforcer(self.conf) enforcer.register_defaults(rule_list) expected_msg = ( 'Policy "foo:create_bar":"role:fizz" was deprecated in N in favor ' 'of "foo:create_bar":"role:bang". Reason: "role:bang" is a better ' 'default. Either ensure your deployment is ready for the new ' 'default or copy/paste the deprecated policy into your policy ' 'file and maintain it manually.' ) with mock.patch('warnings.warn') as mock_warn: enforcer.load_rules() mock_warn.assert_called_once_with(expected_msg) def test_deprecate_a_policy_name(self): deprecated_rule = policy.DeprecatedRule( name='foo:bar', check_str='role:baz' ) rule_list = [policy.DocumentedRuleDefault( name='foo:create_bar', check_str='role:baz', description='Create a bar.', operations=[{'path': '/v1/bars/', 'method': 'POST'}], deprecated_rule=deprecated_rule, deprecated_reason=( '"foo:bar" is not granular enough. If your deployment has ' 'overridden "foo:bar", ensure you override the new policies ' 'with same role or rule. Not doing this will require the ' 'service to assume the new defaults for "foo:bar:create", ' '"foo:bar:update", "foo:bar:list", and "foo:bar:delete", ' 'which might be backwards incompatible for your deployment' ), deprecated_since='N' )] expected_msg = ( 'Policy "foo:bar":"role:baz" was deprecated in N in favor of ' '"foo:create_bar":"role:baz". Reason: "foo:bar" is not granular ' 'enough. If your deployment has overridden "foo:bar", ensure you ' 'override the new policies with same role or rule. Not doing this ' 'will require the service to assume the new defaults for ' '"foo:bar:create", "foo:bar:update", "foo:bar:list", and ' '"foo:bar:delete", which might be backwards incompatible for your ' 'deployment. Either ensure your deployment is ready for the new ' 'default or copy/paste the deprecated policy into your policy ' 'file and maintain it manually.' ) rules = jsonutils.dumps({'foo:bar': 'role:bang'}) self.create_config_file('policy.json', rules) enforcer = policy.Enforcer(self.conf) enforcer.register_defaults(rule_list) with mock.patch('warnings.warn') as mock_warn: enforcer.load_rules(True) mock_warn.assert_called_once_with(expected_msg) def test_deprecate_a_policy_for_removal_logs_warning_when_overridden(self): rule_list = [policy.DocumentedRuleDefault( name='foo:bar', check_str='role:baz', description='Create a foo.', operations=[{'path': '/v1/foos/', 'method': 'POST'}], deprecated_for_removal=True, deprecated_reason=( '"foo:bar" is no longer a policy used by the service' ), deprecated_since='N' )] expected_msg = ( 'Policy "foo:bar":"role:baz" was deprecated for removal in N. ' 'Reason: "foo:bar" is no longer a policy used by the service. Its ' 'value may be silently ignored in the future.' ) rules = jsonutils.dumps({'foo:bar': 'role:bang'}) self.create_config_file('policy.json', rules) enforcer = policy.Enforcer(self.conf) enforcer.register_defaults(rule_list) with mock.patch('warnings.warn') as mock_warn: enforcer.load_rules() mock_warn.assert_called_once_with(expected_msg) def test_deprecate_a_policy_for_removal_does_not_log_warning(self): # We should only log a warning for operators if they are supplying an # override for a policy that is deprecated for removal. rule_list = [policy.DocumentedRuleDefault( name='foo:bar', check_str='role:baz', description='Create a foo.', operations=[{'path': '/v1/foos/', 'method': 'POST'}], deprecated_for_removal=True, deprecated_reason=( '"foo:bar" is no longer a policy used by the service' ), deprecated_since='N' )] enforcer = policy.Enforcer(self.conf) enforcer.register_defaults(rule_list) with mock.patch('warnings.warn') as mock_warn: enforcer.load_rules() mock_warn.assert_not_called() def test_deprecated_policy_for_removal_must_include_deprecated_since(self): self.assertRaises( ValueError, policy.DocumentedRuleDefault, name='foo:bar', check_str='rule:baz', description='Create a foo.', operations=[{'path': '/v1/foos/', 'method': 'POST'}], deprecated_for_removal=True, deprecated_reason='Some reason.' ) def test_deprecated_policy_must_include_deprecated_since(self): deprecated_rule = policy.DeprecatedRule( name='foo:bar', check_str='rule:baz' ) self.assertRaises( ValueError, policy.DocumentedRuleDefault, name='foo:bar', check_str='rule:baz', description='Create a foo.', operations=[{'path': '/v1/foos/', 'method': 'POST'}], deprecated_rule=deprecated_rule, deprecated_reason='Some reason.' ) def test_deprecated_rule_requires_deprecated_rule_object(self): self.assertRaises( ValueError, policy.DocumentedRuleDefault, name='foo:bar', check_str='rule:baz', description='Create a foo.', operations=[{'path': '/v1/foos/', 'method': 'POST'}], deprecated_rule='foo:bar', deprecated_reason='Some reason.' ) def test_deprecated_policy_must_include_deprecated_reason(self): self.assertRaises( ValueError, policy.DocumentedRuleDefault, name='foo:bar', check_str='rule:baz', description='Create a foo.', operations=[{'path': '/v1/foos/', 'method': 'POST'}], deprecated_for_removal=True, deprecated_since='N' ) class DocumentedRuleDefaultTestCase(base.PolicyBaseTestCase): def test_contain_operations(self): opt = policy.DocumentedRuleDefault( name='foo', check_str='rule:foo', description='foo_api', operations=[{'path': '/foo/', 'method': 'GET'}]) self.assertEqual(1, len(opt.operations)) def test_multiple_operations(self): opt = policy.DocumentedRuleDefault( name='foo', check_str='rule:foo', description='foo_api', operations=[{'path': '/foo/', 'method': 'GET'}, {'path': '/foo/', 'method': 'POST'}]) self.assertEqual(2, len(opt.operations)) def test_description_not_empty(self): invalid_desc = '' self.assertRaises(policy.InvalidRuleDefault, policy.DocumentedRuleDefault, name='foo', check_str='rule:foo', description=invalid_desc, operations=[{'path': '/foo/', 'method': 'GET'}]) def test_operation_not_empty_list(self): invalid_op = [] self.assertRaises(policy.InvalidRuleDefault, policy.DocumentedRuleDefault, name='foo', check_str='rule:foo', description='foo_api', operations=invalid_op) def test_operation_must_be_list(self): invalid_op = 'invalid_op' self.assertRaises(policy.InvalidRuleDefault, policy.DocumentedRuleDefault, name='foo', check_str='rule:foo', description='foo_api', operations=invalid_op) def test_operation_must_be_list_of_dicts(self): invalid_op = ['invalid_op'] self.assertRaises(policy.InvalidRuleDefault, policy.DocumentedRuleDefault, name='foo', check_str='rule:foo', description='foo_api', operations=invalid_op) def test_operation_must_have_path(self): invalid_op = [{'method': 'POST'}] self.assertRaises(policy.InvalidRuleDefault, policy.DocumentedRuleDefault, name='foo', check_str='rule:foo', description='foo_api', operations=invalid_op) def test_operation_must_have_method(self): invalid_op = [{'path': '/foo/path/'}] self.assertRaises(policy.InvalidRuleDefault, policy.DocumentedRuleDefault, name='foo', check_str='rule:foo', description='foo_api', operations=invalid_op) def test_operation_must_contain_method_and_path_only(self): invalid_op = [{'path': '/some/path/', 'method': 'GET', 'break': 'me'}] self.assertRaises(policy.InvalidRuleDefault, policy.DocumentedRuleDefault, name='foo', check_str='rule:foo', description='foo_api', operations=invalid_op) class EnforcerCheckRulesTest(base.PolicyBaseTestCase): def setUp(self): super(EnforcerCheckRulesTest, self).setUp() def test_no_violations(self): self.create_config_file('policy.json', POLICY_JSON_CONTENTS) self.enforcer.load_rules(True) self.assertTrue(self.enforcer.check_rules(raise_on_violation=True)) def test_undefined_rule(self): rules = jsonutils.dumps({'foo': 'rule:bar'}) self.create_config_file('policy.json', rules) self.enforcer.load_rules(True) self.assertFalse(self.enforcer.check_rules()) def test_undefined_rule_raises(self): rules = jsonutils.dumps({'foo': 'rule:bar'}) self.create_config_file('policy.json', rules) self.enforcer.load_rules(True) self.assertRaises(policy.InvalidDefinitionError, self.enforcer.check_rules, raise_on_violation=True) def test_cyclical_rules(self): rules = jsonutils.dumps({'foo': 'rule:bar', 'bar': 'rule:foo'}) self.create_config_file('policy.json', rules) self.enforcer.load_rules(True) self.assertFalse(self.enforcer.check_rules()) def test_cyclical_rules_raises(self): rules = jsonutils.dumps({'foo': 'rule:bar', 'bar': 'rule:foo'}) self.create_config_file('policy.json', rules) self.enforcer.load_rules(True) self.assertRaises(policy.InvalidDefinitionError, self.enforcer.check_rules, raise_on_violation=True) def test_complex_cyclical_rules_false(self): rules = jsonutils.dumps({'foo': 'rule:bar', 'bar': 'rule:baz and role:admin', 'baz': 'rule:foo or role:user'}) self.create_config_file('policy.json', rules) self.enforcer.load_rules(True) self.assertFalse(self.enforcer.check_rules()) def test_complex_cyclical_rules_true(self): rules = jsonutils.dumps({'foo': 'rule:bar or rule:baz', 'bar': 'role:admin', 'baz': 'rule:bar or role:user'}) self.create_config_file('policy.json', rules) self.enforcer.load_rules(True) self.assertTrue(self.enforcer.check_rules()) oslo.policy-1.33.1/oslo_policy/tests/test_fixtures.py0000666000175100017510000000414713224676023023071 0ustar zuulzuul00000000000000# 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 json from oslo_policy import fixture from oslo_policy import policy as oslo_policy from oslo_policy.tests import base as test_base class FixtureTestCase(test_base.PolicyBaseTestCase): def test_enforce_http_true(self): self.assertTrue(self._test_enforce_http(True)) def test_enforce_http_false(self): self.assertFalse(self._test_enforce_http(False)) def _test_enforce_http(self, return_value): self.useFixture(fixture.HttpCheckFixture(return_value=return_value)) action = self.getUniqueString() rules_json = { action: "http:" + self.getUniqueString() } rules = oslo_policy.Rules.load(json.dumps(rules_json)) self.enforcer.set_rules(rules) return self.enforcer.enforce(rule=action, target={}, creds={}) def test_enforce_https_true(self): self.assertTrue(self._test_enforce_http(True)) def test_enforce_https_false(self): self.assertFalse(self._test_enforce_http(False)) def _test_enforce_https(self, return_value): self.useFixture(fixture.HttpsCheckFixture(return_value=return_value)) action = self.getUniqueString() rules_json = { action: "https:" + self.getUniqueString() } rules = oslo_policy.Rules.load(json.dumps(rules_json)) self.enforcer.set_rules(rules) return self.enforcer.enforce(rule=action, target={}, creds={}) oslo.policy-1.33.1/oslo_policy/tests/test_sphinxext.py0000666000175100017510000000400213224676023023240 0ustar zuulzuul00000000000000# Copyright 2017 Red Hat, Inc. # # 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 textwrap from oslotest import base from oslo_policy import policy from oslo_policy import sphinxext class FormatPolicyTest(base.BaseTestCase): def test_minimal(self): results = '\n'.join(list(sphinxext._format_policy_section( 'foo', [policy.RuleDefault('rule_a', '@')]))) self.assertEqual(textwrap.dedent(""" foo === ``rule_a`` :Default: ``@`` (no description provided) """).lstrip(), results) def test_with_description(self): results = '\n'.join(list(sphinxext._format_policy_section( 'foo', [policy.RuleDefault('rule_a', '@', 'My sample rule')] ))) self.assertEqual(textwrap.dedent(""" foo === ``rule_a`` :Default: ``@`` My sample rule """).lstrip(), results) def test_with_operations(self): results = '\n'.join(list(sphinxext._format_policy_section( 'foo', [policy.DocumentedRuleDefault( 'rule_a', '@', 'My sample rule', [ {'method': 'GET', 'path': '/foo'}, {'method': 'POST', 'path': '/some'}])] ))) self.assertEqual(textwrap.dedent(""" foo === ``rule_a`` :Default: ``@`` :Operations: - **GET** ``/foo`` - **POST** ``/some`` My sample rule """).lstrip(), results) oslo.policy-1.33.1/oslo_policy/tests/__init__.py0000666000175100017510000000000013224676023021700 0ustar zuulzuul00000000000000oslo.policy-1.33.1/oslo_policy/tests/base.py0000666000175100017510000000416313224676023021071 0ustar zuulzuul00000000000000# Copyright (c) 2015 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. import codecs import os import os.path import fixtures from oslo_config import fixture as config from oslotest import base as test_base from oslo_policy import _checks from oslo_policy import policy class PolicyBaseTestCase(test_base.BaseTestCase): def setUp(self): super(PolicyBaseTestCase, self).setUp() self.conf = self.useFixture(config.Config()).conf self.config_dir = self.useFixture(fixtures.TempDir()).path self.conf(args=['--config-dir', self.config_dir]) self.enforcer = policy.Enforcer(self.conf) self.addCleanup(self.enforcer.clear) def get_config_file_fullname(self, filename): return os.path.join(self.config_dir, filename.lstrip(os.sep)) def create_config_file(self, filename, contents): """Create a configuration file under the config dir. Also creates any intermediate paths needed so the file can be in a subdirectory. """ path = self.get_config_file_fullname(filename) pardir = os.path.dirname(path) if not os.path.exists(pardir): os.makedirs(pardir) with codecs.open(path, 'w', encoding='utf-8') as f: f.write(contents) class FakeCheck(_checks.BaseCheck): def __init__(self, result=None): self.result = result def __str__(self): return str(self.result) def __call__(self, target, creds, enforcer): if self.result is not None: return self.result return (target, creds, enforcer) oslo.policy-1.33.1/oslo_policy/tests/test_generator.py0000666000175100017510000004615713224676023023215 0ustar zuulzuul00000000000000# 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 operator import sys import fixtures import mock from oslo_config import cfg from six import moves import stevedore import testtools from oslo_policy import generator from oslo_policy import policy from oslo_policy.tests import base OPTS = {'base_rules': [policy.RuleDefault('admin', 'is_admin:True', description='Basic admin check'), policy.DocumentedRuleDefault('owner', ('project_id:%' '(project_id)s'), 'This is a long ' 'description to check ' 'that line wrapping ' 'functions properly', [{'path': '/foo/', 'method': 'GET'}, {'path': '/test/', 'method': 'POST'}])], 'custom_field': [policy.RuleDefault('shared', 'field:networks:shared=True')], 'rules': [policy.RuleDefault('admin_or_owner', 'rule:admin or rule:owner')], } class GenerateSampleYAMLTestCase(base.PolicyBaseTestCase): def setUp(self): super(GenerateSampleYAMLTestCase, self).setUp() self.enforcer = policy.Enforcer(self.conf, policy_file='policy.yaml') def _capture_stdout(self): self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO())) return sys.stdout def test_generate_loadable_yaml(self): extensions = [] for name, opts in OPTS.items(): ext = stevedore.extension.Extension(name=name, entry_point=None, plugin=None, obj=opts) extensions.append(ext) test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( extensions=extensions, namespace=['base_rules', 'rules']) output_file = self.get_config_file_fullname('policy.yaml') with mock.patch('stevedore.named.NamedExtensionManager', return_value=test_mgr) as mock_ext_mgr: # generate sample-policy file with only rules generator._generate_sample(['base_rules', 'rules'], output_file, include_help=False) mock_ext_mgr.assert_called_once_with( 'oslo.policy.policies', names=['base_rules', 'rules'], on_load_failure_callback=generator.on_load_failure_callback, invoke_on_load=True) self.enforcer.load_rules() self.assertIn('owner', self.enforcer.rules) self.assertIn('admin', self.enforcer.rules) self.assertIn('admin_or_owner', self.enforcer.rules) self.assertEqual('project_id:%(project_id)s', str(self.enforcer.rules['owner'])) self.assertEqual('is_admin:True', str(self.enforcer.rules['admin'])) self.assertEqual('(rule:admin or rule:owner)', str(self.enforcer.rules['admin_or_owner'])) def test_expected_content(self): extensions = [] for name, opts in OPTS.items(): ext = stevedore.extension.Extension(name=name, entry_point=None, plugin=None, obj=opts) extensions.append(ext) test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( extensions=extensions, namespace=['base_rules', 'rules']) expected = '''# Basic admin check #"admin": "is_admin:True" # This is a long description to check that line wrapping functions # properly # GET /foo/ # POST /test/ #"owner": "project_id:%(project_id)s" # #"shared": "field:networks:shared=True" # #"admin_or_owner": "rule:admin or rule:owner" ''' output_file = self.get_config_file_fullname('policy.yaml') with mock.patch('stevedore.named.NamedExtensionManager', return_value=test_mgr) as mock_ext_mgr: generator._generate_sample(['base_rules', 'rules'], output_file) mock_ext_mgr.assert_called_once_with( 'oslo.policy.policies', names=['base_rules', 'rules'], on_load_failure_callback=generator.on_load_failure_callback, invoke_on_load=True) with open(output_file, 'r') as written_file: written_policy = written_file.read() self.assertEqual(expected, written_policy) def test_expected_content_stdout(self): extensions = [] for name, opts in OPTS.items(): ext = stevedore.extension.Extension(name=name, entry_point=None, plugin=None, obj=opts) extensions.append(ext) test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( extensions=extensions, namespace=['base_rules', 'rules']) expected = '''# Basic admin check #"admin": "is_admin:True" # This is a long description to check that line wrapping functions # properly # GET /foo/ # POST /test/ #"owner": "project_id:%(project_id)s" # #"shared": "field:networks:shared=True" # #"admin_or_owner": "rule:admin or rule:owner" ''' stdout = self._capture_stdout() with mock.patch('stevedore.named.NamedExtensionManager', return_value=test_mgr) as mock_ext_mgr: generator._generate_sample(['base_rules', 'rules'], output_file=None) mock_ext_mgr.assert_called_once_with( 'oslo.policy.policies', names=['base_rules', 'rules'], on_load_failure_callback=generator.on_load_failure_callback, invoke_on_load=True) self.assertEqual(expected, stdout.getvalue()) def test_empty_line_formatting(self): rule = [policy.RuleDefault('admin', 'is_admin:True', description='Check Summary \n' '\n' 'This is a description to ' 'check that empty line has ' 'no white spaces.')] ext = stevedore.extension.Extension(name='check_rule', entry_point=None, plugin=None, obj=rule) test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( extensions=[ext], namespace=['check_rule']) # no whitespace on empty line expected = '''# Check Summary # # This is a description to check that empty line has no white spaces. #"admin": "is_admin:True" ''' output_file = self.get_config_file_fullname('policy.yaml') with mock.patch('stevedore.named.NamedExtensionManager', return_value=test_mgr) as mock_ext_mgr: generator._generate_sample(['check_rule'], output_file) mock_ext_mgr.assert_called_once_with( 'oslo.policy.policies', names=['check_rule'], on_load_failure_callback=generator.on_load_failure_callback, invoke_on_load=True) with open(output_file, 'r') as written_file: written_policy = written_file.read() self.assertEqual(expected, written_policy) class GenerateSampleJSONTestCase(base.PolicyBaseTestCase): def setUp(self): super(GenerateSampleJSONTestCase, self).setUp() self.enforcer = policy.Enforcer(self.conf, policy_file='policy.json') def _capture_stdout(self): self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO())) return sys.stdout def test_generate_loadable_json(self): extensions = [] for name, opts in OPTS.items(): ext = stevedore.extension.Extension(name=name, entry_point=None, plugin=None, obj=opts) extensions.append(ext) test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( extensions=extensions, namespace=['base_rules', 'rules']) output_file = self.get_config_file_fullname('policy.json') with mock.patch('stevedore.named.NamedExtensionManager', return_value=test_mgr) as mock_ext_mgr: # generate sample-policy file with only rules generator._generate_sample(['base_rules', 'rules'], output_file, output_format='json', include_help=False) mock_ext_mgr.assert_called_once_with( 'oslo.policy.policies', names=['base_rules', 'rules'], on_load_failure_callback=generator.on_load_failure_callback, invoke_on_load=True) self.enforcer.load_rules() self.assertIn('owner', self.enforcer.rules) self.assertIn('admin', self.enforcer.rules) self.assertIn('admin_or_owner', self.enforcer.rules) self.assertEqual('project_id:%(project_id)s', str(self.enforcer.rules['owner'])) self.assertEqual('is_admin:True', str(self.enforcer.rules['admin'])) self.assertEqual('(rule:admin or rule:owner)', str(self.enforcer.rules['admin_or_owner'])) def test_expected_content(self): extensions = [] for name, opts in OPTS.items(): ext = stevedore.extension.Extension(name=name, entry_point=None, plugin=None, obj=opts) extensions.append(ext) test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( extensions=extensions, namespace=['base_rules', 'rules']) expected = '''{ "admin": "is_admin:True", "owner": "project_id:%(project_id)s", "shared": "field:networks:shared=True", "admin_or_owner": "rule:admin or rule:owner" } ''' output_file = self.get_config_file_fullname('policy.json') with mock.patch('stevedore.named.NamedExtensionManager', return_value=test_mgr) as mock_ext_mgr: generator._generate_sample(['base_rules', 'rules'], output_file=output_file, output_format='json') mock_ext_mgr.assert_called_once_with( 'oslo.policy.policies', names=['base_rules', 'rules'], on_load_failure_callback=generator.on_load_failure_callback, invoke_on_load=True) with open(output_file, 'r') as written_file: written_policy = written_file.read() self.assertEqual(expected, written_policy) def test_expected_content_stdout(self): extensions = [] for name, opts in OPTS.items(): ext = stevedore.extension.Extension(name=name, entry_point=None, plugin=None, obj=opts) extensions.append(ext) test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( extensions=extensions, namespace=['base_rules', 'rules']) expected = '''{ "admin": "is_admin:True", "owner": "project_id:%(project_id)s", "shared": "field:networks:shared=True", "admin_or_owner": "rule:admin or rule:owner" } ''' stdout = self._capture_stdout() with mock.patch('stevedore.named.NamedExtensionManager', return_value=test_mgr) as mock_ext_mgr: generator._generate_sample(['base_rules', 'rules'], output_file=None, output_format='json') mock_ext_mgr.assert_called_once_with( 'oslo.policy.policies', names=['base_rules', 'rules'], on_load_failure_callback=generator.on_load_failure_callback, invoke_on_load=True) self.assertEqual(expected, stdout.getvalue()) class GeneratorRaiseErrorTestCase(testtools.TestCase): def test_generator_raises_error(self): """Verifies that errors from extension manager are not suppressed.""" class FakeException(Exception): pass class FakeEP(object): def __init__(self): self.name = 'callback_is_expected' self.require = self.resolve self.load = self.resolve def resolve(self, *args, **kwargs): raise FakeException() fake_ep = FakeEP() fake_eps = mock.Mock(return_value=[fake_ep]) with mock.patch('pkg_resources.iter_entry_points', fake_eps): self.assertRaises(FakeException, generator._generate_sample, fake_ep.name) def test_generator_call_with_no_arguments_raises_error(self): testargs = ['oslopolicy-sample-generator'] with mock.patch('sys.argv', testargs): self.assertRaises(cfg.RequiredOptError, generator.generate_sample, []) class GeneratePolicyTestCase(base.PolicyBaseTestCase): def setUp(self): super(GeneratePolicyTestCase, self).setUp() def test_merged_rules(self): extensions = [] for name, opts in OPTS.items(): ext = stevedore.extension.Extension(name=name, entry_point=None, plugin=None, obj=opts) extensions.append(ext) test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( extensions=extensions, namespace=['base_rules', 'rules']) # Write the policy file for an enforcer to load sample_file = self.get_config_file_fullname('policy-sample.yaml') with mock.patch('stevedore.named.NamedExtensionManager', return_value=test_mgr): # generate sample-policy file with only rules generator._generate_sample(['base_rules', 'rules'], sample_file, include_help=False) enforcer = policy.Enforcer(self.conf, policy_file='policy-sample.yaml') # register an opt defined in the file enforcer.register_default(policy.RuleDefault('admin', 'is_admin:False')) # register a new opt enforcer.register_default(policy.RuleDefault('foo', 'role:foo')) # Mock out stevedore to return the configured enforcer ext = stevedore.extension.Extension(name='testing', entry_point=None, plugin=None, obj=enforcer) test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( extensions=[ext], namespace='testing') # Generate a merged file merged_file = self.get_config_file_fullname('policy-merged.yaml') with mock.patch('stevedore.named.NamedExtensionManager', return_value=test_mgr) as mock_ext_mgr: generator._generate_policy(namespace='testing', output_file=merged_file) mock_ext_mgr.assert_called_once_with( 'oslo.policy.enforcer', names=['testing'], on_load_failure_callback=generator.on_load_failure_callback, invoke_on_load=True) # load the merged file with a new enforcer merged_enforcer = policy.Enforcer(self.conf, policy_file='policy-merged.yaml') merged_enforcer.load_rules() for rule in ['admin', 'owner', 'admin_or_owner', 'foo']: self.assertIn(rule, merged_enforcer.rules) self.assertEqual('is_admin:True', str(merged_enforcer.rules['admin'])) self.assertEqual('role:foo', str(merged_enforcer.rules['foo'])) class ListRedundantTestCase(base.PolicyBaseTestCase): def setUp(self): super(ListRedundantTestCase, self).setUp() def _capture_stdout(self): self.useFixture(fixtures.MonkeyPatch('sys.stdout', moves.StringIO())) return sys.stdout def test_matched_rules(self): extensions = [] for name, opts in OPTS.items(): ext = stevedore.extension.Extension(name=name, entry_point=None, plugin=None, obj=opts) extensions.append(ext) test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( extensions=extensions, namespace=['base_rules', 'rules']) # Write the policy file for an enforcer to load sample_file = self.get_config_file_fullname('policy-sample.yaml') with mock.patch('stevedore.named.NamedExtensionManager', return_value=test_mgr): # generate sample-policy file with only rules generator._generate_sample(['base_rules', 'rules'], sample_file, include_help=False) enforcer = policy.Enforcer(self.conf, policy_file='policy-sample.yaml') # register opts that match those defined in policy-sample.yaml enforcer.register_default(policy.RuleDefault('admin', 'is_admin:True')) enforcer.register_default( policy.RuleDefault('owner', 'project_id:%(project_id)s')) # register a new opt enforcer.register_default(policy.RuleDefault('foo', 'role:foo')) # Mock out stevedore to return the configured enforcer ext = stevedore.extension.Extension(name='testing', entry_point=None, plugin=None, obj=enforcer) test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( extensions=[ext], namespace='testing') stdout = self._capture_stdout() with mock.patch('stevedore.named.NamedExtensionManager', return_value=test_mgr) as mock_ext_mgr: generator._list_redundant(namespace='testing') mock_ext_mgr.assert_called_once_with( 'oslo.policy.enforcer', names=['testing'], on_load_failure_callback=generator.on_load_failure_callback, invoke_on_load=True) matches = [line.split(': ', 1) for line in stdout.getvalue().splitlines()] matches.sort(key=operator.itemgetter(0)) # Should be 'admin' opt0 = matches[0] self.assertEqual('"admin"', opt0[0]) self.assertEqual('"is_admin:True"', opt0[1]) # Should be 'owner' opt1 = matches[1] self.assertEqual('"owner"', opt1[0]) self.assertEqual('"project_id:%(project_id)s"', opt1[1]) oslo.policy-1.33.1/oslo_policy/tests/test_sphinxpolicygen.py0000666000175100017510000000411013224676023024431 0ustar zuulzuul00000000000000# 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 mock from oslotest import base from oslo_policy import sphinxpolicygen class SingleSampleGenerationTest(base.BaseTestCase): @mock.patch('os.path.isdir') @mock.patch('os.path.isfile') @mock.patch('oslo_policy.generator.generate_sample') def test_sample_gen_with_single_config_file(self, sample, isfile, isdir): isfile.side_effect = [False, True] isdir.return_value = True config = mock.Mock(policy_generator_config_file='nova.conf', sample_policy_basename='nova') app = mock.Mock(srcdir='/opt/nova', config=config) sphinxpolicygen.generate_sample(app) sample.assert_called_once_with(args=[ '--config-file', '/opt/nova/nova.conf', '--output-file', '/opt/nova/nova.policy.yaml.sample']) @mock.patch('os.path.isdir') @mock.patch('os.path.isfile') @mock.patch('oslo_policy.generator.generate_sample') def test_sample_gen_with_single_config_file_no_base(self, sample, isfile, isdir): isfile.side_effect = [False, True] isdir.return_value = True config = mock.Mock(policy_generator_config_file='nova.conf', sample_policy_basename=None) app = mock.Mock(srcdir='/opt/nova', config=config) sphinxpolicygen.generate_sample(app) sample.assert_called_once_with(args=[ '--config-file', '/opt/nova/nova.conf', '--output-file', '/opt/nova/sample.policy.yaml']) oslo.policy-1.33.1/oslo_policy/tests/test_opts.py0000666000175100017510000000227313224676023022203 0ustar zuulzuul00000000000000# 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_config import fixture as config from oslotest import base as test_base from oslo_policy import opts class OptsTestCase(test_base.BaseTestCase): def setUp(self): super(OptsTestCase, self).setUp() self.conf = self.useFixture(config.Config()).conf def test_set_defaults_policy_file(self): opts._register(self.conf) self.assertNotEqual('new-value.json', self.conf.oslo_policy.policy_file) opts.set_defaults(self.conf, policy_file='new-value.json') self.assertEqual('new-value.json', self.conf.oslo_policy.policy_file) oslo.policy-1.33.1/oslo_policy/tests/test_external.py0000666000175100017510000003242213224676023023037 0ustar zuulzuul00000000000000# Copyright (c) 2015 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. import json import mock from oslo_serialization import jsonutils from requests_mock.contrib import fixture as rm_fixture import six.moves.urllib.parse as urlparse from oslo_policy import _external from oslo_policy import opts from oslo_policy.tests import base class HttpCheckTestCase(base.PolicyBaseTestCase): def setUp(self): super(HttpCheckTestCase, self).setUp() opts._register(self.conf) self.requests_mock = self.useFixture(rm_fixture.Fixture()) def decode_post_data(self, post_data): result = {} for item in post_data.split('&'): key, _sep, value = item.partition('=') result[key] = jsonutils.loads(urlparse.unquote_plus(value)) return result def test_accept(self): self.requests_mock.post('http://example.com/target', text='True') check = _external.HttpCheck('http', '//example.com/%(name)s') target_dict = dict(name='target', spam='spammer') cred_dict = dict(user='user', roles=['a', 'b', 'c']) self.assertTrue(check(target_dict, cred_dict, self.enforcer)) last_request = self.requests_mock.last_request self.assertEqual('application/x-www-form-urlencoded', last_request.headers['Content-Type']) self.assertEqual('POST', last_request.method) self.assertEqual(dict(target=target_dict, credentials=cred_dict, rule=None), self.decode_post_data(last_request.body)) def test_accept_json(self): self.conf.set_override('remote_content_type', 'application/json', group='oslo_policy') self.requests_mock.post('http://example.com/target', text='True') check = _external.HttpCheck('http', '//example.com/%(name)s') target_dict = dict(name='target', spam='spammer') cred_dict = dict(user='user', roles=['a', 'b', 'c']) self.assertTrue(check(target_dict, cred_dict, self.enforcer)) last_request = self.requests_mock.last_request self.assertEqual('application/json', last_request.headers['Content-Type']) self.assertEqual('POST', last_request.method) self.assertEqual(dict(rule=None, credentials=cred_dict, target=target_dict), json.loads(last_request.body.decode('utf-8'))) def test_reject(self): self.requests_mock.post("http://example.com/target", text='other') check = _external.HttpCheck('http', '//example.com/%(name)s') target_dict = dict(name='target', spam='spammer') cred_dict = dict(user='user', roles=['a', 'b', 'c']) self.assertFalse(check(target_dict, cred_dict, self.enforcer)) last_request = self.requests_mock.last_request self.assertEqual('POST', last_request.method) self.assertEqual(dict(target=target_dict, credentials=cred_dict, rule=None), self.decode_post_data(last_request.body)) def test_http_with_objects_in_target(self): self.requests_mock.post("http://example.com/target", text='True') check = _external.HttpCheck('http', '//example.com/%(name)s') target = {'a': object(), 'name': 'target', 'b': 'test data'} self.assertTrue(check(target, dict(user='user', roles=['a', 'b', 'c']), self.enforcer)) def test_http_with_strings_in_target(self): self.requests_mock.post("http://example.com/target", text='True') check = _external.HttpCheck('http', '//example.com/%(name)s') target = {'a': 'some_string', 'name': 'target', 'b': 'test data'} self.assertTrue(check(target, dict(user='user', roles=['a', 'b', 'c']), self.enforcer)) def test_accept_with_rule_in_argument(self): self.requests_mock.post('http://example.com/target', text='True') check = _external.HttpCheck('http', '//example.com/%(name)s') target_dict = dict(name='target', spam='spammer') cred_dict = dict(user='user', roles=['a', 'b', 'c']) current_rule = "a_rule" self.assertTrue(check(target_dict, cred_dict, self.enforcer, current_rule)) last_request = self.requests_mock.last_request self.assertEqual('POST', last_request.method) self.assertEqual(dict(target=target_dict, credentials=cred_dict, rule=current_rule), self.decode_post_data(last_request.body)) def test_reject_with_rule_in_argument(self): self.requests_mock.post("http://example.com/target", text='other') check = _external.HttpCheck('http', '//example.com/%(name)s') target_dict = dict(name='target', spam='spammer') cred_dict = dict(user='user', roles=['a', 'b', 'c']) current_rule = "a_rule" self.assertFalse(check(target_dict, cred_dict, self.enforcer, current_rule)) last_request = self.requests_mock.last_request self.assertEqual('POST', last_request.method) self.assertEqual(dict(target=target_dict, credentials=cred_dict, rule=current_rule), self.decode_post_data(last_request.body)) class HttpsCheckTestCase(base.PolicyBaseTestCase): def setUp(self): super(HttpsCheckTestCase, self).setUp() opts._register(self.conf) self.requests_mock = self.useFixture(rm_fixture.Fixture()) def decode_post_data(self, post_data): result = {} for item in post_data.split('&'): key, _sep, value = item.partition('=') result[key] = jsonutils.loads(urlparse.unquote_plus(value)) return result def test_https_accept(self): self.requests_mock.post('https://example.com/target', text='True') check = _external.HttpsCheck('https', '//example.com/%(name)s') target_dict = dict(name='target', spam='spammer') cred_dict = dict(user='user', roles=['a', 'b', 'c']) self.assertTrue(check(target_dict, cred_dict, self.enforcer)) last_request = self.requests_mock.last_request self.assertEqual('application/x-www-form-urlencoded', last_request.headers['Content-Type']) self.assertEqual('POST', last_request.method) self.assertEqual(dict(rule=None, target=target_dict, credentials=cred_dict), self.decode_post_data(last_request.body)) def test_https_accept_json(self): self.conf.set_override('remote_content_type', 'application/json', group='oslo_policy') self.requests_mock.post('https://example.com/target', text='True') check = _external.HttpsCheck('https', '//example.com/%(name)s') target_dict = dict(name='target', spam='spammer') cred_dict = dict(user='user', roles=['a', 'b', 'c']) self.assertTrue(check(target_dict, cred_dict, self.enforcer)) last_request = self.requests_mock.last_request self.assertEqual('application/json', last_request.headers['Content-Type']) self.assertEqual('POST', last_request.method) self.assertEqual(dict(rule=None, target=target_dict, credentials=cred_dict), json.loads(last_request.body.decode('utf-8'))) def test_https_accept_with_verify(self): self.conf.set_override('remote_ssl_verify_server_crt', True, group='oslo_policy') self.requests_mock.post('https://example.com/target', text='True') check = _external.HttpsCheck('https', '//example.com/%(name)s') target_dict = dict(name='target', spam='spammer') cred_dict = dict(user='user', roles=['a', 'b', 'c']) self.assertTrue(check(target_dict, cred_dict, self.enforcer)) last_request = self.requests_mock.last_request self.assertEqual(True, last_request.verify) self.assertEqual('POST', last_request.method) self.assertEqual(dict(rule=None, target=target_dict, credentials=cred_dict), self.decode_post_data(last_request.body)) def test_https_accept_with_verify_cert(self): self.conf.set_override('remote_ssl_verify_server_crt', True, group='oslo_policy') self.conf.set_override('remote_ssl_ca_crt_file', "ca.crt", group='oslo_policy') self.requests_mock.post('https://example.com/target', text='True') check = _external.HttpsCheck('https', '//example.com/%(name)s') target_dict = dict(name='target', spam='spammer') cred_dict = dict(user='user', roles=['a', 'b', 'c']) with mock.patch('os.path.exists') as path_exists: path_exists.return_value = True self.assertTrue(check(target_dict, cred_dict, self.enforcer)) last_request = self.requests_mock.last_request self.assertEqual('ca.crt', last_request.verify) self.assertEqual('POST', last_request.method) self.assertEqual(dict(rule=None, target=target_dict, credentials=cred_dict), self.decode_post_data(last_request.body)) def test_https_accept_with_verify_and_client_certs(self): self.conf.set_override('remote_ssl_verify_server_crt', True, group='oslo_policy') self.conf.set_override('remote_ssl_ca_crt_file', "ca.crt", group='oslo_policy') self.conf.set_override('remote_ssl_client_key_file', "client.key", group='oslo_policy') self.conf.set_override('remote_ssl_client_crt_file', "client.crt", group='oslo_policy') self.requests_mock.post('https://example.com/target', text='True') check = _external.HttpsCheck('https', '//example.com/%(name)s') target_dict = dict(name='target', spam='spammer') cred_dict = dict(user='user', roles=['a', 'b', 'c']) with mock.patch('os.path.exists') as path_exists: with mock.patch('os.access') as os_access: path_exists.return_value = True os_access.return_value = True self.assertTrue(check(target_dict, cred_dict, self.enforcer)) last_request = self.requests_mock.last_request self.assertEqual('ca.crt', last_request.verify) self.assertEqual(('client.crt', 'client.key'), last_request.cert) self.assertEqual('POST', last_request.method) self.assertEqual(dict(rule=None, target=target_dict, credentials=cred_dict), self.decode_post_data(last_request.body)) def test_https_reject(self): self.requests_mock.post("https://example.com/target", text='other') check = _external.HttpsCheck('https', '//example.com/%(name)s') target_dict = dict(name='target', spam='spammer') cred_dict = dict(user='user', roles=['a', 'b', 'c']) self.assertFalse(check(target_dict, cred_dict, self.enforcer)) last_request = self.requests_mock.last_request self.assertEqual('POST', last_request.method) self.assertEqual(dict(rule=None, target=target_dict, credentials=cred_dict), self.decode_post_data(last_request.body)) def test_https_with_objects_in_target(self): self.requests_mock.post("https://example.com/target", text='True') check = _external.HttpsCheck('https', '//example.com/%(name)s') target = {'a': object(), 'name': 'target', 'b': 'test data'} self.assertTrue(check(target, dict(user='user', roles=['a', 'b', 'c']), self.enforcer)) def test_https_with_strings_in_target(self): self.requests_mock.post("https://example.com/target", text='True') check = _external.HttpsCheck('https', '//example.com/%(name)s') target = {'a': 'some_string', 'name': 'target', 'b': 'test data'} self.assertTrue(check(target, dict(user='user', roles=['a', 'b', 'c']), self.enforcer)) oslo.policy-1.33.1/oslo_policy/locale/0000775000175100017510000000000013224676252017700 5ustar zuulzuul00000000000000oslo.policy-1.33.1/oslo_policy/locale/en_GB/0000775000175100017510000000000013224676252020652 5ustar zuulzuul00000000000000oslo.policy-1.33.1/oslo_policy/locale/en_GB/LC_MESSAGES/0000775000175100017510000000000013224676252022437 5ustar zuulzuul00000000000000oslo.policy-1.33.1/oslo_policy/locale/en_GB/LC_MESSAGES/oslo_policy.po0000666000175100017510000000664013224676023025336 0ustar zuulzuul00000000000000# OpenStack Infra , 2015. #zanata # Andi Chandler , 2016. #zanata # Andi Chandler , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.policy 1.28.2.dev1\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2017-10-04 14:01+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-10-07 09:33+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en-GB\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #, python-format msgid "%(rule)s is disallowed by policy" msgstr "%(rule)s is disallowed by policy" msgid "Absolute path client key file REST based policy check" msgstr "Absolute path client key file REST based policy check" msgid "Absolute path to ca cert file for REST based policy check" msgstr "Absolute path to ca cert file for REST based policy check" msgid "Absolute path to client cert for REST based policy check" msgstr "Absolute path to client cert for REST based policy check" msgid "Content Type to send and receive data for REST based policy check" msgstr "Content Type to send and receive data for REST based policy check" msgid "Default rule. Enforced when a requested rule is not found." msgstr "Default rule. Enforced when a requested rule is not found." msgid "" "Directories where policy configuration files are stored. They can be " "relative to any directory in the search path defined by the config_dir " "option, or absolute paths. The file defined by policy_file must exist for " "these directories to be searched. Missing or empty directories are ignored." msgstr "" "Directories where policy configuration files are stored. They can be " "relative to any directory in the search path defined by the config_dir " "option, or absolute paths. The file defined by policy_file must exist for " "these directories to be searched. Missing or empty directories are ignored." #, python-format msgid "Invalid policy rule default: %(error)s." msgstr "Invalid policy rule default: %(error)s." #, python-format msgid "Policies %(names)s are not well defined. Check logs for more details." msgstr "Policies %(names)s are not well defined. Check logs for more details." #, python-format msgid "Policy %(name)s has not been registered" msgstr "Policy %(name)s has not been registered" #, python-format msgid "Policy %(name)s is already registered" msgstr "Policy %(name)s is already registered" #, python-format msgid "Rules must be an instance of dict or Rules, got %s instead" msgstr "Rules must be an instance of dict or Rules, got %s instead" msgid "The file that defines policies." msgstr "The file that defines policies." #, python-format msgid "Unable to access ssl cert_file : %s" msgstr "Unable to access SSL cert_file : %s" #, python-format msgid "Unable to access ssl key_file : %s" msgstr "Unable to access SSL key_file : %s" #, python-format msgid "Unable to find ca cert_file : %s" msgstr "Unable to find ca cert_file : %s" #, python-format msgid "Unable to find ssl cert_file : %s" msgstr "Unable to find SSL cert_file : %s" #, python-format msgid "Unable to find ssl key_file : %s" msgstr "Unable to find SSL key_file : %s" msgid "server identity verification for REST based policy check" msgstr "server identity verification for REST based policy check" oslo.policy-1.33.1/oslo_policy/policy.py0000666000175100017510000014072613224676041020322 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2012 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. """ Common Policy Engine Implementation Policies are expressed as a target and an associated rule:: "": The `target` is specific to the service that is conducting policy enforcement. Typically, the target refers to an API call. For the `` part, see `Policy Rule Expressions`. Policy Rule Expressions ~~~~~~~~~~~~~~~~~~~~~~~ Policy rules can be expressed in one of two forms: a string written in the new policy language or a list of lists. The string format is preferred since it's easier for most people to understand. In the policy language, each check is specified as a simple "a:b" pair that is matched to the correct class to perform that check: +--------------------------------+------------------------------------------+ | TYPE | SYNTAX | +================================+==========================================+ |User's Role | role:admin | +--------------------------------+------------------------------------------+ |Rules already defined on policy | rule:admin_required | +--------------------------------+------------------------------------------+ |Against URLs¹ | http://my-url.org/check | +--------------------------------+------------------------------------------+ |User attributes² | project_id:%(target.project.id)s | +--------------------------------+------------------------------------------+ |Strings | - :'xpto2035abc' | | | - 'myproject': | +--------------------------------+------------------------------------------+ | | - project_id:xpto2035abc | |Literals | - domain_id:20 | | | - True:%(user.enabled)s | +--------------------------------+------------------------------------------+ ¹URL checking must return ``True`` to be valid ²User attributes (obtained through the token): user_id, domain_id or project_id Conjunction operators ``and`` and ``or`` are available, allowing for more expressiveness in crafting policies. For example:: "role:admin or (project_id:%(project_id)s and role:projectadmin)" The policy language also has the ``not`` operator, allowing a richer policy rule:: "project_id:%(project_id)s and not role:dunce" Operator precedence is below: +------------+-------------+-------------+ | PRECEDENCE | TYPE | EXPRESSION | +============+=============+=============+ | 4 | Grouping | (...) | +------------+-------------+-------------+ | 3 | Logical NOT | not ... | +------------+-------------+-------------+ | 2 | Logical AND | ... and ... | +------------+-------------+-------------+ | 1 | Logical OR | ... or ... | +------------+-------------+-------------+ Operator with larger precedence number precedes others with smaller numbers. In the list-of-lists representation, each check inside the innermost list is combined as with an "and" conjunction -- for that check to pass, all the specified checks must pass. These innermost lists are then combined as with an "or" conjunction. As an example, take the following rule, expressed in the list-of-lists representation:: [["role:admin"], ["project_id:%(project_id)s", "role:projectadmin"]] Finally, two special policy checks should be mentioned; the policy check "@" will always accept an access, and the policy check "!" will always reject an access. (Note that if a rule is either the empty list (``[]``) or the empty string (``""``), this is equivalent to the "@" policy check.) Of these, the "!" policy check is probably the most useful, as it allows particular rules to be explicitly disabled. Generic Checks ~~~~~~~~~~~~~~ A `generic` check is used to perform matching against attributes that are sent along with the API calls. These attributes can be used by the policy engine (on the right side of the expression), by using the following syntax:: :%(user.id)s The value on the right-hand side is either a string or resolves to a string using regular Python string substitution. The available attributes and values are dependent on the program that is using the common policy engine. All of these attributes (related to users, API calls, and context) can be checked against each other or against constants. It is important to note that these attributes are specific to the service that is conducting policy enforcement. Generic checks can be used to perform policy checks on the following user attributes obtained through a token: - user_id - domain_id or project_id (depending on the token scope) - list of roles held for the given token scope .. note:: Some resources which are exposed by the API do not support policy enforcement by user_id, and only support policy enforcement by project_id. Some global resources do not support policy enforcement by combination of user_id and project_id. For example, a check on the user_id would be defined as:: user_id: Together with the previously shown example, a complete generic check would be:: user_id:%(user.id)s It is also possible to perform checks against other attributes that represent the credentials. This is done by adding additional values to the ``creds`` dict that is passed to the :meth:`~oslo_policy.policy.Enforcer.enforce` method. Special Checks ~~~~~~~~~~~~~~ Special checks allow for more flexibility than is possible using generic checks. The built-in special check types are ``role``, ``rule``, and ``http`` checks. Role Check ^^^^^^^^^^ A ``role`` check is used to check if a specific role is present in the supplied credentials. A role check is expressed as:: "role:" Rule Check ^^^^^^^^^^ A :class:`rule check ` is used to reference another defined rule by its name. This allows for common checks to be defined once as a reusable rule, which is then referenced within other rules. It also allows one to define a set of checks as a more descriptive name to aid in readability of policy. A rule check is expressed as:: "rule:" The following example shows a role check that is defined as a rule, which is then used via a rule check:: "admin_required": "role:admin" "": "rule:admin_required" HTTP Check ^^^^^^^^^^ An ``http`` check is used to make an HTTP request to a remote server to determine the results of the check. The target and credentials are passed to the remote server for evaluation. The action is authorized if the remote server returns a response of ``True``. An http check is expressed as:: "http:" It is expected that the target URI contains a string formatting keyword, where the keyword is a key from the target dictionary. An example of an http check where the `name` key from the target is used to construct the URL is would be defined as:: "http://server.test/%(name)s" Registering New Special Checks ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ It is also possible for additional special check types to be registered using the :func:`~oslo_policy.policy.register` function. The following classes can be used as parents for custom special check types: * :class:`~oslo_policy.policy.AndCheck` * :class:`~oslo_policy.policy.NotCheck` * :class:`~oslo_policy.policy.OrCheck` * :class:`~oslo_policy.policy.RuleCheck` Default Rule ~~~~~~~~~~~~ A default rule can be defined, which will be enforced when a rule does not exist for the target that is being checked. By default, the rule associated with the rule name of ``default`` will be used as the default rule. It is possible to use a different rule name as the default rule by setting the ``policy_default_rule`` configuration setting to the desired rule name. """ import copy import logging import os import warnings from oslo_config import cfg from oslo_serialization import jsonutils import six import yaml from oslo_policy import _cache_handler from oslo_policy import _checks from oslo_policy._i18n import _ from oslo_policy import _parser from oslo_policy import opts LOG = logging.getLogger(__name__) register = _checks.register """Register a function or :class:`.Check` class as a policy check. :param name: Gives the name of the check type, e.g., "rule", "role", etc. If name is ``None``, a default check type will be registered. :param func: If given, provides the function or class to register. If not given, returns a function taking one argument to specify the function or class to register, allowing use as a decorator. """ Check = _checks.Check """A base class to allow for user-defined policy checks. :param kind: The kind of the check, i.e., the field before the ``:``. :param match: The match of the check, i.e., the field after the ``:``. """ AndCheck = _checks.AndCheck """Implements the "and" logical operator. A policy check that requires that a list of other checks all return True. :param list rules: rules that will be tested. """ NotCheck = _checks.NotCheck """Implements the "not" logical operator. A policy check that inverts the result of another policy check. :param rule: The rule to negate. """ OrCheck = _checks.OrCheck """Implements the "or" operator. A policy check that requires that at least one of a list of other checks returns ``True``. :param rules: A list of rules that will be tested. """ RuleCheck = _checks.RuleCheck """Recursively checks credentials based on the defined rules.""" class PolicyNotAuthorized(Exception): """Default exception raised for policy enforcement failure.""" def __init__(self, rule, target, creds): msg = _("%(rule)s is disallowed by policy") % {'rule': rule} super(PolicyNotAuthorized, self).__init__(msg) class InvalidScope(Exception): """Raised when the scope of the request mismatches the policy scope.""" def __init__(self, rule, operation_scopes, token_scope): msg = ( "%(rule)s requires a scope of %(operation_scopes)s, request " "was made with %(token_scope)s scope." % { 'rule': rule, 'operation_scopes': operation_scopes, 'token_scope': token_scope } ) super(InvalidScope, self).__init__(msg) class DuplicatePolicyError(Exception): def __init__(self, name): msg = _('Policy %(name)s is already registered') % {'name': name} super(DuplicatePolicyError, self).__init__(msg) class PolicyNotRegistered(Exception): def __init__(self, name): msg = _('Policy %(name)s has not been registered') % {'name': name} super(PolicyNotRegistered, self).__init__(msg) class InvalidDefinitionError(Exception): def __init__(self, names): msg = _('Policies %(names)s are not well defined. Check logs for ' 'more details.') % {'names': names} super(InvalidDefinitionError, self).__init__(msg) class InvalidRuleDefault(Exception): def __init__(self, error): msg = (_('Invalid policy rule default: ' '%(error)s.') % {'error': error}) super(InvalidRuleDefault, self).__init__(msg) def parse_file_contents(data): """Parse the raw contents of a policy file. Parses the contents of a policy file which currently can be in either yaml or json format. Both can be parsed as yaml. :param data: A string containing the contents of a policy file. :returns: A dict of the form {'policy_name1': 'policy1', 'policy_name2': 'policy2,...} """ try: # NOTE(snikitin): jsonutils.loads() is much faster than # yaml.safe_load(). However jsonutils.loads() parses only JSON while # yaml.safe_load() parses JSON and YAML. So here we try to parse data # by jsonutils.loads() first. In case of failure yaml.safe_load() # will be used instead. parsed = jsonutils.loads(data) except ValueError: try: parsed = yaml.safe_load(data) except yaml.YAMLError as e: # For backwards-compatibility, convert yaml error to ValueError, # which is what JSON loader raised. raise ValueError(six.text_type(e)) return parsed or {} class Rules(dict): """A store for rules. Handles the default_rule setting directly.""" @classmethod def load(cls, data, default_rule=None): """Allow loading of YAML/JSON rule data. .. versionadded:: 1.5.0 """ parsed_file = parse_file_contents(data) # Parse the rules rules = {k: _parser.parse_rule(v) for k, v in parsed_file.items()} return cls(rules, default_rule) @classmethod def load_json(cls, data, default_rule=None): """Allow loading of YAML/JSON rule data. .. warning:: This method is deprecated as of the 1.5.0 release in favor of :meth:`load` and may be removed in the 2.0 release. """ warnings.warn( 'The load_json() method is deprecated as of the 1.5.0 release in ' 'favor of load() and may be removed in the 2.0 release.', DeprecationWarning) return cls.load(data, default_rule) @classmethod def from_dict(cls, rules_dict, default_rule=None): """Allow loading of rule data from a dictionary.""" # Parse the rules stored in the dictionary rules = {k: _parser.parse_rule(v) for k, v in rules_dict.items()} return cls(rules, default_rule) def __init__(self, rules=None, default_rule=None): """Initialize the Rules store.""" super(Rules, self).__init__(rules or {}) self.default_rule = default_rule def __missing__(self, key): """Implements the default rule handling.""" if isinstance(self.default_rule, dict): raise KeyError(key) # If the default rule isn't actually defined, do something # reasonably intelligent if not self.default_rule: raise KeyError(key) if isinstance(self.default_rule, _checks.BaseCheck): return self.default_rule # We need to check this or we can get infinite recursion if self.default_rule not in self: raise KeyError(key) elif isinstance(self.default_rule, six.string_types): return self[self.default_rule] def __str__(self): """Dumps a string representation of the rules.""" # Start by building the canonical strings for the rules out_rules = {} for key, value in self.items(): # Use empty string for singleton TrueCheck instances if isinstance(value, _checks.TrueCheck): out_rules[key] = '' else: out_rules[key] = str(value) # Dump a pretty-printed JSON representation return jsonutils.dumps(out_rules, indent=4) class Enforcer(object): """Responsible for loading and enforcing rules. :param conf: A configuration object. :param policy_file: Custom policy file to use, if none is specified, ``conf.oslo_policy.policy_file`` will be used. :param rules: Default dictionary / Rules to use. It will be considered just in the first instantiation. If :meth:`load_rules` with ``force_reload=True``, :meth:`clear` or :meth:`set_rules` with ``overwrite=True`` is called this will be overwritten. :param default_rule: Default rule to use, conf.default_rule will be used if none is specified. :param use_conf: Whether to load rules from cache or config file. :param overwrite: Whether to overwrite existing rules when reload rules from config file. """ def __init__(self, conf, policy_file=None, rules=None, default_rule=None, use_conf=True, overwrite=True): self.conf = conf opts._register(conf) self.default_rule = (default_rule or self.conf.oslo_policy.policy_default_rule) self.rules = Rules(rules, self.default_rule) self.registered_rules = {} self.file_rules = {} self.policy_path = None self.policy_file = policy_file or self.conf.oslo_policy.policy_file self.use_conf = use_conf self.overwrite = overwrite self._loaded_files = [] self._policy_dir_mtimes = {} self._file_cache = {} self._informed_no_policy_file = False def set_rules(self, rules, overwrite=True, use_conf=False): """Create a new :class:`Rules` based on the provided dict of rules. :param dict rules: New rules to use. :param overwrite: Whether to overwrite current rules or update them with the new rules. :param use_conf: Whether to reload rules from cache or config file. """ if not isinstance(rules, dict): raise TypeError(_('Rules must be an instance of dict or Rules, ' 'got %s instead') % type(rules)) self.use_conf = use_conf if overwrite: self.rules = Rules(rules, self.default_rule) else: self.rules.update(rules) def clear(self): """Clears :class:`Enforcer` contents. This will clear this instances rules, policy's cache, file cache and policy's path. """ self.set_rules({}) self.default_rule = None self.policy_path = None self._loaded_files = [] self._policy_dir_mtimes = {} self._file_cache.clear() self.registered_rules = {} self.file_rules = {} self._informed_no_policy_file = False def load_rules(self, force_reload=False): """Loads policy_path's rules. Policy file is cached and will be reloaded if modified. :param force_reload: Whether to reload rules from config file. """ if force_reload: self.use_conf = force_reload if self.use_conf: if not self.policy_path: try: self.policy_path = self._get_policy_path(self.policy_file) except cfg.ConfigFilesNotFoundError: if not self._informed_no_policy_file: LOG.debug('The policy file %s could not be found.', self.policy_file) self._informed_no_policy_file = True if self.policy_path: self._load_policy_file(self.policy_path, force_reload, overwrite=self.overwrite) for path in self.conf.oslo_policy.policy_dirs: try: path = self._get_policy_path(path) except cfg.ConfigFilesNotFoundError: continue if (force_reload or self._is_directory_updated( self._policy_dir_mtimes, path)): self._walk_through_policy_directory(path, self._load_policy_file, force_reload, False) for default in self.registered_rules.values(): if default.deprecated_rule: deprecated_msg = ( 'Policy "%(old_name)s":"%(old_check_str)s" was ' 'deprecated in %(release)s in favor of "%(name)s":' '"%(check_str)s". Reason: %(reason)s. Either ensure ' 'your deployment is ready for the new default or ' 'copy/paste the deprecated policy into your policy ' 'file and maintain it manually.' % { 'old_name': default.deprecated_rule.name, 'old_check_str': default.deprecated_rule.check_str, 'release': default.deprecated_since, 'name': default.name, 'check_str': default.check_str, 'reason': default.deprecated_reason } ) if default.deprecated_rule.name != default.name and ( default.deprecated_rule.name in self.rules): # Print a warning because the actual policy name is # changing. If deployers are relying on an override for # foo:bar and it's getting renamed to foo:create_bar # then they need to be able to see that before they # roll out the next release. warnings.warn(deprecated_msg) if (default.deprecated_rule.check_str != default.check_str and default.name not in self.rules): # In this case, the default check_str is changing. We # need to let operators know that this is going to # change. If they don't want to override it, they are # going to have to make sure the right infrastructure # exists before they upgrade. This overrides the new # check with an OrCheck that combines the new and old # check_str attributes from the new and deprecated # policies. This will make it so that deployments don't # break on upgrade, but they receive log messages # telling them stuff is going to change if they don't # maintain the policy manually or add infrastructure to # their deployment to support the new policy. default.check = _parser.parse_rule( default.check_str + ' or ' + default.deprecated_rule.check_str ) warnings.warn(deprecated_msg) if default.deprecated_for_removal and ( default.name in self.file_rules): # If a policy is going to be removed altogether, then we # need to make sure we let operators know so they can clean # up their policy files, if they are overriding it. warnings.warn( 'Policy "%(policy)s":"%(check_str)s" was ' 'deprecated for removal in %(release)s. Reason: ' '%(reason)s. Its value may be silently ignored in ' 'the future.' % { 'policy': default.name, 'check_str': default.check_str, 'release': default.deprecated_since, 'reason': default.deprecated_reason } ) if default.name not in self.rules: self.rules[default.name] = default.check # Detect and log obvious incorrect rule definitions self.check_rules() def check_rules(self, raise_on_violation=False): """Look for rule definitions that are obviously incorrect.""" undefined_checks = [] cyclic_checks = [] violation = False for name, check in self.rules.items(): if self._undefined_check(check): undefined_checks.append(name) violation = True if self._cycle_check(check): cyclic_checks.append(name) violation = True if undefined_checks: LOG.warning('Policies %(names)s reference a rule that is not ' 'defined.', {'names': undefined_checks}) if cyclic_checks: LOG.warning('Policies %(names)s are part of a cyclical ' 'reference.', {'names': cyclic_checks}) if raise_on_violation and violation: raise InvalidDefinitionError(undefined_checks + cyclic_checks) return not violation def _undefined_check(self, check): '''Check if a RuleCheck references an undefined rule.''' if isinstance(check, RuleCheck): if check.match not in self.rules: # Undefined rule return True # An AndCheck or OrCheck is composed of multiple rules so check # each of those. rules = getattr(check, 'rules', None) if rules: for rule in rules: if self._undefined_check(rule): return True return False def _cycle_check(self, check, seen=None): '''Check if RuleChecks cycle. Looking for something like: "foo": "rule:bar" "bar": "rule:foo" ''' if seen is None: seen = set() if isinstance(check, RuleCheck): if check.match in seen: # Cycle found return True seen.add(check.match) if check.match in self.rules: # There can only be a cycle if the referenced rule is defined. if self._cycle_check(self.rules[check.match], seen): return True # An AndCheck or OrCheck is composed of multiple rules so check # each of those. rules = getattr(check, 'rules', None) if rules: for rule in rules: # As there being an OrCheck or AndCheck, a copy of the father's # seen should be called here. In order that the checks in # different branchs are seperated. if self._cycle_check(rule, seen.copy()): return True return False @staticmethod def _is_directory_updated(cache, path): # Get the current modified time and compare it to what is in # the cache and check if the new mtime is greater than what # is in the cache mtime = 0 if os.path.exists(path): # Make a list of all the files files = [path] + [os.path.join(path, file) for file in os.listdir(path)] # Pick the newest one, let's use its time. mtime = os.path.getmtime(max(files, key=os.path.getmtime)) cache_info = cache.setdefault(path, {}) if mtime > cache_info.get('mtime', 0): cache_info['mtime'] = mtime return True return False @staticmethod def _walk_through_policy_directory(path, func, *args): if not os.path.isdir(path): raise ValueError('%s is not a directory' % path) # We do not iterate over sub-directories. policy_files = next(os.walk(path))[2] policy_files.sort() for policy_file in [p for p in policy_files if not p.startswith('.')]: func(os.path.join(path, policy_file), *args) def _record_file_rules(self, data, overwrite=False): """Store a copy of rules loaded from a file. It is useful to be able to distinguish between rules loaded from a file and those registered by a consuming service. In order to do so we keep a record of rules loaded from a file. :param data: The raw contents of a policy file. :param overwrite: If True clear out previously loaded rules. """ if overwrite: self.file_rules = {} parsed_file = parse_file_contents(data) for name, check_str in parsed_file.items(): self.file_rules[name] = RuleDefault(name, check_str) def _load_policy_file(self, path, force_reload, overwrite=True): reloaded, data = _cache_handler.read_cached_file( self._file_cache, path, force_reload=force_reload) if reloaded or not self.rules: rules = Rules.load(data, self.default_rule) self.set_rules(rules, overwrite=overwrite, use_conf=True) self._record_file_rules(data, overwrite) self._loaded_files.append(path) LOG.debug('Reloaded policy file: %(path)s', {'path': path}) def _get_policy_path(self, path): """Locate the policy YAML/JSON data file/path. :param path: It's value can be a full path or related path. When full path specified, this function just returns the full path. When related path specified, this function will search configuration directories to find one that exists. :returns: The policy path :raises: ConfigFilesNotFoundError if the file/path couldn't be located. """ policy_path = self.conf.find_file(path) if policy_path: return policy_path raise cfg.ConfigFilesNotFoundError((path,)) def enforce(self, rule, target, creds, do_raise=False, exc=None, *args, **kwargs): """Checks authorization of a rule against the target and credentials. :param rule: The rule to evaluate. :type rule: string or :class:`BaseCheck` :param dict target: As much information about the object being operated on as possible. The target argument should be a dict instance or an instance of a class that fully supports the Mapping abstract base class and deep copying. :param dict creds: As much information about the user performing the action as possible. :param do_raise: Whether to raise an exception or not if check fails. :param exc: Class of the exception to raise if the check fails. Any remaining arguments passed to :meth:`enforce` (both positional and keyword arguments) will be passed to the exception class. If not specified, :class:`PolicyNotAuthorized` will be used. :return: ``False`` if the policy does not allow the action and `exc` is not provided; otherwise, returns a value that evaluates to ``True``. Note: for rules using the "case" expression, this ``True`` value will be the specified string from the expression. """ self.load_rules() # Allow the rule to be a Check tree if isinstance(rule, _checks.BaseCheck): # If the thing we're given is a Check, we don't know the # name of the rule, so pass None for current_rule. result = _checks._check( rule=rule, target=target, creds=creds, enforcer=self, current_rule=None, ) elif not self.rules: # No rules to reference means we're going to fail closed result = False else: try: to_check = self.rules[rule] except KeyError: LOG.debug('Rule [%s] does not exist', rule) # If the rule doesn't exist, fail closed result = False else: # Check the scope of the operation against the possible scope # attributes provided in `creds`. if creds.get('system'): token_scope = 'system' else: # If the token isn't system-scoped then we're dealing with # either a domain-scoped token or a project-scoped token. # From a policy perspective, both are "project" operations. # Whether or not the project is a domain depends on where # it sits in the hierarchy. token_scope = 'project' registered_rule = self.registered_rules.get(rule) if registered_rule and registered_rule.scope_types: if token_scope not in registered_rule.scope_types: if self.conf.oslo_policy.enforce_scope: raise InvalidScope( rule, registered_rule.scope_types, token_scope ) # If we don't raise an exception we should at least # inform operators about policies that are being used # with improper scopes. msg = ( 'Policy %(rule)s failed scope check. The token ' 'used to make the request was %(token_scope)s ' 'scoped but the policy requires %(policy_scope)s ' 'scope. This behavior may change in the future ' 'where using the intended scope is required' % { 'rule': rule, 'token_scope': token_scope, 'policy_scope': registered_rule.scope_types } ) warnings.warn(msg) result = _checks._check( rule=to_check, target=target, creds=creds, enforcer=self, current_rule=rule, ) # If it is False, raise the exception if requested if do_raise and not result: if exc: raise exc(*args, **kwargs) raise PolicyNotAuthorized(rule, target, creds) return result def register_default(self, default): """Registers a RuleDefault. Adds a RuleDefault to the list of registered rules. Rules must be registered before using the Enforcer.authorize method. :param default: A RuleDefault object to register. """ if default.name in self.registered_rules: raise DuplicatePolicyError(default.name) self.registered_rules[default.name] = default def register_defaults(self, defaults): """Registers a list of RuleDefaults. Adds each RuleDefault to the list of registered rules. Rules must be registered before using the Enforcer.authorize method. :param default: A list of RuleDefault objects to register. """ for default in defaults: self.register_default(default) def authorize(self, rule, target, creds, do_raise=False, exc=None, *args, **kwargs): """A wrapper around 'enforce' that checks for policy registration. To ensure that a policy being checked has been registered this method should be used rather than enforce. By doing so a project can be sure that all of it's used policies are registered and therefore available for sample file generation. The parameters match the enforce method and a description of them can be found there. """ if rule not in self.registered_rules: raise PolicyNotRegistered(rule) return self.enforce(rule, target, creds, do_raise, exc, *args, **kwargs) class RuleDefault(object): """A class for holding policy definitions. It is required to supply a name and value at creation time. It is encouraged to also supply a description to assist operators. :param name: The name of the policy. This is used when referencing it from another rule or during policy enforcement. :param check_str: The policy. This is a string defining a policy that conforms to the policy language outlined at the top of the file. :param description: A plain text description of the policy. This will be used to comment sample policy files for use by deployers. :param deprecated_rule: :class:`.DeprecatedRule` :param deprecated_for_removal: indicates whether the policy is planned for removal in a future release. :param deprecated_reason: indicates why this policy is planned for removal in a future release. Silently ignored if deprecated_for_removal is False. :param deprecated_since: indicates which release this policy was deprecated in. Accepts any string, though valid version strings are encouraged. Silently ignored if deprecated_for_removal is False. :param scope_types: A list containing the intended scopes of the operation being done. .. versionchanged 1.29 Added *deprecated_rule* parameter. .. versionchanged 1.29 Added *deprecated_for_removal* parameter. .. versionchanged 1.29 Added *deprecated_reason* parameter. .. versionchanged 1.29 Added *deprecated_since* parameter. .. versionchanged 1.31 Added *scope_types* parameter. """ def __init__(self, name, check_str, description=None, deprecated_rule=None, deprecated_for_removal=False, deprecated_reason=None, deprecated_since=None, scope_types=None): self.name = name self.check_str = check_str self.check = _parser.parse_rule(check_str) self.description = description self.deprecated_rule = copy.deepcopy(deprecated_rule) or [] self.deprecated_for_removal = deprecated_for_removal self.deprecated_reason = deprecated_reason self.deprecated_since = deprecated_since if self.deprecated_rule: if not isinstance(self.deprecated_rule, DeprecatedRule): raise ValueError( 'deprecated_rule must be a DeprecatedRule object.' ) if (deprecated_for_removal or deprecated_rule) and ( deprecated_reason is None or deprecated_since is None): raise ValueError( '%(name)s deprecated without deprecated_reason or ' 'deprecated_since. Both must be supplied if deprecating a ' 'policy' % {'name': self.name} ) if scope_types: msg = 'scope_types must be a list of strings.' if not isinstance(scope_types, list): raise ValueError(msg) for scope_type in scope_types: if not isinstance(scope_type, six.string_types): raise ValueError(msg) if scope_types.count(scope_type) > 1: raise ValueError( 'scope_types must be a list of unique strings.' ) self.scope_types = scope_types def __str__(self): return '"%(name)s": "%(check_str)s"' % {'name': self.name, 'check_str': self.check_str} def __eq__(self, other): """Equality operator. All check objects have a stable string representation. It is used for comparison rather than check_str because multiple check_str's may parse to the same check object. For instance '' and '@' are equivalent and the parsed rule string representation for both is '@'. The description does not play a role in the meaning of the check so it is not considered for equality. """ # Name should match, check should match, and class should be equivalent # or one should be a subclass of the other. if (self.name == other.name and str(self.check) == str(other.check) and (isinstance(self, other.__class__) or isinstance(other, self.__class__))): return True return False class DocumentedRuleDefault(RuleDefault): """A class for holding policy-in-code policy objects definitions This class provides the same functionality as the RuleDefault class, but it also requires additional data about the policy rule being registered. This is necessary so that proper documentation can be rendered based on the attributes of this class. Eventually, all usage of RuleDefault should be converted to use DocumentedRuleDefault. :param operations: List of dicts containing each api url and corresponding http request method. Example:: operations=[{'path': '/foo', 'method': 'GET'}, {'path': '/some', 'method': 'POST'}] """ def __init__(self, name, check_str, description, operations, deprecated_rule=None, deprecated_for_removal=False, deprecated_reason=None, deprecated_since=None, scope_types=None): super(DocumentedRuleDefault, self).__init__( name, check_str, description, deprecated_rule=deprecated_rule, deprecated_for_removal=deprecated_for_removal, deprecated_reason=deprecated_reason, deprecated_since=deprecated_since, scope_types=scope_types ) self.operations = operations @property def description(self): return self._description @description.setter def description(self, value): # Validates description isn't empty. if not value: raise InvalidRuleDefault('Description is required') self._description = value @property def operations(self): return self._operations @operations.setter def operations(self, ops): if not isinstance(ops, list): raise InvalidRuleDefault('Operations must be a list') if not ops: raise InvalidRuleDefault('Operations list must not be empty') for op in ops: if 'path' not in op: raise InvalidRuleDefault('Operation must contain a path') if 'method' not in op: raise InvalidRuleDefault('Operation must contain a method') if len(op.keys()) > 2: raise InvalidRuleDefault('Operation contains > 2 keys') self._operations = ops class DeprecatedRule(object): """Represents a Deprecated policy or rule. Here's how you can use it to change a policy's default role or rule. Assume the following policy exists in code:: from oslo_policy import policy policy.DocumentedRuleDefault( name='foo:create_bar', check_str='role:fizz', description='Create a bar.', operations=[{'path': '/v1/bars', 'method': 'POST'}] ) The next snippet will maintain the deprecated option, but allow ``foo:create_bar`` to default to ``role:bang`` instead of ``role:fizz``:: deprecated_rule = policy.DeprecatedRule( name='foo:create_bar', check_str='role:fizz' ) policy.DocumentedRuleDefault( name='foo:create_bar', check_str='role:bang', description='Create a bar.', operations=[{'path': '/v1/bars', 'method': 'POST'}], deprecated_rule=deprecated_rule, deprecated_reason='role:bang is a better default', deprecated_since='N' ) DeprecatedRule can be used to change the policy name itself. Assume the following policy exists in code:: from oslo_policy import policy policy.DocumentedRuleDefault( name='foo:post_bar', check_str='role:fizz', description='Create a bar.', operations=[{'path': '/v1/bars', 'method': 'POST'}] ) For the sake of consistency, let's say we want to replace ``foo:post_bar`` with ``foo:create_bar``, but keep the same ``check_str`` as the default. We can accomplish this by doing:: deprecated_rule = policy.DeprecatedRule( name='foo:post_bar', check_str='role:fizz' ) policy.DocumentedRuleDefault( name='foo:create_bar', check_str='role:fizz', description='Create a bar.', operations=[{'path': '/v1/bars', 'method': 'POST'}], deprecated_rule=deprecated_rule, deprecated_reason='foo:create_bar is more consistent', deprecated_since='N' ) Finally, let's use DeprecatedRule to break a policy into more granular policies. Let's assume the following policy exists in code:: policy.DocumentedRuleDefault( name='foo:bar', check_str='role:bazz', description='Create, read, update, or delete a bar.', operations=[ { 'path': '/v1/bars', 'method': 'POST' }, { 'path': '/v1/bars', 'method': 'GET' }, { 'path': '/v1/bars/{bar_id}', 'method': 'GET' }, { 'path': '/v1/bars/{bar_id}', 'method': 'PATCH' }, { 'path': '/v1/bars/{bar_id}', 'method': 'DELETE' } ] ) Here we can see the same policy is used to protect multiple operations on bars. This prevents operators from being able to assign different roles to different actions that can be taken on bar. For example, what if an operator wanted to require a less restrictive role or rule to list bars but a more restrictive rule to delete them? The following will introduce a policy that helps achieve that and deprecate the original, overly-broad policy:: deprecated_rule = policy.DeprecatedRule( name='foo:bar', check_str='role:bazz' ) policy.DocumentedRuleDefault( name='foo:create_bar', check_str='role:bang', description='Create a bar.', operations=[{'path': '/v1/bars', 'method': 'POST'}], deprecated_rule=deprecated_rule, deprecated_reason='foo:create_bar is more granular than foo:bar', deprecated_since='N' ) policy.DocumentedRuleDefault( name='foo:list_bars', check_str='role:bazz', description='List bars.', operations=[{'path': '/v1/bars', 'method': 'GET'}], deprecated_rule=deprecated_rule, deprecated_reason='foo:list_bars is more granular than foo:bar', deprecated_since='N' ) policy.DocumentedRuleDefault( name='foo:get_bar', check_str='role:bazz', description='Get a bar.', operations=[{'path': '/v1/bars/{bar_id}', 'method': 'GET'}], deprecated_rule=deprecated_rule, deprecated_reason='foo:get_bar is more granular than foo:bar', deprecated_since='N' ) policy.DocumentedRuleDefault( name='foo:update_bar', check_str='role:bang', description='Update a bar.', operations=[{'path': '/v1/bars/{bar_id}', 'method': 'PATCH'}], deprecated_rule=deprecated_rule, deprecated_reason='foo:update_bar is more granular than foo:bar', deprecated_since='N' ) policy.DocumentedRuleDefault( name='foo:delete_bar', check_str='role:bang', description='Delete a bar.', operations=[{'path': '/v1/bars/{bar_id}', 'method': 'DELETE'}], deprecated_rule=deprecated_rule, deprecated_reason='foo:delete_bar is more granular than foo:bar', deprecated_since='N' ) .. versionchanged 1.29 Added *DeprecatedRule* object. """ def __init__(self, name, check_str): """Construct a DeprecatedRule object. :param name: the policy name :param check_str: the value of the policy's check string """ self.name = name self.check_str = check_str oslo.policy-1.33.1/oslo_policy/_i18n.py0000666000175100017510000000147313224676023017734 0ustar zuulzuul00000000000000# 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. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/index.html . """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='oslo_policy') # The primary translation function using the well-known name "_" _ = _translators.primary oslo.policy-1.33.1/oslo_policy/opts.py0000666000175100017510000001152113224676041017776 0ustar zuulzuul00000000000000# 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. __all__ = [ 'list_opts', 'set_defaults', ] import copy from oslo_config import cfg from oslo_policy._i18n import _ _option_group = 'oslo_policy' _options = [ cfg.BoolOpt('enforce_scope', default=False, help=_('This option controls whether or not to enforce scope ' 'when evaluating policies. If ``True``, the scope of ' 'the token used in the request is compared to the ' '``scope_types`` of the policy being enforced. If the ' 'scopes do not match, an ``InvalidScope`` exception ' 'will be raised. If ``False``, a message will be ' 'logged informing operators that policies are being ' 'invoked with mismatching scope.')), cfg.StrOpt('policy_file', default='policy.json', help=_('The file that defines policies.'), deprecated_group='DEFAULT'), cfg.StrOpt('policy_default_rule', default='default', help=_('Default rule. Enforced when a requested rule is not ' 'found.'), deprecated_group='DEFAULT'), cfg.MultiStrOpt('policy_dirs', default=['policy.d'], help=_('Directories where policy configuration files are ' 'stored. They can be relative to any directory ' 'in the search path defined by the config_dir ' 'option, or absolute paths. The file defined by ' 'policy_file must exist for these directories to ' 'be searched. Missing or empty directories are ' 'ignored.'), deprecated_group='DEFAULT'), cfg.StrOpt('remote_content_type', choices=('application/x-www-form-urlencoded', 'application/json'), default='application/x-www-form-urlencoded', help=_("Content Type to send and receive data for " "REST based policy check")), cfg.BoolOpt('remote_ssl_verify_server_crt', help=_("server identity verification for REST based " "policy check"), default=False), cfg.StrOpt('remote_ssl_ca_crt_file', help=_("Absolute path to ca cert file for REST based " "policy check")), cfg.StrOpt('remote_ssl_client_crt_file', help=_("Absolute path to client cert for REST based " "policy check")), cfg.StrOpt('remote_ssl_client_key_file', help=_("Absolute path client key file REST based " "policy check")), ] def list_opts(): """Return a list of oslo.config options available in the library. The returned list includes all oslo.config options which may be registered at runtime by the library. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. This function is also discoverable via the 'oslo_messaging' entry point under the 'oslo.config.opts' namespace. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users by this library. :returns: a list of (group_name, opts) tuples """ return [(_option_group, copy.deepcopy(_options))] def _register(conf): """Register the policy options. We do this in a few places, so use a function to ensure it is done consistently. """ conf.register_opts(_options, group=_option_group) def set_defaults(conf, policy_file=None): """Set defaults for configuration variables. Overrides default options values. :param conf: Configuration object, managed by the caller. :type conf: oslo.config.cfg.ConfigOpts :param policy_file: The base filename for the file that defines policies. :type policy_file: unicode """ _register(conf) if policy_file is not None: conf.set_default('policy_file', policy_file, group=_option_group) oslo.policy-1.33.1/oslo_policy/__init__.py0000666000175100017510000000000013224676023020536 0ustar zuulzuul00000000000000oslo.policy-1.33.1/oslo_policy/_checks.py0000666000175100017510000002350213224676023020412 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2015 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. import abc import ast import inspect import six import stevedore if hasattr(inspect, 'getfullargspec'): getargspec = inspect.getfullargspec else: getargspec = inspect.getargspec registered_checks = {} extension_checks = None def get_extensions(): global extension_checks if extension_checks is None: em = stevedore.ExtensionManager('oslo.policy.rule_checks', invoke_on_load=False) extension_checks = { extension.name: extension.plugin for extension in em } return extension_checks def _check(rule, target, creds, enforcer, current_rule): """Evaluate the rule. This private method is meant to be used by the enforcer to call the rule. It can also be used by built-in checks that have nested rules. We use a private function because it makes it easier to change the API without having an impact on subclasses not defined within the oslo.policy library. We don't put this logic in Enforcer.enforce() and invoke that method recursively because that changes the BaseCheck API to require that the enforcer argument to __call__() be a valid Enforcer instance (as evidenced by all of the breaking unit tests). We don't put this in a private method of BaseCheck because that propagates the problem of extending the list of arguments to __call__() if subclasses change the implementation of the function. :param rule: A check object. :type rule: BaseCheck :param target: Attributes of the object of the operation. :type target: dict :param creds: Attributes of the user performing the operation. :type creds: dict :param enforcer: The Enforcer being used. :type enforcer: Enforcer :param current_rule: The name of the policy being checked. :type current_rule: str """ # Evaluate the rule argspec = getargspec(rule.__call__) rule_args = [target, creds, enforcer] # Check if the rule argument must be included or not if len(argspec.args) > 4: rule_args.append(current_rule) return rule(*rule_args) @six.add_metaclass(abc.ABCMeta) class BaseCheck(object): """Abstract base class for Check classes.""" @abc.abstractmethod def __str__(self): """String representation of the Check tree rooted at this node.""" pass @abc.abstractmethod def __call__(self, target, cred, enforcer, current_rule=None): """Triggers if instance of the class is called. Performs the check. Returns False to reject the access or a true value (not necessary True) to accept the access. """ pass class FalseCheck(BaseCheck): """A policy check that always returns ``False`` (disallow).""" def __str__(self): """Return a string representation of this check.""" return '!' def __call__(self, target, cred, enforcer, current_rule=None): """Check the policy.""" return False class TrueCheck(BaseCheck): """A policy check that always returns ``True`` (allow).""" def __str__(self): """Return a string representation of this check.""" return '@' def __call__(self, target, cred, enforcer, current_rule=None): """Check the policy.""" return True class Check(BaseCheck): def __init__(self, kind, match): self.kind = kind self.match = match def __str__(self): """Return a string representation of this check.""" return '%s:%s' % (self.kind, self.match) class NotCheck(BaseCheck): def __init__(self, rule): self.rule = rule def __str__(self): """Return a string representation of this check.""" return 'not %s' % self.rule def __call__(self, target, cred, enforcer, current_rule=None): """Check the policy. Returns the logical inverse of the wrapped check. """ return not _check(self.rule, target, cred, enforcer, current_rule) class AndCheck(BaseCheck): def __init__(self, rules): self.rules = rules def __str__(self): """Return a string representation of this check.""" return '(%s)' % ' and '.join(str(r) for r in self.rules) def __call__(self, target, cred, enforcer, current_rule=None): """Check the policy. Requires that all rules accept in order to return True. """ for rule in self.rules: if not _check(rule, target, cred, enforcer, current_rule): return False return True def add_check(self, rule): """Adds rule to be tested. Allows addition of another rule to the list of rules that will be tested. :returns: self :rtype: :class:`.AndCheck` """ self.rules.append(rule) return self class OrCheck(BaseCheck): def __init__(self, rules): self.rules = rules def __str__(self): """Return a string representation of this check.""" return '(%s)' % ' or '.join(str(r) for r in self.rules) def __call__(self, target, cred, enforcer, current_rule=None): """Check the policy. Requires that at least one rule accept in order to return True. """ for rule in self.rules: if _check(rule, target, cred, enforcer, current_rule): return True return False def add_check(self, rule): """Adds rule to be tested. Allows addition of another rule to the list of rules that will be tested. Returns the OrCheck object for convenience. """ self.rules.append(rule) return self def pop_check(self): """Pops the last check from the list and returns them :returns: self, the popped check :rtype: :class:`.OrCheck`, class:`.Check` """ check = self.rules.pop() return self, check def register(name, func=None): # Perform the actual decoration by registering the function or # class. Returns the function or class for compliance with the # decorator interface. def decorator(func): registered_checks[name] = func return func # If the function or class is given, do the registration if func: return decorator(func) return decorator @register('rule') class RuleCheck(Check): def __call__(self, target, creds, enforcer, current_rule=None): try: return _check( rule=enforcer.rules[self.match], target=target, creds=creds, enforcer=enforcer, current_rule=current_rule, ) except KeyError: # We don't have any matching rule; fail closed return False @register('role') class RoleCheck(Check): """Check that there is a matching role in the ``creds`` dict.""" def __call__(self, target, creds, enforcer, current_rule=None): try: match = self.match % target except KeyError: # While doing RoleCheck if key not # present in Target return false return False if 'roles' in creds: return match.lower() in [x.lower() for x in creds['roles']] return False @register(None) class GenericCheck(Check): """Check an individual match. Matches look like: - tenant:%(tenant_id)s - role:compute:admin - True:%(user.enabled)s - 'Member':%(role.name)s """ def _find_in_dict(self, test_value, path_segments, match): '''Searches for a match in the dictionary. test_value is a reference inside the dictionary. Since the process is recursive, each call to _find_in_dict will be one level deeper. path_segments is the segments of the path to search. The recursion ends when there are no more segments of path. When specifying a value inside a list, each element of the list is checked for a match. If the value is found within any of the sub lists the check succeeds; The check only fails if the entry is not in any of the sublists. ''' if len(path_segments) == 0: return match == six.text_type(test_value) key, path_segments = path_segments[0], path_segments[1:] try: test_value = test_value[key] except KeyError: return False if isinstance(test_value, list): for val in test_value: if self._find_in_dict(val, path_segments, match): return True return False else: return self._find_in_dict(test_value, path_segments, match) def __call__(self, target, creds, enforcer, current_rule=None): try: match = self.match % target except KeyError: # While doing GenericCheck if key not # present in Target return false return False try: # Try to interpret self.kind as a literal test_value = ast.literal_eval(self.kind) return match == six.text_type(test_value) except ValueError: pass path_segments = self.kind.split('.') return self._find_in_dict(creds, path_segments, match) oslo.policy-1.33.1/oslo_policy/sphinxpolicygen.py0000666000175100017510000000471513224676023022243 0ustar zuulzuul00000000000000# Copyright 2015 Hewlett-Packard Development Company, L.P. # Copyright 2016 Red Hat, Inc. # # 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. """Generate a sample policy file.""" import os from oslo_policy import generator def generate_sample(app): """Generate a sample policy file.""" if not app.config.policy_generator_config_file: app.warn("No policy_generator_config_file is specified, " "skipping sample policy generation") return _generate_sample(app, app.config.policy_generator_config_file, app.config.sample_policy_basename) def _generate_sample(app, policy_file, base_name): def info(msg): app.info('[%s] %s' % (__name__, msg)) # If we are given a file that isn't an absolute path, look for it # in the source directory if it doesn't exist. candidates = [ policy_file, os.path.join(app.srcdir, policy_file,), ] for c in candidates: if os.path.isfile(c): info('reading config generator instructions from %s' % c) config_path = c break else: raise ValueError( "Could not find policy_generator_config_file %r" % app.config.policy_generator_config_file) if base_name: out_file = os.path.join(app.srcdir, base_name) + '.policy.yaml.sample' if not os.path.isdir(os.path.dirname(os.path.abspath(out_file))): os.mkdir(os.path.dirname(os.path.abspath(out_file))) else: file_name = 'sample.policy.yaml' out_file = os.path.join(app.srcdir, file_name) info('writing sample policy to %s' % out_file) generator.generate_sample(args=['--config-file', config_path, '--output-file', out_file]) def setup(app): app.add_config_value('policy_generator_config_file', None, 'env') app.add_config_value('sample_policy_basename', None, 'env') app.connect('builder-inited', generate_sample) oslo.policy-1.33.1/oslo_policy/fixture.py0000666000175100017510000000423013224676023020476 0ustar zuulzuul00000000000000# 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. __all__ = ['HttpCheckFixture', 'HttpsCheckFixture'] import fixtures class HttpCheckFixture(fixtures.Fixture): """Helps short circuit the external http call""" def __init__(self, return_value=True): """Initialize the fixture. :param return_value: True implies the policy check passed and False implies that the policy check failed :type return_value: boolean """ super(HttpCheckFixture, self).__init__() self.return_value = return_value def setUp(self): super(HttpCheckFixture, self).setUp() def mocked_call(target, cred, enforcer, rule): return self.return_value self.useFixture( fixtures.MonkeyPatch( 'oslo_policy._external.HttpCheck.__call__', mocked_call, ) ) class HttpsCheckFixture(fixtures.Fixture): """Helps short circuit the external http call""" def __init__(self, return_value=True): """Initialize the fixture. :param return_value: True implies the policy check passed and False implies that the policy check failed :type return_value: boolean """ super(HttpsCheckFixture, self).__init__() self.return_value = return_value def setUp(self): super(HttpsCheckFixture, self).setUp() def mocked_call(target, cred, enforcer, rule): return self.return_value self.useFixture( fixtures.MonkeyPatch( 'oslo_policy._external.HttpsCheck.__call__', mocked_call, ) ) oslo.policy-1.33.1/oslo_policy/shell.py0000666000175100017510000000512513224676023020123 0ustar zuulzuul00000000000000#!/usr/bin/env python # 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 argparse import sys from oslo_serialization import jsonutils from oslo_policy import policy def _try_rule(key, rule, target, access_data, o): try: result = rule(target, access_data, o) if result: print("passed: %s" % key) else: print("failed: %s" % key) except Exception as e: print(e) print("exception: %s" % rule) def tool(policy_file, access_file, apply_rule, is_admin=False): access = access_file.read() access_data = jsonutils.loads(access)['token'] access_data['roles'] = [role['name'] for role in access_data['roles']] access_data['project_id'] = access_data['project']['id'] access_data['is_admin'] = is_admin policy_data = policy_file.read() rules = policy.Rules.load(policy_data, "default") class Object(object): pass o = Object() o.rules = rules target = {"project_id": access_data['project_id']} if apply_rule: key = apply_rule rule = rules[apply_rule] _try_rule(key, rule, target, access_data, o) return for key, rule in rules.items(): if ":" in key: _try_rule(key, rule, target, access_data, o) def main(): parser = argparse.ArgumentParser(sys.argv[0]) parser.add_argument( '--policy', required=True, type=argparse.FileType('rb', 0), help='path to a policy file') parser.add_argument( '--access', required=True, type=argparse.FileType('rb', 0), help='path to a file containing OpenStack Identity API' + ' access info in JSON format') parser.add_argument( '--rule', help='rule to test') parser.add_argument( '--is_admin', help='set is_admin=True on the credentials used for the evaluation') args = parser.parse_args() try: is_admin = args.is_admin.lower() == "true" except Exception: is_admin = False tool(args.policy, args.access, args.rule, is_admin) if __name__ == "__main__": sys.exit(main()) oslo.policy-1.33.1/oslo_policy/generator.py0000666000175100017510000002551613224676023021010 0ustar zuulzuul00000000000000# 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 logging import sys import textwrap from oslo_config import cfg import stevedore from oslo_policy import policy LOG = logging.getLogger(__name__) GENERATOR_OPTS = [ cfg.StrOpt('output-file', help='Path of the file to write to. Defaults to stdout.'), ] RULE_OPTS = [ cfg.MultiStrOpt('namespace', required=True, help='Option namespace(s) under "oslo.policy.policies" in ' 'which to query for options.'), cfg.StrOpt('format', help='Desired format for the output.', default='yaml', choices=['json', 'yaml']), ] ENFORCER_OPTS = [ cfg.StrOpt('namespace', required=True, help='Option namespace under "oslo.policy.enforcer" in ' 'which to look for a policy.Enforcer.'), ] def get_policies_dict(namespaces): """Find the options available via the given namespaces. :param namespaces: a list of namespaces registered under 'oslo.policy.policies' :returns: a dict of {namespace1: [rule_default_1, rule_default_2], namespace2: [rule_default_3]...} """ mgr = stevedore.named.NamedExtensionManager( 'oslo.policy.policies', names=namespaces, on_load_failure_callback=on_load_failure_callback, invoke_on_load=True) opts = {ep.name: ep.obj for ep in mgr} return opts def _get_enforcer(namespace): """Find a policy.Enforcer via an entry point with the given namespace. :param namespace: a namespace under oslo.policy.enforcer where the desired enforcer object can be found. :returns: a policy.Enforcer object """ mgr = stevedore.named.NamedExtensionManager( 'oslo.policy.enforcer', names=[namespace], on_load_failure_callback=on_load_failure_callback, invoke_on_load=True) enforcer = mgr[namespace].obj return enforcer def _format_help_text(description): """Format a comment for a policy based on the description provided. :param description: A string with helpful text. :returns: A line wrapped comment, or blank comment if description is None """ if not description: return '#' lines = description.splitlines() formatted_lines = [] # wrap each line to support multi line descriptions for line in lines: if not line: formatted_lines.append('#') else: formatted_lines.append(textwrap.fill(line, 70, initial_indent='# ', subsequent_indent='# ', break_long_words=False, replace_whitespace=False)) return "\n".join(formatted_lines) def _format_rule_default_yaml(default, include_help=True): """Create a yaml node from policy.RuleDefault or policy.DocumentedRuleDefault. :param default: A policy.RuleDefault or policy.DocumentedRuleDefault object :returns: A string containing a yaml representation of the RuleDefault """ text = ('"%(name)s": "%(check_str)s"\n' % {'name': default.name, 'check_str': default.check_str}) if include_help: op = "" if hasattr(default, 'operations'): for operation in default.operations: op += ('# %(method)s %(path)s\n' % {'method': operation['method'], 'path': operation['path']}) intended_scope = "" if getattr(default, 'scope_types', None) is not None: intended_scope = ( '# Intended scope(s): ' + ', '.join(default.scope_types) + '\n' ) text = ('%(help)s\n%(op)s%(scope)s#%(text)s\n' % {'help': _format_help_text(default.description), 'op': op, 'scope': intended_scope, 'text': text}) if default.deprecated_for_removal: text = ( '# DEPRECATED\n# "%(name)s" has been deprecated since ' '%(since)s.\n%(reason)s\n%(text)s' ) % {'name': default.name, 'since': default.deprecated_since, 'reason': _format_help_text(default.deprecated_reason), 'text': text} elif default.deprecated_rule: text = ( '# DEPRECATED\n# "%(old_name)s":"%(old_check_str)s" has been ' 'deprecated since %(since)s in favor of ' '"%(name)s":"%(check_str)s".\n' '%(reason)s\n%(text)s' ) % {'old_name': default.deprecated_rule.name, 'old_check_str': default.deprecated_rule.check_str, 'since': default.deprecated_since, 'name': default.name, 'check_str': default.check_str, 'reason': _format_help_text(default.deprecated_reason), 'text': text} return text def _format_rule_default_json(default): """Create a json node from policy.RuleDefault or policy.DocumentedRuleDefault. :param default: A policy.RuleDefault or policy.DocumentedRuleDefault object :returns: A string containing a json representation of the RuleDefault """ return ('"%(name)s": "%(check_str)s"' % {'name': default.name, 'check_str': default.check_str}) def _sort_and_format_by_section(policies, output_format='yaml', include_help=True): """Generate a list of policy section texts The text for a section will be created and returned one at a time. The sections are sorted first to provide for consistent output. Text is created in yaml format. This is done manually because PyYaml does not facilitate outputing comments. :param policies: A dict of {section1: [rule_default_1, rule_default_2], section2: [rule_default_3]} :param output_format: The format of the file to output to. """ for section in sorted(policies.keys()): rule_defaults = policies[section] for rule_default in rule_defaults: if output_format == 'yaml': yield _format_rule_default_yaml(rule_default, include_help=include_help) elif output_format == 'json': yield _format_rule_default_json(rule_default) def _generate_sample(namespaces, output_file=None, output_format='yaml', include_help=True): """Generate a sample policy file. List all of the policies available via the namespace specified in the given configuration and write them to the specified output file. :param namespaces: a list of namespaces registered under 'oslo.policy.policies'. Stevedore will look here for policy options. :param output_file: The path of a file to output to. stdout used if None. :param output_format: The format of the file to output to. :param include_help: True, generates a sample-policy file with help text along with rules in which everything is commented out. False, generates a sample-policy file with only rules. """ policies = get_policies_dict(namespaces) output_file = (open(output_file, 'w') if output_file else sys.stdout) sections_text = [] for section in _sort_and_format_by_section(policies, output_format, include_help=include_help): sections_text.append(section) if output_format == 'yaml': output_file.writelines(sections_text) elif output_format == 'json': output_file.writelines(( '{\n ', ',\n '.join(sections_text), '\n}\n')) def _generate_policy(namespace, output_file=None): """Generate a policy file showing what will be used. This takes all registered policies and merges them with what's defined in a policy file and outputs the result. That result is the effective policy that will be honored by policy checks. :param output_file: The path of a file to output to. stdout used if None. """ enforcer = _get_enforcer(namespace) # Ensure that files have been parsed enforcer.load_rules() file_rules = [policy.RuleDefault(name, default.check_str) for name, default in enforcer.file_rules.items()] registered_rules = [policy.RuleDefault(name, default.check_str) for name, default in enforcer.registered_rules.items() if name not in enforcer.file_rules] policies = {'rules': file_rules + registered_rules} output_file = (open(output_file, 'w') if output_file else sys.stdout) for section in _sort_and_format_by_section(policies, include_help=False): output_file.write(section) def _list_redundant(namespace): """Generate a list of configured policies which match defaults. This checks all policies loaded from policy files and checks to see if they match registered policies. If so then it is redundant to have them defined in a policy file and operators should consider removing them. """ enforcer = _get_enforcer(namespace) # Ensure that files have been parsed enforcer.load_rules() for name, file_rule in enforcer.file_rules.items(): reg_rule = enforcer.registered_rules.get(name, None) if reg_rule: if file_rule == reg_rule: print(reg_rule) def on_load_failure_callback(*args, **kwargs): raise def generate_sample(args=None): logging.basicConfig(level=logging.WARN) conf = cfg.ConfigOpts() conf.register_cli_opts(GENERATOR_OPTS + RULE_OPTS) conf.register_opts(GENERATOR_OPTS + RULE_OPTS) conf(args) _generate_sample(conf.namespace, conf.output_file, conf.format) def generate_policy(args=None): logging.basicConfig(level=logging.WARN) conf = cfg.ConfigOpts() conf.register_cli_opts(GENERATOR_OPTS + ENFORCER_OPTS) conf.register_opts(GENERATOR_OPTS + ENFORCER_OPTS) conf(args) _generate_policy(conf.namespace, conf.output_file) def list_redundant(args=None): logging.basicConfig(level=logging.WARN) conf = cfg.ConfigOpts() conf.register_cli_opts(ENFORCER_OPTS) conf.register_opts(ENFORCER_OPTS) conf(args) _list_redundant(conf.namespace) oslo.policy-1.33.1/oslo_policy/_external.py0000666000175100017510000001035213224676023020773 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2015 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. import contextlib import copy import os from oslo_policy import _checks from oslo_policy._i18n import _ from oslo_serialization import jsonutils import requests class HttpCheck(_checks.Check): """Check ``http:`` rules by calling to a remote server. This example implementation simply verifies that the response is exactly ``True``. """ def __call__(self, target, creds, enforcer, current_rule=None): url = ('http:' + self.match) % target data, json = self._construct_payload(creds, current_rule, enforcer, target) with contextlib.closing( requests.post(url, json=json, data=data) ) as r: return r.text.lstrip('"').rstrip('"') == 'True' @staticmethod def _construct_payload(creds, current_rule, enforcer, target): # Convert instances of object() in target temporarily to # empty dict to avoid circular reference detection # errors in jsonutils.dumps(). temp_target = copy.deepcopy(target) for key in target.keys(): element = target.get(key) if type(element) is object: temp_target[key] = {} data = json = None if (enforcer.conf.oslo_policy.remote_content_type == 'application/x-www-form-urlencoded'): data = {'rule': jsonutils.dumps(current_rule), 'target': jsonutils.dumps(temp_target), 'credentials': jsonutils.dumps(creds)} else: json = {'rule': current_rule, 'target': temp_target, 'credentials': creds} return data, json class HttpsCheck(HttpCheck): """Check ``https:`` rules by calling to a remote server. This example implementation simply verifies that the response is exactly ``True``. """ def __call__(self, target, creds, enforcer, current_rule=None): url = ('https:' + self.match) % target cert_file = enforcer.conf.oslo_policy.remote_ssl_client_crt_file key_file = enforcer.conf.oslo_policy.remote_ssl_client_key_file ca_crt_file = enforcer.conf.oslo_policy.remote_ssl_ca_crt_file verify_server = enforcer.conf.oslo_policy.remote_ssl_verify_server_crt if cert_file: if not os.path.exists(cert_file): raise RuntimeError( _("Unable to find ssl cert_file : %s") % cert_file) if not os.access(cert_file, os.R_OK): raise RuntimeError( _("Unable to access ssl cert_file : %s") % cert_file) if key_file: if not os.path.exists(key_file): raise RuntimeError( _("Unable to find ssl key_file : %s") % key_file) if not os.access(key_file, os.R_OK): raise RuntimeError( _("Unable to access ssl key_file : %s") % key_file) cert = (cert_file, key_file) if verify_server: if ca_crt_file: if not os.path.exists(ca_crt_file): raise RuntimeError( _("Unable to find ca cert_file : %s") % ca_crt_file) verify_server = ca_crt_file data, json = self._construct_payload(creds, current_rule, enforcer, target) with contextlib.closing( requests.post(url, json=json, data=data, cert=cert, verify=verify_server) ) as r: return r.text.lstrip('"').rstrip('"') == 'True' oslo.policy-1.33.1/oslo_policy/version.py0000666000175100017510000000126313224676023020500 0ustar zuulzuul00000000000000# Copyright 2016 OpenStack Foundation # # 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 pbr.version version_info = pbr.version.VersionInfo('oslo.policy') oslo.policy-1.33.1/oslo_policy/sphinxext.py0000666000175100017510000001062613224676023021050 0ustar zuulzuul00000000000000# Copyright 2017 Red Hat, Inc. # # 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. """Sphinx extension for pretty-formatting policy docs.""" import os from docutils import nodes from docutils.parsers import rst from docutils.parsers.rst import directives from docutils import statemachine from oslo_config import cfg from sphinx.util.nodes import nested_parse_with_titles from oslo_policy import generator def _indent(text): """Indent by four spaces.""" prefix = ' ' * 4 def prefixed_lines(): for line in text.splitlines(True): yield (prefix + line if line.strip() else line) return ''.join(prefixed_lines()) def _format_policy_rule(rule): """Output a definition list-style rule. For example: ``os_compute_api:servers:create`` :Default: ``rule:admin_or_owner`` :Operations: - **POST** ``/servers`` Create a server """ yield '``{}``'.format(rule.name) if rule.check_str: yield _indent(':Default: ``{}``'.format(rule.check_str)) else: yield _indent(':Default: ') if hasattr(rule, 'operations'): yield _indent(':Operations:') for operation in rule.operations: yield _indent(_indent('- **{}** ``{}``'.format( operation['method'], operation['path']))) yield '' if rule.description: for line in statemachine.string2lines( rule.description, tab_width=4, convert_whitespace=True): if line: yield _indent(line) else: yield _indent('(no description provided)') yield '' def _format_policy_section(section, rules): # The nested_parse_with_titles will ensure the correct header leve is used. yield section yield '=' * len(section) yield '' for rule in rules: for line in _format_policy_rule(rule): yield line def _format_policy(namespaces): policies = generator.get_policies_dict(namespaces) for section in sorted(policies.keys()): for line in _format_policy_section(section, policies[section]): yield line class ShowPolicyDirective(rst.Directive): has_content = False option_spec = { 'config-file': directives.unchanged, } def run(self): env = self.state.document.settings.env app = env.app config_file = self.options.get('config-file') # if the config_file option was not defined, attempt to reuse the # 'oslo_policy.sphinxpolicygen' extension's setting if not config_file and hasattr(env.config, 'policy_generator_config_file'): config_file = env.config.policy_generator_config_file # If we are given a file that isn't an absolute path, look for it # in the source directory if it doesn't exist. candidates = [ config_file, os.path.join(app.srcdir, config_file,), ] for c in candidates: if os.path.isfile(c): config_path = c break else: raise ValueError( 'could not find config file in: %s' % str(candidates) ) self.info('loading config file %s' % config_path) conf = cfg.ConfigOpts() opts = generator.GENERATOR_OPTS + generator.RULE_OPTS conf.register_cli_opts(opts) conf.register_opts(opts) conf( args=['--config-file', config_path], ) namespaces = conf.namespace[:] result = statemachine.ViewList() source_name = '<' + __name__ + '>' for line in _format_policy(namespaces): result.append(line, source_name) node = nodes.section() node.document = self.state.document nested_parse_with_titles(self.state, result, node) return node.children def setup(app): app.add_directive('show-policy', ShowPolicyDirective) oslo.policy-1.33.1/oslo_policy/_cache_handler.py0000666000175100017510000000336713224676023021721 0ustar zuulzuul00000000000000# Copyright 2011 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. import logging import os LOG = logging.getLogger(__name__) def read_cached_file(cache, filename, force_reload=False): """Read from a file if it has been modified. :param cache: dictionary to hold opaque cache. :param filename: the file path to read. :param force_reload: Whether to reload the file. :returns: A tuple with a boolean specifying if the data is fresh or not. """ if force_reload: delete_cached_file(cache, filename) reloaded = False mtime = os.path.getmtime(filename) 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() cache_info['mtime'] = mtime reloaded = True return (reloaded, cache_info['data']) def delete_cached_file(cache, filename): """Delete cached file if present. :param cache: dictionary to hold opaque cache. :param filename: filename to delete """ try: del cache[filename] except KeyError: pass oslo.policy-1.33.1/oslo_policy/_parser.py0000666000175100017510000002430013224676023020443 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2015 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. import logging import re import six from oslo_policy import _checks LOG = logging.getLogger(__name__) def reducer(*tokens): """Decorator for reduction methods. Arguments are a sequence of tokens, in order, which should trigger running this reduction method. """ def decorator(func): # Make sure we have a list of reducer sequences if not hasattr(func, 'reducers'): func.reducers = [] # Add the tokens to the list of reducer sequences func.reducers.append(list(tokens)) return func return decorator class ParseStateMeta(type): """Metaclass for the :class:`.ParseState` class. Facilitates identifying reduction methods. """ def __new__(mcs, name, bases, cls_dict): """Create the class. Injects the 'reducers' list, a list of tuples matching token sequences to the names of the corresponding reduction methods. """ reducers = [] for key, value in cls_dict.items(): if not hasattr(value, 'reducers'): continue for reduction in value.reducers: reducers.append((reduction, key)) cls_dict['reducers'] = reducers return super(ParseStateMeta, mcs).__new__(mcs, name, bases, cls_dict) @six.add_metaclass(ParseStateMeta) class ParseState(object): """Implement the core of parsing the policy language. Uses a greedy reduction algorithm to reduce a sequence of tokens into a single terminal, the value of which will be the root of the :class:`Check` tree. .. note:: Error reporting is rather lacking. The best we can get with this parser formulation is an overall "parse failed" error. Fortunately, the policy language is simple enough that this shouldn't be that big a problem. """ def __init__(self): """Initialize the ParseState.""" self.tokens = [] self.values = [] def reduce(self): """Perform a greedy reduction of the token stream. If a reducer method matches, it will be executed, then the :meth:`reduce` method will be called recursively to search for any more possible reductions. """ for reduction, methname in self.reducers: if (len(self.tokens) >= len(reduction) and self.tokens[-len(reduction):] == reduction): # Get the reduction method meth = getattr(self, methname) # Reduce the token stream results = meth(*self.values[-len(reduction):]) # Update the tokens and values self.tokens[-len(reduction):] = [r[0] for r in results] self.values[-len(reduction):] = [r[1] for r in results] # Check for any more reductions return self.reduce() def shift(self, tok, value): """Adds one more token to the state. Calls :meth:`reduce`. """ self.tokens.append(tok) self.values.append(value) # Do a greedy reduce... self.reduce() @property def result(self): """Obtain the final result of the parse. :raises ValueError: If the parse failed to reduce to a single result. """ if len(self.values) != 1: raise ValueError('Could not parse rule') return self.values[0] @reducer('(', 'check', ')') @reducer('(', 'and_expr', ')') @reducer('(', 'or_expr', ')') def _wrap_check(self, _p1, check, _p2): """Turn parenthesized expressions into a 'check' token.""" return [('check', check)] @reducer('check', 'and', 'check') def _make_and_expr(self, check1, _and, check2): """Create an 'and_expr'. Join two checks by the 'and' operator. """ return [('and_expr', _checks.AndCheck([check1, check2]))] @reducer('or_expr', 'and', 'check') def _mix_or_and_expr(self, or_expr, _and, check): """Modify the case 'A or B and C'""" or_expr, check1 = or_expr.pop_check() if isinstance(check1, _checks.AndCheck): and_expr = check1 and_expr.add_check(check) else: and_expr = _checks.AndCheck([check1, check]) return [('or_expr', or_expr.add_check(and_expr))] @reducer('and_expr', 'and', 'check') def _extend_and_expr(self, and_expr, _and, check): """Extend an 'and_expr' by adding one more check.""" return [('and_expr', and_expr.add_check(check))] @reducer('check', 'or', 'check') @reducer('and_expr', 'or', 'check') def _make_or_expr(self, check1, _or, check2): """Create an 'or_expr'. Join two checks by the 'or' operator. """ return [('or_expr', _checks.OrCheck([check1, check2]))] @reducer('or_expr', 'or', 'check') def _extend_or_expr(self, or_expr, _or, check): """Extend an 'or_expr' by adding one more check.""" return [('or_expr', or_expr.add_check(check))] @reducer('not', 'check') def _make_not_expr(self, _not, check): """Invert the result of another check.""" return [('check', _checks.NotCheck(check))] def _parse_check(rule): """Parse a single base check rule into an appropriate Check object.""" # Handle the special checks if rule == '!': return _checks.FalseCheck() elif rule == '@': return _checks.TrueCheck() try: kind, match = rule.split(':', 1) except Exception: LOG.exception('Failed to understand rule %s', rule) # If the rule is invalid, we'll fail closed return _checks.FalseCheck() # Find what implements the check extension_checks = _checks.get_extensions() if kind in extension_checks: return extension_checks[kind](kind, match) elif kind in _checks.registered_checks: return _checks.registered_checks[kind](kind, match) elif None in _checks.registered_checks: return _checks.registered_checks[None](kind, match) else: LOG.error('No handler for matches of kind %s', kind) return _checks.FalseCheck() def _parse_list_rule(rule): """Translates the old list-of-lists syntax into a tree of Check objects. Provided for backwards compatibility. """ # Empty rule defaults to True if not rule: return _checks.TrueCheck() # Outer list is joined by "or"; inner list by "and" or_list = [] for inner_rule in rule: # Skip empty inner lists if not inner_rule: continue # Handle bare strings if isinstance(inner_rule, six.string_types): inner_rule = [inner_rule] # Parse the inner rules into Check objects and_list = [_parse_check(r) for r in inner_rule] # Append the appropriate check to the or_list if len(and_list) == 1: or_list.append(and_list[0]) else: or_list.append(_checks.AndCheck(and_list)) # If we have only one check, omit the "or" if not or_list: return _checks.FalseCheck() elif len(or_list) == 1: return or_list[0] return _checks.OrCheck(or_list) # Used for tokenizing the policy language _tokenize_re = re.compile(r'\s+') def _parse_tokenize(rule): """Tokenizer for the policy language. Most of the single-character tokens are specified in the _tokenize_re; however, parentheses need to be handled specially, because they can appear inside a check string. Thankfully, those parentheses that appear inside a check string can never occur at the very beginning or end ("%(variable)s" is the correct syntax). """ for tok in _tokenize_re.split(rule): # Skip empty tokens if not tok or tok.isspace(): continue # Handle leading parens on the token clean = tok.lstrip('(') for i in range(len(tok) - len(clean)): yield '(', '(' # If it was only parentheses, continue if not clean: continue else: tok = clean # Handle trailing parens on the token clean = tok.rstrip(')') trail = len(tok) - len(clean) # Yield the cleaned token lowered = clean.lower() if lowered in ('and', 'or', 'not'): # Special tokens yield lowered, clean elif clean: # Not a special token, but not composed solely of ')' if len(tok) >= 2 and ((tok[0], tok[-1]) in [('"', '"'), ("'", "'")]): # It's a quoted string yield 'string', tok[1:-1] else: yield 'check', _parse_check(clean) # Yield the trailing parens for i in range(trail): yield ')', ')' def _parse_text_rule(rule): """Parses policy to the tree. Translates a policy written in the policy language into a tree of Check objects. """ # Empty rule means always accept if not rule: return _checks.TrueCheck() # Parse the token stream state = ParseState() for tok, value in _parse_tokenize(rule): state.shift(tok, value) try: return state.result except ValueError: # Couldn't parse the rule LOG.exception('Failed to understand rule %s', rule) # Fail closed return _checks.FalseCheck() def parse_rule(rule): """Parses a policy rule into a tree of :class:`.Check` objects.""" # If the rule is a string, it's in the policy language if isinstance(rule, six.string_types): return _parse_text_rule(rule) return _parse_list_rule(rule) oslo.policy-1.33.1/requirements.txt0000666000175100017510000000064513224676023017375 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. requests>=2.14.2 # Apache-2.0 oslo.config>=5.1.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 PyYAML>=3.10 # MIT six>=1.10.0 # MIT stevedore>=1.20.0 # Apache-2.0 oslo.policy-1.33.1/LICENSE0000666000175100017510000002363713224676023015124 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. oslo.policy-1.33.1/AUTHORS0000664000175100017510000000725313224676251015164 0ustar zuulzuul00000000000000Adam Young Adrian Vladu Akira Yoshiyama Alexander Gordeev Andreas Jaeger Andreas Jaeger Andrew Bogott Andrew Laski Ann Kamyshnikova Anthony Washington Anusha Unnam Arata Notsu Arvind Tiwari Ben Nemec Brant Knudson Chang Bo Guo ChangBo Guo(gcb) Chuck Short Corey Wright Daisuke Fujita Davanum Srinivas David Stanek Dina Belova Doug Hellmann Doug Hellmann Douglas Mendizábal Edan David Eric Brown Eric Windisch Flaper Fesp Flavio Percoco Florent Flament Gary Kotton Haiwei Xu He Jie Xu He Jie Xu Ian Cordasco Ian Cordasco Ian Wienand Ihar Hrachyshka Jamie Lennox Jason Kölker Javeme Jay Pipes Joe Gordon Joshua Harlow Julien Danjou Kamil Rykowski Kevin L. Mitchell Kirill Bespalov Kseniya Tychkova Lance Bragstad Luong Anh Tuan Mark McClain Mark McLoughlin Maruti Michael McCune Monty Taylor Nathan Kinder OpenStack Release Bot Rodrigo Duarte Sousa Ronald Bradford Sami Makki Sean Dague Sean McGinnis Sergey Kraynev Sergey Lukjanov Sergey Nikitin ShaoHe Feng Shuangtai Tian Stephen Finucane Steve Martinelli Sujitha Takashi NATSUME Thiago Paiva Thomas Duval Tim Goddard Timothy Symanczyk TommyLike Tony Breeds Tony Xu Victor Sergeyev Vishvananda Ishaya Wei Li Xu Ao Zhi Yan Liu ZhongShengping Zhongyue Luo Zuul fujioka yuuichi gecong1973 gengchc2 guohliu howardlee loooosy melissaml ricolin sonu.kumar vponomaryov zhangyanxian oslo.policy-1.33.1/PKG-INFO0000664000175100017510000000355013224676252015206 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: oslo.policy Version: 1.33.1 Summary: Oslo Policy library Home-page: https://docs.openstack.org/oslo.policy/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/oslo.policy.svg :target: https://governance.openstack.org/reference/tags/index.html .. Change things from this point on ============= oslo.policy ============= .. image:: https://img.shields.io/pypi/v/oslo.policy.svg :target: https://pypi.python.org/pypi/oslo.policy/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.policy.svg :target: https://pypi.python.org/pypi/oslo.policy/ :alt: Downloads The Oslo Policy library provides support for RBAC policy enforcement across all OpenStack services. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.policy/latest/ * Source: https://git.openstack.org/cgit/openstack/oslo.policy * Bugs: https://bugs.launchpad.net/oslo.policy Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 oslo.policy-1.33.1/tox.ini0000666000175100017510000000266613224676023015431 0ustar zuulzuul00000000000000[tox] minversion = 2.0 envlist = py35,py27,pep8,docs [testenv] install_command = pip install {opts} {packages} deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] deps = -r{toxinidir}/test-requirements.txt commands = flake8 # Run security linter bandit -r oslo_policy tests -n5 [testenv:venv] commands = {posargs} [testenv:docs] commands = python setup.py build_sphinx [testenv:cover] commands = python setup.py test --coverage --coverage-package-name=oslo_policy --testr-args='{posargs}' [flake8] show-source = True ignore = builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build [hacking] import_exceptions = oslo_policy._i18n [testenv:pip-missing-reqs] # do not install test-requirements as that will pollute the virtualenv for # determining missing packages # this also means that pip-missing-reqs must be installed separately, outside # of the requirements.txt files deps = pip_missing_reqs commands = pip-missing-reqs -d --ignore-module=oslo_policy* --ignore-module=pkg_resources --ignore-file=oslo_policy/test.py --ignore-file=oslo_policy/tests/* oslo_policy [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html oslo.policy-1.33.1/oslo.policy.egg-info/0000775000175100017510000000000013224676252020052 5ustar zuulzuul00000000000000oslo.policy-1.33.1/oslo.policy.egg-info/requires.txt0000664000175100017510000000020513224676251022446 0ustar zuulzuul00000000000000requests>=2.14.2 oslo.config>=5.1.0 oslo.i18n>=3.15.3 oslo.serialization!=2.19.1,>=2.18.0 PyYAML>=3.10 six>=1.10.0 stevedore>=1.20.0 oslo.policy-1.33.1/oslo.policy.egg-info/entry_points.txt0000664000175100017510000000066013224676251023351 0ustar zuulzuul00000000000000[console_scripts] oslopolicy-checker = oslo_policy.shell:main oslopolicy-list-redundant = oslo_policy.generator:list_redundant oslopolicy-policy-generator = oslo_policy.generator:generate_policy oslopolicy-sample-generator = oslo_policy.generator:generate_sample [oslo.config.opts] oslo.policy = oslo_policy.opts:list_opts [oslo.policy.rule_checks] http = oslo_policy._external:HttpCheck https = oslo_policy._external:HttpsCheck oslo.policy-1.33.1/oslo.policy.egg-info/SOURCES.txt0000664000175100017510000000463113224676252021742 0ustar zuulzuul00000000000000.coveragerc .mailmap .testr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/source/conf.py doc/source/index.rst doc/source/admin/index.rst doc/source/admin/policy-json-file.rst doc/source/admin/policy-yaml-file.rst doc/source/cli/index.rst doc/source/configuration/index.rst doc/source/contributor/index.rst doc/source/install/index.rst doc/source/reference/index.rst doc/source/user/history.rst doc/source/user/index.rst doc/source/user/plugins.rst doc/source/user/sphinxpolicygen.rst doc/source/user/usage.rst oslo.policy.egg-info/PKG-INFO oslo.policy.egg-info/SOURCES.txt oslo.policy.egg-info/dependency_links.txt oslo.policy.egg-info/entry_points.txt oslo.policy.egg-info/not-zip-safe oslo.policy.egg-info/pbr.json oslo.policy.egg-info/requires.txt oslo.policy.egg-info/top_level.txt oslo_policy/__init__.py oslo_policy/_cache_handler.py oslo_policy/_checks.py oslo_policy/_external.py oslo_policy/_i18n.py oslo_policy/_parser.py oslo_policy/fixture.py oslo_policy/generator.py oslo_policy/opts.py oslo_policy/policy.py oslo_policy/shell.py oslo_policy/sphinxext.py oslo_policy/sphinxpolicygen.py oslo_policy/version.py oslo_policy/locale/en_GB/LC_MESSAGES/oslo_policy.po oslo_policy/tests/__init__.py oslo_policy/tests/base.py oslo_policy/tests/test_checks.py oslo_policy/tests/test_external.py oslo_policy/tests/test_fixtures.py oslo_policy/tests/test_generator.py oslo_policy/tests/test_opts.py oslo_policy/tests/test_parser.py oslo_policy/tests/test_policy.py oslo_policy/tests/test_sphinxext.py oslo_policy/tests/test_sphinxpolicygen.py oslo_policy/tests/token_fixture.py releasenotes/notes/add-sphinxpolicygen-39e2f8fa24930b0c.yaml releasenotes/notes/add_custom_rule_check_plugins-3c15c2c7ca5e.yaml releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml releasenotes/notes/enforce_scope_types-1e92f6a34e4173ef.yaml releasenotes/notes/oslo-policy-descriptive-support-3ee688c5fa48d751.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po sample_data/auth_v3_token_admin.json sample_data/auth_v3_token_member.jsonoslo.policy-1.33.1/oslo.policy.egg-info/pbr.json0000664000175100017510000000005613224676251021530 0ustar zuulzuul00000000000000{"git_version": "4f68708", "is_release": true}oslo.policy-1.33.1/oslo.policy.egg-info/dependency_links.txt0000664000175100017510000000000113224676251024117 0ustar zuulzuul00000000000000 oslo.policy-1.33.1/oslo.policy.egg-info/not-zip-safe0000664000175100017510000000000113224676205022276 0ustar zuulzuul00000000000000 oslo.policy-1.33.1/oslo.policy.egg-info/PKG-INFO0000664000175100017510000000355013224676251021151 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: oslo.policy Version: 1.33.1 Summary: Oslo Policy library Home-page: https://docs.openstack.org/oslo.policy/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/oslo.policy.svg :target: https://governance.openstack.org/reference/tags/index.html .. Change things from this point on ============= oslo.policy ============= .. image:: https://img.shields.io/pypi/v/oslo.policy.svg :target: https://pypi.python.org/pypi/oslo.policy/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.policy.svg :target: https://pypi.python.org/pypi/oslo.policy/ :alt: Downloads The Oslo Policy library provides support for RBAC policy enforcement across all OpenStack services. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.policy/latest/ * Source: https://git.openstack.org/cgit/openstack/oslo.policy * Bugs: https://bugs.launchpad.net/oslo.policy Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 oslo.policy-1.33.1/oslo.policy.egg-info/top_level.txt0000664000175100017510000000001413224676251022576 0ustar zuulzuul00000000000000oslo_policy oslo.policy-1.33.1/.mailmap0000666000175100017510000000013113224676023015520 0ustar zuulzuul00000000000000# Format is: # # oslo.policy-1.33.1/releasenotes/0000775000175100017510000000000013224676252016577 5ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/source/0000775000175100017510000000000013224676252020077 5ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/source/conf.py0000666000175100017510000002152513224676023021401 0ustar zuulzuul00000000000000# -*- coding: 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. # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/oslo.policy' bug_project = 'oslo.policy' bug_tag = '' html_last_updated_fmt = '%Y-%m-%d %H:%M' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'oslo.policy Release Notes' copyright = u'2016, oslo.policy Developers' # Release notes do not need a version in the title, they span # multiple versions. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'oslo.policyReleaseNotesDoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'oslo.policyReleaseNotes.tex', u'oslo.policy Release Notes Documentation', u'oslo.policy Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'oslo.policyReleaseNotes', u'oslo.policy Release Notes Documentation', [u'oslo.policy Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'oslo.policyReleaseNotes', u'oslo.policy Release Notes Documentation', u'oslo.policy Developers', 'oslo.policyReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] oslo.policy-1.33.1/releasenotes/source/locale/0000775000175100017510000000000013224676252021336 5ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/source/locale/en_GB/0000775000175100017510000000000013224676252022310 5ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175100017510000000000013224676252024075 5ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000666000175100017510000000561313224676023027131 0ustar zuulzuul00000000000000# Andi Chandler , 2016. #zanata # Andi Chandler , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.policy Release Notes\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-12-04 22:02+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-12-05 10:32+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en-GB\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "1.15.0" msgstr "1.15.0" msgid "1.22.1" msgstr "1.22.1" msgid "1.29.0" msgstr "1.29.0" msgid "1.9.0" msgstr "1.9.0" msgid "" "Add ``sphinxpolicygen`` Sphinx plugin, which can be used to generate a " "sample policy file for use in documentation." msgstr "" "Add ``sphinxpolicygen`` Sphinx plugin, which can be used to generate a " "sample policy file for use in documentation." msgid "" "Add support for custom rule check plugins. ``http`` and ``https`` external " "rule checks have been converted into stevedore plugins and serve as examples." msgstr "" "Add support for custom rule check plugins. ``http`` and ``https`` external " "rule checks have been converted into stevedore plugins and serve as examples." msgid "" "Added the option to define a more descriptive policy rule by using `policy." "DocumentedRuleDefault` class. When using this class it is required that the " "description and operations parameters are defined, unlike `policy." "RuleDefault`. The operations parameter is a list of dictionaries that must " "contain the two keys 'path' and 'method' which represent the API URL and the " "HTTP REQUEST METHOD. More information can be found in the `policy usage " "documentation `_." msgstr "" "Added the option to define a more descriptive policy rule by using `policy." "DocumentedRuleDefault` class. When using this class it is required that the " "description and operations parameters are defined, unlike `policy." "RuleDefault`. The operations parameter is a list of dictionaries that must " "contain the two keys 'path' and 'method' which represent the API URL and the " "HTTP REQUEST METHOD. More information can be found in the `policy usage " "documentation `_." msgid "New Features" msgstr "New Features" msgid "Newton Series Release Notes" msgstr "Newton Series Release Notes" msgid "Ocata Series Release Notes" msgstr "Ocata Series Release Notes" msgid "Other Notes" msgstr "Other Notes" msgid "Pike Series Release Notes" msgstr "Pike Series Release Notes" msgid "Switch to reno for managing release notes." msgstr "Switch to reno for managing release notes." msgid "Unreleased Release Notes" msgstr "Unreleased Release Notes" msgid "oslo.policy Release Notes" msgstr "oslo.policy Release Notes" oslo.policy-1.33.1/releasenotes/source/locale/fr/0000775000175100017510000000000013224676252021745 5ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175100017510000000000013224676252023532 5ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000666000175100017510000000204013224676023026555 0ustar zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.policy Release Notes 1.16.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-10-23 20:40+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 06:02+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.7.3\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "1.15.0" msgstr "1.15.0" msgid "1.9.0" msgstr "1.9.0" msgid "New Features" msgstr "Nouvelles fonctionnalités" msgid "Newton Series Release Notes" msgstr "Note de release pour Newton" msgid "Other Notes" msgstr "Autres notes" msgid "Switch to reno for managing release notes." msgstr "Commence à utiliser reno pour la gestion des notes de release" msgid "Unreleased Release Notes" msgstr "Note de release pour les changements non déployées" msgid "oslo.policy Release Notes" msgstr "Note de release pour oslo.policy" oslo.policy-1.33.1/releasenotes/source/newton.rst0000666000175100017510000000021613224676023022140 0ustar zuulzuul00000000000000============================= Newton Series Release Notes ============================= .. release-notes:: :branch: origin/stable/newton oslo.policy-1.33.1/releasenotes/source/_static/0000775000175100017510000000000013224676252021525 5ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/source/_static/.placeholder0000666000175100017510000000000013224676023023774 0ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/source/unreleased.rst0000666000175100017510000000014413224676023022755 0ustar zuulzuul00000000000000========================== Unreleased Release Notes ========================== .. release-notes:: oslo.policy-1.33.1/releasenotes/source/index.rst0000666000175100017510000000024113224676023021733 0ustar zuulzuul00000000000000=========================== oslo.policy Release Notes =========================== .. toctree:: :maxdepth: 1 unreleased pike ocata newton oslo.policy-1.33.1/releasenotes/source/ocata.rst0000666000175100017510000000023013224676023021711 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata oslo.policy-1.33.1/releasenotes/source/_templates/0000775000175100017510000000000013224676252022234 5ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013224676023024503 0ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/source/pike.rst0000666000175100017510000000021713224676023021557 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike oslo.policy-1.33.1/releasenotes/notes/0000775000175100017510000000000013224676252017727 5ustar zuulzuul00000000000000oslo.policy-1.33.1/releasenotes/notes/oslo-policy-descriptive-support-3ee688c5fa48d751.yaml0000666000175100017510000000107413224676023031351 0ustar zuulzuul00000000000000--- features: - | Added the option to define a more descriptive policy rule by using `policy.DocumentedRuleDefault` class. When using this class it is required that the description and operations parameters are defined, unlike `policy.RuleDefault`. The operations parameter is a list of dictionaries that must contain the two keys 'path' and 'method' which represent the API URL and the HTTP REQUEST METHOD. More information can be found in the `policy usage documentation `_. oslo.policy-1.33.1/releasenotes/notes/add-sphinxpolicygen-39e2f8fa24930b0c.yaml0000666000175100017510000000022013224676023026770 0ustar zuulzuul00000000000000--- features: - | Add ``sphinxpolicygen`` Sphinx plugin, which can be used to generate a sample policy file for use in documentation. oslo.policy-1.33.1/releasenotes/notes/enforce_scope_types-1e92f6a34e4173ef.yaml0000666000175100017510000000141213224676041027066 0ustar zuulzuul00000000000000--- features: - | A new configuration option has been added to the ``[oslo_policy]`` group called ``enforce_scope``. When set to ``True``, oslo.policy will raise an ``InvalidScope`` exception if the context passed into the enforce method doesn't match the policy's ``scope_types``. If ``False``, a warning will be logged for operators. Note that operators should only enable this option once they've audited their users to ensure system users have roles on the system. This could potentially prevent some users from being able to make system-level API calls. This will also give other services the flexibility to fix long-standing RBAC issues in OpenStack once they start introducing ``scope_types`` for policies used in their service. oslo.policy-1.33.1/releasenotes/notes/add_custom_rule_check_plugins-3c15c2c7ca5e.yaml0000666000175100017510000000027013224676023030545 0ustar zuulzuul00000000000000--- features: - | Add support for custom rule check plugins. ``http`` and ``https`` external rule checks have been converted into stevedore plugins and serve as examples.oslo.policy-1.33.1/releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml0000666000175100017510000000007113224676023024606 0ustar zuulzuul00000000000000--- other: - Switch to reno for managing release notes.oslo.policy-1.33.1/.coveragerc0000666000175100017510000000021113224676023016217 0ustar zuulzuul00000000000000[run] branch = True source = oslo_policy omit = oslo_policy/tests/*,oslo_policy/openstack/* [report] ignore_errors = True precision = 2 oslo.policy-1.33.1/.testr.conf0000666000175100017510000000051313224676023016171 0ustar zuulzuul00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ ./oslo_policy $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list oslo.policy-1.33.1/README.rst0000666000175100017510000000155313224676023015577 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/oslo.policy.svg :target: https://governance.openstack.org/reference/tags/index.html .. Change things from this point on ============= oslo.policy ============= .. image:: https://img.shields.io/pypi/v/oslo.policy.svg :target: https://pypi.python.org/pypi/oslo.policy/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.policy.svg :target: https://pypi.python.org/pypi/oslo.policy/ :alt: Downloads The Oslo Policy library provides support for RBAC policy enforcement across all OpenStack services. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.policy/latest/ * Source: https://git.openstack.org/cgit/openstack/oslo.policy * Bugs: https://bugs.launchpad.net/oslo.policy oslo.policy-1.33.1/doc/0000775000175100017510000000000013224676252014653 5ustar zuulzuul00000000000000oslo.policy-1.33.1/doc/source/0000775000175100017510000000000013224676252016153 5ustar zuulzuul00000000000000oslo.policy-1.33.1/doc/source/user/0000775000175100017510000000000013224676252017131 5ustar zuulzuul00000000000000oslo.policy-1.33.1/doc/source/user/sphinxpolicygen.rst0000666000175100017510000000356513224676023023115 0ustar zuulzuul00000000000000==================================== Sphinx Oslo Sample Policy Generation ==================================== .. note:: This extension relies on ``oslopolicy-sample-generator``, which requires configuration of policies in code to function. Refer to the :doc:`usage` guide for more information. oslo.policy includes a sphinx extension to generate a sample policy file at the beginning of each sphinx build. This sample policy file can then be included in your documents as a raw file, for example, via the ``literalinclude`` directive. To activate the extension add ``oslo_policy.sphinxpolicygen`` to the list of extensions in your sphinx ``conf.py``. Once enabled, you need to define two options: ``policy_generator_config_file`` and ``sample_policy_basename``. For example:: policy_generator_config_file = '../../etc/nova/nova-policy-generator.conf' sample_policy_basename = '_static/nova' where: ``policy_generator_config_file`` Path to an configuration file used with the ``oslopolicy-sample-generator`` utility. This can be an full path or a value relative to the documentation source directory (``app.srcdir``). If this option is not specified or is invalid then the sample policy file generation will be skipped. ``sample_policy_basename`` Base name of the output file. This name will be appended with a ``.policy.yaml.sample`` extension to generate the final output file and the path is relative to documentation source directory (``app.srcdir``). As such, using the above example, the policy file will be output to ``_static/nova.policy.yaml.sample``. If this option is not specified, the file will be output to ``sample.policy.yaml``. Once configured, you can include this configuration file in your source: .. code:: reST ============= Sample Policy ============= Here is a sample policy file. .. literalinclude:: _static/nova.policy.yaml.sample oslo.policy-1.33.1/doc/source/user/usage.rst0000666000175100017510000001555413224676023020777 0ustar zuulzuul00000000000000======= Usage ======= To use oslo.policy in a project, import the relevant module. For example:: from oslo_policy import policy Migrating to oslo.policy ======================== Applications using the incubated version of the policy code from Oslo aside from changing the way the library is imported, may need to make some extra changes. Changes to Enforcer Initialization ---------------------------------- The ``oslo.policy`` library no longer assumes a global configuration object is available. Instead the :py:class:`oslo_policy.policy.Enforcer` class has been changed to expect the consuming application to pass in an ``oslo.config`` configuration object. When using policy from oslo-incubator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: enforcer = policy.Enforcer(policy_file=_POLICY_PATH) When using oslo.policy ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: from oslo_config import cfg CONF = cfg.CONF enforcer = policy.Enforcer(CONF, policy_file=_POLICY_PATH) Registering policy defaults in code =================================== A project can register policy defaults in their code which brings with it some benefits. * A deployer only needs to add a policy file if they wish to override the project defaults. * Projects can use Enforcer.authorize to ensure that a policy check is being done against a registered policy. This can be used to ensure that all policies used are registered. The signature of Enforcer.authorize matches Enforcer.enforce. * Projects can register policies as `DocumentedRuleDefault` objects, which require a method and path of the corresponding policy. This helps policy readers understand which path maps to a particular policy ultimately providing better documentation. * A sample policy file can be generated based on the registered policies rather than needing to manually maintain one. * A policy file can be generated which is a merge of registered defaults and policies loaded from a file. This shows the effective policy in use. * A list can be generated which contains policies defined in a file which match defaults registered in code. These are candidates for removal from the file in order to keep it small and understandable. How to register --------------- :: from oslo_config import cfg CONF = cfg.CONF enforcer = policy.Enforcer(CONF, policy_file=_POLICY_PATH) base_rules = [ policy.RuleDefault('admin_required', 'role:admin or is_admin:1', description='Who is considered an admin'), policy.RuleDefault('service_role', 'role:service', description='service role'), ] enforcer.register_defaults(base_rules) enforcer.register_default(policy.RuleDefault('identity:create_region', 'rule:admin_required', description='helpful text')) To provide more information about the policy, use the `DocumentedRuleDefault` class:: enforcer.register_default( policy.DocumentedRuleDefault( 'identity:create_region', 'rule:admin_required', 'helpful text', [{'path': '/regions/{region_id}', 'method': 'POST'}] ) ) The `DocumentedRuleDefault` class inherits from the `RuleDefault` implementation, but it must be supplied with the `description` attribute in order to be used. In addition, the `DocumentedRuleDefault` class requires a new `operations` attributes that is a list of dictionaries. Each dictionary must have a `path` and a `method` key. The `path` should map to the path used to interact with the resource the policy protects. The `method` should be the HTTP verb corresponding to the `path`. The list of `operations` can be supplied with multiple dictionaries if the policy is used to protect multiple paths. Setting scope ------------- The `RuleDefault` and `DocumentedRuleDefault` objects have an attribute dedicated to the intended scope of the operation called `scope_types`. This attribute can only be set at rule definition and never overridden via a policy file. This variable is designed to save the scope at which a policy should operate. During enforcement, the information in `scope_types` is compared to the scope of the token used in the request. Sample file generation ---------------------- In setup.cfg of a project using oslo.policy:: [entry_points] oslo.policy.policies = nova = nova.policy:list_policies where list_policies is a method that returns a list of policy.RuleDefault objects. Run the oslopolicy-sample-generator script with some configuration options:: oslopolicy-sample-generator --namespace nova --output-file policy-sample.yaml or:: oslopolicy-sample-generator --config-file policy-generator.conf where policy-generator.conf looks like:: [DEFAULT] output_file = policy-sample.yaml namespace = nova If output_file is omitted the sample file will be sent to stdout. Merged file generation ---------------------- This will output a policy file which includes all registered policy defaults and all policies configured with a policy file. This file shows the effective policy in use by the project. In setup.cfg of a project using oslo.policy:: [entry_points] oslo.policy.enforcer = nova = nova.policy:get_enforcer where get_enforcer is a method that returns a configured oslo_policy.policy.Enforcer object. This object should be setup exactly as it is used for actual policy enforcement, if it differs the generated policy file may not match reality. Run the oslopolicy-policy-generator script with some configuration options:: oslopolicy-policy-generator --namespace nova --output-file policy-merged.yaml or:: oslopolicy-policy-generator --config-file policy-merged-generator.conf where policy-merged-generator.conf looks like:: [DEFAULT] output_file = policy-merged.yaml namespace = nova If output_file is omitted the file will be sent to stdout. List of redundant configuration ------------------------------- This will output a list of matches for policy rules that are defined in a configuration file where the rule does not differ from a registered default rule. These are rules that can be removed from the policy file with no change in effective policy. In setup.cfg of a project using oslo.policy:: [entry_points] oslo.policy.enforcer = nova = nova.policy:get_enforcer where get_enforcer is a method that returns a configured oslo_policy.policy.Enforcer object. This object should be setup exactly as it is used for actual policy enforcement, if it differs the generated policy file may not match reality. Run the oslopolicy-list-redundant script:: oslopolicy-list-redundant --namespace nova or:: oslopolicy-list-redundant --config-file policy-redundant.conf where policy-redundant.conf looks like:: [DEFAULT] namespace = nova Output will go to stdout. oslo.policy-1.33.1/doc/source/user/plugins.rst0000666000175100017510000000264213224676023021346 0ustar zuulzuul00000000000000========================== Writing custom check rules ========================== oslo.policy has supported the following syntax for a while:: http:, which delegates the check to a remote server Starting with 1.29, oslo.policy will also support https url(s) as well:: https:, which delegates the check to a remote server Both ``http`` and ``https`` support are implemented as custom check rules. If you see the setup.cfg for oslo.policy, you can see the following entry points:: oslo.policy.rule_checks = http = oslo_policy._external:HttpCheck https = oslo_policy._external:HttpsCheck When a policy is evaluated, when the engine encounters ``https`` like in a snippet below:: { ... "target 1" : "https://foo.bar/baz", ... } The engine will look for a plugin named ``https`` in the ``rule_checks`` entry point and will try to invoke that stevedore plugin. This mechanism allows anyone to write their own code, in their own library with their own custom stevedore based rule check plugins and can enhance their policies with custom checks. This would be useful for example to integrate with a in-house policy server. Example code - HttpCheck ======================== .. note:: Full source located at :example:`_external.py` .. literalinclude:: ../../../oslo_policy/_external.py :language: python :linenos: :lines: 28-64oslo.policy-1.33.1/doc/source/user/history.rst0000666000175100017510000000010413224676023021355 0ustar zuulzuul00000000000000=========== ChangeLog =========== .. include:: ../../../ChangeLog oslo.policy-1.33.1/doc/source/user/index.rst0000666000175100017510000000017513224676023020773 0ustar zuulzuul00000000000000=================== Using oslo.policy =================== .. toctree:: usage plugins sphinxpolicygen history oslo.policy-1.33.1/doc/source/conf.py0000777000175100017510000000614013224676023017454 0ustar zuulzuul00000000000000# -*- coding: 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. import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.extlinks', #'sphinx.ext.intersphinx', 'openstackdocstheme', 'oslo_config.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/oslo.policy' bug_project = 'oslo.policy' bug_tag = '' html_last_updated_fmt = '%Y-%m-%d %H:%M' # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['api/oslo_policy.tests.*', 'api/setup.rst'] # General information about the project. project = u'oslo.policy' copyright = u'2014, OpenStack Foundation' source_tree = 'https://git.openstack.org/cgit/openstack/oslo.policy/tree' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['oslo_policy.'] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ["."] # html_theme = '_theme' # html_static_path = ['static'] html_theme = 'openstackdocs' # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', '%s.tex' % project, u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] # Shortened external links. extlinks = { 'example': (source_tree + '/oslo_policy/%s', ''), } # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} oslo.policy-1.33.1/doc/source/configuration/0000775000175100017510000000000013224676252021022 5ustar zuulzuul00000000000000oslo.policy-1.33.1/doc/source/configuration/index.rst0000666000175100017510000000044213224676023022661 0ustar zuulzuul00000000000000======================= Configuration Options ======================= oslo.policy uses oslo.config to define and manage configuration options to allow the deployer to control where the policy files are located and the default rule to apply when policy etc. .. show-options:: oslo.policy oslo.policy-1.33.1/doc/source/admin/0000775000175100017510000000000013224676252017243 5ustar zuulzuul00000000000000oslo.policy-1.33.1/doc/source/admin/policy-json-file.rst0000666000175100017510000002052113224676023023156 0ustar zuulzuul00000000000000==================== The policy.json file ==================== .. warning:: While the old json format policy file is still supported, we recommend using the :doc:`newer YAML format `. Each OpenStack service, Identity, Compute, Networking, and so on, has its own role-based access policies. They determine which user can access which objects in which way, and are defined in the service's ``policy.json`` file. Whenever an API call to an OpenStack service is made, the service's policy engine uses the appropriate policy definitions to determine if the call can be accepted. Any changes to ``policy.json`` are effective immediately, which allows new policies to be implemented while the service is running. A ``policy.json`` file is a text file in JSON (Javascript Object Notation) format. Each policy is defined by a one-line statement in the form ``"" : ""``. The policy target, also named "action", represents an API call like "start an instance" or "attach a volume". Action names are usually qualified. For example, the Compute service features API calls to list instances, volumes, and networks. In ``/etc/nova/policy.json``, these APIs are represented by ``compute:get_all``, ``volume:get_all``, and ``network:get_all``, respectively. The mapping between API calls and actions is not generally documented. The policy rule determines under which circumstances the API call is permitted. Usually this involves the user who makes the call (hereafter named the "API user") and often the object on which the API call operates. A typical rule checks if the API user is the object's owner. .. warning:: **Modifying the policy** While recipes for editing ``policy.json`` files are found on blogs, modifying the policy can have unexpected side effects and is not encouraged. Examples ~~~~~~~~ A simple rule might look like this: .. code-block:: none "compute:get_all" : "" The target is ``"compute:get_all"``, the "list all instances" API of the Compute service. The rule is an empty string meaning "always". This policy allows anybody to list instances. You can also decline permission to use an API: .. code-block:: none "compute:shelve": "!" The exclamation mark stands for "never" or "nobody", which effectively disables the Compute API "shelve an instance". Many APIs can only be called by administrators. This can be expressed by the rule ``"role:admin"``. The following policy ensures that only administrators can create new users in the Identity database: .. code-block:: none "identity:create_user" : "role:admin" You can limit APIs to any role. For example, the Orchestration service defines a role named ``heat_stack_user``. Whoever has this role is not allowed to create stacks: .. code-block:: none "stacks:create": "not role:heat_stack_user" This rule makes use of the boolean operator ``not``. More complex rules can be built using operators ``and``, ``or``, and parentheses. You can define aliases for rules: .. code-block:: none "deny_stack_user": "not role:heat_stack_user" The policy engine understands that ``"deny_stack_user"`` is not an API and consequently interprets it as an alias. The stack creation policy above can then be written as: .. code-block:: none "stacks:create": "rule:deny_stack_user" This is taken verbatim from ``/etc/heat/policy.json``. Rules can compare API attributes to object attributes. For example: .. code-block:: none "os_compute_api:servers:start" : "project_id:%(project_id)s" states that only the owner of an instance can start it up. The ``project_id`` string before the colon is an API attribute, namely the project ID of the API user. It is compared with the project ID of the object (in this case, an instance). More precisely, it is compared with the ``project_id`` field of that object in the database. If the two values are equal, permission is granted. An administrator always has permission to call APIs. This is how ``/etc/keystone/policy.json`` makes this policy explicit: .. code-block:: none "admin_required": "role:admin or is_admin:1", "owner" : "user_id:%(user_id)s", "admin_or_owner": "rule:admin_required or rule:owner", "identity:change_password": "rule:admin_or_owner" The first line defines an alias for "user is an admin user". The ``is_admin`` flag is only used when setting up the Identity service for the first time. It indicates that the user has admin privileges granted by the service token (``--os-token`` parameter of the ``keystone`` command line client). The second line creates an alias for "user owns the object" by comparing the API's user ID with the object's user ID. Line 3 defines a third alias ``admin_or_owner``, combining the two first aliases with the Boolean operator ``or``. Line 4 sets up the policy that a password can only be modified by its owner or an admin user. As a final example, let's examine a more complex rule: .. code-block:: none "identity:ec2_delete_credential": "rule:admin_required or (rule:owner and user_id:%(target.credential.user_id)s)" This rule determines who can use the Identity API "delete EC2 credential". Here, boolean operators and parentheses combine three simpler rules. ``admin_required`` and ``owner`` are the same aliases as in the previous example. ``user_id:%(target.credential.user_id)s`` compares the API user with the user ID of the credential object associated with the target. Syntax ~~~~~~ A ``policy.json`` file consists of policies and aliases of the form ``target:rule`` or ``alias:definition``, separated by commas and enclosed in curly braces: .. code-block:: none { "alias 1" : "definition 1", "alias 2" : "definition 2", ... "target 1" : "rule 1", "target 2" : "rule 2", .... } Targets are APIs and are written ``"service:API"`` or simply ``"API"``. For example, ``"compute:create"`` or ``"add_image"``. Rules determine whether the API call is allowed. Rules can be: - always true. The action is always permitted. This can be written as ``""`` (empty string), ``[]``, or ``"@"``. - always false. The action is never permitted. Written as ``"!"``. - a special check - a comparison of two values - boolean expressions based on simpler rules Special checks are: - ``:``, a test whether the API credentials contain this role. - ``:``, the definition of an alias. - ``http:``, which delegates the check to a remote server. The API is authorized when the server returns True. Developers can define additional special checks. Two values are compared in the following way: .. code-block:: none "value1 : value2" Possible values are: - constants: Strings, numbers, ``true``, ``false`` - API attributes - target object attributes - the flag ``is_admin`` API attributes can be ``project_id``, ``user_id`` or ``domain_id``. 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. ``is_admin`` indicates that administrative privileges are granted via the admin token mechanism (the ``--os-token`` option of the ``keystone`` command). The admin token allows initialisation of the Identity database before the admin role exists. The alias construct exists for convenience. An alias is short name for a complex or hard to understand rule. It is defined in the same way as a policy: .. code-block:: none alias name : alias definition Once an alias is defined, use the ``rule`` keyword to use it in a policy rule. Older syntax ~~~~~~~~~~~~ You may encounter older ``policy.json`` files that feature a different syntax, where JavaScript arrays are used instead of boolean operators. For example, the EC2 credentials rule above would have been written as follows: .. code-block:: none "identity:ec2_delete_credential": [ [ "rule:admin_required ], [ "rule:owner", "user_id:%(target.credential.user_id)s)" ] ] The rule is an array of arrays. The innermost arrays are or'ed together, whereas elements inside the innermost arrays are and'ed. While the old syntax is still supported, we recommend using the newer, more intuitive syntax. oslo.policy-1.33.1/doc/source/admin/policy-yaml-file.rst0000666000175100017510000001677513224676023023167 0ustar zuulzuul00000000000000==================== The policy.yaml file ==================== Each OpenStack service, Identity, Compute, Networking, and so on, has its own role-based access policies. They determine which user can access which objects in which way, and are defined in the service's ``policy.yaml`` file. Whenever an API call to an OpenStack service is made, the service's policy engine uses the appropriate policy definitions to determine if the call can be accepted. Any changes to ``policy.yaml`` are effective immediately, which allows new policies to be implemented while the service is running. A ``policy.yaml`` file is a text file in YAML (YAML Ain't Markup Language) format. Each policy is defined by a one-line statement in the form ``"" : ""``. The policy target, also named "action", represents an API call like "start an instance" or "attach a volume". Action names are usually qualified. For example, the Compute service features API calls to list instances, volumes, and networks. In ``/etc/nova/policy.yaml``, these APIs are represented by ``compute:get_all``, ``volume:get_all``, and ``network:get_all``, respectively. The mapping between API calls and actions is not generally documented. The policy rule determines under which circumstances the API call is permitted. Usually this involves the user who makes the call (hereafter named the "API user") and often the object on which the API call operates. A typical rule checks if the API user is the object's owner. .. warning:: **Modifying the policy** While recipes for editing ``policy.yaml`` files are found on blogs, modifying the policy can have unexpected side effects and is not encouraged. Examples ~~~~~~~~ A simple rule might look like this: .. code-block:: yaml "compute:get_all" : "" The target is ``"compute:get_all"``, the "list all instances" API of the Compute service. The rule is an empty string meaning "always". This policy allows anybody to list instances. You can also decline permission to use an API: .. code-block:: yaml "compute:shelve": "!" The exclamation mark stands for "never" or "nobody", which effectively disables the Compute API "shelve an instance". Many APIs can only be called by administrators. This can be expressed by the rule ``"role:admin"``. The following policy ensures that only administrators can create new users in the Identity database: .. code-block:: yaml "identity:create_user" : "role:admin" You can limit APIs to any role. For example, the Orchestration service defines a role named ``heat_stack_user``. Whoever has this role is not allowed to create stacks: .. code-block:: yaml "stacks:create": "not role:heat_stack_user" This rule makes use of the boolean operator ``not``. More complex rules can be built using operators ``and``, ``or``, and parentheses. You can define aliases for rules: .. code-block:: yaml "deny_stack_user": "not role:heat_stack_user" The policy engine understands that ``"deny_stack_user"`` is not an API and consequently interprets it as an alias. The stack creation policy above can then be written as: .. code-block:: yaml "stacks:create": "rule:deny_stack_user" This is taken verbatim from ``/etc/heat/policy.yaml``. Rules can compare API attributes to object attributes. For example: .. code-block:: yaml "os_compute_api:servers:start" : "project_id:%(project_id)s" states that only the owner of an instance can start it up. The ``project_id`` string before the colon is an API attribute, namely the project ID of the API user. It is compared with the project ID of the object (in this case, an instance). More precisely, it is compared with the ``project_id`` field of that object in the database. If the two values are equal, permission is granted. An administrator always has permission to call APIs. This is how ``/etc/keystone/policy.yaml`` makes this policy explicit: .. code-block:: yaml "admin_required": "role:admin or is_admin:1" "owner" : "user_id:%(user_id)s" "admin_or_owner": "rule:admin_required or rule:owner" "identity:change_password": "rule:admin_or_owner" The first line defines an alias for "user is an admin user". The ``is_admin`` flag is only used when setting up the Identity service for the first time. It indicates that the user has admin privileges granted by the service token (``--os-token`` parameter of the ``keystone`` command line client). The second line creates an alias for "user owns the object" by comparing the API's user ID with the object's user ID. Line 3 defines a third alias ``admin_or_owner``, combining the two first aliases with the Boolean operator ``or``. Line 4 sets up the policy that a password can only be modified by its owner or an admin user. As a final example, let's examine a more complex rule: .. code-block:: yaml "identity:ec2_delete_credential": "rule:admin_required or (rule:owner and user_id:%(target.credential.user_id)s)" This rule determines who can use the Identity API "delete EC2 credential". Here, boolean operators and parentheses combine three simpler rules. ``admin_required`` and ``owner`` are the same aliases as in the previous example. ``user_id:%(target.credential.user_id)s`` compares the API user with the user ID of the credential object associated with the target. Syntax ~~~~~~ A ``policy.yaml`` file consists of policies and aliases of the form ``target:rule`` or ``alias:definition``, separated by commas and enclosed in curly braces: .. code-block:: yaml "alias 1" : "definition 1" "alias 2" : "definition 2" .... "target 1" : "rule 1" "target 2" : "rule 2" .... Targets are APIs and are written ``"service:API"`` or simply ``"API"``. For example, ``"compute:create"`` or ``"add_image"``. Rules determine whether the API call is allowed. Rules can be: - Always true. The action is always permitted. This can be written as ``""`` (empty string), ``[]``, or ``"@"``. - Always false. The action is never permitted. Written as ``"!"``. - A special check - A comparison of two values - Boolean expressions based on simpler rules Special checks are: - ``:``, a test whether the API credentials contain this role. - ``:``, the definition of an alias. - ``http:``, which delegates the check to a remote server. The API is authorized when the server returns True. Developers can define additional special checks. Two values are compared in the following way: .. code-block:: yaml "value1 : value2" Possible values are: - Constants: Strings, numbers, ``true``, ``false`` - API attributes - Target object attributes - The flag ``is_admin`` API attributes can be ``project_id``, ``user_id`` or ``domain_id``. 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. ``is_admin`` indicates that administrative privileges are granted via the admin token mechanism (the ``--os-token`` option of the ``keystone`` command). The admin token allows initialisation of the Identity database before the admin role exists. The alias construct exists for convenience. An alias is short name for a complex or hard to understand rule. It is defined in the same way as a policy: .. code-block:: yaml alias name : alias definition Once an alias is defined, use the ``rule`` keyword to use it in a policy rule. oslo.policy-1.33.1/doc/source/admin/index.rst0000666000175100017510000000033413224676023021102 0ustar zuulzuul00000000000000================================================= Administering Applications that use oslo.policy ================================================= .. toctree:: :maxdepth: 2 policy-yaml-file policy-json-file oslo.policy-1.33.1/doc/source/install/0000775000175100017510000000000013224676252017621 5ustar zuulzuul00000000000000oslo.policy-1.33.1/doc/source/install/index.rst0000666000175100017510000000032413224676023021457 0ustar zuulzuul00000000000000============== Installation ============== At the command line:: $ pip install oslo.policy Or, if you want to use it in a virtualenvwrapper:: $ mkvirtualenv oslo.policy $ pip install oslo.policy oslo.policy-1.33.1/doc/source/cli/0000775000175100017510000000000013224676252016722 5ustar zuulzuul00000000000000oslo.policy-1.33.1/doc/source/cli/index.rst0000666000175100017510000000265713224676023020573 0ustar zuulzuul00000000000000====================== Command Line Interface ====================== Run the command line ``oslopolicy-checker`` to check policy against the OpenStack Identity API access information. Command-line arguments: * ``--policy POLICY`` path to policy file. * ``--access ACCESS`` path to access token file. * ``--rule RULE`` (optional) rule to test. If omitted, tests all rules. * ``--is_admin IS_ADMIN`` (optional) set is_admin=True on the credentials. Sample access tokens are provided in the ``sample_data`` directory. Examples -------- Test all of Nova's policy with an admin token .. code-block:: bash tox -e venv -- oslopolicy-checker \ --policy /opt/stack/nova/etc/nova/policy.json --access sample_data/auth_v3_token_admin.json Test the ``compute_extension:flavorextraspecs:index`` rule in Nova's policy with the admin member token and ``is_admin`` set to ``True`` .. code-block:: bash tox -e venv -- oslopolicy-checker \ --policy /opt/stack/nova/etc/nova/policy.json \ --access sample_data/auth_v3_token_admin.json \ --is_admin=true --rule compute_extension:flavorextraspecs:index Test the ``compute_extension:flavorextraspecs:index`` rule in Nova's policy with the plain member token .. code-block:: bash tox -e venv -- oslopolicy-checker \ --policy /opt/stack/nova/etc/nova/policy.json \ --access sample_data/auth_v3_token_member.json \ --rule compute_extension:flavorextraspecs:index oslo.policy-1.33.1/doc/source/index.rst0000666000175100017510000000057213224676023020016 0ustar zuulzuul00000000000000============= oslo.policy ============= An OpenStack library providing support for RBAC policy enforcement across all OpenStack services. .. toctree:: :maxdepth: 2 install/index admin/index configuration/index cli/index user/index reference/index contributor/index .. rubric:: Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` oslo.policy-1.33.1/doc/source/reference/0000775000175100017510000000000013224676252020111 5ustar zuulzuul00000000000000oslo.policy-1.33.1/doc/source/reference/index.rst0000666000175100017510000000016413224676023021751 0ustar zuulzuul00000000000000=========================== oslo.policy API Reference =========================== .. toctree:: api/autoindex oslo.policy-1.33.1/doc/source/contributor/0000775000175100017510000000000013224676252020525 5ustar zuulzuul00000000000000oslo.policy-1.33.1/doc/source/contributor/index.rst0000666000175100017510000000012413224676023022361 0ustar zuulzuul00000000000000============== Contributing ============== .. include:: ../../../CONTRIBUTING.rst oslo.policy-1.33.1/setup.cfg0000666000175100017510000000335613224676252015740 0ustar zuulzuul00000000000000[metadata] name = oslo.policy summary = Oslo Policy library description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/oslo.policy/latest/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = oslo_policy [pbr] autodoc_index_modules = True api_doc_dir = reference/api autodoc_exclude_modules = oslo_policy.tests.* oslo_policy._* [entry_points] oslo.config.opts = oslo.policy = oslo_policy.opts:list_opts console_scripts = oslopolicy-checker = oslo_policy.shell:main oslopolicy-sample-generator = oslo_policy.generator:generate_sample oslopolicy-policy-generator = oslo_policy.generator:generate_policy oslopolicy-list-redundant = oslo_policy.generator:list_redundant oslo.policy.rule_checks = http = oslo_policy._external:HttpCheck https = oslo_policy._external:HttpsCheck [build_sphinx] all-files = 1 warning-is-error = 1 source-dir = doc/source build-dir = doc/build [upload_sphinx] upload-dir = doc/build/html [compile_catalog] directory = oslo_policy/locale domain = oslo_policy [update_catalog] domain = oslo_policy output_dir = oslo_policy/locale input_file = oslo_policy/locale/oslo_policy.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = oslo_policy/locale/oslo_policy.pot [wheel] universal = true [egg_info] tag_build = tag_date = 0 oslo.policy-1.33.1/babel.cfg0000666000175100017510000000002113224676023015623 0ustar zuulzuul00000000000000[python: **.py] oslo.policy-1.33.1/setup.py0000666000175100017510000000200613224676023015614 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True)