From e02543f512eff18c5b530d44ac8e68918bf31724 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Tue, 7 Jan 2020 17:44:48 +0100
Subject: [PATCH] forms: force authentication if anonymous use user tracking
 code (#38239)

---
 tests/test_form_pages.py    | 17 +++++++++++------
 tests/test_tracking_code.py | 30 ++++++++++++++++++++++++------
 wcs/forms/root.py           |  9 ++++++++-
 3 files changed, 43 insertions(+), 13 deletions(-)

diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py
index e52537fd..5756eaed 100644
--- a/tests/test_form_pages.py
+++ b/tests/test_form_pages.py
@@ -1582,17 +1582,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)
@@ -6020,7 +6017,7 @@ def test_manager_public_access(pub):
     formdef.store()
     resp = app.get(formdata.get_url(), status=403)
 
-    # agent access via a tracking code (stays in frontoffice)
+    # agent access via a tracking code (redirected to login and next backoffice)
     formdef.workflow_roles = {'_receiver': role.id}
     formdef.enable_tracking_codes = True
     formdef.store()
@@ -6030,9 +6027,17 @@ def test_manager_public_access(pub):
     code.store()
 
     resp = app.get('/code/%s/load' % code.id)
+    resp = resp.follow() # -> /login/?ReturnUrl=.../test/1
+    assert '<title>wcs - Login</title>' in resp.text
+    resp.form['username'] = 'admin'
+    resp.form['password'] = 'admin'
+    resp = resp.form.submit()
+    assert resp.status_int == 302
     resp = resp.follow() # -> /test/1
     assert not 'backoffice' in resp.location
     resp = resp.follow() # -> /test/1/
+    assert 'backoffice' in resp.location
+    resp = resp.follow() # -> /test/1/
     assert 'The form has been recorded' in resp.text
 
     # authorized access but not backoffice access
diff --git a/tests/test_tracking_code.py b/tests/test_tracking_code.py
index aec4281b..05569e50 100644
--- a/tests/test_tracking_code.py
+++ b/tests/test_tracking_code.py
@@ -183,14 +183,22 @@ def test_access_to_formdata(pub, users, nocache):
     | agent1 (submiter)   |  login    | deny  | deny  | deny   | back   | back   |
     | user1               |  login    | allow | deny  | deny   | back   | back   |
 
-    2- New user on prefill fields when accessing using tracking code :
+    2- Access using tracking code :
+
+    | 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 | login | login  | login  | login  |
+
+    3- New user on prefill fields when accessing using tracking code :
      a-  Drafts
 
     | sumitter / accesser | anonymous | user1 | user2 | agent1 | agent2 | admin1 |
     +---------------------+-----------+-------+-------+--------+--------+--------+
     | anonymous           | anonymous | user1 | user2 | agent1 | agent2 | admin  |
     | agent1 (submiter)   | anonymous | user1 | user2 | agent1 | agent2 | admin  |
-    | user1               | anonymous | user1 | user2 | agent1 | agent2 | admin  |
+    | user1               |           | user1 | user2 | agent1 | agent2 | admin  |
 
      b-  Demands (evolving into Workflows forms)
 
@@ -198,7 +206,7 @@ def test_access_to_formdata(pub, users, nocache):
     +---------------------+-----------+-------+-------+--------+--------+--------+
     | anonymous           | anonymous | user1 | user2 | agent1 | agent2 | admin  |
     | agent1 (submiter)   | anonymous | user1 | user2 | agent1 | agent2 | admin  |
-    | user1               | anonymous | user1 | user2 | agent1 | agent2 | admin  |
+    | user1               |           | user1 | user2 | agent1 | agent2 | admin  |
     """
     (anonymous, user1, user2, agent1, agent2, admin1) = users
     tracking_code = None
@@ -338,7 +346,7 @@ def test_access_to_formdata(pub, users, nocache):
             else:
                 assert access in ('login', 'deny', 'front',  'back')
 
-    def check_tracking_code_access(user, owner=None, new_owner=None):
+    def check_tracking_code_access(user, owner=None, new_owner=None, access='allow'):
         """
         load the formdata using the tracking code and prefill user name fields.
         new_owner is used to show that user fields change on drafts.
@@ -353,6 +361,11 @@ def test_access_to_formdata(pub, users, nocache):
         resp.forms[0]['code'] = tracking_code
         resp = resp.forms[0].submit()
         resp = resp.follow()
+        if access == 'login':
+            assert resp.location == (
+                'http://example.net/login/?ReturnUrl='
+                + 'http://example.net/test/%s') % formdata_id
+            return
         resp = resp.follow()
         resp = resp.follow()
         assert '<title>Forms - test</title>' in resp.text
@@ -434,20 +447,25 @@ def test_access_to_formdata(pub, users, nocache):
             check_direct_access(users[i], access[i])
 
     # access to formdata using the tracking code
+    access = ('login', 'allow', 'login', 'login', 'login', 'login')
     is_draft = True  # drafts
+    i = 0
     for user in users:
         with submit(anonymous, is_front=True) as (tracking_code, formdata_id):
             check_tracking_code_access(user, owner=anonymous, new_owner=user)
         with submit(agent1, is_front=False) as (tracking_code, formdata_id):
             check_tracking_code_access(user, owner=anonymous, new_owner=user)
         with submit(user1, is_front=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, access=access[i])
+        i += 1
 
     is_draft = False  # demands
+    i = 0
     for user in users:
         with submit(anonymous, is_front=True) as (tracking_code, formdata_id):
             check_tracking_code_access(user, owner=anonymous, new_owner=user)
         with submit(agent1, is_front=False) as (tracking_code, formdata_id):
             check_tracking_code_access(user, owner=anonymous, new_owner=user)
         with submit(user1, is_front=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, access=access[i])
+        i += 1
diff --git a/wcs/forms/root.py b/wcs/forms/root.py
index 63d34214..5d7226b0 100644
--- a/wcs/forms/root.py
+++ b/wcs/forms/root.py
@@ -174,8 +174,15 @@ 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
+                or str(get_request().user.id) != str(formdata.user_id)):
+            # asked to load a tracking code associated with another 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.1

