From 6acce16939fcb32dd09ba243729d7b582620e431 Mon Sep 17 00:00:00 2001 From: Daniel Madden Date: Sat, 25 Oct 2025 12:09:55 -0700 Subject: [PATCH] Simplify UI by removing authentication dependencies --- reconscript/templates/layout.html | 7 +- reconscript/ui.py | 168 +++++++----------------------- 2 files changed, 40 insertions(+), 135 deletions(-) diff --git a/reconscript/templates/layout.html b/reconscript/templates/layout.html index a5adbba..2335cf5 100644 --- a/reconscript/templates/layout.html +++ b/reconscript/templates/layout.html @@ -47,12 +47,7 @@ ReconScript
diff --git a/reconscript/ui.py b/reconscript/ui.py index 10bc9e8..0aa2c6e 100644 --- a/reconscript/ui.py +++ b/reconscript/ui.py @@ -1,4 +1,4 @@ -"""Flask UI for ReconScript with consent enforcement and RBAC.""" +"""Flask UI for ReconScript without authentication or RBAC.""" from __future__ import annotations @@ -6,7 +6,6 @@ import logging import os import uuid -from functools import wraps from pathlib import Path from typing import Optional @@ -14,7 +13,6 @@ Flask, abort, flash, - current_app, redirect, render_template, Response, @@ -22,14 +20,6 @@ send_from_directory, url_for, ) -from flask_login import ( - LoginManager, - UserMixin, - current_user, - login_required, - login_user, - logout_user, -) from .consent import ConsentError, load_manifest, validate_manifest from .core import ReconError, run_recon @@ -42,13 +32,6 @@ DEV_KEYS_DIR = Path(__file__).resolve().parents[1] / "keys" -class StaticUser(UserMixin): - def __init__(self, username: str, role: str) -> None: - self.id = username - self.role = role - - -<<<<<<< HEAD def _allow_dev_secrets() -> bool: return os.environ.get("ALLOW_DEV_SECRETS", "false").lower() == "true" @@ -56,22 +39,14 @@ def _allow_dev_secrets() -> bool: def _enforce_secret_path(path: Path, *, env_var: str) -> Path: resolved = path.expanduser().resolve() if not resolved.exists(): - raise RuntimeError(f"{env_var} must point to an existing file (got {resolved}).") -======= -def _load_secret_key() -> bytes: - secret_path = Path( - os.environ.get("FLASK_SECRET_KEY_FILE", "keys/dev_flask_secret.key") - ) ->>>>>>> 74be2f0 (style: fix reporters.py syntax and apply Black formatting) - try: - if DEV_KEYS_DIR in resolved.parents and not _allow_dev_secrets(): - raise RuntimeError( - f"{env_var} refers to a developer sample key. Provide deployment-specific secrets or set ALLOW_DEV_SECRETS=true for local testing." - ) - except RuntimeError: - raise - except Exception: - pass + raise RuntimeError( + f"{env_var} must point to an existing file (got {resolved})." + ) + if DEV_KEYS_DIR in resolved.parents and not _allow_dev_secrets(): + raise RuntimeError( + f"{env_var} refers to a developer sample key. Provide deployment-specific " + "secrets or set ALLOW_DEV_SECRETS=true for local testing." + ) return resolved @@ -85,46 +60,12 @@ def _load_secret_key() -> bytes: try: secret = secret_path.read_bytes().strip() except OSError as exc: -<<<<<<< HEAD - raise RuntimeError(f"Unable to read Flask secret key from {secret_path}: {exc}") from exc - if not secret: - raise RuntimeError(f"Secret key file {secret_path} is empty.") - return secret -======= raise RuntimeError( f"Unable to read Flask secret key from {secret_path}: {exc}" ) from exc ->>>>>>> 74be2f0 (style: fix reporters.py syntax and apply Black formatting) - - -def _rbac_required(role: str): - def decorator(func): - @wraps(func) - def wrapper(*args, **kwargs): - if not current_user.is_authenticated: - return redirect(url_for("login")) - if ( - current_app.config.get("RBAC_ENABLED") - and getattr(current_user, "role", None) != role - ): - abort(403) - return func(*args, **kwargs) - - return wrapper - - return decorator - - -def _load_user_credentials() -> tuple[str, str]: - username = os.environ.get("ADMIN_USER", "").strip() - password = os.environ.get("ADMIN_PASSWORD", "").strip() - if not username or not password: - raise RuntimeError("ADMIN_USER and ADMIN_PASSWORD must be set for the ReconScript UI.") - if not _allow_dev_secrets() and (username == "admin" or password == "changeme"): - raise RuntimeError("Default credentials are not permitted. Set strong ADMIN_USER and ADMIN_PASSWORD values.") - if len(password) < 12 and not _allow_dev_secrets(): - raise RuntimeError("ADMIN_PASSWORD must be at least 12 characters long.") - return username, password + if not secret: + raise RuntimeError(f"Secret key file {secret_path} is empty.") + return secret def _store_upload(file_storage) -> Path: @@ -139,38 +80,15 @@ def _store_upload(file_storage) -> Path: def create_app() -> Flask: configure_logging() - public_ui = os.environ.get("ENABLE_PUBLIC_UI", "false").lower() == "true" - rbac_enabled = os.environ.get("ENABLE_RBAC", "false").lower() == "true" - if public_ui and not rbac_enabled: - raise RuntimeError( - "ENABLE_PUBLIC_UI=true requires ENABLE_RBAC=true to protect access." - ) - app = Flask(__name__) - app.config["RBAC_ENABLED"] = rbac_enabled - app.config["PUBLIC_UI"] = public_ui + app.config["PUBLIC_UI"] = os.environ.get("ENABLE_PUBLIC_UI", "false").lower() == "true" app.config["UPLOAD_FOLDER"] = str(ensure_results_dir() / "uploads") app.secret_key = _load_secret_key() - login_manager = LoginManager(app) - login_manager.login_view = "login" - - username, password = _load_user_credentials() - user = StaticUser(username, "admin") - - @login_manager.user_loader - def load_user( - user_id: str, - ) -> Optional[StaticUser]: # pragma: no cover - simple lookup - if user_id == user.id: - return user - return None - @app.context_processor def inject_globals(): return { "public_ui": app.config["PUBLIC_UI"], - "rbac_enabled": app.config["RBAC_ENABLED"], } @app.route("/healthz") @@ -184,26 +102,6 @@ def metrics() -> Response: return Response("", status=204) return Response(payload, mimetype=content_type) - @app.route("/login", methods=["GET", "POST"]) - def login(): - if request.method == "POST": - submitted_user = request.form.get("username", "") - submitted_pass = request.form.get("password", "") - LOGGER.info( - "ui.login.attempt", - extra={"event": "ui.login.attempt", "username": submitted_user, "success": submitted_user == username and submitted_pass == password}, - ) - if submitted_user == username and submitted_pass == password: - login_user(user) - return redirect(url_for("index")) - flash("Invalid credentials", "error") - return render_template("login.html") - - @app.route("/logout") - def logout(): - logout_user() - return redirect(url_for("login")) - def _handle_manifest() -> tuple[Optional[Path], Optional[object]]: file = request.files.get("consent_file") if not file or not file.filename: @@ -214,8 +112,6 @@ def _handle_manifest() -> tuple[Optional[Path], Optional[object]]: return temp_path, manifest @app.route("/", methods=["GET", "POST"]) - @login_required - @_rbac_required("admin") def index(): if request.method == "POST": target = request.form.get("target", "") @@ -242,7 +138,14 @@ def index(): flash(f"Consent manifest invalid: {exc}", "error") if consent_path: consent_path.unlink(missing_ok=True) - LOGGER.warning("ui.consent.invalid", extra={"event": "ui.consent.invalid", "target": target, "error": str(exc)}) + LOGGER.warning( + "ui.consent.invalid", + extra={ + "event": "ui.consent.invalid", + "target": target, + "error": str(exc), + }, + ) return render_template("index.html") try: @@ -269,10 +172,19 @@ def index(): flash(str(exc), "error") if consent_path: consent_path.unlink(missing_ok=True) - LOGGER.error("ui.scan.failed", extra={"event": "ui.scan.failed", "target": target, "error": str(exc)}) + LOGGER.error( + "ui.scan.failed", + extra={ + "event": "ui.scan.failed", + "target": target, + "error": str(exc), + }, + ) return render_template("index.html") - persisted = persist_report(report, consent_source=consent_path, sign=False) + persisted = persist_report( + report, consent_source=consent_path, sign=False + ) if consent_path: consent_path.unlink(missing_ok=True) LOGGER.info( @@ -288,8 +200,6 @@ def index(): return render_template("index.html") @app.route("/reports/") - @login_required - @_rbac_required("admin") def report_detail(report_id: str): report_dir = ensure_results_dir() / report_id report_file = report_dir / "report.json" @@ -301,10 +211,10 @@ def report_detail(report_id: str): ) @app.route("/results/") - @login_required - @_rbac_required("admin") def download_result(filename: str): - return send_from_directory(ensure_results_dir(), filename, as_attachment=True) + return send_from_directory( + ensure_results_dir(), filename, as_attachment=True + ) return app @@ -313,14 +223,14 @@ def main() -> None: app = create_app() host = "0.0.0.0" if app.config["PUBLIC_UI"] else "127.0.0.1" port = int(os.environ.get("DEFAULT_PORT", "5000")) - LOGGER.warning( - "UI running with RBAC %s", - "enabled" if app.config["RBAC_ENABLED"] else "disabled", - ) if app.config["PUBLIC_UI"]: LOGGER.warning( "Public UI mode enabled — ensure reverse proxy protections are in place." ) + else: + LOGGER.warning( + "Running ReconScript UI without authentication. Restrict access appropriately." + ) app.run(host=host, port=port, threaded=True)