From b3d1fcf5dc03f0cffb7ac91c92510eef879f2667 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Tue, 19 Nov 2019 17:41:17 +0100
Subject: [PATCH] forms: force authentication if anonymous use user tracking
 code (#38239)

---
 tests/test_form_pages.py    |  7 ++-----
 tests/test_tracking_code.py | 29 +++++++++++++++++++++--------
 wcs/forms/root.py           |  8 +++++++-
 3 files changed, 30 insertions(+), 14 deletions(-)

diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py
index 767511fb..97bbb491 100644
--- a/tests/test_form_pages.py
+++ b/tests/test_form_pages.py
@@ -1559,17 +1559,14 @@ def test_form_tracking_code_as_user(pub, nocache):
     resp = resp.forms[0].submit()
     assert formdef.data_class().get(formdata_id).evolution[-1].comment == 'hello world'
 
-    # and check we can also get back to it as anonymous
+    # check we can't get back to it as anonymous
     app = get_app(pub)
     resp = app.get('/')
     resp.forms[0]['code'] = tracking_code
     resp = resp.forms[0].submit()
     assert resp.location == 'http://example.net/code/%s/load' % tracking_code
     resp = resp.follow()
-    assert resp.location == 'http://example.net/test/%s' % formdata_id
-    resp = resp.follow()
-    resp = resp.follow()
-    assert 'form_comment' in resp.text # makes sure user is treated as submitter
+    assert resp.location == 'http://example.net/login/?ReturnUrl=http://example.net/test/%s' % formdata_id
 
     # and check a bot is not allowed to get it
     app = get_app(pub)
diff --git a/tests/test_tracking_code.py b/tests/test_tracking_code.py
index 611cdcf5..39d4a58f 100644
--- a/tests/test_tracking_code.py
+++ b/tests/test_tracking_code.py
@@ -160,7 +160,12 @@ def test_access_to_formdata(pub, nocache):
 
     2- Access using tracking code :
 
-    All access is granted,
+    | sumitter / accesser | anonymous | user1 | user2 | agent1 | agent2 | admin1 |
+    +---------------------+-----------+-------+-------+--------+--------+--------+
+    | anonymous           |  allow    | allow | allow | allow  | allow  | allow  |
+    | agent1 (submiter))  |  allow    | allow | allow | allow  | allow  | allow  |
+    | user1               |  login    | allow | allow | allow  | allow  | allow  |
+
     On restoring draft, the logged user become the new draft owner,
     this affect the computed and prefill fields.
     """
@@ -261,7 +266,8 @@ def test_access_to_formdata(pub, nocache):
             else:
                 assert expected in ('login', 'forbidden', 'frontoffice',  'backoffice')
 
-    def check_tracking_code_access(user, owner=None, new_owner=None):
+    def check_tracking_code_access(user, owner=None, new_owner=None,
+                                   expected='allow'):
         """load the formdata using the tracking code"""
         pub.session_manager.session_class.wipe()
         app = get_app(pub)
@@ -272,6 +278,11 @@ def test_access_to_formdata(pub, nocache):
         resp = resp.forms[0].submit()
         assert resp.location == 'http://example.net/code/%s/load' % tracking_code
         resp = resp.follow()
+        if expected == 'login':
+            assert resp.location == (
+                'http://example.net/login/?ReturnUrl='
+                + 'http://example.net/test/%s') % formdata_id
+            return
         assert resp.location == 'http://example.net/test/%s' % formdata_id
         resp = resp.follow()
         if is_draft:
@@ -340,20 +351,22 @@ def test_access_to_formdata(pub, nocache):
             check_direct_access(users[i], expected[i])
 
     # access to formdata using the tracking code
+    expected = ('login', 'allow', 'allow', 'allow', 'allow', 'allow')
     is_draft = False  # demands
-    for user in users:
+    for i in range(len(users)):
         with submission(anonymous, is_frontoffice=True) as (tracking_code, formdata_id):
-            check_tracking_code_access(user, owner=anonymous)
+            check_tracking_code_access(users[i], owner=anonymous)
         with submission(agent1, is_frontoffice=False) as (tracking_code, formdata_id):
-            check_tracking_code_access(user, owner=anonymous)
+            check_tracking_code_access(users[i], owner=anonymous)
         with submission(user1, is_frontoffice=True) as (tracking_code, formdata_id):
-            check_tracking_code_access(user, owner=user1)
+            check_tracking_code_access(users[i], owner=user1, expected=expected[i])
 
     is_draft = True  # drafts
-    for user in users:
+    for i in range(len(users)):
+        user = users[i]
         with submission(anonymous, is_frontoffice=True) as (tracking_code, formdata_id):
             check_tracking_code_access(user, owner=anonymous, new_owner=user)
         with submission(agent1, is_frontoffice=False) as (tracking_code, formdata_id):
             check_tracking_code_access(user, owner=anonymous, new_owner=user)
         with submission(user1, is_frontoffice=True) as (tracking_code, formdata_id):
-            check_tracking_code_access(user, owner=user1, new_owner=user)
+            check_tracking_code_access(user, owner=user1, new_owner=user, expected=expected[i])
diff --git a/wcs/forms/root.py b/wcs/forms/root.py
index 63d34214..596e49d8 100644
--- a/wcs/forms/root.py
+++ b/wcs/forms/root.py
@@ -174,8 +174,14 @@ class TrackingCodeDirectory(Directory):
             raise errors.TraversalError()
         if BotFilter.is_bot():
             raise errors.AccessForbiddenError()
+
+        formdata_url = formdata.get_url().rstrip('/')
+        if formdata.user_id and not get_request().user:
+            # anonymous user asked to load a tracking code associated with an user,
+            # don't load, ask for authentication instead
+            return redirect('/login/?ReturnUrl=%s' % formdata_url)
         get_session().mark_anonymous_formdata(formdata)
-        return redirect(formdata.get_url().rstrip('/'))
+        return redirect(formdata_url)
 
 
 class TrackingCodesDirectory(Directory):
-- 
2.24.0

