about summary refs log tree commit diff
diff options
context:
space:
mode:
authorClaude Sonnet 4.62026-06-03 00:00:00 +0000
committerFrederick Muriuki Muriithi2026-06-03 14:40:23 -0500
commit7c44220d2a2683c17369dcf4d6b24d8dd2df62ab (patch)
tree5fab49155b43973361fd69e60ec965ebacf90c89
parent6a0d09960be96619d26d080d8d4c420eb48adb25 (diff)
downloadgn-auth-7c44220d2a2683c17369dcf4d6b24d8dd2df62ab.tar.gz
wsgi: add __create_one_client__ helper and create-oauth2-client
Add a __create_one_client__ helper that constructs an OAuth2Client,
hashes the secret, persists it via save_client, and returns a credential
record dict. Add create-oauth2-client CLI command that exposes all client
parameters explicitly. Preparation for reuse by create-test-oauth2-client.
-rw-r--r--gn_auth/wsgi.py92
1 files changed, 91 insertions, 1 deletions
diff --git a/gn_auth/wsgi.py b/gn_auth/wsgi.py
index a6d8b00..0feb69a 100644
--- a/gn_auth/wsgi.py
+++ b/gn_auth/wsgi.py
@@ -7,7 +7,7 @@ import uuid
 import json
 from math import ceil
 from pathlib import Path
-from datetime import datetime
+from datetime import datetime, timezone
 
 import click
 from yoyo import get_backend, read_migrations
@@ -23,6 +23,9 @@ from gn_auth.auth.authorisation.roles.models import assign_default_roles
 from gn_auth.auth.authorisation.users.admin.models import (
     make_sys_admin, grant_sysadmin_role)
 from gn_auth.auth.authorisation.users.models import delete_users_by_id
+from gn_auth.auth.authentication.oauth2.models.oauth2client import (
+    OAuth2Client, save_client, delete_client,
+    client as oauth2_client_by_id)
 from gn_auth.scripts import register_sys_admin as rsysadm# type: ignore[import]
 
 
@@ -297,6 +300,93 @@ def create_test_users(session_timestamp, user_specs, output_path):
         {"session_timestamp": session_timestamp, "users": records},
         output_path)
 
+
+_DEFAULT_GRANT_TYPES_ = (
+    "password",
+    "authorization_code",
+    "refresh_token",
+    "urn:ietf:params:oauth:grant-type:jwt-bearer",
+)
+
+_DEFAULT_SCOPES_ = (
+    "profile", "group", "role", "resource",
+    "register-client", "user", "masquerade",
+    "migrate-data", "introspect",
+)
+
+
+def __create_one_client__(
+        conn,
+        client_name: str,
+        owner_user,
+        redirect_uris: tuple,
+        scopes: tuple = _DEFAULT_SCOPES_,
+        grant_types: tuple = _DEFAULT_GRANT_TYPES_,
+        jwks_uri: str = "",
+) -> dict:
+    """Create a single OAuth2 client and return its credential record."""
+    raw_secret = secrets.token_urlsafe(32)
+    the_client = OAuth2Client(
+        client_id=uuid.uuid4(),
+        client_secret=hash_password(raw_secret),
+        client_id_issued_at=datetime.now(tz=timezone.utc),
+        client_secret_expires_at=datetime.fromtimestamp(0),
+        client_metadata={
+            "client_name": client_name,
+            "token_endpoint_auth_method": [
+                "client_secret_post", "client_secret_basic"],
+            "client_type": "confidential",
+            "grant_types": list(grant_types),
+            "default_redirect_uri": redirect_uris[0] if redirect_uris else "",
+            "redirect_uris": list(redirect_uris),
+            "response_type": ["code", "token"],
+            "scope": list(scopes),
+            "public-jwks-uri": jwks_uri,
+        },
+        user=owner_user)
+    save_client(conn, the_client)
+    return {
+        "client_id": str(the_client.client_id),
+        "client_secret": raw_secret,
+        "client_name": client_name,
+    }
+
+
+@app.cli.command()
+@click.option("--name", "client_name", required=True,
+              help="Human-readable name for the OAuth2 client")
+@click.option("--owner-id", required=True, type=click.UUID,
+              help="UUID of the user who owns this client")
+@click.option("--redirect-uri", "redirect_uris", multiple=True,
+              help="Allowed redirect URI (repeatable)")
+@click.option("--scope", "scopes", multiple=True,
+              default=_DEFAULT_SCOPES_, show_default=False,
+              help="OAuth2 scope (repeatable; defaults to full scope set)")
+@click.option("--grant-type", "grant_types", multiple=True,
+              default=_DEFAULT_GRANT_TYPES_, show_default=False,
+              help="Grant type (repeatable; defaults to all standard types)")
+@click.option("--jwks-uri", default="",
+              help="URI to the client's public JWKS (optional)")
+@click.option("--output", "output_path", type=click.Path(), default=None,
+              help="Write credentials as JSON to this file (default: stdout)")
+def create_oauth2_client(client_name, owner_id, redirect_uris, scopes,
+                         grant_types, jwks_uri, output_path):
+    """Create an OAuth2 client with specified parameters.
+
+    Scopes and grant types default to the full standard set if not provided.
+    """
+    with db.connection(app.config["AUTH_DB"]) as conn:
+        try:
+            owner = user_by_id(conn, owner_id)
+        except NotFoundError:
+            print(f"No user found with ID {owner_id}", file=sys.stderr)
+            sys.exit(1)
+        record = __create_one_client__(
+            conn, client_name, owner, redirect_uris, scopes, grant_types,
+            jwks_uri)
+
+    __write_output__({"client": record}, output_path)
+
 ##### END: CLI Commands #####
 
 if __name__ == '__main__':