diff --git a/README.md b/README.md index 05f02a2..fac3677 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This repository contains the base files for the CS 3214 - `src` - contains the base code's source files. - `tests` - contains unit tests, performance tests, and associated files. - `react-app` - contains a JavaScript web app. -- `fuzz` - contains documentation for the 'server fuzzing interface'. +- `sfi` - contains documentation for the 'server fuzzing interface'. ## Get Started Run the script: `./install-dependencies.sh`. Then, `cd` into `src` and type `make` to build the base code. diff --git a/tests/server_unit_test_pserv.py b/tests/server_unit_test_pserv.py index 02143c5..b65715b 100755 --- a/tests/server_unit_test_pserv.py +++ b/tests/server_unit_test_pserv.py @@ -1184,6 +1184,107 @@ class Access_Control(Doc_Print_Test_Case): self.assertEqual(response.status_code, requests.codes.ok, "Server failed to respond with private file despite being authenticated.") + def test_access_control_private_wrong_cookie(self): + """ Test Name: test_access_control_public_wrong_cookie + Number Connections: N/A + Procedure: Sends multiple requests with different cookies: + 0. (First, a POST to /api/login to retrieve a valid cookie) + 1. A cookie with the wrong name and correct JWT value + 2. A cookie with the wrong name and wrong JWT value + 3. Two cookies, both with wrong names and values + 4. One wrong cookie, and one correct cookie. + Requests 1-3 should NOT be allowed to access a private file. + Request 4 SHOULD be allowed to access a private file. + (Note: a "wrong" name/value means a name/value that doesn't + equal the cookie returned by a successful POST to /api/login.) + """ + # Login using the default credentials + try: + response = self.session.post('http://%s:%s/api/login' % (self.hostname, self.port), + json={'username': self.username, 'password': self.password}, + timeout=2) + except requests.exceptions.RequestException: + raise AssertionError("The server did not respond within 2s") + + # Ensure that the user is authenticated + self.assertEqual(response.status_code, requests.codes.ok, "Authentication failed.") + + # Define the private URL to get + url = 'http://%s:%s/%s' % (self.hostname, self.port, self.private_file) + + # find the cookie returned by the server (its name and value) + cookie_dict = self.session.cookies.get_dict() # convert cookies to dict + self.assertEqual(len(cookie_dict), 1, "Server did not return an authentication token.") + cookie_key = list(cookie_dict.keys())[0] # grab cookie's name + cookie_val = self.session.cookies.get(cookie_key) # grab cookie value + # create a few "bad cookie" key-value pairs + bad_cookie1_key = "a_bad_cookie1" + bad_cookie1_val = "chocolate_chip" + bad_cookie2_key = "a_bad_cookie2" + bad_cookie2_val = "oatmeal_raisin" + + # ---------- test 1: same cookie value, different name ---------- # + # append a string to the cookie name, making it the wrong cookie. + # Then, clear the session cookies and add the wrong cookie + self.session.cookies.clear() # wipe cookies + self.session.cookies.set(bad_cookie1_key, cookie_val) # add wrong-named cookie + + # Use the INVALID cookie to try to get the private file + try: + response = self.session.get(url, timeout=2) + except requests.exceptions.RequestException: + raise AssertionError("The server did not respond within 2s") + + # Ensure that access is forbidden + self.assertEqual(response.status_code, requests.codes.forbidden, + "Server responded with private file despite not being authenticated.") + + # ----------- test 2: different cookie value AND name ----------- # + # append a string to the cookie value. Then, update the bad cookie's value + self.session.cookies.set(bad_cookie1_key, bad_cookie1_val) + + # Use the INVALID cookie to try to get the private file + try: + response = self.session.get(url, timeout=2) + except requests.exceptions.RequestException: + raise AssertionError("The server did not respond within 2s") + + # Ensure that access is forbidden + self.assertEqual(response.status_code, requests.codes.forbidden, + "Server responded with private file despite not being authenticated.") + + # ------------- test 3: multiple incorrect cookies -------------- # + # set another cookie, so we end up sending TWO invalid cookies + self.session.cookies.set(bad_cookie2_key, bad_cookie2_val) + + # Use the INVALID cookies to try to get the private file + try: + response = self.session.get(url, timeout=2) + except requests.exceptions.RequestException: + raise AssertionError("The server did not respond within 2s") + + # Ensure that access is forbidden + self.assertEqual(response.status_code, requests.codes.forbidden, + "Server responded with private file despite not being authenticated.") + + # --------- test 4: one bad cookie, one correct cookie ---------- # + # clear the session cookies and add a bad cookie followed by a good cookie + self.session.cookies.clear() + self.session.cookies.set(bad_cookie1_key, bad_cookie1_val) + self.session.cookies.set(cookie_key, cookie_val) + + # this time, even though we do have a wrong cookie, we also have the + # correct cookie. So, the student's code *should* see this correct + # cookie and allow us to get access to the private file + try: + response = self.session.get(url, timeout=2) + except requests.exceptions.RequestException: + raise AssertionError("The server did not respond within 2s") + + # Ensure that access is forbidden + self.assertEqual(response.status_code, requests.codes.ok, + "Server failed to respond with private file despite being authenticated.") + def test_access_control_public_valid_token(self): """ Test Name: test_access_control_public_valid_token Number Connections: N/A @@ -1251,7 +1352,7 @@ class Access_Control(Doc_Print_Test_Case): # Ensure that file was correctly returned self.assertEqual(response.content, open(f'{base_dir}/{self.public_file_3}', 'rb').read()) - + def test_access_control_public_no_token(self): """ Test Name: test_access_control_public_no_token Number Connections: N/A @@ -1297,7 +1398,7 @@ class Access_Control(Doc_Print_Test_Case): # Ensure that access is granted self.assertEqual(response.status_code, requests.codes.ok, "Server failed to respond with a public file.") - + def test_access_control_private_invalid_token(self): """ Test Name: test_access_control_private_invalid_token Number Connections: N/A @@ -1362,6 +1463,42 @@ class Access_Control(Doc_Print_Test_Case): # Ensure that access is forbidden self.assertEqual(response.status_code, requests.codes.forbidden, "Server responded with private file despite not being authenticated.") + + def test_access_control_private_valid_flipped_token(self): + """ Test Name: test_access_control_private_valid_flipped_token + Number Connections: N/A + Procedure: Checks if JSON parsing appropriately covers the case + when the correct key/value pair is present, but the + order in which it appears is flipped. + For example, typically one might expect: + '{"username": "", "password": ""}' + This tests for parsing of: + '{"password": "", "username": ""}' + """ + # Login using the default credentials, with the password appearing + # first in the JWT, followed by the username + try: + response = self.session.post('http://%s:%s/api/login' % (self.hostname, self.port), + json={'password': self.password, 'username': self.username}, + timeout=2) + except requests.exceptions.RequestException: + raise AssertionError("The server did not respond within 2s") + + # Ensure that the user is authenticated + self.assertEqual(response.status_code, requests.codes.ok, "Authentication failed.") + + # Define the private URL to get + url = 'http://%s:%s/%s' % (self.hostname, self.port, self.private_file) + + # Use the session cookie to get the private file + try: + response = self.session.get(url, timeout=2) + except requests.exceptions.RequestException: + raise AssertionError("The server did not respond within 2s") + + # Ensure that access is forbidden + self.assertEqual(response.status_code, requests.codes.ok, + "Server did not respond with private file despite being authenticated.") def test_access_control_private_no_token(self): """ Test Name: test_access_control_private_no_token