Merge branch 'fix_arbitrary_code_execution' into develop
This commit is contained in:
commit
a5bdf6c3ac
64
owrx/dsp.py
64
owrx/dsp.py
@ -4,16 +4,26 @@ from owrx.js8 import Js8Parser
|
||||
from owrx.aprs import AprsParser
|
||||
from owrx.pocsag import PocsagParser
|
||||
from owrx.source import SdrSource, SdrSourceEventClient
|
||||
from owrx.property import PropertyStack, PropertyLayer
|
||||
from owrx.property import PropertyStack, PropertyLayer, PropertyValidator
|
||||
from owrx.property.validators import OrValidator, RegexValidator, BoolValidator
|
||||
from owrx.modes import Modes
|
||||
from csdr import csdr
|
||||
import threading
|
||||
import re
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ModulationValidator(OrValidator):
|
||||
"""
|
||||
This validator only allows alphanumeric characters and numbers, but no spaces or special characters
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__(BoolValidator(), RegexValidator(re.compile("^[a-z0-9]+$")))
|
||||
|
||||
|
||||
class DspManager(csdr.output, SdrSourceEventClient):
|
||||
def __init__(self, handler, sdrSource):
|
||||
self.handler = handler
|
||||
@ -27,22 +37,42 @@ class DspManager(csdr.output, SdrSourceEventClient):
|
||||
}
|
||||
|
||||
self.props = PropertyStack()
|
||||
|
||||
# local demodulator properties not forwarded to the sdr
|
||||
self.props.addLayer(
|
||||
0,
|
||||
PropertyLayer().filter(
|
||||
"output_rate",
|
||||
"hd_output_rate",
|
||||
"squelch_level",
|
||||
"secondary_mod",
|
||||
"low_cut",
|
||||
"high_cut",
|
||||
"offset_freq",
|
||||
"mod",
|
||||
"secondary_offset_freq",
|
||||
"dmr_filter",
|
||||
),
|
||||
)
|
||||
# ensure strict validation since these can be set from the client
|
||||
# and are used to build executable commands
|
||||
validators = {
|
||||
"output_rate": "int",
|
||||
"hd_output_rate": "int",
|
||||
"squelch_level": "num",
|
||||
"secondary_mod": ModulationValidator(),
|
||||
"low_cut": "num",
|
||||
"high_cut": "num",
|
||||
"offset_freq": "int",
|
||||
"mod": ModulationValidator(),
|
||||
"secondary_offset_freq": "int",
|
||||
"dmr_filter": "int",
|
||||
}
|
||||
self.localProps = PropertyValidator(PropertyLayer().filter(*validators.keys()), validators)
|
||||
|
||||
self.props.addLayer(0, self.localProps)
|
||||
# ensure strict validation since these can be set from the client
|
||||
# and are used to build executable commands
|
||||
validators = {
|
||||
"output_rate": "int",
|
||||
"hd_output_rate": "int",
|
||||
"squelch_level": "num",
|
||||
"secondary_mod": ModulationValidator(),
|
||||
"low_cut": "num",
|
||||
"high_cut": "num",
|
||||
"offset_freq": "int",
|
||||
"mod": ModulationValidator(),
|
||||
"secondary_offset_freq": "int",
|
||||
"dmr_filter": "int",
|
||||
}
|
||||
self.localProps = PropertyValidator(PropertyLayer().filter(*validators.keys()), validators)
|
||||
|
||||
self.props.addLayer(0, self.localProps)
|
||||
# properties that we inherit from the sdr
|
||||
self.props.addLayer(
|
||||
1,
|
||||
@ -188,7 +218,7 @@ class DspManager(csdr.output, SdrSourceEventClient):
|
||||
self.setProperty(k, v)
|
||||
|
||||
def setProperty(self, prop, value):
|
||||
self.props[prop] = value
|
||||
self.localProps[prop] = value
|
||||
|
||||
def getClientClass(self):
|
||||
return SdrSource.CLIENT_USER
|
||||
|
@ -1,4 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from owrx.property.validators import Validator
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -154,6 +155,50 @@ class PropertyFilter(PropertyManager):
|
||||
return [k for k in self.pm.keys() if k in self.props]
|
||||
|
||||
|
||||
class PropertyValidationError(Exception):
|
||||
def __init__(self, key, value):
|
||||
super().__init__('Invalid value for property "{key}": "{value}"'.format(key=key, value=str(value)))
|
||||
|
||||
|
||||
class PropertyValidator(PropertyManager):
|
||||
def __init__(self, pm: PropertyManager, validators=None):
|
||||
self.pm = pm
|
||||
self.pm.wire(self._fireCallbacks)
|
||||
if validators is None:
|
||||
self.validators = {}
|
||||
else:
|
||||
self.validators = {k: Validator.of(v) for k, v in validators.items()}
|
||||
super().__init__()
|
||||
|
||||
def validate(self, key, value):
|
||||
if key not in self.validators:
|
||||
return
|
||||
if not self.validators[key].isValid(value):
|
||||
raise PropertyValidationError(key, value)
|
||||
|
||||
def setValidator(self, key, validator):
|
||||
self.validators[key] = Validator.of(validator)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.pm.__getitem__(item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.validate(key, value)
|
||||
return self.pm.__setitem__(key, value)
|
||||
|
||||
def __contains__(self, item):
|
||||
return self.pm.__contains__(item)
|
||||
|
||||
def __dict__(self):
|
||||
return self.pm.__dict__()
|
||||
|
||||
def __delitem__(self, key):
|
||||
return self.pm.__delitem__(key)
|
||||
|
||||
def keys(self):
|
||||
return self.pm.keys()
|
||||
|
||||
|
||||
class PropertyStack(PropertyManager):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
98
owrx/property/validators.py
Normal file
98
owrx/property/validators.py
Normal file
@ -0,0 +1,98 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from functools import reduce
|
||||
from operator import or_
|
||||
import re
|
||||
|
||||
|
||||
class ValidatorException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Validator(ABC):
|
||||
@staticmethod
|
||||
def of(x):
|
||||
if isinstance(x, Validator):
|
||||
return x
|
||||
if callable(x):
|
||||
return LambdaValidator(x)
|
||||
if x in validator_types:
|
||||
return validator_types[x]()
|
||||
raise ValidatorException("Cannot create validator")
|
||||
|
||||
@abstractmethod
|
||||
def isValid(self, value):
|
||||
pass
|
||||
|
||||
|
||||
class LambdaValidator(Validator):
|
||||
def __init__(self, c):
|
||||
self.callable = c
|
||||
|
||||
def isValid(self, value):
|
||||
return self.callable(value)
|
||||
|
||||
|
||||
class TypeValidator(Validator):
|
||||
def __init__(self, type):
|
||||
self.type = type
|
||||
super().__init__()
|
||||
|
||||
def isValid(self, value):
|
||||
return isinstance(value, self.type)
|
||||
|
||||
|
||||
class IntegerValidator(TypeValidator):
|
||||
def __init__(self):
|
||||
super().__init__(int)
|
||||
|
||||
|
||||
class FloatValidator(TypeValidator):
|
||||
def __init__(self):
|
||||
super().__init__(float)
|
||||
|
||||
|
||||
class StringValidator(TypeValidator):
|
||||
def __init__(self):
|
||||
super().__init__(str)
|
||||
|
||||
|
||||
class BoolValidator(TypeValidator):
|
||||
def __init__(self):
|
||||
super().__init__(bool)
|
||||
|
||||
|
||||
class OrValidator(Validator):
|
||||
def __init__(self, *validators):
|
||||
self.validators = validators
|
||||
super().__init__()
|
||||
|
||||
def isValid(self, value):
|
||||
return reduce(
|
||||
or_,
|
||||
[v.isValid(value) for v in self.validators],
|
||||
False
|
||||
)
|
||||
|
||||
|
||||
class NumberValidator(OrValidator):
|
||||
def __init__(self):
|
||||
super().__init__(IntegerValidator(), FloatValidator())
|
||||
|
||||
|
||||
class RegexValidator(StringValidator):
|
||||
def __init__(self, regex: re.Pattern):
|
||||
self.regex = regex
|
||||
super().__init__()
|
||||
|
||||
def isValid(self, value):
|
||||
return super().isValid(value) and self.regex.match(value) is not None
|
||||
|
||||
|
||||
validator_types = {
|
||||
"string": StringValidator,
|
||||
"str": StringValidator,
|
||||
"integer": IntegerValidator,
|
||||
"int": IntegerValidator,
|
||||
"number": NumberValidator,
|
||||
"num": NumberValidator,
|
||||
}
|
@ -48,3 +48,11 @@ class PropertyFilterTest(TestCase):
|
||||
pf["testkey"] = "new value"
|
||||
self.assertEqual(pm["testkey"], "new value")
|
||||
self.assertEqual(pf["testkey"], "new value")
|
||||
|
||||
def testRejectsWrite(self):
|
||||
pm = PropertyLayer()
|
||||
pm["testkey"] = "old value"
|
||||
pf = PropertyFilter(pm, "otherkey")
|
||||
with self.assertRaises(KeyError):
|
||||
pf["testkey"] = "new value"
|
||||
self.assertEqual(pm["testkey"], "old value")
|
||||
|
37
test/property/test_property_validator.py
Normal file
37
test/property/test_property_validator.py
Normal file
@ -0,0 +1,37 @@
|
||||
from unittest import TestCase
|
||||
from owrx.property import PropertyLayer, PropertyValidator, PropertyValidationError
|
||||
from owrx.property.validators import NumberValidator, StringValidator
|
||||
|
||||
|
||||
class PropertyValidatorTest(TestCase):
|
||||
def testPassesUnvalidated(self):
|
||||
pm = PropertyLayer()
|
||||
pv = PropertyValidator(pm)
|
||||
pv["testkey"] = "testvalue"
|
||||
self.assertEqual(pv["testkey"], "testvalue")
|
||||
self.assertEqual(pm["testkey"], "testvalue")
|
||||
|
||||
def testPassesValidValue(self):
|
||||
pv = PropertyValidator(PropertyLayer(), {"testkey": NumberValidator()})
|
||||
pv["testkey"] = 42
|
||||
self.assertEqual(pv["testkey"], 42)
|
||||
|
||||
def testThrowsErrorOnInvalidValue(self):
|
||||
pv = PropertyValidator(PropertyLayer(), {"testkey": NumberValidator()})
|
||||
with self.assertRaises(PropertyValidationError):
|
||||
pv["testkey"] = "text"
|
||||
|
||||
def testSetValidator(self):
|
||||
pv = PropertyValidator(PropertyLayer())
|
||||
pv.setValidator("testkey", NumberValidator())
|
||||
with self.assertRaises(PropertyValidationError):
|
||||
pv["testkey"] = "text"
|
||||
|
||||
def testUpdateValidator(self):
|
||||
pv = PropertyValidator(PropertyLayer(), {"testkey": StringValidator()})
|
||||
# this should pass
|
||||
pv["testkey"] = "text"
|
||||
pv.setValidator("testkey", NumberValidator())
|
||||
# this should raise
|
||||
with self.assertRaises(PropertyValidationError):
|
||||
pv["testkey"] = "text"
|
0
test/property/validators/__init__.py
Normal file
0
test/property/validators/__init__.py
Normal file
17
test/property/validators/test_bool_validator.py
Normal file
17
test/property/validators/test_bool_validator.py
Normal file
@ -0,0 +1,17 @@
|
||||
from unittest import TestCase
|
||||
from owrx.property.validators import BoolValidator
|
||||
|
||||
|
||||
class NumberValidatorTest(TestCase):
|
||||
def testPassesNumbers(self):
|
||||
validator = BoolValidator()
|
||||
self.assertTrue(validator.isValid(True))
|
||||
self.assertTrue(validator.isValid(False))
|
||||
|
||||
def testDoesntPassOthers(self):
|
||||
validator = BoolValidator()
|
||||
self.assertFalse(validator.isValid(123))
|
||||
self.assertFalse(validator.isValid(-2))
|
||||
self.assertFalse(validator.isValid(.5))
|
||||
self.assertFalse(validator.isValid("text"))
|
||||
self.assertFalse(validator.isValid(object()))
|
15
test/property/validators/test_float_validator.py
Normal file
15
test/property/validators/test_float_validator.py
Normal file
@ -0,0 +1,15 @@
|
||||
from unittest import TestCase
|
||||
from owrx.property.validators import FloatValidator
|
||||
|
||||
|
||||
class FloatValidatorTest(TestCase):
|
||||
def testPassesNumbers(self):
|
||||
validator = FloatValidator()
|
||||
self.assertTrue(validator.isValid(.5))
|
||||
|
||||
def testDoesntPassOthers(self):
|
||||
validator = FloatValidator()
|
||||
self.assertFalse(validator.isValid(123))
|
||||
self.assertFalse(validator.isValid(-2))
|
||||
self.assertFalse(validator.isValid("text"))
|
||||
self.assertFalse(validator.isValid(object()))
|
15
test/property/validators/test_integer_validator.py
Normal file
15
test/property/validators/test_integer_validator.py
Normal file
@ -0,0 +1,15 @@
|
||||
from unittest import TestCase
|
||||
from owrx.property.validators import IntegerValidator
|
||||
|
||||
|
||||
class IntegerValidatorTest(TestCase):
|
||||
def testPassesIntegers(self):
|
||||
validator = IntegerValidator()
|
||||
self.assertTrue(validator.isValid(123))
|
||||
self.assertTrue(validator.isValid(-2))
|
||||
|
||||
def testDoesntPassOthers(self):
|
||||
validator = IntegerValidator()
|
||||
self.assertFalse(validator.isValid(.5))
|
||||
self.assertFalse(validator.isValid("text"))
|
||||
self.assertFalse(validator.isValid(object()))
|
21
test/property/validators/test_lambda_validator.py
Normal file
21
test/property/validators/test_lambda_validator.py
Normal file
@ -0,0 +1,21 @@
|
||||
from unittest import TestCase
|
||||
from unittest.mock import Mock
|
||||
from owrx.property.validators import LambdaValidator
|
||||
|
||||
|
||||
class LambdaValidatorTest(TestCase):
|
||||
def testPassesValue(self):
|
||||
mock = Mock()
|
||||
validator = LambdaValidator(mock.method)
|
||||
validator.isValid("test")
|
||||
mock.method.assert_called_once_with("test")
|
||||
|
||||
def testReturnsTrue(self):
|
||||
validator = LambdaValidator(lambda x: True)
|
||||
self.assertTrue(validator.isValid("any value"))
|
||||
self.assertTrue(validator.isValid(3.1415926))
|
||||
|
||||
def testReturnsFalse(self):
|
||||
validator = LambdaValidator(lambda x: False)
|
||||
self.assertFalse(validator.isValid("any value"))
|
||||
self.assertFalse(validator.isValid(42))
|
18
test/property/validators/test_number_validator.py
Normal file
18
test/property/validators/test_number_validator.py
Normal file
@ -0,0 +1,18 @@
|
||||
from unittest import TestCase
|
||||
from owrx.property.validators import NumberValidator
|
||||
|
||||
|
||||
class NumberValidatorTest(TestCase):
|
||||
def testPassesNumbers(self):
|
||||
validator = NumberValidator()
|
||||
self.assertTrue(validator.isValid(123))
|
||||
self.assertTrue(validator.isValid(-2))
|
||||
self.assertTrue(validator.isValid(.5))
|
||||
|
||||
def testDoesntPassOthers(self):
|
||||
validator = NumberValidator()
|
||||
# bool is a subclass of int, so it passes this test.
|
||||
# not sure if we need to be more specific or if this is alright.
|
||||
# self.assertFalse(validator.isValid(True))
|
||||
self.assertFalse(validator.isValid("text"))
|
||||
self.assertFalse(validator.isValid(object()))
|
17
test/property/validators/test_or_validator.py
Normal file
17
test/property/validators/test_or_validator.py
Normal file
@ -0,0 +1,17 @@
|
||||
from unittest import TestCase
|
||||
from owrx.property.validators import OrValidator, IntegerValidator, StringValidator
|
||||
|
||||
|
||||
class OrValidatorTest(TestCase):
|
||||
def testPassesAnyValidators(self):
|
||||
validator = OrValidator(IntegerValidator(), StringValidator())
|
||||
self.assertTrue(validator.isValid(42))
|
||||
self.assertTrue(validator.isValid("text"))
|
||||
|
||||
def testRejectsOtherTypes(self):
|
||||
validator = OrValidator(IntegerValidator(), StringValidator())
|
||||
self.assertFalse(validator.isValid(.5))
|
||||
|
||||
def testRejectsIfNoValidator(self):
|
||||
validator = OrValidator()
|
||||
self.assertFalse(validator.isValid("any value"))
|
17
test/property/validators/test_regex_validator.py
Normal file
17
test/property/validators/test_regex_validator.py
Normal file
@ -0,0 +1,17 @@
|
||||
from unittest import TestCase
|
||||
from owrx.property.validators import RegexValidator
|
||||
import re
|
||||
|
||||
|
||||
class RegexValidatorTest(TestCase):
|
||||
def testMatchesRegex(self):
|
||||
validator = RegexValidator(re.compile("abc"))
|
||||
self.assertTrue(validator.isValid("abc"))
|
||||
|
||||
def testDoesntMatchRegex(self):
|
||||
validator = RegexValidator(re.compile("abc"))
|
||||
self.assertFalse(validator.isValid("xyz"))
|
||||
|
||||
def testFailsIfValueIsNoString(self):
|
||||
validator = RegexValidator(re.compile("abc"))
|
||||
self.assertFalse(validator.isValid(42))
|
14
test/property/validators/test_string_validator.py
Normal file
14
test/property/validators/test_string_validator.py
Normal file
@ -0,0 +1,14 @@
|
||||
from unittest import TestCase
|
||||
from owrx.property.validators import StringValidator
|
||||
|
||||
class StringValidatorTest(TestCase):
|
||||
def testPassesStrings(self):
|
||||
validator = StringValidator()
|
||||
self.assertTrue(validator.isValid("text"))
|
||||
|
||||
def testDoesntPassOthers(self):
|
||||
validator = StringValidator()
|
||||
self.assertFalse(validator.isValid(123))
|
||||
self.assertFalse(validator.isValid(-2))
|
||||
self.assertFalse(validator.isValid(.5))
|
||||
self.assertFalse(validator.isValid(object()))
|
20
test/property/validators/test_validator.py
Normal file
20
test/property/validators/test_validator.py
Normal file
@ -0,0 +1,20 @@
|
||||
from unittest import TestCase
|
||||
from owrx.property.validators import Validator, NumberValidator, LambdaValidator, StringValidator
|
||||
|
||||
|
||||
class ValidatorTest(TestCase):
|
||||
|
||||
def testReturnsValidator(self):
|
||||
validator = NumberValidator()
|
||||
self.assertIs(validator, Validator.of(validator))
|
||||
|
||||
def testTransformsLambda(self):
|
||||
def my_callable(v):
|
||||
return True
|
||||
validator = Validator.of(my_callable)
|
||||
self.assertIsInstance(validator, LambdaValidator)
|
||||
self.assertTrue(validator.isValid("test"))
|
||||
|
||||
def testGetsValidatorByKey(self):
|
||||
validator = Validator.of("str")
|
||||
self.assertIsInstance(validator, StringValidator)
|
Loading…
Reference in New Issue
Block a user