Repository: readthedocs/readthedocs.org

Issue:
csrf required for triggering build via http auth authenticated webhook
## Details

Hi,

In OpenStack, we've been using authentication with the webhook endpoints to trigger builds.

The way this has been working is that projects add a common ``openstackci`` user as an admin, and then in their project configuration inside our platform they add their webhook URL.

When new releases are made, our CI system uses the ``openstackci`` account (with the password only known to it) to authenticate and trigger RTD builds for the releasing project with a POST.  The exact code that does this is https://git.openstack.org/cgit/openstack-infra/zuul-jobs/tree/roles/trigger-readthedocs/tasks/main.yaml#n11

But in essence that boils down to (for, say jenkins-job-builder project)

```
curl --request POST -u openstackci:OURPASSWORD https://readthedocs.org/api/v2/webhook/jenkins-job-builder/47271/
```

Our jobs starting failing around 2018-12-05 (http://zuul.openstack.org/builds?job_name=trigger-readthedocs-webhook).  The jobs don't log the output of this POST so we don't leak the password into logs.

However, testing manually, the responses indicate a content-length is now required, and a referer, which ends up with

```
$ curl --request POST -H "Content-Length:0" -u openstackci:PASSWORD -e https://readthedocs.org/api/v2/webhook/jenkins-jo2/webhook/jenkins-job-builder/47271/ https://readthedocs.org/api/v2/webhook/jenkins-jo2/webhook/jenkins-job-builder/47271/
{"detail":"CSRF Failed: CSRF cookie not set."}
```

So I'm guessing all this points to CSRF has been somehow enabled for webhook endpoints when using HTTP basic-auth?  Is this possibly a new configuration?

We don't use the token-based webhook method because this system means we only have one common secret to keep -- the openstackci user password -- and everything else is self-service for projects who simply add the common user as an admin and include in their configuration their endpoint URL.

Any help with this will be greatly appreciated, thanks


Discussion:
On 2018-12-05 we did a deploy: https://docs.readthedocs.io/en/latest/changelog.html#version-2-8-3 but I don't find anything relevant there. I need to research a little more what has happened here.

Also, I got a different error:

    $ curl -X POST -d "branches=datetime" -u humitos:$RTD_PASS https://readthedocs.org/api/v2/webhook/test-builds/56359/
    {"detail":"CSRF Failed: Referer checking failed - no Referer."}

@humitos thanks - yes I saw that at first to.  If you add a "-e" with your URL to the request, it will set a referer.

I sort of had 3 steps to get to the csrf error; first it wanted a content-length to be set, then the referer, then it failed with the csrf error
@humitos did you manage to find anything?  
No yet. I will try to take a look at this soon.

Marking this as bug, since it was working properly and also the docs says that it should work properly: https://docs.readthedocs.io/en/latest/webhooks.html#authentication
@humitos any update on this?

I see it's been tagged with a milestone that seems closed, but per http://zuul.openstack.org/builds?job_name=trigger-readthedocs-webhook all our publishing jobs are still failing.  If it expected to be fixed, I can look closer and see what's going on (as mentioned before, the logging isn't very helpful as we don't want to leak the password, so I'll have to dig into it more manually).
@ianw the PR is still under review. I'm pinging @agjohnson here since he had different opinions about this.
Hello team. Airship project (lives under OpenStack) is affected by this bug. Is there anything I can help with? Thank you.

Resolving patch:
diff --git a/readthedocs/restapi/views/integrations.py b/readthedocs/restapi/views/integrations.py
--- a/readthedocs/restapi/views/integrations.py
+++ b/readthedocs/restapi/views/integrations.py
@@ -349,7 +349,7 @@ class IsAuthenticatedOrHasToken(permissions.IsAuthenticated):
     """
 
     def has_permission(self, request, view):
-        has_perm = (super().has_permission(request, view))
+        has_perm = super().has_permission(request, view)
         return has_perm or 'token' in request.data
 
 
@@ -420,8 +420,18 @@ class WebhookView(APIView):
     ever get webhook requests for established webhooks on our side. The other
     views can receive webhooks for unknown webhooks, as all legacy webhooks will
     be.
+
+    .. warning::
+        We're turning off Authenication for this view.
+        This fixes a bug where we were double-authenticating these views,
+        because of the way we're passing the request along to the subviews.
+
+        If at any time we add real logic to this view,
+        it will be completely unauthenticated.
     """
 
+    authentication_classes = []
+
     VIEW_MAP = {
         Integration.GITHUB_WEBHOOK: GitHubWebhookView,
         Integration.GITLAB_WEBHOOK: GitLabWebhookView,


Test patch: