Skip to content
Open
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
7 changes: 7 additions & 0 deletions gssapi/raw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,10 @@
from gssapi.raw.ext_set_cred_opt import * # noqa
except ImportError:
pass

# optional localname support
try:
from gssapi.raw.ext_localname import * # noqa
from gssapi.raw.ext_localname_attr import * # noqa
except ImportError:
pass
93 changes: 93 additions & 0 deletions gssapi/raw/ext_localname.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import typing as t

if t.TYPE_CHECKING:
from gssapi.raw.names import Name
from gssapi.raw.oids import OID


def localname(
name: "Name",
mech: t.Optional["OID"] = None,
) -> bytes:
"""Get the local name for a GSSAPI name.

This method determines the local name associated with a GSSAPI
name, optionally for a given mechanism.

Args:
name (Name): the GSSAPI name to map to a local name
mech (~gssapi.OID): the mechanism to use for the mapping
(or None for the default)

Returns:
bytes: the local name

Raises:
~gssapi.exceptions.GSSError
"""


def userok(
name: "Name",
username: t.Union[bytes, str],
) -> bool:
"""Determine whether a GSSAPI name is authorized to act as a local user.

This method determines whether a given GSSAPI name is authorized
to act as the given local username. This is a simple wrapper
around :func:`authorize_localname` that only supports system
usernames as local names.

Args:
name (Name): the GSSAPI name to check
username (Union[bytes, str]): the local username to check against

Returns:
bool: whether or not the name is authorized to act as the user
"""


def authorize_localname(
name: "Name",
user: "Name",
) -> bool:
"""Determine whether a GSSAPI name is authorized to act as a local name.

This method determines whether a given GSSAPI name is authorized
to act as the given local name.

Args:
name (Name): the mechanism name to check
user (Name): the local name to check against

Returns:
bool: whether or not the name is authorized

Raises:
~gssapi.exceptions.GSSError
"""


def pname_to_uid(
name: "Name",
mech: t.Optional["OID"] = None,
) -> int:
"""Get the local UID for a GSSAPI name.

This method determines the local UID associated with a GSSAPI
name, optionally for a given mechanism.

Note:
This function is not available on Windows.

Args:
name (Name): the GSSAPI name to map to a local UID
mech (~gssapi.OID): the mechanism to use for the mapping
(or None for the default)

Returns:
int: the local UID

Raises:
~gssapi.exceptions.GSSError
"""
156 changes: 156 additions & 0 deletions gssapi/raw/ext_localname.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
GSSAPI="BASE" # This ensures that a full module is generated by Cython

from gssapi.raw.cython_types cimport *
from gssapi.raw.names cimport Name
from gssapi.raw.oids cimport OID

from gssapi.raw.misc import GSSError
from gssapi import _utils

from posix.types cimport uid_t

cdef extern from "python_gssapi_ext.h":
OM_uint32 gss_localname(OM_uint32 *minor,
const gss_name_t name,
const gss_OID mech_type,
gss_buffer_t localname) nogil

int gss_userok(const gss_name_t name,
const char *username) nogil

OM_uint32 gss_authorize_localname(OM_uint32 *minor,
const gss_name_t name,
const gss_name_t user) nogil

OM_uint32 gss_pname_to_uid(OM_uint32 *minor,
const gss_name_t name,
const gss_OID mech_type,
uid_t *uid_out) nogil


def localname(Name name not None, OID mech=None):
"""Get the local name for a GSSAPI name.

This method determines the local name associated with a GSSAPI
name, optionally for a given mechanism.

Args:
name (Name): the GSSAPI name to map to a local name
mech (~gssapi.OID): the mechanism to use for the mapping
(or None for the default)

Returns:
bytes: the local name

Raises:
~gssapi.exceptions.GSSError
"""
cdef gss_OID m = GSS_C_NO_OID
if mech is not None:
m = &mech.raw_oid

cdef gss_buffer_desc output = gss_buffer_desc(0, NULL)

cdef OM_uint32 maj_stat, min_stat

with nogil:
maj_stat = gss_localname(&min_stat, name.raw_name, m, &output)

if maj_stat == GSS_S_COMPLETE:
py_output = (<char*>output.value)[:output.length]
gss_release_buffer(&min_stat, &output)
return py_output
else:
raise GSSError(maj_stat, min_stat)


def userok(Name name not None, username not None):
"""Determine whether a GSSAPI name is authorized to act as a local user.

This method determines whether a given GSSAPI name is authorized
to act as the given local username. This is a simple wrapper
around :func:`authorize_localname` that only supports system
usernames as local names.

Args:
name (Name): the GSSAPI name to check
username (Union[bytes, str]): the local username to check against

Returns:
bool: whether or not the name is authorized to act as the user
"""
cdef int res

if isinstance(username, str):
username = username.encode(_utils._get_encoding())

cdef char *c_username = username

with nogil:
res = gss_userok(name.raw_name, c_username)

return res == 1


def authorize_localname(Name name not None, Name user not None):
"""Determine whether a GSSAPI name is authorized to act as a local name.

This method determines whether a given GSSAPI name is authorized
to act as the given local name.

Args:
name (Name): the mechanism name to check
user (Name): the local name to check against

Returns:
bool: whether or not the name is authorized

Raises:
~gssapi.exceptions.GSSError
"""
cdef OM_uint32 maj_stat, min_stat

with nogil:
maj_stat = gss_authorize_localname(&min_stat, name.raw_name,
user.raw_name)

if maj_stat == GSS_S_COMPLETE:
return True
else:
raise GSSError(maj_stat, min_stat)


def pname_to_uid(Name name not None, OID mech=None):
"""Get the local UID for a GSSAPI name.

This method determines the local UID associated with a GSSAPI
name, optionally for a given mechanism.

Note:
This function is not available on Windows.

Args:
name (Name): the GSSAPI name to map to a local UID
mech (~gssapi.OID): the mechanism to use for the mapping
(or None for the default)

Returns:
int: the local UID

Raises:
~gssapi.exceptions.GSSError
"""
cdef gss_OID m = GSS_C_NO_OID
if mech is not None:
m = &mech.raw_oid

cdef uid_t uid_out
cdef OM_uint32 maj_stat, min_stat

with nogil:
maj_stat = gss_pname_to_uid(&min_stat, name.raw_name, m, &uid_out)

if maj_stat == GSS_S_COMPLETE:
return uid_out
else:
raise GSSError(maj_stat, min_stat)
7 changes: 7 additions & 0 deletions gssapi/raw/ext_localname_attr.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ATTR_LOCAL_LOGIN_USER: bytes
"""The attribute name for the local login username.

This can be used with RFC 6680 :func:`~gssapi.raw.ext_rfc6680.get_name_attribute`
and :func:`~gssapi.raw.ext_rfc6680.set_name_attribute` to retrieve or set the
local login username for a GSSAPI name.
"""
13 changes: 13 additions & 0 deletions gssapi/raw/ext_localname_attr.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
GSSAPI="BASE" # This ensures that a full module is generated by Cython

from gssapi.raw.cython_types cimport gss_buffer_t

cdef extern from "python_gssapi_ext.h":
gss_buffer_t GSS_C_ATTR_LOCAL_LOGIN_USER


# Export the attribute name constant as a Python bytes object.
# This can be used with RFC 6680 get_name_attribute/set_name_attribute
# to retrieve or set the local login username for a GSSAPI name.
ATTR_LOCAL_LOGIN_USER = (<char*>GSS_C_ATTR_LOCAL_LOGIN_USER.value)[
:GSS_C_ATTR_LOCAL_LOGIN_USER.length]
77 changes: 77 additions & 0 deletions gssapi/tests/test_raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,83 @@ def test_krb5_set_allowable_enctypes(self):
self.assertEqual(acceptor_info.cfx_kd.ctx_key_type,
acceptor_info.cfx_kd.acceptor_subkey_type)

@ktu.gssapi_extension_test('localname', 'Local Name')
def test_localname(self):
base_name = gb.import_name(self.USER_PRINC,
gb.NameType.kerberos_principal)
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)

local = gb.localname(canon_name, gb.MechType.kerberos)
self.assertIsInstance(local, bytes)
self.assertGreater(len(local), 0)

@ktu.gssapi_extension_test('localname', 'Local Name')
def test_localname_no_mech(self):
base_name = gb.import_name(self.USER_PRINC,
gb.NameType.kerberos_principal)
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)

local = gb.localname(canon_name)
self.assertIsInstance(local, bytes)
self.assertGreater(len(local), 0)

@ktu.gssapi_extension_test('localname', 'Local Name')
def test_userok(self):
base_name = gb.import_name(self.USER_PRINC,
gb.NameType.kerberos_principal)
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)

local = gb.localname(canon_name, gb.MechType.kerberos)
# The user should be authorized as their own local name
self.assertTrue(gb.userok(canon_name, local))

# A made-up username should not be authorized
self.assertFalse(gb.userok(canon_name, b'not_a_real_user_name'))

@ktu.gssapi_extension_test('localname', 'Local Name')
def test_userok_str(self):
base_name = gb.import_name(self.USER_PRINC,
gb.NameType.kerberos_principal)
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)

local = gb.localname(canon_name, gb.MechType.kerberos)
# userok should also accept str input
self.assertTrue(gb.userok(canon_name, local.decode('UTF-8')))

# A made-up str username should not be authorized
self.assertFalse(gb.userok(canon_name, 'not_a_real_user_name'))

@ktu.gssapi_extension_test('localname', 'Local Name')
def test_authorize_localname(self):
base_name = gb.import_name(self.USER_PRINC,
gb.NameType.kerberos_principal)
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)

local = gb.localname(canon_name, gb.MechType.kerberos)
local_name = gb.import_name(local, gb.NameType.user)
self.assertTrue(gb.authorize_localname(canon_name, local_name))

@ktu.gssapi_extension_test('localname', 'Local Name')
def test_authorize_localname_fails(self):
base_name = gb.import_name(self.USER_PRINC,
gb.NameType.kerberos_principal)
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)

fake_local_name = gb.import_name(b'not_a_real_user_name',
gb.NameType.user)
self.assertRaises(gb.GSSError, gb.authorize_localname,
canon_name, fake_local_name)

@ktu.gssapi_extension_test('localname', 'Local Name')
def test_pname_to_uid(self):
base_name = gb.import_name(self.USER_PRINC,
gb.NameType.kerberos_principal)
canon_name = gb.canonicalize_name(base_name, gb.MechType.kerberos)

uid = gb.pname_to_uid(canon_name, gb.MechType.kerberos)
self.assertIsInstance(uid, int)
self.assertGreaterEqual(uid, 0)


class TestIntEnumFlagSet(unittest.TestCase):
def test_create_from_int(self):
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,9 @@ def gssapi_modules(lst):
extension_file('password_add', 'gss_add_cred_with_password'),

extension_file('krb5', 'gss_krb5_ccache_name'),

extension_file('localname', 'gss_localname'),
extension_file('localname_attr', 'GSS_C_ATTR_LOCAL_LOGIN_USER'),
]),
options=setup_options,
keywords=['gssapi', 'security'],
Expand Down