Hello community,

here is the log from the commit of package python-acme for openSUSE:Factory 
checked in at 2017-06-12 15:34:41
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-acme (Old)
 and      /work/SRC/openSUSE:Factory/.python-acme.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-acme"

Mon Jun 12 15:34:41 2017 rev:8 rq:502854 version:0.15.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-acme/python-acme.changes  2017-06-02 
10:34:24.270815408 +0200
+++ /work/SRC/openSUSE:Factory/.python-acme.new/python-acme.changes     
2017-06-12 15:34:46.972464474 +0200
@@ -1,0 +2,6 @@
+Sun Jun 11 08:48:15 UTC 2017 - [email protected]
+
+- update to 0.15.0
+  - No changelog from upstream
+
+-------------------------------------------------------------------

Old:
----
  acme-0.14.2.tar.gz
  acme-0.14.2.tar.gz.asc

New:
----
  acme-0.15.0.tar.gz
  acme-0.15.0.tar.gz.asc

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-acme.spec ++++++
--- /var/tmp/diff_new_pack.sBsTRS/_old  2017-06-12 15:34:48.816204429 +0200
+++ /var/tmp/diff_new_pack.sBsTRS/_new  2017-06-12 15:34:48.816204429 +0200
@@ -18,7 +18,7 @@
 
 %define libname acme
 Name:           python-%{libname}
-Version:        0.14.2
+Version:        0.15.0
 Release:        0
 Summary:        Python library for the ACME protocol
 License:        Apache-2.0

++++++ acme-0.14.2.tar.gz -> acme-0.15.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.14.2/PKG-INFO new/acme-0.15.0/PKG-INFO
--- old/acme-0.14.2/PKG-INFO    2017-05-25 23:12:54.000000000 +0200
+++ new/acme-0.15.0/PKG-INFO    2017-06-08 18:26:22.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: acme
-Version: 0.14.2
+Version: 0.15.0
 Summary: ACME protocol implementation in Python
 Home-page: https://github.com/letsencrypt/letsencrypt
 Author: Certbot Project
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.14.2/acme/client.py 
new/acme-0.15.0/acme/client.py
--- old/acme-0.14.2/acme/client.py      2017-05-25 23:12:46.000000000 +0200
+++ new/acme-0.15.0/acme/client.py      2017-06-08 18:26:04.000000000 +0200
@@ -564,6 +564,9 @@
         except ValueError:
             jobj = None
 
+        if response.status_code == 409:
+            raise errors.ConflictError(response.headers.get('Location'))
+
         if not response.ok:
             if jobj is not None:
                 if response_ct != cls.JSON_ERROR_CONTENT_TYPE:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.14.2/acme/client_test.py 
new/acme-0.15.0/acme/client_test.py
--- old/acme-0.14.2/acme/client_test.py 2017-05-25 23:12:46.000000000 +0200
+++ new/acme-0.15.0/acme/client_test.py 2017-06-08 18:26:04.000000000 +0200
@@ -513,6 +513,12 @@
             self.assertEqual(
                 self.response, self.net._check_response(self.response))
 
+    def test_check_response_conflict(self):
+        self.response.ok = False
+        self.response.status_code = 409
+        # pylint: disable=protected-access
+        self.assertRaises(errors.ConflictError, self.net._check_response, 
self.response)
+
     def test_check_response_jobj(self):
         self.response.json.return_value = {}
         for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.14.2/acme/crypto_util.py 
new/acme-0.15.0/acme/crypto_util.py
--- old/acme-0.14.2/acme/crypto_util.py 2017-05-25 23:12:46.000000000 +0200
+++ new/acme-0.15.0/acme/crypto_util.py 2017-06-08 18:26:04.000000000 +0200
@@ -107,7 +107,7 @@
 
 
 def probe_sni(name, host, port=443, timeout=300,
-              method=_DEFAULT_TLSSNI01_SSL_METHOD, source_address=('0', 0)):
+              method=_DEFAULT_TLSSNI01_SSL_METHOD, source_address=('', 0)):
     """Probe SNI server for SSL certificate.
 
     :param bytes name: Byte string to send as the server name in the
@@ -132,9 +132,14 @@
     socket_kwargs = {} if sys.version_info < (2, 7) else {
         'source_address': source_address}
 
+    host_protocol_agnostic = None if host == '::' or host == '0' else host
+
     try:
         # pylint: disable=star-args
-        sock = socket.create_connection((host, port), **socket_kwargs)
+        logger.debug("Attempting to connect to %s:%d%s.", 
host_protocol_agnostic, port,
+            " from {0}:{1}".format(source_address[0], source_address[1]) if \
+            socket_kwargs else "")
+        sock = socket.create_connection((host_protocol_agnostic, port), 
**socket_kwargs)
     except socket.error as error:
         raise errors.Error(error)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.14.2/acme/errors.py 
new/acme-0.15.0/acme/errors.py
--- old/acme-0.14.2/acme/errors.py      2017-05-25 23:12:46.000000000 +0200
+++ new/acme-0.15.0/acme/errors.py      2017-06-08 18:26:04.000000000 +0200
@@ -82,3 +82,14 @@
     def __repr__(self):
         return '{0}(exhausted={1!r}, updated={2!r})'.format(
             self.__class__.__name__, self.exhausted, self.updated)
+
+class ConflictError(ClientError):
+    """Error for when the server returns a 409 (Conflict) HTTP status.
+
+    In the version of ACME implemented by Boulder, this is used to find an
+    account if you only have the private key, but don't know the account URL.
+    """
+    def __init__(self, location):
+        self.location = location
+        super(ConflictError, self).__init__()
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.14.2/acme/jose/jws.py 
new/acme-0.15.0/acme/jose/jws.py
--- old/acme-0.14.2/acme/jose/jws.py    2017-05-25 23:12:46.000000000 +0200
+++ new/acme-0.15.0/acme/jose/jws.py    2017-06-08 18:26:04.000000000 +0200
@@ -222,7 +222,8 @@
 
         protected_params = {}
         for header in protect:
-            protected_params[header] = header_params.pop(header)
+            if header in header_params:
+                protected_params[header] = header_params.pop(header)
         if protected_params:
             # pylint: disable=star-args
             protected = cls.header_cls(**protected_params).json_dumps()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.14.2/acme/jws.py new/acme-0.15.0/acme/jws.py
--- old/acme-0.14.2/acme/jws.py 2017-05-25 23:12:46.000000000 +0200
+++ new/acme-0.15.0/acme/jws.py 2017-06-08 18:26:04.000000000 +0200
@@ -49,6 +49,6 @@
         # jwk field if kid is not provided.
         include_jwk = kid is None
         return super(JWS, cls).sign(payload, key=key, alg=alg,
-                                    protect=frozenset(['nonce', 'url', 'kid']),
+                                    protect=frozenset(['nonce', 'url', 'kid', 
'jwk', 'alg']),
                                     nonce=nonce, url=url, kid=kid,
                                     include_jwk=include_jwk)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.14.2/acme/standalone.py 
new/acme-0.15.0/acme/standalone.py
--- old/acme-0.14.2/acme/standalone.py  2017-05-25 23:12:46.000000000 +0200
+++ new/acme-0.15.0/acme/standalone.py  2017-06-08 18:26:04.000000000 +0200
@@ -4,7 +4,9 @@
 import functools
 import logging
 import os
+import socket
 import sys
+import threading
 
 from six.moves import BaseHTTPServer  # type: ignore  # pylint: 
disable=import-error
 from six.moves import http_client  # pylint: disable=import-error
@@ -26,6 +28,11 @@
     """Generic TLS Server."""
 
     def __init__(self, *args, **kwargs):
+        self.ipv6 = kwargs.pop("ipv6", False)
+        if self.ipv6:
+            self.address_family = socket.AF_INET6
+        else:
+            self.address_family = socket.AF_INET
         self.certs = kwargs.pop("certs", {})
         self.method = kwargs.pop(
             # pylint: disable=protected-access
@@ -49,12 +56,81 @@
     allow_reuse_address = True
 
 
+class BaseDualNetworkedServers(object):
+    """Base class for a pair of IPv6 and IPv4 servers that tries to do 
everything
+       it's asked for both servers, but where failures in one server don't
+       affect the other.
+
+       If two servers are instantiated, they will serve on the same port.
+       """
+
+    def __init__(self, ServerClass, server_address, *remaining_args, **kwargs):
+        port = server_address[1]
+        self.threads = []
+        self.servers = []
+
+        # Must try True first.
+        # Ubuntu, for example, will fail to bind to IPv4 if we've already bound
+        # to IPv6. But that's ok, since it will accept IPv4 connections on the 
IPv6
+        # socket. On the other hand, FreeBSD will successfully bind to IPv4 on 
the
+        # same port, which means that server will accept the IPv4 connections.
+        # If Python is compiled without IPv6, we'll error out but (probably) 
successfully
+        # create the IPv4 server.
+        for ip_version in [True, False]:
+            try:
+                kwargs["ipv6"] = ip_version
+                new_address = (server_address[0],) + (port,) + 
server_address[2:]
+                new_args = (new_address,) + remaining_args
+                server = ServerClass(*new_args, **kwargs) # pylint: 
disable=star-args
+            except socket.error:
+                logger.debug("Failed to bind to %s:%s using %s", 
new_address[0],
+                    new_address[1], "IPv6" if ip_version else "IPv4")
+            else:
+                self.servers.append(server)
+                # If two servers are set up and port 0 was passed in, ensure 
we always
+                # bind to the same port for both servers.
+                port = server.socket.getsockname()[1]
+        if len(self.servers) == 0:
+            raise socket.error("Could not bind to IPv4 or IPv6.")
+
+    def serve_forever(self):
+        """Wraps socketserver.TCPServer.serve_forever"""
+        for server in self.servers:
+            thread = threading.Thread(
+                # pylint: disable=no-member
+                target=server.serve_forever)
+            thread.start()
+            self.threads.append(thread)
+
+    def getsocknames(self):
+        """Wraps socketserver.TCPServer.socket.getsockname"""
+        return [server.socket.getsockname() for server in self.servers]
+
+    def shutdown_and_server_close(self):
+        """Wraps socketserver.TCPServer.shutdown, 
socketserver.TCPServer.server_close, and
+           threading.Thread.join"""
+        for server in self.servers:
+            server.shutdown()
+            server.server_close()
+        for thread in self.threads:
+            thread.join()
+        self.threads = []
+
+
 class TLSSNI01Server(TLSServer, ACMEServerMixin):
     """TLSSNI01 Server."""
 
-    def __init__(self, server_address, certs):
+    def __init__(self, server_address, certs, ipv6=False):
         TLSServer.__init__(
-            self, server_address, BaseRequestHandlerWithLogging, certs=certs)
+            self, server_address, BaseRequestHandlerWithLogging, certs=certs, 
ipv6=ipv6)
+
+
+class TLSSNI01DualNetworkedServers(BaseDualNetworkedServers):
+    """TLSSNI01Server Wrapper. Tries everything for both. Failures for one 
don't
+       affect the other."""
+
+    def __init__(self, *args, **kwargs):
+        BaseDualNetworkedServers.__init__(self, TLSSNI01Server, *args, 
**kwargs)
 
 
 class BaseRequestHandlerWithLogging(socketserver.BaseRequestHandler):
@@ -70,13 +146,33 @@
         socketserver.BaseRequestHandler.handle(self)
 
 
-class HTTP01Server(BaseHTTPServer.HTTPServer, ACMEServerMixin):
+class HTTPServer(BaseHTTPServer.HTTPServer):
+    """Generic HTTP Server."""
+
+    def __init__(self, *args, **kwargs):
+        self.ipv6 = kwargs.pop("ipv6", False)
+        if self.ipv6:
+            self.address_family = socket.AF_INET6
+        else:
+            self.address_family = socket.AF_INET
+        BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
+
+
+class HTTP01Server(HTTPServer, ACMEServerMixin):
     """HTTP01 Server."""
 
-    def __init__(self, server_address, resources):
-        BaseHTTPServer.HTTPServer.__init__(
+    def __init__(self, server_address, resources, ipv6=False):
+        HTTPServer.__init__(
             self, server_address, HTTP01RequestHandler.partial_init(
-                simple_http_resources=resources))
+                simple_http_resources=resources), ipv6=ipv6)
+
+
+class HTTP01DualNetworkedServers(BaseDualNetworkedServers):
+    """HTTP01Server Wrapper. Tries everything for both. Failures for one don't
+       affect the other."""
+
+    def __init__(self, *args, **kwargs):
+        BaseDualNetworkedServers.__init__(self, HTTP01Server, *args, **kwargs)
 
 
 class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.14.2/acme/standalone_test.py 
new/acme-0.15.0/acme/standalone_test.py
--- old/acme-0.14.2/acme/standalone_test.py     2017-05-25 23:12:46.000000000 
+0200
+++ new/acme-0.15.0/acme/standalone_test.py     2017-06-08 18:26:04.000000000 
+0200
@@ -1,6 +1,7 @@
 """Tests for acme.standalone."""
 import os
 import shutil
+import socket
 import threading
 import tempfile
 import time
@@ -9,6 +10,7 @@
 from six.moves import http_client  # pylint: disable=import-error
 from six.moves import socketserver  # type: ignore  # pylint: 
disable=import-error
 
+import mock
 import requests
 
 from acme import challenges
@@ -29,6 +31,13 @@
             ('', 0), socketserver.BaseRequestHandler, bind_and_activate=True)
         server.server_close()  # pylint: disable=no-member
 
+    def test_ipv6(self):
+        if socket.has_ipv6:
+            from acme.standalone import TLSServer
+            server = TLSServer(
+                ('', 0), socketserver.BaseRequestHandler, 
bind_and_activate=True, ipv6=True)
+            server.server_close()  # pylint: disable=no-member
+
 
 class TLSSNI01ServerTest(unittest.TestCase):
     """Test for acme.standalone.TLSSNI01Server."""
@@ -112,6 +121,136 @@
         self.assertFalse(self._test_http01(add=False))
 
 
+class BaseDualNetworkedServersTest(unittest.TestCase):
+    """Test for acme.standalone.BaseDualNetworkedServers."""
+
+    _multiprocess_can_split_ = True
+
+    class SingleProtocolServer(socketserver.TCPServer):
+        """Server that only serves on a single protocol. FreeBSD has this 
behavior for AF_INET6."""
+        def __init__(self, *args, **kwargs):
+            ipv6 = kwargs.pop("ipv6", False)
+            if ipv6:
+                self.address_family = socket.AF_INET6
+                kwargs["bind_and_activate"] = False
+            else:
+                self.address_family = socket.AF_INET
+            socketserver.TCPServer.__init__(self, *args, **kwargs)
+            if ipv6:
+                # pylint: disable=no-member
+                self.socket.setsockopt(socket.IPPROTO_IPV6, 
socket.IPV6_V6ONLY, 1)
+                try:
+                    self.server_bind()
+                    self.server_activate()
+                except:
+                    self.server_close()
+                    raise
+
+    @mock.patch("socket.socket.bind")
+    def test_fail_to_bind(self, mock_bind):
+        mock_bind.side_effect = socket.error
+        from acme.standalone import BaseDualNetworkedServers
+        self.assertRaises(socket.error, BaseDualNetworkedServers,
+            BaseDualNetworkedServersTest.SingleProtocolServer,
+            ("", 0),
+            socketserver.BaseRequestHandler)
+
+    def test_ports_equal(self):
+        from acme.standalone import BaseDualNetworkedServers
+        servers = BaseDualNetworkedServers(
+            BaseDualNetworkedServersTest.SingleProtocolServer,
+            ("", 0),
+            socketserver.BaseRequestHandler)
+        socknames = servers.getsocknames()
+        prev_port = None
+        # assert ports are equal
+        for sockname in socknames:
+            port = sockname[1]
+            if prev_port:
+                self.assertEqual(prev_port, port)
+            prev_port = port
+
+
+class TLSSNI01DualNetworkedServersTest(unittest.TestCase):
+    """Test for acme.standalone.TLSSNI01DualNetworkedServers."""
+
+    _multiprocess_can_split_ = True
+
+    def setUp(self):
+        self.certs = {b'localhost': (
+            test_util.load_pyopenssl_private_key('rsa2048_key.pem'),
+            test_util.load_cert('rsa2048_cert.pem'),
+        )}
+        from acme.standalone import TLSSNI01DualNetworkedServers
+        self.servers = TLSSNI01DualNetworkedServers(("", 0), certs=self.certs)
+        self.servers.serve_forever()
+
+    def tearDown(self):
+        self.servers.shutdown_and_server_close()
+
+    def test_connect(self):
+        socknames = self.servers.getsocknames()
+        # connect to all addresses
+        for sockname in socknames:
+            host, port = sockname[:2]
+            cert = crypto_util.probe_sni(
+                b'localhost', host=host, port=port, timeout=1)
+            self.assertEqual(jose.ComparableX509(cert),
+                             jose.ComparableX509(self.certs[b'localhost'][1]))
+
+
+class HTTP01DualNetworkedServersTest(unittest.TestCase):
+    """Tests for acme.standalone.HTTP01DualNetworkedServers."""
+
+    _multiprocess_can_split_ = True
+
+    def setUp(self):
+        self.account_key = jose.JWK.load(
+            test_util.load_vector('rsa1024_key.pem'))
+        self.resources = set()
+
+        from acme.standalone import HTTP01DualNetworkedServers
+        self.servers = HTTP01DualNetworkedServers(('', 0), 
resources=self.resources)
+
+        # pylint: disable=no-member
+        self.port = self.servers.getsocknames()[0][1]
+        self.servers.serve_forever()
+
+    def tearDown(self):
+        self.servers.shutdown_and_server_close()
+
+    def test_index(self):
+        response = requests.get(
+            'http://localhost:{0}'.format(self.port), verify=False)
+        self.assertEqual(
+            response.text, 'ACME client standalone challenge solver')
+        self.assertTrue(response.ok)
+
+    def test_404(self):
+        response = requests.get(
+            'http://localhost:{0}/foo'.format(self.port), verify=False)
+        self.assertEqual(response.status_code, http_client.NOT_FOUND)
+
+    def _test_http01(self, add):
+        chall = challenges.HTTP01(token=(b'x' * 16))
+        response, validation = chall.response_and_validation(self.account_key)
+
+        from acme.standalone import HTTP01RequestHandler
+        resource = HTTP01RequestHandler.HTTP01Resource(
+            chall=chall, response=response, validation=validation)
+        if add:
+            self.resources.add(resource)
+        return resource.response.simple_verify(
+            resource.chall, 'localhost', self.account_key.public_key(),
+            port=self.port)
+
+    def test_http01_found(self):
+        self.assertTrue(self._test_http01(add=True))
+
+    def test_http01_not_found(self):
+        self.assertFalse(self._test_http01(add=False))
+
+
 class TestSimpleTLSSNI01Server(unittest.TestCase):
     """Tests for acme.standalone.simple_tls_sni_01_server."""
 
@@ -137,7 +276,6 @@
         )
         self.old_cwd = os.getcwd()
         os.chdir(self.test_cwd)
-        self.thread.start()
 
     def tearDown(self):
         os.chdir(self.old_cwd)
@@ -146,13 +284,12 @@
 
     def test_it(self):
         max_attempts = 5
-        while max_attempts:
-            max_attempts -= 1
+        for attempt in range(max_attempts):
             try:
                 cert = crypto_util.probe_sni(
                     b'localhost', b'0.0.0.0', self.port)
             except errors.Error:
-                self.assertTrue(max_attempts > 0, "Timeout!")
+                self.assertTrue(attempt + 1 < max_attempts, "Timeout!")
                 time.sleep(1)  # wait until thread starts
             else:
                 self.assertEqual(jose.ComparableX509(cert),
@@ -160,6 +297,11 @@
                                      'rsa2048_cert.pem'))
                 break
 
+            if attempt == 0:
+                # the first attempt is always meant to fail, so we can test
+                # the socket failure code-path for probe_sni, as well
+                self.thread.start()
+
 
 if __name__ == "__main__":
     unittest.main()  # pragma: no cover
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.14.2/acme.egg-info/PKG-INFO 
new/acme-0.15.0/acme.egg-info/PKG-INFO
--- old/acme-0.14.2/acme.egg-info/PKG-INFO      2017-05-25 23:12:54.000000000 
+0200
+++ new/acme-0.15.0/acme.egg-info/PKG-INFO      2017-06-08 18:26:22.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: acme
-Version: 0.14.2
+Version: 0.15.0
 Summary: ACME protocol implementation in Python
 Home-page: https://github.com/letsencrypt/letsencrypt
 Author: Certbot Project
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.14.2/setup.py new/acme-0.15.0/setup.py
--- old/acme-0.14.2/setup.py    2017-05-25 23:12:46.000000000 +0200
+++ new/acme-0.15.0/setup.py    2017-06-08 18:26:04.000000000 +0200
@@ -4,7 +4,7 @@
 from setuptools import find_packages
 
 
-version = '0.14.2'
+version = '0.15.0'
 
 # Please update tox.ini when modifying dependency version requirements
 install_requires = [



Reply via email to