Blame SOURCES/0024-Convert-configuration-option-strings-into-native-dat.patch

d97ed2
From 883ef5b965bb7b02a701037748436f8daa9d0643 Mon Sep 17 00:00:00 2001
d97ed2
From: Rob Crittenden <rcritten@redhat.com>
d97ed2
Date: Fri Apr 29 11:42:53 2022 -0400
d97ed2
Subject: Convert configuration option strings into native data
d97ed2
 types
d97ed2
d97ed2
When loading options from the configuration file they will all
d97ed2
be strings. This breaks existing boolean checks (if <something>)
d97ed2
and some assumptions about integer types (e.g. timeout, indent).
d97ed2
d97ed2
So try to detect the data type, defaulting to remain as a string.
d97ed2
d97ed2
Also hardcode some type validation for known keys to prevent
d97ed2
things like debug=foo (which would evaluate to True).
d97ed2
d97ed2
https://bugzilla.redhat.com/show_bug.cgi?id=2079861
d97ed2
d97ed2
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
d97ed2
---
d97ed2
 src/ipahealthcheck/core/config.py | 47 ++++++++++++++++++++++++++++++-
d97ed2
 src/ipahealthcheck/core/output.py |  2 +-
d97ed2
 tests/test_config.py              | 16 ++++++++++-
d97ed2
 tests/test_options.py             |  2 +-
d97ed2
 4 files changed, 63 insertions(+), 4 deletions(-)
d97ed2
d97ed2
diff --git a/src/ipahealthcheck/core/config.py b/src/ipahealthcheck/core/config.py
d97ed2
index 14ae27f..3cd4e8e 100644
d97ed2
--- a/src/ipahealthcheck/core/config.py
d97ed2
+++ b/src/ipahealthcheck/core/config.py
d97ed2
@@ -71,6 +71,27 @@ class Config:
d97ed2
             self.__d[key] = d[key]
d97ed2
 
d97ed2
 
d97ed2
+def convert_string(value):
d97ed2
+    """
d97ed2
+    Reading options from the configuration file will leave them as
d97ed2
+    strings. This breaks boolean values so attempt to convert them.
d97ed2
+    """
d97ed2
+    if not isinstance(value, str):
d97ed2
+        return value
d97ed2
+
d97ed2
+    if value.lower() in (
d97ed2
+        "true",
d97ed2
+        "false",
d97ed2
+    ):
d97ed2
+        return value.lower() == 'true'
d97ed2
+    else:
d97ed2
+        try:
d97ed2
+            value = int(value)
d97ed2
+        except ValueError:
d97ed2
+            pass
d97ed2
+    return value
d97ed2
+
d97ed2
+
d97ed2
 def read_config(config_file):
d97ed2
     """
d97ed2
     Simple configuration file reader
d97ed2
@@ -100,6 +121,30 @@ def read_config(config_file):
d97ed2
     items = parser.items(CONFIG_SECTION)
d97ed2
 
d97ed2
     for (key, value) in items:
d97ed2
-        config[key] = value
d97ed2
+        if len(value) == 0 or value is None:
d97ed2
+            logging.error(
d97ed2
+                "Empty value for %s in %s [%s]",
d97ed2
+                key, config_file, CONFIG_SECTION
d97ed2
+            )
d97ed2
+            return None
d97ed2
+        else:
d97ed2
+            # Try to do some basic validation. This is unfortunately
d97ed2
+            # hardcoded.
d97ed2
+            if key in ('all', 'debug', 'failures_only', 'verbose'):
d97ed2
+                if value.lower() not in ('true', 'false'):
d97ed2
+                    logging.error(
d97ed2
+                        "%s is not a valid boolean in %s [%s]",
d97ed2
+                        key, config_file, CONFIG_SECTION
d97ed2
+                    )
d97ed2
+                    return None
d97ed2
+            elif key in ('indent',):
d97ed2
+                if not isinstance(convert_string(value), int):
d97ed2
+                    logging.error(
d97ed2
+                        "%s is not a valid integer in %s [%s]",
d97ed2
+                        key, config_file, CONFIG_SECTION
d97ed2
+                    )
d97ed2
+                    return None
d97ed2
+            # Some rough type translation from strings
d97ed2
+            config[key] = convert_string(value)
d97ed2
 
d97ed2
     return config
d97ed2
diff --git a/src/ipahealthcheck/core/output.py b/src/ipahealthcheck/core/output.py
d97ed2
index 61dffbe..945969f 100644
d97ed2
--- a/src/ipahealthcheck/core/output.py
d97ed2
+++ b/src/ipahealthcheck/core/output.py
d97ed2
@@ -110,7 +110,7 @@ class JSON(Output):
d97ed2
 
d97ed2
     def __init__(self, options):
d97ed2
         super(JSON, self).__init__(options)
d97ed2
-        self.indent = int(options.indent)
d97ed2
+        self.indent = options.indent
d97ed2
 
d97ed2
     def generate(self, data):
d97ed2
         output = json.dumps(data, indent=self.indent)
d97ed2
diff --git a/tests/test_config.py b/tests/test_config.py
d97ed2
index 09c2188..655233c 100644
d97ed2
--- a/tests/test_config.py
d97ed2
+++ b/tests/test_config.py
d97ed2
@@ -2,7 +2,7 @@
d97ed2
 # Copyright (C) 2019 FreeIPA Contributors see COPYING for license
d97ed2
 #
d97ed2
 
d97ed2
-from ipahealthcheck.core.config import read_config
d97ed2
+from ipahealthcheck.core.config import read_config, convert_string
d97ed2
 import tempfile
d97ed2
 
d97ed2
 
d97ed2
@@ -60,3 +60,17 @@ def test_config_recursion():
d97ed2
         config._Config__d['_Config__d']
d97ed2
     except KeyError:
d97ed2
         pass
d97ed2
+
d97ed2
+
d97ed2
+def test_convert_string():
d97ed2
+    for value in ("s", "string", "BiggerString"):
d97ed2
+        assert convert_string(value) == value
d97ed2
+
d97ed2
+    for value in ("True", "true", True):
d97ed2
+        assert convert_string(value) is True
d97ed2
+
d97ed2
+    for value in ("False", "false", False):
d97ed2
+        assert convert_string(value) is False
d97ed2
+
d97ed2
+    for value in ("10", "99999", 807):
d97ed2
+        assert convert_string(value) == int(value)
d97ed2
diff --git a/tests/test_options.py b/tests/test_options.py
d97ed2
index da1866f..00cdb7c 100644
d97ed2
--- a/tests/test_options.py
d97ed2
+++ b/tests/test_options.py
d97ed2
@@ -40,6 +40,6 @@ def test_options_merge(mock_parse, mock_run, mock_service):
d97ed2
 
d97ed2
         # verify two valus that have defaults with our overriden values
d97ed2
         assert run.options.output_type == 'human'
d97ed2
-        assert run.options.indent == '5'
d97ed2
+        assert run.options.indent == 5
d97ed2
     finally:
d97ed2
         os.remove(config_path)
d97ed2
-- 
d97ed2
2.31.1
d97ed2