Repository: ucfopen/canvasapi (language: python)

Issue:
JWTs
**What resource needs additional coverage?**
JWTs

**What endpoints need to be covered?**
- [ ] [Create JWT](https://canvas.instructure.com/doc/api/jw_ts.html#method.jwts.create)
- [ ] [Refresh JWT](https://canvas.instructure.com/doc/api/jw_ts.html#method.jwts.refresh)


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 360f696d..16896ebf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,10 +2,6 @@
 
 ## [Unreleased]
 
-### General
-
-- Added support for Course Events; new Course Event class. (Thanks, [dmols](https://github.com/dmols))
-
 ### New Endpoint Coverage
 
 - Account Calendars (Thanks, [@dmols](https://github.com/dmols))
@@ -14,6 +10,9 @@
     - Update a calendar's visibility
     - Update many calendars' visibility
     - List all account calendars
+- JWTs (Thanks [@dmols](https://github.com/dmols))
+    - Create JWTs
+    - Refresh JWTs
 - Query Course Events (Thanks, [@dmols](https://github.com/dmols))
     - Query by course
     - Query by account
diff --git a/canvasapi/canvas.py b/canvasapi/canvas.py
index 3ae0a7e9..aaa23e0d 100644
--- a/canvasapi/canvas.py
+++ b/canvasapi/canvas.py
@@ -15,6 +15,7 @@
 from canvasapi.file import File
 from canvasapi.folder import Folder
 from canvasapi.group import Group, GroupCategory
+from canvasapi.jwt import JWT
 from canvasapi.outcome import Outcome, OutcomeGroup
 from canvasapi.paginated_list import PaginatedList
 from canvasapi.planner import PlannerNote, PlannerOverride
@@ -298,6 +299,21 @@ def create_group(self, **kwargs):
         )
         return Group(self.__requester, response.json())
 
+    def create_jwt(self, **kwargs):
+        """
+        Creates a unique JWT to use with other Canvas services.
+
+        :calls: `POST /api/v1/jwts \
+        <https://canvas.instructure.com/doc/api/jw_ts.html#method.jwts.create>`_
+
+        :rtype: list of :class:`canvasapi.jwt.JWT`
+        """
+        response = self.__requester.request(
+            "POST", "jwts", _kwargs=combine_kwargs(**kwargs)
+        )
+
+        return JWT(self.__requester, response.json())
+
     def create_planner_note(self, **kwargs):
         """
         Create a planner note for the current user
@@ -1269,6 +1285,27 @@ def graphql(self, query, variables=None, **kwargs):
 
         return response.json()
 
+    def refresh_jwt(self, jwt, **kwargs):
+        """
+        Refreshes a JWT for reuse with other canvas services. It generates a
+        different JWT each time it's called; expires after one hour.
+
+        :calls: `POST /api/v1/jwts/refresh \
+        <https://canvas.instructure.com/doc/api/jw_ts.html#method.jwts.refresh>`_
+
+        :param jwt: An existing JWT to refresh.
+        :type jwt: str or :class:`canvasapi.jwt.JWT`
+        :rtype: :class:`canvasapi.jwt.JWT`
+        """
+        if isinstance(jwt, JWT):
+            jwt = jwt.token
+
+        response = self.__requester.request(
+            "POST", "jwts/refresh", jwt=jwt, _kwargs=combine_kwargs(**kwargs)
+        )
+
+        return JWT(self.__requester, response.json())
+
     def reserve_time_slot(self, calendar_event, participant_id=None, **kwargs):
         """
         Return single Calendar Event by id
diff --git a/canvasapi/jwt.py b/canvasapi/jwt.py
new file mode 100644
index 00000000..72988fdd
--- /dev/null
+++ b/canvasapi/jwt.py
@@ -0,0 +1,6 @@
+from canvasapi.canvas_object import CanvasObject
+
+
+class JWT(CanvasObject):
+    def __str__(self):
+        return "{}".format(self.token)
diff --git a/docs/class-reference.rst b/docs/class-reference.rst
index f5ce6cd0..7420abff 100644
--- a/docs/class-reference.rst
+++ b/docs/class-reference.rst
@@ -38,6 +38,7 @@ Class Reference
     grading-period-ref
     grade-change-log-ref
     group-ref
+    jwt-ref
     login-ref
     license-ref
     module-ref
diff --git a/docs/jwt-ref.rst b/docs/jwt-ref.rst
new file mode 100644
index 00000000..0718442b
--- /dev/null
+++ b/docs/jwt-ref.rst
@@ -0,0 +1,6 @@
+===
+JWT
+===
+
+.. autoclass:: canvasapi.jwt.JWT
+    :members:
diff --git a/scripts/find_missing_modules.py b/scripts/find_missing_modules.py
index 318871ea..37c205aa 100644
--- a/scripts/find_missing_modules.py
+++ b/scripts/find_missing_modules.py
@@ -6,7 +6,6 @@
 
 import canvasapi  # noqa
 
-
 exempt_files = ("__init__",)
 
 


Test patch:
diff --git a/tests/fixtures/jwt.json b/tests/fixtures/jwt.json
new file mode 100644
index 00000000..e4c414a7
--- /dev/null
+++ b/tests/fixtures/jwt.json
@@ -0,0 +1,17 @@
+{
+  "create_jwt": {
+    "method": "POST",
+    "endpoint": "jwts",
+    "data": {
+      "token": "ZjM0UTZmLyVNSjdqb10wLV9jQSxeUiogXUlCWUs7Tg=="
+    }
+  },
+
+  "refresh_jwt": {
+    "method": "POST",
+    "endpoint": "jwts/refresh",
+    "data": {
+      "token": "O3MzNjpPKWc+fmFfMXRJJiEoR1VbSDVDT1IzUF1IJUpjJ3JSe0lrMHw8OUlX"
+    }
+  }
+}
diff --git a/tests/test_canvas.py b/tests/test_canvas.py
index 0a0320d3..8533565e 100644
--- a/tests/test_canvas.py
+++ b/tests/test_canvas.py
@@ -19,6 +19,7 @@
 from canvasapi.exceptions import RequiredFieldMissing, ResourceDoesNotExist
 from canvasapi.file import File
 from canvasapi.group import Group, GroupCategory
+from canvasapi.jwt import JWT
 from canvasapi.outcome import Outcome, OutcomeGroup
 from canvasapi.paginated_list import PaginatedList
 from canvasapi.poll import Poll
@@ -96,6 +97,47 @@ def test_create_account(self, m):
         self.assertTrue(hasattr(account, "name"))
         self.assertEqual(account.name, name)
 
+    # test token creation
+    def test_create_jwt(self, m):
+        register_uris({"jwt": ["create_jwt"]}, m)
+
+        # verify returned object is of type JWT
+        jwt = self.canvas.create_jwt()
+        self.assertIsInstance(jwt, JWT)
+
+        # check the token is correct and we received the right object
+        self.assertEqual(jwt.token, "ZjM0UTZmLyVNSjdqb10wLV9jQSxeUiogXUlCWUs7Tg==")
+
+    # refresh token
+    def test_refresh_jwt_str(self, m):
+        register_uris({"jwt": ["refresh_jwt"]}, m)
+
+        old_token = "ZjM0UTZmLyVNSjdqb10wLV9jQSxeUiogXUlCWUs7Tg=="
+        new_token = "O3MzNjpPKWc+fmFfMXRJJiEoR1VbSDVDT1IzUF1IJUpjJ3JSe0lrMHw8OUlX"
+
+        # verify returned object is of type JWT
+        jwt = self.canvas.refresh_jwt(jwt=old_token)
+        self.assertIsInstance(jwt, JWT)
+
+        # check the token is correct and we received the right object
+        self.assertEqual(jwt.token, new_token)
+
+    def test_refresh_jwt_obj(self, m):
+        register_uris({"jwt": ["create_jwt", "refresh_jwt"]}, m)
+
+        old_token = "ZjM0UTZmLyVNSjdqb10wLV9jQSxeUiogXUlCWUs7Tg=="
+        new_token = "O3MzNjpPKWc+fmFfMXRJJiEoR1VbSDVDT1IzUF1IJUpjJ3JSe0lrMHw8OUlX"
+
+        old_jwt = self.canvas.create_jwt()
+        self.assertEqual(old_jwt.token, old_token)
+
+        # verify returned object is of type JWT
+        jwt = self.canvas.refresh_jwt(jwt=old_jwt)
+        self.assertIsInstance(jwt, JWT)
+
+        # check the token is correct and we received the right object
+        self.assertEqual(jwt.token, new_token)
+
     # create_poll()
     def test_create_poll(self, m):
         register_uris({"poll": ["create_poll"]}, m)
diff --git a/tests/test_jwt.py b/tests/test_jwt.py
new file mode 100644
index 00000000..f6e81275
--- /dev/null
+++ b/tests/test_jwt.py
@@ -0,0 +1,23 @@
+import unittest
+
+import requests_mock
+
+from canvasapi import Canvas
+from tests import settings
+from tests.util import register_uris
+
+
+@requests_mock.Mocker()
+class TestJWT(unittest.TestCase):
+    def setUp(self):
+        self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)
+
+        with requests_mock.Mocker() as m:
+            register_uris({"jwt": ["create_jwt"]}, m)
+
+            self.jwt = self.canvas.create_jwt()
+
+    # __str__()
+    def test__str__(self, m):
+        string = str(self.jwt)
+        self.assertIsInstance(string, str)