import os, json, sys, importlib.util as _importlib_util from flask import Flask, Blueprint, session, redirect, get_flashed_messages, send_from_directory from markupsafe import Markup import config_utils import factory import settings from pages.actions.action import bp as actions_bp from pages.bannedips.action import bp as bannedips_bp from pages.ddns.action import bp as ddns_bp from pages.dhcpreservations.action import bp as dhcpreservations_bp from pages.dnsblocking.action import bp as dnsblocking_bp from pages.dnsserver.action import bp as dnsserver_bp from pages.hostoverrides.action import bp as hostoverrides_bp from pages.intervlan.action import bp as intervlan_bp from pages.accountlogin.action import bp as accountlogin_bp from pages.networklayout.action import bp as networklayout_bp from pages.physicalinterfaces.action import bp as physicalinterfaces_bp from pages.portforwarding.action import bp as portforwarding_bp from pages.portwrangling.action import bp as portwrangling_bp from pages.preferences.action import bp as preferences_bp from pages.accountverifyemail.action import bp as accountverifyemail_bp from pages.vpn.action import bp as vpn_bp from pages.accountcreate.action import bp as accountcreate_bp from pages.accountmanage.action import bp as accountmanage_bp from pages.mdns.action import bp as mdns_bp from pages.radius.action import bp as radius_bp from pages.clientcredentials.action import bp as clientcredentials_bp from pages.captiveportal.action import bp as captiveportal_bp from action_accountlogout import bp as accountlogout_bp from api_apply_health import bp as api_apply_health_bp app = Flask(__name__) app.secret_key = os.environ.get('SECRET_KEY', os.urandom(24)) # Static www/ serving ================================================= @app.route('/www/') def serve_www(filename): response = send_from_directory(config_utils.WWW_DIR, filename) if settings.is_production(): response.cache_control.max_age = 86400 response.cache_control.public = True return response # View blueprint ====================================================== bp = Blueprint('view_page', __name__) page_view_cache = {} def load_page_view(page_name): if page_name not in page_view_cache: path = os.path.join(factory.PAGES_DIR, page_name, 'view.py') if not os.path.exists(path): page_view_cache[page_name] = None else: spec = _importlib_util.spec_from_file_location(f'page_view_{page_name}', path) mod = _importlib_util.module_from_spec(spec) spec.loader.exec_module(mod) page_view_cache[page_name] = mod return page_view_cache[page_name] @bp.route('/') def index(): return serve_view('overview') @bp.route('/') def view(page_name): return serve_view(page_name) def serve_view(page_name): view_def = factory.load_json(os.path.join(factory.PAGES_DIR, page_name, 'content.json')) if not view_def: from flask import abort abort(404) view_req = view_def.get('client_requirement') level = factory.client_level() if not factory.passes(view_req, level): return redirect('/overview' if level > 0 else '/accountlogin') cfg = config_utils.load_config() if level >= factory.LEVEL_RANK['administrator']: try: st = json.load(open(config_utils.HEALTH_FILE)) has_problems = any( item.get('status') == 'problem' for section in ('configurations', 'logs', 'services') for item in st.get(section, []) ) if has_problems: fix_uuid, _ = config_utils._find_cmd_in_queues('fix problems') if fix_uuid is None: config_utils.queue_command('fix problems', user=session.get('email_address', '')) except Exception: pass page_view = load_page_view(page_name) tokens = {} if page_view and hasattr(page_view, 'collect_tokens'): tokens.update(page_view.collect_tokens(cfg)) if page_name == 'radius' and not os.path.exists(f'{config_utils.CONFIGS_DIR}/.radius-secret'): config_utils.queue_command('gen radius') flash_html = '' has_success_flash = False for category, message in get_flashed_messages(with_categories=True): variant = {'error': 'danger', 'warning': 'warning', 'success': 'success'}.get(category, 'info') msg_html = message if isinstance(message, Markup) else factory.e(message) flash_html += f'
{msg_html}
' if category == 'success': has_success_flash = True content_html = flash_html + factory.build_items(view_def.get('items', []), tokens, view_req) return factory.render_layout(page_name, content_html, tokens, page_name=page_name, suppress_pending_bar=has_success_flash) # Register blueprints ================================================= app.register_blueprint(bp) app.register_blueprint(actions_bp) app.register_blueprint(bannedips_bp) app.register_blueprint(ddns_bp) app.register_blueprint(dhcpreservations_bp) app.register_blueprint(dnsblocking_bp) app.register_blueprint(dnsserver_bp) app.register_blueprint(hostoverrides_bp) app.register_blueprint(intervlan_bp) app.register_blueprint(accountlogin_bp) app.register_blueprint(networklayout_bp) app.register_blueprint(physicalinterfaces_bp) app.register_blueprint(portforwarding_bp) app.register_blueprint(portwrangling_bp) app.register_blueprint(preferences_bp) app.register_blueprint(accountverifyemail_bp) app.register_blueprint(vpn_bp) app.register_blueprint(accountcreate_bp) app.register_blueprint(accountmanage_bp) app.register_blueprint(accountlogout_bp) app.register_blueprint(mdns_bp) app.register_blueprint(radius_bp) app.register_blueprint(clientcredentials_bp) app.register_blueprint(captiveportal_bp) app.register_blueprint(api_apply_health_bp) def _seed_initial_account(): email = os.environ.get('INITIAL_MANAGER_EMAIL', '').strip().lower() if not email: try: with open(config_utils.ACCOUNTS_FILE) as f: data = json.load(f) except Exception: data = {'accounts': []} if not data.get('accounts'): print('[main] WARNING: No accounts exist and INITIAL_MANAGER_EMAIL is not set. ' 'Set it in docker-compose.yml to seed the initial manager account.', file=sys.stderr) return try: with open(config_utils.ACCOUNTS_FILE) as f: data = json.load(f) except Exception: data = {'accounts': []} if data.get('accounts'): return data['accounts'] = [{ 'email_address': email, 'access_level': 'manager', 'hashed_password': '', 'timezone': '', }] with open(config_utils.ACCOUNTS_FILE, 'w') as f: json.dump(data, f, indent=2) print(f'[main] Seeded initial manager account: {email}', file=sys.stderr) _seed_initial_account() def _write_credentials_key(): key = settings.get_credentials_key() if not key: return path = f'{config_utils.CONFIGS_DIR}/.credentials-key' try: fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) os.write(fd, key + b'\n') os.close(fd) except OSError as exc: print(f'[main] WARNING: Could not write .credentials-key: {exc}', file=sys.stderr) _write_credentials_key() if __name__ == "__main__": app.run(host="0.0.0.0", port=25327)