Validate user inputs in settings daemon

Currently, the properties defined under /org/openbmc/settings/host0 can take any
value and it is upto the consumer daemons to act only if the valid data was
given.

This patch will provide a validation logic for all the properties and will raise
an exception in the case of invalid inputs. Only on a valid input, will the data
gets written and saved.

Validation methods and types per property are provided in the configuration file
and used by the manager whenever a particular property is changed.

Change-Id: I0731ce6e00ab3cb4e11deb98c03fda8d5adad913
Signed-off-by: Vishwanatha Subbanna <vishwa@linux.vnet.ibm.com>
diff --git a/settings_manager.py b/settings_manager.py
index 864ee9d..997c56f 100644
--- a/settings_manager.py
+++ b/settings_manager.py
@@ -8,10 +8,12 @@
 import os.path as path
 import sys
 from obmc.dbuslib.bindings import DbusProperties, get_dbus
+from IPy import IP
 
 settings_file_path = os.path.join(sys.prefix, 'share/obmc-phosphor-settings')
 sys.path.insert(1, settings_file_path)
 import settings_file as s
+import re
 
 DBUS_NAME = 'org.openbmc.settings.Host'
 OBJ_NAME = '/org/openbmc/settings/host0'
@@ -20,10 +22,14 @@
 
 class HostSettingsObject(DbusProperties):
     def __init__(self, bus, name, settings, path):
-        DbusProperties.__init__(self)
-        dbus.service.Object.__init__(self, bus, name)
-
+        super(HostSettingsObject, self).__init__(conn=bus, object_path=name,
+                                                validator=self.input_validator)
+        self.bus = bus
         self.path = path
+        # Needed to ignore the validation on default networkconfig values as
+        # opposed to user giving the same.
+        self.adminmode = True
+
         if not os.path.exists(path):
             os.mkdir(path)
 
@@ -35,11 +41,13 @@
             path="/org/openbmc/settings/host0")
 
         # Create the dbus properties
-        for i in settings['host'].iterkeys():
-            shk = settings['host'][i]
+        for i in settings[DBUS_NAME].iterkeys():
+            shk = settings[DBUS_NAME][i]
             self.set_settings_property(shk['name'],
                                        shk['type'],
                                        shk['default'])
+        # Done with consuming factory settings.
+        self.adminmode = False
 
     def get_bmc_value(self, name):
         try:
@@ -58,11 +66,11 @@
         if bmcv:
             value = bmcv
         if type == "i":
-            self.Set(DBUS_NAME, name, value)
+            self.Set(DBUS_NAME, name, int(value))
         elif type == "s":
             self.Set(DBUS_NAME, name, str(value))
         elif type == "b":
-            self.Set(DBUS_NAME, name, value)
+            self.Set(DBUS_NAME, name, bool(value))
 
     # Save the settings to the BMC. This will write the settings value in
     # individual files named by the property name to the BMC.
@@ -85,6 +93,85 @@
     def SettingsUpdated(self, sname):
         pass
 
+    def validate_regex(self, regex, value):
+        if not re.compile(regex).search(value):
+            raise ValueError, "Invalid input. Data does not satisfy regex"
+
+    def validate_range(self, min, max, value):
+        if value not in range(min, max):
+            raise ValueError, "Invalid input. Data not in allowed range"
+
+    def validate_list_ignore_case(self, lst, value):
+        if value.lower() not in map(lambda val: val.lower(), lst):
+            raise ValueError, "Invalid input. Data not in allowed values"
+
+    # validate host network configuration
+    # need  "ipaddress=,prefix=,gateway=,mac=,addr_type="
+    # Must be able to handle any order
+    def validate_net_config(self, value):
+        if self.adminmode:
+            return
+
+        # Need all of these to be given by the user.
+        user_config = []
+        all_config = ['ipaddress', 'prefix', 'gateway', 'mac', 'addr_type']
+
+        # This has a hard data format mentioned above so no blanks allowed.
+        if value.count(" ") or value.count("=") != 5:
+            raise ValueError, "Invalid Network Data. No white spaces allowed"
+
+        config = value.split(',')
+        for key_value in config:
+            key , value = key_value.split('=')
+            if not key or not value:
+                raise ValueError, "Invalid key or Data"
+
+            # Add the current key seen so we can compare at the end to see
+            # if all values have been given
+            user_config.append(key.lower())
+
+            if key.lower() == 'ipaddress' or key.lower() == 'gateway':
+	        IP(value)
+
+            elif key.lower() == 'mac':
+                regex = '([a-fA-F0-9]{2}[:|\-]?){6}'
+                self.validate_regex(regex, value)
+
+            elif key.lower() == 'prefix':
+                self.validate_range(0, 33, int(value))
+
+            elif key.lower() == 'addr_type':
+                allowed = ["STATIC", "DYNAMIC"]
+                self.validate_list_ignore_case(allowed, value)
+
+        # Did user pass everything ??
+        if set(all_config) - set(user_config):
+            raise ValueError, "Invalid Network Data. All information is mandatory"
+
+    # Validate to see if the changes are in order
+    def input_validator(self, iface, proprty, value):
+        settings = s.SETTINGS
+        shk = {}
+        for key in settings[iface].iterkeys():
+            if proprty == settings[iface][key]['name']:
+                shk = settings[iface][key]
+                break
+
+        # User entered key is not present
+        if not shk: raise KeyError, "Invalid Property"
+
+        if shk['validation'] == 'list':
+            self.validate_list_ignore_case(shk['allowed'], value)
+
+        elif shk['validation'] == 'range':
+            self.validate_range(shk['min'], shk['max']+1, value)
+
+        elif shk['validation'] == 'regex':
+            self.validate_regex(shk['regex'], value)
+
+        elif shk['validation'] == 'custom':
+	    getattr(self, shk['method'])(value)
+
 if __name__ == '__main__':
     dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)