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
|
git clone https://git@github.com/benmcollins/libjwt.git
|
||||||
(cd libjwt;
|
(cd libjwt;
|
||||||
git checkout v1.13.1;
|
git checkout v1.14.0;
|
||||||
autoreconf -fi;
|
autoreconf -fi;
|
||||||
env PKG_CONFIG_PATH=../deps/lib/pkgconfig:${PKG_CONFIG_PATH} ./configure --prefix=${BASE}/deps;
|
env PKG_CONFIG_PATH=../deps/lib/pkgconfig:${PKG_CONFIG_PATH} ./configure --prefix=${BASE}/deps;
|
||||||
make -j 40 install
|
make -j 40 install
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
* Quick demo of how to use libjwt using a HS256.
|
* Quick demo of how to use libjwt using a HS256.
|
||||||
*
|
*
|
||||||
* @author gback, CS 3214, Spring 2018, updated Spring 2021
|
* @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 <jwt.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -9,6 +13,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <jansson.h>
|
||||||
|
|
||||||
static const char * NEVER_EMBED_A_SECRET_IN_CODE = "supa secret";
|
static const char * NEVER_EMBED_A_SECRET_IN_CODE = "supa secret";
|
||||||
|
|
||||||
@ -56,6 +61,7 @@ main()
|
|||||||
if (encoded == NULL)
|
if (encoded == NULL)
|
||||||
die("jwt_encode_str", ENOMEM);
|
die("jwt_encode_str", ENOMEM);
|
||||||
|
|
||||||
|
jwt_free(mytoken);
|
||||||
printf("encoded as %s\nTry entering this at jwt.io\n", encoded);
|
printf("encoded as %s\nTry entering this at jwt.io\n", encoded);
|
||||||
|
|
||||||
jwt_t *ymtoken;
|
jwt_t *ymtoken;
|
||||||
@ -65,9 +71,32 @@ main()
|
|||||||
if (rc)
|
if (rc)
|
||||||
die("jwt_decode", rc);
|
die("jwt_decode", rc);
|
||||||
|
|
||||||
|
free(encoded);
|
||||||
char *grants = jwt_get_grants_json(ymtoken, NULL); // NULL means all
|
char *grants = jwt_get_grants_json(ymtoken, NULL); // NULL means all
|
||||||
if (grants == NULL)
|
if (grants == NULL)
|
||||||
die("jwt_get_grants_json", ENOMEM);
|
die("jwt_get_grants_json", ENOMEM);
|
||||||
|
|
||||||
|
jwt_free(ymtoken);
|
||||||
printf("redecoded: %s\n", grants);
|
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"
|
" -p port port number to bind to\n"
|
||||||
" -R rootdir root directory from which to serve files\n"
|
" -R rootdir root directory from which to serve files\n"
|
||||||
" -e seconds expiration time for tokens in seconds\n"
|
" -e seconds expiration time for tokens in seconds\n"
|
||||||
|
" -a enable HTML5 fallback\n"
|
||||||
" -h display this help\n"
|
" -h display this help\n"
|
||||||
, av0);
|
, av0);
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
|
@ -4,7 +4,7 @@ PORT=10000
|
|||||||
|
|
||||||
# to test against a working implementation (and see the intended responses)
|
# to test against a working implementation (and see the intended responses)
|
||||||
# change this variable, e.g.
|
# change this variable, e.g.
|
||||||
# use URL=http://theta.cs.vt.edu:3000/
|
#URL=http://hazelnut.rlogin:12345
|
||||||
URL=http://localhost:${PORT}
|
URL=http://localhost:${PORT}
|
||||||
|
|
||||||
# the file in which curl stores cookies across runs
|
# the file in which curl stores cookies across runs
|
||||||
@ -34,8 +34,17 @@ curl -v \
|
|||||||
curl -v \
|
curl -v \
|
||||||
${URL}/private/secret.txt
|
${URL}/private/secret.txt
|
||||||
|
|
||||||
# this should succeed since credentials are included
|
# this should succeed since credentials are included (via the cookie jar)
|
||||||
curl -v \
|
curl -v \
|
||||||
-b ${COOKIEJAR} \
|
-b ${COOKIEJAR} \
|
||||||
${URL}/private/secret.txt
|
${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:
|
else:
|
||||||
return sock
|
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):
|
def run_connection_check_empty_login(http_conn, hostname):
|
||||||
"""
|
"""
|
||||||
Run a check of the connection for validity, using a well-formed
|
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.
|
requesting a non-existent URL object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# GET request for the object /loadavg
|
# GET request for obj
|
||||||
http_conn.request("GET", obj, headers={"Host": hostname})
|
http_conn.request("GET", obj, headers={"Host": hostname})
|
||||||
|
|
||||||
# Get the server's response
|
# Get the server's response
|
||||||
@ -119,27 +98,6 @@ def run_404_check(http_conn, obj, hostname):
|
|||||||
server_response.read()
|
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):
|
def run_method_check(http_conn, method, hostname):
|
||||||
"""
|
"""
|
||||||
Check that the unsupported method supplied has either a NOT IMPLEMENTED
|
Check that the unsupported method supplied has either a NOT IMPLEMENTED
|
||||||
@ -165,92 +123,6 @@ def print_response(response):
|
|||||||
for line in lines:
|
for line in lines:
|
||||||
print(line.strip())
|
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):
|
def check_empty_login_respnse(response):
|
||||||
return response.strip() == "{}"
|
return response.strip() == "{}"
|
||||||
|
|
||||||
@ -385,7 +257,7 @@ class Single_Conn_Protocol_Case(Doc_Print_Test_Case):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
sock.send(encode("\r\n"))
|
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.
|
# response.
|
||||||
data = ""
|
data = ""
|
||||||
|
|
||||||
@ -1313,13 +1185,13 @@ class Single_Conn_Good_Case(Doc_Print_Test_Case):
|
|||||||
print("The server has crashed. Please investigate.")
|
print("The server has crashed. Please investigate.")
|
||||||
|
|
||||||
def test_login_get(self):
|
def test_login_get(self):
|
||||||
""" Test Name: test_loadavg_no_callback\n\
|
""" Test Name: test_login_get\n\
|
||||||
Number Connections: One \n\
|
Number Connections: One \n\
|
||||||
Procedure: Simple GET request:\n\
|
Procedure: Simple GET request:\n\
|
||||||
GET /api/login HTTP/1.1
|
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")
|
self.http_connection.request("GET", "/api/login")
|
||||||
|
|
||||||
# Get the server's response
|
# 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),
|
response = self.sessions[i].post('http://%s:%s/api/login' % (self.hostname, self.port),
|
||||||
json={'username': self.username, 'password': self.password},
|
json={'username': self.username, 'password': self.password},
|
||||||
timeout=2)
|
timeout=2)
|
||||||
|
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
raise AssertionError("The server did not respond within 2s")
|
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:
|
for cookie in self.sessions[i].cookies:
|
||||||
try:
|
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]
|
encoded_data = cookie.value.split('.')[1]
|
||||||
|
|
||||||
# Try to decode the payload
|
# Try to decode the payload
|
||||||
|
Loading…
x
Reference in New Issue
Block a user