From ecd70d8dd9ceeca4e8d429f931a0ec4fd837e69a Mon Sep 17 00:00:00 2001
From: Aurélien Bompard <aurelien@bompard.org>
Date: Jun 17 2022 19:33:45 +0000
Subject: Don't expire refreshable OIDC tokens


Fixes: #372

Signed-off-by: Aurélien Bompard <aurelien@bompard.org>

---

diff --git a/ipsilon/providers/openidc/store.py b/ipsilon/providers/openidc/store.py
index 44cfa4a..8cbf77f 100644
--- a/ipsilon/providers/openidc/store.py
+++ b/ipsilon/providers/openidc/store.py
@@ -385,7 +385,14 @@ class OpenIDCStore(Store):
         tokens = self.get_unique_data('token')
         cleaned = 0
         for iden in tokens:
-            if int(tokens[iden]['expires_at']) <= int(time.time()):
+            token_props = tokens[iden]
+            # Values are stored as a JSON string
+            refreshable = json.loads(token_props.get("refreshable", "null"))
+            if refreshable:
+                expires_at = token_props.get('refreshable_until')
+            else:
+                expires_at = token_props['expires_at']
+            if expires_at is not None and int(expires_at) <= int(time.time()):
                 cleaned += 1
                 self.invalidateToken(iden)
         return cleaned
diff --git a/tests/openidc.py b/tests/openidc.py
index 80c52cb..558f713 100755
--- a/tests/openidc.py
+++ b/tests/openidc.py
@@ -295,7 +295,7 @@ if __name__ == '__main__':
             'providers/openidc/admin/client/%s' % reg_resp['client_id'],
             {'Client Name': 'Test suite client updated'})
 
-    with TC.case('Retrieving toke info'):
+    with TC.case('Retrieving token info'):
         # Testing token without client auth
         r = requests.post('https://127.0.0.10:45080/idp1/openidc/TokenInfo',
                           data={'token': new_token['access_token']})
diff --git a/tests/testcleanup.py b/tests/testcleanup.py
index bfa0289..a728624 100755
--- a/tests/testcleanup.py
+++ b/tests/testcleanup.py
@@ -11,6 +11,8 @@ import sqlite3
 from string import Template
 import time
 
+from ipsilon.providers.openidc.store import OpenIDCStore, OpenIDCStaticStore
+
 idp_g = {'TEMPLATES': '${TESTDIR}/templates/install',
          'CONFDIR': '${TESTDIR}/etc',
          'DATADIR': '${TESTDIR}/lib',
@@ -156,3 +158,53 @@ if __name__ == '__main__':
         cur.execute('SELECT * FROM saml2_sessions;')
         if len(cur.fetchall()) != 0:
             raise ValueError('SAML2 sessions left behind: %s' % cur.fetchall())
+
+
+    with TC.case('Checking that refreshable OpenIDC tokens are not expired'):
+        static_db_path = os.path.join(os.environ['TESTDIR'], 'lib/idp1/openidc.static.sqlite')
+        db_path = os.path.join(os.environ['TESTDIR'], 'lib/idp1/openidc.sqlite')
+        static_store = OpenIDCStaticStore(database_url=f"sqlite:///{static_db_path}")
+        store = OpenIDCStore(
+            database_url=f"sqlite:///{db_path}", static_store=static_store
+        )
+
+        token_refreshable = store.issueToken(
+            client_id="client-id", username="username", scope=["openid"],
+            issue_refresh=True, userinfocode="userinfocode"
+        )
+
+        token_non_refreshable = store.issueToken(
+            client_id="client-id", username="username", scope=["openid"],
+            issue_refresh=False, userinfocode="userinfocode"
+        )
+
+        assert len(store.get_unique_data("token")) == 2
+
+        conn = sqlite3.connect(db_path)
+        cur = conn.cursor()
+
+        expired_ts = int(time.time()) - 1
+
+        # Setting tokens to expire
+        cur.execute(
+            "UPDATE token SET value = ? WHERE name = 'expires_at'",
+            (expired_ts,)
+        )
+        conn.commit()
+        conn.close()
+
+        try:
+            cleanup_count = store._cleanupExpiredTokens()
+        except Exception as e:
+            print(e)
+            raise
+
+        if cleanup_count != 1:
+            raise Exception(
+                f"Should only have cleaned up 1 token, cleaned {cleanup_count}"
+            )
+
+        tokens = store.get_unique_data("token")
+        assert len(tokens) == 1
+        if list(tokens.keys())[0] != token_refreshable["token_id"]:
+            raise Exception("The refreshable token has been cleaned up")

