| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- """SASL mechanisms for AMQP authentication."""
- from __future__ import absolute_import, unicode_literals
- import socket
- import warnings
- from io import BytesIO
- from amqp.serialization import _write_table
- class SASL(object):
- """The base class for all amqp SASL authentication mechanisms.
- You should sub-class this if you're implementing your own authentication.
- """
- @property
- def mechanism(self):
- """Return a bytes containing the SASL mechanism name."""
- raise NotImplementedError
- def start(self, connection):
- """Return the first response to a SASL challenge as a bytes object."""
- raise NotImplementedError
- class PLAIN(SASL):
- """PLAIN SASL authentication mechanism.
- See https://tools.ietf.org/html/rfc4616 for details
- """
- mechanism = b'PLAIN'
- def __init__(self, username, password):
- self.username, self.password = username, password
- def start(self, connection):
- if self.username is None or self.password is None:
- return NotImplemented
- login_response = BytesIO()
- login_response.write(b'\0')
- login_response.write(self.username.encode('utf-8'))
- login_response.write(b'\0')
- login_response.write(self.password.encode('utf-8'))
- return login_response.getvalue()
- class AMQPLAIN(SASL):
- """AMQPLAIN SASL authentication mechanism.
- This is a non-standard mechanism used by AMQP servers.
- """
- mechanism = b'AMQPLAIN'
- def __init__(self, username, password):
- self.username, self.password = username, password
- def start(self, connection):
- if self.username is None or self.password is None:
- return NotImplemented
- login_response = BytesIO()
- _write_table({b'LOGIN': self.username, b'PASSWORD': self.password},
- login_response.write, [])
- # Skip the length at the beginning
- return login_response.getvalue()[4:]
- def _get_gssapi_mechanism():
- try:
- import gssapi
- import gssapi.raw.misc # Fail if the old python-gssapi is installed
- except ImportError:
- class FakeGSSAPI(SASL):
- """A no-op SASL mechanism for when gssapi isn't available."""
- mechanism = None
- def __init__(self, client_name=None, service=b'amqp',
- rdns=False, fail_soft=False):
- if not fail_soft:
- raise NotImplementedError(
- "You need to install the `gssapi` module for GSSAPI "
- "SASL support")
- def start(self): # pragma: no cover
- return NotImplemented
- return FakeGSSAPI
- else:
- class GSSAPI(SASL):
- """GSSAPI SASL authentication mechanism.
- See https://tools.ietf.org/html/rfc4752 for details
- """
- mechanism = b'GSSAPI'
- def __init__(self, client_name=None, service=b'amqp',
- rdns=False, fail_soft=False):
- if client_name and not isinstance(client_name, bytes):
- client_name = client_name.encode('ascii')
- self.client_name = client_name
- self.fail_soft = fail_soft
- self.service = service
- self.rdns = rdns
- def get_hostname(self, connection):
- sock = connection.transport.sock
- if self.rdns and sock.family in (socket.AF_INET,
- socket.AF_INET6):
- peer = sock.getpeername()
- hostname, _, _ = socket.gethostbyaddr(peer[0])
- else:
- hostname = connection.transport.host
- if not isinstance(hostname, bytes):
- hostname = hostname.encode('ascii')
- return hostname
- def start(self, connection):
- try:
- if self.client_name:
- creds = gssapi.Credentials(
- name=gssapi.Name(self.client_name))
- else:
- creds = None
- hostname = self.get_hostname(connection)
- name = gssapi.Name(b'@'.join([self.service, hostname]),
- gssapi.NameType.hostbased_service)
- context = gssapi.SecurityContext(name=name, creds=creds)
- return context.step(None)
- except gssapi.raw.misc.GSSError:
- if self.fail_soft:
- return NotImplemented
- else:
- raise
- return GSSAPI
- GSSAPI = _get_gssapi_mechanism()
- class EXTERNAL(SASL):
- """EXTERNAL SASL mechanism.
- Enables external authentication, i.e. not handled through this protocol.
- Only passes 'EXTERNAL' as authentication mechanism, but no further
- authentication data.
- """
- mechanism = b'EXTERNAL'
- def start(self, connection):
- return b''
- class RAW(SASL):
- """A generic custom SASL mechanism.
- This mechanism takes a mechanism name and response to send to the server,
- so can be used for simple custom authentication schemes.
- """
- mechanism = None
- def __init__(self, mechanism, response):
- assert isinstance(mechanism, bytes)
- assert isinstance(response, bytes)
- self.mechanism, self.response = mechanism, response
- warnings.warn("Passing login_method and login_response to Connection "
- "is deprecated. Please implement a SASL subclass "
- "instead.", DeprecationWarning)
- def start(self, connection):
- return self.response
|