diff options
author | Keith Wall <kwall@apache.org> | 2014-09-25 14:40:31 +0000 |
---|---|---|
committer | Keith Wall <kwall@apache.org> | 2014-09-25 14:40:31 +0000 |
commit | 0336eaa7a0a6c414ff292e96871f3dc6d5a95204 (patch) | |
tree | 4e917fb21a33482d19da20331dee0ff22b574554 | |
parent | 6c4b6c2d0f7739996fa5120c897ec1de800f7107 (diff) | |
download | qpid-python-0336eaa7a0a6c414ff292e96871f3dc6d5a95204.tar.gz |
QPID-6116: [Python Client 08..091] Add ability to negotiate SASL mechanism and add pure python implementations for SCRAM/CRAM/PLAIN mechanisms
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk/qpid@1627554 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | python/qpid/client.py | 33 | ||||
-rw-r--r-- | python/qpid/saslmech/__init__.py | 22 | ||||
-rw-r--r-- | python/qpid/saslmech/amqplain.py | 31 | ||||
-rw-r--r-- | python/qpid/saslmech/anonymous.py | 23 | ||||
-rw-r--r-- | python/qpid/saslmech/cram_md5.py | 30 | ||||
-rw-r--r-- | python/qpid/saslmech/cram_md5_hex.py | 33 | ||||
-rw-r--r-- | python/qpid/saslmech/external.py | 23 | ||||
-rw-r--r-- | python/qpid/saslmech/finder.py | 63 | ||||
-rw-r--r-- | python/qpid/saslmech/plain.py | 31 | ||||
-rw-r--r-- | python/qpid/saslmech/sasl.py | 41 | ||||
-rw-r--r-- | python/qpid/saslmech/scram.py | 94 | ||||
-rw-r--r-- | python/qpid/saslmech/scram_sha_1.py | 27 | ||||
-rw-r--r-- | python/qpid/saslmech/scram_sha_256.py | 25 | ||||
-rw-r--r-- | python/qpid/testlib.py | 5 | ||||
-rw-r--r-- | python/qpid/tests/__init__.py | 1 | ||||
-rw-r--r-- | python/qpid/tests/saslmech/__init__.py | 19 | ||||
-rw-r--r-- | python/qpid/tests/saslmech/finder.py | 62 | ||||
-rw-r--r-- | python/qpid/tests/saslmech/my_sasl.py | 22 | ||||
-rw-r--r-- | python/qpid/tests/saslmech/my_sasl2.py | 25 | ||||
-rw-r--r-- | tests/src/py/qpid_tests/broker_0_9/echo.py | 2 |
20 files changed, 605 insertions, 7 deletions
diff --git a/python/qpid/client.py b/python/qpid/client.py index 9380594973..b0ce5d9009 100644 --- a/python/qpid/client.py +++ b/python/qpid/client.py @@ -30,6 +30,8 @@ from connection08 import Connection, Frame, connect from spec08 import load from queue import Queue from reference import ReferenceId, References +from saslmech.finder import get_sasl_mechanism +from saslmech.sasl import SaslException class Client: @@ -48,6 +50,7 @@ class Client: self.mechanism = None self.response = None self.locale = None + self.sasl = None self.vhost = vhost if self.vhost == None: @@ -77,12 +80,17 @@ class Client: self.lock.release() return q - def start(self, response, mechanism="AMQPLAIN", locale="en_US", tune_params=None, client_properties=None, connection_options=None): + def start(self, response=None, mechanism=None, locale="en_US", tune_params=None, + username=None, password=None, + client_properties=None, connection_options=None, sasl_options = None): self.mechanism = mechanism self.response = response + self.username = username + self.password = password self.locale = locale self.tune_params = tune_params self.client_properties=get_client_properties_with_defaults(provided_client_properties=client_properties) + self.sasl_options = sasl_options self.socket = connect(self.host, self.port, connection_options) self.conn = Connection(self.socket, self.spec) self.peer = Peer(self.conn, ClientDelegate(self), Session) @@ -127,11 +135,32 @@ class ClientDelegate(Delegate): self.client = client def connection_start(self, ch, msg): + + if self.client.mechanism is None: + if self.client.response is not None: + # Supports users passing the response argument alon + self.client.mechanism = "AMQPLAIN" + else: + supportedMechs = msg.frame.args[3].split() + + self.client.sasl = get_sasl_mechanism(supportedMechs, self.client.username, self.client.password, sasl_options=self.client.sasl_options) + + if self.client.sasl == None: + raise SaslException("sasl negotiation failed: no mechanism agreed. Server supports: %s " % supportedMechs) + + self.client.mechanism = self.client.sasl.mechanismName() + + if self.client.response is None: + self.client.response = self.client.sasl.initialResponse() + msg.start_ok(mechanism=self.client.mechanism, - response=self.client.response, + response=self.client.response or "", locale=self.client.locale, client_properties=self.client.client_properties) + def connection_secure(self, ch, msg): + msg.secure_ok(response=self.client.sasl.response(msg.challenge)) + def connection_tune(self, ch, msg): if self.client.tune_params: #todo: just override the params, i.e. don't require them diff --git a/python/qpid/saslmech/__init__.py b/python/qpid/saslmech/__init__.py new file mode 100644 index 0000000000..ebcab03ca2 --- /dev/null +++ b/python/qpid/saslmech/__init__.py @@ -0,0 +1,22 @@ +# +# 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. +# + +"""Pure python implementations of SASL mechanisms""" + + diff --git a/python/qpid/saslmech/amqplain.py b/python/qpid/saslmech/amqplain.py new file mode 100644 index 0000000000..d31997eefd --- /dev/null +++ b/python/qpid/saslmech/amqplain.py @@ -0,0 +1,31 @@ +# +# 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. +# + +from sasl import Sasl, SaslException + +class AMQPLAIN(Sasl): + + def initialResponse(self): + if (self.user is None or self.password is None): + raise SaslException("User and password must be specified") + + return {"LOGIN": self.user, "PASSWORD": self.password} + + def priority(self): + return 0
\ No newline at end of file diff --git a/python/qpid/saslmech/anonymous.py b/python/qpid/saslmech/anonymous.py new file mode 100644 index 0000000000..ea3e51dfba --- /dev/null +++ b/python/qpid/saslmech/anonymous.py @@ -0,0 +1,23 @@ +# +# 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. +# + +from sasl import Sasl + +class ANONYMOUS(Sasl): pass + diff --git a/python/qpid/saslmech/cram_md5.py b/python/qpid/saslmech/cram_md5.py new file mode 100644 index 0000000000..c2eb8078a0 --- /dev/null +++ b/python/qpid/saslmech/cram_md5.py @@ -0,0 +1,30 @@ +# +# 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. +# + +from sasl import Sasl, SaslException +from hmac import HMAC + +class CRAM_MD5(Sasl): + + def response(self, challenge): + if (self.user is None or self.password is None): + raise SaslException("User and password must be specified") + + digest = HMAC( self.password, challenge).hexdigest() + return "%s %s" % (self.user, digest) diff --git a/python/qpid/saslmech/cram_md5_hex.py b/python/qpid/saslmech/cram_md5_hex.py new file mode 100644 index 0000000000..39342060da --- /dev/null +++ b/python/qpid/saslmech/cram_md5_hex.py @@ -0,0 +1,33 @@ +# +# 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. +# + +from sasl import Sasl +from hmac import HMAC +from hashlib import md5 + +class CRAM_MD5_HEX(Sasl): + + def response(self, challenge): + if (self.user is None or self.password is None): + raise SaslException("User and password must be specified") + + m = md5() + m.update(self.password) + digest = HMAC( m.hexdigest(), challenge).hexdigest() + return "%s %s" % (self.user, digest) diff --git a/python/qpid/saslmech/external.py b/python/qpid/saslmech/external.py new file mode 100644 index 0000000000..7b701f539d --- /dev/null +++ b/python/qpid/saslmech/external.py @@ -0,0 +1,23 @@ +# +# 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. +# + +from sasl import Sasl + + +class EXTERNAL(Sasl): pass
\ No newline at end of file diff --git a/python/qpid/saslmech/finder.py b/python/qpid/saslmech/finder.py new file mode 100644 index 0000000000..15a8721f81 --- /dev/null +++ b/python/qpid/saslmech/finder.py @@ -0,0 +1,63 @@ +# +# 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. +# + + +from logging import getLogger + +log = getLogger("qpid.saslmech") + +def get_sasl_mechanism(mechanismNames, username, password, namespace="qpid.saslmech", sasl_options=None): + """Given a list of SASL mechanism names, dynamically loads a SASL implementation + from namespace qpid.sasl.mech respecting a mechanism priority""" + + log.debug("Supported mechanism : %s", mechanismNames) + + instances = [] + for mechanismName in mechanismNames: + convertedName = mechanismName.replace("-","_") + canonicalName = "%s.%s.%s" % (namespace, convertedName.lower(), convertedName) + try: + log.debug("Checking for SASL implementation %s for mechanism %s", canonicalName, mechanismName) + clazz = _get_class(canonicalName) + log.debug("Found SASL implementation") + instance = clazz(username, password, mechanismName, sasl_options) + instances.append(instance) + except (ImportError, AttributeError), e: + # Unknown mechanism - this is normal if the server supports m + log.debug("Could not load implementation for %s", canonicalName) + pass + + if instances: + instances.sort(key=lambda x : x.priority(), reverse=True) + sasl = instances[0] + log.debug("Selected SASL mechanism %s", sasl.mechanismName()) + return sasl + else: + return None + +def _get_class( kls ): + parts = kls.split('.') + module = ".".join(parts[:-1]) + m = __import__( module ) + for comp in parts[1:]: + m = getattr(m, comp) + return m + + + diff --git a/python/qpid/saslmech/plain.py b/python/qpid/saslmech/plain.py new file mode 100644 index 0000000000..aa883e5460 --- /dev/null +++ b/python/qpid/saslmech/plain.py @@ -0,0 +1,31 @@ +# +# 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. +# + +from sasl import Sasl, SaslException + +class PLAIN(Sasl): + + def initialResponse(self): + if (self.user is None or self.password is None): + raise SaslException("User and password must be specified") + + return "\x00" + self.user + "\x00" + self.password + + def priority(self): + return 0
\ No newline at end of file diff --git a/python/qpid/saslmech/sasl.py b/python/qpid/saslmech/sasl.py new file mode 100644 index 0000000000..f6b7f04fb0 --- /dev/null +++ b/python/qpid/saslmech/sasl.py @@ -0,0 +1,41 @@ +# +# 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. +# + +class SaslException(Exception): pass + +class Sasl: + + def __init__(self, user, password, name, sasl_options = None): + self.user = user + self.password = password + self.name = name + self.sasl_options = sasl_options + + def initialResponse(self): + return + + def response(self, challenge): + return + + def priority(self): + """Priority of the mechanism. Mechanism with a higher value will be chosen in preference to those with a lower priority""" + return 1 + + def mechanismName(self): + return self.name
\ No newline at end of file diff --git a/python/qpid/saslmech/scram.py b/python/qpid/saslmech/scram.py new file mode 100644 index 0000000000..d12f8c8fa5 --- /dev/null +++ b/python/qpid/saslmech/scram.py @@ -0,0 +1,94 @@ +# +# 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. +# + +from hmac import HMAC +from binascii import b2a_hex +from sasl import Sasl +import os +import base64 + +class SCRAM_base(Sasl): + + def __init__(self, algorithm, user, password, name, sasl_options=None): + Sasl.__init__(self, user, password, name, sasl_options) + self.algorithm = algorithm + self.client_nonce = b2a_hex(os.urandom(16)) + self.server_signature = None + + def initialResponse(self): + if (self.user is None or self.password is None): + raise SaslException("User and password must be specified") + + name = self.user.replace("=","=3D").replace(",","=2C") + self.client_first_message = "n=" + name + ",r=" + self.client_nonce + return "n,," + self.client_first_message + + + def response(self, challenge): + if(self.server_signature): + self.evaluateOutcome(challenge) + return "" + else: + serverChallenge, salt, iterations = challenge.split(",") + self.server_nonce = serverChallenge[2:] + if self.server_nonce.find(self.client_nonce) != 0: + raise SaslException("Server nonce does not start with client nonce") + self.salt = base64.b64decode(salt[2:]) + + iterations = int(iterations[2:]) + + hmac = HMAC(key=self.password.replace("=","=3D").replace(",","=2C"),digestmod=self.algorithm) + + hmac.update(self.salt) + hmac.update("\x00\x00\x00\x01") + + saltedPassword = hmac.digest() + previous = saltedPassword + + for i in range(1,iterations): + hmac = HMAC(key=self.password.replace("=","=3D").replace(",","=2C"),digestmod=self.algorithm) + hmac.update(previous) + previous = hmac.digest() + saltedPassword = ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(saltedPassword,previous)) + + clientFinalMessageWithoutProof = "c=" + base64.b64encode("n,,") + ",r=" + self.server_nonce + authMessage = self.client_first_message + "," + challenge + "," + clientFinalMessageWithoutProof + + clientKey = HMAC(key=saltedPassword,msg="Client Key",digestmod=self.algorithm).digest() + hashFunc = self.algorithm() + hashFunc.update(clientKey) + storedKey = hashFunc.digest() + + clientSignature = HMAC(key=storedKey, msg=authMessage, digestmod=self.algorithm).digest() + + clientProof = ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(clientKey,clientSignature)) + + serverKey = HMAC(key=saltedPassword,msg="Server Key",digestmod=self.algorithm).digest() + + self.server_signature = HMAC(key=serverKey,msg=authMessage,digestmod=self.algorithm).digest() + return clientFinalMessageWithoutProof + ",p=" + base64.b64encode(clientProof) + + + def evaluateOutcome(self, challenge): + serverVerification = challenge.split(",")[0] + + serverSignature = base64.b64decode(serverVerification[2:]) + if serverSignature != self.server_signature: + raise SaslException("Server verification failed") + return diff --git a/python/qpid/saslmech/scram_sha_1.py b/python/qpid/saslmech/scram_sha_1.py new file mode 100644 index 0000000000..83f58f8e76 --- /dev/null +++ b/python/qpid/saslmech/scram_sha_1.py @@ -0,0 +1,27 @@ +# +# 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. +# + +from scram import SCRAM_base +from hashlib import sha1 + +class SCRAM_SHA_1(SCRAM_base): + + def __init__(self, user, password, name, sasl_options=None): + SCRAM_base.__init__(self, sha1, user, password, name, sasl_options) + diff --git a/python/qpid/saslmech/scram_sha_256.py b/python/qpid/saslmech/scram_sha_256.py new file mode 100644 index 0000000000..cc894895c0 --- /dev/null +++ b/python/qpid/saslmech/scram_sha_256.py @@ -0,0 +1,25 @@ +# +# 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. +# + +from scram import SCRAM_base +import hashlib + +class SCRAM_SHA_256(SCRAM_base): + def __init__(self, user, password, name, sasl_options=None): + SCRAM_base.__init__(self,hashlib.sha256,user,password, name, sasl_options)
\ No newline at end of file diff --git a/python/qpid/testlib.py b/python/qpid/testlib.py index d2617c5d18..0103956ec4 100644 --- a/python/qpid/testlib.py +++ b/python/qpid/testlib.py @@ -84,10 +84,7 @@ class TestBase(unittest.TestCase): password = password or self.config.broker.password or "guest" client = qpid.client.Client(host, port) try: - if client.spec.major == 8 and client.spec.minor == 0: - client.start({"LOGIN": user, "PASSWORD": password}, tune_params=tune_params, client_properties=client_properties) - else: - client.start("\x00" + user + "\x00" + password, mechanism="PLAIN", tune_params=tune_params, client_properties=client_properties) + client.start(username = user, password=password, tune_params=tune_params, client_properties=client_properties) except qpid.client.Closed, e: if isinstance(e.args[0], VersionError): raise Skipped(e.args[0]) diff --git a/python/qpid/tests/__init__.py b/python/qpid/tests/__init__.py index dc9988515e..85ab013b5a 100644 --- a/python/qpid/tests/__init__.py +++ b/python/qpid/tests/__init__.py @@ -38,6 +38,7 @@ import qpid.tests.connection import qpid.tests.spec010 import qpid.tests.codec010 import qpid.tests.util +import qpid.tests.saslmech.finder class TestTestsXXX(Test): diff --git a/python/qpid/tests/saslmech/__init__.py b/python/qpid/tests/saslmech/__init__.py new file mode 100644 index 0000000000..d8a500d9d8 --- /dev/null +++ b/python/qpid/tests/saslmech/__init__.py @@ -0,0 +1,19 @@ +# +# 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. +# + diff --git a/python/qpid/tests/saslmech/finder.py b/python/qpid/tests/saslmech/finder.py new file mode 100644 index 0000000000..d9092654bc --- /dev/null +++ b/python/qpid/tests/saslmech/finder.py @@ -0,0 +1,62 @@ +# +# 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. +# + +from unittest import TestCase +from qpid.saslmech.finder import get_sasl_mechanism +from my_sasl import MY_SASL +from my_sasl2 import MY_SASL2 + +class SaslFinderTests (TestCase): + """Tests the ability to chose the a sasl mechanism from those available to be loaded""" + + def test_known_mechansim(self): + + supportedMechs = ["MY-SASL"] + + mech = get_sasl_mechanism(supportedMechs, "myuser", "mypass", namespace="qpid.tests.saslmech") + + self.assertTrue(isinstance(mech, MY_SASL), "Mechanism %s is of unexpected type" % mech) + self.assertEquals("MY-SASL", mech.mechanismName()) + self.assertTrue(mech.sasl_options is None) + + def test_unknown_mechansim(self): + + supportedMechs = ["not_a_mech"] + + mech = get_sasl_mechanism(supportedMechs, "myuser", "mypass", namespace="qpid.tests.saslmech") + + self.assertTrue(mech == None, "Mechanism instance should be none") + + def test_sasl_mechanism_with_higher_priority_prefered(self): + + supportedMechs = ["MY-SASL", "MY-SASL2"] + + mech = get_sasl_mechanism(supportedMechs, "myuser", "mypass", namespace="qpid.tests.saslmech") + + self.assertTrue(isinstance(mech, MY_SASL), "Mechanism %s is of unexpected type" % mech) + + def test_sasl_mechansim_options(self): + + supportedMechs = ["MY-SASL"] + + sasl_options = {'hello': 'world'} + mech = get_sasl_mechanism(supportedMechs, "myuser", "mypass", namespace="qpid.tests.saslmech", sasl_options=sasl_options) + + self.assertTrue(isinstance(mech, MY_SASL), "Mechanism %s is of unexpected type" % mech) + self.assertEquals(sasl_options, mech.sasl_options) diff --git a/python/qpid/tests/saslmech/my_sasl.py b/python/qpid/tests/saslmech/my_sasl.py new file mode 100644 index 0000000000..c15fe4451c --- /dev/null +++ b/python/qpid/tests/saslmech/my_sasl.py @@ -0,0 +1,22 @@ +# +# 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. +# + +from qpid.saslmech.sasl import Sasl + +class MY_SASL(Sasl): pass diff --git a/python/qpid/tests/saslmech/my_sasl2.py b/python/qpid/tests/saslmech/my_sasl2.py new file mode 100644 index 0000000000..f481a615e7 --- /dev/null +++ b/python/qpid/tests/saslmech/my_sasl2.py @@ -0,0 +1,25 @@ +# +# 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. +# + +from qpid.saslmech.sasl import Sasl + +class MY_SASL2(Sasl): + + def priority(self): + return 0
\ No newline at end of file diff --git a/tests/src/py/qpid_tests/broker_0_9/echo.py b/tests/src/py/qpid_tests/broker_0_9/echo.py index 13305cbbf6..a883568e35 100644 --- a/tests/src/py/qpid_tests/broker_0_9/echo.py +++ b/tests/src/py/qpid_tests/broker_0_9/echo.py @@ -90,7 +90,7 @@ class EchoTests(TestBase): # the content in order to send it to us. consuming_client = qpid.client.Client(self.config.broker.host, self.config.broker.port) tune_params = { "channel_max" : 256, "frame_max" : 4096 } - consuming_client.start("\x00" + self.config.broker.user + "\x00" + self.config.broker.password, mechanism="PLAIN", tune_params = tune_params) + consuming_client.start(username = self.config.broker.user, password = self.config.broker.password, tune_params = tune_params) consuming_channel = consuming_client.channel(1) consuming_channel.channel_open() |