Repository: jupyterhub/ltiauthenticator

Issue:
Update package with common utility functions
The LTI 1.1 and LTI 1.3 code (primarily the authenticators and the validators) have some common functions. Refactor the code so that these common tasks are encapsulated in functions to make them easier to document, test, and re-use.

Related to #38 

Comments:

Patch:
diff --git a/ltiauthenticator/utils.py b/ltiauthenticator/utils.py
new file mode 100644
index 0000000..0f6bfb7
--- /dev/null
+++ b/ltiauthenticator/utils.py
@@ -0,0 +1,41 @@
+from tornado.log import app_log
+from tornado.web import RequestHandler
+
+
+def get_client_protocol(handler: RequestHandler) -> dict:
+    """
+    Gets first protocol from the x-forwarded-proto header that should
+    represent the client's original http/https request.
+
+    Args:
+        handler: a tornado.web.RequestHandler object
+
+    Returns:
+        A decoded dict with keys/values extracted from the request's arguments
+    """
+    if "x-forwarded-proto" in handler.request.headers:
+        hops = [
+            h.strip() for h in handler.request.headers["x-forwarded-proto"].split(",")
+        ]
+        protocol = hops[0]
+    else:
+        protocol = handler.request.protocol
+    app_log.debug("First protocol from x-forwarded-proto list: %s" % protocol)
+    return protocol
+
+
+def convert_request_to_dict(arguments: dict) -> dict:
+    """
+    Converts the arguments obtained from a request to a dict.
+
+    Args:
+        handler: a tornado.web.RequestHandler object
+
+    Returns:
+        A decoded dict with keys/values extracted from the request's arguments
+    """
+    args = {}
+    for k, values in arguments.items():
+        args[k] = values[0].decode()
+    app_log.debug("Request converted to dict: %s" % args)
+    return args
diff --git a/setup.py b/setup.py
index 4977b7d..92bb924 100644
--- a/setup.py
+++ b/setup.py
@@ -30,7 +30,7 @@ setup(
     license="3 Clause BSD",
     packages=find_packages(exclude="./tests"),
     python_requires=">=3.6",
-    install_requires=["jupyterhub>=0.8", "oauthlib>=3.1"],
+    install_requires=["jupyterhub>=0.8", "oauthlib>=3.1", "escapism>=1.0"],
     package_data={
         "": ["*.html"],
     },  # noqa: E231


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..4bfe494
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,42 @@
+from unittest.mock import Mock
+
+from tornado.web import RequestHandler
+
+from ltiauthenticator.utils import convert_request_to_dict
+from ltiauthenticator.utils import get_client_protocol
+
+
+def test_get_protocol_with_more_than_one_value():
+    """
+    Assert that the first (left-most) protocol value is correctly fetched from the x-forwarded-header.
+    """
+    handler = Mock(
+        spec=RequestHandler,
+        request=Mock(
+            headers={"x-forwarded-proto": "https,http,http"},
+            protocol="https",
+        ),
+    )
+    expected = "https"
+    protocol = get_client_protocol(handler)
+
+    assert expected == protocol
+
+
+def test_convert_request_arguments_with_encoded_items_to_dict():
+    """
+    Assert that a dict of k/v's is correctly created when receiving encoded values.
+    """
+    arguments = {
+        "key1": [b"value1"],
+        "key2": [b"value2"],
+        "key3": [b"value3"],
+    }
+    expected = {
+        "key1": "value1",
+        "key2": "value2",
+        "key3": "value3",
+    }
+    result = convert_request_to_dict(arguments)
+
+    assert expected == result