From 06153db5c1b6cc9875adadbc5381599f3b9c70fa Mon Sep 17 00:00:00 2001 From: Godmar Back Date: Thu, 17 Nov 2022 01:10:53 -0500 Subject: [PATCH] handout Fall'22 - added Jansson examples to jwt_demo_hs256 - added cookie property tests --- install-dependencies.sh | 2 +- src/jwt_demo_hs256.c | 29 +++++++ src/main.c | 1 + src/testloginapi.sh | 13 ++- tests/server_unit_test_pserv.py | 143 +++----------------------------- 5 files changed, 53 insertions(+), 135 deletions(-) diff --git a/install-dependencies.sh b/install-dependencies.sh index 2c7fc9a..29b1c28 100755 --- a/install-dependencies.sh +++ b/install-dependencies.sh @@ -19,7 +19,7 @@ git clone https://github.com/akheron/jansson.git git clone https://git@github.com/benmcollins/libjwt.git (cd libjwt; - git checkout v1.13.1; + git checkout v1.14.0; autoreconf -fi; env PKG_CONFIG_PATH=../deps/lib/pkgconfig:${PKG_CONFIG_PATH} ./configure --prefix=${BASE}/deps; make -j 40 install diff --git a/src/jwt_demo_hs256.c b/src/jwt_demo_hs256.c index 1d197e2..55f743a 100644 --- a/src/jwt_demo_hs256.c +++ b/src/jwt_demo_hs256.c @@ -2,6 +2,10 @@ * Quick demo of how to use libjwt using a HS256. * * @author gback, CS 3214, Spring 2018, updated Spring 2021 + * Added Jansson demo Fall'22 + * + * I included the necessary free() operations here which + * are needed in a long-running server. */ #include #include @@ -9,6 +13,7 @@ #include #include #include +#include static const char * NEVER_EMBED_A_SECRET_IN_CODE = "supa secret"; @@ -56,6 +61,7 @@ main() if (encoded == NULL) die("jwt_encode_str", ENOMEM); + jwt_free(mytoken); printf("encoded as %s\nTry entering this at jwt.io\n", encoded); jwt_t *ymtoken; @@ -65,9 +71,32 @@ main() if (rc) die("jwt_decode", rc); + free(encoded); char *grants = jwt_get_grants_json(ymtoken, NULL); // NULL means all if (grants == NULL) die("jwt_get_grants_json", ENOMEM); + jwt_free(ymtoken); printf("redecoded: %s\n", grants); + + // an example of how to use Jansson + json_error_t error; + json_t *jgrants = json_loadb(grants, strlen(grants), 0, &error); + if (jgrants == NULL) + die("json_loadb", EINVAL); + + free (grants); + + json_int_t exp, iat; + const char *sub; + rc = json_unpack(jgrants, "{s:I, s:I, s:s}", + "exp", &exp, "iat", &iat, "sub", &sub); + if (rc == -1) + die("json_unpack", EINVAL); + + printf ("exp: %lld\n", exp); + printf ("iat: %lld\n", iat); + printf ("sub: %s\n", sub); + + json_decref(jgrants); } diff --git a/src/main.c b/src/main.c index f07b17a..d328ada 100644 --- a/src/main.c +++ b/src/main.c @@ -62,6 +62,7 @@ usage(char * av0) " -p port port number to bind to\n" " -R rootdir root directory from which to serve files\n" " -e seconds expiration time for tokens in seconds\n" + " -a enable HTML5 fallback\n" " -h display this help\n" , av0); exit(EXIT_FAILURE); diff --git a/src/testloginapi.sh b/src/testloginapi.sh index bee1770..b17998e 100644 --- a/src/testloginapi.sh +++ b/src/testloginapi.sh @@ -4,7 +4,7 @@ PORT=10000 # to test against a working implementation (and see the intended responses) # change this variable, e.g. -# use URL=http://theta.cs.vt.edu:3000/ +#URL=http://hazelnut.rlogin:12345 URL=http://localhost:${PORT} # the file in which curl stores cookies across runs @@ -34,8 +34,17 @@ curl -v \ curl -v \ ${URL}/private/secret.txt -# this should succeed since credentials are included +# this should succeed since credentials are included (via the cookie jar) curl -v \ -b ${COOKIEJAR} \ ${URL}/private/secret.txt +# now log out +curl -v -X POST \ + -c ${COOKIEJAR} \ + ${URL}/api/logout + +# this should fail since the cookie should have been removed from the cookie jar +curl -v \ + -b ${COOKIEJAR} \ + ${URL}/private/secret.txt diff --git a/tests/server_unit_test_pserv.py b/tests/server_unit_test_pserv.py index 0d2620d..dd2dd21 100755 --- a/tests/server_unit_test_pserv.py +++ b/tests/server_unit_test_pserv.py @@ -61,27 +61,6 @@ def get_socket_connection(hostname, port): else: return sock - -def run_connection_check_loadavg(http_conn, hostname): - """ - Run a check of the connection for validity, using a well-formed - request for /loadavg and checking it after receiving it. - """ - - # GET request for the object /loadavg - http_conn.request("GET", "/loadavg", headers={"Host": hostname}) - - # Get the server's response - server_response = http_conn.getresponse() - - # Check the response status code - assert server_response.status == OK, "Server failed to respond" - - # Check the data included in the server's response - assert check_loadavg_response(server_response.read().decode('utf-8')), \ - "loadavg check failed" - - def run_connection_check_empty_login(http_conn, hostname): """ Run a check of the connection for validity, using a well-formed @@ -107,7 +86,7 @@ def run_404_check(http_conn, obj, hostname): requesting a non-existent URL object. """ - # GET request for the object /loadavg + # GET request for obj http_conn.request("GET", obj, headers={"Host": hostname}) # Get the server's response @@ -119,27 +98,6 @@ def run_404_check(http_conn, obj, hostname): server_response.read() -def run_query_check(http_conn, request, req_object, callback, hostname): - """ - Checks that the server properly processes the query string passed to it. - """ - - http_conn.request("GET", request, headers={"Host": hostname}) - server_response = http_conn.getresponse() - assert server_response.status == OK, "Server failed to respond" - - if callback is None: - if req_object == "loadavg": - assert check_loadavg_response(server_response.read().decode('utf-8')), \ - "loadavg check failed" - else: - assert check_meminfo_response(server_response.read().decode('utf-8')), \ - "meminfo check failed" - else: - assert check_callback_response(server_response.read().decode('utf-8'), - callback, req_object), "callback check failed" - - def run_method_check(http_conn, method, hostname): """ Check that the unsupported method supplied has either a NOT IMPLEMENTED @@ -165,92 +123,6 @@ def print_response(response): for line in lines: print(line.strip()) - -def check_loadavg_response(response): - """Check that the response to a loadavg request generated the correctly - formatted output. Returns true if it executes properly, throws an - AssertionError if it does not execute properly or another error if json - is unable to decode the response.""" - - try: - data = json.loads(response.strip()) - except ValueError as msg: - raise AssertionError("Invalid JSON object. Received: " + response) - - assert len(data) == 3, "Improper number of data items returned" - - assert 'total_threads' in data, "total_threads element missing" - assert 'loadavg' in data, "loadavg element missing" - assert 'running_threads' in data, "running_threads element missing" - - assert len(data['loadavg']) == 3, 'Improper number of data items in \ - loadavg' - - return True - - -def check_meminfo_response(response): - """Check that the response to a meminfo request generated the correctly - formatted output. Returns true if it executes properly, throws an - AssertionError if it does not execute properly or another error if json - is unable to decode the response.""" - - try: - data = json.loads(response.strip()) - except ValueError as msg: - raise AssertionError("Invalid JSON object. Received: " + response) - - for line in open("/proc/meminfo"): - entry = re.split(":?\s+", line) - assert entry[0] in data, entry[0] + " key is missing" - - try: - int(data[entry[0]]) - except (TypeError, ValueError): - raise AssertionError("a non-integer was passed to meminfo") - - return True - - -def check_callback_response(response, callback, req_obj): - """Check that the response to a req_obj request with callback function - callback generated the correctly formatted output. Returns true if it - executes properly, throws an AssertionError if it does not execute properly - or another error if json is unable to decode the response.""" - callback.replace(' ', '') - response.replace(' ', '') - assert response[0:len(callback) + 1] == callback + "(", 'callback incorrect, was: ' + response[0:len( - callback) + 1] + ' , expected: ' + callback + '(' - assert response[len(response) - 1] == ")", 'missing close parenthesis' - - if req_obj == "meminfo": - check_meminfo_response(response[len(callback) + 1:len(response) - 1]) - elif req_obj == "loadavg": - check_loadavg_response(response[len(callback) + 1:len(response) - 1]) - else: - return False - - return True - - -def check_meminfo_change(response1, response2, key, delta, safety_margin=0): - """Check that a specific value in meminfo has changed by at least some amount. - Used by allocanon and freeanon tests. Returns true if it executes properly, - throws an AssertionError if it does not execute properly or another error - if json is unable to decode the response.""" - - check_meminfo_response(response1) - check_meminfo_response(response2) - - data1 = json.loads(response1.strip()) - data2 = json.loads(response2.strip()) - - if delta >= 0: - return float(data2[key]) - float(data1[key]) > delta * (1 - safety_margin) - else: - return float(data2[key]) - float(data1[key]) < delta * (1 - safety_margin) - - def check_empty_login_respnse(response): return response.strip() == "{}" @@ -385,7 +257,7 @@ class Single_Conn_Protocol_Case(Doc_Print_Test_Case): pass sock.send(encode("\r\n")) - # If there is a HTTP response, it should be a valid /loadavg + # If there is a HTTP response, it should be a valid /login # response. data = "" @@ -1313,13 +1185,13 @@ class Single_Conn_Good_Case(Doc_Print_Test_Case): print("The server has crashed. Please investigate.") def test_login_get(self): - """ Test Name: test_loadavg_no_callback\n\ + """ Test Name: test_login_get\n\ Number Connections: One \n\ Procedure: Simple GET request:\n\ GET /api/login HTTP/1.1 """ - # GET request for the object /loadavg + # GET request for the object /api/login self.http_connection.request("GET", "/api/login") # Get the server's response @@ -2005,6 +1877,7 @@ class Authentication(Doc_Print_Test_Case): response = self.sessions[i].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") @@ -2016,6 +1889,12 @@ class Authentication(Doc_Print_Test_Case): for cookie in self.sessions[i].cookies: try: + self.assertEquals(cookie.path, "/", "Cookie path should be /") + self.assertTrue("HttpOnly" in cookie._rest, "Cookie is not http only.") + maxage = cookie.expires - time.mktime(datetime.now().timetuple()) + if abs(maxage - int(auth_token_expiry)) > 1: + raise AssertionError(f"Cookie's Max-Age is {maxage} should be {auth_token_expiry}") + encoded_data = cookie.value.split('.')[1] # Try to decode the payload