예제 #1
0
    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"]())
예제 #2
0
    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()
예제 #3
0
    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)
예제 #4
0
    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")
예제 #5
0
    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")
예제 #6
0
    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"]())
예제 #7
0
    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)
예제 #8
0
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)