Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose mechanisms in the high-level API #126

Merged
merged 3 commits into from
Aug 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gssapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
199 changes: 199 additions & 0 deletions gssapi/mechs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import six

from gssapi.raw import oids as roids
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💪

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 = "<Mechanism (%s)>" % self.dotted_form
if rfc5801 is not None:
base = "<Mechanism %s (%s)>" % (
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])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this open as I'm not sure if this is the desired format.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per IRC discussion with @DirectXMan12 this appears to be in the correct format.

: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)
16 changes: 14 additions & 2 deletions gssapi/raw/oids.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -147,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 "<OID {0}>".format(dotted_oid)
return "<OID {0}>".format(self.dotted_form)

def __hash__(self):
return hash(self.__bytes__())
Expand Down
56 changes: 56 additions & 0 deletions gssapi/tests/test_high_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down