From f5959d7a567b1bb910db792af6fecb1954ba45e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Mon, 13 Sep 2021 16:26:38 +0200
Subject: [PATCH] api: add basic auth support to formdefs (#56690)

---
 tests/api/test_formdef.py | 70 ++++++++++++++++++++++++++++++++++-----
 wcs/api.py                |  7 ++--
 2 files changed, 67 insertions(+), 10 deletions(-)

diff --git a/tests/api/test_formdef.py b/tests/api/test_formdef.py
index 79c47f73..74efc6bb 100644
--- a/tests/api/test_formdef.py
+++ b/tests/api/test_formdef.py
@@ -12,6 +12,7 @@ from django.utils.encoding import force_text
 from quixote import get_publisher
 
 from wcs import fields, qommon
+from wcs.api_access import ApiAccess
 from wcs.api_utils import sign_url
 from wcs.categories import Category
 from wcs.data_sources import NamedDataSource
@@ -187,7 +188,8 @@ def test_formdef_list_categories_filter(pub):
     assert len(resp.json['data']) == 2
 
 
-def test_limited_formdef_list(pub, local_user):
+@pytest.mark.parametrize('auth', ['signature', 'http-basic'])
+def test_limited_formdef_list(pub, local_user, auth):
     pub.role_class.wipe()
     role = pub.role_class(name='Foo bar')
     role.id = '14'
@@ -201,6 +203,28 @@ def test_limited_formdef_list(pub, local_user):
     formdef.fields = []
     formdef.store()
 
+    ApiAccess.wipe()
+    access = ApiAccess()
+    access.name = 'test'
+    access.access_identifier = 'test'
+    access.access_key = '12345'
+    access.store()
+
+    app = get_app(pub)
+
+    if auth == 'http-basic':
+
+        def get_url(url, **kwargs):
+            app.set_authorization(('Basic', ('test', '12345')))
+            return app.get(url, **kwargs)
+
+    else:
+
+        def get_url(url, **kwargs):
+            return app.get(
+                sign_uri(url, user=local_user, orig=access.access_identifier, key=access.access_key), **kwargs
+            )
+
     resp = get_app(pub).get(sign_uri('/api/formdefs/'))
     assert resp.json['err'] == 0
     assert len(resp.json['data']) == 1
@@ -216,7 +240,7 @@ def test_limited_formdef_list(pub, local_user):
     resp = get_app(pub).get(sign_uri('/api/formdefs/'))
     resp2 = get_app(pub).get(sign_uri('/api/formdefs/?NameID='))
     resp3 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=XXX'))
-    resp4 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=%s' % local_user.name_identifiers[0]))
+    resp4 = get_url('/api/formdefs/')
     assert resp.json['err'] == 0
     assert len(resp.json['data']) == 1  # advertised in naked calls (as done from combo)
     assert len(resp2.json['data']) == 0  # not advertised otherwise
@@ -229,12 +253,18 @@ def test_limited_formdef_list(pub, local_user):
     # unless user has correct roles
     local_user.roles = [role.id]
     local_user.store()
-    resp = get_app(pub).get(sign_uri('/api/formdefs/?NameID=%s' % local_user.name_identifiers[0]))
+    if auth == 'http-basic':
+        access.roles = [role]
+        access.store()
+    resp = get_url('/api/formdefs/')
     assert resp.json['err'] == 0
     assert len(resp.json['data']) == 1
 
     local_user.roles = []
     local_user.store()
+    if auth == 'http-basic':
+        access.roles = []
+        access.store()
 
     # check it's also included in anonymous/signed calls, but marked for
     # authentication
@@ -248,7 +278,7 @@ def test_limited_formdef_list(pub, local_user):
     resp = get_app(pub).get(sign_uri('/api/formdefs/'))
     resp2 = get_app(pub).get(sign_uri('/api/formdefs/?NameID='))
     resp3 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=XXX'))
-    resp4 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=%s' % local_user.name_identifiers[0]))
+    resp4 = get_url('/api/formdefs/')
     assert resp.json['err'] == 0
     assert len(resp.json['data']) == 1
     assert resp.json['data'][0]['authentication_required']
@@ -277,7 +307,8 @@ def test_formdef_list_redirection(pub):
     assert 'count' not in resp1.json['data'][0]
 
 
-def test_backoffice_submission_formdef_list(pub, local_user):
+@pytest.mark.parametrize('auth', ['signature', 'http-basic'])
+def test_backoffice_submission_formdef_list(pub, local_user, auth):
     pub.role_class.wipe()
     role = pub.role_class(name='Foo bar')
     role.id = '14'
@@ -296,6 +327,28 @@ def test_backoffice_submission_formdef_list(pub, local_user):
     formdef2.fields = []
     formdef2.store()
 
+    ApiAccess.wipe()
+    access = ApiAccess()
+    access.name = 'test'
+    access.access_identifier = 'test'
+    access.access_key = '12345'
+    access.store()
+
+    app = get_app(pub)
+
+    if auth == 'http-basic':
+
+        def get_url(url, **kwargs):
+            app.set_authorization(('Basic', ('test', '12345')))
+            return app.get(url, **kwargs)
+
+    else:
+
+        def get_url(url, **kwargs):
+            return app.get(
+                sign_uri(url, user=local_user, orig=access.access_identifier, key=access.access_key), **kwargs
+            )
+
     resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
     assert resp.json['err'] == 0
     assert len(resp.json['data']) == 0
@@ -328,9 +381,10 @@ def test_backoffice_submission_formdef_list(pub, local_user):
     # ... unless user has correct roles
     local_user.roles = [role.id]
     local_user.store()
-    resp = get_app(pub).get(
-        sign_uri('/api/formdefs/?backoffice-submission=on&NameID=%s' % local_user.name_identifiers[0])
-    )
+    if auth == 'http-basic':
+        access.roles = [role]
+        access.store()
+    resp = get_url('/api/formdefs/?backoffice-submission=on')
     assert resp.json['err'] == 0
     assert len(resp.json['data']) == 1
     assert 'backoffice_submission_url' in resp.json['data'][0]
diff --git a/wcs/api.py b/wcs/api.py
index 70f2b0bc..fefcdd81 100644
--- a/wcs/api.py
+++ b/wcs/api.py
@@ -758,12 +758,15 @@ class ApiFormdefsDirectory(Directory):
             # signed URL with a None user is considered like an appropriate
             # webservice call.
             user = False
-        if not is_url_signed():
+        url_signed = is_url_signed()
+        if user and user.is_api_user:
+            pass  # API users are ok
+        elif not url_signed:
             if not (get_request().user and get_request().user.is_admin):
                 raise AccessForbiddenError('user not authenticated')
             user = get_request().user
 
-        list_all_forms = (user and user.is_admin) or (is_url_signed() and user is None)
+        list_all_forms = (user and user.is_admin) or (url_signed and user is None)
         backoffice_submission = get_request().form.get('backoffice-submission') == 'on'
 
         list_forms = self.get_list_forms(user, list_all_forms, backoffice_submission=backoffice_submission)
-- 
2.30.2

