From 862a251295aa70ab41e54fe236fc7cb6ab89c023 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 20:10:37 +0100 Subject: [PATCH 01/11] allow only limited parameters to be set on the dsp --- owrx/dsp.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/owrx/dsp.py b/owrx/dsp.py index 72e6667..13cc542 100644 --- a/owrx/dsp.py +++ b/owrx/dsp.py @@ -28,7 +28,7 @@ class DspManager(csdr.output, SdrSourceEventClient): self.props = PropertyStack() # local demodulator properties not forwarded to the sdr - self.props.addLayer(0, PropertyLayer().filter( + self.localProps = PropertyLayer().filter( "output_rate", "hd_output_rate", "squelch_level", @@ -39,7 +39,8 @@ class DspManager(csdr.output, SdrSourceEventClient): "mod", "secondary_offset_freq", "dmr_filter", - )) + ) + self.props.addLayer(0, self.localProps) # properties that we inherit from the sdr self.props.addLayer(1, self.sdrSource.getProps().filter( "audio_compression", @@ -177,7 +178,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 From 8b52988dcd81159b87b19d535882efdc16c5709d Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 20:15:02 +0100 Subject: [PATCH 02/11] add a test that makes sure that writing to a filtered property fails --- test/property/test_property_filter.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/property/test_property_filter.py b/test/property/test_property_filter.py index 66eff3b..8553552 100644 --- a/test/property/test_property_filter.py +++ b/test/property/test_property_filter.py @@ -11,7 +11,7 @@ class PropertyFilterTest(TestCase): pf = PropertyFilter(pm, "testkey") self.assertEqual(pf["testkey"], "testvalue") - def testMissesPropert(self): + def testMissesProperty(self): pm = PropertyLayer() pm["testkey"] = "testvalue" pf = PropertyFilter(pm, "other_key") @@ -49,3 +49,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") From 40e531c0daa6bb9ac00eef01cbe4130265d80ed4 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 20:53:51 +0100 Subject: [PATCH 03/11] start implementing a validation layer, refs #215 --- owrx/property/__init__.py | 5 +++ owrx/property/validators.py | 42 +++++++++++++++++++ test/property/validators/__init__.py | 0 .../validators/test_integer_validator.py | 15 +++++++ .../validators/test_lambda_validator.py | 21 ++++++++++ .../validators/test_number_validator.py | 18 ++++++++ .../validators/test_string_validator.py | 14 +++++++ test/property/validators/test_validator.py | 16 +++++++ 8 files changed, 131 insertions(+) create mode 100644 owrx/property/validators.py create mode 100644 test/property/validators/__init__.py create mode 100644 test/property/validators/test_integer_validator.py create mode 100644 test/property/validators/test_lambda_validator.py create mode 100644 test/property/validators/test_number_validator.py create mode 100644 test/property/validators/test_string_validator.py create mode 100644 test/property/validators/test_validator.py diff --git a/owrx/property/__init__.py b/owrx/property/__init__.py index f3560fa..fed36a5 100644 --- a/owrx/property/__init__.py +++ b/owrx/property/__init__.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from owrx.property.validators import Validator import logging logger = logging.getLogger(__name__) @@ -23,6 +24,7 @@ class Subscription(object): class PropertyManager(ABC): def __init__(self): self.subscribers = [] + self.validators = {} @abstractmethod def __getitem__(self, item): @@ -81,6 +83,9 @@ class PropertyManager(ABC): except Exception as e: logger.exception(e) + def setValidator(self, name, validator): + self.validators[name] = Validator.of(validator) + class PropertyLayer(PropertyManager): def __init__(self): diff --git a/owrx/property/validators.py b/owrx/property/validators.py new file mode 100644 index 0000000..fce2955 --- /dev/null +++ b/owrx/property/validators.py @@ -0,0 +1,42 @@ +from abc import ABC, abstractmethod + + +class ValidatorException(Exception): + pass + + +class Validator(ABC): + @staticmethod + def of(x): + if isinstance(x, Validator): + return x + if callable(x): + return LambdaValidator(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 NumberValidator(Validator): + def isValid(self, value): + return isinstance(value, int) or isinstance(value, float) + + +class IntegerValidator(Validator): + def isValid(self, value): + return isinstance(value, int) + + +class StringValidator(Validator): + def isValid(self, value): + return isinstance(value, str) diff --git a/test/property/validators/__init__.py b/test/property/validators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/property/validators/test_integer_validator.py b/test/property/validators/test_integer_validator.py new file mode 100644 index 0000000..454918a --- /dev/null +++ b/test/property/validators/test_integer_validator.py @@ -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())) diff --git a/test/property/validators/test_lambda_validator.py b/test/property/validators/test_lambda_validator.py new file mode 100644 index 0000000..969dce6 --- /dev/null +++ b/test/property/validators/test_lambda_validator.py @@ -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)) diff --git a/test/property/validators/test_number_validator.py b/test/property/validators/test_number_validator.py new file mode 100644 index 0000000..3eff11b --- /dev/null +++ b/test/property/validators/test_number_validator.py @@ -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())) diff --git a/test/property/validators/test_string_validator.py b/test/property/validators/test_string_validator.py new file mode 100644 index 0000000..d285f1a --- /dev/null +++ b/test/property/validators/test_string_validator.py @@ -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())) diff --git a/test/property/validators/test_validator.py b/test/property/validators/test_validator.py new file mode 100644 index 0000000..a083889 --- /dev/null +++ b/test/property/validators/test_validator.py @@ -0,0 +1,16 @@ +from unittest import TestCase +from owrx.property.validators import Validator, NumberValidator, LambdaValidator + + +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")) From ad0a5c27db0d4aaf254acb467a46eaf133f53c8b Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 21:19:45 +0100 Subject: [PATCH 04/11] introduce PropertyValidator (wrapper) --- owrx/property/__init__.py | 47 ++++++++++++++++++++++-- test/property/test_property_validator.py | 37 +++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 test/property/test_property_validator.py diff --git a/owrx/property/__init__.py b/owrx/property/__init__.py index fed36a5..6acb68e 100644 --- a/owrx/property/__init__.py +++ b/owrx/property/__init__.py @@ -24,7 +24,6 @@ class Subscription(object): class PropertyManager(ABC): def __init__(self): self.subscribers = [] - self.validators = {} @abstractmethod def __getitem__(self, item): @@ -83,9 +82,6 @@ class PropertyManager(ABC): except Exception as e: logger.exception(e) - def setValidator(self, name, validator): - self.validators[name] = Validator.of(validator) - class PropertyLayer(PropertyManager): def __init__(self): @@ -153,6 +149,49 @@ 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 + 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__() diff --git a/test/property/test_property_validator.py b/test/property/test_property_validator.py new file mode 100644 index 0000000..a246031 --- /dev/null +++ b/test/property/test_property_validator.py @@ -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" From 66dc4e5772054eb426524da018652cf8e83e6ae1 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 21:25:26 +0100 Subject: [PATCH 05/11] get validator by string --- owrx/property/validators.py | 12 ++++++++++++ test/property/validators/test_validator.py | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/owrx/property/validators.py b/owrx/property/validators.py index fce2955..1812fb2 100644 --- a/owrx/property/validators.py +++ b/owrx/property/validators.py @@ -12,6 +12,8 @@ class Validator(ABC): return x if callable(x): return LambdaValidator(x) + if x in validator_types: + return validator_types[x]() raise ValidatorException("Cannot create validator") @abstractmethod @@ -40,3 +42,13 @@ class IntegerValidator(Validator): class StringValidator(Validator): def isValid(self, value): return isinstance(value, str) + + +validator_types = { + "string": StringValidator, + "str": StringValidator, + "integer": IntegerValidator, + "int": IntegerValidator, + "number": NumberValidator, + "num": NumberValidator, +} diff --git a/test/property/validators/test_validator.py b/test/property/validators/test_validator.py index a083889..6fbbf78 100644 --- a/test/property/validators/test_validator.py +++ b/test/property/validators/test_validator.py @@ -1,5 +1,5 @@ from unittest import TestCase -from owrx.property.validators import Validator, NumberValidator, LambdaValidator +from owrx.property.validators import Validator, NumberValidator, LambdaValidator, StringValidator class ValidatorTest(TestCase): @@ -14,3 +14,7 @@ class ValidatorTest(TestCase): 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) From 4b03ced1f778ff7f475b635c97ef15ac54ded2c9 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 21:58:02 +0100 Subject: [PATCH 06/11] add more validators --- owrx/property/validators.py | 48 ++++++++++++++++--- .../validators/test_bool_validator.py | 17 +++++++ .../validators/test_float_validator.py | 15 ++++++ test/property/validators/test_or_validator.py | 17 +++++++ 4 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 test/property/validators/test_bool_validator.py create mode 100644 test/property/validators/test_float_validator.py create mode 100644 test/property/validators/test_or_validator.py diff --git a/owrx/property/validators.py b/owrx/property/validators.py index 1812fb2..a893b79 100644 --- a/owrx/property/validators.py +++ b/owrx/property/validators.py @@ -1,4 +1,6 @@ from abc import ABC, abstractmethod +from functools import reduce +from operator import or_ class ValidatorException(Exception): @@ -29,19 +31,51 @@ class LambdaValidator(Validator): return self.callable(value) -class NumberValidator(Validator): +class TypeValidator(Validator): + def __init__(self, type): + self.type = type + super().__init__() + def isValid(self, value): - return isinstance(value, int) or isinstance(value, float) + return isinstance(value, self.type) -class IntegerValidator(Validator): +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 isinstance(value, int) + return reduce( + or_, + [v.isValid(value) for v in self.validators], + False + ) -class StringValidator(Validator): - def isValid(self, value): - return isinstance(value, str) +class NumberValidator(OrValidator): + def __init__(self): + super().__init__(IntegerValidator(), FloatValidator()) validator_types = { diff --git a/test/property/validators/test_bool_validator.py b/test/property/validators/test_bool_validator.py new file mode 100644 index 0000000..08cfea6 --- /dev/null +++ b/test/property/validators/test_bool_validator.py @@ -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())) diff --git a/test/property/validators/test_float_validator.py b/test/property/validators/test_float_validator.py new file mode 100644 index 0000000..f4e43ec --- /dev/null +++ b/test/property/validators/test_float_validator.py @@ -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())) diff --git a/test/property/validators/test_or_validator.py b/test/property/validators/test_or_validator.py new file mode 100644 index 0000000..0f7f79d --- /dev/null +++ b/test/property/validators/test_or_validator.py @@ -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")) From 49577953c67f140726d491db7c0455d51de66c3c Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 21:58:15 +0100 Subject: [PATCH 07/11] fix events --- owrx/property/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/owrx/property/__init__.py b/owrx/property/__init__.py index 6acb68e..25977da 100644 --- a/owrx/property/__init__.py +++ b/owrx/property/__init__.py @@ -157,6 +157,7 @@ class PropertyValidationError(Exception): 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: From a880b1f6f9a8ea8a80d296c43bd6d86dc570d9fa Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 22:03:53 +0100 Subject: [PATCH 08/11] add regex validator --- owrx/property/validators.py | 9 +++++++++ test/property/validators/test_regex_validator.py | 13 +++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 test/property/validators/test_regex_validator.py diff --git a/owrx/property/validators.py b/owrx/property/validators.py index a893b79..5d474bc 100644 --- a/owrx/property/validators.py +++ b/owrx/property/validators.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod from functools import reduce from operator import or_ +import re class ValidatorException(Exception): @@ -78,6 +79,14 @@ class NumberValidator(OrValidator): super().__init__(IntegerValidator(), FloatValidator()) +class RegexValidator(Validator): + def __init__(self, regex: re.Pattern): + self.regex = regex + + def isValid(self, value): + return self.regex.match(value) is not None + + validator_types = { "string": StringValidator, "str": StringValidator, diff --git a/test/property/validators/test_regex_validator.py b/test/property/validators/test_regex_validator.py new file mode 100644 index 0000000..1bdfb6c --- /dev/null +++ b/test/property/validators/test_regex_validator.py @@ -0,0 +1,13 @@ +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")) From d126c3acefa1a8251d162f319c33ca3d7612ca48 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 22:28:00 +0100 Subject: [PATCH 09/11] allow regexes only on strings --- owrx/property/validators.py | 2 +- test/property/validators/test_regex_validator.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/owrx/property/validators.py b/owrx/property/validators.py index 5d474bc..9a9ebfe 100644 --- a/owrx/property/validators.py +++ b/owrx/property/validators.py @@ -84,7 +84,7 @@ class RegexValidator(Validator): self.regex = regex def isValid(self, value): - return self.regex.match(value) is not None + return isinstance(value, str) and self.regex.match(value) is not None validator_types = { diff --git a/test/property/validators/test_regex_validator.py b/test/property/validators/test_regex_validator.py index 1bdfb6c..2151d1b 100644 --- a/test/property/validators/test_regex_validator.py +++ b/test/property/validators/test_regex_validator.py @@ -11,3 +11,7 @@ class RegexValidatorTest(TestCase): 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)) From 15940d0a2eac60f905b1ab8f0907f180932da95c Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 22:28:48 +0100 Subject: [PATCH 10/11] extend StringValidator instead --- owrx/property/validators.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/owrx/property/validators.py b/owrx/property/validators.py index 9a9ebfe..23008d4 100644 --- a/owrx/property/validators.py +++ b/owrx/property/validators.py @@ -79,12 +79,13 @@ class NumberValidator(OrValidator): super().__init__(IntegerValidator(), FloatValidator()) -class RegexValidator(Validator): +class RegexValidator(StringValidator): def __init__(self, regex: re.Pattern): self.regex = regex + super().__init__() def isValid(self, value): - return isinstance(value, str) and self.regex.match(value) is not None + return super().isValid(value) and self.regex.match(value) is not None validator_types = { From 7e60efeae249c270d1b37ec91e74d93d9c44fc02 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 22:29:23 +0100 Subject: [PATCH 11/11] validate all parameters sent to dsp, refs #215 --- owrx/dsp.py | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/owrx/dsp.py b/owrx/dsp.py index 13cc542..b75220f 100644 --- a/owrx/dsp.py +++ b/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,19 +37,24 @@ class DspManager(csdr.output, SdrSourceEventClient): } self.props = PropertyStack() + # local demodulator properties not forwarded to the sdr - self.localProps = 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) # properties that we inherit from the sdr self.props.addLayer(1, self.sdrSource.getProps().filter(