diff --git a/app.py b/app.py index 343455561e55393181b9ad342eedb62b242a59ca..b6e06ceb2f891e60337fab8804c9f3bdb4f51c02 100644 --- a/app.py +++ b/app.py @@ -15,7 +15,7 @@ # This file is an original contribution from Telefonica Innovación Digital S.L. import logging -from flask import Flask +from flask import Flask, request, Response from flask_restx import Api from flask_cors import CORS from swagger.tfs_namespace import tfs_ns @@ -27,6 +27,32 @@ from src.webui.gui import gui_bp from src.config.config import create_config from src.database.db import init_db +# Paths that do not require authentication (Swagger UI and its static assets) +# /nsc → Swagger UI +# /swaggerui → Swagger UI static assets (flask-restx) +# /swagger → swagger.json spec file (flask-restx) +UNAUTHENTICATED_PREFIXES = ("/nsc", "/swaggerui", "/swagger") + + +def _unauthorized_response(): + """Return a 401 response without WWW-Authenticate header to avoid browser popup.""" + return Response( + response="Unauthorized: valid credentials required.", + status=401, + headers={} + ) + + +def _check_basic_auth(app): + """Validate Basic Auth credentials against the configured username and password.""" + credentials = request.authorization + if not credentials: + return False + expected_username = app.config["API_USERNAME"] + expected_password = app.config["API_PASSWORD"] + return credentials.username == expected_username and credentials.password == expected_password + + def create_app(): """Create Flask application with configured API and namespaces.""" init_db() @@ -46,7 +72,9 @@ def create_app(): version="1.0", title="Network Slice Controller (NSC) API", description="API for orchestrating and realizing transport network slice requests", - doc="/nsc" # Swagger UI URL + doc="/nsc", # Swagger UI URL + authorizations={"basicAuth": {"type": "basic"}}, + security="basicAuth" ) # Register namespaces @@ -59,8 +87,19 @@ def create_app(): app.secret_key = "clave-secreta-dev" app.register_blueprint(gui_bp) + # Registered after Api and namespaces to ensure flask-restx does not override this hook + @app.before_request + def enforce_basic_auth(): + """Reject any request that does not include valid Basic Auth credentials.""" + # Allow unauthenticated access to Swagger UI and its static assets + if request.path.startswith(UNAUTHENTICATED_PREFIXES): + return None + if not _check_basic_auth(app): + return _unauthorized_response() + return app + if __name__ == "__main__": app = create_app() - app.run(host="0.0.0.0", port=NSC_PORT, debug=True) + app.run(host="0.0.0.0", port=NSC_PORT, debug=True) \ No newline at end of file diff --git a/src/config/.env.example b/src/config/.env.example index c322fa21cc98fd0d6d5b21091023ddca5c60c532..ce04227f2a0598052d14e8abec945304bd73dd59 100644 --- a/src/config/.env.example +++ b/src/config/.env.example @@ -76,3 +76,9 @@ DATAPLANE_SUPPORT=FRR # WebUI # ------------------------- WEBUI_DEPLOY=true + +# ------------------------- +# API +# ------------------------- +API_USERNAME=admin +API_PASSWORD=admin diff --git a/src/config/config.py b/src/config/config.py index 145418d8d0975d2ab65de664704bcde2e81f46e2..ec8a5aa466ec2abb0eaa71114a9c59c6815de6a6 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -69,4 +69,8 @@ def create_config(app: Flask): app.config["SDN_CONTROLLER_TYPE"] = os.getenv("SDN_CONTROLLER_TYPE", "TFS") app.config["DATAPLANE_SUPPORT"] = os.getenv("DATAPLANE_SUPPORT", "FRR") + # API + app.config["API_USERNAME"] = os.getenv("API_USERNAME", "admin") + app.config["API_PASSWORD"] = os.getenv("API_PASSWORD", "admin") + return app