diff options
Diffstat (limited to 'gn_auth/auth/authorisation/users')
| -rw-r--r-- | gn_auth/auth/authorisation/users/admin/models.py | 11 | ||||
| -rw-r--r-- | gn_auth/auth/authorisation/users/admin/views.py | 8 | ||||
| -rw-r--r-- | gn_auth/auth/authorisation/users/collections/views.py | 5 | ||||
| -rw-r--r-- | gn_auth/auth/authorisation/users/models.py | 38 | ||||
| -rw-r--r-- | gn_auth/auth/authorisation/users/views.py | 22 |
5 files changed, 65 insertions, 19 deletions
diff --git a/gn_auth/auth/authorisation/users/admin/models.py b/gn_auth/auth/authorisation/users/admin/models.py index 3d68932..0594864 100644 --- a/gn_auth/auth/authorisation/users/admin/models.py +++ b/gn_auth/auth/authorisation/users/admin/models.py @@ -4,6 +4,7 @@ import warnings from gn_auth.auth.db import sqlite3 as db from gn_auth.auth.authentication.users import User from gn_auth.auth.authorisation.roles.models import Role, db_rows_to_roles +from gn_auth.auth.authorisation.resources.system.models import system_resource def sysadmin_role(conn: db.DbConnection) -> Role: @@ -28,14 +29,14 @@ def grant_sysadmin_role(cursor: db.DbCursor, user: User) -> User: cursor.execute( "SELECT * FROM roles WHERE role_name='system-administrator'") admin_role = cursor.fetchone() - cursor.execute("SELECT resources.resource_id FROM resources") - cursor.executemany( + sysresource = system_resource(cursor) + cursor.execute( "INSERT INTO user_roles VALUES (:user_id, :role_id, :resource_id)", - tuple({ + { "user_id": str(user.user_id), "role_id": admin_role["role_id"], - "resource_id": resource_id - } for resource_id in cursor.fetchall())) + "resource_id": str(sysresource.resource_id) + }) return user diff --git a/gn_auth/auth/authorisation/users/admin/views.py b/gn_auth/auth/authorisation/users/admin/views.py index 9bc1c36..62eccfd 100644 --- a/gn_auth/auth/authorisation/users/admin/views.py +++ b/gn_auth/auth/authorisation/users/admin/views.py @@ -1,6 +1,5 @@ """UI for admin stuff""" import uuid -import json import random import string from typing import Optional @@ -240,13 +239,6 @@ def register_client(): client_secret = raw_client_secret) -def __parse_client__(sqlite3_row) -> dict: - """Parse the client details into python datatypes.""" - return { - **dict(sqlite3_row), - "client_metadata": json.loads(sqlite3_row["client_metadata"]) - } - @admin.route("/list-client", methods=["GET"]) @is_admin def list_clients(): diff --git a/gn_auth/auth/authorisation/users/collections/views.py b/gn_auth/auth/authorisation/users/collections/views.py index f619c3d..5ed2c23 100644 --- a/gn_auth/auth/authorisation/users/collections/views.py +++ b/gn_auth/auth/authorisation/users/collections/views.py @@ -1,4 +1,5 @@ """Views regarding user collections.""" +import logging from uuid import UUID from redis import Redis @@ -25,8 +26,10 @@ from .models import ( REDIS_COLLECTIONS_KEY, delete_collections as _delete_collections) +logger = logging.getLogger(__name__) collections = Blueprint("collections", __name__) + @collections.route("/list") @require_oauth("profile user") def list_user_collections() -> Response: @@ -44,7 +47,7 @@ def list_anonymous_collections(anon_id: UUID) -> Response: def __list__(conn: db.DbConnection) -> tuple: try: _user = user_by_id(conn, anon_id) - current_app.logger.warning( + logger.warning( "Fetch collections for authenticated user using the " "`list_user_collections()` endpoint.") return tuple() diff --git a/gn_auth/auth/authorisation/users/models.py b/gn_auth/auth/authorisation/users/models.py index d30bfd0..ab7a980 100644 --- a/gn_auth/auth/authorisation/users/models.py +++ b/gn_auth/auth/authorisation/users/models.py @@ -1,5 +1,6 @@ """Functions for acting on users.""" import uuid +import warnings from functools import reduce from datetime import datetime, timedelta @@ -128,3 +129,40 @@ def user_resource_roles(conn: db.DbConnection, user: User) -> dict[uuid.UUID, tu (str(user.user_id),)) return __build_resource_roles__( (dict(row) for row in cursor.fetchall())) + + +def delete_users_by_id( + conn: db.DbConnection, + user_ids: tuple[uuid.UUID, ...] +) -> int: + """Delete users unconditionally by ID, removing all dependent data. + + Unlike the HTTP endpoint, this bypasses all policy checks — users are + deleted regardless of their roles or group memberships. Returns the + number of users removed from the users table. + """ + warnings.warn( + (f"Running dangerous function `{__name__}.delete_users_by_id`. " + "Do ensure that is what you actually want."), + category=RuntimeWarning) + if not user_ids: + return 0 + _ids = tuple(str(uid) for uid in user_ids) + _paramstr = ", ".join(["?"] * len(_ids)) + _dependent_tables = ( + ("authorisation_code", "user_id"), + ("forgot_password_tokens", "user_id"), + ("group_join_requests", "requester_id"), + ("jwt_refresh_tokens", "user_id"), + ("oauth2_tokens", "user_id"), + ("user_credentials", "user_id"), + ("user_roles", "user_id"), + ("user_verification_codes", "user_id"), + ) + with db.cursor(conn) as cursor: + for table, col in _dependent_tables: + cursor.execute( + f"DELETE FROM {table} WHERE {col} IN ({_paramstr})", _ids) + cursor.execute( + f"DELETE FROM users WHERE user_id IN ({_paramstr})", _ids) + return cursor.rowcount diff --git a/gn_auth/auth/authorisation/users/views.py b/gn_auth/auth/authorisation/users/views.py index c248ac3..a706067 100644 --- a/gn_auth/auth/authorisation/users/views.py +++ b/gn_auth/auth/authorisation/users/views.py @@ -1,5 +1,6 @@ """User authorisation endpoints.""" import uuid +import logging import sqlite3 import secrets import traceback @@ -57,6 +58,8 @@ from .models import list_users from .masquerade.views import masq from .collections.views import collections +logger = logging.getLogger(__name__) + users = Blueprint("users", __name__) users.register_blueprint(masq, url_prefix="/masquerade") users.register_blueprint(collections, url_prefix="/collections") @@ -235,11 +238,11 @@ def register_user() -> Response: redirect_uri=form["redirect_uri"]) return jsonify(asdict(user)) except sqlite3.IntegrityError as sq3ie: - current_app.logger.error(traceback.format_exc()) + logger.error(traceback.format_exc()) raise UserRegistrationError( "A user with that email already exists") from sq3ie except EmailNotValidError as enve: - current_app.logger.error(traceback.format_exc()) + logger.error(traceback.format_exc()) raise(UserRegistrationError(f"Email Error: {str(enve)}")) from enve raise Exception(# pylint: disable=[broad-exception-raised] @@ -317,12 +320,21 @@ def user_group() -> Response: @require_oauth("profile resource") def user_resources() -> Response: """Retrieve the resources a user has access to.""" + _request_params = request_json() with require_oauth.acquire("profile resource") as the_token: db_uri = current_app.config["AUTH_DB"] with db.connection(db_uri) as conn: - return jsonify([ - asdict(resource) for resource in - _user_resources(conn, the_token.user)]) + _resources, _total_records = _user_resources( + conn, + the_token.user, + start_at=int(_request_params.get("start", 0)), + count=int(_request_params.get("length", 0)), + text_filter=_request_params.get("text_filter", "")) + return jsonify({ + "resources": [asdict(resource) for resource in _resources], + "total-records": _total_records, + "filtered-records": len(_resources) + }) @users.route("group/join-request", methods=["GET"]) @require_oauth("profile group") |
