qpid-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From g...@apache.org
Subject qpid-dispatch git commit: DISPATCH-775: allow delegation of sasl authentication to a remote authentication service
Date Mon, 14 Aug 2017 16:36:28 GMT
Repository: qpid-dispatch
Updated Branches:
  refs/heads/master d1838e6b6 -> c98cb9049


DISPATCH-775: allow delegation of sasl authentication to a remote authentication service


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

Branch: refs/heads/master
Commit: c98cb9049bfe1eac7f5b02cc8ab2d81fdc18e129
Parents: d1838e6
Author: Gordon Sim <gsim@redhat.com>
Authored: Wed May 24 21:57:47 2017 +0100
Committer: Gordon Sim <gsim@redhat.com>
Committed: Mon Aug 14 17:35:40 2017 +0100

----------------------------------------------------------------------
 include/qpid/dispatch/connection_manager.h      |   1 +
 include/qpid/dispatch/server.h                  |  19 +
 python/qpid_dispatch/management/qdrouter.json   |  33 ++
 python/qpid_dispatch_internal/dispatch.py       |   1 +
 .../qpid_dispatch_internal/management/agent.py  |  12 +
 .../qpid_dispatch_internal/management/config.py |   2 +-
 src/CMakeLists.txt                              |   1 +
 src/connection_manager.c                        | 116 ++++-
 src/dispatch.c                                  |   1 +
 src/dispatch_private.h                          |   1 +
 src/remote_sasl.c                               | 428 +++++++++++++++++++
 src/remote_sasl.h                               |  31 ++
 src/server.c                                    |  14 +
 tests/CMakeLists.txt                            |   1 +
 tests/system_tests_auth_service_plugin.py       | 137 ++++++
 tests/system_tests_qdmanage.py                  |   2 +-
 16 files changed, 797 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/include/qpid/dispatch/connection_manager.h
----------------------------------------------------------------------
diff --git a/include/qpid/dispatch/connection_manager.h b/include/qpid/dispatch/connection_manager.h
index 4392966..b7e290e 100644
--- a/include/qpid/dispatch/connection_manager.h
+++ b/include/qpid/dispatch/connection_manager.h
@@ -28,6 +28,7 @@
 
 typedef struct qd_connection_manager_t qd_connection_manager_t;
 typedef struct qd_config_ssl_profile_t qd_config_ssl_profile_t;
+typedef struct qd_config_sasl_plugin_t qd_config_sasl_plugin_t;
 
 typedef void (*qd_connection_manager_handler_t) (void *context, qd_connection_t *conn);
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/include/qpid/dispatch/server.h
----------------------------------------------------------------------
diff --git a/include/qpid/dispatch/server.h b/include/qpid/dispatch/server.h
index ba8494a..7fa61c3 100644
--- a/include/qpid/dispatch/server.h
+++ b/include/qpid/dispatch/server.h
@@ -23,6 +23,7 @@
 #include <qpid/dispatch/failoverlist.h>
 #include <proton/engine.h>
 #include <proton/event.h>
+#include <proton/ssl.h>
 
 struct qd_container_t;
 
@@ -152,6 +153,24 @@ typedef struct qd_server_config_t {
     char *sasl_mechanisms;
 
     /**
+     * Address, i.e. host:port, of remote authentication service to connect to.
+     * (listener only)
+     */
+    char *auth_service;
+    /**
+     * Hostname to set on sasl-init sent to authentication service.
+     */
+    char *sasl_init_hostname;
+    /**
+     * Ssl config for connecting to the authentication service.
+     */
+    pn_ssl_domain_t *auth_ssl_conf;
+    /**
+     * The name of the related sasl plugin config.
+     */
+    char *sasl_plugin;
+
+    /**
      * If appropriate for the mechanism, the username for authentication
      * (connector only)
      */

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/python/qpid_dispatch/management/qdrouter.json
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch/management/qdrouter.json b/python/qpid_dispatch/management/qdrouter.json
index 1a8eded..ccedc94 100644
--- a/python/qpid_dispatch/management/qdrouter.json
+++ b/python/qpid_dispatch/management/qdrouter.json
@@ -563,6 +563,33 @@
             }
         },
 
+        "authServicePlugin": {
+            "description":"EXPERIMENTAL. Attributes for setting SASL plugin.",
+            "referential": true,
+            "extends": "configurationEntity",
+            "operations": ["CREATE", "DELETE"],
+            "attributes": {
+                "authService": {
+                    "type": "string",
+                    "description": "Address of a service to delegate authentication to.",
+                    "required": true,
+                    "create": true
+                },
+                "saslInitHostname": {
+                    "type": "string",
+                    "description": "Value to set for hostname field on sasl-init",
+                    "required": false,
+                    "create": true
+                },
+                "authSslProfile": {
+                    "type": "string",
+                    "required": false,
+                    "description": "Name of the sslProfile to use for the authentication
service.",
+                    "create": true
+                }
+            }
+        },
+
         "listener": {
             "description": "Listens for incoming connections to the router.",
             "extends": "configurationEntity",
@@ -623,6 +650,12 @@
                     "description": "yes: Require the peer's identity to be authenticated;
no: Do not require any authentication.",
                     "create": true
                 },
+                "saslPlugin": {
+                    "type": "string",
+                    "required": false,
+                    "description": "EXPERIMENTAL. Name of the a sasl plugin configuration
section to use for this listener (e.g. authServicePlugin).",
+                    "create": true
+                },
                 "requireEncryption": {
                     "type": "boolean",
                     "default": false,

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/python/qpid_dispatch_internal/dispatch.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/dispatch.py b/python/qpid_dispatch_internal/dispatch.py
index c35fb5d..e536725 100644
--- a/python/qpid_dispatch_internal/dispatch.py
+++ b/python/qpid_dispatch_internal/dispatch.py
@@ -62,6 +62,7 @@ class QdDll(ctypes.PyDLL):
         self._prototype(self.qd_dispatch_configure_listener, ctypes.c_void_p, [self.qd_dispatch_p,
py_object])
         self._prototype(self.qd_dispatch_configure_connector, ctypes.c_void_p, [self.qd_dispatch_p,
py_object])
         self._prototype(self.qd_dispatch_configure_ssl_profile, ctypes.c_void_p, [self.qd_dispatch_p,
py_object])
+        self._prototype(self.qd_dispatch_configure_sasl_plugin, ctypes.c_void_p, [self.qd_dispatch_p,
py_object])
 
         self._prototype(self.qd_connection_manager_delete_listener, None, [self.qd_dispatch_p,
ctypes.c_void_p])
         self._prototype(self.qd_connection_manager_delete_connector, None, [self.qd_dispatch_p,
ctypes.c_void_p])

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/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 a52d934..429c6b7 100644
--- a/python/qpid_dispatch_internal/management/agent.py
+++ b/python/qpid_dispatch_internal/management/agent.py
@@ -359,6 +359,18 @@ class SslProfileEntity(EntityAdapter):
     def __str__(self):
         return super(SslProfileEntity, self).__str__().replace("Entity(", "SslProfileEntity(")
 
+class AuthServicePluginEntity(EntityAdapter):
+    def create(self):
+        return self._qd.qd_dispatch_configure_sasl_plugin(self._dispatch, self)
+
+    def _delete(self):
+        self._qd.qd_connection_manager_delete_sasl_plugin(self._dispatch, self._implementations[0].key)
+    def _identifier(self):
+        return self.name
+
+    def __str__(self):
+        return super(AuthServicePluginEntity, self).__str__().replace("Entity(", "AuthServicePluginEntity(")
+
 class ListenerEntity(EntityAdapter):
     def create(self):
         config_listener = self._qd.qd_dispatch_configure_listener(self._dispatch, self)

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/python/qpid_dispatch_internal/management/config.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/management/config.py b/python/qpid_dispatch_internal/management/config.py
index b8766fb..7f8d629 100644
--- a/python/qpid_dispatch_internal/management/config.py
+++ b/python/qpid_dispatch_internal/management/config.py
@@ -173,7 +173,7 @@ def configure_dispatch(dispatch, lib_handle, filename):
     policyDir = config.by_type('policy')[0]['policyDir']
     policyDefaultVhost = config.by_type('policy')[0]['defaultVhost']
     # Remaining configuration
-    for t in "sslProfile", "fixedAddress", "listener", "connector", "waypoint", "linkRoutePattern",
\
+    for t in "sslProfile", "authServicePlugin", "fixedAddress", "listener", "connector",
"waypoint", "linkRoutePattern", \
              "router.config.address", "router.config.linkRoute", "router.config.autoLink",
\
              "policy", "vhost":
         for a in config.by_type(t):

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/src/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a672f13..78b17b3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -70,6 +70,7 @@ set(qpid_dispatch_SOURCES
   parse.c
   parse_tree.c
   policy.c
+  remote_sasl.c
   posix/threading.c
   python_embedded.c
   router_agent.c

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/src/connection_manager.c
----------------------------------------------------------------------
diff --git a/src/connection_manager.c b/src/connection_manager.c
index 041f231..797326c 100644
--- a/src/connection_manager.c
+++ b/src/connection_manager.c
@@ -48,12 +48,24 @@ struct qd_config_ssl_profile_t {
 
 DEQ_DECLARE(qd_config_ssl_profile_t, qd_config_ssl_profile_list_t);
 
+struct qd_config_sasl_plugin_t {
+    DEQ_LINKS(qd_config_sasl_plugin_t);
+    uint64_t     identity;
+    char        *name;
+    char        *auth_service;
+    char        *sasl_init_hostname;
+    char        *auth_ssl_profile;
+};
+
+DEQ_DECLARE(qd_config_sasl_plugin_t, qd_config_sasl_plugin_list_t);
+
 struct qd_connection_manager_t {
     qd_log_source_t              *log_source;
     qd_server_t                  *server;
     qd_listener_list_t            listeners;
     qd_connector_list_t           connectors;
     qd_config_ssl_profile_list_t  config_ssl_profiles;
+    qd_config_sasl_plugin_list_t  config_sasl_plugins;
 };
 
 const char *qd_log_message_components[] =
@@ -91,6 +103,21 @@ static qd_config_ssl_profile_t *qd_find_ssl_profile(qd_connection_manager_t
*cm,
     return 0;
 }
 
+/**
+ * Search the list of config_sasl_plugins for an sasl-profile that matches the passed in
name
+ */
+static qd_config_sasl_plugin_t *qd_find_sasl_plugin(qd_connection_manager_t *cm, char *name)
+{
+    qd_config_sasl_plugin_t *sasl_plugin = DEQ_HEAD(cm->config_sasl_plugins);
+    while (sasl_plugin) {
+        if (strcmp(sasl_plugin->name, name) == 0)
+            return sasl_plugin;
+        sasl_plugin = DEQ_NEXT(sasl_plugin);
+    }
+
+    return 0;
+}
+
 void qd_server_config_free(qd_server_config_t *cf)
 {
     if (!cf) return;
@@ -104,6 +131,9 @@ void qd_server_config_free(qd_server_config_t *cf)
     if (cf->sasl_username)   free(cf->sasl_username);
     if (cf->sasl_password)   free(cf->sasl_password);
     if (cf->sasl_mechanisms) free(cf->sasl_mechanisms);
+    if (cf->auth_service)    free(cf->auth_service);
+    if (cf->sasl_init_hostname)    free(cf->sasl_init_hostname);
+    if (cf->auth_ssl_conf)   pn_ssl_domain_free(cf->auth_ssl_conf);
     if (cf->ssl_profile)     free(cf->ssl_profile);
     if (cf->failover_list)   qd_failover_list_free(cf->failover_list);
     if (cf->log_message)     free(cf->log_message);
@@ -119,6 +149,7 @@ void qd_server_config_free(qd_server_config_t *cf)
 }
 
 #define CHECK() if (qd_error_code()) goto error
+#define SSTRDUP(S) ((S) ? strdup(S) : NULL)
 
 /**
  * Private function to set the values of booleans strip_inbound_annotations and strip_outbound_annotations
@@ -304,6 +335,7 @@ static qd_error_t load_server_config(qd_dispatch_t *qd, qd_server_config_t
*conf
     config->sasl_password        = qd_entity_opt_string(entity, "saslPassword", 0);  
CHECK();
     config->sasl_mechanisms      = qd_entity_opt_string(entity, "saslMechanisms", 0);
CHECK();
     config->ssl_profile          = qd_entity_opt_string(entity, "sslProfile", 0);    
CHECK();
+    config->sasl_plugin          = qd_entity_opt_string(entity, "saslPlugin", 0);   CHECK();
     config->link_capacity        = qd_entity_opt_long(entity, "linkCapacity", 0);    
CHECK();
     config->multi_tenant         = qd_entity_opt_bool(entity, "multiTenant", false); 
CHECK();
     set_config_host(config, entity);
@@ -371,7 +403,6 @@ static qd_error_t load_server_config(qd_dispatch_t *qd, qd_server_config_t
*conf
         qd_config_ssl_profile_t *ssl_profile =
             qd_find_ssl_profile(qd->connection_manager, config->ssl_profile);
         if (ssl_profile) {
-#define SSTRDUP(S) ((S) ? strdup(S) : NULL)
             config->ssl_certificate_file = SSTRDUP(ssl_profile->ssl_certificate_file);
             config->ssl_private_key_file = SSTRDUP(ssl_profile->ssl_private_key_file);
             config->ssl_password = SSTRDUP(ssl_profile->ssl_password);
@@ -381,6 +412,41 @@ static qd_error_t load_server_config(qd_dispatch_t *qd, qd_server_config_t
*conf
             config->ssl_display_name_file = SSTRDUP(ssl_profile->ssl_display_name_file);
         }
     }
+    if (config->sasl_plugin) {
+        qd_config_sasl_plugin_t *sasl_plugin =
+            qd_find_sasl_plugin(qd->connection_manager, config->sasl_plugin);
+        if (sasl_plugin) {
+            config->auth_service = SSTRDUP(sasl_plugin->auth_service);
+            config->sasl_init_hostname = SSTRDUP(sasl_plugin->sasl_init_hostname);
+            qd_log(qd->connection_manager->log_source, QD_LOG_INFO, "Using auth service
%s from  SASL Plugin %s", config->auth_service, config->sasl_plugin);
+
+            if (sasl_plugin->auth_ssl_profile) {
+                qd_config_ssl_profile_t *auth_ssl_profile =
+                    qd_find_ssl_profile(qd->connection_manager, sasl_plugin->auth_ssl_profile);
+                config->auth_ssl_conf = pn_ssl_domain(PN_SSL_MODE_CLIENT);
+
+                if (auth_ssl_profile->ssl_certificate_file) {
+                    if (pn_ssl_domain_set_credentials(config->auth_ssl_conf,
+                                                      auth_ssl_profile->ssl_certificate_file,
+                                                      auth_ssl_profile->ssl_private_key_file,
+                                                      auth_ssl_profile->ssl_password))
{
+                        qd_error(QD_ERROR_RUNTIME, "Cannot set SSL credentials for authentication
service"); CHECK();
+                    }
+                }
+                if (auth_ssl_profile->ssl_trusted_certificate_db) {
+                    if (pn_ssl_domain_set_trusted_ca_db(config->auth_ssl_conf, auth_ssl_profile->ssl_trusted_certificate_db))
{
+                        qd_error(QD_ERROR_RUNTIME, "Cannot set trusted SSL certificate db
for authentication service" ); CHECK();
+                    } else {
+                        if (pn_ssl_domain_set_peer_authentication(config->auth_ssl_conf,
PN_SSL_VERIFY_PEER, auth_ssl_profile->ssl_trusted_certificate_db)) {
+                            qd_error(QD_ERROR_RUNTIME, "Cannot set SSL peer verification
for authentication service"); CHECK();
+                        }
+                    }
+                }
+            }
+        } else {
+            qd_error(QD_ERROR_RUNTIME, "Cannot find sasl plugin %s", config->sasl_plugin);
CHECK();
+        }
+    }
 
     return QD_ERROR_NONE;
 
@@ -421,6 +487,19 @@ static bool config_ssl_profile_free(qd_connection_manager_t *cm, qd_config_ssl_p
 
 }
 
+static bool config_sasl_plugin_free(qd_connection_manager_t *cm, qd_config_sasl_plugin_t
*sasl_plugin)
+{
+    DEQ_REMOVE(cm->config_sasl_plugins, sasl_plugin);
+
+    free(sasl_plugin->name);
+    free(sasl_plugin->auth_service);
+    free(sasl_plugin->sasl_init_hostname);
+    free(sasl_plugin->auth_ssl_profile);
+    free(sasl_plugin);
+    return true;
+
+}
+
 
 qd_config_ssl_profile_t *qd_dispatch_configure_ssl_profile(qd_dispatch_t *qd, qd_entity_t
*entity)
 {
@@ -485,6 +564,28 @@ qd_config_ssl_profile_t *qd_dispatch_configure_ssl_profile(qd_dispatch_t
*qd, qd
         return 0;
 }
 
+qd_config_sasl_plugin_t *qd_dispatch_configure_sasl_plugin(qd_dispatch_t *qd, qd_entity_t
*entity)
+{
+    qd_error_clear();
+    qd_connection_manager_t *cm = qd->connection_manager;
+
+    qd_config_sasl_plugin_t *sasl_plugin = NEW(qd_config_sasl_plugin_t);
+    DEQ_ITEM_INIT(sasl_plugin);
+    DEQ_INSERT_TAIL(cm->config_sasl_plugins, sasl_plugin);
+    sasl_plugin->name                       = qd_entity_opt_string(entity, "name", 0);
CHECK();
+    sasl_plugin->auth_service               = qd_entity_opt_string(entity, "authService",
0); CHECK();
+    sasl_plugin->sasl_init_hostname         = qd_entity_opt_string(entity, "saslInitHostname",
0); CHECK();
+    sasl_plugin->auth_ssl_profile           = qd_entity_opt_string(entity, "authSslProfile",
0); CHECK();
+
+    qd_log(cm->log_source, QD_LOG_INFO, "Created SASL plugin config with name %s", sasl_plugin->name);
+    return sasl_plugin;
+
+    error:
+        qd_log(cm->log_source, QD_LOG_ERROR, "Unable to create SASL plugin config: %s",
qd_error_message());
+        config_sasl_plugin_free(cm, sasl_plugin);
+        return 0;
+}
+
 static void log_config(qd_log_source_t *log, qd_server_config_t *c, const char *what) {
     qd_log(log, QD_LOG_INFO, "Configured %s: %s proto=%s, role=%s%s%s%s",
            what, c->host_port, c->protocol_family ? c->protocol_family : "any",
@@ -563,6 +664,7 @@ qd_connection_manager_t *qd_connection_manager(qd_dispatch_t *qd)
     DEQ_INIT(cm->listeners);
     DEQ_INIT(cm->connectors);
     DEQ_INIT(cm->config_ssl_profiles);
+    DEQ_INIT(cm->config_sasl_plugins);
 
     return cm;
 }
@@ -590,6 +692,12 @@ void qd_connection_manager_free(qd_connection_manager_t *cm)
         config_ssl_profile_free(cm, sslp);
         sslp = DEQ_HEAD(cm->config_ssl_profiles);
     }
+
+    qd_config_sasl_plugin_t *saslp = DEQ_HEAD(cm->config_sasl_plugins);
+    while (saslp) {
+        config_sasl_plugin_free(cm, saslp);
+        saslp = DEQ_HEAD(cm->config_sasl_plugins);
+    }
 }
 
 
@@ -643,6 +751,12 @@ void qd_connection_manager_delete_ssl_profile(qd_dispatch_t *qd, void
*impl)
     config_ssl_profile_free(qd->connection_manager, ssl_profile);
 }
 
+void qd_connection_manager_delete_sasl_plugin(qd_dispatch_t *qd, void *impl)
+{
+    qd_config_sasl_plugin_t *sasl_plugin = (qd_config_sasl_plugin_t*) impl;
+    config_sasl_plugin_free(qd->connection_manager, sasl_plugin);
+}
+
 
 static void deferred_close(void *context, bool discard) {
     if (!discard) {

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/src/dispatch.c
----------------------------------------------------------------------
diff --git a/src/dispatch.c b/src/dispatch.c
index 74dd0c9..3f8ed53 100644
--- a/src/dispatch.c
+++ b/src/dispatch.c
@@ -207,6 +207,7 @@ qd_error_t qd_dispatch_configure_router(qd_dispatch_t *qd, qd_entity_t
*entity)
     if (! qd->sasl_config_name) {
         qd->sasl_config_name = qd_entity_opt_string(entity, "saslConfigName", "qdrouterd");
QD_ERROR_RET();
     }
+    qd->auth_service = qd_entity_opt_string(entity, "authService", 0); QD_ERROR_RET();
 
     char *dump_file = qd_entity_opt_string(entity, "debugDump", 0); QD_ERROR_RET();
     if (dump_file) {

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/src/dispatch_private.h
----------------------------------------------------------------------
diff --git a/src/dispatch_private.h b/src/dispatch_private.h
index 6d67e38..ed94d0e 100644
--- a/src/dispatch_private.h
+++ b/src/dispatch_private.h
@@ -57,6 +57,7 @@ struct qd_dispatch_t {
     int    thread_count;
     char  *sasl_config_path;
     char  *sasl_config_name;
+    char  *auth_service;
     char  *router_area;
     char  *router_id;
     qd_router_mode_t  router_mode;

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/src/remote_sasl.c
----------------------------------------------------------------------
diff --git a/src/remote_sasl.c b/src/remote_sasl.c
new file mode 100644
index 0000000..e037454
--- /dev/null
+++ b/src/remote_sasl.c
@@ -0,0 +1,428 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+#include "remote_sasl.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <proton/engine.h>
+#include <proton/proactor.h>
+#include <proton/sasl.h>
+#include <proton/sasl-plugin.h>
+
+typedef struct
+{
+    size_t size;
+    char *start;
+} qdr_owned_bytes_t;
+
+const int8_t UPSTREAM_INIT_RECEIVED = 1;
+const int8_t UPSTREAM_RESPONSE_RECEIVED = 2;
+const int8_t DOWNSTREAM_MECHANISMS_RECEIVED = 3;
+const int8_t DOWNSTREAM_CHALLENGE_RECEIVED = 4;
+const int8_t DOWNSTREAM_OUTCOME_RECEIVED = 5;
+const int8_t DOWNSTREAM_CLOSED = 6;
+
+typedef struct
+{
+    char* authentication_service_address;
+    char* sasl_init_hostname;
+    pn_ssl_domain_t* ssl_domain;
+
+    pn_connection_t* downstream;
+    char* selected_mechanism;
+    qdr_owned_bytes_t response;
+    int8_t downstream_state;
+    bool downstream_released;
+
+    pn_connection_t* upstream;
+    char* mechlist;
+    qdr_owned_bytes_t challenge;
+    int8_t upstream_state;
+    bool upstream_released;
+
+    bool complete;
+    char* username;
+    pn_sasl_outcome_t outcome;
+} qdr_sasl_relay_t;
+
+static void copy_bytes(const pn_bytes_t* from, qdr_owned_bytes_t* to)
+{
+    if (to->start) {
+        free(to->start);
+    }
+    to->start = (char*) malloc(from->size);
+    to->size = from->size;
+    memcpy(to->start, from->start, from->size);
+}
+
+static qdr_sasl_relay_t* new_qdr_sasl_relay_t(const char* address, const char* sasl_init_hostname)
+{
+    qdr_sasl_relay_t* instance = (qdr_sasl_relay_t*) malloc(sizeof(qdr_sasl_relay_t));
+    instance->authentication_service_address = strdup(address);
+    if (sasl_init_hostname) {
+        instance->sasl_init_hostname = strdup(sasl_init_hostname);
+    }
+    instance->selected_mechanism = 0;
+    instance->response.start = 0;
+    instance->response.size = 0;
+    instance->mechlist = 0;
+    instance->challenge.start = 0;
+    instance->challenge.size = 0;
+    instance->upstream_state = 0;
+    instance->downstream_state = 0;
+    instance->upstream_released = false;
+    instance->downstream_released = false;
+    instance->complete = false;
+    instance->upstream = 0;
+    instance->downstream = 0;
+    instance->username = 0;
+    return instance;
+}
+
+static void delete_qdr_sasl_relay_t(qdr_sasl_relay_t* instance)
+{
+    if (instance->authentication_service_address) free(instance->authentication_service_address);
+    if (instance->mechlist) free(instance->mechlist);
+    if (instance->selected_mechanism) free(instance->selected_mechanism);
+    if (instance->response.start) free(instance->response.start);
+    if (instance->challenge.start) free(instance->challenge.start);
+    if (instance->username) free(instance->username);
+    free(instance);
+}
+
+PN_HANDLE(REMOTE_SASL_CTXT)
+
+bool qdr_is_authentication_service_connection(pn_connection_t* conn)
+{
+    if (conn) {
+        pn_record_t *r = pn_connection_attachments(conn);
+        return pn_record_has(r, REMOTE_SASL_CTXT);
+    } else {
+        return false;
+    }
+}
+
+static qdr_sasl_relay_t* get_sasl_relay_context(pn_connection_t* conn)
+{
+    if (conn) {
+        pn_record_t *r = pn_connection_attachments(conn);
+        if (pn_record_has(r, REMOTE_SASL_CTXT)) {
+            return (qdr_sasl_relay_t*) pn_record_get(r, REMOTE_SASL_CTXT);
+        } else {
+            return NULL;
+        }
+    } else {
+        return NULL;
+    }
+}
+
+static void set_sasl_relay_context(pn_connection_t* conn, qdr_sasl_relay_t* context)
+{
+    pn_record_t *r = pn_connection_attachments(conn);
+    pn_record_def(r, REMOTE_SASL_CTXT, PN_VOID);
+    pn_record_set(r, REMOTE_SASL_CTXT, context);
+}
+
+static bool remote_sasl_init_server(pn_transport_t* transport)
+{
+    pn_connection_t* upstream = pn_transport_connection(transport);
+    if (upstream && pnx_sasl_get_context(transport)) {
+        qdr_sasl_relay_t* impl = (qdr_sasl_relay_t*) pnx_sasl_get_context(transport);
+        if (impl->upstream) return true;
+        impl->upstream = upstream;
+        pn_proactor_t* proactor = pn_connection_proactor(upstream);
+        if (!proactor) return false;
+        impl->downstream = pn_connection();
+        pn_connection_set_hostname(impl->downstream, pn_connection_get_hostname(upstream));
+        pn_connection_set_user(impl->downstream, "dummy");//force sasl
+        set_sasl_relay_context(impl->downstream, impl);
+
+        pn_proactor_connect(proactor, impl->downstream, impl->authentication_service_address);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static bool remote_sasl_init_client(pn_transport_t* transport)
+{
+    //for the client side of the connection to the authentication
+    //service, need to use the same context as the server side of the
+    //connection it is authenticating on behalf of
+    pn_connection_t* conn = pn_transport_connection(transport);
+    qdr_sasl_relay_t* impl = get_sasl_relay_context(conn);
+    if (impl) {
+        pnx_sasl_set_context(transport, impl);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static void remote_sasl_free(pn_transport_t *transport)
+{
+    qdr_sasl_relay_t* impl = (qdr_sasl_relay_t*) pnx_sasl_get_context(transport);
+    if (impl) {
+        if (pnx_sasl_is_client(transport)) {
+            impl->downstream_released = true;
+            if (impl->upstream_released) {
+                delete_qdr_sasl_relay_t(impl);
+            } else {
+                pn_connection_wake(impl->upstream);
+            }
+        } else {
+            impl->upstream_released = true;
+            if (impl->downstream_released) {
+                delete_qdr_sasl_relay_t(impl);
+            } else {
+                pn_connection_wake(impl->downstream);
+            }
+        }
+    }
+}
+
+static void remote_sasl_prepare(pn_transport_t *transport)
+{
+    qdr_sasl_relay_t* impl = (qdr_sasl_relay_t*) pnx_sasl_get_context(transport);
+    if (!impl) return;
+    if (pnx_sasl_is_client(transport)) {
+        if (impl->downstream_state == UPSTREAM_INIT_RECEIVED) {
+            pnx_sasl_set_selected_mechanism(transport, strdup(impl->selected_mechanism));
+            pnx_sasl_set_local_hostname(transport, impl->sasl_init_hostname);
+            pnx_sasl_set_bytes_out(transport, pn_bytes(impl->response.size, impl->response.start));
+            pnx_sasl_set_desired_state(transport, SASL_POSTED_INIT);
+        } else if (impl->downstream_state == UPSTREAM_RESPONSE_RECEIVED) {
+            pnx_sasl_set_bytes_out(transport, pn_bytes(impl->response.size, impl->response.start));
+            pnx_sasl_set_desired_state(transport, SASL_POSTED_RESPONSE);
+        }
+        impl->downstream_state = 0;
+    } else {
+        if (impl->upstream_state == DOWNSTREAM_MECHANISMS_RECEIVED) {
+            pnx_sasl_set_desired_state(transport, SASL_POSTED_MECHANISMS);
+        } else if (impl->upstream_state == DOWNSTREAM_CHALLENGE_RECEIVED) {
+            pnx_sasl_set_bytes_out(transport, pn_bytes(impl->challenge.size, impl->challenge.start));
+            pnx_sasl_set_desired_state(transport, SASL_POSTED_CHALLENGE);
+        } else if (impl->upstream_state == DOWNSTREAM_OUTCOME_RECEIVED) {
+            switch (impl->outcome) {
+            case PN_SASL_OK:
+                pnx_sasl_succeed_authentication(transport, impl->username);
+                break;
+            default:
+                pnx_sasl_fail_authentication(transport);
+            }
+            pnx_sasl_set_desired_state(transport, SASL_POSTED_OUTCOME);
+        }
+        impl->upstream_state = 0;
+    }
+}
+
+static bool notify_upstream(qdr_sasl_relay_t* impl, uint8_t state)
+{
+    if (!impl->upstream_released) {
+        impl->upstream_state = state;
+        pn_connection_wake(impl->upstream);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static bool notify_downstream(qdr_sasl_relay_t* impl, uint8_t state)
+{
+    if (!impl->downstream_released) {
+        impl->downstream_state = state;
+        pn_connection_wake(impl->downstream);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+// Client / Downstream
+static bool remote_sasl_process_mechanisms(pn_transport_t *transport, const char *mechs)
+{
+    qdr_sasl_relay_t* impl = (qdr_sasl_relay_t*) pnx_sasl_get_context(transport);
+    if (impl) {
+        impl->mechlist = strdup(mechs);
+        if (notify_upstream(impl, DOWNSTREAM_MECHANISMS_RECEIVED)) {
+            return true;
+        } else {
+            pnx_sasl_set_desired_state(transport, SASL_ERROR);
+            return false;
+        }
+    } else {
+        return false;
+    }
+}
+
+// Client / Downstream
+static void remote_sasl_process_challenge(pn_transport_t *transport, const pn_bytes_t *recv)
+{
+    qdr_sasl_relay_t* impl = (qdr_sasl_relay_t*) pnx_sasl_get_context(transport);
+    if (impl) {
+        copy_bytes(recv, &(impl->challenge));
+        if (!notify_upstream(impl, DOWNSTREAM_CHALLENGE_RECEIVED)) {
+            pnx_sasl_set_desired_state(transport, SASL_ERROR);
+        }
+    }
+}
+
+// Client / Downstream
+static void remote_sasl_process_outcome(pn_transport_t *transport)
+{
+    qdr_sasl_relay_t* impl = (qdr_sasl_relay_t*) pnx_sasl_get_context(transport);
+    if (impl) {
+        pn_sasl_t* sasl = pn_sasl(transport);
+        if (sasl) {
+            impl->outcome = pn_sasl_outcome(sasl);
+            impl->username = strdup(pn_sasl_get_user(sasl));
+            impl->complete = true;
+            if (!notify_upstream(impl, DOWNSTREAM_OUTCOME_RECEIVED)) {
+                pnx_sasl_set_desired_state(transport, SASL_ERROR);
+            }
+        }
+    }
+}
+
+// Server / Upstream
+static const char* remote_sasl_list_mechs(pn_transport_t *transport)
+{
+    qdr_sasl_relay_t* impl = (qdr_sasl_relay_t*) pnx_sasl_get_context(transport);
+    if (impl && impl->mechlist) {
+        return impl->mechlist;
+    } else {
+        return NULL;
+    }
+}
+
+// Server / Upstream
+static void remote_sasl_process_init(pn_transport_t *transport, const char *mechanism, const
pn_bytes_t *recv)
+{
+    qdr_sasl_relay_t* impl = (qdr_sasl_relay_t*) pnx_sasl_get_context(transport);
+    if (impl) {
+        impl->selected_mechanism = strdup(mechanism);
+        copy_bytes(recv, &(impl->response));
+        if (!notify_downstream(impl, UPSTREAM_INIT_RECEIVED)) {
+            pnx_sasl_set_desired_state(transport, SASL_ERROR);
+        }
+    }
+}
+
+// Server / Upstream
+static void remote_sasl_process_response(pn_transport_t *transport, const pn_bytes_t *recv)
+{
+    qdr_sasl_relay_t* impl = (qdr_sasl_relay_t*) pnx_sasl_get_context(transport);
+    if (impl) {
+        copy_bytes(recv, &(impl->response));
+        if (!notify_downstream(impl, UPSTREAM_RESPONSE_RECEIVED)) {
+            pnx_sasl_set_desired_state(transport, SASL_ERROR);
+        }
+    }
+}
+
+static bool remote_sasl_can_encrypt(pn_transport_t *transport)
+{
+    return false;
+}
+
+static ssize_t remote_sasl_max_encrypt_size(pn_transport_t *transport)
+{
+    return 0;
+}
+static ssize_t remote_sasl_encode(pn_transport_t *transport, pn_bytes_t in, pn_bytes_t *out)
+{
+    return 0;
+}
+static ssize_t remote_sasl_decode(pn_transport_t *transport, pn_bytes_t in, pn_bytes_t *out)
+{
+    return 0;
+}
+
+
+static const pnx_sasl_implementation remote_sasl_impl = {
+    remote_sasl_free,
+    remote_sasl_list_mechs,
+    remote_sasl_init_server,
+    remote_sasl_init_client,
+    remote_sasl_prepare,
+    remote_sasl_process_init,
+    remote_sasl_process_response,
+    remote_sasl_process_mechanisms,
+    remote_sasl_process_challenge,
+    remote_sasl_process_outcome,
+    remote_sasl_can_encrypt,
+    remote_sasl_max_encrypt_size,
+    remote_sasl_encode,
+    remote_sasl_decode
+};
+
+static void set_remote_impl(pn_transport_t *transport, qdr_sasl_relay_t* context)
+{
+    pnx_sasl_set_implementation(transport, &remote_sasl_impl, context);
+}
+
+void qdr_use_remote_authentication_service(pn_transport_t *transport, const char* address,
const char* sasl_init_hostname, pn_ssl_domain_t* ssl_domain)
+{
+    qdr_sasl_relay_t* context = new_qdr_sasl_relay_t(address, sasl_init_hostname);
+    context->ssl_domain = ssl_domain;
+    set_remote_impl(transport, context);
+}
+
+void qdr_handle_authentication_service_connection_event(pn_event_t *e)
+{
+    pn_connection_t *conn = pn_event_connection(e);
+    pn_transport_t *transport = pn_event_transport(e);
+    if (pn_event_type(e) == PN_CONNECTION_BOUND) {
+        pnx_sasl_logf(transport, "Handling connection bound event for authentication service
connection");
+        qdr_sasl_relay_t* context = get_sasl_relay_context(conn);
+        if (context->ssl_domain) {
+            pn_ssl_t* ssl = pn_ssl(transport);
+            if (!ssl || pn_ssl_init(ssl, context->ssl_domain, 0)) {
+                pnx_sasl_logf(transport, "Cannot initialise SSL");
+            } else {
+                pnx_sasl_logf(transport, "Successfully initialised SSL");
+            }
+        }
+        set_remote_impl(pn_event_transport(e), context);
+    } else if (pn_event_type(e) == PN_CONNECTION_REMOTE_OPEN) {
+        pnx_sasl_logf(transport, "authentication against service complete; closing connection");
+        pn_connection_close(conn);
+    } else if (pn_event_type(e) == PN_CONNECTION_REMOTE_CLOSE) {
+        pnx_sasl_logf(transport, "authentication service closed connection");
+        pn_connection_close(conn);
+        pn_transport_close_head(transport);
+    } else if (pn_event_type(e) == PN_TRANSPORT_CLOSED) {
+        pnx_sasl_logf(transport, "disconnected from authentication service");
+        qdr_sasl_relay_t* impl = (qdr_sasl_relay_t*) pnx_sasl_get_context(transport);
+        if (impl->downstream) {
+            pn_connection_release(impl->downstream);
+            impl->downstream = 0;
+            pnx_sasl_logf(transport, "authentication service: downstream connection released");
+        }
+        if (!impl->complete) {
+            notify_upstream(impl, DOWNSTREAM_CLOSED);
+        }
+    } else if (transport) {
+        pnx_sasl_logf(transport, "Ignoring event for authentication service connection: %s",
pn_event_type_name(pn_event_type(e)));
+    }
+}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/src/remote_sasl.h
----------------------------------------------------------------------
diff --git a/src/remote_sasl.h b/src/remote_sasl.h
new file mode 100644
index 0000000..2afd686
--- /dev/null
+++ b/src/remote_sasl.h
@@ -0,0 +1,31 @@
+#ifndef __remote_sasl_h__
+#define __remote_sasl_h__ 1
+
+/*
+ * 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.
+ */
+
+#include <proton/event.h>
+#include <proton/ssl.h>
+#include <proton/types.h>
+
+void qdr_use_remote_authentication_service(pn_transport_t* transport, const char* address,
const char* sasl_init_hostname, pn_ssl_domain_t* ssl_domain);
+bool qdr_is_authentication_service_connection(pn_connection_t* conn);
+void qdr_handle_authentication_service_connection_event(pn_event_t *e);
+
+#endif /* remote_sasl.h */

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/src/server.c
----------------------------------------------------------------------
diff --git a/src/server.c b/src/server.c
index c1bf43c..9994d12 100644
--- a/src/server.c
+++ b/src/server.c
@@ -40,6 +40,7 @@
 #include "timer_private.h"
 #include "alloc.h"
 #include "config.h"
+#include "remote_sasl.h"
 #include <stdio.h>
 #include <string.h>
 #include <errno.h>
@@ -345,6 +346,11 @@ qd_error_t qd_entity_refresh_sslProfile(qd_entity_t* entity, void *impl)
     return QD_ERROR_NONE;
 }
 
+qd_error_t qd_entity_refresh_authServicePlugin(qd_entity_t* entity, void *impl)
+{
+    return QD_ERROR_NONE;
+}
+
 
 static qd_error_t listener_setup_ssl(qd_connection_t *ctx, const qd_server_config_t *config,
pn_transport_t *tport)
 {
@@ -621,6 +627,10 @@ static void on_connection_bound(qd_server_t *server, pn_event_t *e) {
         pn_sasl_config_name(sasl, ctx->server->sasl_config_name);
         if (config->sasl_mechanisms)
             pn_sasl_allowed_mechs(sasl, config->sasl_mechanisms);
+        if (config->auth_service) {
+            qd_log(server->log_source, QD_LOG_INFO, "enabling remote authentication service
%s", config->auth_service);
+            qdr_use_remote_authentication_service(tport, config->auth_service, config->sasl_init_hostname,
config->auth_ssl_conf);
+        }
         pn_transport_require_auth(tport, config->requireAuthentication);
         pn_transport_require_encryption(tport, config->requireEncryption);
         pn_sasl_set_allow_insecure_mechs(sasl, config->allowInsecureAuthentication);
@@ -751,6 +761,10 @@ void qd_connection_free(qd_connection_t *ctx)
  */
 static bool handle(qd_server_t *qd_server, pn_event_t *e) {
     pn_connection_t *pn_conn = pn_event_connection(e);
+    if (pn_conn && qdr_is_authentication_service_connection(pn_conn)) {
+        qdr_handle_authentication_service_connection_event(e);
+        return true;
+    }
     qd_connection_t *ctx  = pn_conn ? (qd_connection_t*) pn_connection_get_context(pn_conn)
: NULL;
 
     switch (pn_event_type(e)) {

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/tests/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index fbf2f94..8108434 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -98,6 +98,7 @@ foreach(py_test_module
     system_tests_log_message_components
     system_tests_failover_list
     system_tests_denied_unsettled_multicast
+    system_tests_auth_service_plugin
     ${SYSTEM_TESTS_HTTP}
     )
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/tests/system_tests_auth_service_plugin.py
----------------------------------------------------------------------
diff --git a/tests/system_tests_auth_service_plugin.py b/tests/system_tests_auth_service_plugin.py
new file mode 100644
index 0000000..c2f8520
--- /dev/null
+++ b/tests/system_tests_auth_service_plugin.py
@@ -0,0 +1,137 @@
+#
+# 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 unittest, os, json
+from subprocess import PIPE, Popen, STDOUT
+from system_test import TestCase, Qdrouterd, main_module, DIR, TIMEOUT, Process
+from proton import SASL
+from proton.handlers import MessagingHandler
+from proton.reactor import Container
+
+class AuthServicePluginTest(TestCase):
+    @classmethod
+    def createSaslFiles(cls):
+        # Create a sasl database.
+        p = Popen(['saslpasswd2', '-c', '-p', '-f', 'qdrouterd.sasldb', '-u', 'domain.com',
'test'],
+                  stdin=PIPE, stdout=PIPE, stderr=PIPE)
+        result = p.communicate('password')
+        assert p.returncode == 0, \
+            "saslpasswd2 exit status %s, output:\n%s" % (p.returncode, result)
+
+        # Create a SASL configuration file.
+        with open('tests-mech-PLAIN.conf', 'w') as sasl_conf:
+            sasl_conf.write("""
+pwcheck_method: auxprop
+auxprop_plugin: sasldb
+sasldb_path: qdrouterd.sasldb
+mech_list: SCRAM-SHA1 PLAIN
+# The following line stops spurious 'sql_select option missing' errors when cyrus-sql-sasl
plugin is installed
+sql_select: dummy select
+""")
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        Tests the delegation of sasl auth to an external auth service.
+
+        Creates two routers, one acts as the authe service, the other configures the auth
service plugin
+        to point at this auth service.
+
+        """
+        super(AuthServicePluginTest, cls).setUpClass()
+
+        if not SASL.extended():
+            return
+
+        cls.createSaslFiles()
+
+        print('launching auth service...')
+        auth_service_port = cls.tester.get_port()
+        cls.tester.qdrouterd('auth_service', Qdrouterd.Config([
+                     ('listener', {'host': '0.0.0.0', 'role': 'normal', 'port': auth_service_port,
+                                   'saslMechanisms':'PLAIN', 'authenticatePeer': 'yes'}),
+                     ('router', {'workerThreads': 1,
+                                 'id': 'auth_service',
+                                 'mode': 'standalone',
+                                 'saslConfigName': 'tests-mech-PLAIN',
+                                 'saslConfigPath': os.getcwd()})
+        ])).wait_ready()
+
+        cls.router_port = cls.tester.get_port()
+        cls.tester.qdrouterd('router', Qdrouterd.Config([
+                     ('authServicePlugin', {'name':'myauth', 'authService': 'localhost:%d'
% auth_service_port}),
+                     ('listener', {'host': '0.0.0.0', 'port': cls.router_port, 'role': 'normal',
'saslPlugin':'myauth', 'saslMechanisms':'PLAIN'}),
+                     ('router', {'mode': 'standalone', 'id': 'router'})
+        ])).wait_ready()
+
+    def test_valid_credentials(self):
+        """
+        Check authentication succeeds when valid credentials are presented.
+
+        """
+        if not SASL.extended():
+            self.skipTest("Cyrus library not available. skipping test")
+
+        test = SimpleConnect("localhost:%d" % self.router_port, 'test@domain.com', 'password')
+        test.run()
+        self.assertEqual(True, test.connected)
+        self.assertEqual(None, test.error)
+
+    def test_invalid_credentials(self):
+        """
+        Check authentication fails when invalid credentials are presented.
+
+        """
+        if not SASL.extended():
+            self.skipTest("Cyrus library not available. skipping test")
+
+        test = SimpleConnect("localhost:%d" % self.router_port, 'test@domain.com', 'foo')
+        test.run()
+        self.assertEqual(False, test.connected)
+        self.assertEqual('amqp:unauthorized-access', test.error.name)
+        self.assertEqual(test.error.description.startswith('Authentication failed'), True)
+
+class SimpleConnect(MessagingHandler):
+    def __init__(self, url, username, password):
+        super(SimpleConnect, self).__init__()
+        self.url = url
+        self.username = username
+        self.password = password
+        self.connected = False
+        self.error = None
+
+    def on_start(self, event):
+        event.container.user = self.username
+        event.container.password = self.password
+        conn = event.container.connect(self.url)
+
+    def on_connection_opened(self, event):
+        self.connected = True
+        event.connection.close()
+
+    def on_transport_error(self, event):
+        self.error = event.transport.condition
+        event.connection.close()
+
+    def run(self):
+        Container(self).run()
+
+if __name__ == '__main__':
+    unittest.main(main_module())
+

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c98cb904/tests/system_tests_qdmanage.py
----------------------------------------------------------------------
diff --git a/tests/system_tests_qdmanage.py b/tests/system_tests_qdmanage.py
index 9b92dbc..43d1760 100644
--- a/tests/system_tests_qdmanage.py
+++ b/tests/system_tests_qdmanage.py
@@ -177,7 +177,7 @@ class QdmanageTest(TestCase):
 
     def test_get_types(self):
         out = json.loads(self.run_qdmanage("get-types"))
-        self.assertEqual(len(out), 28)
+        self.assertEqual(len(out), 29)
 
     def test_get_log(self):
         log = json.loads(self.run_qdmanage("get-log limit=1"))[0]


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


Mime
View raw message