From e1ebd4988bc41731abef86eb05a836077e4826a7 Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Sat, 31 Mar 2018 18:40:41 +0200
Subject: [PATCH] Fixed inconsistencies between test and prod requests

---
 .../federation/authentication.py              | 17 +++--
 api/funkwhale_api/federation/signing.py       | 30 +++++++--
 api/tests/federation/test_authentication.py   | 11 ++--
 api/tests/federation/test_signing.py          | 66 +++++++++++--------
 4 files changed, 83 insertions(+), 41 deletions(-)

diff --git a/api/funkwhale_api/federation/authentication.py b/api/funkwhale_api/federation/authentication.py
index 7972e78a01..980b7006bc 100644
--- a/api/funkwhale_api/federation/authentication.py
+++ b/api/funkwhale_api/federation/authentication.py
@@ -9,15 +9,17 @@ from . import actors
 from . import keys
 from . import serializers
 from . import signing
+from . import utils
 
 
 class SignatureAuthentication(authentication.BaseAuthentication):
-    def authenticate(self, request):
+    def authenticate_actor(self, request):
+        headers = utils.clean_wsgi_headers(request.META)
         try:
-            signature = request.META['headers']['Signature']
+            signature = headers['Signature']
             key_id = keys.get_key_id_from_signature_header(signature)
         except KeyError:
-            raise exceptions.AuthenticationFailed('No signature')
+            return
         except ValueError as e:
             raise exceptions.AuthenticationFailed(str(e))
 
@@ -33,14 +35,17 @@ class SignatureAuthentication(authentication.BaseAuthentication):
 
         serializer = serializers.ActorSerializer(data=actor_data)
         if not serializer.is_valid():
-            raise exceptions.AuthenticationFailed('Invalid actor payload')
+            raise exceptions.AuthenticationFailed('Invalid actor payload: {}'.format(serializer.errors))
 
         try:
             signing.verify_django(request, public_key.encode('utf-8'))
         except cryptography.exceptions.InvalidSignature:
             raise exceptions.AuthenticationFailed('Invalid signature')
 
+        return serializer.build()
+
+    def authenticate(self, request):
+        actor = self.authenticate_actor(request)
         user = AnonymousUser()
-        ac = serializer.build()
-        setattr(request, 'actor', ac)
+        setattr(request, 'actor', actor)
         return (user, None)
diff --git a/api/funkwhale_api/federation/signing.py b/api/funkwhale_api/federation/signing.py
index 6f77cbd425..7e4d2aa5ae 100644
--- a/api/funkwhale_api/federation/signing.py
+++ b/api/funkwhale_api/federation/signing.py
@@ -1,6 +1,12 @@
+import logging
 import requests
 import requests_http_signature
 
+from . import exceptions
+from . import utils
+
+logger = logging.getLogger(__name__)
+
 
 def verify(request, public_key):
     return requests_http_signature.HTTPSignatureAuth.verify(
@@ -15,21 +21,35 @@ def verify_django(django_request, public_key):
     Given a django WSGI request, create an underlying requests.PreparedRequest
     instance we can verify
     """
-    headers = django_request.META.get('headers', {}).copy()
+    headers = utils.clean_wsgi_headers(django_request.META)
     for h, v in list(headers.items()):
         # we include lower-cased version of the headers for compatibility
         # with requests_http_signature
         headers[h.lower()] = v
     try:
-        signature = headers['signature']
+        signature = headers['Signature']
     except KeyError:
         raise exceptions.MissingSignature
-
+    url = 'http://noop{}'.format(django_request.path)
+    query = django_request.META['QUERY_STRING']
+    if query:
+        url += '?{}'.format(query)
+    signature_headers = signature.split('headers="')[1].split('",')[0]
+    expected = signature_headers.split(' ')
+    logger.debug('Signature expected headers: %s', expected)
+    for header in expected:
+        try:
+            headers[header]
+        except KeyError:
+            logger.debug('Missing header: %s', header)
     request = requests.Request(
         method=django_request.method,
-        url='http://noop',
+        url=url,
         data=django_request.body,
         headers=headers)
-
+    for h in request.headers.keys():
+        v = request.headers[h]
+        if v:
+            request.headers[h] = str(v)
     prepared_request = request.prepare()
     return verify(request, public_key)
diff --git a/api/tests/federation/test_authentication.py b/api/tests/federation/test_authentication.py
index 0c4a25a7f0..1837b3950f 100644
--- a/api/tests/federation/test_authentication.py
+++ b/api/tests/federation/test_authentication.py
@@ -20,14 +20,17 @@ def test_authenticate(nodb_factories, mocker, api_request):
         })
     signed_request = nodb_factories['federation.SignedRequest'](
         auth__key=private,
-        auth__key_id=actor_url + '#main-key'
+        auth__key_id=actor_url + '#main-key',
+        auth__headers=[
+            'date',
+        ]
     )
     prepared = signed_request.prepare()
     django_request = api_request.get(
         '/',
-        headers={
-            'Date': prepared.headers['date'],
-            'Signature': prepared.headers['signature'],
+        **{
+            'HTTP_DATE': prepared.headers['date'],
+            'HTTP_SIGNATURE': prepared.headers['signature'],
         }
     )
     authenticator = authentication.SignatureAuthentication()
diff --git a/api/tests/federation/test_signing.py b/api/tests/federation/test_signing.py
index 9da7a0f872..0c1ec2e0ba 100644
--- a/api/tests/federation/test_signing.py
+++ b/api/tests/federation/test_signing.py
@@ -44,56 +44,67 @@ def test_verify_fails_with_wrong_key(nodb_factories):
         signing.verify(prepared_request, wrong_public)
 
 
-def test_can_verify_django_request(factories, api_request):
+def test_can_verify_django_request(factories, fake_request):
     private_key, public_key = keys.get_key_pair()
     signed_request = factories['federation.SignedRequest'](
-        auth__key=private_key
+        auth__key=private_key,
+        auth__headers=[
+            'date',
+        ]
     )
     prepared = signed_request.prepare()
-    django_request = api_request.get(
+    django_request = fake_request.get(
         '/',
-        headers={
-            'Date': prepared.headers['date'],
-            'Signature': prepared.headers['signature'],
+        **{
+            'HTTP_DATE': prepared.headers['date'],
+            'HTTP_SIGNATURE': prepared.headers['signature'],
         }
     )
     assert signing.verify_django(django_request, public_key) is None
 
 
-def test_can_verify_django_request_digest(factories, api_request):
+def test_can_verify_django_request_digest(factories, fake_request):
     private_key, public_key = keys.get_key_pair()
     signed_request = factories['federation.SignedRequest'](
         auth__key=private_key,
         method='post',
-        data=b'hello=world'
+        data=b'hello=world',
+        auth__headers=[
+            'date',
+            'digest',
+        ]
     )
     prepared = signed_request.prepare()
-    django_request = api_request.post(
+    django_request = fake_request.post(
         '/',
-        headers={
-            'Date': prepared.headers['date'],
-            'Digest': prepared.headers['digest'],
-            'Signature': prepared.headers['signature'],
+        **{
+            'HTTP_DATE': prepared.headers['date'],
+            'HTTP_DIGEST': prepared.headers['digest'],
+            'HTTP_SIGNATURE': prepared.headers['signature'],
         }
     )
 
     assert signing.verify_django(django_request, public_key) is None
 
 
-def test_can_verify_django_request_digest_failure(factories, api_request):
+def test_can_verify_django_request_digest_failure(factories, fake_request):
     private_key, public_key = keys.get_key_pair()
     signed_request = factories['federation.SignedRequest'](
         auth__key=private_key,
         method='post',
-        data=b'hello=world'
+        data=b'hello=world',
+        auth__headers=[
+            'date',
+            'digest',
+        ]
     )
     prepared = signed_request.prepare()
-    django_request = api_request.post(
+    django_request = fake_request.post(
         '/',
-        headers={
-            'Date': prepared.headers['date'],
-            'Digest': prepared.headers['digest'] + 'noop',
-            'Signature': prepared.headers['signature'],
+        **{
+            'HTTP_DATE': prepared.headers['date'],
+            'HTTP_DIGEST': prepared.headers['digest'] + 'noop',
+            'HTTP_SIGNATURE': prepared.headers['signature'],
         }
     )
 
@@ -101,17 +112,20 @@ def test_can_verify_django_request_digest_failure(factories, api_request):
         signing.verify_django(django_request, public_key)
 
 
-def test_can_verify_django_request_failure(factories, api_request):
+def test_can_verify_django_request_failure(factories, fake_request):
     private_key, public_key = keys.get_key_pair()
     signed_request = factories['federation.SignedRequest'](
-        auth__key=private_key
+        auth__key=private_key,
+        auth__headers=[
+            'date',
+        ]
     )
     prepared = signed_request.prepare()
-    django_request = api_request.get(
+    django_request = fake_request.get(
         '/',
-        headers={
-            'Date': 'Wrong',
-            'Signature': prepared.headers['signature'],
+        **{
+            'HTTP_DATE': 'Wrong',
+            'HTTP_SIGNATURE': prepared.headers['signature'],
         }
     )
     with pytest.raises(cryptography.exceptions.InvalidSignature):
-- 
GitLab