From d49f268fd4acc1881e77c6aa2ba2c609100a0786 Mon Sep 17 00:00:00 2001 From: "Karl O. Pinc" Date: Sat, 12 Sep 2020 16:14:02 -0500 Subject: [PATCH] Module for functions that validate the internal setting structure --- src/pgwui_server/__init__.py | 78 +------------- src/pgwui_server/checkset.py | 98 +++++++++++++++++ tests/test___init__.py | 181 ++----------------------------- tests/test_checkset.py | 201 +++++++++++++++++++++++++++++++++++ 4 files changed, 310 insertions(+), 248 deletions(-) create mode 100644 src/pgwui_server/checkset.py create mode 100644 tests/test_checkset.py diff --git a/src/pgwui_server/__init__.py b/src/pgwui_server/__init__.py index fdc930f..2db01be 100644 --- a/src/pgwui_server/__init__.py +++ b/src/pgwui_server/__init__.py @@ -1,4 +1,5 @@ -# Copyright (C) 2018, 2019 The Meme Factory, Inc. http://www.karlpinc.com/ +# Copyright (C) 2018, 2019, 2020 The Meme Factory, Inc. +# http://www.karlpinc.com/ # This file is part of PGWUI_Server. # @@ -22,13 +23,12 @@ '''Provide a way to configure PGWUI. ''' -from ast import literal_eval from pyramid.config import Configurator import logging import sys from . import exceptions as server_ex -from . import constants +from . import checkset from pgwui_common import exceptions as common_ex from pgwui_common import plugin @@ -77,74 +77,6 @@ def dot_to_dict(settings, key, new_key): del settings[key] -def key_to_ini(key): - '''Convert the setting key to a key used in an ini file's declaration - ''' - return 'pgwui.{}'.format(key) - - -def require_setting(errors, setting, pgwui_settings): - if setting not in pgwui_settings: - errors.append(common_ex.MissingSettingError(key_to_ini(setting))) - - -def boolean_setting(errors, setting, pgwui_settings): - if setting in pgwui_settings: - try: - val = literal_eval(pgwui_settings[setting]) - except ValueError: - val = None - if (val is not True - and val is not False): - errors.append(common_ex.NotBooleanSettingError( - key_to_ini(setting), pgwui_settings[setting])) - - -def validate_setting_values(errors, settings): - '''Check each settings value for validity - ''' - pgwui_settings = settings['pgwui'] - - # pg_host can be missing, it defaults to the Unix socket (in psycopg2) - - # pg_port can be missing, it defaults to 5432 (in psycopg2) - - # default_db can be missing, then the user sees no default - - # dry_run - require_setting(errors, 'dry_run', pgwui_settings) - boolean_setting(errors, 'dry_run', pgwui_settings) - - # route_prefix can be missing, defaults to no route prefix which is fine. - - # routes can be missing, sensible defaults are built-in. - - # validate_hmac - boolean_setting(errors, 'validate_hmac', pgwui_settings) - - -def do_validate_hmac(settings): - '''True unless the user has specificly rejected hmac validation - ''' - pgwui_settings = settings['pgwui'] - return ('validate_hmac' not in pgwui_settings - or literal_eval(pgwui_settings['validate_hmac'])) - - -def validate_hmac(errors, settings): - '''Unless otherwise requested, validate the session.secret setting''' - if not do_validate_hmac(settings): - return - - if 'session.secret' not in settings: - errors.append(server_ex.NoHMACError()) - return - - if len(settings['session.secret']) != constants.HMAC_LEN: - errors.append(server_ex.HMACLengthError()) - return - - def parse_assignments(lines): '''Return a list of key/value tuples from the lines of a setting ''' @@ -187,8 +119,8 @@ def dictify_settings(errors, settings, components): for key in list(settings.keys()): setting_into_dict( errors, components, component_checkers, key, settings) - validate_setting_values(errors, settings) - validate_hmac(errors, settings) + checkset.validate_setting_values(errors, settings) + checkset.validate_hmac(errors, settings) def exit_reporting_errors(errors): diff --git a/src/pgwui_server/checkset.py b/src/pgwui_server/checkset.py new file mode 100644 index 0000000..62746ba --- /dev/null +++ b/src/pgwui_server/checkset.py @@ -0,0 +1,98 @@ +# Copyright (C) 2018, 2019, 2020 The Meme Factory, Inc. +# http://www.karlpinc.com/ + +# This file is part of PGWUI_Server. +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with this program. If not, see +# . +# + +# Karl O. Pinc + +'''Check the pgwui settings (in the internal "dict format") +''' + +from ast import literal_eval + +from . import constants +from pgwui_common import exceptions as common_ex +import pgwui_server.exceptions as server_ex + + +def key_to_ini(key): + '''Convert the setting key to a key used in an ini file's declaration + ''' + return 'pgwui.{}'.format(key) + + +def require_setting(errors, setting, pgwui_settings): + if setting not in pgwui_settings: + errors.append(common_ex.MissingSettingError(key_to_ini(setting))) + + +def boolean_setting(errors, setting, pgwui_settings): + if setting in pgwui_settings: + try: + val = literal_eval(pgwui_settings[setting]) + except ValueError: + val = None + if (val is not True + and val is not False): + errors.append(common_ex.NotBooleanSettingError( + key_to_ini(setting), pgwui_settings[setting])) + + +def validate_setting_values(errors, settings): + '''Check each settings value for validity + ''' + pgwui_settings = settings['pgwui'] + + # pg_host can be missing, it defaults to the Unix socket (in psycopg2) + + # pg_port can be missing, it defaults to 5432 (in psycopg2) + + # default_db can be missing, then the user sees no default + + # dry_run + require_setting(errors, 'dry_run', pgwui_settings) + boolean_setting(errors, 'dry_run', pgwui_settings) + + # route_prefix can be missing, defaults to no route prefix which is fine. + + # routes can be missing, sensible defaults are built-in. + + # validate_hmac + boolean_setting(errors, 'validate_hmac', pgwui_settings) + + +def do_validate_hmac(settings): + '''True unless the user has specificly rejected hmac validation + ''' + pgwui_settings = settings['pgwui'] + return ('validate_hmac' not in pgwui_settings + or literal_eval(pgwui_settings['validate_hmac'])) + + +def validate_hmac(errors, settings): + '''Unless otherwise requested, validate the session.secret setting''' + if not do_validate_hmac(settings): + return + + if 'session.secret' not in settings: + errors.append(server_ex.NoHMACError()) + return + + if len(settings['session.secret']) != constants.HMAC_LEN: + errors.append(server_ex.HMACLengthError()) + return diff --git a/tests/test___init__.py b/tests/test___init__.py index 6f786f6..95ad04c 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -32,8 +32,7 @@ import pgwui_common.plugin # Use as a regular module, not a plugin, so lint checks work from pgwui_testing import testing -import pgwui_server.constants as constants -import pgwui_server.exceptions as server_ex +import pgwui_server.checkset import pgwui_server.__init__ as pgwui_server_init @@ -71,6 +70,11 @@ mock_find_pgwui_components = testing.make_mock_fixture( mock_find_pgwui_check_settings = testing.make_mock_fixture( pgwui_common.plugin, 'find_pgwui_check_settings') +mock_validate_setting_values = testing.make_mock_fixture( + pgwui_server.checkset, 'validate_setting_values') +mock_validate_hmac = testing.make_mock_fixture( + pgwui_server.checkset, 'validate_hmac') + # Unit tests @@ -169,179 +173,6 @@ mock_dot_to_dict = testing.make_mock_fixture( pgwui_server_init, 'dot_to_dict') -# key_to_ini() - -def test_key_to_ini(): - '''The return value is as expected - ''' - key = 'pgwui_example' - result = pgwui_server_init.key_to_ini(key) - - assert result == 'pgwui.' + key - - -mock_key_to_ini = testing.make_mock_fixture( - pgwui_server_init, 'key_to_ini') - - -# require_setting() - -def test_require_setting_missing(): - '''Deliver exception when a required setting is missing''' - errors = [] - pgwui_server_init.require_setting(errors, 'key', {}) - - assert errors - assert isinstance(errors[0], common_ex.MissingSettingError) - - -def test_require_setting_present(): - '''Does nothing when a required setting is present''' - errors = [] - pgwui_server_init.require_setting(errors, 'key', {'key': 'value'}) - - assert errors == [] - - -mock_require_setting = testing.make_mock_fixture( - pgwui_server_init, 'require_setting') - - -# boolean_setting() - -def test_boolean_setting_missing(): - '''Does nothing when the setting is not in the settings''' - errors = [] - pgwui_server_init.boolean_setting(errors, 'key', {}) - - assert errors == [] - - -def test_boolean_setting_true(): - '''Does nothing when the setting is "True"''' - errors = [] - pgwui_server_init.boolean_setting(errors, 'key', {'key': 'True'}) - - assert errors == [] - - -def test_boolean_setting_false(): - '''Does nothing when the setting is "False"''' - errors = [] - pgwui_server_init.boolean_setting(errors, 'key', {'key': 'False'}) - - assert errors == [] - - -def test_boolean_setting_notboolean(): - '''Deliver an exception when the setting does not evaluate to a boolean''' - errors = [] - pgwui_server_init.boolean_setting(errors, 'key', {'key': '0'}) - - assert errors - assert isinstance(errors[0], common_ex.NotBooleanSettingError) - - -def test_boolean_setting_notparsable(): - '''Deliver an exception when the setting does not evaluate to a - boolean because it is not parseable - ''' - errors = [] - pgwui_server_init.boolean_setting(errors, 'key', {'key': 'a'}) - - assert errors - assert isinstance(errors[0], common_ex.NotBooleanSettingError) - - -mock_boolean_setting = testing.make_mock_fixture( - pgwui_server_init, 'boolean_setting') - - -# validate_setting_values() - -def test_validate_setting_values(mock_require_setting, mock_boolean_setting): - '''Calls require_setting() and boolean_setting()''' - - pgwui_server_init.validate_setting_values([], {'pgwui': {}}) - - assert mock_require_setting.called - assert mock_boolean_setting.called - - -mock_validate_setting_values = testing.make_mock_fixture( - pgwui_server_init, 'validate_setting_values') - - -# do_validate_hmac() - -def test_do_validate_hmac_none(): - '''pgwui.validate_hmac defaults to True''' - assert pgwui_server_init.do_validate_hmac({'pgwui': {}}) is True - - -def test_do_validate_hmac_True(): - '''Require hmac validation when pgwui.validate_hmac is True''' - result = pgwui_server_init.do_validate_hmac( - {'pgwui': {'validate_hmac': 'True'}}) - assert result is True - - -def test_do_validate_hmac_False(): - '''No hmac validation when pgwui.validate_hmac is False''' - result = pgwui_server_init.do_validate_hmac( - {'pgwui': {'validate_hmac': 'False'}}) - assert result is False - - -mock_do_validate_hmac = testing.make_mock_fixture( - pgwui_server_init, 'do_validate_hmac') - - -# validate_hmac() - -def test_validate_hmac_unvalidated(mock_do_validate_hmac): - '''No error is returned when hmac validation is off''' - mock_do_validate_hmac.return_value = False - errors = [] - pgwui_server_init.validate_hmac(errors, {}) - - assert errors == [] - - -def test_validate_hmac_success(mock_do_validate_hmac): - '''No error is returned when hmac is validated an the right length''' - mock_do_validate_hmac.return_value = True - errors = [] - pgwui_server_init.validate_hmac( - errors, {'session.secret': 'x' * constants.HMAC_LEN}) - - assert errors == [] - - -def test_validate_hmac_missing(mock_do_validate_hmac): - '''Deliver error when hmac is validated and missing''' - mock_do_validate_hmac.return_value = True - errors = [] - pgwui_server_init.validate_hmac(errors, {}) - - assert errors - assert isinstance(errors[0], server_ex.NoHMACError) - - -def test_validate_hmac_length(mock_do_validate_hmac): - '''Deliver error when hmac is validated and the wrong length''' - mock_do_validate_hmac.return_value = True - errors = [] - pgwui_server_init.validate_hmac(errors, {'session.secret': ''}) - - assert errors - assert isinstance(errors[0], server_ex.HMACLengthError) - - -mock_validate_hmac = testing.make_mock_fixture( - pgwui_server_init, 'validate_hmac') - - # parse_assignments() def test_parse_assignments_str(): diff --git a/tests/test_checkset.py b/tests/test_checkset.py new file mode 100644 index 0000000..fa3509d --- /dev/null +++ b/tests/test_checkset.py @@ -0,0 +1,201 @@ +# Copyright (C) 2018, 2019, 2020 The Meme Factory, Inc. +# http://www.karlpinc.com/ + +# This file is part of PGWUI_Server. +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with this program. If not, see +# . +# + +# Karl O. Pinc + + +import pgwui_common.exceptions as common_ex +from pgwui_server import checkset +import pgwui_server.constants as constants +from pgwui_server import exceptions as server_ex +from pgwui_testing import testing + + +# key_to_ini() + +def test_key_to_ini(): + '''The return value is as expected + ''' + key = 'pgwui_example' + result = checkset.key_to_ini(key) + + assert result == 'pgwui.' + key + + +mock_key_to_ini = testing.make_mock_fixture( + checkset, 'key_to_ini') + + +# require_setting() + +def test_require_setting_missing(): + '''Deliver exception when a required setting is missing''' + errors = [] + checkset.require_setting(errors, 'key', {}) + + assert errors + assert isinstance(errors[0], common_ex.MissingSettingError) + + +def test_require_setting_present(): + '''Does nothing when a required setting is present''' + errors = [] + checkset.require_setting(errors, 'key', {'key': 'value'}) + + assert errors == [] + + +mock_require_setting = testing.make_mock_fixture( + checkset, 'require_setting') + + +# boolean_setting() + +def test_boolean_setting_missing(): + '''Does nothing when the setting is not in the settings''' + errors = [] + checkset.boolean_setting(errors, 'key', {}) + + assert errors == [] + + +def test_boolean_setting_true(): + '''Does nothing when the setting is "True"''' + errors = [] + checkset.boolean_setting(errors, 'key', {'key': 'True'}) + + assert errors == [] + + +def test_boolean_setting_false(): + '''Does nothing when the setting is "False"''' + errors = [] + checkset.boolean_setting(errors, 'key', {'key': 'False'}) + + assert errors == [] + + +def test_boolean_setting_notboolean(): + '''Deliver an exception when the setting does not evaluate to a boolean''' + errors = [] + checkset.boolean_setting(errors, 'key', {'key': '0'}) + + assert errors + assert isinstance(errors[0], common_ex.NotBooleanSettingError) + + +def test_boolean_setting_notparsable(): + '''Deliver an exception when the setting does not evaluate to a + boolean because it is not parseable + ''' + errors = [] + checkset.boolean_setting(errors, 'key', {'key': 'a'}) + + assert errors + assert isinstance(errors[0], common_ex.NotBooleanSettingError) + + +mock_boolean_setting = testing.make_mock_fixture( + checkset, 'boolean_setting') + + +# validate_setting_values() + +def test_validate_setting_values(mock_require_setting, mock_boolean_setting): + '''Calls require_setting() and boolean_setting()''' + + checkset.validate_setting_values([], {'pgwui': {}}) + + assert mock_require_setting.called + assert mock_boolean_setting.called + + +mock_validate_setting_values = testing.make_mock_fixture( + checkset, 'validate_setting_values') + + +# do_validate_hmac() + +def test_do_validate_hmac_none(): + '''pgwui.validate_hmac defaults to True''' + assert checkset.do_validate_hmac({'pgwui': {}}) is True + + +def test_do_validate_hmac_True(): + '''Require hmac validation when pgwui.validate_hmac is True''' + result = checkset.do_validate_hmac( + {'pgwui': {'validate_hmac': 'True'}}) + assert result is True + + +def test_do_validate_hmac_False(): + '''No hmac validation when pgwui.validate_hmac is False''' + result = checkset.do_validate_hmac( + {'pgwui': {'validate_hmac': 'False'}}) + assert result is False + + +mock_do_validate_hmac = testing.make_mock_fixture( + checkset, 'do_validate_hmac') + + +# validate_hmac() + +def test_validate_hmac_unvalidated(mock_do_validate_hmac): + '''No error is returned when hmac validation is off''' + mock_do_validate_hmac.return_value = False + errors = [] + checkset.validate_hmac(errors, {}) + + assert errors == [] + + +def test_validate_hmac_success(mock_do_validate_hmac): + '''No error is returned when hmac is validated an the right length''' + mock_do_validate_hmac.return_value = True + errors = [] + checkset.validate_hmac( + errors, {'session.secret': 'x' * constants.HMAC_LEN}) + + assert errors == [] + + +def test_validate_hmac_missing(mock_do_validate_hmac): + '''Deliver error when hmac is validated and missing''' + mock_do_validate_hmac.return_value = True + errors = [] + checkset.validate_hmac(errors, {}) + + assert errors + assert isinstance(errors[0], server_ex.NoHMACError) + + +def test_validate_hmac_length(mock_do_validate_hmac): + '''Deliver error when hmac is validated and the wrong length''' + mock_do_validate_hmac.return_value = True + errors = [] + checkset.validate_hmac(errors, {'session.secret': ''}) + + assert errors + assert isinstance(errors[0], server_ex.HMACLengthError) + + +mock_validate_hmac = testing.make_mock_fixture( + checkset, 'validate_hmac') -- 2.34.1