Move policy code to qpid_dispatch_internal/policy/*.
Remove policy test-as-main code.


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/dfb8cfda
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/dfb8cfda
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/dfb8cfda

Branch: refs/heads/crolke-DISPATCH-188-1
Commit: dfb8cfda47daebb95f2487dd85aa9f1b2685281a
Parents: 2fb2ef4
Author: Chuck Rolke <[email protected]>
Authored: Wed Jan 27 17:27:05 2016 -0500
Committer: Chuck Rolke <[email protected]>
Committed: Wed Jan 27 17:27:05 2016 -0500

----------------------------------------------------------------------
 .../qpid_dispatch_internal/management/agent.py  |   2 +-
 .../management/policy_local.py                  | 713 -------------------
 .../management/policy_util.py                   | 335 ---------
 .../qpid_dispatch_internal/policy/__init__.py   |  20 +
 .../policy/policy_local.py                      | 593 +++++++++++++++
 .../policy/policy_util.py                       | 335 +++++++++
 tests/system_tests_policy.py                    |   4 +-
 7 files changed, 951 insertions(+), 1051 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/management/agent.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/management/agent.py 
b/python/qpid_dispatch_internal/management/agent.py
index 0d53bc1..013eddb 100644
--- a/python/qpid_dispatch_internal/management/agent.py
+++ b/python/qpid_dispatch_internal/management/agent.py
@@ -81,7 +81,7 @@ from .schema import ValidationError, SchemaEntity, EntityType
 from .qdrouter import QdSchema
 from ..router.message import Message
 from ..router.address import Address
-from policy_local import PolicyLocal
+from ..policy.policy_local import PolicyLocal
 
 
 def dictstr(d):

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/management/policy_local.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/management/policy_local.py 
b/python/qpid_dispatch_internal/management/policy_local.py
deleted file mode 100644
index 6c455a7..0000000
--- a/python/qpid_dispatch_internal/management/policy_local.py
+++ /dev/null
@@ -1,713 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you 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 sys, os
-import json
-import optparse
-from policy_util import PolicyError, HostStruct, HostAddr, 
PolicyAppConnectionMgr
-import pdb #; pdb.set_trace()
-
-
-
-"""
-Entity implementing the business logic of user connection/access policy.
-"""
-
-#
-#
-class PolicyKeys(object):
-    # Common key words
-    KW_IGNORED_NAME             = "name"
-    KW_IGNORED_IDENTITY         = "identity"
-    KW_IGNORED_TYPE             = "type"
-    KW_APPLICATION_NAME         = "applicationName"
-
-    # Policy ruleset key words
-    KW_MAXCONN                  = "maxConnections"
-    KW_MAXCONNPERHOST           = "maxConnPerHost"
-    KW_MAXCONNPERUSER           = "maxConnPerUser"
-    KW_USER_GROUPS              = "userGroups"
-    KW_CONNECTION_GROUPS        = "connectionGroups"
-    KW_CONNECTION_POLICY        = "connectionIngressPolicies"
-    KW_CONNECTION_ALLOW_DEFAULT = "connectionAllowDefault"
-
-    # Policy settings key words
-    KW_USER_GROUP_NAME          = "userGroupName"
-    KW_MAX_FRAME_SIZE           = "maxFrameSize"
-    KW_MAX_MESSAGE_SIZE         = "maxMessageSize"
-    KW_MAX_SESSION_WINDOW       = "maxSessionWindow"
-    KW_MAX_SESSIONS             = "maxSessions"
-    KW_MAX_SENDERS              = "maxSenders"
-    KW_MAX_RECEIVERS            = "maxReceivers"
-    KW_ALLOW_DYNAMIC_SRC        = "allowDynamicSrc"
-    KW_ALLOW_ANONYMOUS_SENDER   = "allowAnonymousSender"
-    KW_SOURCES                  = "sources"
-    KW_TARGETS                  = "targets"
-
-    # What settings does a user get when allowed to connect but
-    # not restricted by a user group?
-    KW_DEFAULT_SETTINGS         = "default"
-
-    # Config file separator character for two IP addresses in a range
-    KC_CONFIG_IP_SEP            = "-"
-
-    # Config file separator character for names in a list
-    KC_CONFIG_LIST_SEP          = ","
-#
-#
-class PolicyCompiler(object):
-    """
-    Validate incoming configuration for legal schema.
-    - Warn about section options that go unused.
-    - Disallow negative max connection numbers.
-    - Check that connectionOrigins resolve to IP hosts
-    """
-
-    allowed_ruleset_options = [
-        PolicyKeys.KW_IGNORED_NAME,
-        PolicyKeys.KW_IGNORED_IDENTITY,
-        PolicyKeys.KW_IGNORED_TYPE,
-        PolicyKeys.KW_APPLICATION_NAME,
-        PolicyKeys.KW_MAXCONN,
-        PolicyKeys.KW_MAXCONNPERHOST,
-        PolicyKeys.KW_MAXCONNPERUSER,
-        PolicyKeys.KW_USER_GROUPS,
-        PolicyKeys.KW_CONNECTION_GROUPS,
-        PolicyKeys.KW_CONNECTION_POLICY,
-        PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT
-        ]
-
-    allowed_settings_options = [
-        PolicyKeys.KW_IGNORED_NAME,
-        PolicyKeys.KW_IGNORED_IDENTITY,
-        PolicyKeys.KW_IGNORED_TYPE,
-        PolicyKeys.KW_APPLICATION_NAME,
-        PolicyKeys.KW_USER_GROUP_NAME,
-        PolicyKeys.KW_MAX_FRAME_SIZE,
-        PolicyKeys.KW_MAX_MESSAGE_SIZE,
-        PolicyKeys.KW_MAX_SESSION_WINDOW,
-        PolicyKeys.KW_MAX_SESSIONS,
-        PolicyKeys.KW_MAX_SENDERS,
-        PolicyKeys.KW_MAX_RECEIVERS,
-        PolicyKeys.KW_ALLOW_DYNAMIC_SRC,
-        PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
-        PolicyKeys.KW_SOURCES,
-        PolicyKeys.KW_TARGETS
-        ]
-
-    def __init__(self):
-        """
-        Create a validator
-        """
-        pass
-
-
-    def validateNumber(self, val, v_min, v_max, errors):
-        """
-        Range check a numeric int policy value
-        @param[in] val policy value to check
-        @param[in] v_min minumum value
-        @param[in] v_max maximum value. zero disables check
-        @param[out] errors failure message
-        @return v_min <= val <= v_max
-        """
-        error = ""
-        try:
-            v_int = int(val)
-        except Exception, e:
-            errors.append("Value '%s' does not resolve to an integer." % val)
-            return False
-        if v_int < v_min:
-            errors.append("Value '%s' is below minimum '%s'." % (val, v_min))
-            return False
-        if v_max > 0 and v_int > v_max:
-            errors.append("Value '%s' is above maximum '%s'." % (val, v_max))
-            return False
-        return True
-
-
-    def compile_connection_groups(self, name, submap, warnings, errors):
-        """
-        Handle an connectionGroups submap.
-        Each origin value is verified. On a successful run the submap
-        is replaced parsed lists of HostAddr objects.
-        @param[in] name application name
-        @param[in,out] submap user input origin list as text strings
-                       modified in place to be list of HostAddr objects
-        @param[out] warnings nonfatal irregularities observed
-        @param[out] errors descriptions of failure
-        @return - origins is usable. If True then warnings[] may contain useful
-                  information about fields that are ignored. If False then
-                  warnings[] may contain info and errors[0] will hold the
-                  description of why the origin was rejected.
-        """
-        key = PolicyKeys.KW_CONNECTION_GROUPS
-        newmap = {}
-        for coname in submap:
-            try:
-                ostr = str(submap[coname])
-                olist = [x.strip(' ') for x in 
ostr.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
-                newmap[coname] = []
-                for co in olist:
-                    coha = HostAddr(co, PolicyKeys.KC_CONFIG_IP_SEP)
-                    newmap[coname].append(coha)
-            except Exception, e:
-                errors.append("Application '%s' option '%s' connectionOption 
'%s' failed to translate: '%s'." %
-                                (name, key, coname, e))
-                return False
-        submap.update(newmap)
-        return True
-
-
-    def compile_app_settings(self, appname, usergroup, policy_in, policy_out, 
warnings, errors):
-        """
-        Compile a schema from processed json format to local internal format.
-        @param[in] name application name
-        @param[in] policy_in user config settings
-        @param[out] policy_out validated Internal format
-        @param[out] warnings nonfatal irregularities observed
-        @param[out] errors descriptions of failure
-        @return - settings are usable. If True then warnings[] may contain 
useful
-                  information about fields that are ignored. If False then
-                  warnings[] may contain info and errors[0] will hold the
-                  description of why the policy was rejected.
-        """
-        cerror = []
-        for key, val in policy_in.iteritems():
-            if key not in self.allowed_settings_options:
-                warnings.append("Application '%s' user group '%s' option '%s' 
is ignored." %
-                                (appname, usergroup, key))
-            if key in [PolicyKeys.KW_MAX_FRAME_SIZE,
-                       PolicyKeys.KW_MAX_MESSAGE_SIZE,
-                       PolicyKeys.KW_MAX_RECEIVERS,
-                       PolicyKeys.KW_MAX_SENDERS,
-                       PolicyKeys.KW_MAX_SESSION_WINDOW,
-                       PolicyKeys.KW_MAX_SESSIONS
-                       ]:
-                if not self.validateNumber(val, 0, 0, cerror):
-                    errors.append("Application '%s' user group '%s' option 
'%s' has error '%s'." %
-                                  (appname, usergroup, key, cerror[0]))
-                    return False
-                policy_out[key] = val
-            elif key in [PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
-                         PolicyKeys.KW_ALLOW_DYNAMIC_SRC
-                         ]:
-                if not type(val) is bool:
-                    errors.append("Application '%s' user group '%s' option 
'%s' has illegal boolean value '%s'." %
-                                  (appname, usergroup, key, val))
-                    return False
-                policy_out[key] = val
-            elif key in [PolicyKeys.KW_SOURCES,
-                         PolicyKeys.KW_TARGETS
-                         ]:
-                val = [x.strip(' ') for x in 
val.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
-                # deduplicate address lists
-                val = list(set(val))
-                policy_out[key] = val
-        return True
-
-
-    def compile_access_ruleset(self, name, policy_in, policy_out, warnings, 
errors):
-        """
-        Compile a schema from processed json format to local internal format.
-        @param[in] name application name
-        @param[in] policy_in raw policy to be validated
-        @param[out] policy_out validated Internal format
-        @param[out] warnings nonfatal irregularities observed
-        @param[out] errors descriptions of failure
-        @return - policy is usable. If True then warnings[] may contain useful
-                  information about fields that are ignored. If False then
-                  warnings[] may contain info and errors[0] will hold the
-                  description of why the policy was rejected.
-        """
-        cerror = []
-        # validate the options
-        for key, val in policy_in.iteritems():
-            if key not in self.allowed_ruleset_options:
-                warnings.append("Application '%s' option '%s' is ignored." %
-                                (name, key))
-            if key in [PolicyKeys.KW_MAXCONN,
-                       PolicyKeys.KW_MAXCONNPERHOST,
-                       PolicyKeys.KW_MAXCONNPERUSER
-                       ]:
-                if not self.validateNumber(val, 0, 65535, cerror):
-                    msg = ("Application '%s' option '%s' has error '%s'." % 
-                           (name, key, cerror[0]))
-                    errors.append(msg)
-                    return False
-                policy_out[key] = val
-            elif key in [PolicyKeys.KW_USER_GROUPS,
-                         PolicyKeys.KW_CONNECTION_GROUPS,
-                         PolicyKeys.KW_CONNECTION_POLICY
-                         ]:
-                try:
-                    if not type(val) is dict:
-                        errors.append("Application '%s' option '%s' must be of 
type 'dict' but is '%s'" %
-                                      (name, key, type(val)))
-                        return False
-                    if key == PolicyKeys.KW_CONNECTION_GROUPS:
-                        if not self.compile_connection_groups(name, val, 
warnings, errors):
-                            return False
-                    else:
-                        # deduplicate connectionIngressPolicy and userGroups 
lists
-                        for k,v in val.iteritems():
-                            v = [x.strip(' ') for x in 
v.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
-                            v = list(set(v))
-                            val[k] = v
-                    policy_out[key] = val
-                except Exception, e:
-                    errors.append("Application '%s' option '%s' error 
processing map: %s" %
-                                  (name, key, e))
-                    return False
-        return True
-
-class PolicyLocal(object):
-    """
-    The policy database.
-    """
-
-    def __init__(self):
-        """
-        Create instance
-        @params folder: relative path from __file__ to conf file folder
-        """
-        self.policydb = {}
-        self.settingsdb = {}
-        self.lookup_cache = {}
-        self.stats = {}
-        self.policy_compiler = PolicyCompiler()
-        self.name_lookup_cache = {}
-        self.blob_lookup_cache = {}
-
-    #
-    # Service interfaces
-    #
-    def create_ruleset(self, attributes):
-        """
-        Create named policy ruleset
-        @param[in] attributes: from config
-        """
-        warnings = []
-        diag = []
-        candidate = {}
-        name = attributes[PolicyKeys.KW_APPLICATION_NAME]
-        result = self.policy_compiler.compile_access_ruleset(name, attributes, 
candidate, warnings, diag)
-        if not result:
-            raise PolicyError( "Policy '%s' is invalid: %s" % (name, diag[0]) )
-        if len(warnings) > 0:
-            print ("LogMe: Application '%s' has warnings: %s" %
-                   (name, warnings))
-        self.policydb[name] = candidate
-        # TODO: Create stats
-
-    def create_settings(self, attributes):
-        """
-        Create named policy ruleset
-        @param[in] attributes: from config
-        """
-        warnings = []
-        diag = []
-        candidate = {}
-        app_name = attributes[PolicyKeys.KW_APPLICATION_NAME]
-        usergroup = attributes[PolicyKeys.KW_USER_GROUP_NAME]
-        result = self.policy_compiler.compile_app_settings(app_name, 
usergroup, attributes, candidate, warnings, diag)
-        if not result:
-            raise PolicyError( "Policy '%s' is invalid: %s" % (app_name, 
diag[0]) )
-        if len(warnings) > 0:
-            print ("LogMe: Application '%s' has warnings: %s" %
-                   (app_name, warnings))
-        if not app_name in self.settingsdb:
-            self.settingsdb[app_name] = {}
-        self.settingsdb[app_name][usergroup] = candidate  # create named 
settings
-
-    def policy_read(self, name):
-        """
-        Read policy for named application
-        @param[in] name application name
-        @return policy data in raw user format
-        """
-        return self.policydb[name]
-
-    def policy_update(self, name, policy):
-        """
-        Update named policy
-        @param[in] name application name
-        @param[in] policy data in raw user input
-        """
-        if not name in self.policydb:
-            raise PolicyError("Policy '%s' does not exist" % name)
-        self.policy_create(name, policy)
-
-    def policy_delete(self, name):
-        """
-        Delete named policy
-        @param[in] name application name
-        """
-        if not name in self.policydb:
-            raise PolicyError("Policy '%s' does not exist" % name)
-        del self.policydb[name]
-
-    #
-    # db enumerator
-    #
-    def policy_db_get_names(self):
-        """
-        Return a list of application names in this policy
-        """
-        return self.policydb.keys()
-
-
-    #
-    # Runtime query interface
-    #
-    def policy_aggregate_limits(self, upolicy, policy, settingname):
-        """
-        Force a max count value into user policy
-        param[in,out] upolicy user policy receiving aggregations
-        param[in] policy Internal policy holding settings to be aggregated
-        param[in] settingname setting of interest
-        """
-        if settingname in policy:
-            upolicy[settingname] = policy[settingname]
-
-    def policy_aggregate_policy_int(self, upolicy, appsettings, groups, 
settingname):
-        """
-        Pull int out of policy.policies[group] and install into upolicy.
-        Integers are set to max(new, existing)
-        param[in,out] upolicy user policy receiving aggregations
-        param[in] policy Internal policy holding settings to be aggregated
-        param[in] settingname setting of interest
-        """
-        for group in groups:
-            if group in appsettings:
-                rpol = appsettings[group]
-                if settingname in rpol:
-                    sp = rpol[settingname]
-                    if settingname in upolicy:
-                        up = upolicy[settingname]
-                        if sp > up:
-                            # policy bumps up user setting
-                            upolicy[settingname] = sp
-                        else:
-                            # user policy is already better
-                            pass
-                    else:
-                        # user policy doesn't have setting so force it
-                        upolicy[settingname] = sp
-                else:
-                    # no setting of this name in the group's policy
-                    pass
-            else:
-                # no policy for this group
-                pass
-
-    def policy_aggregate_policy_bool(self, upolicy, appsettings, groups, 
settingname):
-        """
-        Pull bool out of policy and install into upolicy if true
-        param[in,out] upolicy user policy receiving aggregations
-        param[in] policy Internal policy holding settings to be aggregated
-        param[in] settingname setting of interest
-        """
-        for group in groups:
-            if group in appsettings:
-                rpol = appsettings[group]
-                if settingname in rpol:
-                    if rpol[settingname]:
-                        upolicy[settingname] = True
-                else:
-                    # no setting of this name in the group's policy
-                    pass
-            else:
-                # no policy for this group
-                pass
-
-    def policy_aggregate_policy_list(self, upolicy, appsettings, groups, 
settingname):
-        """
-        Pull list out of policy and append into upolicy
-        param[in,out] upolicy user policy receiving aggregations
-        param[in] policy Internal policy holding settings to be aggregated
-        param[in] settingname setting of interest
-        """
-        for group in groups:
-            if group in appsettings:
-                rpol = appsettings[group]
-                if settingname in rpol:
-                    sp = rpol[settingname]
-                    if settingname in upolicy:
-                        upolicy[settingname].extend( sp )
-                        upolicy[settingname] = list(set(upolicy[settingname]))
-                    else:
-                        # user policy doesn't have setting so force it
-                        upolicy[settingname] = sp
-                else:
-                    # no setting of this name in the group's policy
-                    pass
-            else:
-                # no policy for this group
-                pass
-
-    #
-    #
-    def lookup_user(self, user, host, app, conn_name, policyname):
-        """
-        Lookup function called from C.
-        Determine if a user on host accessing app through AMQP Open is allowed
-        according to the policy access rules.
-        If allowed then return the policy settings name
-        @param[in] user connection authId
-        @param[in] host connection remote host numeric IP address as string
-        @param[in] app application user is accessing
-        @param[out] policyname name of the policy settings blob for this user
-        @return if allowed by policy
-        # Note: the upolicy[0] output is list of group names joined with '|'.
-        TODO: handle the AccessStats
-        """
-        try:
-            lookup_id = user + "|" + host + "|" + app
-            if lookup_id in self.name_lookup_cache:
-                policyname.append( self.name_lookup_cache[lookup_id] )
-                return True
-
-            if not app in self.policydb:
-                # TODO: ("LogMe: no policy defined for application %s" % app)
-                policyname.append("")
-                return False
-
-            settings = self.policydb[app]
-            # User allowed to connect from host?
-            allowed = False
-            restricted = False
-            uhs = HostStruct(host)
-            ugroups = []
-            if PolicyKeys.KW_USER_GROUPS in settings:
-                for r in settings[PolicyKeys.KW_USER_GROUPS]:
-                    if user in settings[PolicyKeys.KW_USER_GROUPS][r]:
-                        restricted = True
-                        ugroups.append(r)
-            uorigins = []
-            if PolicyKeys.KW_CONNECTION_POLICY in settings:
-                for ur in ugroups:
-                    if ur in settings[PolicyKeys.KW_CONNECTION_POLICY]:
-                        
uorigins.extend(settings[PolicyKeys.KW_CONNECTION_POLICY][ur])
-            if PolicyKeys.KW_CONNECTION_GROUPS in settings:
-                for co in settings[PolicyKeys.KW_CONNECTION_GROUPS]:
-                    if co in uorigins:
-                        for cohost in 
settings[PolicyKeys.KW_CONNECTION_GROUPS][co]:
-                            if cohost.match_bin(uhs):
-                                allowed = True
-                                break
-                    if allowed:
-                        break
-            if not allowed and not restricted:
-                if PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT in settings:
-                    allowed = settings[PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT]
-            if not allowed:
-                return False
-            if not restricted:
-                ugroups.append(PolicyKeys.KW_DEFAULT_SETTINGS)
-            #
-            ugroups.sort()
-            result = "|".join(ugroups)
-            self.name_lookup_cache[lookup_id] = result
-            policyname.append(result)
-            return True
-
-        except Exception, e:
-            #print str(e)
-            #pdb.set_trace()
-            return False
-
-    def lookup_settings(self, appname, name, upolicy):
-        """
-        Given a settings name, return the aggregated policy blob.
-        @param[in] appname: application user is accessing
-        @param[in] name: user group name or concatenation of names of the 
policy settings blob
-        @param[out] upolicy: dict holding policy values - the settings blob
-        @return if allowed by policy
-        # Note: the upolicy output is a non-nested dict with settings of 
interest
-        # TODO: figure out decent defaults for upolicy settings that are 
undefined
-        """
-        try:
-            cachekey = appname + "|" + name
-            if cachekey in self.blob_lookup_cache:
-                upolicy.update( self.blob_lookup_cache[cachekey] )
-                return True
-            settings = self.settingsdb[appname]
-            ugroups = name.split("|")
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_FRAME_SIZE)
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_MESSAGE_SIZE)
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_SESSION_WINDOW)
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_SESSIONS)
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_SENDERS)
-            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_RECEIVERS)
-            self.policy_aggregate_policy_bool(upolicy, settings, ugroups, 
PolicyKeys.KW_ALLOW_DYNAMIC_SRC)
-            self.policy_aggregate_policy_bool(upolicy, settings, ugroups, 
PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER)
-            self.policy_aggregate_policy_list(upolicy, settings, ugroups, 
PolicyKeys.KW_SOURCES)
-            self.policy_aggregate_policy_list(upolicy, settings, ugroups, 
PolicyKeys.KW_TARGETS)
-            c_upolicy = {}
-            c_upolicy.update(upolicy)
-            self.blob_lookup_cache[cachekey] = c_upolicy
-            return True
-        except Exception, e:
-            #print str(e)
-            #pdb.set_trace()
-            return False
-
-    def test_load_config(self):
-        ruleset_str = '["policyAccessRuleset", {"applicationName": 
"photoserver","maxConnections": 50,"maxConnPerUser": 5,"maxConnPerHost": 
20,"userGroups": {"anonymous":       "anonymous","users":           "u1, 
u2","paidsubscribers": "p1, p2","test":            "zeke, ynot","admin":        
   "alice, bob, ellen","superuser":       "ellen"},"connectionGroups": 
{"Ten18":     "10.18.0.0-10.18.255.255","EllensWS":  "72.135.2.9","TheLabs":   
"10.48.0.0-10.48.255.255, 192.168.100.0-192.168.100.255","localhost": 
"127.0.0.1, ::1","TheWorld":  "*"},"connectionIngressPolicies": {"anonymous":   
    "TheWorld","users":           "TheWorld","paidsubscribers": 
"TheWorld","test":            "TheLabs","admin":           "Ten18, TheLabs, 
localhost","superuser":       "EllensWS, localhost"},"connectionAllowDefault": 
true}]'
-        ruleset = json.loads(ruleset_str)
-
-        self.create_ruleset(ruleset[1])
-
-        settings_strs = []
-        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"anonymous",      "maxFrameSize": 
111111,"maxMessageSize":   111111,"maxSessionWindow": 111111,"maxSessions":     
      1,"maxSenders":           11,"maxReceivers":         
11,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": 
"public",                           "targets": ""}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"users",          "maxFrameSize": 
222222,"maxMessageSize":   222222,"maxSessionWindow": 222222,"maxSessions":     
      2,"maxSenders":           22,"maxReceivers":         
22,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": 
"public, private",                  "targets": "public"}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"paidsubscribers","maxFrameSize": 
333333,"maxMessageSize":   333333,"maxSessionWindow": 333333,"maxSessions":     
      3,"maxSenders":           33,"maxReceivers":         
33,"allowDynamicSrc":      true, "allowAnonymousSender": false,"sources": 
"public, private",                  "targets": "public, private"}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"test",           "maxFrameSize": 
444444,"maxMessageSize":   444444,"maxSessionWindow": 444444,"maxSessions":     
      4,"maxSenders":           44,"maxReceivers":         
44,"allowDynamicSrc":      true, "allowAnonymousSender": true, "sources": 
"private",                          "targets": "private"}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"admin",          "maxFrameSize": 
555555,"maxMessageSize":   555555,"maxSessionWindow": 555555,"maxSessions":     
      5,"maxSenders":           55,"maxReceivers":         
55,"allowDynamicSrc":      true, "allowAnonymousSender": true, "sources": 
"public, private, management",      "targets": "public, private, management"}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"superuser",      "maxFrameSize": 
666666,"maxMessageSize":   666666,"maxSessionWindow": 666666,"maxSessions":     
      6,"maxSenders":           66,"maxReceivers":         
66,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": 
"public, private, management, root","targets": "public, private, management, 
root"}]')
-        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"default",        "maxFrameSize": 
222222,"maxMessageSize":   222222,"maxSessionWindow": 222222,"maxSessions":     
      2,"maxSenders":           22,"maxReceivers":         
22,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": 
"public, private",                  "targets": "public"}]')
-
-        for sstr in settings_strs:
-            settings = json.loads(sstr)
-            self.create_settings(settings[1])
-
-
-#
-# HACK ALERT: Temporary
-# Functions related to main
-#
-class ExitStatus(Exception):
-    """Raised if a command wants a non-0 exit status from the script"""
-    def __init__(self, status): self.status = status
-
-def main_except(argv):
-
-    def read_files(policy, path):
-        """
-        Read all .json conf files in path and create the policies they contain.
-        @param policy: The policy_local to receive the configuration.
-        @param path: The path relative to policy_local.py
-        """
-        apath = os.path.abspath(os.path.dirname(__file__))
-        apath = os.path.join(apath, path)
-        for i in os.listdir(apath):
-            if i.endswith(".json"):
-                read_file(policy, os.path.join(apath, i))
-
-    def read_file(policy, fn):
-        """
-        Read a qdrouterd config file and extract the policy sections.
-        @param policy: The policy_local to receive the configuration.
-        @param fn: absolute path to file
-        """
-        try:
-            with open(fn) as json_file:
-                cp = json.load(json_file)
-            for i in range(0, len(cp)):
-                if cp[i][0] == "policyAccessRuleset":
-                    policy.create_ruleset(cp[i][1])
-                elif cp[i][0] == "policyAppSettings":
-                    policy.create_settings(cp[i][1])
-                else:
-                    # some config option we don't care about
-                    pass
-        except Exception, e:
-            # complain but otherwise ignore errors
-            print("Error processing policy configuration file '%s' : %s" % 
(fn, e))
-
-    usage = "usage: %prog [options]\nExercise policy_local functions."
-    parser = optparse.OptionParser(usage=usage)
-    parser.set_defaults(folder="")
-    parser.add_option("-f", "--folder", action="store", type="string", 
dest="folder",
-                      help="Built-in configuration settings are loaded by 
default or by using an empty folder string."
-                      " Use '-f /some/path' to load a config from all .json 
files in that folder."
-                      " Paths may be absolute or relative to  
policy_local.py.")
-    parser.add_option("-e", "--exercise", action="store_true", dest="exercise",
-                      help="Run canned tests. Expect canned tests to work with 
configs in ../../../tests/policy-1.")
-
-    (options, args) = parser.parse_args()
-
-    policy = PolicyLocal()
-
-    if options.folder == "":
-        # Empty folder name uses built-in configuration
-        policy.test_load_config()
-    else:
-        # Load all .json files in given folder
-        read_files(policy, options.folder)
-
-    print("Policy rulesets available: %s" % policy.policy_db_get_names())
-
-    if not options.exercise:
-        return
-
-    # Exercise a few functions
-    # Empty policy
-    policy2 = PolicyLocal()
-
-    print("Print some Policy details:")
-    for pname in policy.policy_db_get_names():
-        print("policy : %s" % pname)
-        p = ("%s" % policy.policy_read(pname))
-        print(p.replace('\\n', '\n'))
-
-    # Lookups
-    policynames = []
-    # pdb.set_trace()
-    res1 = policy.lookup_user('zeke', '192.168.100.5', 'photoserver', 
'192.168.100.5:33334', policynames)
-    print "\nLookup zeke from 192.168.100.5. Expecting True, result is %s" % 
res1
-    print "\nResulting policy expecting 'test', is: %s" % policynames[0]
-    # Hit the cache
-    policynames = []
-    res2  = policy.lookup_user('zeke', '192.168.100.5', 'photoserver', 
'192.168.100.5:33335', policynames)
-
-    policynames3 = []
-    res3 = policy.lookup_user('ellen', '72.135.2.9', 'photoserver', 
'72.135.2.9:33333', policynames3)
-    print "\nLookup ellen from 72.135.2.9. Expect true. Result is %s" % res3
-    print "Resulting policy is: %s" % policynames[0]
-
-    policynames = []
-    res4 = policy2.lookup_user('ellen', '72.135.2.9', 'photoserver', 
'72.135.2.9:33334', policynames)
-    print "\nLookup policy2 ellen from 72.135.2.9. Expect false. Result is %s" 
% res4
-
-    upolicy6 = {}
-    res6 = policy.lookup_settings('photoserver', policynames3[0], upolicy6)
-    res6a = upolicy6['maxFrameSize'] == 666666
-    print "\nNamed settings lookup result = %s, and value check = %s" % (res6, 
res6a)
-
-    print ("Tests success: %s" % (res1 and res2 and res3 and not res4 and res6 
and res6a))
-
-
-def main(argv):
-    try:
-        main_except(argv)
-        return 0
-    except ExitStatus, e:
-        return e.status
-    except Exception, e:
-        print "%s: %s"%(type(e).__name__, e)
-        return 1
-
-if __name__ == "__main__":
-    sys.exit(main(sys.argv))

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/management/policy_util.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/management/policy_util.py 
b/python/qpid_dispatch_internal/management/policy_util.py
deleted file mode 100644
index edcd01f..0000000
--- a/python/qpid_dispatch_internal/management/policy_util.py
+++ /dev/null
@@ -1,335 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you 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 sys, os
-import socket
-import binascii
-
-
-#
-#
-class PolicyError(Exception):
-    def __init__(self, value):
-        self.value = value
-    def __str__(self):
-        return repr(self.value)
-
-#
-#
-class HostStruct():
-    """
-    HostStruct represents a single, binary socket address from getaddrinfo
-        - name     : name given to constructor; numeric IP or host name
-        - saddr    : net name resolved by getaddrinfo; numeric IP
-        - family   : saddr.family; int
-        - binary   : saddr packed binary address; binary string
-    """
-    families = [socket.AF_INET]
-    famnames = ["IPv4"]
-    if socket.has_ipv6:
-        families.append(socket.AF_INET6)
-        famnames.append("IPv6")
-
-    def __init__(self, hostname):
-        """
-        Given a host name text string, return the socket info for it.
-        @param[in] hostname host IP address to parse
-        """
-        try:
-            res = socket.getaddrinfo(hostname, 0)
-            if len(res) == 0:
-                raise PolicyError("HostStruct: '%s' did not resolve to an IP 
address" % hostname)
-            foundFirst = False
-            saddr = ""
-            sfamily = socket.AF_UNSPEC
-            for i0 in range(0, len(res)):
-                family, dum0, dum1, dum2, sockaddr = res[i0]
-                if not foundFirst:
-                    if family in self.families:
-                        saddr = sockaddr[0]
-                        sfamily = family
-                        foundFirst = True
-                else:
-                    if family in self.families:
-                        if not saddr == sockaddr[0] or not sfamily == family:
-                            raise PolicyError("HostStruct: '%s' resolves to 
multiple IP addresses" %
-                                              hostname)
-
-            if not foundFirst:
-                raise PolicyError("HostStruct: '%s' did not resolve to one of 
the supported address family" %
-                        hostname)
-            self.name = hostname
-            self.saddr = saddr
-            self.family = sfamily
-            self.binary = socket.inet_pton(family, saddr)
-            return
-        except Exception, e:
-            raise PolicyError("HostStruct: '%s' failed to resolve: '%s'" %
-                              (hostname, e))
-
-    def __str__(self):
-        return self.name
-
-    def __repr__(self):
-        return self.__str__()
-
-    def dump(self):
-        return ("(%s, %s, %s, %s)" %
-                (self.name,
-                 self.saddr,
-                 "AF_INET" if self.family == socket.AF_INET else "AF_INET6",
-                 binascii.hexlify(self.binary)))
-
-#
-#
-class HostAddr():
-    """
-    Provide HostIP address ranges and comparison functions.
-    A HostIP may be:
-    - single address:      10.10.1.1
-    - a pair of addresses: 10.10.0.0,10.10.255.255
-    - a wildcard:          *
-    Only IPv4 and IPv6 are supported.
-    - No unix sockets.
-    HostIP names must resolve to a single IP address.
-    Address pairs define a range.
-    - The second address must be numerically larger than the first address.
-    - The addresses must be of the same address 'family', IPv4 or IPv6.
-    The wildcard '*' matches all address IPv4 or IPv6.
-    IPv6 support is conditional based on underlying OS network options.
-    Raises a PolicyError on validation error in constructor.
-    """
-
-    def has_ipv6(self):
-        return socket.has_ipv6
-
-    def __init__(self, hostspec, separator=","):
-        """
-        Parse host spec into binary structures to use for comparisons.
-        Validate the hostspec to enforce usage rules.
-        """
-        self.hoststructs = []
-
-        if hostspec == "*":
-            self.wildcard = True
-        else:
-            self.wildcard = False
-
-            hosts = [x.strip() for x in hostspec.split(separator)]
-
-            # hosts must contain one or two host specs
-            if len(hosts) not in [1, 2]:
-                raise PolicyError("hostspec must contain 1 or 2 host names")
-            self.hoststructs.append(HostStruct(hosts[0]))
-            if len(hosts) > 1:
-                self.hoststructs.append(HostStruct(hosts[1]))
-                if not self.hoststructs[0].family == 
self.hoststructs[1].family:
-                    raise PolicyError("mixed IPv4 and IPv6 host specs in range 
not allowed")
-                c0 = self.memcmp(self.hoststructs[0].binary, 
self.hoststructs[1].binary)
-                if c0 > 0:
-                    raise PolicyError("host specs in range must have lower 
numeric address first")
-
-    def __str__(self):
-        if self.wildcard:
-            return "*"
-        res = self.hoststructs[0].name
-        if len(self.hoststructs) > 1:
-            res += "," + self.hoststructs[1].name
-        return res
-
-    def __repr__(self):
-        return self.__str__()
-
-    def dump(self):
-        if self.wildcard:
-            return "(*)"
-        res = "(" + self.hoststructs[0].dump()
-        if len(self.hoststructs) > 1:
-            res += "," + self.hoststructs[1].dump()
-        res += ")"
-        return res
-
-    def memcmp(self, a, b):
-        res = 0
-        for i in range(0,len(a)):
-            if a[i] > b[i]:
-                res = 1
-                break;
-            elif a[i] < b[i]:
-                res = -1
-                break
-        return res
-
-    def match_bin(self, candidate):
-        """
-        Does the candidate hoststruct match the IP or range of IP addresses 
represented by this?
-        @param[in] candidate the IP address to be tested
-        @return candidate matches this or not
-        """
-        if self.wildcard:
-            return True
-        try:
-            if not candidate.family == self.hoststructs[0].family:
-                # sorry, wrong AF_INET family
-                return False
-            c0 = self.memcmp(candidate.binary, self.hoststructs[0].binary)
-            if len(self.hoststructs) == 1:
-                return c0 == 0
-            c1 = self.memcmp(candidate.binary, self.hoststructs[1].binary)
-            return c0 >= 0 and c1 <= 0
-        except PolicyError:
-            return False
-        except Exception, e:
-            assert isinstance(candidate, HostStruct), \
-                ("Wrong type. Expected HostStruct but received %s" % 
candidate.__class__.__name__)
-            return False
-
-    def match_str(self, candidate):
-        """
-        Does the candidate string match the IP or range represented by this?
-        @param[in] candidate the IP address to be tested
-        @return candidate matches this or not
-        """
-        try:
-            hoststruct = HostStruct(candidate)
-        except PolicyError:
-            return False
-        return self.match_bin(hoststruct)
-
-#
-#
-class PolicyAppConnectionMgr():
-    """
-    Track policy user/host connection limits and statistics for one app.
-    # limits - set at creation and by update()
-    max_total            : 20
-    max_per_user         : 5
-    max_per_host         : 10
-    # statistics - maintained for the lifetime of corresponding application
-    connections_approved : N
-    connections_denied   : N
-    # live state - maintained for the lifetime of corresponding application
-    connections_active   : 5
-    per_host_state : { 'host1' : [conn1, conn2, conn3],
-                       'host2' : [conn4, conn5] }
-    per_user_state : { 'user1' : [conn1, conn2, conn3],
-                       'user2' : [conn4, conn5] }
-    """
-    def __init__(self, maxconn, maxconnperuser, maxconnperhost):
-        """
-        The object is constructed with the policy limits and zeroed counts.
-        @param[in] maxconn maximum total concurrent connections
-        @param[in] maxconnperuser maximum total conncurrent connections for 
each user
-        @param[in] maxconnperuser maximum total conncurrent connections for 
each host
-        """
-        if maxconn < 0 or maxconnperuser < 0 or maxconnperhost < 0:
-            raise PolicyError("PolicyAppConnectionMgr settings must be >= 0")
-        self.max_total    = maxconn
-        self.max_per_user = maxconnperuser
-        self.max_per_host = maxconnperhost
-        self.connections_approved = 0
-        self.connections_denied   = 0
-        self.connections_active   = 0
-        self.per_host_state = {}
-        self.per_user_state = {}
-
-    def __str__(self):
-        res = ("Connection Limits: total: %s, per user: %s, per host: %s\n" %
-            (self.max_total, self.max_per_user, self.max_per_host))
-        res += ("Connections Statistics: total approved: %s, total denied: %s" 
%
-                (self.connections_approved, self.connections_denied))
-        res += ("Connection State: total current: %s" % 
self.connections_active)
-        res += ("User state: %s\n" % self.per_user_state)
-        res += ("Host state: %s"   % self.per_host_state)
-        return res
-
-    def __repr__(self):
-        return self.__str__()
-
-    def update(self, maxconn, maxconnperuser, maxconnperhost):
-        """
-        Reset connection limits
-        @param[in] maxconn maximum total concurrent connections
-        @param[in] maxconnperuser maximum total conncurrent connections for 
each user
-        @param[in] maxconnperuser maximum total conncurrent connections for 
each host
-        """
-        if maxconn < 0 or maxconnperuser < 0 or maxconnperhost < 0:
-            raise PolicyError("PolicyAppConnectionMgr settings must be >= 0")
-        self.max_total    = maxconn
-        self.max_per_user = maxconnperuser
-        self.max_per_host = maxconnperhost
-
-    def can_connect(self, conn_id, user, host, diags):
-        """
-        Register a connection attempt.
-        If all the connection limit rules pass then add the
-        user/host to the connection tables.
-        @param[in] conn_id unique ID for connection, usually IP:port
-        @param[in] user authenticated user ID
-        @param[in] host IP address of host
-        @param[out] diags on failure holds 1, 2, or 3 error strings
-        @return connection is allowed and tracked in state tables
-        """
-        n_user = 0
-        if user in self.per_user_state:
-            n_user = len(self.per_user_state[user])
-        n_host = 0
-        if host in self.per_host_state:
-            n_host = len(self.per_host_state[host])
-
-        allowbytotal = self.max_total == 0 or self.connections_active < 
self.max_total
-        allowbyuser  = self.max_per_user == 0 or n_user < self.max_per_user
-        allowbyhost  = self.max_per_host == 0 or n_host < self.max_per_host
-
-        if allowbytotal and allowbyuser and allowbyhost:
-            if not user in self.per_user_state:
-                self.per_user_state[user] = []
-            self.per_user_state[user].append(conn_id)
-            if not host in self.per_host_state:
-                self.per_host_state[host] = []
-            self.per_host_state[host].append(conn_id)
-            self.connections_active += 1
-            self.connections_approved += 1
-            return True
-        else:
-            if not allowbytotal:
-                diags.append("LogMe: INFO user '%s' from host '%s' denied 
connection by total connection limit" %
-                             (user, host))
-            if not allowbyuser:
-                diags.append("LogMe: INFO user '%s' from host '%s' denied 
connection by per user limit" %
-                             (user, host))
-            if not allowbyhost:
-                diags.append("LogMe: INFO user '%s' from host '%s' denied 
connection by per host limit" %
-                             (user, host))
-            self.connections_denied += 1
-            return False
-
-    def disconnect(self, conn_id, user, host):
-        """
-        Unregister a connection
-        """
-        assert(self.connections_active > 0)
-        assert(user in self.per_user_state)
-        assert(conn_id in self.per_user_state[user])
-        assert(host in self.max_per_host)
-        assert(conn_id in self.max_per_host[host])
-        self.connections_active -= 1
-        self.per_user_state[user].remove(conn_id)
-        self.per_host_state[host].remove(conn_id)
-

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/policy/__init__.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/policy/__init__.py 
b/python/qpid_dispatch_internal/policy/__init__.py
new file mode 100644
index 0000000..6417447
--- /dev/null
+++ b/python/qpid_dispatch_internal/policy/__init__.py
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+"""Qpid Dispatch internal policy package."""
+

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfb8cfda/python/qpid_dispatch_internal/policy/policy_local.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/policy/policy_local.py 
b/python/qpid_dispatch_internal/policy/policy_local.py
new file mode 100644
index 0000000..7cc17de
--- /dev/null
+++ b/python/qpid_dispatch_internal/policy/policy_local.py
@@ -0,0 +1,593 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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 sys, os
+import json
+import optparse
+from policy_util import PolicyError, HostStruct, HostAddr, 
PolicyAppConnectionMgr
+import pdb #; pdb.set_trace()
+
+
+
+"""
+Entity implementing the business logic of user connection/access policy.
+"""
+
+#
+#
+class PolicyKeys(object):
+    # Common key words
+    KW_IGNORED_NAME             = "name"
+    KW_IGNORED_IDENTITY         = "identity"
+    KW_IGNORED_TYPE             = "type"
+    KW_APPLICATION_NAME         = "applicationName"
+
+    # Policy ruleset key words
+    KW_MAXCONN                  = "maxConnections"
+    KW_MAXCONNPERHOST           = "maxConnPerHost"
+    KW_MAXCONNPERUSER           = "maxConnPerUser"
+    KW_USER_GROUPS              = "userGroups"
+    KW_CONNECTION_GROUPS        = "connectionGroups"
+    KW_CONNECTION_POLICY        = "connectionIngressPolicies"
+    KW_CONNECTION_ALLOW_DEFAULT = "connectionAllowDefault"
+
+    # Policy settings key words
+    KW_USER_GROUP_NAME          = "userGroupName"
+    KW_MAX_FRAME_SIZE           = "maxFrameSize"
+    KW_MAX_MESSAGE_SIZE         = "maxMessageSize"
+    KW_MAX_SESSION_WINDOW       = "maxSessionWindow"
+    KW_MAX_SESSIONS             = "maxSessions"
+    KW_MAX_SENDERS              = "maxSenders"
+    KW_MAX_RECEIVERS            = "maxReceivers"
+    KW_ALLOW_DYNAMIC_SRC        = "allowDynamicSrc"
+    KW_ALLOW_ANONYMOUS_SENDER   = "allowAnonymousSender"
+    KW_SOURCES                  = "sources"
+    KW_TARGETS                  = "targets"
+
+    # What settings does a user get when allowed to connect but
+    # not restricted by a user group?
+    KW_DEFAULT_SETTINGS         = "default"
+
+    # Config file separator character for two IP addresses in a range
+    KC_CONFIG_IP_SEP            = "-"
+
+    # Config file separator character for names in a list
+    KC_CONFIG_LIST_SEP          = ","
+#
+#
+class PolicyCompiler(object):
+    """
+    Validate incoming configuration for legal schema.
+    - Warn about section options that go unused.
+    - Disallow negative max connection numbers.
+    - Check that connectionOrigins resolve to IP hosts
+    """
+
+    allowed_ruleset_options = [
+        PolicyKeys.KW_IGNORED_NAME,
+        PolicyKeys.KW_IGNORED_IDENTITY,
+        PolicyKeys.KW_IGNORED_TYPE,
+        PolicyKeys.KW_APPLICATION_NAME,
+        PolicyKeys.KW_MAXCONN,
+        PolicyKeys.KW_MAXCONNPERHOST,
+        PolicyKeys.KW_MAXCONNPERUSER,
+        PolicyKeys.KW_USER_GROUPS,
+        PolicyKeys.KW_CONNECTION_GROUPS,
+        PolicyKeys.KW_CONNECTION_POLICY,
+        PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT
+        ]
+
+    allowed_settings_options = [
+        PolicyKeys.KW_IGNORED_NAME,
+        PolicyKeys.KW_IGNORED_IDENTITY,
+        PolicyKeys.KW_IGNORED_TYPE,
+        PolicyKeys.KW_APPLICATION_NAME,
+        PolicyKeys.KW_USER_GROUP_NAME,
+        PolicyKeys.KW_MAX_FRAME_SIZE,
+        PolicyKeys.KW_MAX_MESSAGE_SIZE,
+        PolicyKeys.KW_MAX_SESSION_WINDOW,
+        PolicyKeys.KW_MAX_SESSIONS,
+        PolicyKeys.KW_MAX_SENDERS,
+        PolicyKeys.KW_MAX_RECEIVERS,
+        PolicyKeys.KW_ALLOW_DYNAMIC_SRC,
+        PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
+        PolicyKeys.KW_SOURCES,
+        PolicyKeys.KW_TARGETS
+        ]
+
+    def __init__(self):
+        """
+        Create a validator
+        """
+        pass
+
+
+    def validateNumber(self, val, v_min, v_max, errors):
+        """
+        Range check a numeric int policy value
+        @param[in] val policy value to check
+        @param[in] v_min minumum value
+        @param[in] v_max maximum value. zero disables check
+        @param[out] errors failure message
+        @return v_min <= val <= v_max
+        """
+        error = ""
+        try:
+            v_int = int(val)
+        except Exception, e:
+            errors.append("Value '%s' does not resolve to an integer." % val)
+            return False
+        if v_int < v_min:
+            errors.append("Value '%s' is below minimum '%s'." % (val, v_min))
+            return False
+        if v_max > 0 and v_int > v_max:
+            errors.append("Value '%s' is above maximum '%s'." % (val, v_max))
+            return False
+        return True
+
+
+    def compile_connection_groups(self, name, submap, warnings, errors):
+        """
+        Handle an connectionGroups submap.
+        Each origin value is verified. On a successful run the submap
+        is replaced parsed lists of HostAddr objects.
+        @param[in] name application name
+        @param[in,out] submap user input origin list as text strings
+                       modified in place to be list of HostAddr objects
+        @param[out] warnings nonfatal irregularities observed
+        @param[out] errors descriptions of failure
+        @return - origins is usable. If True then warnings[] may contain useful
+                  information about fields that are ignored. If False then
+                  warnings[] may contain info and errors[0] will hold the
+                  description of why the origin was rejected.
+        """
+        key = PolicyKeys.KW_CONNECTION_GROUPS
+        newmap = {}
+        for coname in submap:
+            try:
+                ostr = str(submap[coname])
+                olist = [x.strip(' ') for x in 
ostr.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
+                newmap[coname] = []
+                for co in olist:
+                    coha = HostAddr(co, PolicyKeys.KC_CONFIG_IP_SEP)
+                    newmap[coname].append(coha)
+            except Exception, e:
+                errors.append("Application '%s' option '%s' connectionOption 
'%s' failed to translate: '%s'." %
+                                (name, key, coname, e))
+                return False
+        submap.update(newmap)
+        return True
+
+
+    def compile_app_settings(self, appname, usergroup, policy_in, policy_out, 
warnings, errors):
+        """
+        Compile a schema from processed json format to local internal format.
+        @param[in] name application name
+        @param[in] policy_in user config settings
+        @param[out] policy_out validated Internal format
+        @param[out] warnings nonfatal irregularities observed
+        @param[out] errors descriptions of failure
+        @return - settings are usable. If True then warnings[] may contain 
useful
+                  information about fields that are ignored. If False then
+                  warnings[] may contain info and errors[0] will hold the
+                  description of why the policy was rejected.
+        """
+        cerror = []
+        for key, val in policy_in.iteritems():
+            if key not in self.allowed_settings_options:
+                warnings.append("Application '%s' user group '%s' option '%s' 
is ignored." %
+                                (appname, usergroup, key))
+            if key in [PolicyKeys.KW_MAX_FRAME_SIZE,
+                       PolicyKeys.KW_MAX_MESSAGE_SIZE,
+                       PolicyKeys.KW_MAX_RECEIVERS,
+                       PolicyKeys.KW_MAX_SENDERS,
+                       PolicyKeys.KW_MAX_SESSION_WINDOW,
+                       PolicyKeys.KW_MAX_SESSIONS
+                       ]:
+                if not self.validateNumber(val, 0, 0, cerror):
+                    errors.append("Application '%s' user group '%s' option 
'%s' has error '%s'." %
+                                  (appname, usergroup, key, cerror[0]))
+                    return False
+                policy_out[key] = val
+            elif key in [PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
+                         PolicyKeys.KW_ALLOW_DYNAMIC_SRC
+                         ]:
+                if not type(val) is bool:
+                    errors.append("Application '%s' user group '%s' option 
'%s' has illegal boolean value '%s'." %
+                                  (appname, usergroup, key, val))
+                    return False
+                policy_out[key] = val
+            elif key in [PolicyKeys.KW_SOURCES,
+                         PolicyKeys.KW_TARGETS
+                         ]:
+                val = [x.strip(' ') for x in 
val.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
+                # deduplicate address lists
+                val = list(set(val))
+                policy_out[key] = val
+        return True
+
+
+    def compile_access_ruleset(self, name, policy_in, policy_out, warnings, 
errors):
+        """
+        Compile a schema from processed json format to local internal format.
+        @param[in] name application name
+        @param[in] policy_in raw policy to be validated
+        @param[out] policy_out validated Internal format
+        @param[out] warnings nonfatal irregularities observed
+        @param[out] errors descriptions of failure
+        @return - policy is usable. If True then warnings[] may contain useful
+                  information about fields that are ignored. If False then
+                  warnings[] may contain info and errors[0] will hold the
+                  description of why the policy was rejected.
+        """
+        cerror = []
+        # validate the options
+        for key, val in policy_in.iteritems():
+            if key not in self.allowed_ruleset_options:
+                warnings.append("Application '%s' option '%s' is ignored." %
+                                (name, key))
+            if key in [PolicyKeys.KW_MAXCONN,
+                       PolicyKeys.KW_MAXCONNPERHOST,
+                       PolicyKeys.KW_MAXCONNPERUSER
+                       ]:
+                if not self.validateNumber(val, 0, 65535, cerror):
+                    msg = ("Application '%s' option '%s' has error '%s'." % 
+                           (name, key, cerror[0]))
+                    errors.append(msg)
+                    return False
+                policy_out[key] = val
+            elif key in [PolicyKeys.KW_USER_GROUPS,
+                         PolicyKeys.KW_CONNECTION_GROUPS,
+                         PolicyKeys.KW_CONNECTION_POLICY
+                         ]:
+                try:
+                    if not type(val) is dict:
+                        errors.append("Application '%s' option '%s' must be of 
type 'dict' but is '%s'" %
+                                      (name, key, type(val)))
+                        return False
+                    if key == PolicyKeys.KW_CONNECTION_GROUPS:
+                        if not self.compile_connection_groups(name, val, 
warnings, errors):
+                            return False
+                    else:
+                        # deduplicate connectionIngressPolicy and userGroups 
lists
+                        for k,v in val.iteritems():
+                            v = [x.strip(' ') for x in 
v.split(PolicyKeys.KC_CONFIG_LIST_SEP)]
+                            v = list(set(v))
+                            val[k] = v
+                    policy_out[key] = val
+                except Exception, e:
+                    errors.append("Application '%s' option '%s' error 
processing map: %s" %
+                                  (name, key, e))
+                    return False
+        return True
+
+class PolicyLocal(object):
+    """
+    The policy database.
+    """
+
+    def __init__(self):
+        """
+        Create instance
+        @params folder: relative path from __file__ to conf file folder
+        """
+        self.policydb = {}
+        self.settingsdb = {}
+        self.lookup_cache = {}
+        self.stats = {}
+        self.policy_compiler = PolicyCompiler()
+        self.name_lookup_cache = {}
+        self.blob_lookup_cache = {}
+
+    #
+    # Service interfaces
+    #
+    def create_ruleset(self, attributes):
+        """
+        Create named policy ruleset
+        @param[in] attributes: from config
+        """
+        warnings = []
+        diag = []
+        candidate = {}
+        name = attributes[PolicyKeys.KW_APPLICATION_NAME]
+        result = self.policy_compiler.compile_access_ruleset(name, attributes, 
candidate, warnings, diag)
+        if not result:
+            raise PolicyError( "Policy '%s' is invalid: %s" % (name, diag[0]) )
+        if len(warnings) > 0:
+            print ("LogMe: Application '%s' has warnings: %s" %
+                   (name, warnings))
+        self.policydb[name] = candidate
+        # TODO: Create stats
+
+    def create_settings(self, attributes):
+        """
+        Create named policy ruleset
+        @param[in] attributes: from config
+        """
+        warnings = []
+        diag = []
+        candidate = {}
+        app_name = attributes[PolicyKeys.KW_APPLICATION_NAME]
+        usergroup = attributes[PolicyKeys.KW_USER_GROUP_NAME]
+        result = self.policy_compiler.compile_app_settings(app_name, 
usergroup, attributes, candidate, warnings, diag)
+        if not result:
+            raise PolicyError( "Policy '%s' is invalid: %s" % (app_name, 
diag[0]) )
+        if len(warnings) > 0:
+            print ("LogMe: Application '%s' has warnings: %s" %
+                   (app_name, warnings))
+        if not app_name in self.settingsdb:
+            self.settingsdb[app_name] = {}
+        self.settingsdb[app_name][usergroup] = candidate  # create named 
settings
+
+    def policy_read(self, name):
+        """
+        Read policy for named application
+        @param[in] name application name
+        @return policy data in raw user format
+        """
+        return self.policydb[name]
+
+    def policy_update(self, name, policy):
+        """
+        Update named policy
+        @param[in] name application name
+        @param[in] policy data in raw user input
+        """
+        if not name in self.policydb:
+            raise PolicyError("Policy '%s' does not exist" % name)
+        self.policy_create(name, policy)
+
+    def policy_delete(self, name):
+        """
+        Delete named policy
+        @param[in] name application name
+        """
+        if not name in self.policydb:
+            raise PolicyError("Policy '%s' does not exist" % name)
+        del self.policydb[name]
+
+    #
+    # db enumerator
+    #
+    def policy_db_get_names(self):
+        """
+        Return a list of application names in this policy
+        """
+        return self.policydb.keys()
+
+
+    #
+    # Runtime query interface
+    #
+    def policy_aggregate_limits(self, upolicy, policy, settingname):
+        """
+        Force a max count value into user policy
+        param[in,out] upolicy user policy receiving aggregations
+        param[in] policy Internal policy holding settings to be aggregated
+        param[in] settingname setting of interest
+        """
+        if settingname in policy:
+            upolicy[settingname] = policy[settingname]
+
+    def policy_aggregate_policy_int(self, upolicy, appsettings, groups, 
settingname):
+        """
+        Pull int out of policy.policies[group] and install into upolicy.
+        Integers are set to max(new, existing)
+        param[in,out] upolicy user policy receiving aggregations
+        param[in] policy Internal policy holding settings to be aggregated
+        param[in] settingname setting of interest
+        """
+        for group in groups:
+            if group in appsettings:
+                rpol = appsettings[group]
+                if settingname in rpol:
+                    sp = rpol[settingname]
+                    if settingname in upolicy:
+                        up = upolicy[settingname]
+                        if sp > up:
+                            # policy bumps up user setting
+                            upolicy[settingname] = sp
+                        else:
+                            # user policy is already better
+                            pass
+                    else:
+                        # user policy doesn't have setting so force it
+                        upolicy[settingname] = sp
+                else:
+                    # no setting of this name in the group's policy
+                    pass
+            else:
+                # no policy for this group
+                pass
+
+    def policy_aggregate_policy_bool(self, upolicy, appsettings, groups, 
settingname):
+        """
+        Pull bool out of policy and install into upolicy if true
+        param[in,out] upolicy user policy receiving aggregations
+        param[in] policy Internal policy holding settings to be aggregated
+        param[in] settingname setting of interest
+        """
+        for group in groups:
+            if group in appsettings:
+                rpol = appsettings[group]
+                if settingname in rpol:
+                    if rpol[settingname]:
+                        upolicy[settingname] = True
+                else:
+                    # no setting of this name in the group's policy
+                    pass
+            else:
+                # no policy for this group
+                pass
+
+    def policy_aggregate_policy_list(self, upolicy, appsettings, groups, 
settingname):
+        """
+        Pull list out of policy and append into upolicy
+        param[in,out] upolicy user policy receiving aggregations
+        param[in] policy Internal policy holding settings to be aggregated
+        param[in] settingname setting of interest
+        """
+        for group in groups:
+            if group in appsettings:
+                rpol = appsettings[group]
+                if settingname in rpol:
+                    sp = rpol[settingname]
+                    if settingname in upolicy:
+                        upolicy[settingname].extend( sp )
+                        upolicy[settingname] = list(set(upolicy[settingname]))
+                    else:
+                        # user policy doesn't have setting so force it
+                        upolicy[settingname] = sp
+                else:
+                    # no setting of this name in the group's policy
+                    pass
+            else:
+                # no policy for this group
+                pass
+
+    #
+    #
+    def lookup_user(self, user, host, app, conn_name, policyname):
+        """
+        Lookup function called from C.
+        Determine if a user on host accessing app through AMQP Open is allowed
+        according to the policy access rules.
+        If allowed then return the policy settings name
+        @param[in] user connection authId
+        @param[in] host connection remote host numeric IP address as string
+        @param[in] app application user is accessing
+        @param[out] policyname name of the policy settings blob for this user
+        @return if allowed by policy
+        # Note: the upolicy[0] output is list of group names joined with '|'.
+        TODO: handle the AccessStats
+        """
+        try:
+            lookup_id = user + "|" + host + "|" + app
+            if lookup_id in self.name_lookup_cache:
+                policyname.append( self.name_lookup_cache[lookup_id] )
+                return True
+
+            if not app in self.policydb:
+                # TODO: ("LogMe: no policy defined for application %s" % app)
+                policyname.append("")
+                return False
+
+            settings = self.policydb[app]
+            # User allowed to connect from host?
+            allowed = False
+            restricted = False
+            uhs = HostStruct(host)
+            ugroups = []
+            if PolicyKeys.KW_USER_GROUPS in settings:
+                for r in settings[PolicyKeys.KW_USER_GROUPS]:
+                    if user in settings[PolicyKeys.KW_USER_GROUPS][r]:
+                        restricted = True
+                        ugroups.append(r)
+            uorigins = []
+            if PolicyKeys.KW_CONNECTION_POLICY in settings:
+                for ur in ugroups:
+                    if ur in settings[PolicyKeys.KW_CONNECTION_POLICY]:
+                        
uorigins.extend(settings[PolicyKeys.KW_CONNECTION_POLICY][ur])
+            if PolicyKeys.KW_CONNECTION_GROUPS in settings:
+                for co in settings[PolicyKeys.KW_CONNECTION_GROUPS]:
+                    if co in uorigins:
+                        for cohost in 
settings[PolicyKeys.KW_CONNECTION_GROUPS][co]:
+                            if cohost.match_bin(uhs):
+                                allowed = True
+                                break
+                    if allowed:
+                        break
+            if not allowed and not restricted:
+                if PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT in settings:
+                    allowed = settings[PolicyKeys.KW_CONNECTION_ALLOW_DEFAULT]
+            if not allowed:
+                return False
+            if not restricted:
+                ugroups.append(PolicyKeys.KW_DEFAULT_SETTINGS)
+            #
+            ugroups.sort()
+            result = "|".join(ugroups)
+            self.name_lookup_cache[lookup_id] = result
+            policyname.append(result)
+            return True
+
+        except Exception, e:
+            #print str(e)
+            #pdb.set_trace()
+            return False
+
+    def lookup_settings(self, appname, name, upolicy):
+        """
+        Given a settings name, return the aggregated policy blob.
+        @param[in] appname: application user is accessing
+        @param[in] name: user group name or concatenation of names of the 
policy settings blob
+        @param[out] upolicy: dict holding policy values - the settings blob
+        @return if allowed by policy
+        # Note: the upolicy output is a non-nested dict with settings of 
interest
+        # TODO: figure out decent defaults for upolicy settings that are 
undefined
+        """
+        try:
+            cachekey = appname + "|" + name
+            if cachekey in self.blob_lookup_cache:
+                upolicy.update( self.blob_lookup_cache[cachekey] )
+                return True
+            settings = self.settingsdb[appname]
+            ugroups = name.split("|")
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_FRAME_SIZE)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_MESSAGE_SIZE)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_SESSION_WINDOW)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_SESSIONS)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_SENDERS)
+            self.policy_aggregate_policy_int (upolicy, settings, ugroups, 
PolicyKeys.KW_MAX_RECEIVERS)
+            self.policy_aggregate_policy_bool(upolicy, settings, ugroups, 
PolicyKeys.KW_ALLOW_DYNAMIC_SRC)
+            self.policy_aggregate_policy_bool(upolicy, settings, ugroups, 
PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER)
+            self.policy_aggregate_policy_list(upolicy, settings, ugroups, 
PolicyKeys.KW_SOURCES)
+            self.policy_aggregate_policy_list(upolicy, settings, ugroups, 
PolicyKeys.KW_TARGETS)
+            c_upolicy = {}
+            c_upolicy.update(upolicy)
+            self.blob_lookup_cache[cachekey] = c_upolicy
+            return True
+        except Exception, e:
+            #print str(e)
+            #pdb.set_trace()
+            return False
+
+    def test_load_config(self):
+        ruleset_str = '["policyAccessRuleset", {"applicationName": 
"photoserver","maxConnections": 50,"maxConnPerUser": 5,"maxConnPerHost": 
20,"userGroups": {"anonymous":       "anonymous","users":           "u1, 
u2","paidsubscribers": "p1, p2","test":            "zeke, ynot","admin":        
   "alice, bob, ellen","superuser":       "ellen"},"connectionGroups": 
{"Ten18":     "10.18.0.0-10.18.255.255","EllensWS":  "72.135.2.9","TheLabs":   
"10.48.0.0-10.48.255.255, 192.168.100.0-192.168.100.255","localhost": 
"127.0.0.1, ::1","TheWorld":  "*"},"connectionIngressPolicies": {"anonymous":   
    "TheWorld","users":           "TheWorld","paidsubscribers": 
"TheWorld","test":            "TheLabs","admin":           "Ten18, TheLabs, 
localhost","superuser":       "EllensWS, localhost"},"connectionAllowDefault": 
true}]'
+        ruleset = json.loads(ruleset_str)
+
+        self.create_ruleset(ruleset[1])
+
+        settings_strs = []
+        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"anonymous",      "maxFrameSize": 
111111,"maxMessageSize":   111111,"maxSessionWindow": 111111,"maxSessions":     
      1,"maxSenders":           11,"maxReceivers":         
11,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": 
"public",                           "targets": ""}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"users",          "maxFrameSize": 
222222,"maxMessageSize":   222222,"maxSessionWindow": 222222,"maxSessions":     
      2,"maxSenders":           22,"maxReceivers":         
22,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": 
"public, private",                  "targets": "public"}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"paidsubscribers","maxFrameSize": 
333333,"maxMessageSize":   333333,"maxSessionWindow": 333333,"maxSessions":     
      3,"maxSenders":           33,"maxReceivers":         
33,"allowDynamicSrc":      true, "allowAnonymousSender": false,"sources": 
"public, private",                  "targets": "public, private"}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"test",           "maxFrameSize": 
444444,"maxMessageSize":   444444,"maxSessionWindow": 444444,"maxSessions":     
      4,"maxSenders":           44,"maxReceivers":         
44,"allowDynamicSrc":      true, "allowAnonymousSender": true, "sources": 
"private",                          "targets": "private"}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"admin",          "maxFrameSize": 
555555,"maxMessageSize":   555555,"maxSessionWindow": 555555,"maxSessions":     
      5,"maxSenders":           55,"maxReceivers":         
55,"allowDynamicSrc":      true, "allowAnonymousSender": true, "sources": 
"public, private, management",      "targets": "public, private, management"}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"superuser",      "maxFrameSize": 
666666,"maxMessageSize":   666666,"maxSessionWindow": 666666,"maxSessions":     
      6,"maxSenders":           66,"maxReceivers":         
66,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": 
"public, private, management, root","targets": "public, private, management, 
root"}]')
+        settings_strs.append('["policyAppSettings", {"applicationName": 
"photoserver","userGroupName":"default",        "maxFrameSize": 
222222,"maxMessageSize":   222222,"maxSessionWindow": 222222,"maxSessions":     
      2,"maxSenders":           22,"maxReceivers":         
22,"allowDynamicSrc":      false,"allowAnonymousSender": false,"sources": 
"public, private",                  "targets": "public"}]')
+
+        for sstr in settings_strs:
+            settings = json.loads(sstr)
+            self.create_settings(settings[1])


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to