You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ch...@apache.org on 2016/09/19 21:23:47 UTC

qpid-dispatch git commit: DISPATCH-8: Authenticate message user-id on ingress

Repository: qpid-dispatch
Updated Branches:
  refs/heads/master 39b349a36 -> 6be6e4610


DISPATCH-8: Authenticate message user-id on ingress

Add proxy check enable setting per vhost user group.
Verify proxy is allowed for incoming messages.
Add self tests to demonstrate proxy check rejecting messages or not.


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

Branch: refs/heads/master
Commit: 6be6e461040808ec9aed75b3213a2f03496a510b
Parents: 39b349a
Author: Chuck Rolke <cr...@redhat.com>
Authored: Mon Sep 19 17:18:32 2016 -0400
Committer: Chuck Rolke <cr...@redhat.com>
Committed: Mon Sep 19 17:18:32 2016 -0400

----------------------------------------------------------------------
 doc/book/policy.adoc                            |   1 +
 .../qdrouter.policyRuleset.settings.txt         |   7 +
 .../policy/policy_local.py                      |   6 +-
 src/policy.c                                    |   1 +
 src/policy.h                                    |   1 +
 src/router_node.c                               |  32 +-
 tests/CMakeLists.txt                            |   2 +
 tests/policy-4/management-access.json           | 104 ++++++
 tests/system_tests_user_id.py                   |  12 +
 tests/system_tests_user_id_proxy.py             | 315 +++++++++++++++++++
 10 files changed, 479 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6be6e461/doc/book/policy.adoc
----------------------------------------------------------------------
diff --git a/doc/book/policy.adoc b/doc/book/policy.adoc
index 9d41323..84a5b91 100644
--- a/doc/book/policy.adoc
+++ b/doc/book/policy.adoc
@@ -160,6 +160,7 @@ This object is the data value contained in entries in the policy/groups map.
 | maxReceivers         | 2^31-1  | Maximum number of receiving links that may be created on this connection.
 | allowDynamicSource   | false   | This connection is allowed to create receiving links using the Dynamic Link Source feature.
 | allowAnonymousSender | false   | This connection is allowed to create sending links using the Anonymous Sender feature.
+| allowUserIdProxy     | false   | This connection is allowed to send messages with a user_id property that differs from the connection's authenticated user id.
 | sources              | ""      | List of Source addresses allowed when creating receiving links. This list may be expressed as a CSV string or as a list of strings. An empty list denies all access.
 | targets              | ""      | List of Target addresses allowed when creating sending links. This list may be expressed as a CSV string or as a list of strings. An empty list denies all access.
 |====

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6be6e461/python/qpid_dispatch/management/qdrouter.policyRuleset.settings.txt
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch/management/qdrouter.policyRuleset.settings.txt b/python/qpid_dispatch/management/qdrouter.policyRuleset.settings.txt
index c0c4208..798e146 100644
--- a/python/qpid_dispatch/management/qdrouter.policyRuleset.settings.txt
+++ b/python/qpid_dispatch/management/qdrouter.policyRuleset.settings.txt
@@ -95,6 +95,13 @@ Until the schema is extended specify embedded maps this document describes the v
               "required": false,
               "create": true
           },
+          "allowUserIdProxy": {
+              "type": "boolean",
+              "description": "This connection is allowed to send messages with a user_id property that differs from the connection authenticated user name.",
+              "default": false,
+              "required": false,
+              "create": true
+          },
           "sources": {
               "type": "string",
               "description": "List of Source addresses allowed when creating receiving links.",

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6be6e461/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
index 87fe11a..8da5402 100644
--- a/python/qpid_dispatch_internal/policy/policy_local.py
+++ b/python/qpid_dispatch_internal/policy/policy_local.py
@@ -59,6 +59,7 @@ class PolicyKeys(object):
     KW_MAX_RECEIVERS            = "maxReceivers"
     KW_ALLOW_DYNAMIC_SRC        = "allowDynamicSource"
     KW_ALLOW_ANONYMOUS_SENDER   = "allowAnonymousSender"
+    KW_ALLOW_USERID_PROXY       = "allowUserIdProxy"
     KW_SOURCES                  = "sources"
     KW_TARGETS                  = "targets"
 
@@ -118,6 +119,7 @@ class PolicyCompiler(object):
         PolicyKeys.KW_MAX_RECEIVERS,
         PolicyKeys.KW_ALLOW_DYNAMIC_SRC,
         PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
+        PolicyKeys.KW_ALLOW_USERID_PROXY,
         PolicyKeys.KW_SOURCES,
         PolicyKeys.KW_TARGETS
         ]
@@ -219,6 +221,7 @@ class PolicyCompiler(object):
         policy_out[PolicyKeys.KW_MAX_RECEIVERS] = 2147483647
         policy_out[PolicyKeys.KW_ALLOW_DYNAMIC_SRC] = False
         policy_out[PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER] = False
+        policy_out[PolicyKeys.KW_ALLOW_USERID_PROXY] = False
         policy_out[PolicyKeys.KW_SOURCES] = ''
         policy_out[PolicyKeys.KW_TARGETS] = ''
 
@@ -247,7 +250,8 @@ class PolicyCompiler(object):
                     return False
                 policy_out[key] = val_out
             elif key in [PolicyKeys.KW_ALLOW_ANONYMOUS_SENDER,
-                         PolicyKeys.KW_ALLOW_DYNAMIC_SRC
+                         PolicyKeys.KW_ALLOW_DYNAMIC_SRC,
+                         PolicyKeys.KW_ALLOW_USERID_PROXY
                          ]:
                 if not type(val) is bool:
                     errors.append("Policy vhost '%s' user group '%s' option '%s' has illegal boolean value '%s'." %

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6be6e461/src/policy.c
----------------------------------------------------------------------
diff --git a/src/policy.c b/src/policy.c
index 76f863c..a347c12 100644
--- a/src/policy.c
+++ b/src/policy.c
@@ -337,6 +337,7 @@ bool qd_policy_open_lookup_user(
                     settings->maxReceivers         = qd_entity_opt_long((qd_entity_t*)upolicy, "maxReceivers", 0);
                     settings->allowAnonymousSender = qd_entity_opt_bool((qd_entity_t*)upolicy, "allowAnonymousSender", false);
                     settings->allowDynamicSource   = qd_entity_opt_bool((qd_entity_t*)upolicy, "allowDynamicSource", false);
+                    settings->allowUserIdProxy     = qd_entity_opt_bool((qd_entity_t*)upolicy, "allowUserIdProxy", false);
                     settings->sources              = qd_entity_get_string((qd_entity_t*)upolicy, "sources");
                     settings->targets              = qd_entity_get_string((qd_entity_t*)upolicy, "targets");
                     settings->denialCounts         = (qd_policy_denial_counts_t*)

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6be6e461/src/policy.h
----------------------------------------------------------------------
diff --git a/src/policy.h b/src/policy.h
index 6287bfb..c89ca2b 100644
--- a/src/policy.h
+++ b/src/policy.h
@@ -50,6 +50,7 @@ struct qd_policy__settings_s {
     int  maxReceivers;
     bool allowDynamicSource;
     bool allowAnonymousSender;
+    bool allowUserIdProxy;
     char *sources;
     char *targets;
     qd_policy_denial_counts_t *denialCounts;

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6be6e461/src/router_node.c
----------------------------------------------------------------------
diff --git a/src/router_node.c b/src/router_node.c
index 3dcf960..115bf2e 100644
--- a/src/router_node.c
+++ b/src/router_node.c
@@ -264,6 +264,18 @@ static void AMQP_rx_handler(void* context, qd_link_t *link, pn_delivery_t *pnd)
     bool anonymous_link = qdr_link_is_anonymous(rlink);
 
     //
+    // Determine if the user of this connection is allowed to proxy the
+    // user_id of messages. A message user_id is proxied when the
+    // property value differs from the authenticated user name of the connection.
+    // If the user is not allowed to proxy the user_id then the message user_id
+    // must be blank or it must be equal to the connection user name.
+    //
+    bool             check_user = false;
+    qd_connection_t *conn       = qd_link_connection(link);
+    if (conn->policy_settings) 
+        check_user = !conn->policy_settings->allowUserIdProxy;
+
+    //
     // Validate the content of the delivery as an AMQP message.  This is done partially, only
     // to validate that we can find the fields we need to route the message.
     //
@@ -271,10 +283,28 @@ static void AMQP_rx_handler(void* context, qd_link_t *link, pn_delivery_t *pnd)
     // 'to' field.  If the link is not anonymous, we don't need the 'to' field as we will be
     // using the address from the link target.
     //
-    qd_message_depth_t  validation_depth = anonymous_link ? QD_DEPTH_PROPERTIES : QD_DEPTH_MESSAGE_ANNOTATIONS;
+    qd_message_depth_t  validation_depth = (anonymous_link || check_user) ? QD_DEPTH_PROPERTIES : QD_DEPTH_MESSAGE_ANNOTATIONS;
     bool                valid_message    = qd_message_check(msg, validation_depth);
 
     if (valid_message) {
+        if (check_user) {
+            // This connection must not allow proxied user_id
+            qd_field_iterator_t *userid_iter  = qd_message_field_iterator(msg, QD_FIELD_USER_ID);
+            if (userid_iter) {
+                // The user_id property has been specified
+                if (qd_field_iterator_remaining(userid_iter) > 0) {
+                    // user_id property in message is not blank
+                    if (!qd_field_iterator_equal(userid_iter, (const unsigned char *)conn->user_id)) {
+                        // This message is rejected: attempted user proxy is disallowed
+                        qd_log(router->log_source, QD_LOG_DEBUG, "Message rejected due to user_id proxy violation. User:%s", conn->user_id);
+                        pn_delivery_update(pnd, PN_REJECTED);
+                        pn_delivery_settle(pnd);
+                        return;
+                    }
+                }
+            }
+        }
+
         qd_parsed_field_t   *in_ma        = qd_message_message_annotations(msg);
         qd_bitmask_t        *link_exclusions;
         bool                 strip        = qdr_link_strip_annotations_in(rlink);

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6be6e461/tests/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5ab3a0f..eb04e82 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -82,6 +82,7 @@ foreach(py_test_module
     system_tests_qdstat
     system_tests_sasl_plain
     system_tests_user_id
+    system_tests_user_id_proxy
     system_tests_deprecated
     system_tests_two_routers)
 
@@ -110,6 +111,7 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/policy-1/policy-boardwalk.json   DESTINATI
 file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/policy-1/policy-safari.json      DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/policy-1/)
 file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/policy-2/policy-photoserver-sasl.sasldb  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/policy-2)
 file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/policy-3/test-sender-receiver-limits.json DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/policy-3)
+file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/policy-4/management-access.json  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/policy-4/)
 
 # following install() functions will be called only if you do a make "install"
 install(FILES ${SYSTEM_TEST_FILES}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6be6e461/tests/policy-4/management-access.json
----------------------------------------------------------------------
diff --git a/tests/policy-4/management-access.json b/tests/policy-4/management-access.json
new file mode 100644
index 0000000..708f547
--- /dev/null
+++ b/tests/policy-4/management-access.json
@@ -0,0 +1,104 @@
+##
+## 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
+##
+
+# A policy to allow unrestricted access to management
+# from host
+#    0.0.0.0     - proton 0.12
+#    localhost   - proton 0.13
+#    unnamed host- proton 0.13
+#
+# These vhosts deny user_id proxy.
+#
+[
+  ["vhost", {
+      "id": "",
+      "maxConnections": 50,
+      "maxConnectionsPerUser": 5,
+      "maxConnectionsPerHost": 20,
+      "allowUnknownUser": true,
+      "groups": {
+        "$default" : {
+          "users":            "*",
+          "remoteHosts":      "*",
+          "maxFrameSize":     222222,
+          "maxMessageSize":   222222,
+          "maxSessionWindow": 222222,
+          "maxSessions":           2,
+          "maxSenders":           22,
+          "maxReceivers":         22,
+          "allowDynamicSource":   true,
+          "allowAnonymousSender": true,
+          "allowUserIdProxy":     false,
+          "sources": "$management, _local/$displayname",
+          "targets": "$management, _local/$displayname"
+        }
+      }
+    }
+  ],
+  ["vhost", {
+      "id": "0.0.0.0",
+      "maxConnections": 50,
+      "maxConnectionsPerUser": 5,
+      "maxConnectionsPerHost": 20,
+      "allowUnknownUser": true,
+      "groups": {
+        "$default" : {
+          "users":            "*",
+          "remoteHosts":      "*",
+          "maxFrameSize":     222222,
+          "maxMessageSize":   222222,
+          "maxSessionWindow": 222222,
+          "maxSessions":           2,
+          "maxSenders":           22,
+          "maxReceivers":         22,
+          "allowDynamicSource":   true,
+          "allowAnonymousSender": true,
+          "allowUserIdProxy":     false,
+          "sources": "$management, _local/$displayname",
+          "targets": "$management, _local/$displayname"
+        }
+      }
+    }
+  ],
+  ["vhost", {
+      "id": "localhost",
+      "maxConnections": 50,
+      "maxConnectionsPerUser": 5,
+      "maxConnectionsPerHost": 20,
+      "allowUnknownUser": true,
+      "groups": {
+        "$default" : {
+          "users":            "*",
+          "remoteHosts":      "*",
+          "maxFrameSize":     222222,
+          "maxMessageSize":   222222,
+          "maxSessionWindow": 222222,
+          "maxSessions":           2,
+          "maxSenders":           22,
+          "maxReceivers":         22,
+          "allowDynamicSource":   true,
+          "allowAnonymousSender": true,
+          "allowUserIdProxy":     false,
+          "sources": "$management, _local/$displayname",
+          "targets": "$management, _local/$displayname"
+        }
+      }
+    }
+  ]
+]

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6be6e461/tests/system_tests_user_id.py
----------------------------------------------------------------------
diff --git a/tests/system_tests_user_id.py b/tests/system_tests_user_id.py
index ddd4f67..7f40b4d 100644
--- a/tests/system_tests_user_id.py
+++ b/tests/system_tests_user_id.py
@@ -363,6 +363,18 @@ class QdSSLUseridTest(TestCase):
         M1.get(rm)
         self.assertEqual('12345', rm.body['user_name'])
 
+        tm = Message()
+        rm = Message()
+        tm.address = addr
+        tm.reply_to = reply_to
+        tm.user_id = "bad-user-id" # policy is disabled; user proxy is allowed
+        tm.body = {'profilename': 'server-ssl10', 'opcode': 'QUERY', 'userid': '12345'}
+        M1.put(tm)
+        M1.send()
+        M1.recv(1)
+        M1.get(rm)
+        self.assertEqual('12345', rm.body['user_name'])
+
         M1.stop()
 
         node.close()

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/6be6e461/tests/system_tests_user_id_proxy.py
----------------------------------------------------------------------
diff --git a/tests/system_tests_user_id_proxy.py b/tests/system_tests_user_id_proxy.py
new file mode 100644
index 0000000..b82617b
--- /dev/null
+++ b/tests/system_tests_user_id_proxy.py
@@ -0,0 +1,315 @@
+#
+# 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 os
+import unittest
+from system_test import TestCase, Qdrouterd, DIR, main_module
+from qpid_dispatch.management.client import Node
+import proton
+from proton import SSLDomain, Message, ProtonException, Delivery
+from proton.utils import BlockingConnection
+
+class QdSSLUseridTest(TestCase):
+
+    @staticmethod
+    def ssl_file(name):
+        return os.path.join(DIR, 'ssl_certs', name)
+
+    @classmethod
+    def setUpClass(cls):
+        super(QdSSLUseridTest, cls).setUpClass()
+
+        ssl_profile1_json = os.path.join(DIR, 'displayname_files', 'profile_names1.json')
+        ssl_profile2_json = os.path.join(DIR, 'displayname_files', 'profile_names2.json')
+        policy_config_path = os.path.join(DIR, 'policy-4')
+
+        config = Qdrouterd.Config([
+            ('router', {'id': 'QDR', 'workerThreads': 1}),
+
+            ('policy', {'maxConnections': 20, 'policyDir': policy_config_path, 'enableVhostPolicy': 'true'}),
+
+            # sha1
+            ('sslProfile', {'name': 'server-ssl1',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'uidFormat': '1',
+                             'password': 'server-password'}),
+
+            # sha256
+            ('sslProfile', {'name': 'server-ssl2',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'uidFormat': '2',
+                             'password': 'server-password'}),
+
+            # sha512
+            ('sslProfile', {'name': 'server-ssl3',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'uidFormat': '5',
+                             'password': 'server-password'}),
+
+            # sha256 combination
+            ('sslProfile', {'name': 'server-ssl4',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'uidFormat': '2noucs',
+                             'password': 'server-password'}),
+
+            # sha1 combination
+            ('sslProfile', {'name': 'server-ssl5',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'uidFormat': '1cs',
+                             'password': 'server-password'}),
+
+            # sha512 combination
+            ('sslProfile', {'name': 'server-ssl6',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'uidFormat': 'cs5',
+                             'password': 'server-password'}),
+
+            # no fingerprint field
+            ('sslProfile', {'name': 'server-ssl7',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'uidFormat': 'nsuco',
+                             'password': 'server-password'}),
+
+            # no fingerprint field variation
+            ('sslProfile', {'name': 'server-ssl8',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'uidFormat': 'scounl',
+                             'password': 'server-password'}),
+
+            #no uidFormat
+            ('sslProfile', {'name': 'server-ssl9',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'password': 'server-password'}),
+
+            # one component of uidFormat is invalid (x), the unrecognized component will be ignored,
+            # this will be treated like 'uidFormat': '1'
+            ('sslProfile', {'name': 'server-ssl10',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'uidFormat': '1x',
+                             'displayNameFile': ssl_profile2_json,
+                             'password': 'server-password'}),
+
+            # All components in the uidFormat are unrecognized, pn_get_transport_user will be returned
+            ('sslProfile', {'name': 'server-ssl11',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'uidFormat': 'abxd',
+                             'password': 'server-password'}),
+
+            ('sslProfile', {'name': 'server-ssl12',
+                             'certDb': cls.ssl_file('ca-certificate.pem'),
+                             'certFile': cls.ssl_file('server-certificate.pem'),
+                             'keyFile': cls.ssl_file('server-private-key.pem'),
+                             'uidFormat': '1',
+                             'displayNameFile': ssl_profile1_json,
+                             'password': 'server-password'}),
+
+            # should translate a display name
+            ('sslProfile', {'name': 'server-ssl13',
+                            'certDb': cls.ssl_file('ca-certificate.pem'),
+                            'certFile': cls.ssl_file('server-certificate.pem'),
+                            'keyFile': cls.ssl_file('server-private-key.pem'),
+                            'uidFormat': '2',
+                            'displayNameFile': ssl_profile2_json,
+                            'password': 'server-password'}),
+
+            ('sslProfile', {'name': 'server-ssl14',
+                            'certDb': cls.ssl_file('ca-certificate.pem'),
+                            'certFile': cls.ssl_file('server-certificate.pem'),
+                            'keyFile': cls.ssl_file('server-private-key.pem'),
+                            'uidFormat': '1',
+                            'displayNameFile': ssl_profile1_json,
+                            'password': 'server-password'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl1', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl2', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl3', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl4', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl5', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl6', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl7', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl8', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl9', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl10', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl11', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            # peer is not being authenticated here. the user must "anonymous" which is what pn_transport_get_user
+            # returns
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl12', 'authenticatePeer': 'no',
+                          'requireSsl': 'yes', 'saslMechanisms': 'ANONYMOUS'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl13', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl14', 'authenticatePeer': 'yes',
+                          'requireSsl': 'yes', 'saslMechanisms': 'EXTERNAL'}),
+
+            ('listener', {'port': cls.tester.get_port(), 'authenticatePeer': 'no'})
+
+        ])
+
+        cls.router = cls.tester.qdrouterd('ssl-test-router', config, wait=True)
+
+    def address(self, index):
+        return self.router.addresses[index]
+
+    def create_ssl_domain(self, ssl_options_dict, mode=SSLDomain.MODE_CLIENT):
+        """Return proton.SSLDomain from command line options or None if no SSL options specified.
+            @param opts: Parsed optoins including connection_options()
+        """
+        certificate, key, trustfile, password = ssl_options_dict.get('ssl-certificate'), \
+                                                ssl_options_dict.get('ssl-key'), \
+                                                ssl_options_dict.get('ssl-trustfile'), \
+                                                ssl_options_dict.get('ssl-password')
+
+        if not (certificate or trustfile):
+            return None
+        domain = SSLDomain(mode)
+        if trustfile:
+            domain.set_trusted_ca_db(str(trustfile))
+            domain.set_peer_authentication(SSLDomain.VERIFY_PEER, str(trustfile))
+        if certificate:
+            domain.set_credentials(str(certificate), str(key), str(password))
+
+        return domain
+
+class QdSSLUseridProxy(QdSSLUseridTest):
+
+    def test_message_user_id_proxy_bad_name_disallowed(self):
+        ssl_opts = dict()
+        ssl_opts['ssl-trustfile'] = self.ssl_file('ca-certificate.pem')
+        ssl_opts['ssl-certificate'] = self.ssl_file('client-certificate.pem')
+        ssl_opts['ssl-key'] = self.ssl_file('client-private-key.pem')
+        ssl_opts['ssl-password'] = 'client-password'
+
+        # create the SSL domain object
+        domain = self.create_ssl_domain(ssl_opts)
+
+        # Send a message with bad user_id. This message should be rejected.
+        # Connection has user_id 'user13'.
+        addr = self.address(13).replace("amqp", "amqps")
+        blocking_connection = BlockingConnection(addr, ssl_domain=domain)
+        blocking_sender = blocking_connection.create_sender("$management")
+
+        request = proton.Message()
+        request.user_id = u"bad-user-id"
+
+        result = Delivery.ACCEPTED
+        try:
+            delivery = blocking_sender.send(request, timeout=10)
+            result = delivery.remote_state
+        except proton.utils.SendException as e:
+            result = e.state
+
+        self.assertTrue (result == Delivery.REJECTED,
+                        "Router accepted a message with user_id that did not match connection user_id")
+
+    def test_message_user_id_proxy_blank_name_allowed(self):
+        # Send a message with a blank user_id that should be allowed
+        M1 = self.messenger()
+        M1.route("amqp:/*", self.address(14) + "/$1")
+
+        subscription = M1.subscribe("amqp:/#")
+
+        reply_to = subscription.address
+        addr = 'amqp:/_local/$displayname'
+
+        tm = Message()
+        rm = Message()
+        tm.address = addr
+        tm.reply_to = reply_to
+        tm.body = {'profilename': 'server-ssl10', 'opcode': 'QUERY',
+                   'userid': '94745961c5646ee0129536b3acef1eea0d8d2f26f8c353455233027bcd47'}
+        M1.put(tm)
+
+        M1.send()
+        M1.recv(1)
+        M1.get(rm)
+        self.assertEqual('elaine', rm.body['user_name'])
+
+    def test_message_user_id_proxy_correct_name_allowed(self):
+        # Send a message with a good user_id that should be allowed
+        M2 = self.messenger()
+        M2.route("amqp:/*", self.address(14) + "/$1")
+
+        subscription = M2.subscribe("amqp:/#")
+
+        reply_to = subscription.address
+        addr = 'amqp:/_local/$displayname'
+
+        tm = Message()
+        rm = Message()
+        tm.address = addr
+        tm.reply_to = reply_to
+        tm.user_id = "anonymous"
+        tm.body = {'profilename': 'server-ssl10', 'opcode': 'QUERY',
+                   'userid': '94745961c5646ee0129536b3acef1eea0d8d2f26f8c353455233027bcd47'}
+        M2.put(tm)
+
+        M2.send()
+        M2.recv(1)
+        M2.get(rm)
+        self.assertEqual('elaine', rm.body['user_name'])
+
+
+if __name__ == '__main__':
+    unittest.main(main_module())


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org