From e53725091b65fabb2f13ceab95a549cfd58e69e7 Mon Sep 17 00:00:00 2001 From: Kamil Páral Date: Jun 30 2021 11:37:27 +0000 Subject: config: introduce FAS_ENABLED This allows to either use FAS or FakeFAS with a configuration change, independently on the deployment type. This means we no longer need to use `TESTING=True` on our staging instance. Fixes: https://pagure.io/fedora-qa/blockerbugs/issue/182 --- diff --git a/blockerbugs/config.py b/blockerbugs/config.py index 81c0673..ea3dde0 100644 --- a/blockerbugs/config.py +++ b/blockerbugs/config.py @@ -1,6 +1,3 @@ -# -# config.py - settings used in the blockerbug application -# # Copyright 2012, Red Hat, Inc # # This program is free software; you can redistribute it and/or modify @@ -20,11 +17,16 @@ # Authors: # Tim Flink +"""config.py - settings used in the blockerbug application""" + import os import sys class Config(object): + """Default configuration for the whole application. Default values are tailored more towards a + development environment. + """ PRODUCTION = False DEBUG = True SECRET_KEY = None # REPLACE THIS WITH A RANDOM STRING @@ -34,6 +36,9 @@ class Config(object): BUGZILLA_XMLRPC = BUGZILLA_URL + 'xmlrpc.cgi' KOJI_URL = 'http://koji.stg.fedoraproject.org/' BODHI_URL = 'https://bodhi.stg.fedoraproject.org/' + FAS_ENABLED = False + """When FAS is not enabled, a fake stub is used instead, which allows the user to log in (under + the credentials+groups defined here in this config) without authentication.""" FAS_ADMIN_GROUP = 'qa-admin' FAS_USER = '' FAS_PASSWORD = '' @@ -75,12 +80,14 @@ and the vote is one of `+1`/`0`/`-1`) class ProductionConfig(Config): + """A `Config` subclass intended for a production deployment""" PRODUCTION = True DEBUG = False BUGZILLA_URL = 'https://bugzilla.redhat.com/' BUGZILLA_XMLRPC = BUGZILLA_URL + 'xmlrpc.cgi' KOJI_URL = 'http://koji.fedoraproject.org/' BODHI_URL = 'https://bodhi.fedoraproject.org/' + FAS_ENABLED = True FAS_HTTPS_REQUIRED = True FAS_BASE_URL = 'https://admin.fedoraproject.org/accounts/' PAGURE_URL = "https://pagure.io/" @@ -90,12 +97,14 @@ class ProductionConfig(Config): class DevelopmentConfig(Config): + """A `Config` subclass intended for a development environment""" TRAP_BAD_REQUEST_ERRORS = True SQLALCHEMY_DATABASE_URI = 'sqlite:////var/tmp/blockerbugs_db.sqlite' SHOW_DB_URI = True class TestingConfig(Config): + """A `Config` subclass intended for running the test suite""" TESTING = True SHOW_DB_URI = True diff --git a/blockerbugs/util/login.py b/blockerbugs/util/login.py index 0aef6bf..7cd4ec3 100644 --- a/blockerbugs/util/login.py +++ b/blockerbugs/util/login.py @@ -1,6 +1,3 @@ -# -# login.py - a login wrapper -# # Copyright 2013, Red Hat, Inc # # This program is free software; you can redistribute it and/or modify @@ -20,6 +17,8 @@ # Authors: # Martin Krizek +"""login.py - a login wrapper""" + import munch from flask import g, request, redirect from flask_fas_openid import FAS @@ -27,25 +26,29 @@ from flask_fas_openid import FAS from blockerbugs import app -#: a singleton FAS instance. Use getFAS() to access it. _fas = None +"""A singleton FAS instance. Use getFAS() to access it.""" def getFAS(): - '''Return either the real or a fake FAS object depending on the app config''' + """Return either the real or a fake FAS object depending on the app config""" global _fas if _fas is not None: return _fas - if (app.config['PRODUCTION'] or app.config['TESTING']): + if app.config['FAS_ENABLED']: _fas = FAS(app) - else: # development + else: _fas = FakeFAS(app) return _fas class FakeFAS(object): + """A FAS stub class. Allows a login without any authentication. It uses + a username provided in the app config (or 'developer'), and automatically + puts it into the admin group (as defined in the app config). + """ fake_user = munch.Munch( username=app.config['FAS_USER'] or 'developer', groups=[app.config['FAS_ADMIN_GROUP']]) diff --git a/conf/settings.py.example b/conf/settings.py.example index b8044c1..92b1de5 100644 --- a/conf/settings.py.example +++ b/conf/settings.py.example @@ -2,6 +2,7 @@ # cli to generate a config file SECRET_KEY = 'replace-me-with-something-random' SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://dbuser:dbpassword@dbhost:dbport/dbname' +FAS_ENABLED = False FAS_ADMIN_GROUP = "magic-fas-group" FAS_USER = "magicuser@unicornsarereal.com" FAS_PASSWORD = "password" diff --git a/testing/test_login.py b/testing/test_login.py new file mode 100644 index 0000000..dfae9cf --- /dev/null +++ b/testing/test_login.py @@ -0,0 +1,51 @@ +"""Test blockerbugs/util/login.py""" + +import pytest +import mock +import flask_fas_openid + +from blockerbugs.util import login +from blockerbugs import app + + +@pytest.fixture(autouse=True) +def reset_fas(monkeypatch): + """Reset login._fas to None for all tests, so that they can test its initialization. Also, it is + a global variable, we don't want to change the real value, it could affect other test modules. + """ + monkeypatch.setattr(login, '_fas', None) + + +@pytest.fixture(autouse=True) +def avoid_flask_setup(monkeypatch): + """Flask aborts, if a setup method is called after it already handled some requests (which it + already did in other test modules). We must avoid calling such setup methods. + """ + monkeypatch.setattr(app, 'before_request', mock.MagicMock()) + monkeypatch.setattr(app, 'add_url_rule', mock.MagicMock()) + + +class TestLogin: + def test_default_fakefas(self): + """During a test suite run, FakeFAS should be used by default""" + assert isinstance(login.getFAS(), login.FakeFAS) + + def test_singleton(self): + """A singleton _fas should not be overwritten with subsequent calls""" + fas = login.getFAS() + assert fas is login._fas + fas2 = login.getFAS() + assert fas is fas2 is login._fas + + def test_config_fas_enabled(self, monkeypatch): + """Config option FAS_ENABLED should affect the returned object""" + monkeypatch.setitem(app.config, 'FAS_ENABLED', False) + assert isinstance(login.getFAS(), login.FakeFAS) + + login._fas = None + monkeypatch.setitem(app.config, 'FAS_ENABLED', '') + assert isinstance(login.getFAS(), login.FakeFAS) + + login._fas = None + monkeypatch.setitem(app.config, 'FAS_ENABLED', True) + assert isinstance(login.getFAS(), flask_fas_openid.FAS)