Repository: fedora-infra/bodhi

Issue:
bodhi.client.bindings.BodhiClientException: You must provide a captcha_key.
I hit this traceback today:

```
bodhi updates comment --karma 1 vim-8.0.1030-1.fc26 "LGTM"
Traceback (most recent call last):
  File "/usr/bin/bodhi", line 11, in <module>
    load_entry_point('bodhi-client==2.8.1', 'console_scripts', 'bodhi')()
  File "/usr/lib/python2.7/site-packages/click/core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "/usr/lib/python2.7/site-packages/click/core.py", line 697, in main
    rv = self.invoke(ctx)
  File "/usr/lib/python2.7/site-packages/click/core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/lib/python2.7/site-packages/click/core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/lib/python2.7/site-packages/click/core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/lib/python2.7/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/usr/lib/python2.7/site-packages/bodhi/client/__init__.py", line 336, in comment
    resp = client.comment(update, text, karma)
  File "/usr/lib/python2.7/site-packages/bodhi/client/bindings.py", line 97, in wrapper
    raise BodhiClientException(problems)
bodhi.client.bindings.BodhiClientException: You must provide a captcha_key.
```

We need [```bodhi.client.bindings.errorhandled```](https://github.com/fedora-infra/bodhi/blob/2.10.1/bodhi/client/bindings.py#L70-L102) to catch this problem and retry when it is present, similarly to how it does for ```AuthError```.


Discussion:

Resolving patch:
diff --git a/bodhi/client/bindings.py b/bodhi/client/bindings.py
--- a/bodhi/client/bindings.py
+++ b/bodhi/client/bindings.py
@@ -73,13 +73,23 @@ def errorhandled(method):
     def wrapper(*args, **kwargs):
         try:
             result = method(*args, **kwargs)
+            # Bodhi allows comments to be written by unauthenticated users if they solve a Captcha.
+            # Due to this, an authentication error is not raised by the server if the client fails
+            # to authenticate for any reason, and instead an error about needing a captcha key is
+            # presented instead. If we see that error, we can just raise an AuthError to trigger the
+            # retry logic in the exception handler below.
+            if 'errors' in result:
+                for error in result['errors']:
+                    if 'name' in error and error['name'] == 'captcha_key':
+                        raise AuthError('Captcha key needed.')
         except AuthError:
-            # An AuthError can be raised for three different reasons:
+            # An AuthError can be raised for four different reasons:
             #
             # 0) The password is wrong.
             # 1) The session cookies are expired. fedora.python does not handle this automatically.
             # 2) The session cookies are not expired, but are no longer valid (for example, this can
             #    happen if the server's auth secret has changed.)
+            # 3) The client received a captcha_key error, as described in the try block above.
             #
             # We don't know the difference between the cases here, but case #1 is fairly common and
             # we can work around it and case #2 by removing the session cookies and csrf token and


Test patch:
diff --git a/bodhi/tests/client/test_bindings.py b/bodhi/tests/client/test_bindings.py
--- a/bodhi/tests/client/test_bindings.py
+++ b/bodhi/tests/client/test_bindings.py
@@ -1001,6 +1001,38 @@ def wrong_password_lol(a_fake_self):
         self.assertTrue(a_fake_self.csrf_token is None)
         self.assertEqual(a_fake_self.call_count, 2)
 
+    def test_retry_on_captcha_key_failure(self):
+        """
+        Test the decorator when the wrapped method returns a captch_key error.
+
+        This test ensures that the decorator will retry the wrapped method if it returns a
+        captcha_key error, after clearing cookies and the csrf token.
+
+        This test was written to assert the fix for
+        https://github.com/fedora-infra/bodhi/issues/1787
+        """
+        a_fake_self = mock.MagicMock()
+        a_fake_self.csrf_token = 'some_token'
+        a_fake_self.call_count = 0
+
+        @bindings.errorhandled
+        def captcha_plz(a_fake_self):
+            a_fake_self.call_count = a_fake_self.call_count + 1
+
+            # Fail on the first call with a captcha_key error to simulate unauth'd user on a
+            # comment.
+            if a_fake_self.call_count == 1:
+                return {'errors': [{'name': 'captcha_key'}]}
+
+            return 'here you go'
+
+        # No Exception should be raised.
+        captcha_plz(a_fake_self)
+
+        a_fake_self._session.cookies.clear.assert_called_once_with()
+        self.assertTrue(a_fake_self.csrf_token is None)
+        self.assertEqual(a_fake_self.call_count, 2)
+
     def test_success(self):
         """
         Test the decorator for the success case.