[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