diff --git a/pyngus/connection.py b/pyngus/connection.py index 9b70a02..9252dad 100644 --- a/pyngus/connection.py +++ b/pyngus/connection.py @@ -106,7 +106,7 @@ class Connection(Endpoint): # set of all SASL connection configuration properties _SASL_PROPS = set(['x-username', 'x-password', 'x-require-auth', 'x-sasl-mechs', 'x-sasl-config-dir', - 'x-sasl-config-name']) + 'x-sasl-config-name', 'x-force-sasl']) def _not_reentrant(func): """Decorator that prevents callbacks from calling into methods that are @@ -162,6 +162,13 @@ class Connection(Endpoint): x-sasl-config-name: string, name of the Cyrus SASL configuration file contained in the x-sasl-config-dir (without the '.conf' suffix) + x-force-sasl: by default SASL authentication is disabled. SASL will be + enabled if any of the above x-sasl-* options are set. For clients using + GSSAPI it is likely none of these options will be set. In order for + these clients to authenticate this flag must be set true. The value of + this property is ignored if any of the other SASL related properties + are set. + x-ssl-identity: tuple, contains identifying certificate information which will be presented to the peer. The first item in the tuple is the path to the certificate file (PEM format). The second item is the @@ -249,6 +256,11 @@ class Connection(Endpoint): self._pn_sasl = None self._sasl_done = False + # if x-force-sasl is false remove it so it does not trigger the SASL + # configuration logic below + if not self._properties.get('x-force-sasl', True): + del self._properties['x-force-sasl'] + if self._SASL_PROPS.intersection(set(self._properties.keys())): # SASL config specified, need to enable SASL if (_PROTON_VERSION < (0, 10)): diff --git a/tests/unit_tests/connection.py b/tests/unit_tests/connection.py index 540062a..36b3844 100644 --- a/tests/unit_tests/connection.py +++ b/tests/unit_tests/connection.py @@ -903,3 +903,53 @@ mech_list: EXTERNAL DIGEST-MD5 SCRAM-SHA-1 CRAM-MD5 PLAIN ANONYMOUS # # NOTE WELL: Update TEST_COUNT as new test methods are added!! # + + +class SASLTest(common.Test): + """A test class to check SASL configuration flags. + """ + + def setup(self): + super(SASLTest, self).setup() + # logging.getLogger("pyngus").setLevel(logging.DEBUG) + self.container1 = pyngus.Container("test-container-1") + + def teardown(self): + if self.container1: + self.container1.destroy() + + def _header_protocol(self, conn): + # fetch the protocol # from the AMQP header + conn.open() + conn.process(time.time()) + assert conn.has_output >= 5, "header expected" + p = conn.output_data()[4] + try: + return ord(p) if isinstance(p, str) else int(p) + except: + assert False, "could not convert protocol" + + def test_sasl_disabled(self): + """Verify SASL is disabled when it is NOT configured. + """ + c1 = self.container1.create_connection("c1") + p = self._header_protocol(c1) + assert p == 0, "Bad protocol - expect '0' got '%s'" % p + + def test_sasl_force(self): + """Verify SASL is enabled if forced + """ + props = {'x-force-sasl': True} + c1 = self.container1.create_connection("c1", + properties=props) + p = self._header_protocol(c1) + assert p == 3, "Bad protocol - expect '3' got '%s'" % p + + def test_sasl_force_false(self): + """Verify setting x-force-sasl to False does not enable SASL. + """ + props = {'x-force-sasl': False} + c1 = self.container1.create_connection("c1", + properties=props) + p = self._header_protocol(c1) + assert p == 0, "Bad protocol - expect '0' got '%s'" % p