diff --git a/bin/launch_webadmin b/bin/launch_webadmin index 95e113a..fc722f0 100755 --- a/bin/launch_webadmin +++ b/bin/launch_webadmin @@ -4,16 +4,17 @@ import argparse from imaginaerraum_door_admin.webapp import create_application parser = argparse.ArgumentParser() +parser.add_argument("--key_file", default='/root/flask_keys', help="Path to file with Flask SECRET_KEY and SECURITY_PASSWORD_SALT") parser.add_argument("--token_file", default="/etc/door_tokens", help="path to the file with door tokens and users") parser.add_argument("--nfc_socket", default="/tmp/nfc.sock", help="socket for handling NFC reader commands") parser.add_argument("--template_folder", default="templates", help="path to Flask templates folder") parser.add_argument("--static_folder", default="static", help="path to Flask static folder") -parser.add_argument("--admin_file", help="Path to file for creating initial admin users") +parser.add_argument("--admin_file", help="Path to file for creating super admin users") parser.add_argument("--log_file", default="/var/log/webinterface.log", help="Path to log file") parser.add_argument("--ldap_url", default="ldaps://ldap.imaginaerraum.de", help="URL for LDAP server for alternative user authorization") parser.add_argument("--mqtt_host", default="10.10.21.2", help="IP address of MQTT broker") -parser.add_argument("--port", default=80, help="Port for running the Flask server") +parser.add_argument("--flask_port", default=80, help="Port for running the Flask server") parser.add_argument("--mail_server", default="smtp.googlemail.com", help="email server for sending security messages") parser.add_argument("--mail_port", default=465, help="port for security email server") parser.add_argument("--mail_use_tls", default=False, help="use TLS for security emails") @@ -23,4 +24,4 @@ parser.add_argument("--mail_password", default="password", help="Password for em config = parser.parse_args() app = create_application(config) -app.run(host='0.0.0.0', port=config.port) +app.run(host='0.0.0.0', port=config.flask_port) diff --git a/imaginaerraum_door_admin/webapp.py b/imaginaerraum_door_admin/webapp.py index e88b483..e6b55a6 100644 --- a/imaginaerraum_door_admin/webapp.py +++ b/imaginaerraum_door_admin/webapp.py @@ -1,4 +1,3 @@ -import os from flask import Flask, render_template, request, flash, redirect, session, send_file from werkzeug.utils import secure_filename from flask_wtf import FlaskForm @@ -10,9 +9,9 @@ from flask_security import Security, SQLAlchemyUserDatastore, auth_required, has current_user, roles_required from flask_security.models import fsqla_v2 as fsqla from flask_security.forms import LoginForm, Required, PasswordField -from flask_security.utils import find_user, verify_password +from flask_security.utils import find_user from flask_mail import Mail -from email_validator import validate_email, EmailNotValidError +from email_validator import validate_email import json import secrets @@ -83,12 +82,36 @@ def create_application(config): logger.addHandler(ch) # do some checks for file existence etc. - if not Path(config.template_folder).exists(): - logger.error(f'Flask template folder not found at {config.template_folder}') - if not Path(config.static_folder).exists(): - logger.error(f'Flask static folder not found at {config.static_folder}') + try: + with open(config.key_file) as f: + data = f.readlines() + if 'SECRET_KEY' in data[0]: + secret_key = data[0].split()[-1] + else: + raise Exception("Could not read SECURITY_PASSWORD_SALT") + if 'SECURITY_PASSWORD_SALT' in data[1]: + security_password_salt = data[1].split()[-1] + else: + raise Exception("Could not read SECURITY_PASSWORD_SALT") + except Exception as e: + logger.warning(f"Flask keys could not be read from file at {Path(config.key_file).absolute()}. Exception: {e}. Using default values instead.") + secret_key = 'Q7PJu2fg2jabYwP-Psop6c6f2G4' + security_password_salt = '10036796768252925167749545152988277953' + + if Path(config.template_folder).is_absolute(): + if not Path(config.template_folder).exists(): + logger.error(f'Flask template folder not found at {Path(config.template_folder).absolute()}') + else: + if not (Path(__file__).parent / config.template_folder).exists(): + logger.error(f'Flask template folder not found at {(Path(__file__).parent / config.template_folder).absolute()}') + if Path(config.static_folder).is_absolute(): + if not Path(config.static_folder).exists(): + logger.error(f'Flask static folder not found at {Path(config.static_folder).absolute()}') + else: + if not (Path(__file__).parent / config.static_folder).exists(): + logger.error(f'Flask static folder not found at {(Path(__file__).parent / config.static_folder).absolute()}') if not Path(config.token_file).exists(): - logger.warning(f"Token file not found at {config.token_file}") + logger.warning(f"Token file not found at {Path(config.token_file).absolute()}") # create door objects which provides access to the token file and current door state via MQTT door = DoorHandle(token_file=config.token_file, mqtt_host=config.mqtt_host, nfc_socket=config.nfc_socket, @@ -97,11 +120,10 @@ def create_application(config): app = Flask(__name__, template_folder=config.template_folder, static_folder=config.static_folder) # Generate a nice key using secrets.token_urlsafe() - app.config['SECRET_KEY'] = os.environ.get("SECRET_KEY", 'Q7PJu2fg2jabYwP-Psop6c6f2G4') + app.config['SECRET_KEY'] = secret_key # Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt # Generate a good salt using: secrets.SystemRandom().getrandbits(128) - app.config['SECURITY_PASSWORD_SALT'] = os.environ.get("SECURITY_PASSWORD_SALT", - '10036796768252925167749545152988277953') + app.config['SECURITY_PASSWORD_SALT'] = security_password_salt app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = [ {"email": {"mapper": uia_email_mapper, "case_insensitive": True}}, @@ -233,14 +255,14 @@ def create_application(config): user_datastore.commit() self.user = find_user(self.email.data) - logger.info(f"Admin user with credentials '{self.email.data}' authorized through LDAP") + logger.info(f"User with credentials '{self.email.data}' authorized through LDAP") if not authorized: # try authorizing locally using Flask security user datastore authorized = super(ExtendedLoginForm, self).validate() if authorized: - logger.info(f"Admin user with credentials '{self.email.data}' authorized through local database") + logger.info(f"User with credentials '{self.email.data}' authorized through local database") # if any of the authorization methods is successful we authorize the user return authorized @@ -668,7 +690,7 @@ def create_application(config): for d in new_admin_data: if user_datastore.find_user(email=d['email'], username=d['username']) is None: - logger.info(f"New admin user created with username '{d['username']}' and email '{d['email']}'") + logger.info(f"New super admin user created with username '{d['username']}' and email '{d['email']}'") # create new admin (only if admin does not already exist) new_admin = user_datastore.create_user(email=d['email'], username=d['username'], password=hash_password(d['password']),