+++ /dev/null
-# 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
-# <http://www.gnu.org/licenses/>.
-#
-
-# Karl O. Pinc <kop@karlpinc.com>
-
-'''Validate PGWUI_Core and PGWUI_Common configuration
-'''
-
-import re
-from ast import literal_eval
-
-from . import constants
-from pgwui_common import exceptions as common_ex
-from pgwui_common import checkset
-import pgwui_server.exceptions as server_ex
-
-
-# Regular expressions for page "source" values, by type
-URL_RE = re.compile('^(?:(?:[^:/]+:)?//[^/])|(?:/(?:[^/]|$))')
-
-
-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, formatter):
- if setting not in pgwui_settings:
- errors.append(common_ex.MissingSettingError(formatter(setting)))
- return False
- return True
-
-
-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, key_to_ini)
- 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 page_key_to_ini(page_key, subkey):
- '''Convert the page setting subkey to a ini file declaration
- '''
- return key_to_ini(f'{page_key}:{subkey}')
-
-
-def require_page_settings(errors, required_settings, page_settings, page_key):
- '''Check for required keys in the page setting
- '''
- def subkey_to_ini(subkey):
- return page_key_to_ini(page_key, subkey)
-
- have_settings = True
- for subkey in required_settings:
- have_settings &= require_setting(
- errors, subkey, page_settings, subkey_to_ini)
-
- return have_settings
-
-
-def validate_url_source(errors, page_key, source):
- '''Validate the page setting "source" for URLs
- '''
- if URL_RE.match(source):
- return
- errors.append(common_ex.BadURLSourceError(
- page_key_to_ini(page_key, 'source'), source))
-
-
-def validate_url_path(errors, page_key, page_settings):
- '''Validate the page setting "url_path"
- '''
- url_path = page_settings['url_path']
- if url_path[0:1] == '/':
- return
- errors.append(common_ex.BadFileURLPathError(
- page_key_to_ini(page_key, 'url_path'), url_path))
-
-
-def validate_file_source(errors, page_key, source):
- '''Validate the page setting "source" for files
- '''
- if source[0:1] == '/':
- return
- errors.append(common_ex.BadFileSourceError(
- page_key_to_ini(page_key, 'file'), source))
-
-
-def validate_route_source(errors, page_key, source):
- '''Validate the page setting "source" for routes
-
- The routes are not yet established, so we don't confirm
- existance at this point.
- '''
- if source != '':
- return
- errors.append(common_ex.BadRouteSourceError(
- page_key_to_ini(page_key, 'route'), source))
-
-
-def validate_asset_source(errors, page_key, source):
- '''Validate the page setting "source" for assets
- '''
- if source != '':
- return
- errors.append(common_ex.BadAssetSourceError(
- page_key_to_ini(page_key, 'asset'), source))
-
-
-def validate_file_content(errors, page_key, page_settings, source):
- '''Validate the content of a "file" page setting
- '''
- validate_file_source(errors, page_key, source)
- if require_page_settings(
- errors, ['url_path'], page_settings, page_key):
- validate_url_path(errors, page_key, page_settings)
- errors.extend(checkset.unknown_settings(
- f'pgwui:{page_key}', ['type', 'source', 'url_path'], page_settings))
-
-
-def validate_type_content(errors, page_key, page_settings):
- '''Validate the page setting's "type", and other page setting content
- based on the type
- '''
- type = page_settings['type']
- source = page_settings['source']
- if type == 'URL':
- validate_url_source(errors, page_key, source)
- errors.extend(checkset.unknown_settings(
- 'pgwui_common', ['type', 'source'], page_settings))
- return
- if type == 'file':
- validate_file_content(errors, page_key, page_settings, source)
- return
- if type == 'route':
- validate_route_source(errors, page_key, source)
- errors.extend(checkset.unknown_settings(
- 'pgwui_common', ['type', 'source'], page_settings))
- return
- if type == 'asset':
- validate_asset_source(errors, page_key, source)
- errors.extend(checkset.unknown_settings(
- 'pgwui_common', ['type', 'source'], page_settings))
- return
-
- errors.append(common_ex.BadPageTypeError(
- page_key_to_ini(page_key, 'type'), type))
-
-
-def validate_page_setting(errors, settings, page_key):
- '''Validate the multiple values of the page setting
- '''
- pgwui_settings = settings['pgwui']
- if page_key not in pgwui_settings:
- return
-
- page_settings = pgwui_settings[page_key]
- if not require_page_settings(
- errors, ['type', 'source'], page_settings, page_key):
- return
-
- validate_type_content(errors, page_key, page_settings)
+++ /dev/null
-# 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
-# <http://www.gnu.org/licenses/>.
-#
-
-# Karl O. Pinc <kop@karlpinc.com>
-
-import pytest
-
-import pgwui_common.exceptions as common_ex
-import pgwui_common
-from pgwui_server import checkset
-import pgwui_server.constants as constants
-from pgwui_server import exceptions as server_ex
-from pgwui_testing import testing
-
-mock_unknown_settings = testing.make_mock_fixture(
- pgwui_common.checkset, 'unknown_settings')
-
-
-# key_to_ini()
-
-@pytest.mark.unittest
-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()
-
-@pytest.mark.unittest
-def test_require_setting_missing():
- '''Deliver exception when a required setting is missing'''
- errors = []
- checkset.require_setting(errors, 'key', {}, lambda x: x)
-
- assert errors
- assert isinstance(errors[0], common_ex.MissingSettingError)
-
-
-@pytest.mark.unittest
-def test_require_setting_present():
- '''Does nothing when a required setting is present'''
- errors = []
- checkset.require_setting(errors, 'key', {'key': 'value'}, lambda x: x)
-
- assert errors == []
-
-
-mock_require_setting = testing.make_mock_fixture(
- checkset, 'require_setting')
-
-
-# boolean_setting()
-
-@pytest.mark.unittest
-def test_boolean_setting_missing():
- '''Does nothing when the setting is not in the settings'''
- errors = []
- checkset.boolean_setting(errors, 'key', {})
-
- assert errors == []
-
-
-@pytest.mark.unittest
-def test_boolean_setting_true():
- '''Does nothing when the setting is "True"'''
- errors = []
- checkset.boolean_setting(errors, 'key', {'key': 'True'})
-
- assert errors == []
-
-
-@pytest.mark.unittest
-def test_boolean_setting_false():
- '''Does nothing when the setting is "False"'''
- errors = []
- checkset.boolean_setting(errors, 'key', {'key': 'False'})
-
- assert errors == []
-
-
-@pytest.mark.unittest
-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)
-
-
-@pytest.mark.unittest
-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()
-
-@pytest.mark.unittest
-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()
-
-@pytest.mark.unittest
-def test_do_validate_hmac_none():
- '''pgwui.validate_hmac defaults to True'''
- assert checkset.do_validate_hmac({'pgwui': {}}) is True
-
-
-@pytest.mark.unittest
-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
-
-
-@pytest.mark.unittest
-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()
-
-@pytest.mark.unittest
-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 == []
-
-
-@pytest.mark.unittest
-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 == []
-
-
-@pytest.mark.unittest
-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)
-
-
-@pytest.mark.unittest
-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')
-
-
-# page_key_to_ini()
-
-@pytest.mark.unittest
-def test_page_key_to_ini(mock_key_to_ini):
- '''key_to_ini() is called, expected result returned
- '''
- mock_key_to_ini.return_value = 'foo'
- result = checkset.page_key_to_ini(None, None)
- assert result == 'foo'
-
-
-mock_page_key_to_ini = testing.make_mock_fixture(
- checkset, 'page_key_to_ini')
-
-
-# require_page_settings()
-
-@pytest.mark.parametrize(
- ('required_settings', 'rs_results', 'expected'), [
- # Settings exist, return True
- (['s1', 's2'], [True, True], True),
- # One setting does not exist, return False
- (['s1', 's2'], [True, False], False)])
-@pytest.mark.unittest
-def test_require_page_settings_result(
- mock_page_key_to_ini, mock_require_setting,
- required_settings, rs_results, expected):
- '''Returns the expected result
- '''
- mock_require_setting.side_effect = rs_results
- result = checkset.require_page_settings(
- None, required_settings, None, None)
- assert result == expected
-
-
-@pytest.mark.unittest
-def test_require_page_settings_subfunc(
- mock_page_key_to_ini, mock_require_setting):
- '''Calls page_key_to_ini() when function is passed to require_setting()
- '''
- def mock_rs(x, subkey, z, subkey_to_ini):
- subkey_to_ini(subkey)
- return True
-
- required_settings = ['s1', 's2']
- mock_require_setting.side_effect = mock_rs
- checkset.require_page_settings(None, required_settings, None, None)
-
- assert mock_page_key_to_ini.call_count == len(required_settings)
-
-
-mock_require_page_settings = testing.make_mock_fixture(
- checkset, 'require_page_settings')
-
-
-# validate_url_source()
-
-@pytest.mark.parametrize(
- ('source', 'expected_error'), [
- ('/', None),
- ('/foo', None),
- ('//www.example.com', None),
- ('//www.example.com/', None),
- ('//www.example.com/foo', None),
- ('http://www.example.com', None),
- ('https://www.example.com', None),
- ('anything://www.example.com', None),
- ('http://www.example.com/', None),
- ('http://www.example.com/foo', None),
- # No domain
- ('//', common_ex.BadURLSourceError),
- # Nothing
- ('', common_ex.BadURLSourceError),
- # Missing / after scheme
- ('http:/www.example.com', common_ex.BadURLSourceError),
- # Extra / after scheme
- ('http:///www.example.com', common_ex.BadURLSourceError)])
-@pytest.mark.unittest
-def test_validate_url_source(mock_page_key_to_ini, source, expected_error):
- '''The test url produces the expected error, or no error as may be
- '''
- errors = []
- checkset.validate_url_source(errors, None, source)
-
- if expected_error:
- assert len(errors) == 1
- assert isinstance(errors[0], expected_error)
- else:
- assert len(errors) == 0
-
-
-mock_validate_url_source = testing.make_mock_fixture(
- checkset, 'validate_url_source')
-
-
-# validate_url_path()
-
-@pytest.mark.parametrize(
- ('path',), [
- ('',),
- ('foo',)])
-@pytest.mark.unittest
-def test_validate_url_path_no_slash(mock_page_key_to_ini, path):
- '''When the path does not begin with a /,
- the right error is added to errors
- '''
- errors = []
- checkset.validate_url_path(errors, 'ignored', {'url_path': path})
-
- assert len(errors) == 1
- assert isinstance(errors[0], common_ex.BadFileURLPathError)
-
-
-@pytest.mark.parametrize(
- ('path',), [
- ('/',),
- ('/foo',)])
-@pytest.mark.unittest
-def test_validate_url_path_slash(mock_page_key_to_ini, path):
- '''When the path begins with a '/', no error is added to errors
- '''
- errors = []
- checkset.validate_url_path(errors, 'ignored', {'url_path': path})
-
- assert len(errors) == 0
-
-
-mock_validate_url_path = testing.make_mock_fixture(
- checkset, 'validate_url_path')
-
-
-# validate_file_source()
-
-@pytest.mark.parametrize(
- ('source',), [
- ('',),
- ('foo',)])
-@pytest.mark.unittest
-def test_validate_file_source_no_slash(mock_page_key_to_ini, source):
- '''When the source does not begin with a /,
- the right error is added to errors
- '''
- errors = []
- checkset.validate_file_source(errors, 'ignored', source)
-
- assert len(errors) == 1
- assert isinstance(errors[0], common_ex.BadFileSourceError)
-
-
-@pytest.mark.parametrize(
- ('source',), [
- ('/',),
- ('/foo',)])
-@pytest.mark.unittest
-def test_validate_file_source_slash(mock_page_key_to_ini, source):
- '''When the source begins with a '/', no error is added to errors
- '''
- errors = []
- checkset.validate_file_source(errors, 'ignored', source)
-
- assert len(errors) == 0
-
-
-mock_validate_file_source = testing.make_mock_fixture(
- checkset, 'validate_file_source')
-
-
-# validate_route_source()
-
-@pytest.mark.unittest
-def test_validate_route_source_empty(mock_page_key_to_ini):
- '''When there is no source the right error is added to errors
- '''
- errors = []
- checkset.validate_route_source(errors, 'ignored', '')
-
- assert len(errors) == 1
- assert isinstance(errors[0], common_ex.BadRouteSourceError)
-
-
-@pytest.mark.unittest
-def test_validate_route_source_not_empty(mock_page_key_to_ini):
- '''When there is a source no error is added to errors
- '''
- errors = []
- checkset.validate_route_source(errors, 'ignored', 'something')
-
- assert len(errors) == 0
-
-
-mock_validate_route_source = testing.make_mock_fixture(
- checkset, 'validate_route_source')
-
-
-# validate_asset_source()
-
-@pytest.mark.unittest
-def test_validate_asset_source_empty(mock_page_key_to_ini):
- '''When there is no source the right error is added to errors
- '''
- errors = []
- checkset.validate_asset_source(errors, 'ignored', '')
-
- assert len(errors) == 1
- assert isinstance(errors[0], common_ex.BadAssetSourceError)
-
-
-@pytest.mark.unittest
-def test_validate_asset_source_not_empty(mock_page_key_to_ini):
- '''When there is a source no error is added to errors
- '''
- errors = []
- checkset.validate_asset_source(errors, 'ignored', 'something')
-
- assert len(errors) == 0
-
-
-mock_validate_asset_source = testing.make_mock_fixture(
- checkset, 'validate_asset_source')
-
-
-# validate_file_content()
-
-@pytest.mark.parametrize(
- ('have_settings', 'vup_called'), [
- (True, 1),
- (False, 0)])
-@pytest.mark.unittest
-def test_validate_file_content(
- mock_validate_file_source, mock_require_page_settings,
- mock_validate_url_path,
- mock_unknown_settings, have_settings, vup_called):
- '''validate_file_source() is called, validate_url_path()
- is called when settings validate, the unknown_settings()
- return value is appended to the errors
- '''
- expected_errors = ['some error']
- mock_require_page_settings.return_value = have_settings
- mock_unknown_settings.return_value = expected_errors
-
- errors = []
- checkset.validate_file_content(errors, None, None, None)
-
- mock_validate_file_source.assert_called_once()
- mock_require_page_settings.assert_called_once()
- assert mock_validate_url_path.call_count == vup_called
- assert errors == expected_errors
-
-
-mock_validate_file_content = testing.make_mock_fixture(
- checkset, 'validate_file_content')
-
-
-# validate_type_content()
-
-@pytest.mark.parametrize(
- ('page_settings',
- 'vus_called',
- 'vfc_called',
- 'vrs_called',
- 'vas_called',
- 'pkti_called',
- 'error_class'), [
- # URL type
- ({'type': 'URL',
- 'source': 'ignored'},
- 1, 0, 0, 0, 0,
- common_ex.UnknownSettingKeyError),
- # file type
- ({'type': 'file',
- 'source': 'ignored'},
- 0, 1, 0, 0, 0,
- common_ex.MissingSettingError),
- # route type
- ({'type': 'route',
- 'source': 'ignored'},
- 0, 0, 1, 0, 0,
- common_ex.UnknownSettingKeyError),
- # asset type
- ({'type': 'asset',
- 'source': 'ignored'},
- 0, 0, 0, 1, 0,
- common_ex.UnknownSettingKeyError),
- # a unknown type
- ({'type': 'unknown',
- 'source': 'ignored'},
- 0, 0, 0, 0, 1,
- common_ex.BadPageTypeError)])
-@pytest.mark.unittest
-def test_validate_type_content(
- mock_validate_url_source, mock_unknown_settings,
- mock_validate_file_content, mock_validate_route_source,
- mock_validate_asset_source, mock_page_key_to_ini,
- page_settings, vus_called, vfc_called,
- vrs_called, vas_called, pkti_called, error_class):
- '''The expected calls are make, the expected errors returned
- '''
- mock_validate_file_content.side_effect = (
- lambda errors, *args:
- errors.append(common_ex.MissingSettingError('ignored')))
- mock_unknown_settings.return_value = [common_ex.UnknownSettingKeyError(
- 'ignored')]
-
- errors = []
- checkset.validate_type_content(errors, 'some_page', page_settings)
-
- assert mock_validate_url_source.call_count == vus_called
- assert mock_validate_file_content.call_count == vfc_called
- assert mock_validate_asset_source.call_count == vas_called
- assert mock_validate_route_source.call_count == vrs_called
- assert len(errors) == 1
- assert isinstance(errors[0], error_class)
-
-
-mock_validate_type_content = testing.make_mock_fixture(
- checkset, 'validate_type_content')
-
-
-# validate_page_setting()
-
-@pytest.mark.unittest
-def test_validate_page_setting_nopage(
- mock_require_page_settings, mock_validate_type_content):
- '''When the page does not have a setting, nothing is done
- '''
- errors = []
- settings = {'pgwui': {}}
- result = checkset.validate_page_setting(errors, settings, 'test_page')
-
- assert errors == []
- assert result is None
- mock_require_page_settings.assert_not_called()
- mock_validate_type_content.assert_not_called()
-
-
-@pytest.mark.unittest
-def test_validate_page_setting_not_required(
- mock_require_page_settings, mock_validate_type_content):
- '''When require_page_settings() says something is missing, nothing is done
- '''
- errors = []
- settings = {'pgwui': {'test_page': 'ignored'}}
- mock_require_page_settings.return_value = False
- result = checkset.validate_page_setting(errors, settings, 'test_page')
-
- assert errors == []
- assert result is None
- mock_require_page_settings.assert_called_once()
- mock_validate_type_content.assert_not_called()
-
-
-@pytest.mark.unittest
-def test_validate_page_setting_required(
- mock_require_page_settings, mock_validate_type_content):
- '''When require_page_settings() says nothing is missing,
- validate_type_content() is called
- '''
- errors = []
- settings = {'pgwui': {'test_page': 'ignored'}}
- mock_require_page_settings.return_value = True
- result = checkset.validate_page_setting(errors, settings, 'test_page')
-
- assert errors == []
- assert result is None
- mock_require_page_settings.assert_called_once()
- mock_validate_type_content.assert_called_once()