From 5b4380e61e003a9b4450221d0f499db069098c19 Mon Sep 17 00:00:00 2001 From: Claude Sonnet 4.6 Date: Mon, 31 Aug 2026 10:00:00 +0000 Subject: Move create_new_user to gn_auth/auth/system/admin/users.py Move the user-creation endpoint from gn_auth/auth/authorisation/users/views.py into the new sysadmin package at gn_auth/auth/system/admin/users.py. New URL: POST /auth/system/administration/users/create Old URL: POST /auth/user/create now issues a 308 Permanent Redirect. The admin blueprint is updated to register the new users blueprint at /users. Reviewed-By: Frederick M. Muriithi --- gn_auth/auth/authorisation/users/views.py | 59 ++++----------------------- gn_auth/auth/system/admin/users.py | 67 +++++++++++++++++++++++++++++++ gn_auth/auth/system/admin/views.py | 2 + 3 files changed, 77 insertions(+), 51 deletions(-) create mode 100644 gn_auth/auth/system/admin/users.py (limited to 'gn_auth') diff --git a/gn_auth/auth/authorisation/users/views.py b/gn_auth/auth/authorisation/users/views.py index e454a80..f0b1b0c 100644 --- a/gn_auth/auth/authorisation/users/views.py +++ b/gn_auth/auth/authorisation/users/views.py @@ -10,6 +10,9 @@ from functools import reduce, partial from typing import Any, Union, Sequence from datetime import datetime, timedelta from email.headerregistry import Address + + +import werkzeug.wrappers.response from email_validator import validate_email, EmailNotValidError from flask import ( flash, @@ -17,15 +20,14 @@ from flask import ( jsonify, url_for, redirect, - Response, Blueprint, current_app, make_response, - render_template) + render_template, + Response as _Response) from gn_libs import sqlite3 as db from gn_libs.sqlite3 import with_db_connection -from gn_libs.privileges.system import can_create_or_delete_user from gn_libs.privileges.resources import can_assign_role from gn_auth.smtp import send_message, build_email_message @@ -33,9 +35,7 @@ from gn_auth.smtp import send_message, build_email_message from gn_auth.auth.requests import request_json -from gn_auth.auth.authorisation.resources.system.models import ( - system_resource, - user_roles_on_system) +from gn_auth.auth.authorisation.resources.system.models import system_resource from gn_auth.auth.authorisation.resources.checks import authorised_for2 from gn_auth.auth.authorisation.resources.models import ( @@ -63,7 +63,6 @@ from gn_auth.auth.authentication.oauth2.models.oauth2token import ( token_by_access_token) from .models import list_users -from .admin.models import create_verified_user from .masquerade.views import masq from .collections.views import collections @@ -73,6 +72,8 @@ users = Blueprint("users", __name__) users.register_blueprint(masq, url_prefix="/masquerade") users.register_blueprint(collections, url_prefix="/collections") +Response = Union[_Response, werkzeug.wrappers.response.Response] + @users.route("/", methods=["GET"]) @require_oauth("profile") def user_details() -> Response: @@ -761,50 +762,6 @@ def delete_users(): }), 400 -@users.route("/create", methods=["POST"]) -def create_new_user() -> Response: - """Create a new User.""" - with (require_oauth.acquire("profile") as token, - db.connection(current_app.config["AUTH_DB"]) as conn): - u_roles = user_roles_on_system(conn, token.user) - if not can_create_or_delete_user(tuple( - priv.privilege_id for role in u_roles - for priv in role.privileges)): - raise ForbiddenAccess( - "You need the `system:user:create-user` privilege.") - form = request_json() - errors = {} - try: - email = validate_email( - form.get("email", "").strip(), check_deliverability=False) - except EmailNotValidError as enve: - errors["email"] = ( - f"E-Mail error: {'==>'.join(str(arg) for arg in enve.args)}") - - try: - username = validate_username(form.get("name", "").strip()) - except UsernameError as uerr: - errors["name"] = str(uerr.args[0]) - - try: - passwd = validate_password( - form.get("password", "").strip(), - form.get("password", "").strip()) - except PasswordError as perr: - errors["password"] = str(perr.args[0]) - - if len(tuple(errors.keys())) > 0: - raise UserRegistrationError(tuple(errors.values())) - - user = create_verified_user(conn, email.normalized, username, passwd) - - return make_response(jsonify({ - "user_id": str(user.user_id), - "email": user.email, - "name": user.name - }), 201) - - @users.route("//roles/assign", methods=["POST"]) def assign_user_role(user_id: uuid.UUID) -> Response: """Assign a role to a user on a given resource.""" diff --git a/gn_auth/auth/system/admin/users.py b/gn_auth/auth/system/admin/users.py new file mode 100644 index 0000000..f26518b --- /dev/null +++ b/gn_auth/auth/system/admin/users.py @@ -0,0 +1,67 @@ +"""Administrative endpoints for user management.""" +from flask import jsonify, Response, Blueprint, make_response, current_app as app + +from gn_libs import sqlite3 as db + +from email_validator import validate_email, EmailNotValidError + +from gn_libs.privileges.system import can_create_or_delete_user + +from gn_auth.auth.errors import ( + PasswordError, + UsernameError, + ForbiddenAccess, + UserRegistrationError) +from gn_auth.auth.requests import request_json +from gn_auth.auth.authentication.oauth2.resource_server import require_oauth +from gn_auth.auth.authorisation.resources.system.models import user_roles_on_system +from gn_auth.auth.authorisation.users.admin.models import create_verified_user +from gn_auth.auth.authorisation.users.views import ( + validate_password, + validate_username) + +users = Blueprint("users", __name__) + + +@users.route("/create", methods=["POST"]) +def create_user() -> Response: + """Create a new user. Requires system:user:create-user privilege.""" + with (require_oauth.acquire("profile") as token, + db.connection(app.config["AUTH_DB"]) as conn): + u_roles = user_roles_on_system(conn, token.user) + if not can_create_or_delete_user(tuple( + priv.privilege_id for role in u_roles + for priv in role.privileges)): + raise ForbiddenAccess( + "You need the `system:user:create-user` privilege.") + form = request_json() + errors = {} + try: + email = validate_email( + form.get("email", "").strip(), check_deliverability=False) + except EmailNotValidError as enve: + errors["email"] = ( + f"E-Mail error: {'==>'.join(str(arg) for arg in enve.args)}") + + try: + username = validate_username(form.get("name", "").strip()) + except UsernameError as uerr: + errors["name"] = str(uerr.args[0]) + + try: + passwd = validate_password( + form.get("password", "").strip(), + form.get("password", "").strip()) + except PasswordError as perr: + errors["password"] = str(perr.args[0]) + + if len(tuple(errors.keys())) > 0: + raise UserRegistrationError(tuple(errors.values())) + + user = create_verified_user(conn, email.normalized, username, passwd) + + return make_response(jsonify({ + "user_id": str(user.user_id), + "email": user.email, + "name": user.name + }), 201) diff --git a/gn_auth/auth/system/admin/views.py b/gn_auth/auth/system/admin/views.py index 1ea8acd..f1f76f9 100644 --- a/gn_auth/auth/system/admin/views.py +++ b/gn_auth/auth/system/admin/views.py @@ -3,6 +3,8 @@ from flask import Blueprint from .resources import resources +from .users import users admin = Blueprint("admin", __name__) admin.register_blueprint(resources, url_prefix="/resources") +admin.register_blueprint(users, url_prefix="/users") -- cgit 1.4.1