diff --git a/mig/shared/safeinput.py b/mig/shared/safeinput.py index 6b37f4cb0..f6ee34af1 100644 --- a/mig/shared/safeinput.py +++ b/mig/shared/safeinput.py @@ -145,10 +145,10 @@ "`|^" + '\\' + '\n\r\t' VALID_FQDN_CHARACTERS = ascii_letters + digits + '.-' VALID_BACKEND_NAME_CHARACTERS = ascii_letters + digits + '-_' -VALID_BASEURL_CHARACTERS = VALID_FQDN_CHARACTERS + ':/_' -VALID_URL_CHARACTERS = VALID_BASEURL_CHARACTERS + '?;&%=' # According to https://tools.ietf.org/html/rfc3986#section-2 URLs may contain # '%'-encoded chars, alphanum chars and query chars "-._~:/?#[]@!$&'()*+,;=`." +VALID_BASEURL_CHARACTERS = VALID_FQDN_CHARACTERS + ':/_' +VALID_URL_CHARACTERS = VALID_BASEURL_CHARACTERS + '?;&%=' VALID_COMPLEXURL_CHARACTERS = VALID_BASEURL_CHARACTERS + \ "%-._~:/?#[]@!$&'()*+,;=`." VALID_JOB_ID_CHARACTERS = VALID_FQDN_CHARACTERS + '_' @@ -2047,9 +2047,12 @@ def guess_type(name): for key in ('modauthopenid.referrer', 'transfer_src', 'transfer_dst', - 'redirect_url', ): __type_map[key] = valid_url + # NOTE: Trac URLs may have user IDs with '@' and can hit twofactor + for key in ('redirect_url', + ): + __type_map[key] = valid_complex_url # GDP diff --git a/tests/test_mig_shared_safeinput.py b/tests/test_mig_shared_safeinput.py index 181fcc2cf..4a01dddd8 100644 --- a/tests/test_mig_shared_safeinput.py +++ b/tests/test_mig_shared_safeinput.py @@ -3,7 +3,7 @@ # --- BEGIN_HEADER --- # # test_mig_shared_safeinput - unit tests for shared safeinput validation -# Copyright (C) 2003-2025 The MiG Project by the Science HPC Center at UCPH +# Copyright (C) 2003-2026 The MiG Project by the Science HPC Center at UCPH # # This file is part of MiG. # @@ -36,7 +36,8 @@ from mig.shared.safeinput import main as safeinput_main, InputException, \ filter_commonname, valid_alphanumeric, valid_commonname, valid_path, \ - valid_printable, VALID_NAME_CHARACTERS + valid_printable, valid_base_url, valid_url, valid_complex_url, \ + VALID_NAME_CHARACTERS PY2 = sys.version_info[0] == 2 @@ -88,6 +89,11 @@ class TestMigSharedSafeInput(MigTestCase): 'Test Invalid ?', 'Test HTML Invalid ') + BASE_URL = 'https://www.migrid.org' + REGULAR_URL = 'https://www.migrid.org/wsgi-bin/ls.py?path=README&flags=v' + COMPLEX_URL = 'https://www.migrid.org/abc123@some.org/ls.py?path=R+D#HERE' + INVALID_URL = 'https://www.migrid.org/¾½§' + def _provide_configuration(self): """Provide test configuration""" return 'testconfig' @@ -210,6 +216,66 @@ def test_valid_path_unicode_normalization(self): # Make sure unicode normalization doesn't raise exception self.assertEqual(valid_path(self.DECOMPOSED_UNICODE), None) + def test_valid_base_url_accepts_sample_base_url(self): + """Test that base URL succeeds in valid_base_url""" + # Make sure expected URL value doesn't raise exception + self.assertEqual(valid_base_url(self.BASE_URL), None) + + def test_valid_base_url_refuses_sample_regular_url(self): + """Test that regular URL fails in valid_base_url""" + with self.assertRaises(InputException): + valid_base_url(self.REGULAR_URL) + + def test_valid_base_url_refuses_sample_complex_url(self): + """Test that more complex URL fails in valid_base_url""" + with self.assertRaises(InputException): + valid_base_url(self.COMPLEX_URL) + + def test_valid_base_url_refuses_sample_invalid_url(self): + """Test that invalid URL fails in valid_base_url""" + with self.assertRaises(InputException): + valid_base_url(self.INVALID_URL) + + def test_valid_url_accepts_sample_base_url(self): + """Test that base URL succeeds in valid_url""" + # Make sure expected URL value doesn't raise exception + self.assertEqual(valid_url(self.BASE_URL), None) + + def test_valid_url_accepts_sample_regular_url(self): + """Test that regular URL succeeds in valid_url""" + # Make sure expected URL value doesn't raise exception + self.assertEqual(valid_url(self.REGULAR_URL), None) + + def test_valid_url_refuses_sample_complex_url(self): + """Test that complex URL fails in valid_url""" + with self.assertRaises(InputException): + valid_url(self.COMPLEX_URL) + + def test_valid_url_refuses_sample_invalid_url(self): + """Test that invalid URL fails in valid_url""" + with self.assertRaises(InputException): + valid_url(self.INVALID_URL) + + def test_valid_complex_url_accepts_sample_base_url(self): + """Test that base URL succeeds in valid_complex_url""" + # Make sure expected URL value doesn't raise exception + self.assertEqual(valid_complex_url(self.BASE_URL), None) + + def test_valid_complex_url_accepts_sample_regular_url(self): + """Test that regular URL succeeds in valid_complex_url""" + # Make sure expected URL value doesn't raise exception + self.assertEqual(valid_complex_url(self.REGULAR_URL), None) + + def test_valid_complex_url_accepts_sample_complex_url(self): + """Test that complex URL succeeds in valid_complex_url""" + # Make sure expected URL value doesn't raise exception + self.assertEqual(valid_complex_url(self.COMPLEX_URL), None) + + def test_valid_complex_url_refuses_sample_invalid_url(self): + """Test that invalid URL fails in valid_complex_url""" + with self.assertRaises(InputException): + valid_complex_url(self.INVALID_URL) + class TestMigSharedSafeInput__legacy(MigTestCase): """Legacy tests for safeinput module self-checks"""