From b317cd7e3684bf7c034ad0a1bb208d55fb87b164 Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Wed, 24 Apr 2024 05:17:00 +0300 Subject: Setup logging: Use gunicorn's loggers where appropriate If the application is run under GUnicorn, then use the gunicorn loggers otherwise, use some default development loggers. This can be extended to support other WSGI servers down the line if ever necessary. --- gn_auth/__init__.py | 18 ++---- main.py | 125 -------------------------------------- scripts/gn_auth_wsgi.py | 158 ++++++++++++++++++++++++++++++++++++++++++++++++ wsgi.py | 7 --- 4 files changed, 164 insertions(+), 144 deletions(-) delete mode 100644 main.py create mode 100644 scripts/gn_auth_wsgi.py delete mode 100644 wsgi.py diff --git a/gn_auth/__init__.py b/gn_auth/__init__.py index 881ae1e..895f593 100644 --- a/gn_auth/__init__.py +++ b/gn_auth/__init__.py @@ -1,9 +1,8 @@ """Application initialisation module.""" import os import sys -import logging from pathlib import Path -from typing import Optional +from typing import Optional, Callable from flask import Flask from flask_cors import CORS @@ -38,14 +37,6 @@ def override_settings_with_envvars( for setting in (key for key in app.config if key not in ignore): app.config[setting] = os.environ.get(setting) or app.config[setting] -def setup_logging_handlers(app: Flask) -> None: - """Setup the loggging handlers.""" - stderr_handler = logging.StreamHandler(stream=sys.stderr) - app.logger.addHandler(stderr_handler) - - root_logger = logging.getLogger() - root_logger.addHandler(stderr_handler) - root_logger.setLevel(app.config["LOGLEVEL"]) def load_secrets_conf(app: Flask) -> None: """Load the secrets file.""" @@ -78,7 +69,10 @@ def parse_ssl_keys(app): app.config["SSL_PRIVATE_KEY"] = __parse_key__( Path(app.config["SSL_PRIVATE_KEY"])) -def create_app(config: Optional[dict] = None) -> Flask: +def create_app( + config: Optional[dict] = None, + setup_logging: Callable[[Flask], None] = lambda appl: None +) -> Flask: """Create and return a new flask application.""" app = Flask(__name__) @@ -96,9 +90,9 @@ def create_app(config: Optional[dict] = None) -> Flask: parse_ssl_keys(app) # ====== END: Setup configuration ====== + setup_logging(app) check_mandatory_settings(app) - setup_logging_handlers(app) setup_oauth2_server(app) CORS( diff --git a/main.py b/main.py deleted file mode 100644 index ae4b4a1..0000000 --- a/main.py +++ /dev/null @@ -1,125 +0,0 @@ -"""Main entry point for project""" -import sys -import uuid -import json -from math import ceil -from pathlib import Path -from datetime import datetime - - -import click -from yoyo import get_backend, read_migrations - -from gn_auth import migrations -from gn_auth import create_app - -from gn_auth.auth.db import sqlite3 as db -from gn_auth.auth.authentication.users import user_by_id, hash_password - -from gn_auth.auth.authorisation.errors import NotFoundError -from gn_auth.auth.authorisation.users.admin.models import make_sys_admin - -from scripts import register_sys_admin as rsysadm# type: ignore[import] -from scripts import migrate_existing_data as med# type: ignore[import] - -app = create_app() - -##### BEGIN: CLI Commands ##### - -@app.cli.command() -def apply_migrations(): - """Apply the dabasase migrations.""" - migrations.apply_migrations( - get_backend(f'sqlite:///{app.config["AUTH_DB"]}'), - read_migrations(app.config["AUTH_MIGRATIONS"])) - -def __init_dev_users__(): - """Initialise dev users. Get's used in more than one place""" - dev_users_query = "INSERT INTO users VALUES (:user_id, :email, :name)" - dev_users_passwd = "INSERT INTO user_credentials VALUES (:user_id, :hash)" - dev_users = ({ - "user_id": "0ad1917c-57da-46dc-b79e-c81c91e5b928", - "email": "test@development.user", - "name": "Test Development User", - "password": "testpasswd"},) - - with db.connection(app.config["AUTH_DB"]) as conn, db.cursor(conn) as cursor: - cursor.executemany(dev_users_query, dev_users) - cursor.executemany(dev_users_passwd, ( - {**usr, "hash": hash_password(usr["password"])} - for usr in dev_users)) - -@app.cli.command() -def init_dev_users(): - """ - Initialise development users for OAuth2 sessions. - - **NOTE**: You really should not run this in production/staging - """ - __init_dev_users__() - -@app.cli.command() -def init_dev_clients(): - """ - Initialise a development client for OAuth2 sessions. - - **NOTE**: You really should not run this in production/staging - """ - __init_dev_users__() - dev_clients_query = ( - "INSERT INTO oauth2_clients VALUES (" - ":client_id, :client_secret, :client_id_issued_at, " - ":client_secret_expires_at, :client_metadata, :user_id" - ")") - dev_clients = ({ - "client_id": "0bbfca82-d73f-4bd4-a140-5ae7abb4a64d", - "client_secret": "yadabadaboo", - "client_id_issued_at": ceil(datetime.now().timestamp()), - "client_secret_expires_at": 0, - "client_metadata": json.dumps({ - "client_name": "GN2 Dev Server", - "token_endpoint_auth_method": [ - "client_secret_post", "client_secret_basic"], - "client_type": "confidential", - "grant_types": ["password", "authorization_code", "refresh_token"], - "default_redirect_uri": "http://localhost:5033/oauth2/code", - "redirect_uris": ["http://localhost:5033/oauth2/code", - "http://localhost:5033/oauth2/token"], - "response_type": ["code", "token"], - "scope": ["profile", "group", "role", "resource", "register-client", - "user", "masquerade", "migrate-data", "introspect"] - }), - "user_id": "0ad1917c-57da-46dc-b79e-c81c91e5b928"},) - - with db.connection(app.config["AUTH_DB"]) as conn, db.cursor(conn) as cursor: - cursor.executemany(dev_clients_query, dev_clients) - - -@app.cli.command() -@click.argument("user_id", type=click.UUID) -def assign_system_admin(user_id: uuid.UUID): - """Assign user with ID `user_id` administrator role.""" - try: - dburi = app.config["AUTH_DB"] - with db.connection(dburi) as conn, db.cursor(conn) as cursor: - make_sys_admin(cursor, user_by_id(conn, user_id)) - return 0 - except NotFoundError as nfe: - print(nfe, file=sys.stderr) - sys.exit(1) - -@app.cli.command() -def make_data_public(): - """Make existing data that is not assigned to any group publicly visible.""" - med.entry(app.config["AUTH_DB"], app.config["SQL_URI"]) - -@app.cli.command() -def register_admin(): - """Register the administrator.""" - rsysadm.register_admin(Path(app.config["AUTH_DB"])) - -##### END: CLI Commands ##### - -if __name__ == '__main__': - print("Starting app...") - app.run() diff --git a/scripts/gn_auth_wsgi.py b/scripts/gn_auth_wsgi.py new file mode 100644 index 0000000..1de0b56 --- /dev/null +++ b/scripts/gn_auth_wsgi.py @@ -0,0 +1,158 @@ +"""Main entry point for project""" +import os +import sys +import uuid +import json +import logging +from math import ceil +from pathlib import Path +from typing import Callable +from datetime import datetime + +import click +from flask import Flask +from yoyo import get_backend, read_migrations + +from gn_auth import migrations +from gn_auth import create_app + +from gn_auth.auth.db import sqlite3 as db +from gn_auth.auth.authentication.users import user_by_id, hash_password + +from gn_auth.auth.authorisation.errors import NotFoundError +from gn_auth.auth.authorisation.users.admin.models import make_sys_admin + +from scripts import register_sys_admin as rsysadm# type: ignore[import] +from scripts import migrate_existing_data as med# type: ignore[import] + + +def dev_loggers(appl: Flask) -> None: + """Setup the logging handlers.""" + stderr_handler = logging.StreamHandler(stream=sys.stderr) + appl.logger.addHandler(stderr_handler) + + root_logger = logging.getLogger() + root_logger.addHandler(stderr_handler) + root_logger.setLevel(appl.config["LOGLEVEL"]) + + +def gunicorn_loggers(appl: Flask) -> None: + """Use gunicorn logging handlers for the application.""" + logger = logging.getLogger("gunicorn.error") + appl.logger.handlers = logger.handlers + appl.logger.setLevel(logger.level) + + +def setup_loggers() -> Callable[[Flask], None]: + """ + Setup the loggers according to the WSGI server used to run the application. + """ + # https://datatracker.ietf.org/doc/html/draft-coar-cgi-v11-03#section-4.1.17 + # https://wsgi.readthedocs.io/en/latest/proposals-2.0.html#making-some-keys-required + # https://peps.python.org/pep-3333/#id4 + software, *_version_and_comments = os.environ.get( + "SERVER_SOFTWARE", "").split('/') + return gunicorn_loggers if bool(software) else dev_loggers + +# app = create_app() +app = create_app(setup_logging=setup_loggers()) + +##### BEGIN: CLI Commands ##### + +@app.cli.command() +def apply_migrations(): + """Apply the dabasase migrations.""" + migrations.apply_migrations( + get_backend(f'sqlite:///{app.config["AUTH_DB"]}'), + read_migrations(app.config["AUTH_MIGRATIONS"])) + +def __init_dev_users__(): + """Initialise dev users. Get's used in more than one place""" + dev_users_query = "INSERT INTO users VALUES (:user_id, :email, :name)" + dev_users_passwd = "INSERT INTO user_credentials VALUES (:user_id, :hash)" + dev_users = ({ + "user_id": "0ad1917c-57da-46dc-b79e-c81c91e5b928", + "email": "test@development.user", + "name": "Test Development User", + "password": "testpasswd"},) + + with db.connection(app.config["AUTH_DB"]) as conn, db.cursor(conn) as cursor: + cursor.executemany(dev_users_query, dev_users) + cursor.executemany(dev_users_passwd, ( + {**usr, "hash": hash_password(usr["password"])} + for usr in dev_users)) + +@app.cli.command() +def init_dev_users(): + """ + Initialise development users for OAuth2 sessions. + + **NOTE**: You really should not run this in production/staging + """ + __init_dev_users__() + +@app.cli.command() +def init_dev_clients(): + """ + Initialise a development client for OAuth2 sessions. + + **NOTE**: You really should not run this in production/staging + """ + __init_dev_users__() + dev_clients_query = ( + "INSERT INTO oauth2_clients VALUES (" + ":client_id, :client_secret, :client_id_issued_at, " + ":client_secret_expires_at, :client_metadata, :user_id" + ")") + dev_clients = ({ + "client_id": "0bbfca82-d73f-4bd4-a140-5ae7abb4a64d", + "client_secret": "yadabadaboo", + "client_id_issued_at": ceil(datetime.now().timestamp()), + "client_secret_expires_at": 0, + "client_metadata": json.dumps({ + "client_name": "GN2 Dev Server", + "token_endpoint_auth_method": [ + "client_secret_post", "client_secret_basic"], + "client_type": "confidential", + "grant_types": ["password", "authorization_code", "refresh_token"], + "default_redirect_uri": "http://localhost:5033/oauth2/code", + "redirect_uris": ["http://localhost:5033/oauth2/code", + "http://localhost:5033/oauth2/token"], + "response_type": ["code", "token"], + "scope": ["profile", "group", "role", "resource", "register-client", + "user", "masquerade", "migrate-data", "introspect"] + }), + "user_id": "0ad1917c-57da-46dc-b79e-c81c91e5b928"},) + + with db.connection(app.config["AUTH_DB"]) as conn, db.cursor(conn) as cursor: + cursor.executemany(dev_clients_query, dev_clients) + + +@app.cli.command() +@click.argument("user_id", type=click.UUID) +def assign_system_admin(user_id: uuid.UUID): + """Assign user with ID `user_id` administrator role.""" + try: + dburi = app.config["AUTH_DB"] + with db.connection(dburi) as conn, db.cursor(conn) as cursor: + make_sys_admin(cursor, user_by_id(conn, user_id)) + return 0 + except NotFoundError as nfe: + print(nfe, file=sys.stderr) + sys.exit(1) + +@app.cli.command() +def make_data_public(): + """Make existing data that is not assigned to any group publicly visible.""" + med.entry(app.config["AUTH_DB"], app.config["SQL_URI"]) + +@app.cli.command() +def register_admin(): + """Register the administrator.""" + rsysadm.register_admin(Path(app.config["AUTH_DB"])) + +##### END: CLI Commands ##### + +if __name__ == '__main__': + print("Starting app...") + app.run() diff --git a/wsgi.py b/wsgi.py deleted file mode 100644 index c44cb8a..0000000 --- a/wsgi.py +++ /dev/null @@ -1,7 +0,0 @@ -"""WSGI application entry-point.""" -from gn_auth import create_app - -app = create_app() - -if __name__ == "__main__": - app.run() -- cgit v1.2.3