about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2023-06-30 09:19:39 +0300
committerFrederick Muriuki Muriithi2023-06-30 09:19:39 +0300
commitcd16f99aa23123f2398e3a3a542d84363d7a7b16 (patch)
tree019eadec715989ee80cc62f5f78070d54dc8c75d
parentd46d1eb47f9ee18518894d850a18cd952231dddc (diff)
downloadgenenetwork3-cd16f99aa23123f2398e3a3a542d84363d7a7b16.tar.gz
List all OAuth2 clients.
-rw-r--r--gn3/auth/authentication/oauth2/models/oauth2client.py33
-rw-r--r--gn3/auth/authentication/users.py16
-rw-r--r--gn3/auth/authorisation/users/admin/views.py20
-rw-r--r--gn3/templates/admin/dashboard.html4
-rw-r--r--gn3/templates/admin/list-oauth2-clients.html45
5 files changed, 110 insertions, 8 deletions
diff --git a/gn3/auth/authentication/oauth2/models/oauth2client.py b/gn3/auth/authentication/oauth2/models/oauth2client.py
index 14c4c94..564ed32 100644
--- a/gn3/auth/authentication/oauth2/models/oauth2client.py
+++ b/gn3/auth/authentication/oauth2/models/oauth2client.py
@@ -1,13 +1,13 @@
 """OAuth2 Client model."""
 import json
-import uuid
 import datetime
+from uuid import UUID
 from typing import Sequence, Optional, NamedTuple
 
 from pymonad.maybe import Just, Maybe, Nothing
 
 from gn3.auth import db
-from gn3.auth.authentication.users import User, user_by_id, same_password
+from gn3.auth.authentication.users import User, users, user_by_id, same_password
 
 from gn3.auth.authorisation.errors import NotFoundError
 
@@ -18,7 +18,7 @@ class OAuth2Client(NamedTuple):
     This is defined according to the mixin at
     https://docs.authlib.org/en/latest/specs/rfc6749.html#authlib.oauth2.rfc6749.ClientMixin
     """
-    client_id: uuid.UUID
+    client_id: UUID
     client_secret: str
     client_id_issued_at: datetime.datetime
     client_secret_expires_at: datetime.datetime
@@ -129,7 +129,7 @@ class OAuth2Client(NamedTuple):
         """Return the default redirect uri"""
         return self.client_metadata.get("default_redirect_uri", "")
 
-def client(conn: db.DbConnection, client_id: uuid.UUID,
+def client(conn: db.DbConnection, client_id: UUID,
            user: Optional[User] = None) -> Maybe:
     """Retrieve a client by its ID"""
     with db.cursor(conn) as cursor:
@@ -145,7 +145,7 @@ def client(conn: db.DbConnection, client_id: uuid.UUID,
                     the_user = None
 
             return Just(
-                OAuth2Client(uuid.UUID(result["client_id"]),
+                OAuth2Client(UUID(result["client_id"]),
                              result["client_secret"],
                              datetime.datetime.fromtimestamp(
                                  result["client_id_issued_at"]),
@@ -156,7 +156,7 @@ def client(conn: db.DbConnection, client_id: uuid.UUID,
 
     return Nothing
 
-def client_by_id_and_secret(conn: db.DbConnection, client_id: uuid.UUID,
+def client_by_id_and_secret(conn: db.DbConnection, client_id: UUID,
                             client_secret: str) -> OAuth2Client:
     """Retrieve a client by its ID and secret"""
     with db.cursor(conn) as cursor:
@@ -171,7 +171,7 @@ def client_by_id_and_secret(conn: db.DbConnection, client_id: uuid.UUID,
                 datetime.datetime.fromtimestamp(
                     row["client_secret_expires_at"]),
                 json.loads(row["client_metadata"]),
-                user_by_id(conn, uuid.UUID(row["user_id"])))
+                user_by_id(conn, UUID(row["user_id"])))
 
         raise NotFoundError("Could not find client with the given credentials.")
 
@@ -203,3 +203,22 @@ def save_client(conn: db.DbConnection, the_client: OAuth2Client) -> OAuth2Client
                 "user_id": str(the_client.user.user_id)
             })
         return the_client
+
+def oauth2_clients(conn: db.DbConnection) -> tuple[OAuth2Client, ...]:
+    """Fetch a list of all OAuth2 clients."""
+    with db.cursor(conn) as cursor:
+        cursor.execute("SELECT * FROM oauth2_clients")
+        clients_rs = cursor.fetchall()
+        the_users = {
+            usr.user_id: usr for usr in users(
+                conn, tuple({UUID(result["user_id"]) for result in clients_rs}))
+        }
+        return tuple(OAuth2Client(UUID(result["client_id"]),
+                                  result["client_secret"],
+                                  datetime.datetime.fromtimestamp(
+                                     result["client_id_issued_at"]),
+                                  datetime.datetime.fromtimestamp(
+                                     result["client_secret_expires_at"]),
+                                  json.loads(result["client_metadata"]),
+                                  the_users[UUID(result["user_id"])])
+                     for result in clients_rs)
diff --git a/gn3/auth/authentication/users.py b/gn3/auth/authentication/users.py
index 8b4f115..0e72ed2 100644
--- a/gn3/auth/authentication/users.py
+++ b/gn3/auth/authentication/users.py
@@ -110,3 +110,19 @@ def set_user_password(
          "ON CONFLICT (user_id) DO UPDATE SET password=:hash"),
         {"user_id": str(user.user_id), "hash": hashed_password})
     return user, hashed_password
+
+def users(conn: db.DbConnection,
+          ids: tuple[UUID, ...] = tuple()) -> tuple[User, ...]:
+    """
+    Fetch all users with the given `ids`. If `ids` is empty, return ALL users.
+    """
+    params = ", ".join(["?"] * len(ids))
+    with db.cursor(conn) as cursor:
+        query = "SELECT * FROM users" + (
+            f" WHERE user_id IN ({params})"
+            if len(ids) > 0 else "")
+        print(query)
+        cursor.execute(query, tuple(str(the_id) for the_id in ids))
+        return tuple(User(UUID(row["user_id"]), row["email"], row["name"])
+                     for row in cursor.fetchall())
+    return tuple()
diff --git a/gn3/auth/authorisation/users/admin/views.py b/gn3/auth/authorisation/users/admin/views.py
index ee76354..11152d2 100644
--- a/gn3/auth/authorisation/users/admin/views.py
+++ b/gn3/auth/authorisation/users/admin/views.py
@@ -1,5 +1,6 @@
 """UI for admin stuff"""
 import uuid
+import json
 import random
 import string
 from functools import partial
@@ -22,7 +23,8 @@ from gn3.auth.db_utils import with_db_connection
 
 from gn3.auth.authentication.oauth2.models.oauth2client import (
     save_client,
-    OAuth2Client)
+    OAuth2Client,
+    oauth2_clients)
 from gn3.auth.authentication.users import (
     User,
     user_by_id,
@@ -44,6 +46,7 @@ def update_expires():
     return None
 
 @admin.route("/dashboard", methods=["GET"])
+@is_admin
 def dashboard():
     """Admin dashboard."""
     return render_template("admin/dashboard.html")
@@ -151,3 +154,18 @@ def register_client():
         "admin/registered-client.html",
         client=client,
         client_secret = raw_client_secret)
+
+def __parse_client__(sqlite3Row) -> dict:
+    """Parse the client details into python datatypes."""
+    return {
+        **dict(sqlite3Row),
+        "client_metadata": json.loads(sqlite3Row["client_metadata"])
+    }
+
+@admin.route("/list-client", methods=["GET"])
+@is_admin
+def list_clients():
+    """List all registered OAuth2 clients."""
+    return render_template(
+        "admin/list-oauth2-clients.html",
+        clients=with_db_connection(oauth2_clients))
diff --git a/gn3/templates/admin/dashboard.html b/gn3/templates/admin/dashboard.html
index 889e652..52fb7d3 100644
--- a/gn3/templates/admin/dashboard.html
+++ b/gn3/templates/admin/dashboard.html
@@ -13,6 +13,10 @@
        title="Register a new OAuth2 client.">Register OAuth2 Client</a>
   </li>
   <li>
+    <a href="{{url_for('oauth2.admin.list_clients')}}"
+       title="List OAuth2 clients.">List OAuth2 Client</a>
+  </li>
+  <li>
     <a href="{{url_for('oauth2.admin.logout')}}"
        title="Log out of the system.">Logout</a>
   </li>
diff --git a/gn3/templates/admin/list-oauth2-clients.html b/gn3/templates/admin/list-oauth2-clients.html
new file mode 100644
index 0000000..f6bbcb2
--- /dev/null
+++ b/gn3/templates/admin/list-oauth2-clients.html
@@ -0,0 +1,45 @@
+{%extends "base.html"%}
+
+{%block title%}Genenetwork3: OAuth2 Clients{%endblock%}
+
+{%block content%}
+{{flash_messages()}}
+
+<h1>Genenetwork3: OAuth2 Clients</h1>
+
+<table>
+  <legend>List of registered OAuth2 clients</legend>
+  <thead>
+    <tr>
+      <th>Client ID</th>
+      <th>Client Name</th>
+      <th>Default Redirect URI</th>
+      <th>Owner</th>
+      <th>Actions</th>
+    </tr>
+  </thead>
+
+  <tbody>
+    {%for client in clients%}
+    <tr>
+      <td>{{client.client_id}}</td>
+      <td>{{client.client_metadata.client_name}}</td>
+      <td>{{client.client_metadata.default_redirect_uri}}</td>
+      <td>{{client.user.name}} ({{client.user.email}})</td>
+      <td>
+	<a href="#{{client.client_id}}"
+	   title"View/Edit client {{client.client_metadata.client_name}}">
+	  View/Edit
+	</a>
+      </td>
+    </tr>
+    {%else%}
+    <tr>
+      <td colspan="4" style="text-align: center;">
+	No registered OAuth2 clients!
+      </td>
+    </tr>
+    {%endfor%}
+  </tbody>
+</table>
+{%endblock%}