handout Fall'22
- added Jansson examples to jwt_demo_hs256 - added cookie property tests
This commit is contained in:
parent
be04f7d339
commit
06153db5c1
@ -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
|
||||
|
@ -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 <jwt.h>
|
||||
#include <stdlib.h>
|
||||
@ -9,6 +13,7 @@
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <jansson.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user