Repository: certbot/certbot

Issue:
[PATCH] >=0.22.0: Bug in acme.Client.__init__ breaks acme.Client.register
Hi!

There is a bug in `acme.Client.__init__`. When called with `net=None`, `self.net` is first initialited with a new instance of `ClientNetwork` but then `net` (rather than `self.net`) is pass to `ClientBase.__init__` which sets `self.net` back to None. The breaking code was introduced in 9baf75d6c887a9b69bd92c7bffe585f091c9b08e and is this:
```
class ClientBase(object):  # pylint: disable=too-many-instance-attributes
    [..]
    def __init__(self, directory, net, acme_version):
        [..]
        self.net = net
[..]
class Client(ClientBase):
    [..]
    def __init__(self, directory, key, alg=jose.RS256, verify_ssl=True,
                 net=None):
        [..]
        self.net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl) if net is None else net
        [..]
        super(Client, self).__init__(directory=directory,
            net=net, acme_version=1)
```
As a result, calling `.register(...)` on the client will raise `AttributeError: 'NoneType' object has no attribute 'post'` which breaks wile, see https://github.com/costela/wile/issues/34.

One way to fix the issue is to change `net=net` to `self.net=net` in the line using `super`. I could provide a pull request to do that if that's the way you want to patch it.

What do you think?
[PATCH] >=0.22.0: Bug in acme.Client.__init__ breaks acme.Client.register
Hi!

There is a bug in `acme.Client.__init__`. When called with `net=None`, `self.net` is first initialited with a new instance of `ClientNetwork` but then `net` (rather than `self.net`) is pass to `ClientBase.__init__` which sets `self.net` back to None. The breaking code was introduced in 9baf75d6c887a9b69bd92c7bffe585f091c9b08e and is this:
```
class ClientBase(object):  # pylint: disable=too-many-instance-attributes
    [..]
    def __init__(self, directory, net, acme_version):
        [..]
        self.net = net
[..]
class Client(ClientBase):
    [..]
    def __init__(self, directory, key, alg=jose.RS256, verify_ssl=True,
                 net=None):
        [..]
        self.net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl) if net is None else net
        [..]
        super(Client, self).__init__(directory=directory,
            net=net, acme_version=1)
```
As a result, calling `.register(...)` on the client will raise `AttributeError: 'NoneType' object has no attribute 'post'` which breaks wile, see https://github.com/costela/wile/issues/34.

One way to fix the issue is to change `net=net` to `self.net=net` in the line using `super`. I could provide a pull request to do that if that's the way you want to patch it.

What do you think?


Discussion:
You're right! Sorry for the trouble @hartwork. We'll include a patch fixing this in our 0.22.1 release which we're planning on releasing early next week.

I don't have too strong of a preference on how this is fixed and we'd gladly accept a pull request. The way I'd do it personally is always set `self.net` for the first time in `ClientBase` so the relevant code in `Client.__init__` looks something like:
```
if net is None:
    net = ClientNetwork(...)
[...]
super(Client, self).__init__(directory=directory,
    net=net, acme_version=1)
```
If you like your approach better, it's pretty much equivalent so feel free to go with that.

Bonus points if you add a test to `acme/acme/client_test.py` to prevent regressions here. Probably the best way to do that is through using `mock.patch` to mock out `acme.client.ClientNetwork` during the new test.
You're right! Sorry for the trouble @hartwork. We'll include a patch fixing this in our 0.22.1 release which we're planning on releasing early next week.

I don't have too strong of a preference on how this is fixed and we'd gladly accept a pull request. The way I'd do it personally is always set `self.net` for the first time in `ClientBase` so the relevant code in `Client.__init__` looks something like:
```
if net is None:
    net = ClientNetwork(...)
[...]
super(Client, self).__init__(directory=directory,
    net=net, acme_version=1)
```
If you like your approach better, it's pretty much equivalent so feel free to go with that.

Bonus points if you add a test to `acme/acme/client_test.py` to prevent regressions here. Probably the best way to do that is through using `mock.patch` to mock out `acme.client.ClientNetwork` during the new test.

Resolving patch:
diff --git a/acme/acme/client.py b/acme/acme/client.py
--- a/acme/acme/client.py
+++ b/acme/acme/client.py
@@ -259,11 +259,12 @@ def __init__(self, directory, key, alg=jose.RS256, verify_ssl=True,
         """
         # pylint: disable=too-many-arguments
         self.key = key
-        self.net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl) if net is None else net
+        if net is None:
+            net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl)
 
         if isinstance(directory, six.string_types):
             directory = messages.Directory.from_json(
-                self.net.get(directory).json())
+                net.get(directory).json())
         super(Client, self).__init__(directory=directory,
             net=net, acme_version=1)
 


Test patch:
diff --git a/acme/acme/client_test.py b/acme/acme/client_test.py
--- a/acme/acme/client_test.py
+++ b/acme/acme/client_test.py
@@ -299,6 +299,16 @@ def test_init_downloads_directory(self):
             directory=uri, key=KEY, alg=jose.RS256, net=self.net)
         self.net.get.assert_called_once_with(uri)
 
+    @mock.patch('acme.client.ClientNetwork')
+    def test_init_without_net(self, mock_net):
+        mock_net.return_value = mock.sentinel.net
+        alg = jose.RS256
+        from acme.client import Client
+        self.client = Client(
+            directory=self.directory, key=KEY, alg=alg)
+        mock_net.called_once_with(KEY, alg=alg, verify_ssl=True)
+        self.assertEqual(self.client.net, mock.sentinel.net)
+
     def test_register(self):
         # "Instance of 'Field' has no to_json/update member" bug:
         # pylint: disable=no-member