[Pkg-freeipa-devel] [Git][freeipa-team/python-kdcproxy][upstream] 4 commits: Fix usages of "retval" instead of "restype"
Timo Aaltonen
gitlab at salsa.debian.org
Wed Oct 17 09:14:08 BST 2018
Timo Aaltonen pushed to branch upstream at FreeIPA packaging / python-kdcproxy
Commits:
bb1b3411 by Robbie Harwood at 2017-10-16T09:37:57Z
Fix usages of "retval" instead of "restype"
Closes: #32
- - - - -
074fda53 by Robbie Harwood at 2018-08-08T18:52:51Z
Downgrade socket problems to warnings
Previously, these were logged at exception - which logs at ERROR and
prints a traceback. This led to two problems: first, that they're not
kdcproxy errors (rather problems with the other end); and second, that
the traceback is quite noisy. Log a simplified version of the
exception instead of the traceback.
In the process, correct the sendall() error message to refer to
sendall(), not recv().
- - - - -
5df7db40 by Christian Heimes at 2018-08-08T21:38:42Z
Add asn1crypto support
Closes: #33
Signed-off-by: Christian Heimes <cheimes at redhat.com>
- - - - -
898760d0 by Robbie Harwood at 2018-08-08T21:44:53Z
Release 0.4
- - - - -
11 changed files:
- .gitignore
- .travis.yml
- kdcproxy/__init__.py
- kdcproxy/codec.py
- kdcproxy/config/mit.py
- + kdcproxy/exceptions.py
- + kdcproxy/parse_asn1crypto.py
- kdcproxy/asn1.py → kdcproxy/parse_pyasn1.py
- setup.py
- tests.py
- tox.ini
Changes:
=====================================
.gitignore
=====================================
@@ -7,3 +7,4 @@ __pycache__
/MANIFEST
/*.egg-info
/.cache
+/.coverage.*
=====================================
.travis.yml
=====================================
@@ -7,16 +7,20 @@ cache: pip
matrix:
include:
- python: 2.7
- env: TOXENV=py27
- - python: 3.4
- env: TOXENV=py34
+ env: TOXENV=py27-asn1crypto
- python: 3.5
- env: TOXENV=py35
+ env: TOXENV=py35-asn1crypto
- python: 3.6
- env: TOXENV=py36
+ env: TOXENV=py36-asn1crypto
- python: 2.7
- env: TOXENV=pep8
+ env: TOXENV=py27-pyasn1
- python: 3.5
+ env: TOXENV=py35-pyasn1
+ - python: 3.6
+ env: TOXENV=py36-pyasn1
+ - python: 2.7
+ env: TOXENV=pep8
+ - python: 3.6
env: TOXENV=py3pep8
install:
=====================================
kdcproxy/__init__.py
=====================================
@@ -99,8 +99,8 @@ class Application:
else:
sock.sendall(pr.request)
extra = 10 # New connections get 10 extra seconds
- except Exception:
- logging.exception('Error in recv() of %s', sock)
+ except Exception as e:
+ logging.warning("Conection broken while writing (%s)", e)
continue
rsocks.append(sock)
wsocks.remove(sock)
@@ -108,8 +108,8 @@ class Application:
for sock in r:
try:
reply = self.__handle_recv(sock, read_buffers)
- except Exception:
- logging.exception('Error in recv() of %s', sock)
+ except Exception as e:
+ logging.warning("Connection broken while reading (%s)", e)
if self.sock_type(sock) == socket.SOCK_STREAM:
# Remove broken TCP socket from readers
rsocks.remove(sock)
=====================================
kdcproxy/codec.py
=====================================
@@ -19,19 +19,35 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
+import os
import struct
-from pyasn1 import error
-from pyasn1.codec.der import decoder, encoder
-
-import kdcproxy.asn1 as asn1
-
-
-class ParsingError(Exception):
-
- def __init__(self, message):
- super(ParsingError, self).__init__(message)
- self.message = message
+from kdcproxy.exceptions import ParsingError
+
+ASN1MOD = os.environ.get('KDCPROXY_ASN1MOD')
+
+if ASN1MOD is None:
+ try:
+ from asn1crypto.version import __version_info__ as asn1crypto_version
+ except ImportError:
+ asn1crypto_version = None
+ else:
+ if asn1crypto_version >= (0, 22, 0):
+ ASN1MOD = 'asn1crypto'
+ if ASN1MOD is None:
+ try:
+ __import__('pyasn1')
+ except ImportError:
+ pass
+ else:
+ ASN1MOD = 'pyasn1'
+
+if ASN1MOD == 'asn1crypto':
+ from kdcproxy import parse_asn1crypto as asn1mod
+elif ASN1MOD == 'pyasn1':
+ from kdcproxy import parse_pyasn1 as asn1mod
+else:
+ raise ValueError("Invalid KDCPROXY_ASN1MOD='{}'".format(ASN1MOD))
class ProxyRequest(object):
@@ -40,16 +56,7 @@ class ProxyRequest(object):
@classmethod
def parse(cls, data):
- (req, err) = decoder.decode(data, asn1Spec=asn1.ProxyMessage())
- if err:
- raise ParsingError("Invalid request.")
-
- request = req.getComponentByName('message').asOctets()
- realm = req.getComponentByName('realm').asOctets()
- try: # Python 3.x
- realm = str(realm, "UTF8")
- except TypeError: # Python 2.x
- realm = str(realm)
+ request, realm, _ = asn1mod.decode_proxymessage(data)
# Check the length of the whole request message.
(length, ) = struct.unpack("!I", request[0:4])
@@ -58,42 +65,41 @@ class ProxyRequest(object):
for subcls in cls.__subclasses__():
try:
- (req, err) = decoder.decode(request[subcls.OFFSET:],
- asn1Spec=subcls.TYPE())
- return subcls(realm, request, err)
- except error.PyAsn1Error:
+ return subcls.parse_request(realm, request)
+ except ParsingError:
pass
raise ParsingError("Invalid request.")
- def __init__(self, realm, request, err):
+ @classmethod
+ def parse_request(cls, realm, request):
+ pretty_name = asn1mod.try_decode(request[cls.OFFSET:], cls.TYPE)
+ return cls(realm, request, pretty_name)
+
+ def __init__(self, realm, request, pretty_name):
self.realm = realm
self.request = request
-
- if len(err) > 0:
- type = self.__class__.__name__[:0 - len(ProxyRequest.__name__)]
- raise ParsingError("%s request has %d extra bytes." %
- (type, len(err)))
+ self.pretty_name = pretty_name
def __str__(self):
- type = self.__class__.__name__[:0 - len(ProxyRequest.__name__)]
- return "%s %s-REQ (%d bytes)" % (self.realm, type,
- len(self.request) - 4)
+ return "%s %s (%d bytes)" % (self.realm, self.pretty_name,
+ len(self.request) - 4)
class TGSProxyRequest(ProxyRequest):
- TYPE = asn1.TGSREQ
+ TYPE = asn1mod.TGSREQ
class ASProxyRequest(ProxyRequest):
- TYPE = asn1.ASREQ
+ TYPE = asn1mod.ASREQ
class KPASSWDProxyRequest(ProxyRequest):
- TYPE = asn1.APREQ
+ TYPE = asn1mod.APREQ
OFFSET = 10
- def __init__(self, realm, request, err):
+ @classmethod
+ def parse_request(cls, realm, request):
# Check the length count in the password change request, assuming it
# actually is a password change request. It should be the length of
# the rest of the request, including itself.
@@ -118,13 +124,12 @@ class KPASSWDProxyRequest(ProxyRequest):
# See if the tag looks like an AP request, which would look like the
# start of a password change request. The rest of it should be a
# KRB-PRIV message.
- (apreq, err) = decoder.decode(request[10:length + 10],
- asn1Spec=asn1.APREQ())
- (krbpriv, err) = decoder.decode(request[length + 10:],
- asn1Spec=asn1.KRBPriv())
+ asn1mod.try_decode(request[10:length + 10], asn1mod.APREQ)
+ asn1mod.try_decode(request[length + 10:], asn1mod.KRBPriv)
- super(KPASSWDProxyRequest, self).__init__(realm, request, err)
+ self = cls(realm, request, "KPASSWD-REQ")
self.version = version
+ return self
def __str__(self):
tmp = super(KPASSWDProxyRequest, self).__str__()
@@ -137,6 +142,4 @@ def decode(data):
def encode(data):
- rep = asn1.ProxyMessage()
- rep.setComponentByName('message', data)
- return encoder.encode(rep)
+ return asn1mod.encode_proxymessage(data)
=====================================
kdcproxy/config/mit.py
=====================================
@@ -93,7 +93,7 @@ else:
krb5_free_context = LIBKRB5.krb5_free_context
krb5_free_context.argtypes = (krb5_context, )
- krb5_free_context.retval = None
+ krb5_free_context.restype = None
krb5_get_profile = LIBKRB5.krb5_get_profile
krb5_get_profile.argtypes = (krb5_context, ctypes.POINTER(profile_t))
@@ -114,7 +114,7 @@ else:
profile_iterator_free = LIBKRB5.profile_iterator_free
profile_iterator_free.argtypes = (ctypes.POINTER(iter_p), )
- profile_iterator_free.retval = None
+ profile_iterator_free.restype = None
profile_iterator = LIBKRB5.profile_iterator
profile_iterator.argtypes = (ctypes.POINTER(iter_p),
=====================================
kdcproxy/exceptions.py
=====================================
@@ -0,0 +1,30 @@
+# Copyright (C) 2017, Red Hat, Inc.
+# All rights reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+
+class ParsingError(Exception):
+ def __init__(self, message):
+ super(ParsingError, self).__init__(message)
+ self.message = message
+
+
+class ASN1ParsingError(ParsingError):
+ pass
=====================================
kdcproxy/parse_asn1crypto.py
=====================================
@@ -0,0 +1,104 @@
+# Copyright (C) 2017, Red Hat, Inc.
+# All rights reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+from asn1crypto import core
+
+from kdcproxy.exceptions import ASN1ParsingError
+
+
+APPLICATION = 1
+
+
+class KerberosString(core.GeneralString):
+ """KerberosString ::= GeneralString (IA5String)
+
+ For compatibility, implementations MAY choose to accept GeneralString
+ values that contain characters other than those permitted by
+ IA5String...
+ """
+
+
+class Realm(KerberosString):
+ """Realm ::= KerberosString
+ """
+
+
+class ProxyMessage(core.Sequence):
+ pretty_name = 'KDC-PROXY-MESSAGE'
+
+ _fields = [
+ ('kerb-message', core.OctetString, {
+ 'explicit': 0}),
+ ('target-domain', Realm, {
+ 'explicit': 1, 'optional': True}),
+ ('dclocator-hint', core.Integer, {
+ 'explicit': 2, 'optional': True}),
+ ]
+
+
+class ASREQ(core.Sequence):
+ pretty_name = 'AS-REQ'
+
+ explicit = (APPLICATION, 10)
+
+
+class TGSREQ(core.Sequence):
+ pretty_name = 'TGS-REQ'
+
+ explicit = (APPLICATION, 12)
+
+
+class APREQ(core.Sequence):
+ pretty_name = 'AP-REQ'
+
+ explicit = (APPLICATION, 14)
+
+
+class KRBPriv(core.Sequence):
+ pretty_name = 'KRBPRiv'
+
+ explicit = (APPLICATION, 21)
+
+
+def decode_proxymessage(data):
+ req = ProxyMessage.load(data, strict=True)
+ message = req['kerb-message'].native
+ realm = req['target-domain'].native
+ try: # Python 3.x
+ realm = str(realm, "utf-8")
+ except TypeError: # Python 2.x
+ realm = str(realm)
+ flags = req['dclocator-hint'].native
+ return message, realm, flags
+
+
+def encode_proxymessage(data):
+ rep = ProxyMessage()
+ rep['kerb-message'] = data
+ return rep.dump()
+
+
+def try_decode(data, cls):
+ try:
+ req = cls.load(data, strict=True)
+ except ValueError as e:
+ raise ASN1ParsingError(e)
+ return req.pretty_name
=====================================
kdcproxy/asn1.py → kdcproxy/parse_pyasn1.py
=====================================
@@ -19,8 +19,12 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
+from pyasn1 import error
+from pyasn1.codec.der import decoder, encoder
from pyasn1.type import char, namedtype, tag, univ
+from kdcproxy.exceptions import ASN1ParsingError, ParsingError
+
class ProxyMessageKerberosMessage(univ.OctetString):
tagSet = univ.OctetString.tagSet.tagExplicitly(
@@ -41,6 +45,8 @@ class ProxyMessageDCLocateHint(univ.Integer):
class ProxyMessage(univ.Sequence):
+ pretty_name = 'KDC-PROXY-MESSAGE'
+
componentType = namedtype.NamedTypes(
namedtype.NamedType('message', ProxyMessageKerberosMessage()),
namedtype.OptionalNamedType('realm', ProxyMessageTargetDomain()),
@@ -49,24 +55,70 @@ class ProxyMessage(univ.Sequence):
class ASREQ(univ.Sequence):
+ pretty_name = 'AS-REQ'
+
tagSet = univ.Sequence.tagSet.tagExplicitly(
tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 10)
)
class TGSREQ(univ.Sequence):
+ pretty_name = 'TGS-REQ'
+
tagSet = univ.Sequence.tagSet.tagExplicitly(
tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 12)
)
class APREQ(univ.Sequence):
+ pretty_name = 'AP-REQ'
+
tagSet = univ.Sequence.tagSet.tagExplicitly(
tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 14)
)
class KRBPriv(univ.Sequence):
+ pretty_name = 'KRBPRiv'
+
tagSet = univ.Sequence.tagSet.tagExplicitly(
tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 21)
)
+
+
+def decode_proxymessage(data):
+ try:
+ req, tail = decoder.decode(data, asn1Spec=ProxyMessage())
+ except error.PyAsn1Error as e:
+ raise ASN1ParsingError(e)
+ if tail:
+ raise ParsingError("Invalid request.")
+ message = req.getComponentByName('message').asOctets()
+ realm = req.getComponentByName('realm')
+ if realm.hasValue():
+ try: # Python 3.x
+ realm = str(realm, "utf-8")
+ except TypeError: # Python 2.x
+ realm = str(realm)
+ else:
+ realm = None
+ flags = req.getComponentByName('flags')
+ flags = int(flags) if flags.hasValue() else None
+ return message, realm, flags
+
+
+def encode_proxymessage(data):
+ rep = ProxyMessage()
+ rep.setComponentByName('message', data)
+ return encoder.encode(rep)
+
+
+def try_decode(data, cls):
+ try:
+ req, tail = decoder.decode(data, asn1Spec=cls())
+ except error.PyAsn1Error as e:
+ raise ASN1ParsingError(e)
+ if tail:
+ raise ParsingError("%s request has %d extra bytes." %
+ (cls.pretty_name, len(tail)))
+ return cls.pretty_name
=====================================
setup.py
=====================================
@@ -29,7 +29,7 @@ from setuptools import setup
SETUPTOOLS_VERSION = tuple(int(v) for v in setuptools.__version__.split("."))
install_requires = [
- 'pyasn1',
+ 'asn1crypto>=0.23',
]
extras_require = {
@@ -57,9 +57,9 @@ def read(fname):
setup(
name="kdcproxy",
- version="0.3.3",
- author="Nalin Dahyabhai, Nathaniel McCallum, Christian Heimes",
- author_email="nalin at redhat.com, npmccallum at redhat.com, cheimes at redhat.com",
+ version="0.4",
+ author="Nalin Dahyabhai, Nathaniel McCallum, Christian Heimes, Robbie Harwood",
+ author_email="nalin at redhat.com, npmccallum at redhat.com, cheimes at redhat.com, rharwood at redhat.com",
description=("A kerberos KDC HTTP proxy WSGI module."),
license="MIT",
keywords="krb5 proxy http https kerberos",
=====================================
tests.py
=====================================
@@ -20,6 +20,7 @@
# THE SOFTWARE.
import os
+import sys
import unittest
from base64 import b64decode
try:
@@ -32,16 +33,14 @@ from dns.rdataclass import IN as RDCLASS_IN
from dns.rdatatype import SRV as RDTYPE_SRV
from dns.rdtypes.IN.SRV import SRV
-from pyasn1.codec.der import decoder, encoder
-
from webtest import TestApp as WebTestApp
import kdcproxy
-# from kdcproxy import asn1
from kdcproxy import codec
from kdcproxy import config
from kdcproxy.config import mit
+
HERE = os.path.dirname(os.path.abspath(__file__))
KRB5_CONFIG = os.path.join(HERE, 'tests.krb5.conf')
@@ -184,18 +183,24 @@ class KDCProxyCodecTests(unittest.TestCase):
""")
def assert_decode(self, data, cls):
+ # manual decode
+ request, realm, _ = codec.asn1mod.decode_proxymessage(data)
+ self.assertEqual(realm, self.realm)
+ inst = cls.parse_request(realm, request)
+ self.assertIsInstance(inst, cls)
+ self.assertEqual(inst.realm, self.realm)
+ self.assertEqual(inst.request, request)
+ if cls is codec.KPASSWDProxyRequest:
+ self.assertEqual(inst.version, 1)
+ # codec decode
outer = codec.decode(data)
self.assertEqual(outer.realm, self.realm)
self.assertIsInstance(outer, cls)
- if cls is not codec.KPASSWDProxyRequest:
- inner, err = decoder.decode(outer.request[outer.OFFSET:],
- asn1Spec=outer.TYPE())
- if err: # pragma: no cover
- self.fail(err)
- self.assertIsInstance(inner, outer.TYPE)
- der = encoder.encode(inner)
- encoded = codec.encode(der)
- self.assertIsInstance(encoded, bytes)
+ # re-decode
+ der = codec.encode(outer.request)
+ self.assertIsInstance(der, bytes)
+ decoded = codec.decode(der)
+ self.assertIsInstance(decoded, cls)
return outer
def test_asreq(self):
@@ -216,6 +221,21 @@ class KDCProxyCodecTests(unittest.TestCase):
'FREEIPA.LOCAL KPASSWD-REQ (603 bytes) (version 0x0001)'
)
+ def test_asn1mod(self):
+ modmap = {
+ 'asn1crypto': (
+ 'kdcproxy.parse_asn1crypto', 'kdcproxy.parse_pyasn1'),
+ 'pyasn1': (
+ 'kdcproxy.parse_pyasn1', 'kdcproxy.parse_asn1crypto'),
+ }
+ asn1mod = os.environ.get('KDCPROXY_ASN1MOD', None)
+ if asn1mod is None:
+ self.fail("Tests require KDCPROXY_ASN1MOD env var.")
+ self.assertIn(asn1mod, modmap)
+ mod, opposite = modmap[asn1mod]
+ self.assertIn(mod, set(sys.modules))
+ self.assertNotIn(opposite, set(sys.modules))
+
class KDCProxyConfigTests(unittest.TestCase):
=====================================
tox.ini
=====================================
@@ -1,19 +1,27 @@
[tox]
minversion = 2.3.1
-envlist = py27,py34,py35,py36,pep8,py3pep8,doc
+envlist = {py27,py35,py36}-{asn1crypto,pyasn1},pep8,py3pep8,doc,coverage-report
skip_missing_interpreters = true
[testenv]
deps =
- .[tests]
+ .[tests]
+ py27: mock
+ pyasn1: pyasn1
+ asn1crypto: asn1crypto>=0.23
+setenv =
+ asn1crypto: KDCPROXY_ASN1MOD=asn1crypto
+ pyasn1: KDCPROXY_ASN1MOD=pyasn1
commands =
- {envpython} -m coverage run -m pytest --capture=no --strict {posargs}
- {envpython} -m coverage report -m
+ {envpython} -m coverage run --parallel \
+ -m pytest --capture=no --strict {posargs}
-[testenv:py27]
-deps =
- .[tests]
- mock
+[testenv:coverage-report]
+deps = coverage
+skip_install = true
+commands =
+ {envpython} -m coverage combine
+ {envpython} -m coverage report --show-missing
[testenv:pep8]
basepython = python2.7
View it on GitLab: https://salsa.debian.org/freeipa-team/python-kdcproxy/compare/d2a2e11a09584f4e8df60b3ca69c2d161565a547...898760d01a3bdb3de2c6862cf6ed285790bae1cd
--
View it on GitLab: https://salsa.debian.org/freeipa-team/python-kdcproxy/compare/d2a2e11a09584f4e8df60b3ca69c2d161565a547...898760d01a3bdb3de2c6862cf6ed285790bae1cd
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-freeipa-devel/attachments/20181017/5cf89dce/attachment-0001.html>
More information about the Pkg-freeipa-devel
mailing list