aboutsummaryrefslogtreecommitdiff
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%}