From 5d861ef124397922017e65f341d924db74c4dbe1 Mon Sep 17 00:00:00 2001
From: Henrich Neumann <henrich.neumann@usit.uio.no>
Date: Fri, 14 Jul 2023 11:42:54 +0200
Subject: [PATCH] Further restrict the set of valid characters

Improve the tests.
---
 .../src/routes/guest/register/index.test.tsx  |  2 +-
 .../guest/register/steps/register.test.tsx    | 12 +++----
 frontend/src/utils/index.test.ts              | 32 +++++++++++++++----
 frontend/src/utils/index.ts                   | 20 ++++++++++--
 greg/tests/test_utils.py                      |  4 ++-
 greg/utils.py                                 | 20 ++++++++++--
 gregui/tests/api/views/test_invitation.py     |  4 +--
 7 files changed, 73 insertions(+), 21 deletions(-)

diff --git a/frontend/src/routes/guest/register/index.test.tsx b/frontend/src/routes/guest/register/index.test.tsx
index a1240c16..624a3841 100644
--- a/frontend/src/routes/guest/register/index.test.tsx
+++ b/frontend/src/routes/guest/register/index.test.tsx
@@ -12,7 +12,7 @@ enableFetchMocks()
 
 const testData = {
   person: {
-    first_name: 'Test20',
+    first_name: 'TestTwenty',
     last_name: 'Tester',
     private_mobile: '+4797543910',
     private_email: 'test@example.org',
diff --git a/frontend/src/routes/guest/register/steps/register.test.tsx b/frontend/src/routes/guest/register/steps/register.test.tsx
index ad034c11..f6669fe5 100644
--- a/frontend/src/routes/guest/register/steps/register.test.tsx
+++ b/frontend/src/routes/guest/register/steps/register.test.tsx
@@ -194,7 +194,7 @@ test('Gender required to be set if gender field is showing', async () => {
 
   const formData: GuestRegisterData = {
     firstName: 'Test',
-    lastName: 'Test2',
+    lastName: 'TestTwo',
     mobilePhoneCountry: 'NO',
     mobilePhone: '97543980',
     nationalIdNumber: '',
@@ -249,7 +249,7 @@ test('Gender not required to be set if gender field is not showing', async () =>
 
   const formData: GuestRegisterData = {
     firstName: 'Test',
-    lastName: 'Test2',
+    lastName: 'TestTwo',
     mobilePhoneCountry: 'NO',
     mobilePhone: '97543980',
     nationalIdNumber: '',
@@ -297,7 +297,7 @@ test('Guest not allowed to proceed in wizard if phone number is not valid', asyn
 
   const formData: GuestRegisterData = {
     firstName: 'Test',
-    lastName: 'Test2',
+    lastName: 'TestTwo',
     mobilePhoneCountry: 'NO',
     mobilePhone: '50',
     nationalIdNumber: '',
@@ -341,7 +341,7 @@ test('Guest allowed to proceed in wizard if data is valid', async () => {
 
   const formData: GuestRegisterData = {
     firstName: 'Test',
-    lastName: 'Test2',
+    lastName: 'TestTwo',
     mobilePhoneCountry: 'NO',
     mobilePhone: '97543992',
     nationalIdNumber: '',
@@ -391,7 +391,7 @@ test('Default country code gets set in form values', async () => {
   // be populated with a default value
   const formData: GuestRegisterData = {
     firstName: 'Test',
-    lastName: 'Test2',
+    lastName: 'TestTwo',
     mobilePhoneCountry: '',
     mobilePhone: '97543992',
     nationalIdNumber: '',
@@ -441,7 +441,7 @@ test('Foreign country code gets set in form values', async () => {
 
   const formData: GuestRegisterData = {
     firstName: 'Test',
-    lastName: 'Test2',
+    lastName: 'TestTwo',
     mobilePhoneCountry: '',
     mobilePhone: '3892778472',
     nationalIdNumber: '',
diff --git a/frontend/src/utils/index.test.ts b/frontend/src/utils/index.test.ts
index 3acfa0d2..bdc3108c 100644
--- a/frontend/src/utils/index.test.ts
+++ b/frontend/src/utils/index.test.ts
@@ -57,23 +57,43 @@ test('Invalid e-mail', async () => {
 })
 
 test('Valid first name', async () => {
-  expect(isValidFirstName('Ååæž')).toEqual(true)
+  expect(isValidFirstName('AZ az ÀÖ ØÞßö øÿ Āſ')).toEqual(true)
 })
 
 test('Invalid first name', async () => {
   expect(isValidFirstName('')).toEqual('common:validation.firstNameRequired')
-  expect(isValidFirstName('aaƂåå')).toEqual('common:validation.firstNameContainsInvalidChars')
-  expect(isValidFirstName('汉字')).toEqual('common:validation.firstNameContainsInvalidChars')
+  expect(isValidFirstName('aaƂåå')).toEqual(
+    'common:validation.firstNameContainsInvalidChars'
+  )
+  expect(isValidFirstName('!')).toEqual(
+    'common:validation.firstNameContainsInvalidChars'
+  )
+  expect(isValidFirstName('÷')).toEqual(
+    'common:validation.firstNameContainsInvalidChars'
+  )
+  expect(isValidFirstName('汉字')).toEqual(
+    'common:validation.firstNameContainsInvalidChars'
+  )
 })
 
 test('Valid last name', async () => {
-  expect(isValidLastName('Ååæž')).toEqual(true)
+  expect(isValidLastName('AZ az ÀÖ ØÞßö øÿ Āſ')).toEqual(true)
 })
 
 test('Invalid last name', async () => {
   expect(isValidLastName('')).toEqual('common:validation.lastNameRequired')
-  expect(isValidLastName('aaƂåå')).toEqual('common:validation.lastNameContainsInvalidChars')
-  expect(isValidLastName('汉字')).toEqual('common:validation.lastNameContainsInvalidChars')
+  expect(isValidLastName('aaƂåå')).toEqual(
+    'common:validation.lastNameContainsInvalidChars'
+  )
+  expect(isValidLastName('!')).toEqual(
+    'common:validation.lastNameContainsInvalidChars'
+  )
+  expect(isValidLastName('÷')).toEqual(
+    'common:validation.lastNameContainsInvalidChars'
+  )
+  expect(isValidLastName('汉字')).toEqual(
+    'common:validation.lastNameContainsInvalidChars'
+  )
 })
 
 test('Body has values', async () => {
diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts
index d482cd40..d85c7f3f 100644
--- a/frontend/src/utils/index.ts
+++ b/frontend/src/utils/index.ts
@@ -122,9 +122,23 @@ export async function isValidEmail(data: string | undefined) {
 }
 
 function stringContainsIllegalChars(string: string): boolean {
-  // Only allow ISO-8859-1 and Latin Extended-A
+  // Only allow the following characters:
+  // ----- Basic Latin -----
+  // U+0020 (Space)
+  // U+0041 - U+005A (Latin Alphabet: Uppercase)
+  // U+0061 - U+007A (Latin Alphabet: Lowercase)
+  // ----- Latin-1 Supplement -----
+  // U+00C0 - U+00D6 (Letters: Uppercase)
+  // U+00D8 - U+00DE (Letters: Uppercase)
+  // U+00DF - U+00F6 (Letters: Lowercase)
+  // U+00F8 - U+00FF (Letters: Lowercase)
+  // ----- Latin Extended-A -----
+  // U+0100 - U+017F (European Latin)
+
   // eslint-disable-next-line no-control-regex
-  return /[^\u0000-\u017F]/g.test(string)
+  return /[^\u0020\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u017F]/g.test(
+    string
+  )
 }
 
 export function isValidFirstName(data: string | undefined): string | true {
@@ -341,7 +355,7 @@ function extractBirthdateFromFnr(nationalId: string): Date {
 function extractBirthdateFromDnumber(nationalId: string): Date {
   return suggestBirthDate(
     (parseInt(nationalId.charAt(0), 10) - 4).toString(10) +
-    nationalId.substring(1, 6)
+      nationalId.substring(1, 6)
   )
 }
 
diff --git a/greg/tests/test_utils.py b/greg/tests/test_utils.py
index 696a742b..75b5687e 100644
--- a/greg/tests/test_utils.py
+++ b/greg/tests/test_utils.py
@@ -32,8 +32,10 @@ def test_not_valid_so_number():
 @pytest.mark.parametrize(
     "string, expected_output",
     [
-        ("Ååæž", False),
+        ("AZ az ÀÖ ØÞßö øÿ Āſ", False),
         ("aaƂåå", True),
+        ("!", True),
+        ("÷", True),
         ("汉字", True),
     ],
 )
diff --git a/greg/utils.py b/greg/utils.py
index 8efa0b4b..b60d9b94 100644
--- a/greg/utils.py
+++ b/greg/utils.py
@@ -223,5 +223,21 @@ def role_invitation_date_validator(
 
 
 def string_contains_illegal_chars(string: str) -> bool:
-    # Only allow ISO-8859-1 and Latin Extended-A
-    return bool(re.search(r"[^\u0000-\u017F]", string))
+    # Only allow the following characters:
+    # ----- Basic Latin -----
+    # U+0020 (Space)
+    # U+0041 - U+005A (Latin Alphabet: Uppercase)
+    # U+0061 - U+007A (Latin Alphabet: Lowercase)
+    # ----- Latin-1 Supplement -----
+    # U+00C0 - U+00D6 (Letters: Uppercase)
+    # U+00D8 - U+00DE (Letters: Uppercase)
+    # U+00DF - U+00F6 (Letters: Lowercase)
+    # U+00F8 - U+00FF (Letters: Lowercase)
+    # ----- Latin Extended-A -----
+    # U+0100 - U+017F (European Latin)
+    return bool(
+        re.search(
+            r"[^\u0020\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u017F]",
+            string,
+        )
+    )
diff --git a/gregui/tests/api/views/test_invitation.py b/gregui/tests/api/views/test_invitation.py
index 54370a36..8c8cb866 100644
--- a/gregui/tests/api/views/test_invitation.py
+++ b/gregui/tests/api/views/test_invitation.py
@@ -747,7 +747,7 @@ def test_session_type_id_porten(
     data = {
         "person": {
             "first_name": "Updated",
-            "last_name": "Updated2",
+            "last_name": "UpdatedTwo",
             "private_mobile": "+4797543992",
         }
     }
@@ -763,7 +763,7 @@ def test_session_type_id_porten(
     person.refresh_from_db()
 
     assert person.first_name == "Updated"
-    assert person.last_name == "Updated2"
+    assert person.last_name == "UpdatedTwo"
 
 
 @pytest.mark.django_db
-- 
GitLab