text: #    Copyright 2013 Rackspace
  #
  #    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 ConfigParser
  import re
  try:
      from collections import OrderedDict
  except ImportError:
      from ordereddict import OrderedDict
  
  from oslo.config import cfg
  
  import sps.api.policy
  from sps.common import exception
  from sps.openstack.common import log as logging
  from sps.openstack.common import policy
  
  # NOTE(bourke): The default dict_type is collections.OrderedDict in py27, but
  # we must set manually for compatibility with py26
  CONFIG = ConfigParser.SafeConfigParser(dict_type=OrderedDict)
  LOG = logging.getLogger(__name__)
  
  property_opts = [
      cfg.StrOpt('property_protection_file',
                 help=_('The location of the property protection file.')),
      cfg.StrOpt('property_protection_rule_format',
                 default='roles',
                 help=_('This config value indicates whether "roles" or '
                        '"policies" are used in the property protection file.')),
  ]
  
  CONF = cfg.CONF
  CONF.register_opts(property_opts)
  
  # NOTE (spredzy): Due to the particularly lengthy name of the exception
  # and the number of occurrence it is raise in this file, a variable is
  # created
  InvalidPropProtectConf = exception.InvalidPropertyProtectionConfiguration
  
  
  def is_property_protection_enabled():
      if CONF.property_protection_file:
          return True
      return False
  
  
  class PropertyRules(object):
  
      def __init__(self, policy_enforcer=None):
          self.rules = []
          self.prop_exp_mapping = {}
          self.policies = []
          self.policy_enforcer = policy_enforcer or sps.api.policy.Enforcer()
          self.prop_prot_rule_format = CONF.property_protection_rule_format
          self.prop_prot_rule_format = self.prop_prot_rule_format.lower()
          self._load_rules()
  
      def _load_rules(self):
          try:
              conf_file = CONF.find_file(CONF.property_protection_file)
              CONFIG.read(conf_file)
          except Exception as e:
              msg = (_("Couldn't find property protection file %(file)s: "
                       "%(error)s.") % {'file': CONF.property_protection_file,
                                        'error': e})
              LOG.error(msg)
              raise InvalidPropProtectConf()
  
          if self.prop_prot_rule_format not in ['policies', 'roles']:
              msg = _("Invalid value '%s' for "
                      "'property_protection_rule_format'. "
                      "The permitted values are "
                      "'roles' and 'policies'") % self.prop_prot_rule_format
              LOG.error(msg)
              raise InvalidPropProtectConf()
  
          operations = ['create', 'read', 'update', 'delete']
          properties = CONFIG.sections()
          for property_exp in properties:
              property_dict = {}
              compiled_rule = self._compile_rule(property_exp)
  
              for operation in operations:
                  permissions = CONFIG.get(property_exp, operation)
                  if permissions:
                      if self.prop_prot_rule_format == 'policies':
                          if ',' in permissions:
                              LOG.error(
                                  _("Multiple policies '%s' not allowed"
                                    "for a given operation. Policies can be "
                                    "combined in the policy file"),
                                  permissions)
                              raise InvalidPropProtectConf()
                          self.prop_exp_mapping[compiled_rule] = property_exp
                          self._add_policy_rules(property_exp, operation,
                                                 permissions)
                          permissions = [permissions]
                      else:
                          permissions = [permission.strip() for permission in
                                         permissions.split(',')]
                          if '@' in permissions and '!' in permissions:
                              msg = (_(
                                  "Malformed property protection rule in "
                                  "[%(prop)s] %(op)s=%(perm)s: '@' and '!' "
                                  "are mutually exclusive") %
                                  dict(prop=property_exp,
                                       op=operation,
                                       perm=permissions))
                              LOG.error(msg)
                              raise InvalidPropProtectConf()
                      property_dict[operation] = permissions
                  else:
                      property_dict[operation] = []
                      LOG.warn(
                          _('Property protection on operation %(operation)s'
                            ' for rule %(rule)s is not found. No role will be'
                            ' allowed to perform this operation.') %
                          {'operation': operation,
                           'rule': property_exp})
  
              self.rules.append((compiled_rule, property_dict))
  
      def _compile_rule(self, rule):
          try:
              return re.compile(rule)
          except Exception as e:
              msg = (_("Encountered a malformed property protection rule"
                       " %(rule)s: %(error)s.") % {'rule': rule,
                                                   'error': e})
              LOG.error(msg)
              raise InvalidPropProtectConf()
  
      def _add_policy_rules(self, property_exp, action, rule):
          """Add policy rules to the policy enforcer.
  
          For example, if the file listed as property_protection_file has:
          [prop_a]
          create = sps_creator
          then the corresponding policy rule would be:
          "prop_a:create": "rule:sps_creator"
          where sps_creator is defined in policy.json. For example:
          "sps:creator": "role:admin or role:sps_create_user"
          """
          rule = "rule:%s" % rule
          rule_name = "%s:%s" % (property_exp, action)
          rule_dict = {}
          rule_dict[rule_name] = policy.parse_rule(rule)
          self.policy_enforcer.add_rules(rule_dict)
  
      def _check_policy(self, property_exp, action, context):
          try:
              target = ":".join([property_exp, action])
              self.policy_enforcer.enforce(context, target, {})
          except exception.Forbidden:
              return False
          return True
  
      def check_property_rules(self, property_name, action, context):
          roles = context.roles
          if not self.rules:
              return True
  
          if action not in ['create', 'read', 'update', 'delete']:
              return False
  
          for rule_exp, rule in self.rules:
              if rule_exp.search(str(property_name)):
                  break
          else:  # no matching rules
              return False
  
          rule_roles = rule.get(action)
          if rule_roles:
              if '!' in rule_roles:
                  return False
              elif '@' in rule_roles:
                  return True
              if self.prop_prot_rule_format == 'policies':
                  prop_exp_key = self.prop_exp_mapping[rule_exp]
                  return self._check_policy(prop_exp_key, action,
                                            context)
              if set(roles).intersection(set(rule_roles)):
                  return True
          return False
  