Skip to content
Snippets Groups Projects
Commit 70a40bac authored by Piotr Maślanka's avatar Piotr Maślanka
Browse files

2.4.45

parent 906e39e4
No related branches found
No related tags found
No related merge requests found
# v2.4.45 # v2.4.45
* _TBA_ * added capability for Configuration to execute arbitary Python
# v2.4.44 # v2.4.44
......
...@@ -61,8 +61,47 @@ Always you can provide a key called ``optional`` with a value of True, this will ...@@ -61,8 +61,47 @@ Always you can provide a key called ``optional`` with a value of True, this will
The second reserved type if ``binary``. This will encode the ``value`` key with ``encoding`` encoding (default is ascii). The second reserved type if ``binary``. This will encode the ``value`` key with ``encoding`` encoding (default is ascii).
An extra type is ``import``. It imports an expression and calls it with
discovered value, returning the output.
It accepts the following variables:
* module - module to import the expression from
* attribute - name of the attribute inside the module
* cast_before - a type to convert the value to before applying it to.
Eg:
::
class MyEnum(enum.IntEnum):
A = 0
os.environ['TEST_ENV'] = '2'
dct = {
"type": "EnvironmentSource",
"args": ["TEST_ENV", "TEST_ENV"],
"cast_to": {
"module": "my_module",
"attribute": "MyEnum",
"cast_before": {
"type": "lambda",
"operation": "int"
}
}
}
config = load_source_from_dict(dct)
assert config.provide()['TEST_ENV'] == MyEnum(0)
To instantiate the schema, use the following functions: To instantiate the schema, use the following functions:
.. autofunction:: satella.configuration.sources.load_source_from_dict .. autofunction:: satella.configuration.sources.load_source_from_dict
.. autofunction:: satella.configuration.sources.load_source_from_list .. autofunction:: satella.configuration.sources.load_source_from_list
Please note that if your attacker has control over these files, he might
provoke the application into executing arbitrary Python.
**Sanitize your inputs!**
\ No newline at end of file
__version__ = '2.4.45a1' __version__ = '2.4.45'
import copy import copy
import importlib
from satella.coding import rethrow_as, for_argument from satella.coding import rethrow_as, for_argument
from satella.configuration import sources from satella.configuration import sources
...@@ -23,10 +24,21 @@ __all__ = [ ...@@ -23,10 +24,21 @@ __all__ = [
'load_source_from_list' 'load_source_from_list'
] ]
def handle_import(dct: dict):
def convert(v):
if 'cast_before' in dct:
v = EXTRA_TYPES[dct['cast_before']['type']](dct['cast_before'])(v)
return getattr(importlib.import_module(dct['module']), dct['attribute'])(v)
return convert
EXTRA_TYPES = { EXTRA_TYPES = {
'binary': lambda dct: dct['value'].encode(dct.get('encoding', 'ascii')), 'binary': lambda dct: dct['value'].encode(dct.get('encoding', 'ascii')),
'lambda': lambda dct: eval('lambda x: ' + dct['operation'], globals(), 'lambda': lambda dct: eval('lambda x: ' + dct['operation'], globals(),
locals()) locals()),
'import': handle_import,
} }
......
import enum
import os
from satella.configuration.sources import load_source_from_dict, load_source_from_list from satella.configuration.sources import load_source_from_dict, load_source_from_list
from .test_sources.utils import SourceTestCase from .test_sources.utils import SourceTestCase
...@@ -27,6 +30,26 @@ INNER_DATA = [ ...@@ -27,6 +30,26 @@ INNER_DATA = [
} }
] ]
INNER_DATA_2 = {
'type': 'EnvironmentSource',
'args': [
'TEST_ENV', 'TEST_ENV'
],
'cast_to': {
'type': 'import',
'module': 'tests.test_configuration.test_load_source_from_dict',
'attribute': 'MyEnum',
'cast_before': {
'type': 'lambda',
'operation': 'int(x)'
}
}
}
class MyEnum(enum.IntEnum):
A = 0
DICT_DATA = { DICT_DATA = {
'type': 'MergingSource', 'type': 'MergingSource',
'args': INNER_DATA 'args': INNER_DATA
...@@ -38,3 +61,8 @@ class TestLoadSourceFromDict(SourceTestCase): ...@@ -38,3 +61,8 @@ class TestLoadSourceFromDict(SourceTestCase):
output = {'a': 5, 'b': 5, 'c': 21} output = {'a': 5, 'b': 5, 'c': 21}
self.assertSourceHas(load_source_from_dict(DICT_DATA), output) self.assertSourceHas(load_source_from_dict(DICT_DATA), output)
self.assertSourceHas(load_source_from_list(INNER_DATA), output) self.assertSourceHas(load_source_from_list(INNER_DATA), output)
def test_env(self):
os.environ['TEST_ENV'] = '0'
source = load_source_from_dict(INNER_DATA_2).provide()
self.assertEqual(source['TEST_ENV'], MyEnum(0))
...@@ -38,10 +38,7 @@ class SourceTestCase(unittest.TestCase): ...@@ -38,10 +38,7 @@ class SourceTestCase(unittest.TestCase):
self.assertRaises(fails_with, source.provide) self.assertRaises(fails_with, source.provide)
def assertSourceEmpty(self, source: BaseSource): def assertSourceEmpty(self, source: BaseSource):
self.assertEqual(self.provide(source), {}) self.assertEqual(source.provide(), {})
def assertSourceHas(self, source: BaseSource, value: tp.Any, postop=lambda x: x): def assertSourceHas(self, source: BaseSource, value: tp.Any, postop=lambda x: x):
self.assertEqual(postop(self.provide(source)), value) self.assertEqual(postop(source.provide()), value)
def provide(self, source: BaseSource) -> dict:
return source.provide()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment