From: Jay S. Bryant Date: Mon, 16 Feb 2015 23:05:01 +0000 (-0600) Subject: Sync policy module from oslo-incubator X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=1c28ead9006a6f6d48f417537d58c0cab9f984b2;p=openstack-build%2Fcinder-build.git Sync policy module from oslo-incubator The policy module has not had a sync since back in November. There have been a number of changes that should be pulled into Kilo. Current HEAD in OSLO: --------------------- commit 9bf01f9d98487cb13e3d95ad2a580fe8fc6f2479 Date: Fri Feb 13 14:18:58 2015 -0500 Switch from oslo.config to oslo_config Changes merged with this patch: --------------------- 2aacb111 Change oslo.config to oslo_config 2fbf5065 Remove oslo.log code and clean up versionutils API 262279b1 switch to oslo_serialization 07e9b32a Improving docstrings for policy API e67f5cd0 Merge "Don't log missing policy.d as a warning" 99d991ce Merge "Fixed a problem with neutron http policy check" b19af080 Don't log missing policy.d as a warning 2324c775 Add rule overwrite flag to Enforcer class 6166a960 Fixed a problem with neutron http policy check Closes-bug: 1288178 Change-Id: I6987029b9c15f3d35fa591014859f5f96c98f3a3 --- diff --git a/cinder/openstack/common/policy.py b/cinder/openstack/common/policy.py index cf45ffa1d..7349ec949 100644 --- a/cinder/openstack/common/policy.py +++ b/cinder/openstack/common/policy.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright (c) 2012 OpenStack Foundation. # All Rights Reserved. # @@ -22,22 +24,43 @@ string written in the new policy language. 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. This is the original way of -expressing policies, but there now exists a new way: the policy -language. - -In the policy language, each check is specified the same way as in the -list-of-lists representation: a simple "a:b" pair that is matched to -the correct code to perform that check. However, conjunction -operators are available, allowing for more expressiveness in crafting -policies. - -As an example, take the following rule, expressed in the list-of-lists -representation:: +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"]] -In the policy language, this becomes:: +This is the original way of expressing policies, but there now exists a +new way: the policy language. + +In the policy language, each check is specified the same way as in the +list-of-lists representation: 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 URL's¹ | 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 are available, allowing for more expressiveness +in crafting policies. So, in the policy language, the previous check in +list-of-lists becomes:: role:admin or (project_id:%(project_id)s and role:projectadmin) @@ -46,26 +69,16 @@ policy rule:: project_id:%(project_id)s and not role:dunce -It is possible to perform policy checks on the following user -attributes (obtained through the token): user_id, domain_id or -project_id:: - - domain_id: - Attributes sent along with API calls can be used by the policy engine (on the right side of the expression), by using the following syntax:: - :user.id + :%(user.id)s Contextual attributes of objects identified by their IDs are loaded from the database. They are also available to the policy engine and can be checked through the `target` keyword:: - :target.role.name - -All these attributes (related to users, API calls, and context) can be -checked against each other or against constants, be it literals (True, -) or strings. + :%(target.role.name)s Finally, two special policy checks should be mentioned; the policy check "@" will always accept an access, and the policy check "!" will @@ -78,6 +91,7 @@ as it allows particular rules to be explicitly disabled. import abc import ast import copy +import logging import os import re @@ -88,8 +102,7 @@ import six.moves.urllib.parse as urlparse import six.moves.urllib.request as urlrequest from cinder.openstack.common import fileutils -from cinder.openstack.common._i18n import _, _LE, _LW -from cinder.openstack.common import log as logging +from cinder.openstack.common._i18n import _, _LE, _LI policy_opts = [ @@ -199,16 +212,19 @@ class Enforcer(object): :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, policy_file=None, rules=None, - default_rule=None, use_conf=True): + default_rule=None, use_conf=True, overwrite=True): self.default_rule = default_rule or CONF.policy_default_rule self.rules = Rules(rules, self.default_rule) self.policy_path = None self.policy_file = policy_file or CONF.policy_file self.use_conf = use_conf + self.overwrite = overwrite def set_rules(self, rules, overwrite=True, use_conf=False): """Create a new Rules object based on the provided dict of rules. @@ -240,7 +256,7 @@ class Enforcer(object): Policy file is cached and will be reloaded if modified. - :param force_reload: Whether to overwrite current rules. + :param force_reload: Whether to reload rules from config file. """ if force_reload: @@ -250,12 +266,13 @@ class Enforcer(object): if not self.policy_path: self.policy_path = self._get_policy_path(self.policy_file) - self._load_policy_file(self.policy_path, force_reload) + self._load_policy_file(self.policy_path, force_reload, + overwrite=self.overwrite) for path in CONF.policy_dirs: try: path = self._get_policy_path(path) except cfg.ConfigFilesNotFoundError: - LOG.warn(_LW("Can not find policy directory: %s"), path) + LOG.info(_LI("Can not find policy directory: %s"), path) continue self._walk_through_policy_directory(path, self._load_policy_file, @@ -272,9 +289,9 @@ class Enforcer(object): def _load_policy_file(self, path, force_reload, overwrite=True): reloaded, data = fileutils.read_cached_file( path, force_reload=force_reload) - if reloaded or not self.rules: + if reloaded or not self.rules or not overwrite: rules = Rules.load_json(data, self.default_rule) - self.set_rules(rules, overwrite) + self.set_rules(rules, overwrite=overwrite, use_conf=True) LOG.debug("Rules successfully reloaded") def _get_policy_path(self, path): @@ -894,7 +911,17 @@ class HttpCheck(Check): """ url = ('http:' + self.match) % target - data = {'target': jsonutils.dumps(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 = {'target': jsonutils.dumps(temp_target), 'credentials': jsonutils.dumps(creds)} post_data = urlparse.urlencode(data) f = urlrequest.urlopen(url, post_data)