def forgot() -> Union[Response, str]: """ Allows a user to reset their password :return: The response """ if request.method == "POST": email = request.form["email"] recaptcha_result = verify_recaptcha( request.remote_addr, request.form.get("g-recaptcha-response", ""), Config.RECAPTCHA_SECRET_KEY) user: User = User.query.filter_by(email=email).first() if not recaptcha_result: flash(Config.STRINGS["recaptcha_incorrect"], "danger") return redirect(url_for("user_management.forgot")) else: if user is None: # Fail silently to ensure that a potential attacker can't # use the response to figure out information # on registered users pass else: new_pass = generate_random(20) user.password_hash = generate_hash(new_pass) db.session.commit() email_msg = render_template( "email/forgot_password.html", domain_name=Config.DOMAIN_NAME, host_url=Config.base_url(), target_url=os.path.join(Config.base_url(), "login"), password=new_pass, username=user.username, **Config.TEMPLATE_EXTRAS["forgot_email"]()) try: send_email( email, Config.STRINGS["password_reset_email_title"], email_msg, Config.SMTP_HOST, Config.SMTP_ADDRESS, Config.SMTP_PASSWORD, Config.SMTP_PORT) except SMTPAuthenticationError: # pragma: no cover app.logger.error("SMTP Authentication failed") flash("SMTP AUTHENTICATION FAILED", "info") flash(Config.STRINGS["password_was_reset"], "success") return redirect(url_for("static.index")) else: return render_template("user_management/forgot.html", **Config.TEMPLATE_EXTRAS["forgot"]())
def test_dumping_environment_variables(self): """ Tests dumping environment variables :return: None """ dumpfile = "/tmp/envdump" os.environ["FLASK_SECRET"] = "ABCXYZ" Config.dump_env_variables(dumpfile) def verify(output: str): with open(dumpfile, "r") as f: self.assertEqual(output, f.read()) with patch("builtins.print", verify): Config.dump_env_variables()
def test_initializing_telegram_bot_connection(self): """ 'Tests' initializing the telegram bot connection :return: None """ class DummyBot: def __init__(self, settings): self.settings = settings os.environ["TELEGRAM_API_KEY"] = "ABC" Config.load_config(self.root_path, "jerrycan", "sentry_dsn") try: self.assertIsNone(Config.TELEGRAM_BOT_CONNECTION) self.fail() except AttributeError: pass with patch("jerrycan.Config.TelegramBotConnection", DummyBot): Config.initialize_telegram() self.assertIsNotNone(Config.TELEGRAM_BOT_CONNECTION)
def test_db_config(self): """ Tests the database configuration :return: None """ os.environ.pop("FLASK_TESTING") os.environ["DB_MODE"] = "sqlite" Config.load_config(self.root_path, "jerrycan", "sentry_dsn") self.assertEqual(Config.DB_URI, "sqlite:////tmp/jerrycan.db") os.environ["SQLITE_PATH"] = "/tmp/test.db" Config.load_config(self.root_path, "jerrycan", "sentry_dsn") self.assertEqual(Config.DB_URI, "sqlite:////tmp/test.db") os.environ["DB_MODE"] = "mysql" os.environ["MYSQL_USER"] = "******" os.environ["MYSQL_PASSWORD"] = "******" os.environ["MYSQL_HOST"] = "ghi" os.environ["MYSQL_PORT"] = "1000" os.environ["MYSQL_DATABASE"] = "xyz" Config.load_config(self.root_path, "jerrycan", "sentry_dsn") self.assertEqual(Config.DB_URI, "mysql://*****:*****@ghi:1000/xyz") os.environ["DB_MODE"] = "postgresql" os.environ["POSTGRES_USER"] = "******" os.environ["POSTGRES_PASSWORD"] = "******" os.environ["POSTGRES_HOST"] = "GHI" os.environ["POSTGRES_PORT"] = "2000" os.environ["POSTGRES_DATABASE"] = "XYZ" try: Config.load_config(self.root_path, "jerrycan", "sentry_dsn") self.fail() except SystemExit: pass os.environ["POSTGRES_DB"] = "XYZ" Config.load_config(self.root_path, "jerrycan", "sentry_dsn") self.assertEqual(Config.DB_URI, "postgresql://*****:*****@GHI:2000/XYZ")
def test_building_base_url(self): """ Tests building the base URL :return: None """ os.environ["DOMAIN_NAME"] = "example.org" os.environ["HTTP_PORT"] = "1000" os.environ["BEHIND_PROXY"] = "0" Config.load_config(self.root_path, "jerrycan", "sentry_dsn") self.assertEqual(Config.base_url(), "http://example.org:1000") os.environ["BEHIND_PROXY"] = "1" Config.load_config(self.root_path, "jerrycan", "sentry_dsn") self.assertEqual(Config.base_url(), "https://example.org")
def register() -> Union[Response, str]: """ Page that allows a new user to register :return: The response """ if request.method == "POST": username = request.form["username"] email = request.form["email"] password = request.form["password"] password_repeat = request.form["password-repeat"] recaptcha_result = verify_recaptcha( request.remote_addr, request.form.get("g-recaptcha-response", ""), Config.RECAPTCHA_SECRET_KEY) all_users = User.query.all() usernames = [user.username for user in all_users] emails = [user.email for user in all_users] _min, _max = Config.MIN_USERNAME_LENGTH, Config.MAX_USERNAME_LENGTH if len(username) < _min or len(username) > _max: flash(Config.STRINGS["username_length"].format(_min, _max), "danger") elif password != password_repeat: flash(Config.STRINGS["passwords_do_not_match"], "danger") elif username in usernames: flash(Config.STRINGS["username_already_exists"], "danger") elif email in emails: flash(Config.STRINGS["email_already_in_use"], "danger") elif not recaptcha_result: flash(Config.STRINGS["recaptcha_incorrect"], "danger") else: confirmation_key = generate_random(32) confirmation_hash = generate_hash(confirmation_key) user = User(username=username, email=email, password_hash=generate_hash(password), confirmation_hash=confirmation_hash) db.session.add(user) db.session.commit() email_msg = render_template( "email/registration.html", domain_name=Config.DOMAIN_NAME, host_url=Config.base_url(), target_url=os.path.join(Config.base_url(), "confirm"), username=username, user_id=user.id, confirm_key=confirmation_key, **Config.TEMPLATE_EXTRAS["registration_email"]()) try: send_email(email, Config.STRINGS["registration_email_title"], email_msg, Config.SMTP_HOST, Config.SMTP_ADDRESS, Config.SMTP_PASSWORD, Config.SMTP_PORT) except SMTPAuthenticationError: # pragma: no cover app.logger.error("Failed to authenticate SMTP, could not " "send confirmation email to user") flash("SMTP AUTHENTICATION ERROR", "danger") app.logger.info("User {} registered.".format(user.username)) flash(Config.STRINGS["registration_successful"], "info") return redirect(url_for("static.index")) return redirect(url_for("user_management.register")) else: return render_template("user_management/register.html", **Config.TEMPLATE_EXTRAS["register"]())
def test_environment_variables_definitions(self): """ Tests the definition of the environent variables :return: None """ if "DB_MODE" in os.environ: os.environ.pop("DB_MODE") variables = Config.environment_variables() first_required = variables["required"] first_optional = variables["optional"] self.assertTrue("FLASK_SECRET" in first_required) self.assertFalse("SQLITE_PATH" in first_optional) self.assertFalse("MYSQL_USER" in first_required) self.assertFalse("MYSQL_DATABASE" in first_required) self.assertFalse("MYSQL_DB" in first_required) self.assertFalse("POSTGRES_USER" in first_required) self.assertFalse("POSTGRES_DATABASE" in first_required) self.assertFalse("POSTGRES_DB" in first_required) self.assertTrue("LOGGING_PATH" in first_optional) os.environ["DB_MODE"] = "sqlite" variables = Config.environment_variables() required = variables["required"] optional = variables["optional"] self.assertEqual(first_required, required) self.assertNotEqual(first_optional, optional) self.assertTrue("SQLITE_PATH" in optional) self.assertFalse("MYSQL_USER" in required) self.assertFalse("MYSQL_DATABASE" in required) self.assertFalse("MYSQL_DB" in required) self.assertFalse("POSTGRES_USER" in required) self.assertFalse("POSTGRES_DATABASE" in required) self.assertFalse("POSTGRES_DB" in required) os.environ["DB_MODE"] = "mysql" variables = Config.environment_variables() required = variables["required"] optional = variables["optional"] self.assertNotEqual(first_required, required) self.assertEqual(first_optional, optional) self.assertFalse("SQLITE_PATH" in optional) self.assertTrue("MYSQL_USER" in required) self.assertTrue("MYSQL_DATABASE" in required) self.assertFalse("MYSQL_DB" in required) self.assertFalse("POSTGRES_USER" in required) self.assertFalse("POSTGRES_DATABASE" in required) self.assertFalse("POSTGRES_DB" in required) os.environ["DB_MODE"] = "postgresql" variables = Config.environment_variables() required = variables["required"] optional = variables["optional"] self.assertNotEqual(first_required, required) self.assertEqual(first_optional, optional) self.assertFalse("SQLITE_PATH" in optional) self.assertFalse("MYSQL_USER" in required) self.assertFalse("MYSQL_DATABASE" in required) self.assertFalse("MYSQL_DB" in required) self.assertTrue("POSTGRES_USER" in required) self.assertFalse("POSTGRES_DATABASE" in required) self.assertTrue("POSTGRES_DB" in required)
def __init_app( config: Type[Config], blueprint_generators: List[Tuple[Callable[[str], Blueprint], str]], extra_jinja_vars: Dict[str, Any] ): """ Initializes the flask app :param config: The configuration to use :param blueprint_generators: Tuples that contain a function that generates a blueprint and the name of the blueprint :param extra_jinja_vars: Any extra variables to pass to jinja :return: None """ app.testing = config.TESTING app.config["TRAP_HTTP_EXCEPTIONS"] = True app.config["SERVER_NAME"] = Config.base_url().split("://", 1)[1] if Config.BEHIND_PROXY: app.config["PREFERRED_URL_SCHEME"] = "https" app.secret_key = config.FLASK_SECRET for blueprint_generator, blueprint_name in blueprint_generators: if blueprint_name in CREATED_BLUEPRINTS: app.logger.debug(f"Blueprint {blueprint_name} already created") continue else: app.logger.info(f"Creating blueprint {blueprint_name}") CREATED_BLUEPRINTS.append(blueprint_name) blueprint = blueprint_generator(blueprint_name) app.register_blueprint(blueprint) @app.context_processor def inject_template_variables(): """ Injects the project's version string so that it will be available in templates :return: The dictionary to inject """ defaults = { "version": config.VERSION, "env": app.env, "config": config } defaults.update(extra_jinja_vars) return defaults @app.errorhandler(Exception) def exception_handling(e: Exception): """ Handles any uncaught exceptions and shows an applicable error page :param e: The caught exception :return: The response to the exception """ if isinstance(e, HTTPException): error = e if e.code == 401: flash( config.STRINGS["401_message"], AlertSeverity.DANGER.value ) return redirect(url_for("user_management.login")) app.logger.warning("Caught HTTP exception: {}".format(e)) else: error = HTTPException(config.STRINGS["500_message"]) error.code = 500 trace = "".join(traceback.format_exception(*sys.exc_info())) app.logger.error("Caught exception: {}\n{}".format(e, trace)) sentry_sdk.capture_exception(e) return render_template( config.REQUIRED_TEMPLATES["error_page"], error=error ) @app.errorhandler(HTTPException) # type: ignore def unauthorized_handling(e: HTTPException): """ Forwards HTTP exceptions to the error handler :param e: The HTTPException :return: The response to the exception """ return exception_handling(e)