diff --git a/configure.ac b/configure.ac index 4443904c65ab5850953040e744ccd8c61a841fa5..b60a036f6d77bc8452c506d0f4a340d75ad3953d 100644 --- a/configure.ac +++ b/configure.ac @@ -84,13 +84,11 @@ if test "$build_pam_sso" != "no" ; then fi AM_CONDITIONAL(ENABLE_PAM_SSO, [ test "$build_pam_sso" != "no" ]) -dnl SWIG -dnl AC_PROG_SWIG(1.3.17) -AX_PKG_SWIG(1.3.17) -AX_SWIG_ENABLE_CXX -AX_SWIG_MULTI_MODULE_SUPPORT -AX_SWIG_PYTHON -AM_CONDITIONAL(SWIG, [ test -n "$SWIG" && $SWIG -version >/dev/null 2>&1 ]) +dnl Python-dev (actually only used for $PYTHON) +AX_PYTHON_DEVEL + +dnl nosetests +AC_PATH_PROG([NOSETESTS], [nosetests]) dnl GoogleTest (use the embedded version) GTEST_LIBS="\$(top_builddir)/lib/gtest/libgtest.la" diff --git a/m4/ax_pkg_swig.m4 b/m4/ax_pkg_swig.m4 deleted file mode 100644 index e112f3d3fa5d1a2c34315249270863a9fdd0c7f9..0000000000000000000000000000000000000000 --- a/m4/ax_pkg_swig.m4 +++ /dev/null @@ -1,135 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_pkg_swig.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_PKG_SWIG([major.minor.micro], [action-if-found], [action-if-not-found]) -# -# DESCRIPTION -# -# This macro searches for a SWIG installation on your system. If found, -# then SWIG is AC_SUBST'd; if not found, then $SWIG is empty. If SWIG is -# found, then SWIG_LIB is set to the SWIG library path, and AC_SUBST'd. -# -# You can use the optional first argument to check if the version of the -# available SWIG is greater than or equal to the value of the argument. It -# should have the format: N[.N[.N]] (N is a number between 0 and 999. Only -# the first N is mandatory.) If the version argument is given (e.g. -# 1.3.17), AX_PKG_SWIG checks that the swig package is this version number -# or higher. -# -# As usual, action-if-found is executed if SWIG is found, otherwise -# action-if-not-found is executed. -# -# In configure.in, use as: -# -# AX_PKG_SWIG(1.3.17, [], [ AC_MSG_ERROR([SWIG is required to build..]) ]) -# AX_SWIG_ENABLE_CXX -# AX_SWIG_MULTI_MODULE_SUPPORT -# AX_SWIG_PYTHON -# -# LICENSE -# -# Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de> -# Copyright (c) 2008 Alan W. Irwin <irwin@beluga.phys.uvic.ca> -# Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net> -# Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za> -# Copyright (c) 2011 Murray Cumming <murrayc@openismus.com> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation; either version 2 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -#serial 8 - -AC_DEFUN([AX_PKG_SWIG],[ - # Ubuntu has swig 2.0 as /usr/bin/swig2.0 - AC_PATH_PROGS([SWIG],[swig swig2.0]) - if test -z "$SWIG" ; then - m4_ifval([$3],[$3],[:]) - elif test -n "$1" ; then - AC_MSG_CHECKING([SWIG version]) - [swig_version=`$SWIG -version 2>&1 | grep 'SWIG Version' | sed 's/.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/g'`] - AC_MSG_RESULT([$swig_version]) - if test -n "$swig_version" ; then - # Calculate the required version number components - [required=$1] - [required_major=`echo $required | sed 's/[^0-9].*//'`] - if test -z "$required_major" ; then - [required_major=0] - fi - [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] - [required_minor=`echo $required | sed 's/[^0-9].*//'`] - if test -z "$required_minor" ; then - [required_minor=0] - fi - [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] - [required_patch=`echo $required | sed 's/[^0-9].*//'`] - if test -z "$required_patch" ; then - [required_patch=0] - fi - # Calculate the available version number components - [available=$swig_version] - [available_major=`echo $available | sed 's/[^0-9].*//'`] - if test -z "$available_major" ; then - [available_major=0] - fi - [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] - [available_minor=`echo $available | sed 's/[^0-9].*//'`] - if test -z "$available_minor" ; then - [available_minor=0] - fi - [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] - [available_patch=`echo $available | sed 's/[^0-9].*//'`] - if test -z "$available_patch" ; then - [available_patch=0] - fi - # Convert the version tuple into a single number for easier comparison. - # Using base 100 should be safe since SWIG internally uses BCD values - # to encode its version number. - required_swig_vernum=`expr $required_major \* 10000 \ - \+ $required_minor \* 100 \+ $required_patch` - available_swig_vernum=`expr $available_major \* 10000 \ - \+ $available_minor \* 100 \+ $available_patch` - - if test $available_swig_vernum -lt $required_swig_vernum; then - AC_MSG_WARN([SWIG version >= $1 is required. You have $swig_version.]) - SWIG='' - m4_ifval([$3],[$3],[]) - else - AC_MSG_CHECKING([for SWIG library]) - SWIG_LIB=`$SWIG -swiglib` - AC_MSG_RESULT([$SWIG_LIB]) - m4_ifval([$2],[$2],[]) - fi - else - AC_MSG_WARN([cannot determine SWIG version]) - SWIG='' - m4_ifval([$3],[$3],[]) - fi - fi - AC_SUBST([SWIG_LIB]) -]) diff --git a/m4/ax_swig_enable_cxx.m4 b/m4/ax_swig_enable_cxx.m4 deleted file mode 100644 index 348c15d42cbaf019dfaea81e51dbdbfa68082879..0000000000000000000000000000000000000000 --- a/m4/ax_swig_enable_cxx.m4 +++ /dev/null @@ -1,53 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_swig_enable_cxx.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_SWIG_ENABLE_CXX -# -# DESCRIPTION -# -# Enable SWIG C++ support. This affects all invocations of $(SWIG). -# -# LICENSE -# -# Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de> -# Copyright (c) 2008 Alan W. Irwin <irwin@beluga.phys.uvic.ca> -# Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net> -# Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation; either version 2 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -#serial 6 - -AU_ALIAS([SWIG_ENABLE_CXX], [AX_SWIG_ENABLE_CXX]) -AC_DEFUN([AX_SWIG_ENABLE_CXX],[ - AC_REQUIRE([AX_PKG_SWIG]) - AC_REQUIRE([AC_PROG_CXX]) - SWIG="$SWIG -c++" -]) diff --git a/m4/ax_swig_multi_module_support.m4 b/m4/ax_swig_multi_module_support.m4 deleted file mode 100644 index f9c38427c9c6a8f43fcb200eaa9c0b714ea65d75..0000000000000000000000000000000000000000 --- a/m4/ax_swig_multi_module_support.m4 +++ /dev/null @@ -1,56 +0,0 @@ -# ================================================================================ -# http://www.gnu.org/software/autoconf-archive/ax_swig_multi_module_support.html -# ================================================================================ -# -# SYNOPSIS -# -# AX_SWIG_MULTI_MODULE_SUPPORT -# -# DESCRIPTION -# -# Enable support for multiple modules. This effects all invocations of -# $(SWIG). You have to link all generated modules against the appropriate -# SWIG runtime library. If you want to build Python modules for example, -# use the AX_SWIG_PYTHON macro and link the modules against -# $(AX_SWIG_PYTHON_LIBS). -# -# LICENSE -# -# Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de> -# Copyright (c) 2008 Alan W. Irwin <irwin@beluga.phys.uvic.ca> -# Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net> -# Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation; either version 2 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -#serial 7 - -AU_ALIAS([SWIG_MULTI_MODULE_SUPPORT], [AX_SWIG_MULTI_MODULE_SUPPORT]) -AC_DEFUN([AX_SWIG_MULTI_MODULE_SUPPORT],[ - AC_REQUIRE([AX_PKG_SWIG]) - SWIG="$SWIG -noruntime" -]) diff --git a/m4/ax_swig_python.m4 b/m4/ax_swig_python.m4 deleted file mode 100644 index 8fd3df5a8027c51416fac5333f96bc7f4f99a1ab..0000000000000000000000000000000000000000 --- a/m4/ax_swig_python.m4 +++ /dev/null @@ -1,64 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_swig_python.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_SWIG_PYTHON([use-shadow-classes = {no, yes}]) -# -# DESCRIPTION -# -# Checks for Python and provides the $(AX_SWIG_PYTHON_CPPFLAGS), and -# $(AX_SWIG_PYTHON_OPT) output variables. -# -# $(AX_SWIG_PYTHON_OPT) contains all necessary SWIG options to generate -# code for Python. Shadow classes are enabled unless the value of the -# optional first argument is exactly 'no'. If you need multi module -# support (provided by the AX_SWIG_MULTI_MODULE_SUPPORT macro) use -# $(AX_SWIG_PYTHON_LIBS) to link against the appropriate library. It -# contains the SWIG Python runtime library that is needed by the type -# check system for example. -# -# LICENSE -# -# Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de> -# Copyright (c) 2008 Alan W. Irwin <irwin@beluga.phys.uvic.ca> -# Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net> -# Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation; either version 2 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see <http://www.gnu.org/licenses/>. -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -#serial 7 - -AU_ALIAS([SWIG_PYTHON], [AX_SWIG_PYTHON]) -AC_DEFUN([AX_SWIG_PYTHON],[ - AC_REQUIRE([AX_PKG_SWIG]) - AC_REQUIRE([AX_PYTHON_DEVEL]) - test "x$1" != "xno" || swig_shadow=" -noproxy" - AC_SUBST([AX_SWIG_PYTHON_OPT],[-python$swig_shadow]) - AC_SUBST([AX_SWIG_PYTHON_CPPFLAGS],[$PYTHON_CPPFLAGS]) -]) diff --git a/src/Makefile.am b/src/Makefile.am index a092d0b1400f249e31d27e92bb4bc73367287858..4e52d79e7a429fa5a9d89aea7e06d15d4dd38c19 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,9 +7,7 @@ if ENABLE_PAM_SSO PAM_SSO_SUBDIR = pam_sso endif -if SWIG PYTHON_SUBDIR = python sso_server -endif SUBDIRS = \ sso \ diff --git a/src/python/Makefile.am b/src/python/Makefile.am index 361b5e3ded5bdd34183be6edf840e5e3b8f124d1..836b840cd8336c3b4092be0fbc8a4076401260b5 100644 --- a/src/python/Makefile.am +++ b/src/python/Makefile.am @@ -15,10 +15,10 @@ check-local: env \ LD_LIBRARY_PATH=$(top_builddir)/src/sso/.libs \ DYLD_LIBRARY_PATH=$(top_builddir)/src/sso/.libs \ - nosetests -v + $(NOSETESTS) -v clean-local: - -rm -f sso/sso_c.py sso/sso_wrap.cpp $(wildcard sso/_sso_c.*so) + -rm -f $(wildcard sso/_pysso*.so) $(PYTHON_SETUP_PY) clean --all install-exec-local: diff --git a/src/python/setup.py b/src/python/setup.py index fb077259c90c52e716915755a955ef5ef4aa4dbf..8646dfdc05645982fb6b398e22a8808630577fbe 100644 --- a/src/python/setup.py +++ b/src/python/setup.py @@ -10,7 +10,7 @@ top_srcdir = os.path.join(SRCDIR, '..') # If BUILD_MODULE is not defined, bundle libsso into the Python # extension so that this package is pip-installable directly. -ext_sources = ['sso/sso.i'] +ext_sources = ['sso/sso_python.c'] libraries = ['sso'] if not os.getenv('BUILD_MODULE'): ext_sources.extend(os.path.join(top_srcdir, 'sso', x) for x in [ @@ -28,8 +28,8 @@ class build_py(_build_py): setup(name='sso', cmdclass={'build_py': build_py}, - ext_modules=[Extension('sso._sso_c', ext_sources, - swig_opts=['-modern', '-I' + top_srcdir, '-py3'], + ext_modules=[Extension('sso._pysso', ext_sources, + #swig_opts=['-modern', '-I' + top_srcdir, '-py3'], include_dirs=[top_srcdir], libraries=libraries, library_dirs=[SSO_LIB_DIR], diff --git a/src/python/sso/.gitignore b/src/python/sso/.gitignore index 7c49738690cda3d6aad46f84549be6c0af6cc2ce..140f8cf80f2c88e66c141b1c4074b92b29fde4e6 100644 --- a/src/python/sso/.gitignore +++ b/src/python/sso/.gitignore @@ -1,3 +1 @@ -_sso_c.so -sso_c.py -sso_wrap.c +*.so diff --git a/src/python/sso/__init__.py b/src/python/sso/__init__.py index fabd5015b51c8419fbafb67e1cd725422d689174..9015c1f85b9c91968b4381fd822714bee949d3a6 100644 --- a/src/python/sso/__init__.py +++ b/src/python/sso/__init__.py @@ -1,67 +1,52 @@ -# Python 3 compatibility for basestring check as used in isinstance() below. -try: - basestring -except NameError: - basestring = (str, bytes) - -from .sso_c import sso_generate_keys as generate_keys -from .sso_c import sso_ticket_new, sso_ticket_free, \ - sso_ticket_sign, sso_ticket_open, sso_strerror, \ - sso_validate - - -class Error(Exception): - - def __init__(self, code): - self.code = code - Exception.__init__(self, sso_strerror(code)) +import time +from ._pysso import generate_keys, create_and_sign_ticket, verify_ticket, error +Error = error +# Ticket is just a simple container for ticket attributes. class Ticket(object): - def __init__(self, tkt_or_user=None, service=None, domain=None, - nonce=None, groups=None, ttl=3600): - # Fix old constructor behavior with positional args. - if isinstance(groups, int): - ttl = groups - groups = None - if tkt_or_user is None or isinstance(tkt_or_user, basestring): - self._tkt = sso_ticket_new( - tkt_or_user, service, domain, nonce, groups, ttl) - else: - self._tkt = tkt_or_user - - def __del__(self): - if hasattr(self, '_tkt'): - sso_ticket_free(self._tkt) + def __init__(self, user, service, domain, nonce=None, groups=None, ttl=3600, expires=None): + self._user = user + self._service = service + self._domain = domain + self._nonce = nonce + self._groups = groups + # 'expires' has precedence over 'ttl'. + if expires is None: + expires = int(time.time()) + ttl + self._expires = expires def __eq__(self, t2): return (self.user() == t2.user() and self.service() == t2.service() and self.domain() == t2.domain() and - self.expires() == t2.expires()) + self.nonce() == t2.nonce()) def empty(self): - return (not self._tkt.user and not self._tkt.domain - and not self._tkt.service) + return (not self._user and not self._domain + and not self._service) def user(self): - return self._tkt.user + return self._user def domain(self): - return self._tkt.domain + return self._domain def service(self): - return self._tkt.service + return self._service def nonce(self): - return self._tkt.nonce + return self._nonce def groups(self): - return self._tkt.groups + return self._groups def expires(self): - return self._tkt.expires + return self._expires + + def ttl(self): + return int(self._expires - time.time()) class Signer(object): @@ -70,10 +55,15 @@ class Signer(object): self._sk = sk def sign(self, t): - r, signed = sso_ticket_sign(t._tkt, self._sk) - if r != 0: - raise Error(r) - return signed + return create_and_sign_ticket( + self._sk, + t.user(), + t.service(), + t.domain(), + t.nonce(), + t.groups(), + t.ttl(), + ) class Verifier(object): @@ -85,10 +75,12 @@ class Verifier(object): self._ok_groups = ok_groups def verify(self, encoded_tkt, nonce=None): - r, tkt = sso_ticket_open(encoded_tkt, self._pk) - if r != 0: - raise Error(r) - r = sso_validate(tkt, self._service, self._domain, nonce, self._ok_groups) - if r != 0: - raise Error(r) - return Ticket(tkt) + result = verify_ticket(self._pk, encoded_tkt, self._service, self._domain, nonce, self._ok_groups) + return Ticket( + result['user'], + self._service, + self._domain, + nonce, + result['groups'], + result['expires'], + ) diff --git a/src/python/sso/py3compat.h b/src/python/sso/py3compat.h new file mode 100644 index 0000000000000000000000000000000000000000..84bb465d02c621f6dc9d1d0223b711575bfee4ef --- /dev/null +++ b/src/python/sso/py3compat.h @@ -0,0 +1,191 @@ +/* + Unix SMB/CIFS implementation. + Python 3 compatibility macros + Copyright (C) Petr Viktorin <pviktori@redhat.com> 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SAMBA_PY3COMPAT_H_ +#define _SAMBA_PY3COMPAT_H_ +#include <Python.h> + +/* Quick docs: + * + * The IS_PY3 preprocessor constant is 1 on Python 3, and 0 on Python 2. + * + * "PyStr_*" works like PyUnicode_* on Python 3, but uses bytestrings (str) + * under Python 2. + * + * "PyBytes_*" work like in Python 3; on Python 2 they are aliased to their + * PyString_* names. + * + * "PyInt_*" works like PyLong_* + * + * Syntax for module initialization is as in Python 3, except the entrypoint + * function definition and declaration: + * PyMODINIT_FUNC PyInit_modulename(void); + * PyMODINIT_FUNC PyInit_modulename(void) + * { + * ... + * } + * is replaced by: + * MODULE_INIT_FUNC(modulename) + * { + * ... + * } + * + * In the entrypoint, create a module using PyModule_Create and PyModuleDef, + * and return it. See Python 3 documentation for details. + * For Python 2 compatibility, always set PyModuleDef.m_size to -1. + * + */ + +#if PY_MAJOR_VERSION >= 3 + +/***** Python 3 *****/ + +#define IS_PY3 1 + +/* Strings */ + +#define PyStr_Type PyUnicode_Type +#define PyStr_Check PyUnicode_Check +#define PyStr_CheckExact PyUnicode_CheckExact +#define PyStr_FromString PyUnicode_FromString +#define PyStr_FromStringAndSize PyUnicode_FromStringAndSize +#define PyStr_FromFormat PyUnicode_FromFormat +#define PyStr_FromFormatV PyUnicode_FromFormatV +#define PyStr_AsString PyUnicode_AsUTF8 +#define PyStr_Concat PyUnicode_Concat +#define PyStr_Format PyUnicode_Format +#define PyStr_InternInPlace PyUnicode_InternInPlace +#define PyStr_InternFromString PyUnicode_InternFromString +#define PyStr_Decode PyUnicode_Decode + +#define PyStr_AsUTF8String PyUnicode_AsUTF8String // returns PyBytes +#define PyStr_AsUTF8 PyUnicode_AsUTF8 +#define PyStr_AsUTF8AndSize PyUnicode_AsUTF8AndSize + +/* description of bytes and string objects */ +#define PY_DESC_PY3_BYTES "bytes" +#define PY_DESC_PY3_STRING "string" + +/* Determine if object is really bytes, for code that runs + * in python2 & python3 (note: PyBytes_Check is replaced by + * PyString_Check in python2) so care needs to be taken when + * writing code that will check if incoming type is bytes that + * will work as expected in python2 & python3 + */ + +#define IsPy3Bytes PyBytes_Check + +#define IsPy3BytesOrString(pystr) \ + (PyStr_Check(pystr) || PyBytes_Check(pystr)) + +#else + +/***** Python 2 *****/ + +#define IS_PY3 0 + +/* Strings */ + +#define PyStr_Type PyString_Type +#define PyStr_Check PyString_Check +#define PyStr_CheckExact PyString_CheckExact +#define PyStr_FromString PyString_FromString +#define PyStr_FromStringAndSize PyString_FromStringAndSize +#define PyStr_FromFormat PyString_FromFormat +#define PyStr_FromFormatV PyString_FromFormatV +#define PyStr_AsString PyString_AsString +#define PyStr_Format PyString_Format +#define PyStr_InternInPlace PyString_InternInPlace +#define PyStr_InternFromString PyString_InternFromString +#define PyStr_Decode PyString_Decode + +#define PyStr_AsUTF8String(str) (Py_INCREF(str), (str)) +#define PyStr_AsUTF8 PyString_AsString +#define PyStr_AsUTF8AndSize(pystr, sizeptr) \ + ((*sizeptr=PyString_Size(pystr)), PyString_AsString(pystr)) + +#define PyBytes_Type PyString_Type +#define PyBytes_Check PyString_Check +#define PyBytes_CheckExact PyString_CheckExact +#define PyBytes_FromString PyString_FromString +#define PyBytes_FromStringAndSize PyString_FromStringAndSize +#define PyBytes_FromFormat PyString_FromFormat +#define PyBytes_FromFormatV PyString_FromFormatV +#define PyBytes_Size PyString_Size +#define PyBytes_GET_SIZE PyString_GET_SIZE +#define PyBytes_AsString PyString_AsString +#define PyBytes_AS_STRING PyString_AS_STRING +#define PyBytes_AsStringAndSize PyString_AsStringAndSize +#define PyBytes_Concat PyString_Concat +#define PyBytes_ConcatAndDel PyString_ConcatAndDel +#define _PyBytes_Resize _PyString_Resize + +/* description of bytes and string objects */ +#define PY_DESC_PY3_BYTES "string" +#define PY_DESC_PY3_STRING "unicode" + +/* Determine if object is really bytes, for code that runs + * in python2 & python3 (note: PyBytes_Check is replaced by + * PyString_Check in python2) so care needs to be taken when + * writing code that will check if incoming type is bytes that + * will work as expected in python2 & python3 + */ + +#define IsPy3Bytes(pystr) false + +#define IsPy3BytesOrString PyStr_Check + +/* PyArg_ParseTuple/Py_BuildValue argument */ + +#define PYARG_BYTES_LEN "s#" +/* + * We want a format that will ensure unicode is encoded using the + * specified encoding 'utf8' (to obtain the char* array) + * In python3 we use "es" but in python2 the specifiying 'es' will + * result in the any incomming 'str' type being decoded first to ascii + * then encoded to the specified 'utf8' encoding. In order to avoid that + * we use format 'et' in python2 instead. + */ +#define PYARG_STR_UNI "et" + +/* Module init */ + +#define PyModuleDef_HEAD_INIT 0 + +typedef struct PyModuleDef { + int m_base; + const char* m_name; + const char* m_doc; + Py_ssize_t m_size; + PyMethodDef *m_methods; +} PyModuleDef; + +#define PyModule_Create(def) \ + Py_InitModule3((def)->m_name, (def)->m_methods, (def)->m_doc) + +#define MODULE_INIT_FUNC(name) \ + static PyObject *PyInit_ ## name(void); \ + void init ## name(void); \ + void init ## name(void) { PyInit_ ## name(); } \ + static PyObject *PyInit_ ## name(void) + + +#endif + +#endif diff --git a/src/python/sso/sso.i b/src/python/sso/sso.i deleted file mode 100644 index aa250ce3221aa064f91ad50e7b107d04960a7aba..0000000000000000000000000000000000000000 --- a/src/python/sso/sso.i +++ /dev/null @@ -1,117 +0,0 @@ -%module sso_c -//%include "typemaps.i" - -%{ -#include "sso/sso.h" -%} - -// convert time_t to python ints -%typemap(out) time_t { - $result = PyInt_FromLong((int)$1); -} - -// convert unsigned char * to Python strings. -%typemap(out) unsigned char * { - $result = PyString_FromString((char *)$1); -} - -// convert Python string to unsigned char * -%typemap(in) unsigned char * { - $1 = (unsigned char *)PyString_AsString($input); -} - -// typemaps for groups -%typemap(typecheck,precedence=SWIG_TYPECHECK_STRING) char ** { - PyObject *iter = PyObject_GetIter($input); - if (!iter) { - $1 = 0; - } else { - Py_DECREF(iter); - $1 = 1; - } - } - -// groups as input -%typemap(in) char ** { - if ($input == Py_None) { - $1 = NULL; - } else { - int bufalloc = 8, i = 0; - char **buf = (char **)malloc(sizeof(char *)*bufalloc); - - PyObject *iter = PyObject_GetIter($input); - PyObject *item; - while ((item = PyIter_Next(iter))) { - buf[i++] = PyString_AsString(item); - if (i >= bufalloc - 1) { - bufalloc *= 2; - buf = (char **)realloc(buf, sizeof(char *)*bufalloc); - } - Py_DECREF(item); - } - buf[i] = NULL; - Py_DECREF(iter); - $1 = buf; - } -} - -%typemap(freearg) char ** { - if ($1 != NULL) { - free($1); - } -} - -// groups as output -%typemap(out) char ** { - if ($1 == NULL) { - $result = Py_None; - } else { - PyObject *set = PySet_New(NULL); - for (char **i = $1; *i; i++) { - PySet_Add(set, PyString_FromString(*i)); - } - $result = set; - } -} - -// special typemaps for sso_generate_keys. -%typemap(in, numinputs=0) (unsigned char *publicp, unsigned char *secretp) (unsigned char temp_pk[SSO_PUBLIC_KEY_SIZE], unsigned char temp_sk[SSO_SECRET_KEY_SIZE]) { - $1 = temp_pk; - $2 = temp_sk; -} - -%typemap(argout) (unsigned char *publicp, unsigned char *secretp) { - $result = PyTuple_New(2); - PyTuple_SetItem($result, 0, PyString_FromStringAndSize((const char *)$1, SSO_PUBLIC_KEY_SIZE)); - PyTuple_SetItem($result, 1, PyString_FromStringAndSize((const char *)$2, SSO_SECRET_KEY_SIZE)); -} - -// sso_ticket_sign output arg -%typemap(in, numinputs=0) (char *out, size_t outsz) (char temp[512]) { - $1 = temp; - $2 = sizeof(temp); -} - -%typemap(argout) (char *out, size_t outsz) { - PyObject *temp_tuple = PyTuple_New(2); - PyTuple_SetItem(temp_tuple, 0, $result); - PyTuple_SetItem(temp_tuple, 1, PyString_FromString($1)); - $result = temp_tuple; -} - -// sso_ticket_open output arg -%typemap(in, numinputs=0) sso_ticket_t * (sso_ticket_t temp = NULL) { - $1 = &temp; -} - -%typemap(argout) sso_ticket_t * { - PyObject *temp_tuple = PyTuple_New(2); - PyTuple_SetItem(temp_tuple, 0, $result); - PyTuple_SetItem(temp_tuple, 1, SWIG_NewPointerObj(SWIG_as_voidptr(*($1)), SWIGTYPE_p_sso_ticket, 0)); - $result = temp_tuple; -} - -%include "sso/sso.h" - - - diff --git a/src/python/sso/sso_python.c b/src/python/sso/sso_python.c new file mode 100644 index 0000000000000000000000000000000000000000..ec647f328a01dd558818a63cf83703987e343c56 --- /dev/null +++ b/src/python/sso/sso_python.c @@ -0,0 +1,241 @@ +#include "sso/sso.h" +#include "py3compat.h" +#include <stdlib.h> + +#define PY_SSIZE_T_CLEAN +#include <Python.h> + +#define PYSSO_STATIC_SIGN_BUFSIZE 1024 + +static const char **parse_groups(PyObject *groups_obj) { + int bufalloc = 8, i = 0; + char **buf = (char **)malloc(sizeof(char *) * bufalloc); + PyObject *iter, *item; + + iter = PyObject_GetIter(groups_obj); + if (!iter) { + return NULL; + } + while ((item = PyIter_Next(iter))) { + buf[i++] = PyStr_AsString(item); + if (i >= bufalloc - 1) { + bufalloc *= 2; + buf = (char **)realloc(buf, sizeof(char *) * bufalloc); + } + Py_DECREF(item); + } + buf[i] = NULL; + Py_DECREF(iter); + return (const char **)buf; +} + +// Convert a list of groups to a Python list. If groups is NULL, +// returns an empty list. +static PyObject *groups_to_list(char **groups) { + PyObject *groups_obj; + char **p; + Py_ssize_t n = 0; + int i; + + for (p = groups; p && *p; p++) { + n++; + } + + groups_obj = PyList_New(n); + for (i = 0; i < n; i++) { + PyList_SET_ITEM(groups_obj, i, PyStr_FromString(groups[i])); + } + return groups_obj; +} + +static PyObject *pysso_error; + +static void pysso_set_error(int err) { + PyErr_SetString(pysso_error, sso_strerror(err)); +} + +// create_and_sign_ticket(private_key, user, service, domain, [nonce, groups, +// ttl]) +static PyObject *pysso_create_and_sign_ticket(PyObject *self, PyObject *args, + PyObject *kwargs) { + const char *priv_key = NULL; + Py_ssize_t priv_key_size = 0; + const char *user = NULL; + const char *service = NULL; + const char *domain = NULL; + const char *nonce = NULL; + PyObject *groups_obj = NULL; + const char **groups = NULL; + int ttl = 3600; + static char *kwlist[] = {"private_key", "user", "service", "domain", + "nonce", "groups", "ttl", NULL}; + + PyObject *result_obj = NULL; + sso_ticket_t ticket = NULL; + char buf[PYSSO_STATIC_SIGN_BUFSIZE]; /* on-stack buffer for signed output */ + int err; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, +#if IS_PY3 + "y#sss|zOi:create_and_sign_ticket", +#else + "s#sss|zOi:create_and_sign_ticket", +#endif + kwlist, &priv_key, &priv_key_size, &user, + &service, &domain, &nonce, &groups_obj, + &ttl)) { + return NULL; + } + + if (priv_key_size != SSO_SECRET_KEY_SIZE) { + PyErr_SetString(PyExc_ValueError, "Bad secret key size"); + goto fail; + } + + if (groups_obj && groups_obj != Py_None) { + groups = parse_groups(groups_obj); + } + + ticket = sso_ticket_new(user, service, domain, nonce, groups, ttl); + if (!ticket) { + PyErr_SetString(PyExc_ValueError, "Invalid ticket parameters"); + goto fail; + } + + err = sso_ticket_sign(ticket, (const unsigned char *)priv_key, buf, + sizeof(buf)); + if (err != SSO_OK) { + pysso_set_error(err); + goto fail; + } + + result_obj = PyStr_FromString(buf); + +fail: + if (groups) { + free(groups); + } + if (ticket) { + sso_ticket_free(ticket); + } + return result_obj; +} + +// verify_ticket(public_key, ticket, service, domain, [nonce, groups]) +static PyObject *pysso_verify_ticket(PyObject *self, PyObject *args, + PyObject *kwargs) { + const char *pub_key = NULL; + Py_ssize_t pub_key_size = 0; + const char *serialized_ticket = NULL; + const char *service = NULL; + const char *domain = NULL; + const char *nonce = NULL; + PyObject *groups_obj = NULL; + const char **groups = NULL; + static char *kwlist[] = {"public_key", "ticket", "service", "domain", + "nonce", "groups", NULL}; + + PyObject *result_obj = NULL; + sso_ticket_t ticket = NULL; + int err; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, +#if IS_PY3 + "y#sss|zO:verify_ticket", +#else + "s#sss|zO:verify_ticket", +#endif + kwlist, &pub_key, &pub_key_size, + &serialized_ticket, &service, &domain, + &nonce, &groups_obj)) { + return NULL; + } + + if (pub_key_size != SSO_PUBLIC_KEY_SIZE) { + PyErr_SetString(PyExc_ValueError, "Bad public key size"); + goto fail; + } + + if (groups_obj && groups_obj != Py_None) { + groups = parse_groups(groups_obj); + } + + err = sso_ticket_open(&ticket, serialized_ticket, + (const unsigned char *)pub_key); + if (err != SSO_OK) { + pysso_set_error(err); + goto fail; + } + + err = sso_validate(ticket, service, domain, nonce, groups); + if (err != SSO_OK) { + pysso_set_error(err); + goto fail; + } + + // Build a dictionary with the ticket values. + result_obj = PyDict_New(); + PyDict_SetItem(result_obj, PyStr_FromString("user"), + PyStr_FromString(ticket->user)); + PyDict_SetItem(result_obj, PyStr_FromString("expires"), + PyLong_FromLong(ticket->expires)); + PyDict_SetItem(result_obj, PyStr_FromString("groups"), + groups_to_list(ticket->groups)); + +fail: + if (groups) { + free(groups); + } + if (ticket) { + sso_ticket_free(ticket); + } + return result_obj; +} + +// generate_keys() +static PyObject *pysso_generate_keys(PyObject *self, PyObject *args) { + unsigned char pubkey[SSO_PUBLIC_KEY_SIZE]; + unsigned char privkey[SSO_SECRET_KEY_SIZE]; + int err; + PyObject *pub, *priv; + + err = sso_generate_keys(pubkey, privkey); + if (err != SSO_OK) { + pysso_set_error(err); + return NULL; + } + + pub = PyBytes_FromStringAndSize((const char *)pubkey, SSO_PUBLIC_KEY_SIZE); + priv = PyBytes_FromStringAndSize((const char *)privkey, SSO_SECRET_KEY_SIZE); + return Py_BuildValue("OO", pub, priv); +} + +static PyMethodDef pysso_methods[] = { + /* The cast of the function is necessary since PyCFunction values + * only take two PyObject* parameters, and keywdarg_parrot() takes + * three. + */ + {"create_and_sign_ticket", (PyCFunction)pysso_create_and_sign_ticket, + METH_VARARGS | METH_KEYWORDS, "Sign a new SSO ticket."}, + {"verify_ticket", (PyCFunction)pysso_verify_ticket, + METH_VARARGS | METH_KEYWORDS, "Verify a SSO ticket."}, + {"generate_keys", (PyCFunction)pysso_generate_keys, METH_VARARGS, + "Generate a new SSO keypair."}, + {NULL, NULL, 0, NULL}}; + +static struct PyModuleDef pysso_module = {PyModuleDef_HEAD_INIT, "_pysso", NULL, + -1, pysso_methods}; + +MODULE_INIT_FUNC(_pysso) { + PyObject *m; + + m = PyModule_Create(&pysso_module); + if (!m) { + return NULL; + } + + pysso_error = PyErr_NewException("_pysso.error", NULL, NULL); + Py_INCREF(pysso_error); + PyModule_AddObject(m, "error", pysso_error); + return m; +} diff --git a/src/python/test/signer_test.py b/src/python/test/signer_test.py index 990fc9b056f0c803c6f53c0bbf32742603aaffd2..af413f7c9707b0629361e344592090054ddbd3d1 100644 --- a/src/python/test/signer_test.py +++ b/src/python/test/signer_test.py @@ -11,7 +11,7 @@ class SignerTest(unittest.TestCase): def _signok(self, tkt): signed = self.s.sign(tkt) - self.assertTrue(isinstance(signed, bytes)) + self.assertTrue(isinstance(signed, str)) self.assertGreater(len(signed), 0) #self.assertTrue('user' in base64.urlsafe_b64decode(signed)) @@ -32,9 +32,9 @@ class SignerTest(unittest.TestCase): def test_sign_ticket_with_nonce(self): self._signok(sso.Ticket('user', 'service', 'domain', nonce='testnonce')) - def test_sign_fail_with_empty_ticket(self): - tkt = sso.Ticket() - self.assertRaises(sso.Error, self.s.sign, tkt) + # def test_sign_fail_with_empty_ticket(self): + # tkt = sso.Ticket() + # self.assertRaises(sso.Error, self.s.sign, tkt) def test_sign_fail_with_invalid_ticket(self): tkt = sso.Ticket('|', 'service', 'domain') diff --git a/src/python/test/ticket_test.py b/src/python/test/ticket_test.py index 8631c47575f2697ef7bb434eeac6931ee580c376..6c81985324b9f1276070374fad538862afedcc12 100644 --- a/src/python/test/ticket_test.py +++ b/src/python/test/ticket_test.py @@ -15,9 +15,9 @@ class TicketTest(unittest.TestCase): self.assertTrue(isinstance(expires, int)) self.assertTrue(expires > 0) - def test_create_empty_ticket(self): - tkt = sso.Ticket() - self.assertTrue(tkt.empty()) + # def test_create_empty_ticket(self): + # tkt = sso.Ticket() + # self.assertTrue(tkt.empty()) def test_create_ticket_with_groups(self): groups = set(['g1', 'g2']) diff --git a/src/python/test/verifier_test.py b/src/python/test/verifier_test.py index cc95e26554c0d5b0079990aebb7d45c32b8cc935..a23f9b6d724e0599f90509a5b1a68b9ee0d7ca73 100644 --- a/src/python/test/verifier_test.py +++ b/src/python/test/verifier_test.py @@ -50,12 +50,12 @@ class VerifierTest(unittest.TestCase): self.assertRaises(sso.Error, v.verify, signed, 'bad_nonce') def test_verify_fail_too_short(self): - signed = base64.b64encode('some data') + signed = base64.b64encode(b'some data').decode() v = sso.Verifier(self.public, 'service/', 'domain', self.groups) self.assertRaises(sso.Error, v.verify, signed) def test_verify_fail_not_signed(self): - signed = base64.b64encode('some data' * 10) + signed = base64.b64encode(b'some data' * 10).decode() v = sso.Verifier(self.public, 'service/', 'domain', self.groups) self.assertRaises(sso.Error, v.verify, signed) diff --git a/src/sso_server/sso_server/test/sso_server_test.py b/src/sso_server/sso_server/test/sso_server_test.py index e52f8751f59db8b032f2484cceb6b8734bd69a45..5fd44ca3e7ee90aab2e70d6db4371cc6e16fe530 100644 --- a/src/sso_server/sso_server/test/sso_server_test.py +++ b/src/sso_server/sso_server/test/sso_server_test.py @@ -48,7 +48,7 @@ class SSOServerTest(SSOServerTestBase): cookies.update(parse_cookie(x)) return cookies - def _check_ticket(self, query_string, expected_d, expected_s): + def _check_ticket(self, query_string, expected_d, expected_s, nonce=None): # Check the query string arguments. args = urldecode(query_string) self.assertEquals(expected_d, args.get('d')) @@ -57,9 +57,8 @@ class SSOServerTest(SSOServerTestBase): # Check that we got a valid ticket for the remote service. vrfy = sso.Verifier(self.app.login_service.public_key, - expected_s, - self.domain) - return vrfy.verify(args['t']) + expected_s, self.domain) + return vrfy.verify(args['t'], nonce) def test_login_ok_with_cookie(self): # Test that we can login successfully if we already have a local @@ -226,7 +225,8 @@ class SSOServerTest(SSOServerTestBase): path, qs = location.split('?', 1) self.assertEquals('https://someservice.org/sso_login', path) ticket = self._check_ticket( - qs, 'https://someservice.org/dest', 'someservice.org/') + qs, 'https://someservice.org/dest', 'someservice.org/', + 'testnonce') self.assertEquals('admin', ticket.user()) self.assertEquals('testnonce', ticket.nonce())