From c09e94c0d17bc5a8035100dbbc2cfaf856bac991 Mon Sep 17 00:00:00 2001 From: Alexander Scheel Date: Wed, 26 Jul 2017 08:24:09 -0400 Subject: [PATCH 1/3] OIDs: Copy constructor take ownership When constructing via a the cpy parameter, cpy would maintain ownership; if cpy._free_on_dealloc was set to true, the new object could have memory freed while still maintaining a reference to it. This fixes the issue by having the new object take ownership. To perform a memory copy, use the elements argument to the constructor. Signed-off-by: Alexander Scheel --- gssapi/raw/oids.pyx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gssapi/raw/oids.pyx b/gssapi/raw/oids.pyx index a5340609..a2afe36d 100644 --- a/gssapi/raw/oids.pyx +++ b/gssapi/raw/oids.pyx @@ -32,11 +32,20 @@ cdef class OID: # cdef bint _free_on_dealloc = NULL def __cinit__(OID self, OID cpy=None, elements=None): + """ + Note: cpy is named such for historical reasons. To perform a deep + copy, specify the elements parameter; this will copy the value of the + OID. To perform a shallow copy and take ownership of an existing OID, + use the cpy (default) argument. + """ if cpy is not None and elements is not None: raise TypeError("Cannot instantiate a OID from both a copy and " " a new set of elements") if cpy is not None: self.raw_oid = cpy.raw_oid + # take ownership of this OID (for dynamic cases) + self._free_on_dealloc = cpy._free_on_dealloc + cpy._free_on_dealloc = False if elements is None: self._free_on_dealloc = False From e0b80848292eb3f38b19b524990f75a29551d352 Mon Sep 17 00:00:00 2001 From: Alexander Scheel Date: Thu, 27 Jul 2017 11:55:24 -0400 Subject: [PATCH 2/3] OIDs: Add dotted_form properties This introduces a new property, dotted_form, for querying the dotted form of the OID. Signed-off-by: Alexander Scheel --- gssapi/raw/oids.pyx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/gssapi/raw/oids.pyx b/gssapi/raw/oids.pyx index a2afe36d..492ca9ea 100644 --- a/gssapi/raw/oids.pyx +++ b/gssapi/raw/oids.pyx @@ -156,9 +156,12 @@ cdef class OID: pos += 1 return decoded + @property + def dotted_form(self): + return '.'.join(str(x) for x in self._decode_asn1ber()) + def __repr__(self): - dotted_oid = '.'.join(str(x) for x in self._decode_asn1ber()) - return "".format(dotted_oid) + return "".format(self.dotted_form) def __hash__(self): return hash(self.__bytes__()) From 535e67aa3e374ba829f676c48f83b4377441b6dc Mon Sep 17 00:00:00 2001 From: Alexander Scheel Date: Mon, 24 Jul 2017 15:52:13 -0400 Subject: [PATCH 3/3] Expose mechanisms in the high-level API This creates a new class, Mechanism, for inquiring information about a mechanism. This includes support for RFCs 5587 and 5801. As Mechanism derives from OID, it is compatible with all places that accept a mech by OID. Signed-off-by: Alexander Scheel Signed-off-by: Alexander Scheel --- gssapi/__init__.py | 1 + gssapi/mechs.py | 199 ++++++++++++++++++++++++++++++++ gssapi/tests/test_high_level.py | 56 +++++++++ 3 files changed, 256 insertions(+) create mode 100644 gssapi/mechs.py diff --git a/gssapi/__init__.py b/gssapi/__init__.py index 9c894529..4d6eba43 100644 --- a/gssapi/__init__.py +++ b/gssapi/__init__.py @@ -33,5 +33,6 @@ from gssapi.creds import Credentials # noqa from gssapi.names import Name # noqa from gssapi.sec_contexts import SecurityContext # noqa +from gssapi.mechs import Mechanism # noqa from gssapi._utils import set_encoding # noqa diff --git a/gssapi/mechs.py b/gssapi/mechs.py new file mode 100644 index 00000000..4944572a --- /dev/null +++ b/gssapi/mechs.py @@ -0,0 +1,199 @@ +import six + +from gssapi.raw import oids as roids +from gssapi._utils import import_gssapi_extension +from gssapi.raw import misc as rmisc +from gssapi import _utils + +rfc5587 = import_gssapi_extension('rfc5587') +rfc5801 = import_gssapi_extension('rfc5801') + + +class Mechanism(roids.OID): + """ + A GSSAPI Mechanism + + This class represents a mechanism and centralizes functions dealing with + mechanisms and can be used with any calls. + + It inherits from the low-level GSSAPI :class:`~gssapi.raw.oids.OID` class, + and thus can be used with both low-level and high-level API calls. + """ + def __new__(cls, cpy=None, elements=None): + return super(Mechanism, cls).__new__(cls, cpy, elements) + + @property + def name_types(self): + """ + Get the set of name types supported by this mechanism. + """ + return rmisc.inquire_names_for_mech(self) + + @property + def _saslname(self): + if rfc5801 is None: + raise NotImplementedError("Your GSSAPI implementation does not " + "have support for RFC 5801") + return rfc5801.inquire_saslname_for_mech(self) + + @property + def _attrs(self): + if rfc5587 is None: + raise NotImplementedError("Your GSSAPI implementation does not " + "have support for RFC 5587") + + return rfc5587.inquire_attrs_for_mech(self) + + def __str__(self): + if issubclass(str, six.text_type): + # Python 3 -- we should return unicode + return self._bytes_desc().decode(_utils._get_encoding()) + else: + return self._bytes_desc() + + def __unicode__(self): + return self._bytes_desc().decode(_utils._get_encoding()) + + def _bytes_desc(self): + base = self.dotted_form + if rfc5801 is not None: + base = self._saslname.mech_name + + if isinstance(base, six.text_type): + base = base.encode(_utils._get_encoding()) + + return base + + def __repr__(self): + """ + Get a name representing the mechanism; always safe to call + """ + base = "" % self.dotted_form + if rfc5801 is not None: + base = "" % ( + self._saslname.mech_name.decode('UTF-8'), + self.dotted_form + ) + + return base + + @property + def sasl_name(self): + """ + Get the SASL name for the mechanism + + :requires-ext:`rfc5801` + """ + return self._saslname.sasl_mech_name.decode('UTF-8') + + @property + def description(self): + """ + Get the description of the mechanism + + :requires-ext:`rfc5801` + """ + return self._saslname.mech_description.decode('UTF-8') + + @property + def known_attrs(self): + """ + Get the known attributes of the mechanism; returns a set of OIDs + ([OID]) + + :requires-ext:`rfc5587` + """ + return self._attrs.known_mech_attrs + + @property + def attrs(self): + """ + Get the attributes of the mechanism; returns a set of OIDs ([OID]) + + :requires-ext:`rfc5587` + """ + return self._attrs.mech_attrs + + @classmethod + def all_mechs(cls): + """ + Get a generator of all mechanisms supported by GSSAPI + """ + return (cls(mech) for mech in rmisc.indicate_mechs()) + + @classmethod + def from_name(cls, name=None): + """ + Get a generator of mechanisms that may be able to process the name + + Args: + name (Name): a name to inquire about + + Returns: + [Mechanism]: a set of mechanisms which support this name + + Raises: + GSSError + """ + return (cls(mech) for mech in rmisc.inquire_mechs_for_name(name)) + + @classmethod + def from_sasl_name(cls, name=None): + """ + Create a Mechanism from its SASL name + + Args: + name (str): SASL name of the desired mechanism + + Returns: + Mechanism: the desired mechanism + + Raises: + GSSError + + :requires-ext:`rfc5801` + """ + if rfc5801 is None: + raise NotImplementedError("Your GSSAPI implementation does not " + "have support for RFC 5801") + if isinstance(name, six.text_type): + name = name.encode(_utils._get_encoding()) + + m = rfc5801.inquire_mech_for_saslname(name) + + return cls(m) + + @classmethod + def from_attrs(cls, m_desired=None, m_except=None, m_critical=None): + """ + Get a generator of mechanisms supporting the specified attributes. See + RFC 5587's :func:`indicate_mechs_by_attrs` for more information. + + Args: + m_desired ([OID]): Desired attributes + m_except ([OID]): Except attributes + m_critical ([OID]): Critical attributes + + Returns: + [Mechanism]: A set of mechanisms having the desired features. + + Raises: + GSSError + + :requires-ext:`rfc5587` + """ + if isinstance(m_desired, roids.OID): + m_desired = set([m_desired]) + if isinstance(m_except, roids.OID): + m_except = set([m_except]) + if isinstance(m_critical, roids.OID): + m_critical = set([m_critical]) + + if rfc5587 is None: + raise NotImplementedError("Your GSSAPI implementation does not " + "have support for RFC 5587") + + mechs = rfc5587.indicate_mechs_by_attrs(m_desired, + m_except, + m_critical) + return (cls(mech) for mech in mechs) diff --git a/gssapi/tests/test_high_level.py b/gssapi/tests/test_high_level.py index ff5690f7..fa9e95fb 100644 --- a/gssapi/tests/test_high_level.py +++ b/gssapi/tests/test_high_level.py @@ -9,6 +9,7 @@ from nose_parameterized import parameterized from gssapi import creds as gsscreds +from gssapi import mechs as gssmechs from gssapi import names as gssnames from gssapi import sec_contexts as gssctx from gssapi import raw as gb @@ -369,6 +370,61 @@ def test_add_with_impersonate(self): new_creds.should_be_a(gsscreds.Credentials) +class MechsTestCase(_GSSAPIKerberosTestCase): + def test_indicate_mechs(self): + mechs = gssmechs.Mechanism.all_mechs() + for mech in mechs: + s = str(mech) + s.shouldnt_be_empty() + + @ktu.gssapi_extension_test('rfc5801', 'RFC 5801: SASL Names') + def test_sasl_properties(self): + mechs = gssmechs.Mechanism.all_mechs() + encoding = gssutils._get_encoding() + for mech in mechs: + s = str(mech) + s.shouldnt_be_empty() + s.should_be_a(str) + s.should_be(mech._saslname.mech_name.decode(encoding)) + + mech.sasl_name.shouldnt_be_empty() + mech.sasl_name.should_be_a(six.text_type) + + mech.description.shouldnt_be_empty() + mech.description.should_be_a(six.text_type) + + cmp_mech = gssmechs.Mechanism.from_sasl_name(mech.sasl_name) + str(cmp_mech).should_be(str(mech)) + + @ktu.gssapi_extension_test('rfc5587', 'RFC 5587: Mech Inquiry') + def test_mech_inquiry(self): + mechs = list(gssmechs.Mechanism.all_mechs()) + c = len(mechs) + for mech in mechs: + attrs = mech.attrs + known_attrs = mech.known_attrs + + for attr in attrs: + from_desired = gssmechs.Mechanism.from_attrs(m_desired=[attr]) + from_except = gssmechs.Mechanism.from_attrs(m_except=[attr]) + + from_desired = list(from_desired) + from_except = list(from_except) + + (len(from_desired) + len(from_except)).should_be(c) + from_desired.should_include(mech) + from_except.shouldnt_include(mech) + + for attr in known_attrs: + from_desired = gssmechs.Mechanism.from_attrs(m_desired=[attr]) + from_except = gssmechs.Mechanism.from_attrs(m_except=[attr]) + + from_desired = list(from_desired) + from_except = list(from_except) + + (len(from_desired) + len(from_except)).should_be(c) + + class NamesTestCase(_GSSAPIKerberosTestCase): def test_create_from_other(self): raw_name = gb.import_name(SERVICE_PRINCIPAL)